looking-glass-B6/000077500000000000000000000000001434445012300140745ustar00rootroot00000000000000looking-glass-B6/AUTHORS000066400000000000000000000060641434445012300151520ustar00rootroot00000000000000The following authors have licensed all their contributions to the Looking Glass project under the terms detailed in LICENSE: Geoffrey McRae (gnif) Guanzhong Chen (quantum5) Tudor Brindus (Xyene) Jonathan Rubenstein (JJRcop) arcnmx (arcnmx) TheCakeIsNaOH (TheCakeIsNaOH) NamoDev (NamoDev) feltcat <58396817+feltcat@users.noreply.github.com> (feltcat) Ali Abdel-Qader (thrifty-txt) Jack Karamanian Mikko Rasa (DataBeaver) Omar Pakker (Omar007) Yvan da Silva (YvanDaSilva) r4m0n (r4m0n) Łukasz Kostka A.J. Ruckman (ajruckman) Aaron (mcd1992) Alam Arias (alama) Alexander Olofsson (ananace) Andrew Sheldon (aqxa1) Andy Chun (noneucat) Arti Zirk Ash <5615358+ash-hat@users.noreply.github.com> (ash-hat) Dominik Csapak (flumm) Frediano Ziglio Luke Brown (Dynamic-Gravity) Marius Barbu (mbarbu) Max Sistemich (mafrasi2) Michael Golisch (mgolisch) Michał Zając Netboy3 <1472804+netboy3@users.noreply.github.com> (netboy3) Patrick Steinhardt Paul Götzinger (pagdot) Rikard Falkeborn (rikardfalkeborn) Rokas Kupstys (rokups) Samuel Bowman (SamuelBowman) Txanton (tbejos) Tyler Watson Xiretza (Xiretza) aspen (aspenluxxxy) camr0 (camr0) chrsm (chrsm) commander kotori (cmdrkotori) eater <=@eater.me> (the-eater) fishery (fisherwise) four0four (four0four) jmossman (jmossman) jonpas (jonpas) orcephrye (orcephrye) thejavascriptman (thejavascriptman) vroad <396351+vroad@users.noreply.github.com> (vroad) williamvds (williamvds) SytheZN (SytheZN) RTXUX (RTXUX) Vincent LaRocca (VMFortress) Johnathon Paul Weaver (8BallBomBom) Chris Spencer (spencercw) Mark Boorer (Shootfast) babbaj (Babbaj) Matthew McMullin (matthewjmc) Leonard Fricke (Leo1998) David Meier (Kenny.ch) Daniel Cordero (0xdc) esi (esibun) looking-glass-B6/CONTRIBUTORS000066400000000000000000000023731434445012300157610ustar00rootroot00000000000000Level1Techs (level1techs.com) Provided a motherboard and CPU cooler and publicizing this project. Sapphire Technologies (www.sapphiretech.com) Provided an AMD Vega 56 PLE Australia (ple.com.au) Provided hardware at cost pricing Members of the community that donated the funding to obtain the remaining hardware. Tom Kopra Josh Strawbridge mercipher _ Jiri Belsky Brad Anstis Marcin Skarbek Fabian Tschopp Dolf Andringa Maxime Poulin Mike D always FlOoReD Matt Wirth Justin Phillips Jan Killius Tyler Watson Viesturs Kols Cooper YANG Srdjan Rosic Matthew Fisk Tyll Jungke Kimmo Huoso Oliver Masur Mark Tomlin Jason Simon Nicolas Avrutin Alex Karypidis Niklas Bloedorn Bruno Z Dennis Keitzel Martin Eisig August Örnhall Timothy Viola David Reddick david gerdeman Christopher Swenson Benjamin Klettbach Chadd Bland Peder Madsen Zachary Piper Declan Scott Thomas A Neal Stein Luke Brown Andy Bright Kyle Farwell Jai Mu Martin Lilleøen Daniel hauger Michael Hillman Andreas Jacobsen NikkyAi Michael Lindman And another 41 people that wish to remain anonymous. Thank you everyone for making this project possible. - Geoffrey McRae Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. looking-glass-B6/README.md000066400000000000000000000013151434445012300153530ustar00rootroot00000000000000# Looking Glass An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with VGA PCI Passthrough. * Project Website: https://looking-glass.io * Documentation: https://looking-glass.io/docs ## Documentation ❕❕❕ **IMPORTANT** ❕❕❕ This project contains submodules that must be checked out if building from the git repository! If you are not a developer and just want to compile Looking Glass, please download the source archive from the website instead: https://looking-glass.io/downloads Source code for the documentation can be found in the `/doc` directory. You may view this locally as HTML by running `make html` with `python3-sphinx` and `python3-sphinx-rtd-theme` installed. looking-glass-B6/client/000077500000000000000000000000001434445012300153525ustar00rootroot00000000000000looking-glass-B6/client/CMakeLists.txt000066400000000000000000000123071434445012300201150ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(looking-glass-client C CXX) get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE) if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) message(FATAL_ERROR "\n" "In-source builds are not supported\n" "See build instructions provided in: " "${PROJECT_TOP}/doc/build.rst\n" "Refusing to continue" ) endif() list(APPEND CMAKE_MODULE_PATH "${PROJECT_TOP}/cmake/" "${PROJECT_SOURCE_DIR}/cmake/") include(CheckSubmodule) include(GNUInstallDirs) include(CheckCCompilerFlag) include(FeatureSummary) set(OPTIMIZE_FOR_NATIVE_DEFAULT ON) include(OptimizeForNative) # option(OPTIMIZE_FOR_NATIVE) include(UninstallTarget) find_package(PkgConfig) pkg_check_modules(FONTCONFIG REQUIRED IMPORTED_TARGET fontconfig) option(ENABLE_OPENGL "Enable the OpenGL renderer" ON) add_feature_info(ENABLE_OPENGL ENABLE_OPENGL "Legacy OpenGL renderer.") option(ENABLE_EGL "Enable the EGL renderer" ON) add_feature_info(ENABLE_EGL ENABLE_EGL "EGL renderer.") option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON) add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.") option(ENABLE_ASAN "Build with AddressSanitizer" OFF) add_feature_info(ENABLE_ASAN ENABLE_ASAN "AddressSanitizer support.") option(ENABLE_UBSAN "Build with UndefinedBehaviorSanitizer" OFF) add_feature_info(ENABLE_UBSAN ENABLE_UBSAN "UndefinedBehaviorSanitizer support.") option(ENABLE_X11 "Build with X11 support" ON) add_feature_info(ENABLE_X11 ENABLE_X11 "X11 support.") option(ENABLE_WAYLAND "Build with Wayland support" ON) add_feature_info(ENABLE_WAYLAND ENABLE_WAYLAND "Wayland support.") option(ENABLE_LIBDECOR "Build with libdecor support" OFF) add_feature_info(ENABLE_LIBDECOR ENABLE_LIBDECOR "libdecor support.") option(ENABLE_PIPEWIRE "Build with PipeWire audio output support" ON) add_feature_info(ENABLE_PIPEWIRE ENABLE_PIPEWIRE "PipeWire audio support.") option(ENABLE_PULSEAUDIO "Build with PulseAudio audio output support" ON) add_feature_info(ENABLE_PULSEAUDIO ENABLE_PULSEAUDIO "PulseAudio audio support.") if (NOT ENABLE_X11 AND NOT ENABLE_WAYLAND) message(FATAL_ERROR "Either ENABLE_X11 or ENABLE_WAYLAND must be on") endif() add_compile_options( "-Wall" "-Wextra" "-Wno-sign-compare" "-Wno-unused-parameter" "$<$:-Wstrict-prototypes>" "$<$:-Wimplicit-fallthrough=2>" "-Werror" "-Wfatal-errors" "-ffast-math" "-fdata-sections" "-ffunction-sections" "$<$:-O0;-g3;-ggdb>" ) set(EXE_FLAGS "-Wl,--gc-sections -z noexecstack") set(CMAKE_C_STANDARD 11) if (ENABLE_OPENGL) add_definitions(-D ENABLE_OPENGL) endif() if (ENABLE_EGL) add_definitions(-D ENABLE_EGL) endif() if(ENABLE_ASAN) add_compile_options("-fno-omit-frame-pointer" "-fsanitize=address") set(EXE_FLAGS "${EXE_FLAGS} -fno-omit-frame-pointer -fsanitize=address") endif() if(ENABLE_UBSAN) add_compile_options("-fsanitize=undefined") set(EXE_FLAGS "${EXE_FLAGS} -fsanitize=undefined") endif() add_definitions(-D ATOMIC_LOCKING) add_definitions(-D GL_GLEXT_PROTOTYPES) add_custom_command( OUTPUT ${CMAKE_BINARY_DIR}/version.c ${CMAKE_BINARY_DIR}/_version.c COMMAND ${CMAKE_COMMAND} -D PROJECT_TOP=${PROJECT_TOP} -P ${PROJECT_TOP}/version.cmake ) include_directories( ${PROJECT_TOP} ${PROJECT_SOURCE_DIR}/include ${CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR}/include ${PROJECT_TOP}/repos/nanosvg/src ) link_libraries( ${CMAKE_DL_LIBS} rt m ) set(SOURCES ${CMAKE_BINARY_DIR}/version.c src/main.c src/core.c src/app.c src/audio.c src/config.c src/keybind.c src/util.c src/clipboard.c src/kb.c src/gl_dynprocs.c src/egl_dynprocs.c src/eglutil.c src/overlay_utils.c src/render_queue.c src/overlay/splash.c src/overlay/alert.c src/overlay/fps.c src/overlay/graphs.c src/overlay/help.c src/overlay/config.c src/overlay/msg.c src/overlay/status.c ) # Force cimgui to build as a static library. set(IMGUI_STATIC "yes" CACHE STRING "Build as a static library") add_subdirectory("${PROJECT_TOP}/resources" "${CMAKE_BINARY_DIR}/resources") add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common" ) add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/LGMP" ) add_subdirectory("${PROJECT_TOP}/repos/PureSpice" "${CMAKE_BINARY_DIR}/PureSpice") add_subdirectory("${PROJECT_TOP}/repos/cimgui" "${CMAKE_BINARY_DIR}/cimgui" EXCLUDE_FROM_ALL) add_subdirectory(displayservers) add_subdirectory(renderers) add_executable(looking-glass-client ${SOURCES}) target_compile_definitions(looking-glass-client PRIVATE CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1) target_link_libraries(looking-glass-client ${EXE_FLAGS} PkgConfig::FONTCONFIG lg_resources lg_common displayservers lgmp purespice renderers cimgui ) if (ENABLE_PIPEWIRE OR ENABLE_PULSEAUDIO) add_definitions(-D ENABLE_AUDIO) add_subdirectory(audiodevs) pkg_check_modules(SAMPLERATE REQUIRED IMPORTED_TARGET samplerate) target_link_libraries(looking-glass-client PkgConfig::SAMPLERATE audiodevs ) endif() install(TARGETS looking-glass-client RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT binary) feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES) looking-glass-B6/client/DEBUGGING.md000066400000000000000000000020471434445012300171720ustar00rootroot00000000000000# Debugging the Looking Glass Client If you are asked to provide debugging information to resolve an issue please follow the following procedure. ## If you're experiencing a crash: Run the program under the `gdb` debugger (you may need to install gdb), for example: gdb ./looking-glass-client If you need to set any arguments, do so now by running `set args ARGS`, for example: set args -F -k Now start the program by typing `r`. When the application crashes you will be dumped back into the debugger, the application may appear to be frozen. Run the following command: thread apply all bt Once you have this information please pastebin the log from looking-glass as well as the information resulting from this command. ## If you're experencing high CPU load and/or poor performance. The steps here are identical to the above, except instead of waiting for the program to crash, in the debugger press `CTRL+C` while the program is exhibiting the problem, then run `thread apply all bt` and pastebin your log and the results of the command. looking-glass-B6/client/audiodevs/000077500000000000000000000000001434445012300173355ustar00rootroot00000000000000looking-glass-B6/client/audiodevs/CMakeLists.txt000066400000000000000000000026671434445012300221100ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(audiodevs LANGUAGES C) set(AUDIODEV_H "${CMAKE_BINARY_DIR}/include/dynamic/audiodev.h") set(AUDIODEV_C "${CMAKE_BINARY_DIR}/src/audiodev.c") file(WRITE ${AUDIODEV_H} "#include \"interface/audiodev.h\"\n\n") file(APPEND ${AUDIODEV_H} "extern struct LG_AudioDevOps * LG_AudioDevs[];\n\n") file(WRITE ${AUDIODEV_C} "#include \"interface/audiodev.h\"\n\n") file(APPEND ${AUDIODEV_C} "#include \n\n") set(AUDIODEVS "_") set(AUDIODEVS_LINK "_") function(add_audiodev name) set(AUDIODEVS "${AUDIODEVS};${name}" PARENT_SCOPE) set(AUDIODEVS_LINK "${AUDIODEVS_LINK};audiodev_${name}" PARENT_SCOPE) add_subdirectory(${name}) endfunction() # Add/remove audiodevs here! if(ENABLE_PIPEWIRE) add_audiodev(PipeWire) endif() if(ENABLE_PULSEAUDIO) add_audiodev(PulseAudio) endif() list(REMOVE_AT AUDIODEVS 0) list(REMOVE_AT AUDIODEVS_LINK 0) list(LENGTH AUDIODEVS AUDIODEV_COUNT) file(APPEND ${AUDIODEV_H} "#define LG_AUDIODEV_COUNT ${AUDIODEV_COUNT}\n") foreach(audiodev ${AUDIODEVS}) file(APPEND ${AUDIODEV_C} "extern struct LG_AudioDevOps LGAD_${audiodev};\n") endforeach() file(APPEND ${AUDIODEV_C} "\nconst struct LG_AudioDevOps * LG_AudioDevs[] =\n{\n") foreach(audiodev ${AUDIODEVS}) file(APPEND ${AUDIODEV_C} " &LGAD_${audiodev},\n") endforeach() file(APPEND ${AUDIODEV_C} " NULL\n};") add_library(audiodevs STATIC ${AUDIODEV_C}) target_link_libraries(audiodevs ${AUDIODEVS_LINK}) looking-glass-B6/client/audiodevs/PipeWire/000077500000000000000000000000001434445012300210615ustar00rootroot00000000000000looking-glass-B6/client/audiodevs/PipeWire/CMakeLists.txt000066400000000000000000000006021434445012300236170ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(audiodev_PipeWire LANGUAGES C) find_package(PkgConfig) pkg_check_modules(AUDIODEV_PipeWire REQUIRED IMPORTED_TARGET libpipewire-0.3 ) add_library(audiodev_PipeWire STATIC pipewire.c ) target_link_libraries(audiodev_PipeWire PkgConfig::AUDIODEV_PipeWire lg_common ) target_include_directories(audiodev_PipeWire PRIVATE src ) looking-glass-B6/client/audiodevs/PipeWire/pipewire.c000066400000000000000000000337221434445012300230600ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/audiodev.h" #include #include #include #include #include "common/debug.h" #include "common/stringutils.h" #include "common/util.h" typedef enum { STREAM_STATE_INACTIVE, STREAM_STATE_ACTIVE, STREAM_STATE_DRAINING } StreamState; struct PipeWire { struct pw_loop * loop; struct pw_context * context; struct pw_thread_loop * thread; struct { struct pw_stream * stream; struct spa_io_rate_match * rateMatch; int channels; int sampleRate; int stride; LG_AudioPullFn pullFn; int maxPeriodFrames; int startFrames; StreamState state; } playback; struct { struct pw_stream * stream; int channels; int sampleRate; int stride; LG_AudioPushFn pushFn; bool active; } record; }; static struct PipeWire pw = {0}; static void pipewire_onPlaybackIoChanged(void * userdata, uint32_t id, void * data, uint32_t size) { switch (id) { case SPA_IO_RateMatch: pw.playback.rateMatch = data; break; } } static void pipewire_onPlaybackProcess(void * userdata) { struct pw_buffer * pbuf; if (!(pbuf = pw_stream_dequeue_buffer(pw.playback.stream))) { DEBUG_WARN("out of buffers"); return; } struct spa_buffer * sbuf = pbuf->buffer; uint8_t * dst; if (!(dst = sbuf->datas[0].data)) return; int frames = sbuf->datas[0].maxsize / pw.playback.stride; if (pw.playback.rateMatch && pw.playback.rateMatch->size > 0) frames = min(frames, pw.playback.rateMatch->size); frames = pw.playback.pullFn(dst, frames); if (!frames) { sbuf->datas[0].chunk->size = 0; pw_stream_queue_buffer(pw.playback.stream, pbuf); return; } sbuf->datas[0].chunk->offset = 0; sbuf->datas[0].chunk->stride = pw.playback.stride; sbuf->datas[0].chunk->size = frames * pw.playback.stride; pw_stream_queue_buffer(pw.playback.stream, pbuf); } static void pipewire_onPlaybackDrained(void * userdata) { pw_thread_loop_lock(pw.thread); pw_stream_set_active(pw.playback.stream, false); pw.playback.state = STREAM_STATE_INACTIVE; pw_thread_loop_unlock(pw.thread); } static bool pipewire_init(void) { pw_init(NULL, NULL); pw.loop = pw_loop_new(NULL); pw.context = pw_context_new( pw.loop, pw_properties_new( // Request real-time priority on the PipeWire threads PW_KEY_CONFIG_NAME, "client-rt.conf", NULL ), 0); if (!pw.context) { DEBUG_ERROR("Failed to create a context"); goto err; } /* this is just to test for PipeWire availabillity */ struct pw_core * core = pw_context_connect(pw.context, NULL, 0); if (!core) goto err_context; /* PipeWire is available so create the loop thread and start it */ pw.thread = pw_thread_loop_new_full(pw.loop, "PipeWire", NULL); if (!pw.thread) { DEBUG_ERROR("Failed to create the thread loop"); goto err_context; } pw_thread_loop_start(pw.thread); return true; err_context: pw_context_destroy(pw.context); err: pw_loop_destroy(pw.loop); pw_deinit(); return false; } static void pipewire_playbackStopStream(void) { if (!pw.playback.stream) return; pw_thread_loop_lock(pw.thread); pw_stream_destroy(pw.playback.stream); pw.playback.stream = NULL; pw.playback.rateMatch = NULL; pw_thread_loop_unlock(pw.thread); } static void pipewire_playbackSetup(int channels, int sampleRate, int requestedPeriodFrames, int * maxPeriodFrames, int * startFrames, LG_AudioPullFn pullFn) { const struct spa_pod * params[1]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); static const struct pw_stream_events events = { .version = PW_VERSION_STREAM_EVENTS, .io_changed = pipewire_onPlaybackIoChanged, .process = pipewire_onPlaybackProcess, .drained = pipewire_onPlaybackDrained }; if (pw.playback.stream && pw.playback.channels == channels && pw.playback.sampleRate == sampleRate) { *maxPeriodFrames = pw.playback.maxPeriodFrames; *startFrames = pw.playback.startFrames; return; } pipewire_playbackStopStream(); char requestedNodeLatency[32]; snprintf(requestedNodeLatency, sizeof(requestedNodeLatency), "%d/%d", requestedPeriodFrames, sampleRate); pw.playback.channels = channels; pw.playback.sampleRate = sampleRate; pw.playback.stride = sizeof(float) * channels; pw.playback.pullFn = pullFn; pw_thread_loop_lock(pw.thread); pw.playback.stream = pw_stream_new_simple( pw.loop, "Looking Glass", pw_properties_new( PW_KEY_NODE_NAME , "Looking Glass", PW_KEY_MEDIA_TYPE , "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", PW_KEY_MEDIA_ROLE , "Music", PW_KEY_NODE_LATENCY , requestedNodeLatency, NULL ), &events, NULL ); // The user can override the default node latency with the PIPEWIRE_LATENCY // environment variable, so get the actual node latency value from the stream. // The actual quantum size may be lower than this value depending on what else // is using the audio device, but we can treat this value as a maximum const struct pw_properties * properties = pw_stream_get_properties(pw.playback.stream); const char *actualNodeLatency = pw_properties_get(properties, PW_KEY_NODE_LATENCY); DEBUG_ASSERT(actualNodeLatency != NULL); unsigned num, denom; if (sscanf(actualNodeLatency, "%u/%u", &num, &denom) != 2 || denom != sampleRate) { DEBUG_WARN( "PIPEWIRE_LATENCY value '%s' is invalid or does not match stream sample " "rate; using %d/%d", actualNodeLatency, requestedPeriodFrames, sampleRate); struct spa_dict_item items[] = { { PW_KEY_NODE_LATENCY, requestedNodeLatency } }; pw_stream_update_properties(pw.playback.stream, &SPA_DICT_INIT_ARRAY(items)); pw.playback.maxPeriodFrames = requestedPeriodFrames; } else pw.playback.maxPeriodFrames = num; // If the previous quantum size was very small, PipeWire can request two full // periods almost immediately at the start of playback pw.playback.startFrames = pw.playback.maxPeriodFrames * 2; *maxPeriodFrames = pw.playback.maxPeriodFrames; *startFrames = pw.playback.startFrames; if (!pw.playback.stream) { pw_thread_loop_unlock(pw.thread); DEBUG_ERROR("Failed to create the stream"); return; } params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .channels = channels, .rate = sampleRate )); pw_stream_connect( pw.playback.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS | PW_STREAM_FLAG_INACTIVE, params, 1); pw_thread_loop_unlock(pw.thread); } static void pipewire_playbackStart(void) { if (!pw.playback.stream) return; if (pw.playback.state != STREAM_STATE_ACTIVE) { pw_thread_loop_lock(pw.thread); switch (pw.playback.state) { case STREAM_STATE_INACTIVE: pw_stream_set_active(pw.playback.stream, true); pw.playback.state = STREAM_STATE_ACTIVE; break; case STREAM_STATE_DRAINING: // We are in the middle of draining the PipeWire buffers; we need to // wait for this to complete before allowing the new playback to start break; default: DEBUG_UNREACHABLE(); } pw_thread_loop_unlock(pw.thread); } } static void pipewire_playbackStop(void) { if (pw.playback.state != STREAM_STATE_ACTIVE) return; pw_thread_loop_lock(pw.thread); pw_stream_flush(pw.playback.stream, true); pw.playback.state = STREAM_STATE_DRAINING; pw_thread_loop_unlock(pw.thread); } static void pipewire_playbackVolume(int channels, const uint16_t volume[]) { if (channels != pw.playback.channels) return; float param[channels]; for(int i = 0; i < channels; ++i) param[i] = 9.3234e-7 * pow(1.000211902, volume[i]) - 0.000172787; pw_thread_loop_lock(pw.thread); pw_stream_set_control(pw.playback.stream, SPA_PROP_channelVolumes, channels, param, 0); pw_thread_loop_unlock(pw.thread); } static void pipewire_playbackMute(bool mute) { pw_thread_loop_lock(pw.thread); float val = mute ? 1.0f : 0.0f; pw_stream_set_control(pw.playback.stream, SPA_PROP_mute, 1, &val, 0); pw_thread_loop_unlock(pw.thread); } static size_t pipewire_playbackLatency(void) { struct pw_time time = { 0 }; pw_thread_loop_lock(pw.thread); #if PW_CHECK_VERSION(0, 3, 50) if (pw_stream_get_time_n(pw.playback.stream, &time, sizeof(time)) < 0) #else if (pw_stream_get_time(pw.playback.stream, &time) < 0) #endif DEBUG_ERROR("pw_stream_get_time failed"); pw_thread_loop_unlock(pw.thread); return time.delay + time.queued / pw.playback.stride; } static void pipewire_recordStopStream(void) { if (!pw.record.stream) return; pw_thread_loop_lock(pw.thread); pw_stream_destroy(pw.record.stream); pw.record.stream = NULL; pw_thread_loop_unlock(pw.thread); } static void pipewire_onRecordProcess(void * userdata) { struct pw_buffer * pbuf; if (!(pbuf = pw_stream_dequeue_buffer(pw.record.stream))) { DEBUG_WARN("out of buffers"); return; } struct spa_buffer * sbuf = pbuf->buffer; uint8_t * dst; if (!(dst = sbuf->datas[0].data)) return; dst += sbuf->datas[0].chunk->offset; pw.record.pushFn(dst, min( sbuf->datas[0].chunk->size, sbuf->datas[0].maxsize - sbuf->datas[0].chunk->offset) / pw.record.stride ); pw_stream_queue_buffer(pw.record.stream, pbuf); } static void pipewire_recordStart(int channels, int sampleRate, LG_AudioPushFn pushFn) { const struct spa_pod * params[1]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); static const struct pw_stream_events events = { .version = PW_VERSION_STREAM_EVENTS, .process = pipewire_onRecordProcess }; if (pw.record.stream && pw.record.channels == channels && pw.record.sampleRate == sampleRate) { if (!pw.record.active) { pw_thread_loop_lock(pw.thread); pw_stream_set_active(pw.record.stream, true); pw.record.active = true; pw_thread_loop_unlock(pw.thread); } return; } pipewire_recordStopStream(); pw.record.channels = channels; pw.record.sampleRate = sampleRate; pw.record.stride = sizeof(uint16_t) * channels; pw.record.pushFn = pushFn; pw_thread_loop_lock(pw.thread); pw.record.stream = pw_stream_new_simple( pw.loop, "Looking Glass", pw_properties_new( PW_KEY_NODE_NAME , "Looking Glass", PW_KEY_MEDIA_TYPE , "Audio", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE , "Music", NULL ), &events, NULL ); if (!pw.record.stream) { pw_thread_loop_unlock(pw.thread); DEBUG_ERROR("Failed to create the stream"); return; } params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_S16, .channels = channels, .rate = sampleRate )); pw_stream_connect( pw.record.stream, PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, 1); pw_thread_loop_unlock(pw.thread); pw.record.active = true; } static void pipewire_recordStop(void) { if (!pw.record.active) return; pw_thread_loop_lock(pw.thread); pw_stream_set_active(pw.record.stream, false); pw.record.active = false; pw_thread_loop_unlock(pw.thread); } static void pipewire_recordVolume(int channels, const uint16_t volume[]) { if (channels != pw.record.channels) return; float param[channels]; for(int i = 0; i < channels; ++i) param[i] = 9.3234e-7 * pow(1.000211902, volume[i]) - 0.000172787; pw_thread_loop_lock(pw.thread); pw_stream_set_control(pw.record.stream, SPA_PROP_channelVolumes, channels, param, 0); pw_thread_loop_unlock(pw.thread); } static void pipewire_recordMute(bool mute) { pw_thread_loop_lock(pw.thread); float val = mute ? 1.0f : 0.0f; pw_stream_set_control(pw.record.stream, SPA_PROP_mute, 1, &val, 0); pw_thread_loop_unlock(pw.thread); } static void pipewire_free(void) { pipewire_playbackStopStream(); pipewire_recordStopStream(); pw_thread_loop_stop(pw.thread); pw_thread_loop_destroy(pw.thread); pw_context_destroy(pw.context); pw_loop_destroy(pw.loop); pw.loop = NULL; pw.context = NULL; pw.thread = NULL; pw_deinit(); } struct LG_AudioDevOps LGAD_PipeWire = { .name = "PipeWire", .init = pipewire_init, .free = pipewire_free, .playback = { .setup = pipewire_playbackSetup, .start = pipewire_playbackStart, .stop = pipewire_playbackStop, .volume = pipewire_playbackVolume, .mute = pipewire_playbackMute, .latency = pipewire_playbackLatency }, .record = { .start = pipewire_recordStart, .stop = pipewire_recordStop, .volume = pipewire_recordVolume, .mute = pipewire_recordMute } }; looking-glass-B6/client/audiodevs/PulseAudio/000077500000000000000000000000001434445012300214075ustar00rootroot00000000000000looking-glass-B6/client/audiodevs/PulseAudio/CMakeLists.txt000066400000000000000000000006111434445012300241450ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(audiodev_PulseAudio LANGUAGES C) find_package(PkgConfig) pkg_check_modules(AUDIODEV_PulseAudio REQUIRED IMPORTED_TARGET libpulse ) add_library(audiodev_PulseAudio STATIC pulseaudio.c ) target_link_libraries(audiodev_PulseAudio PkgConfig::AUDIODEV_PulseAudio lg_common ) target_include_directories(audiodev_PulseAudio PRIVATE src ) looking-glass-B6/client/audiodevs/PulseAudio/pulseaudio.c000066400000000000000000000233421434445012300237310ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/audiodev.h" #include #include #include #include "common/debug.h" struct PulseAudio { pa_threaded_mainloop * loop; pa_mainloop_api * api; pa_context * context; pa_operation * contextSub; pa_stream * sink; int sinkIndex; bool sinkCorked; bool sinkMuted; bool sinkStarting; int sinkMaxPeriodFrames; int sinkStartFrames; int sinkSampleRate; int sinkChannels; int sinkStride; LG_AudioPullFn sinkPullFn; }; static struct PulseAudio pa = {0}; static void pulseaudio_sink_input_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { if (eol < 0 || eol == 1) return; pa.sinkIndex = i->index; } static void pulseaudio_subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata) { switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { case PA_SUBSCRIPTION_EVENT_SINK_INPUT: if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) pa.sinkIndex = 0; else { pa_operation *o = pa_context_get_sink_input_info(c, index, pulseaudio_sink_input_cb, NULL); pa_operation_unref(o); } break; } } static void pulseaudio_ctx_state_change_cb(pa_context * c, void * userdata) { switch (pa_context_get_state(c)) { case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; case PA_CONTEXT_READY: DEBUG_INFO("Connected to PulseAudio server"); pa_context_set_subscribe_callback(c, pulseaudio_subscribe_cb, NULL); pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL); pa_threaded_mainloop_signal(pa.loop, 0); break; case PA_CONTEXT_TERMINATED: if (pa.contextSub) { pa_operation_unref(pa.contextSub); pa.contextSub = NULL; } break; case PA_CONTEXT_FAILED: default: DEBUG_ERROR("context error: %s", pa_strerror(pa_context_errno(c))); break; } } static bool pulseaudio_init(void) { pa.loop = pa_threaded_mainloop_new(); if (!pa.loop) { DEBUG_ERROR("Failed to create the main loop"); goto err; } pa.api = pa_threaded_mainloop_get_api(pa.loop); if (pa_signal_init(pa.api) != 0) { DEBUG_ERROR("Failed to init signals"); goto err_loop; } if (pa_threaded_mainloop_start(pa.loop) < 0) { DEBUG_ERROR("Failed to start the main loop"); goto err_loop; } pa_proplist * propList = pa_proplist_new(); if (!propList) { DEBUG_ERROR("Failed to create the proplist"); goto err_thread; } pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, "video"); pa_threaded_mainloop_lock(pa.loop); pa.context = pa_context_new_with_proplist( pa.api, "Looking Glass", propList); if (!pa.context) { DEBUG_ERROR("Failed to create the context"); goto err_context; } pa_context_set_state_callback(pa.context, pulseaudio_ctx_state_change_cb, NULL); if (pa_context_connect(pa.context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0) { DEBUG_ERROR("Failed to connect to the context server"); goto err_context; } for(;;) { pa_context_state_t state = pa_context_get_state(pa.context); if(!PA_CONTEXT_IS_GOOD(state)) { DEBUG_ERROR("Context is bad"); goto err_context; } if (state == PA_CONTEXT_READY) break; pa_threaded_mainloop_wait(pa.loop); } pa_threaded_mainloop_unlock(pa.loop); pa_proplist_free(propList); return true; err_context: pa_threaded_mainloop_unlock(pa.loop); pa_proplist_free(propList); err_thread: pa_threaded_mainloop_stop(pa.loop); err_loop: pa_threaded_mainloop_free(pa.loop); err: return false; } static void pulseaudio_sink_close_nl(void) { if (!pa.sink) return; pa_stream_set_write_callback(pa.sink, NULL, NULL); pa_stream_flush(pa.sink, NULL, NULL); pa_stream_unref(pa.sink); pa.sink = NULL; } static void pulseaudio_free(void) { pa_threaded_mainloop_lock(pa.loop); pulseaudio_sink_close_nl(); pa_context_set_state_callback(pa.context, NULL, NULL); pa_context_set_subscribe_callback(pa.context, NULL, NULL); pa_context_disconnect(pa.context); pa_context_unref(pa.context); if (pa.contextSub) { pa_operation_unref(pa.contextSub); pa.contextSub = NULL; } pa_threaded_mainloop_unlock(pa.loop); } static void pulseaudio_state_cb(pa_stream * p, void * userdata) { if (pa.sinkStarting && pa_stream_get_state(pa.sink) == PA_STREAM_READY) { pa_stream_cork(pa.sink, 0, NULL, NULL); pa.sinkCorked = false; pa.sinkStarting = false; } } static void pulseaudio_write_cb(pa_stream * p, size_t nbytes, void * userdata) { // PulseAudio tries to pull data from the stream as soon as it is created for // some reason, even though it is corked if (pa.sinkCorked) return; uint8_t * dst; pa_stream_begin_write(p, (void **)&dst, &nbytes); int frames = nbytes / pa.sinkStride; frames = pa.sinkPullFn(dst, frames); pa_stream_write(p, dst, frames * pa.sinkStride, NULL, 0, PA_SEEK_RELATIVE); } static void pulseaudio_underflow_cb(pa_stream * p, void * userdata) { DEBUG_WARN("Underflow"); } static void pulseaudio_overflow_cb(pa_stream * p, void * userdata) { DEBUG_WARN("Overflow"); } static void pulseaudio_setup(int channels, int sampleRate, int requestedPeriodFrames, int * maxPeriodFrames, int * startFrames, LG_AudioPullFn pullFn) { if (pa.sink && pa.sinkChannels == channels && pa.sinkSampleRate == sampleRate) { *maxPeriodFrames = pa.sinkMaxPeriodFrames; *startFrames = pa.sinkStartFrames; return; } pa_sample_spec spec = { .format = PA_SAMPLE_FLOAT32, .rate = sampleRate, .channels = channels }; int stride = channels * sizeof(float); int bufferSize = requestedPeriodFrames * 2 * stride; pa_buffer_attr attribs = { .maxlength = -1, .tlength = bufferSize, .prebuf = 0, .minreq = (uint32_t)-1 }; pa_threaded_mainloop_lock(pa.loop); pulseaudio_sink_close_nl(); pa.sinkChannels = channels; pa.sinkSampleRate = sampleRate; pa.sink = pa_stream_new(pa.context, "Looking Glass", &spec, NULL); pa_stream_set_state_callback (pa.sink, pulseaudio_state_cb , NULL); pa_stream_set_write_callback (pa.sink, pulseaudio_write_cb , NULL); pa_stream_set_underflow_callback(pa.sink, pulseaudio_underflow_cb, NULL); pa_stream_set_overflow_callback (pa.sink, pulseaudio_overflow_cb , NULL); pa_stream_connect_playback(pa.sink, NULL, &attribs, PA_STREAM_START_CORKED, NULL, NULL); pa.sinkStride = stride; pa.sinkPullFn = pullFn; pa.sinkMaxPeriodFrames = requestedPeriodFrames; pa.sinkCorked = true; pa.sinkStarting = false; // If something else is, or was recently using a small latency value, // PulseAudio can request way more data at startup than is reasonable pa.sinkStartFrames = requestedPeriodFrames * 4; *maxPeriodFrames = requestedPeriodFrames; *startFrames = pa.sinkStartFrames; pa_threaded_mainloop_unlock(pa.loop); } static void pulseaudio_start(void) { if (!pa.sink) return; pa_threaded_mainloop_lock(pa.loop); pa_stream_state_t state = pa_stream_get_state(pa.sink); if (state == PA_STREAM_CREATING) pa.sinkStarting = true; else { pa_stream_cork(pa.sink, 0, NULL, NULL); pa.sinkCorked = false; } pa_threaded_mainloop_unlock(pa.loop); } static void pulseaudio_stop(void) { if (!pa.sink) return; bool needLock = !pa_threaded_mainloop_in_thread(pa.loop); if (needLock) pa_threaded_mainloop_lock(pa.loop); pa_stream_cork(pa.sink, 1, NULL, NULL); pa.sinkCorked = true; pa.sinkStarting = false; if (needLock) pa_threaded_mainloop_unlock(pa.loop); } static void pulseaudio_volume(int channels, const uint16_t volume[]) { if (!pa.sink || !pa.sinkIndex) return; struct pa_cvolume v = { .channels = channels }; for(int i = 0; i < channels; ++i) v.values[i] = pa_sw_volume_from_linear( 9.3234e-7 * pow(1.000211902, volume[i]) - 0.000172787); pa_threaded_mainloop_lock(pa.loop); pa_context_set_sink_input_volume(pa.context, pa.sinkIndex, &v, NULL, NULL); pa_threaded_mainloop_unlock(pa.loop); } static void pulseaudio_mute(bool mute) { if (!pa.sink || !pa.sinkIndex || pa.sinkMuted == mute) return; pa.sinkMuted = mute; pa_threaded_mainloop_lock(pa.loop); pa_context_set_sink_input_mute(pa.context, pa.sinkIndex, mute, NULL, NULL); pa_threaded_mainloop_unlock(pa.loop); } struct LG_AudioDevOps LGAD_PulseAudio = { .name = "PulseAudio", .init = pulseaudio_init, .free = pulseaudio_free, .playback = { .setup = pulseaudio_setup, .start = pulseaudio_start, .stop = pulseaudio_stop, .volume = pulseaudio_volume, .mute = pulseaudio_mute } }; looking-glass-B6/client/displayservers/000077500000000000000000000000001434445012300204315ustar00rootroot00000000000000looking-glass-B6/client/displayservers/CMakeLists.txt000066400000000000000000000032341434445012300231730ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(displayservers LANGUAGES C) set(DISPLAYSERVER_H "${CMAKE_BINARY_DIR}/include/dynamic/displayservers.h") set(DISPLAYSERVER_C "${CMAKE_BINARY_DIR}/src/displayservers.c") file(WRITE ${DISPLAYSERVER_H} "#include \"interface/displayserver.h\"\n\n") file(APPEND ${DISPLAYSERVER_H} "extern struct LG_DisplayServerOps * LG_DisplayServers[];\n\n") file(WRITE ${DISPLAYSERVER_C} "#include \"interface/displayserver.h\"\n\n") file(APPEND ${DISPLAYSERVER_C} "#include \n\n") set(DISPLAYSERVERS "_") set(DISPLAYSERVERS_LINK "_") function(add_displayserver name) set(DISPLAYSERVERS "${DISPLAYSERVERS};${name}" PARENT_SCOPE) set(DISPLAYSERVERS_LINK "${DISPLAYSERVERS_LINK};displayserver_${name}" PARENT_SCOPE) add_subdirectory(${name}) endfunction() # Add/remove displayservers here! if (ENABLE_WAYLAND) add_displayserver(Wayland) endif() if (ENABLE_X11) add_displayserver(X11) endif() list(REMOVE_AT DISPLAYSERVERS 0) list(REMOVE_AT DISPLAYSERVERS_LINK 0) list(LENGTH DISPLAYSERVERS DISPLAYSERVER_COUNT) file(APPEND ${DISPLAYSERVER_H} "#define LG_DISPLAYSERVER_COUNT ${DISPLAYSERVER_COUNT}\n") foreach(displayserver ${DISPLAYSERVERS}) file(APPEND ${DISPLAYSERVER_C} "extern struct LG_DisplayServerOps LGDS_${displayserver};\n") endforeach() file(APPEND ${DISPLAYSERVER_C} "\nconst struct LG_DisplayServerOps * LG_DisplayServers[] =\n{\n") foreach(displayserver ${DISPLAYSERVERS}) file(APPEND ${DISPLAYSERVER_C} " &LGDS_${displayserver},\n") endforeach() file(APPEND ${DISPLAYSERVER_C} " NULL\n};") add_library(displayservers STATIC ${DISPLAYSERVER_C}) target_link_libraries(displayservers ${DISPLAYSERVERS_LINK}) looking-glass-B6/client/displayservers/Wayland/000077500000000000000000000000001434445012300220305ustar00rootroot00000000000000looking-glass-B6/client/displayservers/Wayland/CMakeLists.txt000066400000000000000000000072011434445012300245700ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(displayserver_Wayland LANGUAGES C) find_package(PkgConfig) pkg_check_modules(DISPLAYSERVER_Wayland REQUIRED IMPORTED_TARGET wayland-client wayland-cursor xkbcommon ) set(DISPLAYSERVER_Wayland_OPT_PKGCONFIG_LIBRARIES "") set(displayserver_Wayland_SHELL_SRC "") if (ENABLE_LIBDECOR) pkg_check_modules(DISPLAYSERVER_Wayland_LIBDECOR REQUIRED IMPORTED_TARGET libdecor-0 ) list(APPEND DISPLAYSERVER_Wayland_OPT_PKGCONFIG_LIBRARIES PkgConfig::DISPLAYSERVER_Wayland_LIBDECOR) list(APPEND displayserver_Wayland_SHELL_SRC shell_libdecor.c) add_compile_definitions(ENABLE_LIBDECOR) else() list(APPEND displayserver_Wayland_SHELL_SRC shell_xdg.c) endif() add_library(displayserver_Wayland STATIC activation.c clipboard.c cursor.c gl.c idle.c input.c output.c poll.c presentation.c state.c registry.c wayland.c window.c ${displayserver_Wayland_SHELL_SRC} ) target_link_libraries(displayserver_Wayland PkgConfig::DISPLAYSERVER_Wayland ${DISPLAYSERVER_Wayland_OPT_PKGCONFIG_LIBRARIES} lg_common ) target_include_directories(displayserver_Wayland PRIVATE src ) find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner) macro(wayland_generate protocol_file output_file) add_custom_command(OUTPUT "${output_file}.h" COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${protocol_file}" "${output_file}.h" DEPENDS "${protocol_file}" VERBATIM) add_custom_command(OUTPUT "${output_file}.c" COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" private-code "${protocol_file}" "${output_file}.c" DEPENDS "${protocol_file}" VERBATIM) target_sources(displayserver_Wayland PRIVATE "${output_file}.h" "${output_file}.c") endmacro() set(WAYLAND_PROTOCOLS_BASE "${PROJECT_TOP}/repos/wayland-protocols") file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/wayland") include_directories("${CMAKE_BINARY_DIR}/wayland") wayland_generate( "${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml" "${CMAKE_BINARY_DIR}/wayland/wayland-xdg-shell-client-protocol") wayland_generate( "${WAYLAND_PROTOCOLS_BASE}/stable/presentation-time/presentation-time.xml" "${CMAKE_BINARY_DIR}/wayland/wayland-presentation-time-client-protocol") wayland_generate( "${WAYLAND_PROTOCOLS_BASE}/stable/viewporter/viewporter.xml" "${CMAKE_BINARY_DIR}/wayland/wayland-viewporter-client-protocol") wayland_generate( "${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml" "${CMAKE_BINARY_DIR}/wayland/wayland-xdg-decoration-unstable-v1-client-protocol") wayland_generate( "${WAYLAND_PROTOCOLS_BASE}/unstable/relative-pointer/relative-pointer-unstable-v1.xml" "${CMAKE_BINARY_DIR}/wayland/wayland-relative-pointer-unstable-v1-client-protocol") wayland_generate( "${WAYLAND_PROTOCOLS_BASE}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml" "${CMAKE_BINARY_DIR}/wayland/wayland-pointer-constraints-unstable-v1-client-protocol") wayland_generate( "${WAYLAND_PROTOCOLS_BASE}/unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml" "${CMAKE_BINARY_DIR}/wayland/wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol") wayland_generate( "${WAYLAND_PROTOCOLS_BASE}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml" "${CMAKE_BINARY_DIR}/wayland/wayland-idle-inhibit-unstable-v1-client-protocol") wayland_generate( "${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-output/xdg-output-unstable-v1.xml" "${CMAKE_BINARY_DIR}/wayland/wayland-xdg-output-unstable-v1-client-protocol") wayland_generate( "${WAYLAND_PROTOCOLS_BASE}/staging/xdg-activation/xdg-activation-v1.xml" "${CMAKE_BINARY_DIR}/wayland/wayland-xdg-activation-v1-client-protocol") looking-glass-B6/client/displayservers/Wayland/activation.c000066400000000000000000000040561434445012300243420ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "wayland.h" #include #include #include "common/debug.h" bool waylandActivationInit(void) { if (!wlWm.xdgActivation) DEBUG_WARN("xdg_activation_v1 not exported by compositor, will not be able " "to request host focus on behalf of guest applications"); return true; } void waylandActivationFree(void) { if (wlWm.xdgActivation) { xdg_activation_v1_destroy(wlWm.xdgActivation); } } static void activationTokenDone(void * data, struct xdg_activation_token_v1 * xdgToken, const char * token) { xdg_activation_v1_activate(wlWm.xdgActivation, token, wlWm.surface); xdg_activation_token_v1_destroy(xdgToken); } static const struct xdg_activation_token_v1_listener activationTokenListener = { .done = &activationTokenDone, }; void waylandActivationRequestActivation(void) { if (!wlWm.xdgActivation) return; struct xdg_activation_token_v1 * token = xdg_activation_v1_get_activation_token(wlWm.xdgActivation); if (!token) { DEBUG_ERROR("failed to retrieve XDG activation token"); return; } xdg_activation_token_v1_add_listener(token, &activationTokenListener, NULL); xdg_activation_token_v1_set_surface(token, wlWm.surface); xdg_activation_token_v1_commit(token); } looking-glass-B6/client/displayservers/Wayland/clipboard.c000066400000000000000000000344121434445012300241370ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "wayland.h" #include #include #include #include #include #include #include #include "app.h" #include "common/debug.h" struct DataOffer { bool isSelfCopy; char * mimetypes[LG_CLIPBOARD_DATA_NONE]; }; static const char * textMimetypes[] = { "text/plain", "text/plain;charset=utf-8", "TEXT", "STRING", "UTF8_STRING", NULL, }; static const char * pngMimetypes[] = { "image/png", NULL, }; static const char * bmpMimetypes[] = { "image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap", NULL, }; static const char * tiffMimetypes[] = { "image/tiff", NULL, }; static const char * jpegMimetypes[] = { "image/jpeg", NULL, }; static const char ** cbTypeToMimetypes(enum LG_ClipboardData type) { switch (type) { case LG_CLIPBOARD_DATA_TEXT: return textMimetypes; case LG_CLIPBOARD_DATA_PNG: return pngMimetypes; case LG_CLIPBOARD_DATA_BMP: return bmpMimetypes; case LG_CLIPBOARD_DATA_TIFF: return tiffMimetypes; case LG_CLIPBOARD_DATA_JPEG: return jpegMimetypes; default: DEBUG_ERROR("invalid clipboard type"); abort(); } } static bool containsMimetype(const char ** mimetypes, const char * needle) { for (const char ** mimetype = mimetypes; *mimetype; mimetype++) if (!strcmp(needle, *mimetype)) return true; return false; } static bool mimetypeEndswith(const char * mimetype, const char * what) { size_t mimetypeLen = strlen(mimetype); size_t whatLen = strlen(what); if (mimetypeLen < whatLen) return false; return !strcmp(mimetype + mimetypeLen - whatLen, what); } static bool isTextMimetype(const char * mimetype) { if (containsMimetype(textMimetypes, mimetype)) return true; if (!strcmp(mimetype, "text/ico")) return false; char * text = "text/"; if (!strncmp(mimetype, text, strlen(text))) return true; if (mimetypeEndswith(mimetype, "script") || mimetypeEndswith(mimetype, "xml") || mimetypeEndswith(mimetype, "yaml")) return true; if (strstr(mimetype, "json")) return true; return false; } static enum LG_ClipboardData mimetypeToCbType(const char * mimetype) { if (isTextMimetype(mimetype)) return LG_CLIPBOARD_DATA_TEXT; if (containsMimetype(pngMimetypes, mimetype)) return LG_CLIPBOARD_DATA_PNG; if (containsMimetype(bmpMimetypes, mimetype)) return LG_CLIPBOARD_DATA_BMP; if (containsMimetype(tiffMimetypes, mimetype)) return LG_CLIPBOARD_DATA_TIFF; if (containsMimetype(jpegMimetypes, mimetype)) return LG_CLIPBOARD_DATA_JPEG; return LG_CLIPBOARD_DATA_NONE; } static bool isImageCbtype(enum LG_ClipboardData type) { switch (type) { case LG_CLIPBOARD_DATA_TEXT: return false; case LG_CLIPBOARD_DATA_PNG: case LG_CLIPBOARD_DATA_BMP: case LG_CLIPBOARD_DATA_TIFF: case LG_CLIPBOARD_DATA_JPEG: return true; default: DEBUG_ERROR("invalid clipboard type"); abort(); } } static bool hasAnyMimetype(char ** mimetypes) { for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) if (mimetypes[i]) return true; return false; } static bool hasImageMimetype(char ** mimetypes) { for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) if (isImageCbtype(i) && mimetypes[i]) return true; return false; } // Destination client handlers. static void dataOfferHandleOffer(void * opaque, struct wl_data_offer * offer, const char * mimetype) { struct DataOffer * data = opaque; if (!strcmp(mimetype, wlCb.lgMimetype)) { data->isSelfCopy = true; return; } enum LG_ClipboardData type = mimetypeToCbType(mimetype); if (type == LG_CLIPBOARD_DATA_NONE) return; // text/html represents rich text format, which is almost never desirable when // and should not be used when a plain text or image format is available. if ((isImageCbtype(type) || containsMimetype(textMimetypes, mimetype)) && data->mimetypes[LG_CLIPBOARD_DATA_TEXT] && strstr(data->mimetypes[LG_CLIPBOARD_DATA_TEXT], "html")) { free(data->mimetypes[LG_CLIPBOARD_DATA_TEXT]); data->mimetypes[LG_CLIPBOARD_DATA_TEXT] = NULL; } if (strstr(mimetype, "html") && hasImageMimetype(data->mimetypes)) return; if (data->mimetypes[type]) return; data->mimetypes[type] = strdup(mimetype); } static void dataOfferHandleSourceActions(void * data, struct wl_data_offer * offer, uint32_t sourceActions) { // Do nothing. } static void dataOfferHandleAction(void * data, struct wl_data_offer * offer, uint32_t dndAction) { // Do nothing. } static const struct wl_data_offer_listener dataOfferListener = { .offer = dataOfferHandleOffer, .source_actions = dataOfferHandleSourceActions, .action = dataOfferHandleAction, }; static void dataDeviceHandleDataOffer(void * data, struct wl_data_device * dataDevice, struct wl_data_offer * offer) { struct DataOffer * extra = calloc(1, sizeof(struct DataOffer)); if (!extra) { DEBUG_ERROR("Out of memory while handling clipboard"); abort(); } wl_data_offer_set_user_data(offer, extra); wl_data_offer_add_listener(offer, &dataOfferListener, extra); } static void dataDeviceHandleSelection(void * opaque, struct wl_data_device * dataDevice, struct wl_data_offer * offer) { if (!offer) { waylandCBInvalidate(); return; } struct DataOffer * extra = wl_data_offer_get_user_data(offer); if (!hasAnyMimetype(extra->mimetypes) || extra->isSelfCopy) { waylandCBInvalidate(); wl_data_offer_destroy(offer); return; } wlCb.offer = offer; for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) free(wlCb.mimetypes[i]); memcpy(wlCb.mimetypes, extra->mimetypes, sizeof(wlCb.mimetypes)); wl_data_offer_set_user_data(offer, NULL); free(extra); int idx = 0; enum LG_ClipboardData types[LG_CLIPBOARD_DATA_NONE]; for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) if (wlCb.mimetypes[i]) types[idx++] = i; app_clipboardNotifyTypes(types, idx); } static void dataDeviceHandleEnter(void * data, struct wl_data_device * device, uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW, wl_fixed_t syW, struct wl_data_offer * offer) { DEBUG_ASSERT(wlCb.dndOffer == NULL); wlCb.dndOffer = offer; struct DataOffer * extra = wl_data_offer_get_user_data(offer); for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) free(extra->mimetypes[i]); free(extra); wl_data_offer_set_user_data(offer, NULL); wl_data_offer_set_actions(offer, WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE, WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE); } static void dataDeviceHandleMotion(void * data, struct wl_data_device * device, uint32_t time, wl_fixed_t sxW, wl_fixed_t syW) { // Do nothing. } static void dataDeviceHandleLeave(void * data, struct wl_data_device * device) { wl_data_offer_destroy(wlCb.dndOffer); wlCb.dndOffer = NULL; } static void dataDeviceHandleDrop(void * data, struct wl_data_device * device) { wl_data_offer_destroy(wlCb.dndOffer); wlCb.dndOffer = NULL; } static const struct wl_data_device_listener dataDeviceListener = { .data_offer = dataDeviceHandleDataOffer, .selection = dataDeviceHandleSelection, .enter = dataDeviceHandleEnter, .motion = dataDeviceHandleMotion, .leave = dataDeviceHandleLeave, .drop = dataDeviceHandleDrop, }; bool waylandCBInit(void) { memset(&wlCb, 0, sizeof(wlCb)); if (!wlWm.dataDeviceManager) { DEBUG_ERROR("Missing wl_data_device_manager interface (version 3+)"); return false; } wlCb.dataDevice = wl_data_device_manager_get_data_device( wlWm.dataDeviceManager, wlWm.seat); if (!wlCb.dataDevice) { DEBUG_ERROR("Failed to get data device"); return false; } wl_data_device_add_listener(wlCb.dataDevice, &dataDeviceListener, NULL); snprintf(wlCb.lgMimetype, sizeof(wlCb.lgMimetype), "application/x-looking-glass-copy;pid=%d", getpid()); return true; } static void clipboardReadCancel(struct ClipboardRead * data) { waylandPollUnregister(data->fd); close(data->fd); free(data->buf); free(data); wlCb.currentRead = NULL; } static void clipboardReadCallback(uint32_t events, void * opaque) { struct ClipboardRead * data = opaque; if (events & EPOLLERR) { clipboardReadCancel(data); return; } ssize_t result = read(data->fd, data->buf + data->numRead, data->size - data->numRead); if (result < 0) { DEBUG_ERROR("Failed to read from clipboard: %s", strerror(errno)); clipboardReadCancel(data); return; } if (result == 0) { app_clipboardNotifySize(data->type, data->numRead); app_clipboardData(data->type, data->buf, data->numRead); clipboardReadCancel(data); return; } data->numRead += result; if (data->numRead >= data->size) { data->size *= 2; void * nbuf = realloc(data->buf, data->size); if (!nbuf) { DEBUG_ERROR("Failed to realloc clipboard buffer: %s", strerror(errno)); clipboardReadCancel(data); return; } data->buf = nbuf; } } void waylandCBInvalidate(void) { if (wlCb.currentRead) clipboardReadCancel(wlCb.currentRead); app_clipboardRelease(); if (wlCb.offer) wl_data_offer_destroy(wlCb.offer); wlCb.offer = NULL; } void waylandCBRequest(LG_ClipboardData type) { if (!wlCb.offer || !wlCb.mimetypes[type]) { app_clipboardRelease(); return; } if (wlCb.currentRead) clipboardReadCancel(wlCb.currentRead); int fds[2]; if (pipe(fds) < 0) { DEBUG_ERROR("Failed to get a clipboard pipe: %s", strerror(errno)); abort(); } wl_data_offer_receive(wlCb.offer, wlCb.mimetypes[type], fds[1]); close(fds[1]); struct ClipboardRead * data = malloc(sizeof(*data)); if (!data) { DEBUG_ERROR("Failed to allocate memory to read clipboard"); close(fds[0]); return; } data->fd = fds[0]; data->size = 4096; data->numRead = 0; data->buf = malloc(data->size); data->offer = wlCb.offer; data->type = type; if (!data->buf) { DEBUG_ERROR("Failed to allocate memory to receive clipboard data"); close(data->fd); free(data); return; } if (!waylandPollRegister(data->fd, clipboardReadCallback, data, EPOLLIN)) { DEBUG_ERROR("Failed to register clipboard read into epoll: %s", strerror(errno)); close(data->fd); free(data->buf); free(data); } wlCb.currentRead = data; } struct ClipboardWrite { int fd; size_t pos; struct CountedBuffer * buffer; }; static void clipboardWriteCallback(uint32_t events, void * opaque) { struct ClipboardWrite * data = opaque; if (events & EPOLLERR) goto error; ssize_t written = write(data->fd, data->buffer->data + data->pos, data->buffer->size - data->pos); if (written < 0) { if (errno != EPIPE) DEBUG_ERROR("Failed to write clipboard data: %s", strerror(errno)); goto error; } data->pos += written; if (data->pos < data->buffer->size) return; error: waylandPollUnregister(data->fd); close(data->fd); countedBufferRelease(&data->buffer); free(data); } static void dataSourceHandleTarget(void * data, struct wl_data_source * source, const char * mimetype) { // Certain Wayland clients send this for copy-paste operations even though // it only makes sense for drag-and-drop. We just do nothing. } static void dataSourceHandleSend(void * data, struct wl_data_source * source, const char * mimetype, int fd) { struct WCBTransfer * transfer = (struct WCBTransfer *) data; if (containsMimetype(transfer->mimetypes, mimetype)) { struct ClipboardWrite * data = malloc(sizeof(*data)); if (!data) { DEBUG_ERROR("Out of memory trying to allocate ClipboardWrite"); goto error; } data->fd = fd; data->pos = 0; data->buffer = transfer->data; countedBufferAddRef(transfer->data); waylandPollRegister(fd, clipboardWriteCallback, data, EPOLLOUT); return; } error: close(fd); } static void dataSourceHandleCancelled(void * data, struct wl_data_source * source) { struct WCBTransfer * transfer = (struct WCBTransfer *) data; countedBufferRelease(&transfer->data); free(transfer); wl_data_source_destroy(source); } static const struct wl_data_source_listener dataSourceListener = { .target = dataSourceHandleTarget, .send = dataSourceHandleSend, .cancelled = dataSourceHandleCancelled, }; static void waylandCBReplyFn(void * opaque, LG_ClipboardData type, uint8_t * data, uint32_t size) { struct WCBTransfer * transfer = malloc(sizeof(*transfer)); if (!transfer) { DEBUG_ERROR("Out of memory when allocating WCBTransfer"); return; } transfer->mimetypes = cbTypeToMimetypes(type); transfer->data = countedBufferNew(size); if (!transfer->data) { DEBUG_ERROR("Out of memory when allocating clipboard buffer"); free(transfer); return; } memcpy(transfer->data->data, data, size); struct wl_data_source * source = wl_data_device_manager_create_data_source(wlWm.dataDeviceManager); wl_data_source_add_listener(source, &dataSourceListener, transfer); for (const char ** mimetype = transfer->mimetypes; *mimetype; mimetype++) wl_data_source_offer(source, *mimetype); wl_data_source_offer(source, wlCb.lgMimetype); wl_data_device_set_selection(wlCb.dataDevice, source, wlWm.keyboardEnterSerial); } void waylandCBNotice(LG_ClipboardData type) { wlCb.haveRequest = true; wlCb.type = type; app_clipboardRequest(waylandCBReplyFn, NULL); } void waylandCBRelease(void) { wlCb.haveRequest = false; } looking-glass-B6/client/displayservers/Wayland/cursor.c000066400000000000000000000150521434445012300235140ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define _GNU_SOURCE #include "wayland.h" #include #include #include #include #include #include #include "common/debug.h" static const uint32_t cursorBitmap[] = { 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, }; static struct wl_buffer * createSquareCursorBuffer(void) { int fd = memfd_create("lg-cursor", 0); if (fd < 0) { DEBUG_ERROR("Failed to create cursor shared memory: %d", errno); return NULL; } struct wl_buffer * result = NULL; if (ftruncate(fd, sizeof cursorBitmap) < 0) { DEBUG_ERROR("Failed to ftruncate cursor shared memory: %d", errno); goto fail; } void * shm_data = mmap(NULL, sizeof cursorBitmap, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (shm_data == MAP_FAILED) { DEBUG_ERROR("Failed to map memory for cursor: %d", errno); goto fail; } struct wl_shm_pool * pool = wl_shm_create_pool(wlWm.shm, fd, sizeof cursorBitmap); result = wl_shm_pool_create_buffer(pool, 0, 4, 4, 16, WL_SHM_FORMAT_XRGB8888); wl_shm_pool_destroy(pool); memcpy(shm_data, cursorBitmap, sizeof cursorBitmap); munmap(shm_data, sizeof cursorBitmap); fail: close(fd); return result; } static bool loadThemedCursor(const char * name, struct wl_surface ** surface, struct Point * hotspot) { struct wl_cursor * cursor = wl_cursor_theme_get_cursor(wlWm.cursorTheme, name); if (!cursor) return false; struct wl_buffer * buffer = wl_cursor_image_get_buffer(cursor->images[0]); if (!buffer) return false; *surface = wl_compositor_create_surface(wlWm.compositor); if (!*surface) return NULL; wl_surface_attach(*surface, buffer, 0, 0); wl_surface_set_buffer_scale(*surface, wlWm.cursorScale); wl_surface_commit(*surface); *hotspot = (struct Point) { .x = cursor->images[0]->hotspot_x, .y = cursor->images[0]->hotspot_y, }; return true; } static const char ** nameLists[LG_POINTER_COUNT] = { [LG_POINTER_ARROW ] = (const char *[]) { "left_ptr", "arrow", NULL }, [LG_POINTER_INPUT ] = (const char *[]) { "text", "xterm", "ibeam", NULL }, [LG_POINTER_MOVE ] = (const char *[]) { "move", "4498f0e0c1937ffe01fd06f973665830", "9081237383d90e509aa00f00170e968f", NULL }, [LG_POINTER_RESIZE_NS ] = (const char *[]) { "sb_v_double_arrow", "size_ver", "v_double_arrow", "2870a09082c103050810ffdffffe0204", "00008160000006810000408080010102", NULL }, [LG_POINTER_RESIZE_EW ] = (const char *[]) { "sb_h_double_arrow", "size_hor", "h_double_arrow", "14fef782d02440884392942c11205230", "028006030e0e7ebffc7f7070c0600140", NULL }, [LG_POINTER_RESIZE_NESW] = (const char *[]) { "fd_double_arrow", "size_bdiag", "fcf1c3c7cd4491d801f1e1c78f100000", NULL }, [LG_POINTER_RESIZE_NWSE] = (const char *[]) { "bd_double_arrow", "size_fdiag", "c7088f0f3e6c8088236ef8e1e3e70000", NULL }, [LG_POINTER_HAND ] = (const char *[]) { "hand", "pointing_hand", "hand1", "hand2", "pointer", "e29285e634086352946a0e7090d73106", "9d800788f1b08800ae810202380a0822", NULL }, [LG_POINTER_NOT_ALLOWED] = (const char *[]) { "crossed_circle", "not-allowed", NULL }, }; static void reloadCursors(void) { if (wlWm.cursorTheme) for (LG_DSPointer pointer = LG_POINTER_ARROW; pointer < LG_POINTER_COUNT; ++pointer) for (const char ** names = nameLists[pointer]; *names; ++names) if (loadThemedCursor(*names, wlWm.cursors + pointer, wlWm.cursorHot + pointer)) break; } bool waylandCursorInit(void) { if (!wlWm.compositor) { DEBUG_ERROR("Compositor missing wl_compositor, will not proceed"); return false; } wlWm.cursorSquareBuffer = createSquareCursorBuffer(); if (wlWm.cursorSquareBuffer) { wlWm.cursors[LG_POINTER_SQUARE] = wl_compositor_create_surface(wlWm.compositor); wl_surface_attach(wlWm.cursors[LG_POINTER_SQUARE], wlWm.cursorSquareBuffer, 0, 0); wl_surface_commit(wlWm.cursors[LG_POINTER_SQUARE]); } wlWm.cursorThemeName = getenv("XCURSOR_THEME"); wlWm.cursorSize = 24; const char * cursorSizeEnv = getenv("XCURSOR_SIZE"); if (cursorSizeEnv) { int size = atoi(cursorSizeEnv); if (size) wlWm.cursorSize = size; } wlWm.cursorTheme = wl_cursor_theme_load(wlWm.cursorThemeName, wlWm.cursorSize, wlWm.shm); wlWm.cursorScale = 1; reloadCursors(); return true; } void waylandCursorFree(void) { for (int i = 0; i < LG_POINTER_COUNT; ++i) if (wlWm.cursors[i]) wl_surface_destroy(wlWm.cursors[i]); if (wlWm.cursorTheme) wl_cursor_theme_destroy(wlWm.cursorTheme); if (wlWm.cursorSquareBuffer) wl_buffer_destroy(wlWm.cursorSquareBuffer); } void waylandCursorScaleChange(void) { int newScale = ceil(wl_fixed_to_double(wlWm.scale)); if (newScale == wlWm.cursorScale) return; struct wl_cursor_theme * new = wl_cursor_theme_load(wlWm.cursorThemeName, wlWm.cursorSize * newScale, wlWm.shm); if (!new) return; struct wl_surface * old[LG_POINTER_COUNT]; memcpy(old, wlWm.cursors, sizeof(old)); memset(wlWm.cursors, 0, sizeof(wlWm.cursors)); if (wlWm.cursorTheme) wl_cursor_theme_destroy(wlWm.cursorTheme); wlWm.cursorTheme = new; wlWm.cursorScale = newScale; reloadCursors(); waylandSetPointer(wlWm.cursorId); for (int i = 0; i < LG_POINTER_COUNT; ++i) if (old[i]) wl_surface_destroy(old[i]); } void waylandSetPointer(LG_DSPointer pointer) { wlWm.cursorId = pointer; wlWm.cursor = wlWm.cursors[pointer]; wlWm.cursorHotX = wlWm.cursorHot[pointer].x; wlWm.cursorHotY = wlWm.cursorHot[pointer].y; if (wlWm.pointer) wl_pointer_set_cursor(wlWm.pointer, wlWm.pointerEnterSerial, wlWm.cursor, wlWm.cursorHotX, wlWm.cursorHotY); } looking-glass-B6/client/displayservers/Wayland/gl.c000066400000000000000000000142771434445012300226110ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "wayland.h" #include #include #include #include #include "app.h" #include "common/debug.h" #include "util.h" #if defined(ENABLE_EGL) || defined(ENABLE_OPENGL) #include "egl_dynprocs.h" #include "eglutil.h" bool waylandEGLInit(int w, int h) { wlWm.eglWindow = wl_egl_window_create(wlWm.surface, w, h); if (!wlWm.eglWindow) { DEBUG_ERROR("Failed to create EGL window"); return false; } return true; } EGLDisplay waylandGetEGLDisplay(void) { EGLNativeDisplayType native = (EGLNativeDisplayType) wlWm.display; const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS); if (util_hasGLExt(early_exts, "EGL_KHR_platform_wayland") && g_egl_dynProcs.eglGetPlatformDisplay) { DEBUG_INFO("Using eglGetPlatformDisplay"); return g_egl_dynProcs.eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, native, NULL); } if (util_hasGLExt(early_exts, "EGL_EXT_platform_wayland") && g_egl_dynProcs.eglGetPlatformDisplayEXT) { DEBUG_INFO("Using eglGetPlatformDisplayEXT"); return g_egl_dynProcs.eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, native, NULL); } DEBUG_INFO("Using eglGetDisplay"); return eglGetDisplay(native); } void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count) { if (!wlWm.swapWithDamage.init) { if (wl_proxy_get_version((struct wl_proxy *) wlWm.surface) < 4) { DEBUG_INFO("Swapping buffers with damage: not supported, need wl_compositor v4"); swapWithDamageDisable(&wlWm.swapWithDamage); } else swapWithDamageInit(&wlWm.swapWithDamage, display); } waylandPresentationFrame(); swapWithDamage(&wlWm.swapWithDamage, display, surface, damage, count); if (wlWm.needsResize) { bool skipResize = false; wl_egl_window_resize(wlWm.eglWindow, wl_fixed_to_int(wlWm.width * wlWm.scale), wl_fixed_to_int(wlWm.height * wlWm.scale), 0, 0); if (wlWm.width == 0 || wlWm.height == 0) skipResize = true; else if (wlWm.fractionalScale) { wl_surface_set_buffer_scale(wlWm.surface, 1); if (!wlWm.viewport) wlWm.viewport = wp_viewporter_get_viewport(wlWm.viewporter, wlWm.surface); wp_viewport_set_source(wlWm.viewport, 0, 0, wlWm.width * wlWm.scale, wlWm.height * wlWm.scale); wp_viewport_set_destination(wlWm.viewport, wlWm.width, wlWm.height); } else { if (wlWm.viewport) { // Clearing the source and destination rectangles should happen in wp_viewport_destroy. // However, wlroots does not clear the rectangle until fixed in 456c6e22 (2021-08-02). // This should be kept to work around old versions of wlroots. wl_fixed_t clear = wl_fixed_from_int(-1); wp_viewport_set_source(wlWm.viewport, clear, clear, clear, clear); wp_viewport_set_destination(wlWm.viewport, -1, -1); wp_viewport_destroy(wlWm.viewport); wlWm.viewport = NULL; } wl_surface_set_buffer_scale(wlWm.surface, wl_fixed_to_int(wlWm.scale)); } struct wl_region * region = wl_compositor_create_region(wlWm.compositor); wl_region_add(region, 0, 0, wlWm.width, wlWm.height); wl_surface_set_opaque_region(wlWm.surface, region); wl_region_destroy(region); app_handleResizeEvent(wlWm.width, wlWm.height, wl_fixed_to_double(wlWm.scale), (struct Border) {0, 0, 0, 0}); app_invalidateWindow(true); waylandStopWaitFrame(); wlWm.needsResize = skipResize; } waylandShellAckConfigureIfNeeded(); } #endif #ifdef ENABLE_EGL EGLNativeWindowType waylandGetEGLNativeWindow(void) { return (EGLNativeWindowType) wlWm.eglWindow; } #endif #ifdef ENABLE_OPENGL bool waylandOpenGLInit(void) { EGLint attr[] = { EGL_BUFFER_SIZE , 24, EGL_CONFORMANT , EGL_OPENGL_BIT, EGL_RENDERABLE_TYPE , EGL_OPENGL_BIT, EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, EGL_RED_SIZE , 8, EGL_GREEN_SIZE , 8, EGL_BLUE_SIZE , 8, EGL_SAMPLE_BUFFERS , 0, EGL_SAMPLES , 0, EGL_NONE }; wlWm.glDisplay = waylandGetEGLDisplay(); int maj, min; if (!eglInitialize(wlWm.glDisplay, &maj, &min)) { DEBUG_ERROR("Unable to initialize EGL"); return false; } if (wlWm.glDisplay == EGL_NO_DISPLAY) { DEBUG_ERROR("Failed to get EGL display (eglError: 0x%x)", eglGetError()); return false; } EGLint num_config; if (!eglChooseConfig(wlWm.glDisplay, attr, &wlWm.glConfig, 1, &num_config)) { DEBUG_ERROR("Failed to choose config (eglError: 0x%x)", eglGetError()); return false; } wlWm.glSurface = eglCreateWindowSurface(wlWm.glDisplay, wlWm.glConfig, wlWm.eglWindow, NULL); if (wlWm.glSurface == EGL_NO_SURFACE) { DEBUG_ERROR("Failed to create EGL surface (eglError: 0x%x)", eglGetError()); return false; } return true; } LG_DSGLContext waylandGLCreateContext(void) { eglBindAPI(EGL_OPENGL_API); return eglCreateContext(wlWm.glDisplay, wlWm.glConfig, EGL_NO_CONTEXT, NULL); } void waylandGLDeleteContext(LG_DSGLContext context) { eglDestroyContext(wlWm.glDisplay, context); } void waylandGLMakeCurrent(LG_DSGLContext context) { eglMakeCurrent(wlWm.glDisplay, wlWm.glSurface, wlWm.glSurface, context); } void waylandGLSetSwapInterval(int interval) { eglSwapInterval(wlWm.glDisplay, interval); } void waylandGLSwapBuffers(void) { waylandEGLSwapBuffers(wlWm.glDisplay, wlWm.glSurface, NULL, 0); } #endif looking-glass-B6/client/displayservers/Wayland/idle.c000066400000000000000000000031741434445012300231160ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "wayland.h" #include #include #include "common/debug.h" bool waylandIdleInit(void) { if (!wlWm.idleInhibitManager) DEBUG_WARN("zwp_idle_inhibit_manager_v1 not exported by compositor, will " "not be able to suppress idle states"); return true; } void waylandIdleFree(void) { if (wlWm.idleInhibitManager) { waylandUninhibitIdle(); zwp_idle_inhibit_manager_v1_destroy(wlWm.idleInhibitManager); } } void waylandInhibitIdle(void) { if (wlWm.idleInhibitManager && !wlWm.idleInhibitor) wlWm.idleInhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor( wlWm.idleInhibitManager, wlWm.surface); } void waylandUninhibitIdle(void) { if (wlWm.idleInhibitor) { zwp_idle_inhibitor_v1_destroy(wlWm.idleInhibitor); wlWm.idleInhibitor = NULL; } } looking-glass-B6/client/displayservers/Wayland/input.c000066400000000000000000000416371434445012300233460ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "wayland.h" #include #include #include #include #include #include #include #include "app.h" #include "common/debug.h" // Mouse-handling listeners. static void pointerMotionHandler(void * data, struct wl_pointer * pointer, uint32_t serial, wl_fixed_t sxW, wl_fixed_t syW) { wlWm.cursorX = wl_fixed_to_double(sxW); wlWm.cursorY = wl_fixed_to_double(syW); app_updateCursorPos(wlWm.cursorX, wlWm.cursorY); if (!wlWm.warpSupport && !wlWm.relativePointer) app_handleMouseBasic(); } static void pointerEnterHandler(void * data, struct wl_pointer * pointer, uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW, wl_fixed_t syW) { if (surface != wlWm.surface) return; wlWm.pointerInSurface = true; app_handleEnterEvent(true); wl_pointer_set_cursor(pointer, serial, wlWm.cursor, wlWm.cursorHotX, wlWm.cursorHotY); wlWm.pointerEnterSerial = serial; wlWm.cursorX = wl_fixed_to_double(sxW); wlWm.cursorY = wl_fixed_to_double(syW); app_updateCursorPos(wlWm.cursorX, wlWm.cursorY); if (wlWm.warpSupport) { app_handleMouseRelative(0.0, 0.0, 0.0, 0.0); return; } if (wlWm.relativePointer) return; app_resyncMouseBasic(); app_handleMouseBasic(); } static void pointerLeaveHandler(void * data, struct wl_pointer * pointer, uint32_t serial, struct wl_surface * surface) { if (surface != wlWm.surface) return; wlWm.pointerInSurface = false; app_handleEnterEvent(false); } static void pointerAxisHandler(void * data, struct wl_pointer * pointer, uint32_t serial, uint32_t axis, wl_fixed_t value) { if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) return; int button = value > 0 ? 5 /* SPICE_MOUSE_BUTTON_DOWN */ : 4 /* SPICE_MOUSE_BUTTON_UP */; app_handleButtonPress(button); app_handleButtonRelease(button); app_handleWheelMotion(wl_fixed_to_double(value) / 15.0); } static int mapWaylandToSpiceButton(uint32_t button) { switch (button) { case BTN_LEFT: return 1; // SPICE_MOUSE_BUTTON_LEFT case BTN_MIDDLE: return 2; // SPICE_MOUSE_BUTTON_MIDDLE case BTN_RIGHT: return 3; // SPICE_MOUSE_BUTTON_RIGHT case BTN_SIDE: return 6; // SPICE_MOUSE_BUTTON_SIDE case BTN_EXTRA: return 7; // SPICE_MOUSE_BUTTON_EXTRA } return 0; // SPICE_MOUSE_BUTTON_INVALID } static void pointerButtonHandler(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t stateW) { button = mapWaylandToSpiceButton(button); if (stateW == WL_POINTER_BUTTON_STATE_PRESSED) app_handleButtonPress(button); else app_handleButtonRelease(button); } static const struct wl_pointer_listener pointerListener = { .enter = pointerEnterHandler, .leave = pointerLeaveHandler, .motion = pointerMotionHandler, .button = pointerButtonHandler, .axis = pointerAxisHandler, }; static void relativePointerMotionHandler(void * data, struct zwp_relative_pointer_v1 *pointer, uint32_t timeHi, uint32_t timeLo, wl_fixed_t dxW, wl_fixed_t dyW, wl_fixed_t dxUnaccelW, wl_fixed_t dyUnaccelW) { wlWm.cursorX += wl_fixed_to_double(dxW); wlWm.cursorY += wl_fixed_to_double(dyW); app_updateCursorPos(wlWm.cursorX, wlWm.cursorY); app_handleMouseRelative( wl_fixed_to_double(dxW), wl_fixed_to_double(dyW), wl_fixed_to_double(dxUnaccelW), wl_fixed_to_double(dyUnaccelW)); } static const struct zwp_relative_pointer_v1_listener relativePointerListener = { .relative_motion = relativePointerMotionHandler, }; // Keyboard-handling listeners. static void keyboardKeymapHandler(void * data, struct wl_keyboard * keyboard, uint32_t format, int fd, uint32_t size) { if (!wlWm.xkb) goto done; if (wlWm.keymap) { xkb_keymap_unref(wlWm.keymap); wlWm.keymap = NULL; } if (wlWm.xkbState) { xkb_state_unref(wlWm.xkbState); wlWm.xkbState = NULL; } if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { DEBUG_WARN("Unsupported keymap format, keyboard input will not work: %d", format); goto done; } char * map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (map == MAP_FAILED) { DEBUG_ERROR("Failed to mmap keymap: %s", strerror(errno)); goto done; } wlWm.keymap = xkb_keymap_new_from_string(wlWm.xkb, map, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); if (!wlWm.keymap) DEBUG_WARN("Failed to load keymap, keyboard input will not work"); munmap(map, size); if (wlWm.keymap) { wlWm.xkbState = xkb_state_new(wlWm.keymap); if (!wlWm.xkbState) DEBUG_WARN("Failed to create xkb_state"); } done: close(fd); } static int getCharcode(uint32_t key) { key += 8; // xkb scancode is evdev scancode + 8 xkb_keysym_t sym = xkb_state_key_get_one_sym(wlWm.xkbState, key); if (sym == XKB_KEY_NoSymbol) return 0; sym = xkb_keysym_to_upper(sym); return xkb_keysym_to_utf32(sym); } static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard, uint32_t serial, struct wl_surface * surface, struct wl_array * keys) { if (surface != wlWm.surface) return; wlWm.focusedOnSurface = true; app_handleFocusEvent(true); wlWm.keyboardEnterSerial = serial; uint32_t * key; wl_array_for_each(key, keys) app_handleKeyPress(*key, getCharcode(*key)); } static void keyboardLeaveHandler(void * data, struct wl_keyboard * keyboard, uint32_t serial, struct wl_surface * surface) { if (surface != wlWm.surface) return; wlWm.focusedOnSurface = false; waylandCBInvalidate(); app_handleFocusEvent(false); } static void keyboardKeyHandler(void * data, struct wl_keyboard * keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { if (!wlWm.focusedOnSurface) return; if (state == WL_KEYBOARD_KEY_STATE_PRESSED) app_handleKeyPress(key, getCharcode(key)); else app_handleKeyRelease(key, getCharcode(key)); if (!wlWm.xkbState || !app_isOverlayMode() || state != WL_KEYBOARD_KEY_STATE_PRESSED) return; key += 8; // xkb scancode is evdev scancode + 8 int size = xkb_state_key_get_utf8(wlWm.xkbState, key, NULL, 0); if (size <= 0) return; char buffer[size + 1]; xkb_state_key_get_utf8(wlWm.xkbState, key, buffer, size + 1); app_handleKeyboardTyped(buffer); } static void keyboardModifiersHandler(void * data, struct wl_keyboard * keyboard, uint32_t serial, uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { if (!wlWm.xkbState) return; xkb_state_update_mask(wlWm.xkbState, modsDepressed, modsLatched, modsLocked, 0, 0, group); app_handleKeyboardModifiers( xkb_state_mod_name_is_active(wlWm.xkbState, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) > 0, xkb_state_mod_name_is_active(wlWm.xkbState, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE) > 0, xkb_state_mod_name_is_active(wlWm.xkbState, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE) > 0, xkb_state_mod_name_is_active(wlWm.xkbState, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE) > 0 ); app_handleKeyboardLEDs( xkb_state_led_name_is_active(wlWm.xkbState, XKB_LED_NAME_NUM) > 0, xkb_state_led_name_is_active(wlWm.xkbState, XKB_LED_NAME_CAPS) > 0, xkb_state_led_name_is_active(wlWm.xkbState, XKB_LED_NAME_SCROLL) > 0 ); } static const struct wl_keyboard_listener keyboardListener = { .keymap = keyboardKeymapHandler, .enter = keyboardEnterHandler, .leave = keyboardLeaveHandler, .key = keyboardKeyHandler, .modifiers = keyboardModifiersHandler, }; static void waylandCleanUpPointer(void) { INTERLOCKED_SECTION(wlWm.confineLock, { if (wlWm.lockedPointer) { zwp_locked_pointer_v1_destroy(wlWm.lockedPointer); wlWm.lockedPointer = NULL; } if (wlWm.confinedPointer) { zwp_confined_pointer_v1_destroy(wlWm.confinedPointer); wlWm.confinedPointer = NULL; } }); if (wlWm.relativePointer) { zwp_relative_pointer_v1_destroy(wlWm.relativePointer); wlWm.relativePointer = NULL; } wl_pointer_destroy(wlWm.pointer); wlWm.pointer = NULL; } // Seat-handling listeners. static void handlePointerCapability(uint32_t capabilities) { bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER; if (!hasPointer && wlWm.pointer) waylandCleanUpPointer(); else if (hasPointer && !wlWm.pointer) { wlWm.pointer = wl_seat_get_pointer(wlWm.seat); wl_pointer_add_listener(wlWm.pointer, &pointerListener, NULL); waylandSetPointer(wlWm.cursorId); if (wlWm.warpSupport) { wlWm.relativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer( wlWm.relativePointerManager, wlWm.pointer); zwp_relative_pointer_v1_add_listener(wlWm.relativePointer, &relativePointerListener, NULL); } } } static void handleKeyboardCapability(uint32_t capabilities) { bool hasKeyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD; if (!hasKeyboard && wlWm.keyboard) { wl_keyboard_destroy(wlWm.keyboard); wlWm.keyboard = NULL; } else if (hasKeyboard && !wlWm.keyboard) { wlWm.keyboard = wl_seat_get_keyboard(wlWm.seat); wl_keyboard_add_listener(wlWm.keyboard, &keyboardListener, NULL); } } static void seatCapabilitiesHandler(void * data, struct wl_seat * seat, uint32_t capabilities) { wlWm.capabilities = capabilities; handlePointerCapability(capabilities); handleKeyboardCapability(capabilities); } static void seatNameHandler(void * data, struct wl_seat * seat, const char * name) { // Do nothing. } static const struct wl_seat_listener seatListener = { .capabilities = seatCapabilitiesHandler, .name = seatNameHandler, }; bool waylandInputInit(void) { if (!wlWm.seat) { DEBUG_ERROR("Compositor missing wl_seat, will not proceed"); return false; } if (wlWm.warpSupport && (!wlWm.relativePointerManager || !wlWm.pointerConstraints)) { DEBUG_WARN("Cursor warp is requested, but cannot be honoured due to lack " "of zwp_relative_pointer_manager_v1 or zwp_pointer_constraints_v1"); wlWm.warpSupport = false; } if (!wlWm.relativePointerManager) DEBUG_WARN("zwp_relative_pointer_manager_v1 not exported by compositor, " "mouse will not be captured"); if (!wlWm.pointerConstraints) DEBUG_WARN("zwp_pointer_constraints_v1 not exported by compositor, mouse " "will not be captured"); if (!wlWm.keyboardInhibitManager) DEBUG_WARN("zwp_keyboard_shortcuts_inhibit_manager_v1 not exported by " "compositor, keyboard will not be grabbed"); wlWm.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!wlWm.xkb) DEBUG_WARN("Failed to initialize xkb, keyboard input will not work"); wl_seat_add_listener(wlWm.seat, &seatListener, NULL); wl_display_roundtrip(wlWm.display); LG_LOCK_INIT(wlWm.confineLock); return true; } void waylandInputFree(void) { waylandUngrabPointer(); LG_LOCK_FREE(wlWm.confineLock); if (wlWm.pointer) waylandCleanUpPointer(); // The only legal way the keyboard can be null is if it never existed. // When unplugged, the compositor must have an inert object. if (wlWm.keyboard) wl_keyboard_destroy(wlWm.keyboard); wl_seat_destroy(wlWm.seat); if (wlWm.xkbState) xkb_state_unref(wlWm.xkbState); if (wlWm.keymap) xkb_keymap_unref(wlWm.keymap); if (wlWm.xkb) xkb_context_unref(wlWm.xkb); } void waylandGrabPointer(void) { if (!wlWm.relativePointerManager || !wlWm.pointerConstraints) return; if (!wlWm.warpSupport && !wlWm.relativePointer) { wlWm.relativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer( wlWm.relativePointerManager, wlWm.pointer); zwp_relative_pointer_v1_add_listener(wlWm.relativePointer, &relativePointerListener, NULL); } INTERLOCKED_SECTION(wlWm.confineLock, { if (!wlWm.confinedPointer && !wlWm.lockedPointer) { wlWm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer( wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); } }); } inline static void internalUngrabPointer(bool lock) { if (lock) LG_LOCK(wlWm.confineLock); if (wlWm.confinedPointer) { zwp_confined_pointer_v1_destroy(wlWm.confinedPointer); wlWm.confinedPointer = NULL; } if (lock) LG_UNLOCK(wlWm.confineLock); if (!wlWm.warpSupport) { if (!wlWm.relativePointer) { wlWm.relativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer( wlWm.relativePointerManager, wlWm.pointer); zwp_relative_pointer_v1_add_listener(wlWm.relativePointer, &relativePointerListener, NULL); } app_resyncMouseBasic(); app_handleMouseBasic(); } } void waylandUngrabPointer(void) { internalUngrabPointer(true); } void waylandCapturePointer(void) { if (!wlWm.warpSupport) { waylandGrabPointer(); return; } INTERLOCKED_SECTION(wlWm.confineLock, { if (wlWm.confinedPointer) { zwp_confined_pointer_v1_destroy(wlWm.confinedPointer); wlWm.confinedPointer = NULL; } wlWm.lockedPointer = zwp_pointer_constraints_v1_lock_pointer( wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); }); } void waylandUncapturePointer(void) { INTERLOCKED_SECTION(wlWm.confineLock, { if (wlWm.lockedPointer) { zwp_locked_pointer_v1_destroy(wlWm.lockedPointer); wlWm.lockedPointer = NULL; } /* we need to ungrab the pointer on the following conditions when exiting capture mode: * - if warp is not supported, exit via window edge detection will never work * as the cursor can not be warped out of the window when we release it. * - if the format is invalid as we do not know where the guest cursor is, * which also breaks edge detection. * - if the user has opted to use captureInputOnly mode. */ if (!wlWm.warpSupport || !app_isFormatValid() || app_isCaptureOnlyMode()) internalUngrabPointer(false); else if (wlWm.pointer) { wlWm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer( wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); } }); } void waylandGrabKeyboard(void) { if (wlWm.keyboardInhibitManager && !wlWm.keyboardInhibitor) { wlWm.keyboardInhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts( wlWm.keyboardInhibitManager, wlWm.surface, wlWm.seat); } } void waylandUngrabKeyboard(void) { if (wlWm.keyboardInhibitor) { zwp_keyboard_shortcuts_inhibitor_v1_destroy(wlWm.keyboardInhibitor); wlWm.keyboardInhibitor = NULL; } } void waylandWarpPointer(int x, int y, bool exiting) { if (!wlWm.pointerInSurface || wlWm.lockedPointer) return; INTERLOCKED_SECTION(wlWm.confineLock, { if (wlWm.lockedPointer) { LG_UNLOCK(wlWm.confineLock); return; } if (x < 0) x = 0; else if (x >= wlWm.width) x = wlWm.width - 1; if (y < 0) y = 0; else if (y >= wlWm.height) y = wlWm.height - 1; struct wl_region * region = wl_compositor_create_region(wlWm.compositor); wl_region_add(region, x, y, 1, 1); if (wlWm.confinedPointer) { zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, region); wl_surface_commit(wlWm.surface); zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, NULL); } else { struct zwp_confined_pointer_v1 * confine; confine = zwp_pointer_constraints_v1_confine_pointer( wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, region, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); wl_surface_commit(wlWm.surface); zwp_confined_pointer_v1_destroy(confine); } wl_surface_commit(wlWm.surface); wl_region_destroy(region); }); } void waylandRealignPointer(void) { if (!wlWm.warpSupport) app_resyncMouseBasic(); } void waylandGuestPointerUpdated(double x, double y, double localX, double localY) { if ( !wlWm.pointer || !wlWm.warpSupport || !wlWm.pointerInSurface || wlWm.lockedPointer ) return; waylandWarpPointer((int) localX, (int) localY, false); } looking-glass-B6/client/displayservers/Wayland/output.c000066400000000000000000000132471434445012300235430ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "wayland.h" #include #include #include #include "common/debug.h" static void outputUpdateScale(struct WaylandOutput * node) { wl_fixed_t original = node->scale; if (!wlWm.useFractionalScale || !wlWm.viewporter || !node->logicalWidth) node->scale = wl_fixed_from_int(node->scaleInt); else { int32_t modeWidth = node->modeRotate ? node->modeHeight : node->modeWidth; node->scale = wl_fixed_from_double(1.0 * modeWidth / node->logicalWidth); } if (original != node->scale) waylandWindowUpdateScale(); } static void outputGeometryHandler(void * opaque, struct wl_output * output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char * make, const char * model, int32_t output_transform) { struct WaylandOutput * node = opaque; switch (output_transform) { case WL_OUTPUT_TRANSFORM_90: case WL_OUTPUT_TRANSFORM_270: case WL_OUTPUT_TRANSFORM_FLIPPED_90: case WL_OUTPUT_TRANSFORM_FLIPPED_270: node->modeRotate = true; break; default: node->modeRotate = false; } } static void outputModeHandler(void * opaque, struct wl_output * wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { if (!(flags & WL_OUTPUT_MODE_CURRENT)) return; struct WaylandOutput * node = opaque; node->modeWidth = width; node->modeHeight = height; } static void outputDoneHandler(void * opaque, struct wl_output * output) { struct WaylandOutput * node = opaque; outputUpdateScale(node); } static void outputScaleHandler(void * opaque, struct wl_output * output, int32_t scale) { struct WaylandOutput * node = opaque; node->scaleInt = scale; } static const struct wl_output_listener outputListener = { .geometry = outputGeometryHandler, .mode = outputModeHandler, .done = outputDoneHandler, .scale = outputScaleHandler, }; static void xdgOutputLogicalPositionHandler(void * opaque, struct zxdg_output_v1 * xdgOutput, int32_t x, int32_t y) { // Do nothing. } static void xdgOutputLogicalSizeHandler(void * opaque, struct zxdg_output_v1 * xdgOutput, int32_t width, int32_t height) { struct WaylandOutput * node = opaque; node->logicalWidth = width; node->logicalHeight = height; } static void xdgOutputDoneHandler(void * opaque, struct zxdg_output_v1 * xdgOutput) { struct WaylandOutput * node = opaque; outputUpdateScale(node); } static void xdgOutputNameHandler(void * opaque, struct zxdg_output_v1 * xdgOutput, const char * name) { // Do nothing. } static void xdgOutputDescriptionHandler(void * opaque, struct zxdg_output_v1 * xdgOutput, const char * description) { // Do nothing. } static const struct zxdg_output_v1_listener xdgOutputListener = { .logical_position = xdgOutputLogicalPositionHandler, .logical_size = xdgOutputLogicalSizeHandler, .done = xdgOutputDoneHandler, .name = xdgOutputNameHandler, .description = xdgOutputDescriptionHandler, }; bool waylandOutputInit(void) { wl_list_init(&wlWm.outputs); return true; } void waylandOutputFree(void) { struct WaylandOutput * node; struct WaylandOutput * temp; wl_list_for_each_safe(node, temp, &wlWm.outputs, link) { if (node->version >= 3) wl_output_release(node->output); if (node->xdgOutput) zxdg_output_v1_destroy(node->xdgOutput); wl_list_remove(&node->link); free(node); } } void waylandOutputBind(uint32_t name, uint32_t version) { struct WaylandOutput * node = calloc(1, sizeof(struct WaylandOutput)); if (!node) return; if (version < 2) { DEBUG_WARN("wl_output version too old: expected >= 2, got %d", version); free(node); return; } node->name = name; node->scale = 0; node->version = version; node->output = wl_registry_bind(wlWm.registry, name, &wl_output_interface, version >= 3 ? 3 : 2); if (!node->output) { DEBUG_ERROR("Failed to bind to wl_output %u\n", name); free(node); return; } if (wlWm.xdgOutputManager) { node->xdgOutput = zxdg_output_manager_v1_get_xdg_output(wlWm.xdgOutputManager, node->output); if (node->xdgOutput) zxdg_output_v1_add_listener(node->xdgOutput, &xdgOutputListener, node); } wl_output_add_listener(node->output, &outputListener, node); wl_list_insert(&wlWm.outputs, &node->link); } void waylandOutputTryUnbind(uint32_t name) { struct WaylandOutput * node; wl_list_for_each(node, &wlWm.outputs, link) { if (node->name == name) { if (node->version >= 3) wl_output_release(node->output); if (node->xdgOutput) zxdg_output_v1_destroy(node->xdgOutput); wl_list_remove(&node->link); free(node); break; } } } wl_fixed_t waylandOutputGetScale(struct wl_output * output) { struct WaylandOutput * node; wl_list_for_each(node, &wlWm.outputs, link) if (node->output == output) return node->scale; return 0; } looking-glass-B6/client/displayservers/Wayland/poll.c000066400000000000000000000110711434445012300231420ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "wayland.h" #include #include #include #include #include #include "common/debug.h" #include "common/locking.h" #ifdef ENABLE_LIBDECOR #include #endif #define EPOLL_EVENTS 10 // Maximum number of fds we can process at once in waylandWait #ifndef ENABLE_LIBDECOR static void waylandDisplayCallback(uint32_t events, void * opaque) { if (events & EPOLLERR) wl_display_cancel_read(wlWm.display); else wl_display_read_events(wlWm.display); wl_display_dispatch_pending(wlWm.display); } #endif bool waylandPollInit(void) { wlWm.epollFd = epoll_create1(EPOLL_CLOEXEC); if (wlWm.epollFd < 0) { DEBUG_ERROR("Failed to initialize epoll: %s", strerror(errno)); return false; } wl_list_init(&wlWm.poll); wl_list_init(&wlWm.pollFree); LG_LOCK_INIT(wlWm.pollLock); LG_LOCK_INIT(wlWm.pollFreeLock); #ifndef ENABLE_LIBDECOR wlWm.displayFd = wl_display_get_fd(wlWm.display); if (!waylandPollRegister(wlWm.displayFd, waylandDisplayCallback, NULL, EPOLLIN)) { DEBUG_ERROR("Failed register display to epoll: %s", strerror(errno)); return false; } #endif return true; } void waylandWait(unsigned int time) { #ifdef ENABLE_LIBDECOR libdecor_dispatch(wlWm.libdecor, 0); #else while (wl_display_prepare_read(wlWm.display)) wl_display_dispatch_pending(wlWm.display); wl_display_flush(wlWm.display); #endif struct epoll_event events[EPOLL_EVENTS]; int count; if ((count = epoll_wait(wlWm.epollFd, events, EPOLL_EVENTS, time)) < 0) { if (errno != EINTR) DEBUG_INFO("epoll failed: %s", strerror(errno)); #ifndef ENABLE_LIBDECOR wl_display_cancel_read(wlWm.display); #endif return; } #ifndef ENABLE_LIBDECOR bool sawDisplay = false; #endif for (int i = 0; i < count; ++i) { struct WaylandPoll * poll = events[i].data.ptr; if (!poll->removed) poll->callback(events[i].events, poll->opaque); #ifndef ENABLE_LIBDECOR if (poll->fd == wlWm.displayFd) sawDisplay = true; #endif } #ifndef ENABLE_LIBDECOR if (!sawDisplay) wl_display_cancel_read(wlWm.display); #endif INTERLOCKED_SECTION(wlWm.pollFreeLock, { struct WaylandPoll * node; struct WaylandPoll * temp; wl_list_for_each_safe(node, temp, &wlWm.pollFree, link) { wl_list_remove(&node->link); free(node); } }); } static void waylandPollRemoveNode(struct WaylandPoll * node) { INTERLOCKED_SECTION(wlWm.pollLock, { wl_list_remove(&node->link); }); } bool waylandPollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events) { struct WaylandPoll * node = malloc(sizeof(*node)); if (!node) return false; node->fd = fd; node->removed = false; node->callback = callback; node->opaque = opaque; INTERLOCKED_SECTION(wlWm.pollLock, { wl_list_insert(&wlWm.poll, &node->link); }); if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_ADD, fd, &(struct epoll_event) { .events = events, .data = (epoll_data_t) { .ptr = node }, }) < 0) { waylandPollRemoveNode(node); free(node); return false; } return true; } bool waylandPollUnregister(int fd) { struct WaylandPoll * node = NULL; INTERLOCKED_SECTION(wlWm.pollLock, { wl_list_for_each(node, &wlWm.poll, link) { if (node->fd == fd) break; } }); if (!node) { DEBUG_ERROR("Attempt to unregister a fd that was not registered: %d", fd); return false; } node->removed = true; if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_DEL, fd, NULL) < 0) { DEBUG_ERROR("Failed to unregistered from epoll: %s", strerror(errno)); return false; } waylandPollRemoveNode(node); INTERLOCKED_SECTION(wlWm.pollFreeLock, { wl_list_insert(&wlWm.pollFree, &node->link); }); return true; } looking-glass-B6/client/displayservers/Wayland/presentation.c000066400000000000000000000065511434445012300247160ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define _GNU_SOURCE #include "wayland.h" #include #include #include #include #include "common/debug.h" #include "common/time.h" struct FrameData { struct timespec sent; }; static void presentationClockId(void * data, struct wp_presentation * presentation, uint32_t clkId) { wlWm.clkId = clkId; } static const struct wp_presentation_listener presentationListener = { .clock_id = presentationClockId, }; static void presentationFeedbackSyncOutput(void * data, struct wp_presentation_feedback * feedback, struct wl_output * output) { // Do nothing. } static void presentationFeedbackPresented(void * opaque, struct wp_presentation_feedback * feedback, uint32_t tvSecHi, uint32_t tvSecLo, uint32_t tvNsec, uint32_t refresh, uint32_t seqHi, uint32_t seqLo, uint32_t flags) { struct FrameData * data = opaque; struct timespec present = { .tv_sec = (uint64_t) tvSecHi << 32 | tvSecLo, .tv_nsec = tvNsec, }; struct timespec delta; tsDiff(&delta, &present, &data->sent); ringbuffer_push(wlWm.photonTimings, &(float){ delta.tv_sec + delta.tv_nsec * 1e-6f }); free(data); wp_presentation_feedback_destroy(feedback); } static void presentationFeedbackDiscarded(void * data, struct wp_presentation_feedback * feedback) { free(data); wp_presentation_feedback_destroy(feedback); } static const struct wp_presentation_feedback_listener presentationFeedbackListener = { .sync_output = presentationFeedbackSyncOutput, .presented = presentationFeedbackPresented, .discarded = presentationFeedbackDiscarded, }; bool waylandPresentationInit(void) { if (wlWm.presentation) { wlWm.photonTimings = ringbuffer_new(256, sizeof(float)); wlWm.photonGraph = app_registerGraph("PHOTON", wlWm.photonTimings, 0.0f, 30.0f, NULL); wp_presentation_add_listener(wlWm.presentation, &presentationListener, NULL); } return true; } void waylandPresentationFree(void) { if (!wlWm.presentation) return; wp_presentation_destroy(wlWm.presentation); app_unregisterGraph(wlWm.photonGraph); ringbuffer_free(&wlWm.photonTimings); } void waylandPresentationFrame(void) { if (!wlWm.presentation) return; struct FrameData * data = malloc(sizeof(*data)); if (clock_gettime(wlWm.clkId, &data->sent)) { DEBUG_ERROR("clock_gettime failed: %s\n", strerror(errno)); free(data); } struct wp_presentation_feedback * feedback = wp_presentation_feedback(wlWm.presentation, wlWm.surface); wp_presentation_feedback_add_listener(feedback, &presentationFeedbackListener, data); } looking-glass-B6/client/displayservers/Wayland/registry.c000066400000000000000000000107311434445012300240460ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "wayland.h" #include #include #include #include "common/debug.h" static void registryGlobalHandler(void * data, struct wl_registry * registry, uint32_t name, const char * interface, uint32_t version) { if (!strcmp(interface, wl_output_interface.name)) waylandOutputBind(name, version); else if (!strcmp(interface, wl_seat_interface.name) && !wlWm.seat) wlWm.seat = wl_registry_bind(wlWm.registry, name, &wl_seat_interface, 1); else if (!strcmp(interface, wl_shm_interface.name)) wlWm.shm = wl_registry_bind(wlWm.registry, name, &wl_shm_interface, 1); else if (!strcmp(interface, wl_compositor_interface.name) && version >= 3) wlWm.compositor = wl_registry_bind(wlWm.registry, name, // we only need v3 to run, but v4 can use eglSwapBuffersWithDamageKHR &wl_compositor_interface, version > 4 ? 4 : version); #ifndef ENABLE_LIBDECOR else if (!strcmp(interface, xdg_wm_base_interface.name)) wlWm.xdgWmBase = wl_registry_bind(wlWm.registry, name, &xdg_wm_base_interface, 1); else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name)) wlWm.xdgDecorationManager = wl_registry_bind(wlWm.registry, name, &zxdg_decoration_manager_v1_interface, 1); #endif else if (!strcmp(interface, wp_presentation_interface.name)) wlWm.presentation = wl_registry_bind(wlWm.registry, name, &wp_presentation_interface, 1); else if (!strcmp(interface, wp_viewporter_interface.name)) wlWm.viewporter = wl_registry_bind(wlWm.registry, name, &wp_viewporter_interface, 1); else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name)) wlWm.relativePointerManager = wl_registry_bind(wlWm.registry, name, &zwp_relative_pointer_manager_v1_interface, 1); else if (!strcmp(interface, zwp_pointer_constraints_v1_interface.name)) wlWm.pointerConstraints = wl_registry_bind(wlWm.registry, name, &zwp_pointer_constraints_v1_interface, 1); else if (!strcmp(interface, zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name)) wlWm.keyboardInhibitManager = wl_registry_bind(wlWm.registry, name, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1); else if (!strcmp(interface, wl_data_device_manager_interface.name) && version >= 3) wlWm.dataDeviceManager = wl_registry_bind(wlWm.registry, name, &wl_data_device_manager_interface, 3); else if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name)) wlWm.idleInhibitManager = wl_registry_bind(wlWm.registry, name, &zwp_idle_inhibit_manager_v1_interface, 1); else if (!strcmp(interface, zxdg_output_manager_v1_interface.name) && version >= 2) wlWm.xdgOutputManager = wl_registry_bind(wlWm.registry, name, // we only need v2 to run, but v3 saves a callback &zxdg_output_manager_v1_interface, version > 3 ? 3 : version); else if (!strcmp(interface, xdg_activation_v1_interface.name)) wlWm.xdgActivation = wl_registry_bind(wlWm.registry, name, &xdg_activation_v1_interface, 1); } static void registryGlobalRemoveHandler(void * data, struct wl_registry * registry, uint32_t name) { waylandOutputTryUnbind(name); } static const struct wl_registry_listener registryListener = { .global = registryGlobalHandler, .global_remove = registryGlobalRemoveHandler, }; bool waylandRegistryInit(void) { wlWm.registry = wl_display_get_registry(wlWm.display); if (!wlWm.registry) { DEBUG_ERROR("Unable to find wl_registry"); return false; } wl_registry_add_listener(wlWm.registry, ®istryListener, NULL); wl_display_roundtrip(wlWm.display); return true; } void waylandRegistryFree(void) { wl_registry_destroy(wlWm.registry); } looking-glass-B6/client/displayservers/Wayland/shell_libdecor.c000066400000000000000000000113771434445012300251570ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "wayland.h" #include #include #include #include #include #include #include "app.h" #include "common/debug.h" struct libdecor_configuration { uint32_t serial; bool has_window_state; enum libdecor_window_state window_state; bool has_size; int window_width; int window_height; }; static void libdecorHandleError(struct libdecor * context, enum libdecor_error error, const char *message) { DEBUG_ERROR("Got libdecor error (%d): %s", error, message); } static void libdecorFrameConfigure(struct libdecor_frame * frame, struct libdecor_configuration * configuration, void * opaque) { if (!wlWm.configured) { xdg_surface_ack_configure(libdecor_frame_get_xdg_surface(frame), configuration->serial); wlWm.configured = true; return; } int width, height; if (libdecor_configuration_get_content_size(configuration, frame, &width, &height)) { wlWm.width = width; wlWm.height = height; struct libdecor_state * state = libdecor_state_new(wlWm.width, wlWm.height); libdecor_frame_commit(wlWm.libdecorFrame, state, NULL); libdecor_state_free(state); } enum libdecor_window_state windowState; if (libdecor_configuration_get_window_state(configuration, &windowState)) wlWm.fullscreen = windowState & LIBDECOR_WINDOW_STATE_FULLSCREEN; wlWm.needsResize = true; wlWm.resizeSerial = configuration->serial; app_invalidateWindow(true); waylandStopWaitFrame(); } static void libdecorFrameClose(struct libdecor_frame * frame, void * opaque) { app_handleCloseEvent(); } static void libdecorFrameCommit(struct libdecor_frame * frame, void * opaque) { } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" static struct libdecor_interface libdecorListener = { libdecorHandleError, }; static struct libdecor_frame_interface libdecorFrameListener = { libdecorFrameConfigure, libdecorFrameClose, libdecorFrameCommit, }; #pragma GCC diagnostic pop static void libdecorCallback(uint32_t events, void * opaque) { libdecor_dispatch(wlWm.libdecor, 0); } bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless, bool resizable) { wlWm.libdecor = libdecor_new(wlWm.display, &libdecorListener); wlWm.libdecorFrame = libdecor_decorate(wlWm.libdecor, wlWm.surface, &libdecorFrameListener, NULL); libdecor_frame_set_app_id(wlWm.libdecorFrame, "looking-glass-client"); libdecor_frame_set_title(wlWm.libdecorFrame, title); libdecor_frame_map(wlWm.libdecorFrame); if (resizable) libdecor_frame_set_capabilities(wlWm.libdecorFrame, LIBDECOR_ACTION_RESIZE); else libdecor_frame_unset_capabilities(wlWm.libdecorFrame, LIBDECOR_ACTION_RESIZE); while (!wlWm.configured) libdecor_dispatch(wlWm.libdecor, 0); if (!waylandPollRegister(libdecor_get_fd(wlWm.libdecor), libdecorCallback, NULL, EPOLLIN)) { DEBUG_ERROR("Failed register display to epoll: %s", strerror(errno)); return false; } return true; } void waylandShellAckConfigureIfNeeded(void) { if (wlWm.resizeSerial) { xdg_surface_ack_configure(libdecor_frame_get_xdg_surface(wlWm.libdecorFrame), wlWm.resizeSerial); wlWm.resizeSerial = 0; } } void waylandSetFullscreen(bool fs) { if (fs) libdecor_frame_set_fullscreen(wlWm.libdecorFrame, NULL); else libdecor_frame_unset_fullscreen(wlWm.libdecorFrame); libdecor_frame_set_visibility(wlWm.libdecorFrame, !fs); } bool waylandGetFullscreen(void) { return wlWm.fullscreen; } void waylandMinimize(void) { libdecor_frame_set_minimized(wlWm.libdecorFrame); } void waylandShellResize(int w, int h) { if (!libdecor_frame_is_floating(wlWm.libdecorFrame)) return; wlWm.width = w; wlWm.height = h; struct libdecor_state * state = libdecor_state_new(w, h); libdecor_frame_commit(wlWm.libdecorFrame, state, NULL); libdecor_state_free(state); wlWm.needsResize = true; app_invalidateWindow(true); waylandStopWaitFrame(); } looking-glass-B6/client/displayservers/Wayland/shell_xdg.c000066400000000000000000000111741434445012300241510ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "wayland.h" #include #include #include "app.h" #include "common/debug.h" // XDG WM base listeners. static void xdgWmBasePing(void * data, struct xdg_wm_base * xdgWmBase, uint32_t serial) { xdg_wm_base_pong(xdgWmBase, serial); } static const struct xdg_wm_base_listener xdgWmBaseListener = { .ping = xdgWmBasePing, }; // XDG Surface listeners. static void xdgSurfaceConfigure(void * data, struct xdg_surface * xdgSurface, uint32_t serial) { if (wlWm.configured) { wlWm.needsResize = true; wlWm.resizeSerial = serial; app_invalidateWindow(true); waylandStopWaitFrame(); } else { xdg_surface_ack_configure(xdgSurface, serial); wlWm.configured = true; } } static const struct xdg_surface_listener xdgSurfaceListener = { .configure = xdgSurfaceConfigure, }; // XDG Toplevel listeners. static void xdgToplevelConfigure(void * data, struct xdg_toplevel * xdgToplevel, int32_t width, int32_t height, struct wl_array * states) { wlWm.width = width; wlWm.height = height; wlWm.fullscreen = false; wlWm.floating = true; enum xdg_toplevel_state * state; wl_array_for_each(state, states) { switch (*state) { case XDG_TOPLEVEL_STATE_FULLSCREEN: wlWm.fullscreen = true; // fallthrough case XDG_TOPLEVEL_STATE_MAXIMIZED: case XDG_TOPLEVEL_STATE_TILED_LEFT: case XDG_TOPLEVEL_STATE_TILED_RIGHT: case XDG_TOPLEVEL_STATE_TILED_TOP: case XDG_TOPLEVEL_STATE_TILED_BOTTOM: wlWm.floating = false; break; default: break; } } } static void xdgToplevelClose(void * data, struct xdg_toplevel * xdgToplevel) { app_handleCloseEvent(); } static const struct xdg_toplevel_listener xdgToplevelListener = { .configure = xdgToplevelConfigure, .close = xdgToplevelClose, }; bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless, bool resizable) { if (!wlWm.xdgWmBase) { DEBUG_ERROR("Compositor missing xdg_wm_base, will not proceed"); return false; } xdg_wm_base_add_listener(wlWm.xdgWmBase, &xdgWmBaseListener, NULL); wlWm.xdgSurface = xdg_wm_base_get_xdg_surface(wlWm.xdgWmBase, wlWm.surface); xdg_surface_add_listener(wlWm.xdgSurface, &xdgSurfaceListener, NULL); wlWm.xdgToplevel = xdg_surface_get_toplevel(wlWm.xdgSurface); xdg_toplevel_add_listener(wlWm.xdgToplevel, &xdgToplevelListener, NULL); xdg_toplevel_set_title(wlWm.xdgToplevel, title); xdg_toplevel_set_app_id(wlWm.xdgToplevel, "looking-glass-client"); if (fullscreen) xdg_toplevel_set_fullscreen(wlWm.xdgToplevel, NULL); if (maximize) xdg_toplevel_set_maximized(wlWm.xdgToplevel); if (wlWm.xdgDecorationManager) { wlWm.xdgToplevelDecoration = zxdg_decoration_manager_v1_get_toplevel_decoration( wlWm.xdgDecorationManager, wlWm.xdgToplevel); if (wlWm.xdgToplevelDecoration) { zxdg_toplevel_decoration_v1_set_mode(wlWm.xdgToplevelDecoration, borderless ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE : ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); } } return true; } void waylandShellAckConfigureIfNeeded(void) { if (wlWm.resizeSerial) { xdg_surface_ack_configure(wlWm.xdgSurface, wlWm.resizeSerial); wlWm.resizeSerial = 0; } } void waylandSetFullscreen(bool fs) { if (fs) xdg_toplevel_set_fullscreen(wlWm.xdgToplevel, NULL); else xdg_toplevel_unset_fullscreen(wlWm.xdgToplevel); } bool waylandGetFullscreen(void) { return wlWm.fullscreen; } void waylandMinimize(void) { xdg_toplevel_set_minimized(wlWm.xdgToplevel); } void waylandShellResize(int w, int h) { if (!wlWm.floating) return; wlWm.width = w; wlWm.height = h; xdg_surface_set_window_geometry(wlWm.xdgSurface, 0, 0, w, h); wlWm.needsResize = true; app_invalidateWindow(true); waylandStopWaitFrame(); } looking-glass-B6/client/displayservers/Wayland/state.c000066400000000000000000000016011434445012300233120ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "wayland.h" struct WaylandDSState wlWm; struct WCBState wlCb; looking-glass-B6/client/displayservers/Wayland/wayland.c000066400000000000000000000126541434445012300236430ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define _GNU_SOURCE #include "wayland.h" #include #include #include #include "common/debug.h" #include "common/option.h" static struct Option waylandOptions[] = { { .module = "wayland", .name = "warpSupport", .description = "Enable cursor warping", .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "wayland", .name = "fractionScale", .description = "Enable fractional scale", .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, {0} }; static bool waylandEarlyInit(void) { // Request to receive EPIPE instead of SIGPIPE when one end of a pipe // disconnects while a write is pending. This is useful to the Wayland // clipboard backend, where an arbitrary application is on the other end of // that pipe. signal(SIGPIPE, SIG_IGN); return true; } static void waylandSetup(void) { option_register(waylandOptions); } static bool waylandProbe(void) { return getenv("WAYLAND_DISPLAY") != NULL; } static bool waylandInit(const LG_DSInitParams params) { memset(&wlWm, 0, sizeof(wlWm)); wl_list_init(&wlWm.surfaceOutputs); wlWm.warpSupport = option_get_bool("wayland", "warpSupport"); wlWm.useFractionalScale = option_get_bool("wayland", "fractionScale"); wlWm.display = wl_display_connect(NULL); wlWm.width = params.w; wlWm.height = params.h; if (!waylandPollInit()) return false; if (!waylandOutputInit()) return false; if (!waylandRegistryInit()) return false; if (!waylandActivationInit()) return false; if (!waylandIdleInit()) return false; if (!waylandPresentationInit()) return false; if (!waylandCursorInit()) return false; if (!waylandInputInit()) return false; if (!waylandWindowInit(params.title, params.fullscreen, params.maximize, params.borderless, params.resizable)) return false; if (!waylandEGLInit(params.w, params.h)) return false; #ifdef ENABLE_OPENGL if (params.opengl && !waylandOpenGLInit()) return false; #endif wlWm.width = params.w; wlWm.height = params.h; return true; } static void waylandStartup(void) { } static void waylandShutdown(void) { } static void waylandFree(void) { waylandIdleFree(); waylandWindowFree(); waylandPresentationFree(); waylandInputFree(); waylandOutputFree(); waylandRegistryFree(); waylandCursorFree(); wl_display_disconnect(wlWm.display); } static bool waylandGetProp(LG_DSProperty prop, void * ret) { if (prop == LG_DS_WARP_SUPPORT) { *(enum LG_DSWarpSupport*)ret = wlWm.warpSupport ? LG_DS_WARP_SURFACE : LG_DS_WARP_NONE; return true; } return false; } struct LG_DisplayServerOps LGDS_Wayland = { .name = "Wayland", .setup = waylandSetup, .probe = waylandProbe, .earlyInit = waylandEarlyInit, .init = waylandInit, .startup = waylandStartup, .shutdown = waylandShutdown, .free = waylandFree, .getProp = waylandGetProp, #ifdef ENABLE_EGL .getEGLDisplay = waylandGetEGLDisplay, .getEGLNativeWindow = waylandGetEGLNativeWindow, .eglSwapBuffers = waylandEGLSwapBuffers, #endif #ifdef ENABLE_OPENGL .glCreateContext = waylandGLCreateContext, .glDeleteContext = waylandGLDeleteContext, .glMakeCurrent = waylandGLMakeCurrent, .glSetSwapInterval = waylandGLSetSwapInterval, .glSwapBuffers = waylandGLSwapBuffers, #endif .waitFrame = waylandWaitFrame, .skipFrame = waylandSkipFrame, .stopWaitFrame = waylandStopWaitFrame, .guestPointerUpdated = waylandGuestPointerUpdated, .setPointer = waylandSetPointer, .grabPointer = waylandGrabPointer, .ungrabPointer = waylandUngrabPointer, .capturePointer = waylandCapturePointer, .uncapturePointer = waylandUncapturePointer, .grabKeyboard = waylandGrabKeyboard, .ungrabKeyboard = waylandUngrabKeyboard, .warpPointer = waylandWarpPointer, .realignPointer = waylandRealignPointer, .isValidPointerPos = waylandIsValidPointerPos, .requestActivation = waylandActivationRequestActivation, .inhibitIdle = waylandInhibitIdle, .uninhibitIdle = waylandUninhibitIdle, .wait = waylandWait, .setWindowSize = waylandSetWindowSize, .setFullscreen = waylandSetFullscreen, .getFullscreen = waylandGetFullscreen, .minimize = waylandMinimize, .cbInit = waylandCBInit, .cbNotice = waylandCBNotice, .cbRelease = waylandCBRelease, .cbRequest = waylandCBRequest }; looking-glass-B6/client/displayservers/Wayland/wayland.h000066400000000000000000000221241434445012300236410ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #if defined(ENABLE_EGL) || defined(ENABLE_OPENGL) # include # include # include # include "eglutil.h" #endif #include "app.h" #include "egl_dynprocs.h" #include "common/locking.h" #include "common/countedbuffer.h" #include "common/ringbuffer.h" #include "interface/displayserver.h" #include "wayland-xdg-shell-client-protocol.h" #include "wayland-presentation-time-client-protocol.h" #include "wayland-viewporter-client-protocol.h" #include "wayland-xdg-decoration-unstable-v1-client-protocol.h" #include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h" #include "wayland-pointer-constraints-unstable-v1-client-protocol.h" #include "wayland-relative-pointer-unstable-v1-client-protocol.h" #include "wayland-idle-inhibit-unstable-v1-client-protocol.h" #include "wayland-xdg-output-unstable-v1-client-protocol.h" #include "wayland-xdg-activation-v1-client-protocol.h" typedef void (*WaylandPollCallback)(uint32_t events, void * opaque); struct WaylandPoll { int fd; bool removed; WaylandPollCallback callback; void * opaque; struct wl_list link; }; struct WaylandOutput { uint32_t name; wl_fixed_t scale; int32_t scaleInt; int32_t logicalWidth; int32_t logicalHeight; int32_t modeWidth; int32_t modeHeight; bool modeRotate; struct wl_output * output; struct zxdg_output_v1 * xdgOutput; uint32_t version; struct wl_list link; }; struct SurfaceOutput { struct wl_output * output; struct wl_list link; }; enum EGLSwapWithDamageState { SWAP_WITH_DAMAGE_UNKNOWN, SWAP_WITH_DAMAGE_UNSUPPORTED, SWAP_WITH_DAMAGE_KHR, SWAP_WITH_DAMAGE_EXT, }; struct xkb_context; struct xkb_keymap; struct xkb_state; struct WaylandDSState { bool pointerGrabbed; bool keyboardGrabbed; bool pointerInSurface; bool focusedOnSurface; struct wl_display * display; struct wl_surface * surface; struct wl_registry * registry; struct wl_seat * seat; struct wl_shm * shm; struct wl_compositor * compositor; int32_t width, height; wl_fixed_t scale; bool fractionalScale; bool needsResize; bool fullscreen; bool floating; uint32_t resizeSerial; bool configured; bool warpSupport; double cursorX, cursorY; #if defined(ENABLE_EGL) || defined(ENABLE_OPENGL) struct wl_egl_window * eglWindow; struct SwapWithDamageData swapWithDamage; #endif #ifdef ENABLE_OPENGL EGLDisplay glDisplay; EGLConfig glConfig; EGLSurface glSurface; #endif struct wp_presentation * presentation; clockid_t clkId; RingBuffer photonTimings; GraphHandle photonGraph; #ifdef ENABLE_LIBDECOR struct libdecor * libdecor; struct libdecor_frame * libdecorFrame; #else struct xdg_wm_base * xdgWmBase; struct xdg_surface * xdgSurface; struct xdg_toplevel * xdgToplevel; struct zxdg_decoration_manager_v1 * xdgDecorationManager; struct zxdg_toplevel_decoration_v1 * xdgToplevelDecoration; #endif const char * cursorThemeName; int cursorSize; int cursorScale; struct wl_cursor_theme * cursorTheme; struct wl_buffer * cursorSquareBuffer; struct wl_surface * cursors[LG_POINTER_COUNT]; struct Point cursorHot[LG_POINTER_COUNT]; LG_DSPointer cursorId; struct wl_surface * cursor; int cursorHotX; int cursorHotY; struct wl_data_device_manager * dataDeviceManager; uint32_t capabilities; struct wl_keyboard * keyboard; struct zwp_keyboard_shortcuts_inhibit_manager_v1 * keyboardInhibitManager; struct zwp_keyboard_shortcuts_inhibitor_v1 * keyboardInhibitor; uint32_t keyboardEnterSerial; struct xkb_context * xkb; struct xkb_state * xkbState; struct xkb_keymap * keymap; struct wl_pointer * pointer; struct zwp_relative_pointer_manager_v1 * relativePointerManager; struct zwp_pointer_constraints_v1 * pointerConstraints; struct zwp_relative_pointer_v1 * relativePointer; struct zwp_confined_pointer_v1 * confinedPointer; struct zwp_locked_pointer_v1 * lockedPointer; bool showPointer; uint32_t pointerEnterSerial; LG_Lock confineLock; struct zwp_idle_inhibit_manager_v1 * idleInhibitManager; struct zwp_idle_inhibitor_v1 * idleInhibitor; struct xdg_activation_v1 * xdgActivation; struct wp_viewporter * viewporter; struct wp_viewport * viewport; struct zxdg_output_manager_v1 * xdgOutputManager; struct wl_list outputs; // WaylandOutput::link struct wl_list surfaceOutputs; // SurfaceOutput::link bool useFractionalScale; LGEvent * frameEvent; struct wl_list poll; // WaylandPoll::link struct wl_list pollFree; // WaylandPoll::link LG_Lock pollLock; LG_Lock pollFreeLock; int epollFd; int displayFd; }; struct WCBTransfer { struct CountedBuffer * data; const char ** mimetypes; }; struct ClipboardRead { int fd; size_t size; size_t numRead; uint8_t * buf; enum LG_ClipboardData type; struct wl_data_offer * offer; }; struct WCBState { struct wl_data_device * dataDevice; char lgMimetype[64]; char * mimetypes[LG_CLIPBOARD_DATA_NONE]; struct wl_data_offer * offer; struct wl_data_offer * dndOffer; bool haveRequest; LG_ClipboardData type; struct ClipboardRead * currentRead; }; extern struct WaylandDSState wlWm; extern struct WCBState wlCb; // activation module bool waylandActivationInit(void); void waylandActivationFree(void); void waylandActivationRequestActivation(void); // clipboard module bool waylandCBInit(void); void waylandCBRequest(LG_ClipboardData type); void waylandCBNotice(LG_ClipboardData type); void waylandCBRelease(void); void waylandCBInvalidate(void); // cursor module bool waylandCursorInit(void); void waylandCursorFree(void); void waylandSetPointer(LG_DSPointer pointer); void waylandCursorScaleChange(void); // gl module #if defined(ENABLE_EGL) || defined(ENABLE_OPENGL) bool waylandEGLInit(int w, int h); EGLDisplay waylandGetEGLDisplay(void); void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count); #endif #ifdef ENABLE_EGL EGLNativeWindowType waylandGetEGLNativeWindow(void); #endif #ifdef ENABLE_OPENGL bool waylandOpenGLInit(void); LG_DSGLContext waylandGLCreateContext(void); void waylandGLDeleteContext(LG_DSGLContext context); void waylandGLMakeCurrent(LG_DSGLContext context); void waylandGLSetSwapInterval(int interval); void waylandGLSwapBuffers(void); #endif // idle module bool waylandIdleInit(void); void waylandIdleFree(void); void waylandInhibitIdle(void); void waylandUninhibitIdle(void); // input module bool waylandInputInit(void); void waylandInputFree(void); void waylandGrabKeyboard(void); void waylandGrabPointer(void); void waylandUngrabKeyboard(void); void waylandUngrabPointer(void); void waylandCapturePointer(void); void waylandUncapturePointer(void); void waylandRealignPointer(void); void waylandWarpPointer(int x, int y, bool exiting); void waylandGuestPointerUpdated(double x, double y, double localX, double localY); // output module bool waylandOutputInit(void); void waylandOutputFree(void); void waylandOutputBind(uint32_t name, uint32_t version); void waylandOutputTryUnbind(uint32_t name); wl_fixed_t waylandOutputGetScale(struct wl_output * output); // poll module bool waylandPollInit(void); void waylandWait(unsigned int time); bool waylandPollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events); bool waylandPollUnregister(int fd); // presentation module bool waylandPresentationInit(void); void waylandPresentationFrame(void); void waylandPresentationFree(void); // registry module bool waylandRegistryInit(void); void waylandRegistryFree(void); // shell module bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless, bool resizable); void waylandShellAckConfigureIfNeeded(void); void waylandSetFullscreen(bool fs); bool waylandGetFullscreen(void); void waylandMinimize(void); void waylandShellResize(int w, int h); // window module bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless, bool resizable); void waylandWindowFree(void); void waylandWindowUpdateScale(void); void waylandSetWindowSize(int x, int y); bool waylandIsValidPointerPos(int x, int y); bool waylandWaitFrame(void); void waylandSkipFrame(void); void waylandStopWaitFrame(void); looking-glass-B6/client/displayservers/Wayland/window.c000066400000000000000000000101521434445012300235020ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "wayland.h" #include #include #include #include "app.h" #include "common/debug.h" #include "common/event.h" // Surface-handling listeners. void waylandWindowUpdateScale(void) { wl_fixed_t maxScale = 0; struct SurfaceOutput * node; wl_list_for_each(node, &wlWm.surfaceOutputs, link) { wl_fixed_t scale = waylandOutputGetScale(node->output); if (scale > maxScale) maxScale = scale; } if (maxScale) { wlWm.scale = maxScale; wlWm.fractionalScale = wl_fixed_from_int(wl_fixed_to_int(maxScale)) != maxScale; wlWm.needsResize = true; waylandCursorScaleChange(); app_invalidateWindow(true); waylandStopWaitFrame(); } } static void wlSurfaceEnterHandler(void * data, struct wl_surface * surface, struct wl_output * output) { struct SurfaceOutput * node = malloc(sizeof(*node)); if (!node) { DEBUG_ERROR("out of memory"); return; } node->output = output; wl_list_insert(&wlWm.surfaceOutputs, &node->link); waylandWindowUpdateScale(); } static void wlSurfaceLeaveHandler(void * data, struct wl_surface * surface, struct wl_output * output) { struct SurfaceOutput * node; wl_list_for_each(node, &wlWm.surfaceOutputs, link) if (node->output == output) { wl_list_remove(&node->link); break; } waylandWindowUpdateScale(); } static const struct wl_surface_listener wlSurfaceListener = { .enter = wlSurfaceEnterHandler, .leave = wlSurfaceLeaveHandler, }; bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless, bool resizable) { wlWm.scale = wl_fixed_from_int(1); wlWm.frameEvent = lgCreateEvent(true, 0); if (!wlWm.frameEvent) { DEBUG_ERROR("Failed to initialize event for waitFrame"); return false; } lgSignalEvent(wlWm.frameEvent); if (!wlWm.compositor) { DEBUG_ERROR("Compositor missing wl_compositor (version 3+), will not proceed"); return false; } wlWm.surface = wl_compositor_create_surface(wlWm.compositor); if (!wlWm.surface) { DEBUG_ERROR("Failed to create wl_surface"); return false; } wl_surface_add_listener(wlWm.surface, &wlSurfaceListener, NULL); if (!waylandShellInit(title, fullscreen, maximize, borderless, resizable)) return false; wl_surface_commit(wlWm.surface); return true; } void waylandWindowFree(void) { wl_surface_destroy(wlWm.surface); lgFreeEvent(wlWm.frameEvent); } void waylandSetWindowSize(int x, int y) { waylandShellResize(x, y); } bool waylandIsValidPointerPos(int x, int y) { return x >= 0 && x < wlWm.width && y >= 0 && y < wlWm.height; } static void frameHandler(void * opaque, struct wl_callback * callback, unsigned int data) { lgSignalEvent(wlWm.frameEvent); wl_callback_destroy(callback); } static const struct wl_callback_listener frame_listener = { .done = frameHandler, }; bool waylandWaitFrame(void) { lgWaitEvent(wlWm.frameEvent, TIMEOUT_INFINITE); struct wl_callback * callback = wl_surface_frame(wlWm.surface); if (callback) wl_callback_add_listener(callback, &frame_listener, NULL); return false; } void waylandSkipFrame(void) { // If we decided to not render, we must commit the surface so that the callback is registered. wl_surface_commit(wlWm.surface); } void waylandStopWaitFrame(void) { lgSignalEvent(wlWm.frameEvent); } looking-glass-B6/client/displayservers/X11/000077500000000000000000000000001434445012300210025ustar00rootroot00000000000000looking-glass-B6/client/displayservers/X11/CMakeLists.txt000066400000000000000000000007731434445012300235510ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(displayserver_X11 LANGUAGES C) find_package(PkgConfig) pkg_check_modules(DISPLAYSERVER_X11 REQUIRED IMPORTED_TARGET x11 xi xfixes xscrnsaver xinerama xcursor xpresent xkbcommon ) add_library(displayserver_X11 STATIC x11.c atoms.c clipboard.c ) add_definitions(-D GLX_GLXEXT_PROTOTYPES) target_link_libraries(displayserver_X11 PkgConfig::DISPLAYSERVER_X11 lg_common ) target_include_directories(displayserver_X11 PRIVATE src ) looking-glass-B6/client/displayservers/X11/atoms.c000066400000000000000000000020341434445012300222700ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "atoms.h" #include "x11.h" struct X11DSAtoms x11atoms = { 0 }; void X11AtomsInit(void) { #define DEF_ATOM(x, onlyIfExists) \ x11atoms.x = XInternAtom(x11.display, #x, onlyIfExists); DEF_ATOMS() #undef DEF_ATOM } looking-glass-B6/client/displayservers/X11/atoms.h000066400000000000000000000037031434445012300223010ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_X11DS_ATOMS_ #define _H_X11DS_ATOMS_ #define DEF_ATOMS() \ DEF_ATOM(_NET_SUPPORTING_WM_CHECK, True) \ DEF_ATOM(_NET_SUPPORTED, True) \ DEF_ATOM(_NET_WM_NAME, True) \ DEF_ATOM(_NET_REQUEST_FRAME_EXTENTS, True) \ DEF_ATOM(_NET_FRAME_EXTENTS, True) \ DEF_ATOM(_NET_WM_BYPASS_COMPOSITOR, False) \ DEF_ATOM(_NET_WM_ICON, True) \ DEF_ATOM(_NET_WM_STATE, True) \ DEF_ATOM(_NET_WM_STATE_FULLSCREEN, True) \ DEF_ATOM(_NET_WM_STATE_FOCUSED, True) \ DEF_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ, True) \ DEF_ATOM(_NET_WM_STATE_MAXIMIZED_VERT, True) \ DEF_ATOM(_NET_WM_STATE_DEMANDS_ATTENTION, True) \ DEF_ATOM(_NET_WM_WINDOW_TYPE, True) \ DEF_ATOM(_NET_WM_WINDOW_TYPE_NORMAL, True) \ DEF_ATOM(_NET_WM_WINDOW_TYPE_UTILITY, True) \ DEF_ATOM(_NET_WM_PID, True) \ DEF_ATOM(WM_DELETE_WINDOW, True) \ DEF_ATOM(_MOTIF_WM_HINTS, True) \ \ DEF_ATOM(CLIPBOARD, False) \ DEF_ATOM(TARGETS, False) \ DEF_ATOM(SEL_DATA, False) \ DEF_ATOM(INCR, False) #include #define DEF_ATOM(x, onlyIfExists) Atom x; struct X11DSAtoms { DEF_ATOMS() }; #undef DEF_ATOM extern struct X11DSAtoms x11atoms; void X11AtomsInit(void); #endif looking-glass-B6/client/displayservers/X11/clipboard.c000066400000000000000000000227041434445012300231120ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "clipboard.h" #include "x11.h" #include "atoms.h" #include #include #include #include #include "app.h" #include "common/debug.h" struct X11ClipboardState { Atom aCurSelection; Atom aTypes[LG_CLIPBOARD_DATA_NONE]; LG_ClipboardData type; bool haveRequest; bool incrStart; unsigned int lowerBound; }; static const char * atomTypes[] = { "UTF8_STRING", "image/png", "image/bmp", "image/tiff", "image/jpeg" }; static struct X11ClipboardState x11cb; // forwards static void x11CBSelectionRequest(const XSelectionRequestEvent e); static void x11CBSelectionClear(const XSelectionClearEvent e); static void x11CBSelectionIncr(const XPropertyEvent e); static void x11CBSelectionNotify(const XSelectionEvent e); static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e); bool x11CBEventThread(const XEvent xe) { switch(xe.type) { case SelectionRequest: x11CBSelectionRequest(xe.xselectionrequest); return true; case SelectionClear: x11CBSelectionClear(xe.xselectionclear); return true; case SelectionNotify: x11CBSelectionNotify(xe.xselection); return true; case PropertyNotify: if (xe.xproperty.state != PropertyNewValue) break; if (xe.xproperty.atom == x11atoms.SEL_DATA) { if (x11cb.lowerBound == 0) return true; x11CBSelectionIncr(xe.xproperty); return true; } break; default: if (xe.type == x11.eventBase + XFixesSelectionNotify) { XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)&xe; x11CBXFixesSelectionNotify(*sne); return true; } break; } return false; } bool x11CBInit(void) { x11cb.aCurSelection = BadValue; for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) { x11cb.aTypes[i] = XInternAtom(x11.display, atomTypes[i], False); if (x11cb.aTypes[i] == BadAlloc || x11cb.aTypes[i] == BadValue) { DEBUG_ERROR("failed to get atom for type: %s", atomTypes[i]); return false; } } // use xfixes to get clipboard change notifications if (!XFixesQueryExtension(x11.display, &x11.eventBase, &x11.errorBase)) { DEBUG_ERROR("failed to initialize xfixes"); return false; } XFixesSelectSelectionInput(x11.display, x11.window, x11atoms.CLIPBOARD, XFixesSetSelectionOwnerNotifyMask); return true; } static void x11CBReplyFn(void * opaque, LG_ClipboardData type, uint8_t * data, uint32_t size) { XEvent *s = (XEvent *)opaque; XChangeProperty( x11.display , s->xselection.requestor, s->xselection.property , s->xselection.target , 8, PropModeReplace, data, size); XSendEvent(x11.display, s->xselection.requestor, 0, 0, s); XFlush(x11.display); free(s); } static void x11CBSelectionRequest(const XSelectionRequestEvent e) { XEvent * s = malloc(sizeof(*s)); if (!s) { DEBUG_ERROR("out of memory"); return; } s->xselection.type = SelectionNotify; s->xselection.requestor = e.requestor; s->xselection.selection = e.selection; s->xselection.target = e.target; s->xselection.property = e.property; s->xselection.time = e.time; if (!x11cb.haveRequest) goto nodata; // target list requested if (e.target == x11atoms.TARGETS) { Atom targets[2]; targets[0] = x11atoms.TARGETS; targets[1] = x11cb.aTypes[x11cb.type]; XChangeProperty( e.display, e.requestor, e.property, XA_ATOM, 32, PropModeReplace, (unsigned char*)targets, sizeof(targets) / sizeof(Atom)); goto send; } // look to see if we can satisfy the data type for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) if (x11cb.aTypes[i] == e.target && x11cb.type == i) { // request the data app_clipboardRequest(x11CBReplyFn, s); return; } nodata: // report no data s->xselection.property = None; send: XSendEvent(x11.display, e.requestor, 0, 0, s); XFlush(x11.display); free(s); } static void x11CBSelectionClear(const XSelectionClearEvent e) { if (e.selection != x11atoms.CLIPBOARD) return; x11cb.aCurSelection = BadValue; app_clipboardRelease(); return; } static void x11CBSelectionIncr(const XPropertyEvent e) { Atom type; int format; unsigned long itemCount, after; unsigned char *data; if (XGetWindowProperty( e.display, e.window, e.atom, 0, ~0L, // start and length True, // delete the property x11atoms.INCR, &type, &format, &itemCount, &after, &data) != Success) { DEBUG_INFO("GetProp Failed"); app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0); goto out; } LG_ClipboardData dataType; for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType) if (x11cb.aTypes[dataType] == type) break; if (dataType == LG_CLIPBOARD_DATA_NONE) { DEBUG_WARN("clipboard data (%s) not in a supported format", XGetAtomName(x11.display, type)); x11cb.lowerBound = 0; app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0); goto out; } if (x11cb.incrStart) { app_clipboardNotifySize(dataType, x11cb.lowerBound); x11cb.incrStart = false; } XFree(data); data = NULL; if (XGetWindowProperty( e.display, e.window, e.atom, 0, ~0L, // start and length True, // delete the property type, &type, &format, &itemCount, &after, &data) != Success) { DEBUG_ERROR("XGetWindowProperty Failed"); app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0); goto out; } app_clipboardData(dataType, data, itemCount); x11cb.lowerBound -= itemCount; out: if (data) XFree(data); } static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e) { // check if the selection is valid and it isn't ourself if (e.selection != x11atoms.CLIPBOARD || e.owner == x11.window || e.owner == 0) { return; } // remember which selection we are working with x11cb.aCurSelection = e.selection; XConvertSelection( x11.display, e.selection, x11atoms.TARGETS, x11atoms.TARGETS, x11.window, CurrentTime); return; } static void x11CBSelectionNotify(const XSelectionEvent e) { if (e.property == None) return; Atom type; int format; unsigned long itemCount, after; unsigned char *data; if (XGetWindowProperty( e.display, e.requestor, e.property, 0, ~0L, // start and length True , // delete the property AnyPropertyType, &type, &format, &itemCount, &after, &data) != Success) { app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0); goto out; } if (type == x11atoms.INCR) { x11cb.incrStart = true; x11cb.lowerBound = *(unsigned int *)data; goto out; } // the target list if (e.property == x11atoms.TARGETS) { // the format is 32-bit and we must have data // this is technically incorrect however as it's // an array of padded 64-bit values if (!data || format != 32) goto out; int typeCount = 0; LG_ClipboardData types[itemCount]; // see if we support any of the targets listed const uint64_t * targets = (const uint64_t *)data; for(unsigned long i = 0; i < itemCount; ++i) { for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n) if (x11cb.aTypes[n] == targets[i]) types[typeCount++] = n; } app_clipboardNotifyTypes(types, typeCount); goto out; } if (e.property == x11atoms.SEL_DATA) { LG_ClipboardData dataType; for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType) if (x11cb.aTypes[dataType] == type) break; if (dataType == LG_CLIPBOARD_DATA_NONE) { DEBUG_WARN("clipboard data (%s) not in a supported format", XGetAtomName(x11.display, type)); goto out; } app_clipboardData(dataType, data, itemCount); goto out; } out: if (data) XFree(data); } void x11CBNotice(LG_ClipboardData type) { x11cb.haveRequest = true; x11cb.type = type; XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD, x11.window, CurrentTime); XFlush(x11.display); } void x11CBRelease(void) { x11cb.haveRequest = false; XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD, None, CurrentTime); XFlush(x11.display); } void x11CBRequest(LG_ClipboardData type) { if (x11cb.aCurSelection == BadValue) return; XConvertSelection( x11.display, x11cb.aCurSelection, x11cb.aTypes[type], x11atoms.SEL_DATA, x11.window, CurrentTime); } looking-glass-B6/client/displayservers/X11/clipboard.h000066400000000000000000000021761434445012300231200ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_X11DS_CLIPBOARD_ #define _H_X11DS_CLIPBOARD_ #include #include #include "interface/displayserver.h" bool x11CBEventThread(const XEvent xe); bool x11CBInit(void); void x11CBNotice(LG_ClipboardData type); void x11CBRelease(void); void x11CBRequest(LG_ClipboardData type); #endif looking-glass-B6/client/displayservers/X11/x11.c000066400000000000000000001371251434445012300215700ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/displayserver.h" #include "x11.h" #include "atoms.h" #include "clipboard.h" #include "resources/icondata.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_EGL #include #include "egl_dynprocs.h" #include "eglutil.h" #endif #include "app.h" #include "common/debug.h" #include "common/time.h" #include "common/event.h" #include "util.h" #define _NET_WM_STATE_REMOVE 0 #define _NET_WM_STATE_ADD 1 #define _NET_WM_STATE_TOGGLE 2 struct X11DSState x11; struct MwmHints { unsigned long flags; unsigned long functions; unsigned long decorations; long input_mode; unsigned long status; }; enum { MWM_HINTS_FUNCTIONS = (1L << 0), MWM_HINTS_DECORATIONS = (1L << 1), MWM_FUNC_ALL = (1L << 0), MWM_FUNC_RESIZE = (1L << 1), MWM_FUNC_MOVE = (1L << 2), MWM_FUNC_MINIMIZE = (1L << 3), MWM_FUNC_MAXIMIZE = (1L << 4), MWM_FUNC_CLOSE = (1L << 5) }; // forwards static void x11SetFullscreen(bool fs); static int x11EventThread(void * unused); static void x11XInputEvent(XGenericEventCookie *cookie); static void x11XPresentEvent(XGenericEventCookie *cookie); static void x11GrabPointer(void); static void x11DoPresent(uint64_t msc) { static bool startup = true; if (startup) { XPresentPixmap( x11.display, x11.window, x11.presentPixmap, x11.presentSerial++, x11.presentRegion, // valid x11.presentRegion, // update 0, // x_off, 0, // y_off, None, // target_crtc None, // wait_fence None, // idle_fence PresentOptionNone, // options 0, // target_msc, 0, // divisor, 0, // remainder, NULL, // notifies 0 // nnotifies ); startup = false; return; } static bool first = true; static uint64_t lastMsc = 0; uint64_t refill; if (!first) { const uint64_t delta = (lastMsc >= msc) ? lastMsc - msc : ~0ULL - msc + lastMsc; if (delta > 50) return; refill = 50 - delta; } else { refill = 50; first = false; lastMsc = msc; } if (refill < 25) return; for(int i = 0; i < refill; ++i) { XPresentPixmap( x11.display, x11.window, x11.presentPixmap, x11.presentSerial++, x11.presentRegion, // valid x11.presentRegion, // update 0, // x_off, 0, // y_off, None, // target_crtc None, // wait_fence None, // idle_fence PresentOptionNone, // options ++lastMsc, // target_msc, 0, // divisor, 0, // remainder, NULL, // notifies 0 // nnotifies ); } } static void x11Setup(void) { } static bool x11Probe(void) { return getenv("DISPLAY") != NULL; } static bool x11EarlyInit(void) { XInitThreads(); return true; } static void x11CheckEWMHSupport(void) { if (x11atoms._NET_SUPPORTING_WM_CHECK == None || x11atoms._NET_SUPPORTED == None) return; Atom type; int fmt; unsigned long num, bytes; unsigned char *data; if (XGetWindowProperty(x11.display, DefaultRootWindow(x11.display), x11atoms._NET_SUPPORTING_WM_CHECK, 0, ~0L, False, XA_WINDOW, &type, &fmt, &num, &bytes, &data) != Success || !data) goto out; Window * windowFromRoot = (Window *)data; if (XGetWindowProperty(x11.display, *windowFromRoot, x11atoms._NET_SUPPORTING_WM_CHECK, 0, ~0L, False, XA_WINDOW, &type, &fmt, &num, &bytes, &data) != Success || !data) goto out_root; Window * windowFromChild = (Window *)data; if (*windowFromChild != *windowFromRoot) goto out_child; if (XGetWindowProperty(x11.display, DefaultRootWindow(x11.display), x11atoms._NET_SUPPORTED, 0, ~0L, False, AnyPropertyType, &type, &fmt, &num, &bytes, &data) != Success || !data) goto out_child; Atom * supported = (Atom *)data; unsigned long supportedCount = num; if (XGetWindowProperty(x11.display, *windowFromRoot, x11atoms._NET_WM_NAME, 0, ~0L, False, AnyPropertyType, &type, &fmt, &num, &bytes, &data) != Success || !data) goto out_supported; char * wmName = (char *)data; for(unsigned long i = 0; i < supportedCount; ++i) { if (supported[i] == x11atoms._NET_WM_STATE_FOCUSED) x11.ewmhHasFocusEvent = true; } DEBUG_INFO("EWMH-complient window manager detected: %s", wmName); x11.ewmhSupport = true; XFree(wmName); out_supported: XFree(supported); out_child: XFree(windowFromChild); out_root: XFree(windowFromRoot); out: return; } static bool x11Init(const LG_DSInitParams params) { XIDeviceInfo *devinfo; int count; int event, error; memset(&x11, 0, sizeof(x11)); x11.xValuator = -1; x11.yValuator = -1; x11.display = XOpenDisplay(NULL); x11.jitRender = params.jitRender; XSetWindowAttributes swa = { .event_mask = StructureNotifyMask | PropertyChangeMask | ExposureMask }; unsigned long swaMask = CWEventMask; #ifdef ENABLE_OPENGL if (params.opengl) { GLint glXAttribs[] = { GLX_RGBA, GLX_DOUBLEBUFFER , GLX_DEPTH_SIZE , 24, GLX_STENCIL_SIZE , 0, GLX_RED_SIZE , 8, GLX_GREEN_SIZE , 8, GLX_BLUE_SIZE , 8, GLX_DEPTH_SIZE , 0, GLX_SAMPLE_BUFFERS, 0, GLX_SAMPLES , 0, None }; x11.visual = glXChooseVisual(x11.display, XDefaultScreen(x11.display), glXAttribs); if (!x11.visual) { DEBUG_ERROR("glXChooseVisual failed"); goto fail_display; } swa.colormap = XCreateColormap(x11.display, XDefaultRootWindow(x11.display), x11.visual->visual, AllocNone); swaMask |= CWColormap; } #endif x11.window = XCreateWindow( x11.display, XDefaultRootWindow(x11.display), params.x, params.y, params.w, params.h, 0, x11.visual ? x11.visual->depth : CopyFromParent, InputOutput, x11.visual ? x11.visual->visual : CopyFromParent, swaMask, &swa); if (!x11.window) { DEBUG_ERROR("XCreateWindow failed"); goto fail_display; } XStoreName(x11.display, x11.window, params.title); XClassHint hint = { .res_name = strdup(params.title), .res_class = strdup("looking-glass-client") }; XSetClassHint(x11.display, x11.window, &hint); free(hint.res_name); free(hint.res_class); XSizeHints *xsh = XAllocSizeHints(); if (params.center) { xsh->flags |= PWinGravity; xsh->win_gravity = 5; //Center } if (!params.resizable) { xsh->flags |= PMinSize | PMaxSize; xsh->min_width = params.w; xsh->max_width = params.w; xsh->min_height = params.h; xsh->max_height = params.h; } XSetWMNormalHints(x11.display, x11.window, xsh); XFree(xsh); X11AtomsInit(); XSetWMProtocols(x11.display, x11.window, &x11atoms.WM_DELETE_WINDOW, 1); // check for Extended Window Manager Hints support x11CheckEWMHSupport(); if (x11atoms._NET_WM_PID) { pid_t pid = getpid(); XChangeProperty( x11.display, x11.window, x11atoms._NET_WM_PID, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1 ); } if (params.borderless) { if (x11atoms._MOTIF_WM_HINTS) { const struct MwmHints hints = { .flags = MWM_HINTS_DECORATIONS, .decorations = 0 }; XChangeProperty( x11.display, x11.window, x11atoms._MOTIF_WM_HINTS, x11atoms._MOTIF_WM_HINTS, 32, PropModeReplace, (unsigned char *)&hints, 5 ); } else { // fallback to making a utility window, not ideal but better then nothing XChangeProperty( x11.display, x11.window, x11atoms._NET_WM_WINDOW_TYPE, XA_ATOM, 32, PropModeReplace, (unsigned char *)&x11atoms._NET_WM_WINDOW_TYPE_UTILITY, 1 ); } } else { XChangeProperty( x11.display, x11.window, x11atoms._NET_WM_WINDOW_TYPE, XA_ATOM, 32, PropModeReplace, (unsigned char *)&x11atoms._NET_WM_WINDOW_TYPE_NORMAL, 1 ); } if (params.maximize) { Atom wmState[2] = { x11atoms._NET_WM_STATE_MAXIMIZED_HORZ, x11atoms._NET_WM_STATE_MAXIMIZED_VERT }; XChangeProperty( x11.display, x11.window, x11atoms._NET_WM_STATE, XA_ATOM, 32, PropModeReplace, (unsigned char *)&wmState, 2 ); } if (x11atoms._NET_REQUEST_FRAME_EXTENTS) { XEvent reqevent = { .xclient = { .type = ClientMessage, .window = x11.window, .format = 32, .message_type = x11atoms._NET_REQUEST_FRAME_EXTENTS } }; XSendEvent(x11.display, DefaultRootWindow(x11.display), False, SubstructureNotifyMask | SubstructureRedirectMask, &reqevent); } int major = 2; int minor = 0; if (XIQueryVersion(x11.display, &major, &minor) != Success) { DEBUG_ERROR("Failed to query the XInput version"); return false; } DEBUG_INFO("X11 XInput %d.%d in use", major, minor); devinfo = XIQueryDevice(x11.display, XIAllDevices, &count); if (!devinfo) { DEBUG_ERROR("XIQueryDevice failed"); goto fail_window; } Atom rel_x = XInternAtom(x11.display, "Rel X", True); Atom rel_y = XInternAtom(x11.display, "Rel Y", True); bool havePointer = false; bool haveKeyboard = false; for(int i = 0; i < count; ++i) { /* look for the master pointing device */ if (!havePointer && devinfo[i].use == XIMasterPointer) { for(int j = 0; j < devinfo[i].num_classes; ++j) { XIAnyClassInfo *cdevinfo = (XIAnyClassInfo *)(devinfo[i].classes[j]); if (cdevinfo->type == XIValuatorClass) { XIValuatorClassInfo *vdevinfo = (XIValuatorClassInfo *)cdevinfo; if (vdevinfo->label == rel_x || (!vdevinfo->label && vdevinfo->number == 0 && vdevinfo->mode == XIModeRelative)) x11.xValuator = vdevinfo->number; else if (vdevinfo->label == rel_y || (!vdevinfo->label && vdevinfo->number == 1 && vdevinfo->mode == XIModeRelative)) x11.yValuator = vdevinfo->number; } } if (x11.xValuator >= 0 && x11.yValuator >= 0) { havePointer = true; x11.pointerDev = devinfo[i].deviceid; } } /* look for the master keyboard device */ if (!haveKeyboard && devinfo[i].use == XIMasterKeyboard) for(int j = 0; j < devinfo[i].num_classes; ++j) { XIAnyClassInfo *cdevinfo = (XIAnyClassInfo *)(devinfo[i].classes[j]); if (cdevinfo->type == XIKeyClass) { haveKeyboard = true; x11.keyboardDev = devinfo[i].deviceid; break; } } } if (!havePointer) { DEBUG_ERROR("Failed to find the master pointing device"); XIFreeDeviceInfo(devinfo); goto fail_window; } if (!haveKeyboard) { DEBUG_ERROR("Failed to find the master keyboard device"); XIFreeDeviceInfo(devinfo); goto fail_window; } XDisplayKeycodes(x11.display, &x11.minKeycode, &x11.maxKeycode); x11.keysyms = XGetKeyboardMapping(x11.display, x11.minKeycode, x11.maxKeycode - x11.minKeycode, &x11.symsPerKeycode); XIFreeDeviceInfo(devinfo); XQueryExtension(x11.display, "XInputExtension", &x11.xinputOp, &event, &error); XIEventMask eventmask; unsigned char mask[XIMaskLen(XI_LASTEVENT)] = { 0 }; eventmask.deviceid = XIAllMasterDevices; eventmask.mask_len = sizeof(mask); eventmask.mask = mask; XISetMask(mask, XI_FocusIn ); XISetMask(mask, XI_FocusOut); XISetMask(mask, XI_Enter ); XISetMask(mask, XI_Leave ); XISetMask(mask, XI_Motion ); XISetMask(mask, XI_KeyPress ); XISetMask(mask, XI_KeyRelease ); XISetMask(mask, XI_ButtonPress ); XISetMask(mask, XI_ButtonRelease); if (XISelectEvents(x11.display, x11.window, &eventmask, 1) != Success) { XFree(mask); DEBUG_ERROR("Failed to select the xinput events"); goto fail_window; } unsigned long value = 1; XChangeProperty( x11.display, x11.window, x11atoms._NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&value, 1 ); XChangeProperty( x11.display, x11.window, x11atoms._NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)icondata, sizeof(icondata) / sizeof(icondata[0]) ); /* create the blank cursor */ { static char data[] = { 0x00 }; XColor dummy; Pixmap temp = XCreateBitmapFromData(x11.display, x11.window, data, 1, 1); x11.cursors[LG_POINTER_NONE] = XCreatePixmapCursor(x11.display, temp, temp, &dummy, &dummy, 0, 0); XFreePixmap(x11.display, temp); } /* create the square cursor */ { static char data[] = { 0x07, 0x05, 0x07 }; static char mask[] = { 0xff, 0xff, 0xff }; Colormap cmap = DefaultColormap(x11.display, DefaultScreen(x11.display)); XColor colors[2] = { { .pixel = BlackPixelOfScreen(DefaultScreenOfDisplay(x11.display)) }, { .pixel = WhitePixelOfScreen(DefaultScreenOfDisplay(x11.display)) } }; XQueryColors(x11.display, cmap, colors, 2); Pixmap img = XCreateBitmapFromData(x11.display, x11.window, data, 3, 3); Pixmap msk = XCreateBitmapFromData(x11.display, x11.window, mask, 3, 3); x11.cursors[LG_POINTER_SQUARE] = XCreatePixmapCursor(x11.display, img, msk, &colors[0], &colors[1], 1, 1); XFreePixmap(x11.display, img); XFreePixmap(x11.display, msk); } /* initialize the rest of the cursors */ const char * cursorLookup[LG_POINTER_COUNT] = { NULL , // LG_POINTER_NONE NULL , // LG_POINTER_SQUARE "left_ptr" , // LG_POINTER_ARROW "text" , // LG_POINTER_INPUT "move" , // LG_POINTER_MOVE "ns-resize" , // LG_POINTER_RESIZE_NS "ew-resize" , // LG_POINTER_RESIZE_EW "nesw-resize" , // LG_POINTER_RESIZE_NESW "nwse-resize" , // LG_POINTER_RESIZE_NWSE "hand" , // LG_POINTER_HAND "not-allowed" , // LG_POINTER_NOT_ALLOWED }; const char * fallbackLookup[LG_POINTER_COUNT] = { NULL , // LG_POINTER_NONE NULL , // LG_POINTER_SQUARE "left_ptr" , // LG_POINTER_ARROW "xterm" , // LG_POINTER_INPUT "fluer" , // LG_POINTER_MOVE "sb_v_double_arrow", // LG_POINTER_RESIZE_NS "sb_h_double_arrow", // LG_POINTER_RESIZE_EW "sizing" , // LG_POINTER_RESIZE_NESW "sizing" , // LG_POINTER_RESIZE_NWSE "hand2" , // LG_POINTER_HAND "X_cursor" , // LG_POINTER_NOT_ALLOWED }; for(int i = 0; i < LG_POINTER_COUNT; ++i) { if (!cursorLookup[i]) continue; x11.cursors[i] = XcursorLibraryLoadCursor(x11.display, cursorLookup[i]); if (!x11.cursors[i]) x11.cursors[i] = XcursorLibraryLoadCursor(x11.display, fallbackLookup[i]); } /* default to the square cursor */ XDefineCursor(x11.display, x11.window, x11.cursors[LG_POINTER_SQUARE]); if (x11.jitRender) { x11.frameEvent = lgCreateEvent(true, 0); XPresentQueryExtension(x11.display, &x11.xpresentOp, &event, &error); XPresentSelectInput(x11.display, x11.window, PresentCompleteNotifyMask); x11.presentPixmap = XCreatePixmap(x11.display, x11.window, 1, 1, 24); x11.presentRegion = XFixesCreateRegion(x11.display, &(XRectangle){0}, 1); } XMapWindow(x11.display, x11.window); XFlush(x11.display); if (!params.center) XMoveWindow(x11.display, x11.window, params.x, params.y); if (params.fullscreen) x11SetFullscreen(true); XSetLocaleModifiers(""); // Load XMODIFIERS x11.xim = XOpenIM(x11.display, 0, 0, 0); if (!x11.xim) { // disable IME XSetLocaleModifiers("@im=none"); x11.xim = XOpenIM(x11.display, 0, 0, 0); } if (x11.xim) { x11.xic = XCreateIC( x11.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, x11.window, XNFocusWindow, x11.window, NULL ); } else DEBUG_WARN("Failed to initialize X Input Method"); if (x11.xic) { XSetICFocus(x11.xic); XSelectInput(x11.display, x11.window, StructureNotifyMask | ExposureMask | PropertyChangeMask | KeyPressMask); } else DEBUG_WARN("Failed to initialize X Input Context, typing will not work"); if (!lgCreateThread("X11EventThread", x11EventThread, NULL, &x11.eventThread)) { DEBUG_ERROR("Failed to create the x11 event thread"); goto fail_window; } if (x11.jitRender) x11DoPresent(0); return true; fail_window: XDestroyWindow(x11.display, x11.window); fail_display: XCloseDisplay(x11.display); return false; } static void x11Startup(void) { } static void x11Shutdown(void) { if (x11.jitRender) lgSignalEvent(x11.frameEvent); } static void x11Free(void) { lgJoinThread(x11.eventThread, NULL); if (x11.jitRender) { lgFreeEvent(x11.frameEvent); XFreePixmap(x11.display, x11.presentPixmap); XFixesDestroyRegion(x11.display, x11.presentRegion); } if (x11.window) XDestroyWindow(x11.display, x11.window); for(int i = 0; i < LG_POINTER_COUNT; ++i) if (x11.cursors[i]) XFreeCursor(x11.display, x11.cursors[i]); if (x11.keysyms) XFree(x11.keysyms); XCloseDisplay(x11.display); } static bool x11GetProp(LG_DSProperty prop, void *ret) { switch (prop) { case LG_DS_WARP_SUPPORT: *(enum LG_DSWarpSupport*)ret = LG_DS_WARP_SCREEN; return true; case LG_DS_MAX_MULTISAMPLE: { Display * dpy = XOpenDisplay(NULL); if (!dpy) return false; XVisualInfo queryTemplate; queryTemplate.screen = 0; int visualCount; int maxSamples = -1; XVisualInfo * visuals = XGetVisualInfo(dpy, VisualScreenMask, &queryTemplate, &visualCount); for (int i = 0; i < visualCount; i++) { XVisualInfo * visual = &visuals[i]; int res, supportsGL; // Some GLX visuals do not use GL, and these must be ignored in our search. if ((res = glXGetConfig(dpy, visual, GLX_USE_GL, &supportsGL)) != 0 || !supportsGL) continue; int sampleBuffers, samples; if ((res = glXGetConfig(dpy, visual, GLX_SAMPLE_BUFFERS, &sampleBuffers)) != 0) continue; // Will be 1 if this visual supports multisampling if (sampleBuffers != 1) continue; if ((res = glXGetConfig(dpy, visual, GLX_SAMPLES, &samples)) != 0) continue; // Track the largest number of samples supported if (samples > maxSamples) maxSamples = samples; } XFree(visuals); XCloseDisplay(dpy); *(int*)ret = maxSamples; return true; } default: return true; } } static int x11EventThread(void * unused) { int epollfd = epoll_create1(0); if (epollfd == -1) { DEBUG_ERROR("epolld_create1 failure"); return 0; } struct epoll_event ev = { .events = EPOLLIN }; const int fd = ConnectionNumber(x11.display); if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) { close(epollfd); DEBUG_ERROR("epoll_ctl failed"); return 0; } while(app_isRunning()) { const uint64_t lastWMEvent = atomic_load(&x11.lastWMEvent); if (x11.invalidateAll && microtime() - lastWMEvent > 100000UL) { x11.invalidateAll = false; app_invalidateWindow(true); } if (!XPending(x11.display)) { struct epoll_event events[1]; int nfds = epoll_wait(epollfd, events, 1, 100); if (nfds == -1) { if (errno == EINTR) continue; close(epollfd); DEBUG_ERROR("epoll_wait failure"); return 0; } if (nfds == 0 || !XPending(x11.display)) continue; } XEvent xe; XNextEvent(x11.display, &xe); // call the clipboard handling code if (x11CBEventThread(xe)) continue; switch(xe.type) { case ClientMessage: if (xe.xclient.data.l[0] == x11atoms.WM_DELETE_WINDOW) app_handleCloseEvent(); continue; case ConfigureNotify: { atomic_store(&x11.lastWMEvent, microtime()); int x, y; /* the window may have been re-parented so we need to translate to * ensure we get the screen top left position of the window */ Window child; XTranslateCoordinates( x11.display, x11.window, DefaultRootWindow(x11.display), 0, 0, &x, &y, &child); x11.rect.x = x; x11.rect.y = y; x11.rect.w = xe.xconfigure.width; x11.rect.h = xe.xconfigure.height; app_updateWindowPos(x, y); if (x11.fullscreen) { struct Border border = {0}; app_handleResizeEvent(x11.rect.w, x11.rect.h, 1, border); } else app_handleResizeEvent(x11.rect.w, x11.rect.h, 1, x11.border); break; } case Expose: { atomic_store(&x11.lastWMEvent, microtime()); x11.invalidateAll = true; break; } case GenericEvent: { XGenericEventCookie *cookie = (XGenericEventCookie*)&xe.xcookie; if (cookie->extension == x11.xinputOp) { XGetEventData(x11.display, cookie); x11XInputEvent(cookie); XFreeEventData(x11.display, cookie); } else if (cookie->extension == x11.xpresentOp) { XGetEventData(x11.display, cookie); x11XPresentEvent(cookie); XFreeEventData(x11.display, cookie); } break; } case PropertyNotify: // ignore property events that are not for us if (xe.xproperty.display != x11.display || xe.xproperty.window != x11.window || xe.xproperty.state != PropertyNewValue) continue; if (xe.xproperty.atom == x11atoms._NET_WM_STATE) { atomic_store(&x11.lastWMEvent, microtime()); Atom type; int fmt; unsigned long num, bytes; unsigned char *data; if (XGetWindowProperty(x11.display, x11.window, x11atoms._NET_WM_STATE, 0, ~0L, False, AnyPropertyType, &type, &fmt, &num, &bytes, &data) != Success) break; bool fullscreen = false; bool focused = false; for(unsigned long i = 0; i < num; ++i) { Atom prop = ((Atom *)data)[i]; if (prop == x11atoms._NET_WM_STATE_FULLSCREEN) fullscreen = true; else if (prop == x11atoms._NET_WM_STATE_FOCUSED) focused = true; } if (x11.ewmhHasFocusEvent && x11.focused != focused) { x11.focused = focused; app_handleFocusEvent(focused); } x11.fullscreen = fullscreen; XFree(data); break; } if (xe.xproperty.atom == x11atoms._NET_FRAME_EXTENTS) { atomic_store(&x11.lastWMEvent, microtime()); Atom type; int fmt; unsigned long num, bytes; unsigned char *data; if (XGetWindowProperty(x11.display, x11.window, x11atoms._NET_FRAME_EXTENTS, 0, 4, False, AnyPropertyType, &type, &fmt, &num, &bytes, &data) != Success) break; if (num >= 4) { long *cardinal = (long *)data; x11.border.left = cardinal[0]; x11.border.right = cardinal[1]; x11.border.top = cardinal[2]; x11.border.bottom = cardinal[3]; app_handleResizeEvent(x11.rect.w, x11.rect.h, 1, x11.border); } XFree(data); break; } break; } } close(epollfd); return 0; } static enum Modifiers keySymToModifier(KeySym sym) { switch (sym) { case XK_Control_L: return MOD_CTRL_LEFT; case XK_Control_R: return MOD_CTRL_RIGHT; case XK_Shift_L: return MOD_SHIFT_LEFT; case XK_Shift_R: return MOD_SHIFT_RIGHT; case XK_Alt_L: return MOD_ALT_LEFT; case XK_Alt_R: return MOD_ALT_RIGHT; case XK_Super_L: return MOD_SUPER_LEFT; case XK_Super_R: return MOD_SUPER_RIGHT; default: return -1; } } static void updateModifiers(void) { app_handleKeyboardModifiers( x11.modifiers[MOD_CTRL_LEFT] || x11.modifiers[MOD_CTRL_RIGHT], x11.modifiers[MOD_SHIFT_LEFT] || x11.modifiers[MOD_SHIFT_RIGHT], x11.modifiers[MOD_ALT_LEFT] || x11.modifiers[MOD_ALT_RIGHT], x11.modifiers[MOD_SUPER_LEFT] || x11.modifiers[MOD_SUPER_RIGHT] ); } static void setFocus(bool focused, double x, double y) { if (x11.focused == focused) return; x11.focused = focused; app_updateCursorPos(x, y); app_handleFocusEvent(focused); } static int getCharcode(int detail) { if (detail < x11.minKeycode || detail > x11.maxKeycode) return 0; KeySym sym = x11.keysyms[(detail - x11.minKeycode) * x11.symsPerKeycode]; sym = xkb_keysym_to_upper(sym); return xkb_keysym_to_utf32(sym); } static void x11XInputEvent(XGenericEventCookie *cookie) { static int button_state = 0; switch(cookie->evtype) { case XI_FocusIn: { XIFocusOutEvent *xie = cookie->data; if (x11.ewmhHasFocusEvent) { // if meta ungrab for move/resize if (xie->mode == XINotifyUngrab) setFocus(true, xie->event_x, xie->event_y); return; } atomic_store(&x11.lastWMEvent, microtime()); if (x11.focused) return; if (xie->mode != XINotifyNormal && xie->mode != XINotifyWhileGrabbed && xie->mode != XINotifyUngrab) return; setFocus(true, xie->event_x, xie->event_y); return; } case XI_FocusOut: { XIFocusOutEvent *xie = cookie->data; if (x11.ewmhHasFocusEvent) { // if meta grab for move/resize if (xie->mode == XINotifyGrab) setFocus(false, xie->event_x, xie->event_y); return; } atomic_store(&x11.lastWMEvent, microtime()); if (!x11.focused) return; if (xie->mode != XINotifyNormal && xie->mode != XINotifyWhileGrabbed && xie->mode != XINotifyGrab) return; setFocus(false, xie->event_x, xie->event_y); return; } case XI_Enter: { atomic_store(&x11.lastWMEvent, microtime()); XIEnterEvent *xie = cookie->data; if (x11.entered || xie->event != x11.window || xie->mode != XINotifyNormal) return; app_updateCursorPos(xie->event_x, xie->event_y); app_handleEnterEvent(true); x11.entered = true; return; } case XI_Leave: { atomic_store(&x11.lastWMEvent, microtime()); XILeaveEvent *xie = cookie->data; if (!x11.entered || xie->event != x11.window || button_state != 0 || app_isCaptureMode() || xie->mode == NotifyGrab) return; app_updateCursorPos(xie->event_x, xie->event_y); app_handleEnterEvent(false); x11.entered = false; return; } case XI_KeyPress: { if (!x11.focused || x11.keyboardGrabbed) return; XIDeviceEvent *device = cookie->data; app_handleKeyPress(device->detail - x11.minKeycode, getCharcode(device->detail)); if (!x11.xic || !app_isOverlayMode()) return; char buffer[128]; KeySym sym; Status status; int count; XKeyPressedEvent ev = { .display = x11.display, .window = x11.window, .type = KeyPress, .keycode = device->detail, .state = device->mods.effective, }; count = Xutf8LookupString(x11.xic, &ev, buffer, sizeof(buffer), &sym, &status); if (status == XBufferOverflow || count >= sizeof(buffer)) { DEBUG_WARN("Typing too many characters at once, ignoring"); return; } if (status == XLookupChars || status == XLookupBoth) { buffer[count] = '\0'; app_handleKeyboardTyped(buffer); } if (status == XLookupKeySym || status == XLookupBoth) { int modifier = keySymToModifier(sym); if (modifier >= 0) { x11.modifiers[modifier] = true; updateModifiers(); } } return; } case XI_KeyRelease: { if (!x11.focused || x11.keyboardGrabbed) return; XIDeviceEvent *device = cookie->data; app_handleKeyRelease(device->detail - x11.minKeycode, getCharcode(device->detail)); if (!x11.xic || !app_isOverlayMode()) return; XKeyPressedEvent ev = { .display = x11.display, .window = x11.window, .type = KeyRelease, .keycode = device->detail, .state = device->mods.effective, }; KeySym sym = XLookupKeysym(&ev, 0); int modifier = keySymToModifier(sym); if (modifier >= 0) { x11.modifiers[modifier] = false; updateModifiers(); } return; } case XI_RawKeyPress: { if (!x11.focused) return; XIRawEvent *raw = cookie->data; app_handleKeyPress(raw->detail - x11.minKeycode, getCharcode(raw->detail)); return; } case XI_RawKeyRelease: { if (!x11.focused) return; XIRawEvent *raw = cookie->data; app_handleKeyRelease(raw->detail - x11.minKeycode, getCharcode(raw->detail)); return; } case XI_ButtonPress: { if (!x11.focused || !x11.entered) return; XIDeviceEvent *device = cookie->data; if (device->detail == 4) app_handleWheelMotion(-0.5); else if (device->detail == 5) app_handleWheelMotion(0.5); else app_handleButtonPress( device->detail > 5 ? device->detail - 2 : device->detail); return; } case XI_ButtonRelease: { if (!x11.focused || !x11.entered) return; XIDeviceEvent *device = cookie->data; app_handleButtonRelease(device->detail); return; } case XI_RawButtonPress: { if (!x11.focused || !x11.entered) return; XIRawEvent *raw = cookie->data; /* filter out duplicate events */ static Time prev_time = 0; static unsigned int prev_detail = 0; if (raw->time == prev_time && raw->detail == prev_detail) return; prev_time = raw->time; prev_detail = raw->detail; button_state |= (1 << raw->detail); app_handleButtonPress( raw->detail > 5 ? raw->detail - 2 : raw->detail); return; } case XI_RawButtonRelease: { if (!x11.focused || !x11.entered) return; XIRawEvent *raw = cookie->data; /* filter out duplicate events */ static Time prev_time = 0; static unsigned int prev_detail = 0; if (raw->time == prev_time && raw->detail == prev_detail) return; prev_time = raw->time; prev_detail = raw->detail; button_state &= ~(1 << raw->detail); app_handleButtonRelease( raw->detail > 5 ? raw->detail - 2 : raw->detail); return; } case XI_Motion: { XIDeviceEvent *device = cookie->data; app_updateCursorPos(device->event_x, device->event_y); if (!x11.pointerGrabbed) app_handleMouseRelative(0.0, 0.0, 0.0, 0.0); return; } case XI_RawMotion: { if (!x11.focused || !x11.entered) return; XIRawEvent *raw = cookie->data; double raw_axis[2] = { 0 }; double axis[2] = { 0 }; /* select the active validators for the X & Y axis */ double *valuator = raw->valuators.values; double *r_value = raw->raw_values; bool has_axes = false; for(int i = 0; i < raw->valuators.mask_len * 8; ++i) { if (XIMaskIsSet(raw->valuators.mask, i)) { if (i == x11.xValuator) { raw_axis[0] = *r_value; axis [0] = *valuator; has_axes = true; } else if (i == x11.yValuator) { raw_axis[1] = *r_value; axis [1] = *valuator; has_axes = true; } ++valuator; ++r_value; } } /* filter out events with no axis data */ if (!has_axes) return; /* filter out duplicate events */ static Time prev_time = 0; static double prev_axis[2] = {0}; if (raw->time == prev_time && axis[0] == prev_axis[0] && axis[1] == prev_axis[1]) return; prev_time = raw->time; prev_axis[0] = axis[0]; prev_axis[1] = axis[1]; app_handleMouseRelative(axis[0], axis[1], raw_axis[0], raw_axis[1]); return; } } } #include static void x11XPresentEvent(XGenericEventCookie *cookie) { switch(cookie->evtype) { case PresentCompleteNotify: { XPresentCompleteNotifyEvent * e = cookie->data; x11DoPresent(e->msc); atomic_store(&x11.presentMsc, e->msc); atomic_store(&x11.presentUst, e->ust); lgSignalEvent(x11.frameEvent); break; } } } #ifdef ENABLE_EGL static EGLDisplay x11GetEGLDisplay(void) { EGLDisplay ret; const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS); if (util_hasGLExt(early_exts, "EGL_KHR_platform_base") && g_egl_dynProcs.eglGetPlatformDisplay) { DEBUG_INFO("Using eglGetPlatformDisplay"); ret = g_egl_dynProcs.eglGetPlatformDisplay(EGL_PLATFORM_X11_KHR, x11.display, NULL); } else if (util_hasGLExt(early_exts, "EGL_EXT_platform_base") && g_egl_dynProcs.eglGetPlatformDisplayEXT) { DEBUG_INFO("Using eglGetPlatformDisplayEXT"); ret = g_egl_dynProcs.eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_KHR, x11.display, NULL); } else { DEBUG_INFO("Using eglGetDisplay"); ret = eglGetDisplay(x11.display); } return ret; } static EGLNativeWindowType x11GetEGLNativeWindow(void) { return (EGLNativeWindowType)x11.window; } static void x11EGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count) { static struct SwapWithDamageData data = {0}; if (!data.init) swapWithDamageInit(&data, display); swapWithDamage(&data, display, surface, damage, count); } #endif #ifdef ENABLE_OPENGL static LG_DSGLContext x11GLCreateContext(void) { return (LG_DSGLContext) glXCreateContext(x11.display, x11.visual, NULL, GL_TRUE); } static void x11GLDeleteContext(LG_DSGLContext context) { glXDestroyContext(x11.display, (GLXContext)context); } static void x11GLMakeCurrent(LG_DSGLContext context) { glXMakeCurrent(x11.display, x11.window, (GLXContext)context); } static void x11GLSetSwapInterval(int interval) { static PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = NULL; if (!glXSwapIntervalEXT) { glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) glXGetProcAddressARB( (const GLubyte *) "glXSwapIntervalEXT"); if (!glXSwapIntervalEXT) DEBUG_FATAL("Failed to load glXSwapIntervalEXT"); } glXSwapIntervalEXT(x11.display, x11.window, interval); } static void x11GLSwapBuffers(void) { glXSwapBuffers(x11.display, x11.window); } #endif static bool x11WaitFrame(void) { /* wait until we are woken up by the present event */ lgWaitEvent(x11.frameEvent, TIMEOUT_INFINITE); #define WARMUP_TIME 3000000 //2s #define CALIBRATION_COUNT 400 const uint64_t ust = atomic_load(&x11.presentUst); const uint64_t msc = atomic_load(&x11.presentMsc); static bool warmup = true; if (warmup) { static uint64_t expire = 0; if (!expire) { DEBUG_INFO("Warming up..."); expire = ust + WARMUP_TIME; } if (ust < expire) return false; warmup = false; DEBUG_INFO("Warmup done, doing calibration..."); } /* calibrate a delay to push our presentation time forward as far as * possible without skipping frames */ static int calibrate = 0; static uint64_t lastts = 0; static uint64_t lastmsc = 0; static uint64_t delay = 0; uint64_t deltamsc = 0; if (lastts) { uint64_t deltats = ust - lastts; deltamsc = msc - lastmsc; if (calibrate == 0) { if (!delay) delay = deltats / 2; else { /* increase the delay until we see a skip */ if (deltamsc < 2) delay += 100; else { delay -= 100; ++calibrate; deltamsc = 0; } } } else { if (calibrate < CALIBRATION_COUNT) { /* every skip we back off the delay */ if (deltamsc > 1) { /* prevent underflow */ if (delay - 100 < delay) { delay -= 100; calibrate = 1; deltamsc = 0; } else { /* if underflow, we are simply too slow, no delay */ delay = 0; calibrate = CALIBRATION_COUNT; } } /* if we have finished, print out the delay */ if (++calibrate == CALIBRATION_COUNT) { /* delays shorter then 1ms are unmaintainable */ if (delay < 1000) { delay = 0; DEBUG_INFO("Calibration done, no delay required"); } else DEBUG_INFO("Calibration done, delay = %lu us", delay); } } } } lastts = ust; lastmsc = msc; /* adjustments if we are still seeing odd skips */ const uint64_t lastWMEvent = atomic_load(&x11.lastWMEvent); const uint64_t eventDelta = ust > lastWMEvent ? ust - lastWMEvent : 0; static int skipCount = 0; static uint64_t lastSkipTime = 0; if (skipCount > 0 && ust - lastSkipTime > 1000000UL) skipCount = 0; if (delay > 0 && deltamsc > 1 && eventDelta > 1000000UL) { /* only adjust if the last skip was less then 1s ago */ const bool flag = ust - lastSkipTime < 1000000UL; lastSkipTime = ust; if (flag && ++skipCount > 10) { if (delay - 500 < delay) { delay -= 500; DEBUG_INFO("Excessing skipping detected, reduced calibration delay to %lu us", delay); skipCount = 0; } else { delay = 0; DEBUG_WARN("Excessive skipping detected, calibration delay disabled"); } } } if (delay) { struct timespec ts = { .tv_nsec = delay * 1000 }; while(nanosleep(&ts, &ts)) {}; } /* force rendering until we have finished calibration so we can take into * account how long it takes for the scene to render */ if (calibrate < CALIBRATION_COUNT) return true; return false; } static void x11StopWaitFrame(void) { lgSignalEvent(x11.frameEvent); } static void x11GuestPointerUpdated(double x, double y, double localX, double localY) { if (app_isCaptureMode() || !x11.entered) return; // avoid running too often static uint64_t last_warp = 0; uint64_t now = microtime(); if (now - last_warp < 10000) return; last_warp = now; XIWarpPointer( x11.display, x11.pointerDev, None, x11.window, 0, 0, 0, 0, localX, localY); XSync(x11.display, False); } static void x11SetPointer(LG_DSPointer pointer) { XDefineCursor(x11.display, x11.window, x11.cursors[pointer]); } static void x11PrintGrabError(const char * type, int dev, Status ret) { const char * errStr; switch(ret) { case AlreadyGrabbed : errStr = "AlreadyGrabbed" ; break; case GrabNotViewable: errStr = "GrabNotViewable"; break; case GrabFrozen : errStr = "GrabFrozen" ; break; case GrabInvalidTime: errStr = "GrabInvalidTime"; break; default: errStr = "Unknown"; break; } DEBUG_ERROR("XIGrabDevice failed for %s dev %d with 0x%x (%s)", type, dev, ret, errStr); } static void x11GrabPointer(void) { if (x11.pointerGrabbed) return; unsigned char mask_bits[XIMaskLen(XI_LASTEVENT)] = { 0 }; XIEventMask mask = { x11.pointerDev, sizeof(mask_bits), mask_bits }; XISetMask(mask.mask, XI_RawButtonPress ); XISetMask(mask.mask, XI_RawButtonRelease); XISetMask(mask.mask, XI_RawMotion ); XISetMask(mask.mask, XI_Motion ); XISetMask(mask.mask, XI_Enter ); XISetMask(mask.mask, XI_Leave ); Status ret; for(int retry = 0; retry < 10; ++retry) { ret = XIGrabDevice( x11.display, x11.pointerDev, x11.window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, XINoOwnerEvents, &mask); // on some WMs (i3) for an unknown reason the first grab attempt when // switching to a desktop that has LG on it fails with GrabFrozen, however // adding as short delay seems to resolve the issue. if (ret == GrabFrozen && retry < 9) { usleep(100000); continue; } break; } if (ret != Success) { x11PrintGrabError("pointer", x11.pointerDev, ret); return; } x11.pointerGrabbed = true; } static void x11UngrabPointer(void) { if (!x11.pointerGrabbed) return; XIUngrabDevice(x11.display, x11.pointerDev, CurrentTime); XSync(x11.display, False); x11.pointerGrabbed = false; } static void x11CapturePointer(void) { x11GrabPointer(); } static void x11UncapturePointer(void) { /* we need to ungrab the pointer on the following conditions when exiting capture mode: * - if the format is invalid as we do not know where the guest cursor is, * which breaks edge detection as the cursor can not be warped out of the * window when we release it. * - if the user has opted to use captureInputOnly mode. */ if (!app_isFormatValid() || app_isCaptureOnlyMode()) x11UngrabPointer(); } static void x11GrabKeyboard(void) { if (x11.keyboardGrabbed) return; unsigned char mask_bits[XIMaskLen (XI_LASTEVENT)] = { 0 }; XIEventMask mask = { x11.keyboardDev, sizeof(mask_bits), mask_bits }; XISetMask(mask.mask, XI_RawKeyPress ); XISetMask(mask.mask, XI_RawKeyRelease); Status ret = XIGrabDevice( x11.display, x11.keyboardDev, x11.window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, XINoOwnerEvents, &mask); if (ret != Success) { x11PrintGrabError("keyboard", x11.keyboardDev, ret); return; } x11.keyboardGrabbed = true; } static void x11UngrabKeyboard(void) { if (!x11.keyboardGrabbed) return; XIUngrabDevice(x11.display, x11.keyboardDev, CurrentTime); XSync(x11.display, False); x11.keyboardGrabbed = false; } static void x11WarpPointer(int x, int y, bool exiting) { XIWarpPointer( x11.display, x11.pointerDev, None, x11.window, 0, 0, 0, 0, x, y); XSync(x11.display, False); } static void x11RealignPointer(void) { app_handleMouseRelative(0.0, 0.0, 0.0, 0.0); } static bool x11IsValidPointerPos(int x, int y) { int screens; XineramaScreenInfo *xinerama = XineramaQueryScreens(x11.display, &screens); if(!xinerama) return true; bool ret = false; for(int i = 0; i < screens; ++i) if (x >= xinerama[i].x_org && x < xinerama[i].x_org + xinerama[i].width && y >= xinerama[i].y_org && y < xinerama[i].y_org + xinerama[i].height) { ret = true; break; } XFree(xinerama); return ret; } static void x11RequestActivation(void) { XEvent e = { .xclient = { .type = ClientMessage, .send_event = true, .message_type = x11atoms._NET_WM_STATE, .format = 32, .window = x11.window, .data.l = { _NET_WM_STATE_ADD, x11atoms._NET_WM_STATE_DEMANDS_ATTENTION, 0 } } }; XSendEvent(x11.display, DefaultRootWindow(x11.display), False, SubstructureNotifyMask | SubstructureRedirectMask, &e); } static void x11InhibitIdle(void) { XScreenSaverSuspend(x11.display, true); } static void x11UninhibitIdle(void) { XScreenSaverSuspend(x11.display, false); } static void x11Wait(unsigned int time) { usleep(time * 1000U); } static void x11SetWindowSize(int w, int h) { XResizeWindow(x11.display, x11.window, w, h); } static void x11SetFullscreen(bool fs) { if (x11.fullscreen == fs) return; XEvent e = { .xclient = { .type = ClientMessage, .send_event = true, .message_type = x11atoms._NET_WM_STATE, .format = 32, .window = x11.window, .data.l = { fs ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE, x11atoms._NET_WM_STATE_FULLSCREEN, 0 } } }; XSendEvent(x11.display, DefaultRootWindow(x11.display), False, SubstructureNotifyMask | SubstructureRedirectMask, &e); } static bool x11GetFullscreen(void) { return x11.fullscreen; } static void x11Minimize(void) { XIconifyWindow(x11.display, x11.window, XDefaultScreen(x11.display)); } struct LG_DisplayServerOps LGDS_X11 = { .name = "X11", .setup = x11Setup, .probe = x11Probe, .earlyInit = x11EarlyInit, .init = x11Init, .startup = x11Startup, .shutdown = x11Shutdown, .free = x11Free, .getProp = x11GetProp, #ifdef ENABLE_EGL .getEGLDisplay = x11GetEGLDisplay, .getEGLNativeWindow = x11GetEGLNativeWindow, .eglSwapBuffers = x11EGLSwapBuffers, #endif #ifdef ENABLE_OPENGL .glCreateContext = x11GLCreateContext, .glDeleteContext = x11GLDeleteContext, .glMakeCurrent = x11GLMakeCurrent, .glSetSwapInterval = x11GLSetSwapInterval, .glSwapBuffers = x11GLSwapBuffers, #endif .waitFrame = x11WaitFrame, .stopWaitFrame = x11StopWaitFrame, .guestPointerUpdated = x11GuestPointerUpdated, .setPointer = x11SetPointer, .grabPointer = x11GrabPointer, .ungrabPointer = x11UngrabPointer, .capturePointer = x11CapturePointer, .uncapturePointer = x11UncapturePointer, .grabKeyboard = x11GrabKeyboard, .ungrabKeyboard = x11UngrabKeyboard, .warpPointer = x11WarpPointer, .realignPointer = x11RealignPointer, .isValidPointerPos = x11IsValidPointerPos, .requestActivation = x11RequestActivation, .inhibitIdle = x11InhibitIdle, .uninhibitIdle = x11UninhibitIdle, .wait = x11Wait, .setWindowSize = x11SetWindowSize, .setFullscreen = x11SetFullscreen, .getFullscreen = x11GetFullscreen, .minimize = x11Minimize, .cbInit = x11CBInit, .cbNotice = x11CBNotice, .cbRelease = x11CBRelease, .cbRequest = x11CBRequest }; looking-glass-B6/client/displayservers/X11/x11.h000066400000000000000000000046001434445012300215640ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_X11DS_X11_ #define _H_X11DS_X11_ #include #include #include #include #include #include #include "interface/displayserver.h" #include "common/thread.h" #include "common/types.h" enum Modifiers { MOD_CTRL_LEFT = 0, MOD_CTRL_RIGHT, MOD_SHIFT_LEFT, MOD_SHIFT_RIGHT, MOD_ALT_LEFT, MOD_ALT_RIGHT, MOD_SUPER_LEFT, MOD_SUPER_RIGHT, }; #define MOD_COUNT (MOD_SUPER_RIGHT + 1) struct X11DSState { Display * display; Window window; XVisualInfo * visual; int minKeycode, maxKeycode; int symsPerKeycode; KeySym * keysyms; //Extended Window Manager Hints //ref: https://specifications.freedesktop.org/wm-spec/latest/ bool ewmhSupport; bool ewmhHasFocusEvent; _Atomic(uint64_t) lastWMEvent; bool invalidateAll; int xpresentOp; bool jitRender; _Atomic(uint64_t) presentMsc, presentUst; uint32_t presentSerial; Pixmap presentPixmap; XserverRegion presentRegion; LGEvent * frameEvent; LGThread * eventThread; int xinputOp; int pointerDev; int keyboardDev; int xValuator; int yValuator; bool pointerGrabbed; bool keyboardGrabbed; bool entered; bool focused; bool fullscreen; struct Rect rect; struct Border border; Cursor cursors[LG_POINTER_COUNT]; XIM xim; XIC xic; bool modifiers[MOD_COUNT]; // XFixes vars int eventBase; int errorBase; }; extern struct X11DSState x11; #endif looking-glass-B6/client/include/000077500000000000000000000000001434445012300167755ustar00rootroot00000000000000looking-glass-B6/client/include/app.h000066400000000000000000000143041434445012300177300ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_APP_ #define _H_LG_APP_ #include #include #include "common/ringbuffer.h" #include "common/types.h" #include "interface/displayserver.h" #include "interface/overlay.h" typedef enum LG_MsgAlert { LG_ALERT_INFO , LG_ALERT_SUCCESS, LG_ALERT_WARNING, LG_ALERT_ERROR } LG_MsgAlert; bool app_isRunning(void); bool app_inputEnabled(void); bool app_isCaptureMode(void); bool app_isCaptureOnlyMode(void); bool app_isFormatValid(void); bool app_isOverlayMode(void); void app_updateCursorPos(double x, double y); void app_updateWindowPos(int x, int y); void app_handleResizeEvent(int w, int h, double scale, const struct Border border); void app_invalidateWindow(bool full); void app_handleMouseRelative(double normx, double normy, double rawx, double rawy); void app_handleMouseBasic(void); void app_resyncMouseBasic(void); void app_handleButtonPress(int button); void app_handleButtonRelease(int button); void app_handleWheelMotion(double motion); void app_handleKeyboardTyped(const char * typed); void app_handleKeyPress(int scancode, int charcode); void app_handleKeyRelease(int scancode, int charcode); void app_handleKeyboardModifiers(bool ctrl, bool shift, bool alt, bool super); void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock); void app_handleEnterEvent(bool entered); void app_handleFocusEvent(bool focused); void app_handleCloseEvent(void); void app_handleRenderEvent(const uint64_t timeUs); void app_setFullscreen(bool fs); bool app_getFullscreen(void); bool app_getProp(LG_DSProperty prop, void * ret); #ifdef ENABLE_EGL EGLDisplay app_getEGLDisplay(void); EGLNativeWindowType app_getEGLNativeWindow(void); void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count); #endif #ifdef ENABLE_OPENGL LG_DSGLContext app_glCreateContext(void); void app_glDeleteContext(LG_DSGLContext context); void app_glMakeCurrent(LG_DSGLContext context); void app_glSetSwapInterval(int interval); void app_glSwapBuffers(void); #endif #define MAX_OVERLAY_RECTS 10 void app_registerOverlay(const struct LG_OverlayOps * ops, const void * params); void app_initOverlays(void); void app_setOverlay(bool enable); bool app_overlayNeedsRender(void); /** * render the overlay * returns: * -1 for full output damage * 0 for no overlay * >0 number of rects written into rects */ int app_renderOverlay(struct Rect * rects, int maxRects); void app_freeOverlays(void); /** * invalidate the window to update the overlay, if renderTwice is set the imgui * render code will run twice so that auto sized windows are calculated correctly */ void app_invalidateOverlay(bool renderTwice); struct OverlayGraph; typedef struct OverlayGraph * GraphHandle; typedef const char * (*GraphFormatFn)(const char * name, float min, float max, float avg, float freq, float last); GraphHandle app_registerGraph(const char * name, RingBuffer buffer, float min, float max, GraphFormatFn formatFn); void app_unregisterGraph(GraphHandle handle); void app_invalidateGraph(GraphHandle handle); void app_overlayConfigRegister(const char * title, void (*callback)(void * udata, int * id), void * udata); void app_overlayConfigRegisterTab(const char * title, void (*callback)(void * udata, int * id), void * udata); void app_clipboardRelease(void); void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count); void app_clipboardNotifySize(const LG_ClipboardData type, size_t size); void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size); void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque); /** * Show an alert on screen * @param type The alert type * param fmt The alert message format @ param ... formatted message values */ void app_alert(LG_MsgAlert type, const char * fmt, ...); typedef struct MsgBoxHandle * MsgBoxHandle; MsgBoxHandle app_msgBox(const char * caption, const char * fmt, ...); typedef void (*MsgBoxConfirmCallback)(bool yes, void * opaque); MsgBoxHandle app_confirmMsgBox(const char * caption, MsgBoxConfirmCallback callback, void * opaque, const char * fmt, ...); void app_msgBoxClose(MsgBoxHandle handle); typedef struct KeybindHandle * KeybindHandle; typedef void (*KeybindFn)(int sc, void * opaque); void app_showRecord(bool show); /** * Register a handler for the + combination * @param sc The scancode to register * @param charcode The charcode to register (used instead of sc if non zero) * @param callback The function to be called when the combination is pressed * @param opaque A pointer to be passed to the callback, may be NULL * @retval A handle for the binding or NULL on failure. * The caller is required to release the handle via `app_releaseKeybind` when it is no longer required */ KeybindHandle app_registerKeybind(int sc, int charcode, KeybindFn callback, void * opaque, const char * description); /** * Release an existing key binding * @param handle A pointer to the keybind handle to release, may be NULL */ void app_releaseKeybind(KeybindHandle * handle); /** * Release all keybindings */ void app_releaseAllKeybinds(void); bool app_guestIsLinux(void); bool app_guestIsWindows(void); bool app_guestIsOSX(void); bool app_guestIsBSD(void); bool app_guestIsOther(void); /** * Enable/disable the LG display */ void app_stopVideo(bool stop); /** * Enable/disable the spice display */ bool app_useSpiceDisplay(bool enable); #endif looking-glass-B6/client/include/egl_dynprocs.h000066400000000000000000000034141434445012300216400ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_EGL_DYNPROCS_ #define _H_LG_EGL_DYNPROCS_ #ifdef ENABLE_EGL #include #include #undef GL_KHR_debug #include #include struct EGLDynProcs { PFNEGLGETPLATFORMDISPLAYPROC eglGetPlatformDisplay; PFNEGLGETPLATFORMDISPLAYPROC eglGetPlatformDisplayEXT; PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC eglSwapBuffersWithDamageKHR; PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC eglSwapBuffersWithDamageEXT; PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; PFNGLDEBUGMESSAGECALLBACKKHRPROC glDebugMessageCallback; PFNGLDEBUGMESSAGECALLBACKKHRPROC glDebugMessageCallbackKHR; PFNGLBUFFERSTORAGEEXTPROC glBufferStorageEXT; PFNEGLCREATEIMAGEPROC eglCreateImage; PFNEGLDESTROYIMAGEPROC eglDestroyImage; }; extern struct EGLDynProcs g_egl_dynProcs; void egl_dynProcsInit(void); #else #define egl_dynProcsInit(...) #endif #endif // _H_LG_EGL_DYNPROCS_ looking-glass-B6/client/include/eglutil.h000066400000000000000000000024651434445012300206220ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_GLUTIL_ #define _H_LG_GLUTIL_ #include #include #include #include "common/types.h" struct SwapWithDamageData { bool init; PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC func; }; void swapWithDamageInit(struct SwapWithDamageData * data, EGLDisplay display); void swapWithDamageDisable(struct SwapWithDamageData * data); void swapWithDamage(struct SwapWithDamageData * data, EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count); #endif looking-glass-B6/client/include/gl_dynprocs.h000066400000000000000000000027521434445012300214770ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_GL_DYNPROCS_ #define _H_LG_GL_DYNPROCS_ #ifdef ENABLE_OPENGL #include #include struct GLDynProcs { PFNGLGENBUFFERSPROC glGenBuffers; PFNGLBINDBUFFERPROC glBindBuffer; PFNGLBUFFERDATAPROC glBufferData; PFNGLBUFFERSUBDATAPROC glBufferSubData; PFNGLDELETEBUFFERSPROC glDeleteBuffers; PFNGLISSYNCPROC glIsSync; PFNGLFENCESYNCPROC glFenceSync; PFNGLCLIENTWAITSYNCPROC glClientWaitSync; PFNGLDELETESYNCPROC glDeleteSync; PFNGLGENERATEMIPMAPPROC glGenerateMipmap; }; extern struct GLDynProcs g_gl_dynProcs; void gl_dynProcsInit(void); #else #define gl_dynProcsInit(...) #endif #endif // _H_LG_GL_DYNPROCS_ looking-glass-B6/client/include/interface/000077500000000000000000000000001434445012300207355ustar00rootroot00000000000000looking-glass-B6/client/include/interface/audiodev.h000066400000000000000000000051401434445012300227060ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_I_AUDIODEV_ #define _H_I_AUDIODEV_ #include #include #include typedef int (*LG_AudioPullFn)(uint8_t * dst, int frames); typedef void (*LG_AudioPushFn)(uint8_t * src, int frames); struct LG_AudioDevOps { /* internal name of the audio for debugging */ const char * name; /* called very early to allow for option registration, optional */ void (*earlyInit)(void); /* called to initialize the audio backend */ bool (*init)(void); /* final free */ void (*free)(void); struct { /* setup the stream for playback but don't start it yet * Note: the pull function returns f32 samples */ void (*setup)(int channels, int sampleRate, int requestedPeriodFrames, int * maxPeriodFrames, int * startFrames, LG_AudioPullFn pullFn); /* called when there is data available to start playback */ void (*start)(void); /* called when SPICE reports the audio stream has stopped */ void (*stop)(void); /* [optional] called to set the volume of the channels */ void (*volume)(int channels, const uint16_t volume[]); /* [optional] called to set muting of the output */ void (*mute)(bool mute); /* return the current total playback latency in microseconds */ uint64_t (*latency)(void); } playback; struct { /* start the record stream * Note: currently SPICE only supports S16 samples so always assume so */ void (*start)(int channels, int sampleRate, LG_AudioPushFn pushFn); /* called when SPICE reports the audio stream has stopped */ void (*stop)(void); /* [optional] called to set the volume of the channels */ void (*volume)(int channels, const uint16_t volume[]); /* [optional] called to set muting of the input */ void (*mute)(bool mute); } record; }; #endif looking-glass-B6/client/include/interface/displayserver.h000066400000000000000000000172361434445012300240130ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_I_DISPLAYSERVER_ #define _H_I_DISPLAYSERVER_ #include #include #include "common/types.h" #include "common/debug.h" typedef enum LG_ClipboardData { LG_CLIPBOARD_DATA_TEXT = 0, LG_CLIPBOARD_DATA_PNG, LG_CLIPBOARD_DATA_BMP, LG_CLIPBOARD_DATA_TIFF, LG_CLIPBOARD_DATA_JPEG, LG_CLIPBOARD_DATA_NONE // enum max, not a data type } LG_ClipboardData; typedef enum LG_DSProperty { /** * returns the maximum number of samples supported * if not implemented LG assumes no multisample support * return data type: int */ LG_DS_MAX_MULTISAMPLE, /** * returns if the platform is warp capable * if not implemented LG assumes that the platform is warp capable * return data type: bool */ LG_DS_WARP_SUPPORT, } LG_DSProperty; enum LG_DSWarpSupport { LG_DS_WARP_NONE, LG_DS_WARP_SURFACE, LG_DS_WARP_SCREEN, }; typedef enum LG_DSPointer { LG_POINTER_NONE = 0, LG_POINTER_SQUARE, LG_POINTER_ARROW, LG_POINTER_INPUT, LG_POINTER_MOVE, LG_POINTER_RESIZE_NS, LG_POINTER_RESIZE_EW, LG_POINTER_RESIZE_NESW, LG_POINTER_RESIZE_NWSE, LG_POINTER_HAND, LG_POINTER_NOT_ALLOWED, } LG_DSPointer; #define LG_POINTER_COUNT (LG_POINTER_NOT_ALLOWED + 1) typedef struct LG_DSInitParams { const char * title; int x, y, w, h; bool center; bool fullscreen; bool resizable; bool borderless; bool maximize; // if true the renderer requires an OpenGL context bool opengl; // x11 needs to know if this is in use so we can decide to setup for // presentation times bool jitRender; } LG_DSInitParams; typedef void (* LG_ClipboardReplyFn)(void * opaque, const LG_ClipboardData type, uint8_t * data, uint32_t size); typedef struct LG_DSGLContext * LG_DSGLContext; typedef struct LGEvent LGEvent; struct LG_DisplayServerOps { const char * name; /* called before options are parsed, useful for registering options */ void (*setup)(void); /* return true if the selected ds is valid for the current platform */ bool (*probe)(void); /* called before anything has been initialized */ bool (*earlyInit)(void); /* called when it's time to create and show the application window */ bool (*init)(const LG_DSInitParams params); /* called at startup after window creation, renderer and SPICE is ready */ void (*startup)(void); /* called just before final window destruction, before final free */ void (*shutdown)(void); /* final free */ void (*free)(void); /* * return a system specific property, returns false if unsupported or failure * if the platform does not support/implement the requested property the value * of `ret` must not be altered. */ bool (*getProp)(LG_DSProperty prop, void * ret); #ifdef ENABLE_EGL /* EGL support */ EGLDisplay (*getEGLDisplay)(void); EGLNativeWindowType (*getEGLNativeWindow)(void); void (*eglSwapBuffers)(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count); #endif #ifdef ENABLE_OPENGL /* opengl platform specific methods */ LG_DSGLContext (*glCreateContext)(void); void (*glDeleteContext)(LG_DSGLContext context); void (*glMakeCurrent)(LG_DSGLContext context); void (*glSetSwapInterval)(int interval); void (*glSwapBuffers)(void); #endif /* Waits for a good time to render the next frame in time for the next vblank. * This is optional and a display server may choose to not implement it. * * return true to force the frame to be rendered, this is used by X11 for * calibration */ bool (*waitFrame)(void); /* This must be called when waitFrame returns, but no frame is actually rendered. */ void (*skipFrame)(void); /* This is used to interrupt waitFrame. */ void (*stopWaitFrame)(void); /* dm specific cursor implementations */ void (*guestPointerUpdated)(double x, double y, double localX, double localY); void (*setPointer)(LG_DSPointer pointer); void (*grabKeyboard)(void); void (*ungrabKeyboard)(void); /* (un)grabPointer is used to toggle cursor tracking/confine in normal mode */ void (*grabPointer)(void); void (*ungrabPointer)(void); /* (un)capturePointer is used do toggle special cursor tracking in capture mode */ void (*capturePointer)(void); void (*uncapturePointer)(void); /* exiting = true if the warp is to leave the window */ void (*warpPointer)(int x, int y, bool exiting); /* called when the client needs to realign the pointer. This should simply * call the appropriate app_handleMouse* method for the platform with zero * deltas */ void (*realignPointer)(void); /* returns true if the position specified is actually valid */ bool (*isValidPointerPos)(int x, int y); /* called to disable/enable the screensaver */ void (*inhibitIdle)(void); void (*uninhibitIdle)(void); /* called to request activation */ void (*requestActivation)(void); /* wait for the specified time without blocking UI processing/event loops */ void (*wait)(unsigned int time); /* get/set the window dimensions & state */ void (*setWindowSize)(int x, int y); bool (*getFullscreen)(void); void (*setFullscreen)(bool fs); void (*minimize)(void); /* clipboard support, optional, if not supported set to NULL */ bool (*cbInit)(void); void (*cbNotice)(LG_ClipboardData type); void (*cbRelease)(void); void (*cbRequest)(LG_ClipboardData type); }; #ifdef ENABLE_EGL #define ASSERT_EGL_FN(x) DEBUG_ASSERT(x) #else #define ASSERT_EGL_FN(x) #endif #ifdef ENABLE_OPENGL #define ASSERT_OPENGL_FN(x) DEBUG_ASSERT(x) #else #define ASSERT_OPENGL_FN(x) #endif #define ASSERT_LG_DS_VALID(x) \ DEBUG_ASSERT((x)->setup ); \ DEBUG_ASSERT((x)->probe ); \ DEBUG_ASSERT((x)->earlyInit); \ DEBUG_ASSERT((x)->init ); \ DEBUG_ASSERT((x)->startup ); \ DEBUG_ASSERT((x)->shutdown ); \ DEBUG_ASSERT((x)->free ); \ DEBUG_ASSERT((x)->getProp ); \ ASSERT_EGL_FN((x)->getEGLDisplay ); \ ASSERT_EGL_FN((x)->getEGLNativeWindow); \ ASSERT_EGL_FN((x)->eglSwapBuffers ); \ ASSERT_OPENGL_FN((x)->glCreateContext ); \ ASSERT_OPENGL_FN((x)->glDeleteContext ); \ ASSERT_OPENGL_FN((x)->glMakeCurrent ); \ ASSERT_OPENGL_FN((x)->glSetSwapInterval); \ ASSERT_OPENGL_FN((x)->glSwapBuffers ); \ DEBUG_ASSERT(!(x)->waitFrame == !(x)->stopWaitFrame); \ DEBUG_ASSERT((x)->guestPointerUpdated); \ DEBUG_ASSERT((x)->setPointer ); \ DEBUG_ASSERT((x)->grabPointer ); \ DEBUG_ASSERT((x)->ungrabPointer ); \ DEBUG_ASSERT((x)->capturePointer ); \ DEBUG_ASSERT((x)->uncapturePointer ); \ DEBUG_ASSERT((x)->warpPointer ); \ DEBUG_ASSERT((x)->realignPointer ); \ DEBUG_ASSERT((x)->isValidPointerPos ); \ DEBUG_ASSERT((x)->inhibitIdle ); \ DEBUG_ASSERT((x)->uninhibitIdle ); \ DEBUG_ASSERT((x)->wait ); \ DEBUG_ASSERT((x)->setWindowSize ); \ DEBUG_ASSERT((x)->setFullscreen ); \ DEBUG_ASSERT((x)->getFullscreen ); \ DEBUG_ASSERT((x)->minimize ); #endif looking-glass-B6/client/include/interface/font.h000066400000000000000000000031711434445012300220560ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include typedef void * LG_FontObj; typedef struct LG_FontBitmap { void * reserved; unsigned int width, height; unsigned int bpp; // bytes per pixel uint8_t * pixels; } LG_FontBitmap; typedef bool (* LG_FontCreate )(LG_FontObj * opaque, const char * font_name, unsigned int size); typedef void (* LG_FontDestroy )(LG_FontObj opaque); typedef LG_FontBitmap * (* LG_FontRender )(LG_FontObj opaque, unsigned int fg_color, const char * text); typedef void (* LG_FontRelease )(LG_FontObj opaque, LG_FontBitmap * bitmap); typedef struct LG_Font { // mandatory support const char * name; LG_FontCreate create; LG_FontDestroy destroy; LG_FontRender render; LG_FontRelease release; } LG_Font;looking-glass-B6/client/include/interface/overlay.h000066400000000000000000000052051434445012300225710ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_I_OVERLAY_ #define _H_I_OVERLAY_ #include #include "common/types.h" #define TICK_RATE 25 struct LG_OverlayOps { /* internal name of the overlay for debugging */ const char * name; /* called very early to allow for option registration, optional */ void (*earlyInit)(void); /* called when the overlay is registered */ bool (*init)(void ** udata, const void * params); /* final free */ void (*free)(void * udata); /* return true if realtime rendering is required when in jitRender mode * optional, if omitted assumes false */ bool (*needs_render)(void * udata, bool interactive); /* return true if the overlay currently requires overlay mode * optional, if omitted assumes false */ bool (*needs_overlay)(void * udata); /* perform the actual drawing/rendering * * `interactive` is true if the application is currently in overlay interaction * mode. * * `windowRects` is an array of window rects that were rendered using screen * coordinates. Will be `NULL` if the information is not required. * * `maxRects` is the length of `windowRects`, or 0 if `windowRects` is `NULL` * * returns the number of rects written to `windowRects`, or -1 if there is not * enough room left. */ int (*render)(void * udata, bool interactive, struct Rect * windowRects, int maxRects); /* called TICK_RATE times a second by the application * * Note: This may not run in the same context as `render`! * * return true if the frame needs to be rendered * optional, if omitted assumes false */ bool (*tick)(void * udata, unsigned long long tickCount); /* TODO: add load/save settings capabillity */ }; #define ASSERT_LG_OVERLAY_VALID(x) \ DEBUG_ASSERT((x)->name ); \ DEBUG_ASSERT((x)->init ); \ DEBUG_ASSERT((x)->free ); \ DEBUG_ASSERT((x)->render); #endif looking-glass-B6/client/include/interface/renderer.h000066400000000000000000000140721434445012300227200ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include #include "app.h" #include "common/KVMFR.h" #include "common/framebuffer.h" #define IS_LG_RENDERER_VALID(x) \ ((x)->getName && \ (x)->create && \ (x)->initialize && \ (x)->deinitialize && \ (x)->onRestart && \ (x)->onResize && \ (x)->onMouseShape && \ (x)->onMouseEvent && \ (x)->renderStartup && \ (x)->render && \ (x)->createTexture && \ (x)->freeTexture && \ (x)->spiceConfigure && \ (x)->spiceDrawFill && \ (x)->spiceDrawBitmap && \ (x)->spiceShow) typedef struct LG_RendererParams { bool quickSplash; } LG_RendererParams; typedef enum LG_RendererSupport { LG_SUPPORTS_DMABUF } LG_RendererSupport; typedef enum LG_RendererRotate { LG_ROTATE_0, LG_ROTATE_90, LG_ROTATE_180, LG_ROTATE_270 } LG_RendererRotate; // kept out of the enum so gcc doesn't warn when it's missing from a switch // statement. #define LG_ROTATE_MAX (LG_ROTATE_270+1) typedef struct LG_RendererFormat { FrameType type; // frame type unsigned int screenWidth; // actual width of the host unsigned int screenHeight; // actual height of the host unsigned int frameWidth; // width of frame transmitted unsigned int frameHeight; // height of frame transmitted unsigned int stride; // scanline width (zero if compresed) unsigned int pitch; // scanline bytes (or compressed size) unsigned int bpp; // bits per pixel (zero if compressed) LG_RendererRotate rotate; // guest rotation } LG_RendererFormat; typedef struct LG_RendererRect { bool valid; int x; int y; int w; int h; } LG_RendererRect; typedef enum LG_RendererCursor { LG_CURSOR_COLOR , LG_CURSOR_MONOCHROME , LG_CURSOR_MASKED_COLOR } LG_RendererCursor; typedef struct LG_Renderer LG_Renderer; typedef struct LG_RendererOps { /* returns the friendly name of the renderer */ const char * (*getName)(void); /* called pre-creation to allow the renderer to register any options it may * have */ void (*setup)(void); /* creates an instance of the renderer * Context: lg_run */ bool (*create)(LG_Renderer ** renderer, const LG_RendererParams params, bool * needsOpenGL); /* initializes the renderer for use * Context: lg_run */ bool (*initialize)(LG_Renderer * renderer); /* deinitializes & frees the renderer * Context: lg_run & renderThread */ void (*deinitialize)(LG_Renderer * renderer); /* returns true if the specified feature is supported * Context: renderThread */ bool (*supports)(LG_Renderer * renderer, LG_RendererSupport support); /* called when the renderer is to reset it's state * Context: lg_run & frameThread */ void (*onRestart)(LG_Renderer * renderer); /* called when the viewport has been resized * Context: renderThrtead */ void (*onResize)(LG_Renderer * renderer, const int width, const int height, const double scale, const LG_RendererRect destRect, LG_RendererRotate rotate); /* called when the mouse shape has changed * Context: cursorThread */ bool (*onMouseShape)(LG_Renderer * renderer, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data); /* called when the mouse has moved or changed visibillity * Context: cursorThread */ bool (*onMouseEvent)(LG_Renderer * renderer, const bool visible, int x, int y, const int hx, const int hy); /* called when the frame format has changed * Context: frameThread */ bool (*onFrameFormat)(LG_Renderer * renderer, const LG_RendererFormat format); /* called when there is a new frame * Context: frameThread */ bool (*onFrame)(LG_Renderer * renderer, const FrameBuffer * frame, int dmaFD, const FrameDamageRect * damage, int damageCount); /* called when the rederer is to startup * Context: renderThread */ bool (*renderStartup)(LG_Renderer * renderer, bool useDMA); /* called to render the scene * Context: renderThread */ bool (*render)(LG_Renderer * renderer, LG_RendererRotate rotate, const bool newFrame, const bool invalidateWindow, void (*preSwap)(void * udata), void * udata); /* called to create a texture from the specified 32-bit RGB image data. This * method is for use with Dear ImGui * Context: renderThread */ void * (*createTexture)(LG_Renderer * renderer, int width, int height, uint8_t * data); /* called to free a texture previously created by createTexture. This method * is for use with Dear ImGui * Context: renderThread */ void (*freeTexture)(LG_Renderer * renderer, void * texture); /* setup the spice display */ void (*spiceConfigure)(LG_Renderer * renderer, int width, int height); /* draw a filled rect on the spice display with the specified color */ void (*spiceDrawFill)(LG_Renderer * renderer, int x, int y, int width, int height, uint32_t color); /* draw an image on the spice display, data is RGBA32 */ void (*spiceDrawBitmap)(LG_Renderer * renderer, int x, int y, int width, int height, int stride, uint8_t * data, bool topDown); /* show the spice display */ void (*spiceShow)(LG_Renderer * renderer, bool show); } LG_RendererOps; typedef struct LG_Renderer { LG_RendererOps ops; } LG_Renderer; looking-glass-B6/client/include/overlay_utils.h000066400000000000000000000027111434445012300220500ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_OVERLAY_UTILS_ #define _H_LG_OVERLAY_UTILS_ #include #include "common/types.h" typedef struct ImVec2 ImVec2; typedef struct { void * tex; int width; int height; } OverlayImage; void overlayGetImGuiRect(struct Rect * rect); ImVec2 * overlayGetScreenSize(void); void overlayTextURL(const char * url, const char * text); void overlayTextMaybeURL(const char * text, bool wrapped); // create a texture from a SVG and scale it to fit the supplied width & height bool overlayLoadSVG(const char * data, unsigned int size, OverlayImage * image, int width, int height); void overlayFreeImage(OverlayImage * image); #endif looking-glass-B6/client/include/util.h000066400000000000000000000031541434445012300201260ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_UTIL_ #define _H_LG_UTIL_ #include #include #include "common/types.h" #include "common/util.h" // reads the specified file into a new buffer // the callee must free the buffer bool util_fileGetContents(const char * filename, char ** buffer, size_t * length); void util_cursorToInt(double ex, double ey, int *x, int *y); bool util_guestCurToLocal(struct DoublePoint *local); void util_localCurToGuest(struct DoublePoint *guest); void util_rotatePoint(struct DoublePoint *point); bool util_hasGLExt(const char * exts, const char * ext); static inline double util_clamp(double x, double min, double max) { if (x < min) return min; if (x > max) return max; return x; } bool util_initUIFonts(void); void util_freeUIFonts(void); char * util_getUIFont(const char * fontName); #endif looking-glass-B6/client/renderers/000077500000000000000000000000001434445012300173435ustar00rootroot00000000000000looking-glass-B6/client/renderers/CMakeLists.txt000066400000000000000000000026211434445012300221040ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(renderers LANGUAGES C) set(RENDERER_H "${CMAKE_BINARY_DIR}/include/dynamic/renderers.h") set(RENDERER_C "${CMAKE_BINARY_DIR}/src/renderers.c") file(WRITE ${RENDERER_H} "#include \"interface/renderer.h\"\n\n") file(APPEND ${RENDERER_H} "extern LG_RendererOps * LG_Renderers[];\n\n") file(WRITE ${RENDERER_C} "#include \"interface/renderer.h\"\n\n") file(APPEND ${RENDERER_C} "#include \n\n") set(RENDERERS "_") set(RENDERERS_LINK "_") function(add_renderer name) set(RENDERERS "${RENDERERS};${name}" PARENT_SCOPE) set(RENDERERS_LINK "${RENDERERS_LINK};renderer_${name}" PARENT_SCOPE) add_subdirectory(${name}) endfunction() # Add/remove renderers here! if(ENABLE_EGL) add_renderer(EGL) endif() if (ENABLE_OPENGL) add_renderer(OpenGL) endif() list(REMOVE_AT RENDERERS 0) list(REMOVE_AT RENDERERS_LINK 0) list(LENGTH RENDERERS RENDERER_COUNT) file(APPEND ${RENDERER_H} "#define LG_RENDERER_COUNT ${RENDERER_COUNT}\n") foreach(renderer ${RENDERERS}) file(APPEND ${RENDERER_C} "extern LG_RendererOps LGR_${renderer};\n") endforeach() file(APPEND ${RENDERER_C} "\nconst LG_RendererOps * LG_Renderers[] =\n{\n") foreach(renderer ${RENDERERS}) file(APPEND ${RENDERER_C} " &LGR_${renderer},\n") endforeach() file(APPEND ${RENDERER_C} " NULL\n};") add_library(renderers STATIC ${RENDERER_C}) target_link_libraries(renderers ${RENDERERS_LINK}) looking-glass-B6/client/renderers/EGL/000077500000000000000000000000001434445012300177525ustar00rootroot00000000000000looking-glass-B6/client/renderers/EGL/CMakeLists.txt000066400000000000000000000051701434445012300225150ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(renderer_EGL LANGUAGES C CXX) find_package(PkgConfig) pkg_check_modules(RENDERER_EGL REQUIRED IMPORTED_TARGET egl gl ) pkg_check_modules(RENDERER_EGL_OPT IMPORTED_TARGET wayland-egl ) find_program(AWK NAMES gawk mawk original-awk awk) if(AWK MATCHES ".+-NOTFOUND") message(FATAL_ERROR "FATAL: some known version of awk couldn't be found (${AWK}).") else() message(STATUS "Using awk: ${AWK}") endif() include(MakeObject) function(build_shaders header_dir) file(GLOB headers "${header_dir}/*.h") set(EGL_SHADER_PROCESSED) foreach(shader ${ARGN}) set(out_f "${CMAKE_CURRENT_BINARY_DIR}/${shader}") add_custom_command(OUTPUT "${out_f}" COMMAND "${AWK}" -f "${CMAKE_CURRENT_SOURCE_DIR}/glsl.include.awk" "${CMAKE_CURRENT_SOURCE_DIR}/${shader}" > "${out_f}" MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/${shader}" DEPENDS ${headers} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/shader" COMMENT "Preprocessing shader ${shader}" VERBATIM ) endforeach() set(CMAKE_CURRENT_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}") make_object( EGL_SHADER ${ARGN} ) set(EGL_SHADER_OBJS "${EGL_SHADER_OBJS}" PARENT_SCOPE) set(EGL_SHADER_INCS "${EGL_SHADER_INCS}" PARENT_SCOPE) endfunction() build_shaders( shader shader/desktop.vert shader/desktop_rgb.frag shader/cursor.vert shader/cursor_rgb.frag shader/cursor_mono.frag shader/damage.vert shader/damage.frag shader/basic.vert shader/ffx_cas.frag shader/ffx_fsr1_easu.frag shader/ffx_fsr1_rcas.frag shader/downscale.frag shader/downscale_lanczos2.frag shader/downscale_linear.frag ) make_defines( "${CMAKE_CURRENT_SOURCE_DIR}/shader/desktop_rgb.frag" "${CMAKE_CURRENT_BINARY_DIR}/shader/desktop_rgb.def.h" ) add_library(renderer_EGL STATIC egl.c egldebug.c shader.c texture_util.c texture.c texture_buffer.c texture_framebuffer.c texture_dmabuf.c model.c desktop.c desktop_rects.c cursor.c damage.c framebuffer.c postprocess.c ffx.c filter.c filter_ffx_cas.c filter_ffx_fsr1.c filter_downscale.c ${EGL_SHADER_OBJS} "${CMAKE_CURRENT_BINARY_DIR}/shader/desktop_rgb.def.h" ${PROJECT_TOP}/repos/cimgui/imgui/backends/imgui_impl_opengl3.cpp ) target_compile_definitions(renderer_EGL PRIVATE CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1 IMGUI_IMPL_OPENGL_ES3) target_link_libraries(renderer_EGL PkgConfig::RENDERER_EGL lg_common cimgui ) if(RENDERER_EGL_OPT_FOUND) target_link_libraries(renderer_EGL PkgConfig::RENDERER_EGL_OPT ) endif() target_include_directories(renderer_EGL PRIVATE src ${EGL_SHADER_INCS} ) looking-glass-B6/client/renderers/EGL/cursor.c000066400000000000000000000272711434445012300214440ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "cursor.h" #include "common/debug.h" #include "common/locking.h" #include "common/option.h" #include "texture.h" #include "shader.h" #include "model.h" #include "util.h" #include #include #include // these headers are auto generated by cmake #include "cursor.vert.h" #include "cursor_rgb.frag.h" #include "cursor_mono.frag.h" struct CursorTex { struct EGL_Texture * texture; struct EGL_Shader * shader; GLuint uMousePos; GLuint uScale; GLuint uRotate; GLuint uCBMode; }; struct CursorPos { float x, y; }; struct CursorSize { float w, h; }; struct EGL_Cursor { LG_Lock lock; LG_RendererCursor type; int width; int height; int stride; uint8_t * data; size_t dataSize; bool update; // cursor state bool visible; LG_RendererRotate rotate; int cbMode; _Atomic(struct CursorPos) pos; _Atomic(struct CursorPos) hs; _Atomic(struct CursorSize) size; _Atomic(float) scale; struct CursorTex norm; struct CursorTex mono; struct EGL_Model * model; }; static bool cursorTexInit(struct CursorTex * t, const char * vertex_code , size_t vertex_size, const char * fragment_code, size_t fragment_size) { if (!egl_textureInit(&t->texture, NULL, EGL_TEXTYPE_BUFFER)) { DEBUG_ERROR("Failed to initialize the cursor texture"); return false; } if (!egl_shaderInit(&t->shader)) { DEBUG_ERROR("Failed to initialize the cursor shader"); return false; } if (!egl_shaderCompile(t->shader, vertex_code, vertex_size, fragment_code, fragment_size)) { DEBUG_ERROR("Failed to compile the cursor shader"); return false; } t->uMousePos = egl_shaderGetUniform(t->shader, "mouse" ); t->uScale = egl_shaderGetUniform(t->shader, "scale" ); t->uRotate = egl_shaderGetUniform(t->shader, "rotate" ); t->uCBMode = egl_shaderGetUniform(t->shader, "cbMode" ); return true; } static inline void setCursorTexUniforms(EGL_Cursor * cursor, struct CursorTex * t, bool mono, float x, float y, float w, float h, float scale) { glUniform4f(t->uMousePos, x, y, w, mono ? h / 2 : h); glUniform1f(t->uScale , scale); glUniform1i(t->uRotate , cursor->rotate); glUniform1i(t->uCBMode , cursor->cbMode); } static void cursorTexFree(struct CursorTex * t) { egl_textureFree(&t->texture); egl_shaderFree (&t->shader ); }; bool egl_cursorInit(EGL_Cursor ** cursor) { *cursor = malloc(sizeof(**cursor)); if (!*cursor) { DEBUG_ERROR("Failed to malloc EGL_Cursor"); return false; } memset(*cursor, 0, sizeof(**cursor)); LG_LOCK_INIT((*cursor)->lock); if (!cursorTexInit(&(*cursor)->norm, b_shader_cursor_vert , b_shader_cursor_vert_size, b_shader_cursor_rgb_frag, b_shader_cursor_rgb_frag_size)) return false; if (!cursorTexInit(&(*cursor)->mono, b_shader_cursor_vert , b_shader_cursor_vert_size, b_shader_cursor_mono_frag, b_shader_cursor_mono_frag_size)) return false; if (!egl_modelInit(&(*cursor)->model)) { DEBUG_ERROR("Failed to initialize the cursor model"); return false; } egl_modelSetDefault((*cursor)->model, true); (*cursor)->cbMode = option_get_int("egl", "cbMode"); struct CursorPos pos = { .x = 0, .y = 0 }; struct CursorPos hs = { .x = 0, .y = 0 }; struct CursorSize size = { .w = 0, .h = 0 }; atomic_init(&(*cursor)->pos , pos ); atomic_init(&(*cursor)->hs , hs ); atomic_init(&(*cursor)->size , size); atomic_init(&(*cursor)->scale, 1.0f); return true; } void egl_cursorFree(EGL_Cursor ** cursor) { if (!*cursor) return; LG_LOCK_FREE((*cursor)->lock); if ((*cursor)->data) free((*cursor)->data); cursorTexFree(&(*cursor)->norm); cursorTexFree(&(*cursor)->mono); egl_modelFree(&(*cursor)->model); free(*cursor); *cursor = NULL; } bool egl_cursorSetShape(EGL_Cursor * cursor, const LG_RendererCursor type, const int width, const int height, const int stride, const uint8_t * data) { LG_LOCK(cursor->lock); cursor->type = type; cursor->width = width; cursor->height = (type == LG_CURSOR_MONOCHROME ? height / 2 : height); cursor->stride = stride; const size_t size = height * stride; if (size > cursor->dataSize) { if (cursor->data) free(cursor->data); cursor->data = malloc(size); if (!cursor->data) { DEBUG_ERROR("Failed to malloc buffer for cursor shape"); return false; } cursor->dataSize = size; } memcpy(cursor->data, data, size); cursor->update = true; LG_UNLOCK(cursor->lock); return true; } void egl_cursorSetSize(EGL_Cursor * cursor, const float w, const float h) { struct CursorSize size = { .w = w, .h = h }; atomic_store(&cursor->size, size); } void egl_cursorSetScale(EGL_Cursor * cursor, const float scale) { atomic_store(&cursor->scale, scale); } void egl_cursorSetState(EGL_Cursor * cursor, const bool visible, const float x, const float y, const float hx, const float hy) { cursor->visible = visible; struct CursorPos pos = { .x = x , .y = y }; struct CursorPos hs = { .x = hx, .y = hy }; atomic_store(&cursor->pos, pos); atomic_store(&cursor->hs , hs); } struct CursorState egl_cursorRender(EGL_Cursor * cursor, LG_RendererRotate rotate, int width, int height) { if (!cursor->visible) return (struct CursorState) { .visible = false }; if (cursor->update) { LG_LOCK(cursor->lock); cursor->update = false; uint8_t * data = cursor->data; switch(cursor->type) { case LG_CURSOR_MASKED_COLOR: { uint32_t xor[cursor->height][cursor->width]; for(int y = 0; y < cursor->height; ++y) for(int x = 0; x < cursor->width; ++x) { uint32_t * src = (uint32_t *)(data + (cursor->stride * y) + x * 4); const bool masked = (*src & 0xFF000000) != 0; if (masked) *src = xor[y][x] = *src & 0x00FFFFFF; else { xor[y][x] = 0xFF000000; *src |= 0xFF000000; } } egl_textureSetup(cursor->mono.texture, EGL_PF_BGRA, cursor->width, cursor->height, sizeof(xor[0])); egl_textureUpdate(cursor->mono.texture, (uint8_t *)xor, true); } // fall through case LG_CURSOR_COLOR: { egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->stride); egl_textureUpdate(cursor->norm.texture, data, true); break; } case LG_CURSOR_MONOCHROME: { uint32_t and[cursor->height][cursor->width]; uint32_t xor[cursor->height][cursor->width]; for(int y = 0; y < cursor->height; ++y) { for(int x = 0; x < cursor->width; ++x) { const uint8_t * srcAnd = data + (cursor->stride * y) + (x / 8); const uint8_t * srcXor = srcAnd + cursor->stride * cursor->height; const uint8_t mask = 0x80 >> (x % 8); const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000; const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000; and[y][x] = andMask; xor[y][x] = xorMask; } } egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, sizeof(and[0])); egl_textureSetup(cursor->mono.texture, EGL_PF_BGRA, cursor->width, cursor->height, sizeof(xor[0])); egl_textureUpdate(cursor->norm.texture, (uint8_t *)and, true); egl_textureUpdate(cursor->mono.texture, (uint8_t *)xor, true); break; } } LG_UNLOCK(cursor->lock); } cursor->rotate = rotate; struct CursorPos pos = atomic_load(&cursor->pos ); float scale = atomic_load(&cursor->scale); struct CursorPos hs = atomic_load(&cursor->hs ); struct CursorSize size = atomic_load(&cursor->size ); pos.x -= hs.x * scale; pos.y -= hs.y * scale; size.w *= scale; size.h *= scale; struct CursorState state = { .visible = true, }; switch (rotate) { case LG_ROTATE_0: state.rect.x = (pos.x * width + width) / 2; state.rect.y = (-pos.y * height + height) / 2 - size.h * height; state.rect.w = size.w * width + 3; state.rect.h = size.h * height + 3; break; case LG_ROTATE_90: state.rect.x = (-pos.y * width + width) / 2 - size.h * width; state.rect.y = (-pos.x * height + height) / 2 - size.w * height; state.rect.w = size.h * width + 3; state.rect.h = size.w * height + 3; break; case LG_ROTATE_180: state.rect.x = (-pos.x * width + width) / 2 - size.w * width; state.rect.y = (pos.y * height + height) / 2; state.rect.w = size.w * width + 3; state.rect.h = size.h * height + 3; break; case LG_ROTATE_270: state.rect.x = (pos.y * width + width) / 2; state.rect.y = (pos.x * height + height) / 2; state.rect.w = size.h * width + 3; state.rect.h = size.w * height + 3; break; default: DEBUG_UNREACHABLE(); } state.rect.x = max(0, state.rect.x - 1); state.rect.y = max(0, state.rect.y - 1); glEnable(GL_BLEND); switch(cursor->type) { case LG_CURSOR_MONOCHROME: { egl_shaderUse(cursor->norm.shader); setCursorTexUniforms(cursor, &cursor->norm, true, pos.x, pos.y, size.w, size.h, scale); glBlendFunc(GL_ZERO, GL_SRC_COLOR); egl_modelSetTexture(cursor->model, cursor->norm.texture); egl_modelRender(cursor->model); egl_shaderUse(cursor->mono.shader); setCursorTexUniforms(cursor, &cursor->mono, true, pos.x, pos.y, size.w, size.h, scale); glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); egl_modelSetTexture(cursor->model, cursor->mono.texture); egl_modelRender(cursor->model); break; } case LG_CURSOR_MASKED_COLOR: { egl_shaderUse(cursor->norm.shader); setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y, size.w, size.h, scale); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); egl_modelSetTexture(cursor->model, cursor->norm.texture); egl_modelRender(cursor->model); egl_shaderUse(cursor->mono.shader); setCursorTexUniforms(cursor, &cursor->mono, false, pos.x, pos.y, size.w, size.h, scale); glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); egl_modelSetTexture(cursor->model, cursor->mono.texture); egl_modelRender(cursor->model); break; } case LG_CURSOR_COLOR: { egl_shaderUse(cursor->norm.shader); setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y, size.w, size.h, scale); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); egl_modelSetTexture(cursor->model, cursor->norm.texture); egl_modelRender(cursor->model); break; } } glDisable(GL_BLEND); return state; } looking-glass-B6/client/renderers/EGL/cursor.h000066400000000000000000000031671434445012300214470ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include "egl.h" #include "interface/renderer.h" typedef struct EGL_Cursor EGL_Cursor; struct CursorState { bool visible; struct Rect rect; }; bool egl_cursorInit(EGL_Cursor ** cursor); void egl_cursorFree(EGL_Cursor ** cursor); bool egl_cursorSetShape( EGL_Cursor * cursor, const LG_RendererCursor type, const int width, const int height, const int stride, const uint8_t * data); void egl_cursorSetSize(EGL_Cursor * cursor, const float x, const float y); void egl_cursorSetScale(EGL_Cursor * cursor, const float scale); void egl_cursorSetState(EGL_Cursor * cursor, const bool visible, const float x, const float y, const float hx, const float hy); struct CursorState egl_cursorRender(EGL_Cursor * cursor, LG_RendererRotate rotate, int width, int height); looking-glass-B6/client/renderers/EGL/damage.c000066400000000000000000000074241434445012300213430ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "damage.h" #include "common/debug.h" #include "common/KVMFR.h" #include "common/locking.h" #include "app.h" #include "desktop_rects.h" #include "shader.h" #include "cimgui.h" #include #include // these headers are auto generated by cmake #include "damage.vert.h" #include "damage.frag.h" struct EGL_Damage { EGL_Shader * shader; EGL_DesktopRects * mesh; GLfloat transform[6]; bool show; int width , height; float translateX, translateY; float scaleX , scaleY; LG_RendererRotate rotate; // uniforms GLint uTransform; }; void egl_damageConfigUI(EGL_Damage * damage) { igCheckbox("Show damage overlay", &damage->show); } bool egl_damageInit(EGL_Damage ** damage) { *damage = malloc(sizeof(**damage)); if (!*damage) { DEBUG_ERROR("Failed to malloc EGL_Damage"); return false; } memset(*damage, 0, sizeof(EGL_Damage)); if (!egl_shaderInit(&(*damage)->shader)) { DEBUG_ERROR("Failed to initialize the damage shader"); return false; } if (!egl_shaderCompile((*damage)->shader, b_shader_damage_vert, b_shader_damage_vert_size, b_shader_damage_frag, b_shader_damage_frag_size)) { DEBUG_ERROR("Failed to compile the damage shader"); return false; } if (!egl_desktopRectsInit(&(*damage)->mesh, KVMFR_MAX_DAMAGE_RECTS)) { DEBUG_ERROR("Failed to initialize the mesh"); return false; } (*damage)->uTransform = egl_shaderGetUniform((*damage)->shader, "transform"); return true; } void egl_damageFree(EGL_Damage ** damage) { if (!*damage) return; egl_desktopRectsFree(&(*damage)->mesh); egl_shaderFree(&(*damage)->shader); free(*damage); *damage = NULL; } static void update_matrix(EGL_Damage * damage) { egl_desktopRectsMatrix(damage->transform, damage->width, damage->height, damage->translateX, damage->translateY, damage->scaleX, damage->scaleY, damage->rotate); } void egl_damageSetup(EGL_Damage * damage, int width, int height) { damage->width = width; damage->height = height; update_matrix(damage); } void egl_damageResize(EGL_Damage * damage, float translateX, float translateY, float scaleX, float scaleY) { damage->translateX = translateX; damage->translateY = translateY; damage->scaleX = scaleX; damage->scaleY = scaleY; update_matrix(damage); } bool egl_damageRender(EGL_Damage * damage, LG_RendererRotate rotate, const struct DesktopDamage * data) { if (!damage->show) return false; if (rotate != damage->rotate) { damage->rotate = rotate; update_matrix(damage); } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); egl_shaderUse(damage->shader); glUniformMatrix3x2fv(damage->uTransform, 1, GL_FALSE, damage->transform); if (data && data->count != 0) egl_desktopRectsUpdate(damage->mesh, (const struct DamageRects *) data, damage->width, damage->height); egl_desktopRectsRender(damage->mesh); glDisable(GL_BLEND); return true; } looking-glass-B6/client/renderers/EGL/damage.h000066400000000000000000000027201434445012300213420ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include "common/KVMFR.h" #include "interface/renderer.h" #include "desktop_rects.h" struct DesktopDamage { int count; FrameDamageRect rects[KVMFR_MAX_DAMAGE_RECTS]; }; typedef struct EGL_Damage EGL_Damage; bool egl_damageInit(EGL_Damage ** damage); void egl_damageFree(EGL_Damage ** damage); void egl_damageConfigUI(EGL_Damage * damage); void egl_damageSetup(EGL_Damage * damage, int width, int height); void egl_damageResize(EGL_Damage * damage, float translateX, float translateY, float scaleX, float scaleY); bool egl_damageRender(EGL_Damage * damage, LG_RendererRotate rotate, const struct DesktopDamage * data); looking-glass-B6/client/renderers/EGL/desktop.c000066400000000000000000000330211434445012300215660ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "desktop.h" #include "common/debug.h" #include "common/option.h" #include "common/locking.h" #include "common/array.h" #include "app.h" #include "texture.h" #include "shader.h" #include "desktop_rects.h" #include "cimgui.h" #include #include // these headers are auto generated by cmake #include "desktop.vert.h" #include "desktop_rgb.frag.h" #include "desktop_rgb.def.h" #include "postprocess.h" #include "filters.h" struct DesktopShader { EGL_Shader * shader; GLint uTransform; GLint uDesktopSize; GLint uScaleAlgo; GLint uNVGain; GLint uCBMode; }; struct EGL_Desktop { EGL * egl; EGLDisplay * display; EGL_Texture * texture; struct DesktopShader shader; EGL_DesktopRects * mesh; CountedBuffer * matrix; // internals int width, height; LG_RendererRotate rotate; bool useSpice; int spiceWidth, spiceHeight; EGL_Texture * spiceTexture; // scale algorithm int scaleAlgo; // night vision int nvMax; int nvGain; // colorblind mode int cbMode; bool useDMA; LG_RendererFormat format; EGL_PostProcess * pp; _Atomic(bool) processFrame; }; // forwards void toggleNV(int key, void * opaque); static bool egl_initDesktopShader( struct DesktopShader * shader, const char * vertex_code , size_t vertex_size, const char * fragment_code, size_t fragment_size ) { if (!egl_shaderInit(&shader->shader)) return false; if (!egl_shaderCompile(shader->shader, vertex_code , vertex_size, fragment_code, fragment_size)) { return false; } shader->uTransform = egl_shaderGetUniform(shader->shader, "transform" ); shader->uDesktopSize = egl_shaderGetUniform(shader->shader, "desktopSize"); shader->uScaleAlgo = egl_shaderGetUniform(shader->shader, "scaleAlgo" ); shader->uNVGain = egl_shaderGetUniform(shader->shader, "nvGain" ); shader->uCBMode = egl_shaderGetUniform(shader->shader, "cbMode" ); return true; } bool egl_desktopInit(EGL * egl, EGL_Desktop ** desktop_, EGLDisplay * display, bool useDMA, int maxRects) { EGL_Desktop * desktop = calloc(1, sizeof(EGL_Desktop)); if (!desktop) { DEBUG_ERROR("Failed to malloc EGL_Desktop"); return false; } *desktop_ = desktop; desktop->egl = egl; desktop->display = display; if (!egl_textureInit(&desktop->texture, display, useDMA ? EGL_TEXTYPE_DMABUF : EGL_TEXTYPE_FRAMEBUFFER)) { DEBUG_ERROR("Failed to initialize the desktop texture"); return false; } if (!egl_initDesktopShader( &desktop->shader, b_shader_desktop_vert , b_shader_desktop_vert_size, b_shader_desktop_rgb_frag, b_shader_desktop_rgb_frag_size)) { DEBUG_ERROR("Failed to initialize the generic desktop shader"); return false; } if (!egl_desktopRectsInit(&desktop->mesh, maxRects)) { DEBUG_ERROR("Failed to initialize the desktop mesh"); return false; } desktop->matrix = countedBufferNew(6 * sizeof(GLfloat)); if (!desktop->matrix) { DEBUG_ERROR("Failed to allocate the desktop matrix buffer"); return false; } app_registerKeybind(0, 'N', toggleNV, desktop, "Toggle night vision mode"); desktop->nvMax = option_get_int("egl", "nvGainMax"); desktop->nvGain = option_get_int("egl", "nvGain" ); desktop->cbMode = option_get_int("egl", "cbMode" ); desktop->scaleAlgo = option_get_int("egl", "scale" ); desktop->useDMA = useDMA; if (!egl_postProcessInit(&desktop->pp)) { DEBUG_ERROR("Failed to initialize the post process manager"); return false; } egl_postProcessAdd(desktop->pp, &egl_filterDownscaleOps); egl_postProcessAdd(desktop->pp, &egl_filterFFXCASOps ); egl_postProcessAdd(desktop->pp, &egl_filterFFXFSR1Ops ); return true; } void toggleNV(int key, void * opaque) { EGL_Desktop * desktop = (EGL_Desktop *)opaque; if (desktop->nvGain++ == desktop->nvMax) desktop->nvGain = 0; if (desktop->nvGain == 0) app_alert(LG_ALERT_INFO, "NV Disabled"); else if (desktop->nvGain == 1) app_alert(LG_ALERT_INFO, "NV Enabled"); else app_alert(LG_ALERT_INFO, "NV Gain + %d", desktop->nvGain - 1); app_invalidateWindow(true); } bool egl_desktopScaleValidate(struct Option * opt, const char ** error) { if (opt->value.x_int >= 0 && opt->value.x_int < EGL_SCALE_MAX) return true; *error = "Invalid scale algorithm number"; return false; } void egl_desktopFree(EGL_Desktop ** desktop) { if (!*desktop) return; egl_textureFree (&(*desktop)->texture ); egl_textureFree (&(*desktop)->spiceTexture ); egl_shaderFree (&(*desktop)->shader.shader); egl_desktopRectsFree(&(*desktop)->mesh ); countedBufferRelease(&(*desktop)->matrix ); egl_postProcessFree(&(*desktop)->pp); free(*desktop); *desktop = NULL; } static const char * algorithmNames[EGL_SCALE_MAX] = { [EGL_SCALE_AUTO] = "Automatic (downscale: linear, upscale: nearest)", [EGL_SCALE_NEAREST] = "Nearest", [EGL_SCALE_LINEAR] = "Linear", }; void egl_desktopConfigUI(EGL_Desktop * desktop) { igText("Scale algorithm:"); igPushItemWidth(igGetWindowWidth() - igGetStyle()->WindowPadding.x * 2); if (igBeginCombo("##scale", algorithmNames[desktop->scaleAlgo], 0)) { for (int i = 0; i < EGL_SCALE_MAX; ++i) { bool selected = i == desktop->scaleAlgo; if (igSelectable_Bool(algorithmNames[i], selected, 0, (ImVec2) { 0.0f, 0.0f })) desktop->scaleAlgo = i; if (selected) igSetItemDefaultFocus(); } igEndCombo(); } igPopItemWidth(); igText("Night vision mode:"); igSameLine(0.0f, -1.0f); igPushItemWidth(igGetWindowWidth() - igGetCursorPosX() - igGetStyle()->WindowPadding.x); const char * format; switch (desktop->nvGain) { case 0: format = "off"; break; case 1: format = "on"; break; default: format = "gain: %d"; } igSliderInt("##nvgain", &desktop->nvGain, 0, desktop->nvMax, format, 0); igPopItemWidth(); } bool egl_desktopSetup(EGL_Desktop * desktop, const LG_RendererFormat format) { memcpy(&desktop->format, &format, sizeof(LG_RendererFormat)); enum EGL_PixelFormat pixFmt; switch(format.type) { case FRAME_TYPE_BGRA: pixFmt = EGL_PF_BGRA; break; case FRAME_TYPE_RGBA: pixFmt = EGL_PF_RGBA; break; case FRAME_TYPE_RGBA10: pixFmt = EGL_PF_RGBA10; break; case FRAME_TYPE_RGBA16F: pixFmt = EGL_PF_RGBA16F; break; default: DEBUG_ERROR("Unsupported frame format"); return false; } desktop->width = format.frameWidth; desktop->height = format.frameHeight; if (!egl_textureSetup( desktop->texture, pixFmt, format.frameWidth, format.frameHeight, format.pitch )) { DEBUG_ERROR("Failed to setup the desktop texture"); return false; } return true; } bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd, const FrameDamageRect * damageRects, int damageRectsCount) { if (desktop->useDMA && dmaFd >= 0) { if (egl_textureUpdateFromDMA(desktop->texture, frame, dmaFd)) { atomic_store(&desktop->processFrame, true); return true; } DEBUG_WARN("DMA update failed, disabling DMABUF imports"); const char * vendor = (const char *)glGetString(GL_VENDOR); if (strstr(vendor, "NVIDIA")) { DEBUG_WARN("NVIDIA's DMABUF support is incomplete, please direct your complaints to NVIDIA"); DEBUG_WARN("This is not a bug in Looking Glass"); } desktop->useDMA = false; const char * gl_exts = (const char *)glGetString(GL_EXTENSIONS); if (!util_hasGLExt(gl_exts, "GL_EXT_buffer_storage")) { DEBUG_ERROR("GL_EXT_buffer_storage is needed to use EGL backend"); return false; } egl_textureFree(&desktop->texture); if (!egl_textureInit(&desktop->texture, desktop->display, EGL_TEXTYPE_FRAMEBUFFER)) { DEBUG_ERROR("Failed to initialize the desktop texture"); return false; } if (!egl_desktopSetup(desktop, desktop->format)) return false; } if (egl_textureUpdateFromFrame(desktop->texture, frame, damageRects, damageRectsCount)) { atomic_store(&desktop->processFrame, true); return true; } return false; } void egl_desktopResize(EGL_Desktop * desktop, int width, int height) { atomic_store(&desktop->processFrame, true); } bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth, unsigned int outputHeight, const float x, const float y, const float scaleX, const float scaleY, enum EGL_DesktopScaleType scaleType, LG_RendererRotate rotate, const struct DamageRects * rects) { EGL_Texture * tex; int width, height; if (desktop->useSpice) { tex = desktop->spiceTexture; width = desktop->spiceWidth; height = desktop->spiceHeight; } else { tex = desktop->texture; width = desktop->width; height = desktop->height; } if (outputWidth == 0 && outputHeight == 0) DEBUG_FATAL("outputWidth || outputHeight == 0"); enum EGL_TexStatus status; if ((status = egl_textureProcess(tex)) != EGL_TEX_STATUS_OK) { if (status != EGL_TEX_STATUS_NOTREADY) DEBUG_ERROR("Failed to process the desktop texture"); } int scaleAlgo = EGL_SCALE_NEAREST; egl_desktopRectsMatrix((float *)desktop->matrix->data, width, height, x, y, scaleX, scaleY, rotate); egl_desktopRectsUpdate(desktop->mesh, rects, width, height); if (atomic_exchange(&desktop->processFrame, false) || egl_postProcessConfigModified(desktop->pp)) egl_postProcessRun(desktop->pp, tex, desktop->mesh, width, height, outputWidth, outputHeight); unsigned int finalSizeX, finalSizeY; GLuint texture = egl_postProcessGetOutput(desktop->pp, &finalSizeX, &finalSizeY); glBindFramebuffer(GL_FRAMEBUFFER, 0); egl_resetViewport(desktop->egl); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); glBindSampler(0, tex->sampler); if (finalSizeX > width || finalSizeY > height) scaleType = EGL_DESKTOP_DOWNSCALE; switch (desktop->scaleAlgo) { case EGL_SCALE_AUTO: switch (scaleType) { case EGL_DESKTOP_UPSCALE: scaleAlgo = EGL_SCALE_NEAREST; break; case EGL_DESKTOP_NOSCALE: case EGL_DESKTOP_DOWNSCALE: scaleAlgo = EGL_SCALE_LINEAR; break; } break; default: scaleAlgo = desktop->scaleAlgo; } const struct DesktopShader * shader = &desktop->shader; EGL_Uniform uniforms[] = { { .type = EGL_UNIFORM_TYPE_1I, .location = shader->uScaleAlgo, .i = { scaleAlgo }, }, { .type = EGL_UNIFORM_TYPE_2F, .location = shader->uDesktopSize, .f = { width, height }, }, { .type = EGL_UNIFORM_TYPE_M3x2FV, .location = shader->uTransform, .m.transpose = GL_FALSE, .m.v = desktop->matrix }, { .type = EGL_UNIFORM_TYPE_1F, .location = shader->uNVGain, .f = { (float)desktop->nvGain } }, { .type = EGL_UNIFORM_TYPE_1I, .location = shader->uCBMode, .f = { desktop->cbMode } } }; egl_shaderSetUniforms(shader->shader, uniforms, ARRAY_LENGTH(uniforms)); egl_shaderUse(shader->shader); egl_desktopRectsRender(desktop->mesh); glBindTexture(GL_TEXTURE_2D, 0); return true; } void egl_desktopSpiceConfigure(EGL_Desktop * desktop, int width, int height) { if (!desktop->spiceTexture) if (!egl_textureInit(&desktop->spiceTexture, desktop->display, EGL_TEXTYPE_BUFFER_MAP)) { DEBUG_ERROR("Failed to initialize the spice desktop texture"); return; } if (!egl_textureSetup( desktop->spiceTexture, EGL_PF_BGRA, width, height, width * 4 )) { DEBUG_ERROR("Failed to setup the spice desktop texture"); return; } desktop->spiceWidth = width; desktop->spiceHeight = height; } void egl_desktopSpiceDrawFill(EGL_Desktop * desktop, int x, int y, int width, int height, uint32_t color) { /* this is a fairly hacky way to do this, but since it's only for the fallback * spice display it's not really an issue */ uint32_t line[width]; for(int x = 0; x < width; ++x) line[x] = color; for(; y < height; ++y) egl_textureUpdateRect(desktop->spiceTexture, x, y, width, 1, sizeof(line), (uint8_t *)line, false); atomic_store(&desktop->processFrame, true); } void egl_desktopSpiceDrawBitmap(EGL_Desktop * desktop, int x, int y, int width, int height, int stride, uint8_t * data, bool topDown) { egl_textureUpdateRect(desktop->spiceTexture, x, y, width, height, stride, data, topDown); atomic_store(&desktop->processFrame, true); } void egl_desktopSpiceShow(EGL_Desktop * desktop, bool show) { desktop->useSpice = show; } looking-glass-B6/client/renderers/EGL/desktop.h000066400000000000000000000044011434445012300215730ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include "egl.h" #include "desktop_rects.h" typedef struct EGL_Desktop EGL_Desktop; enum EGL_DesktopScaleType { EGL_DESKTOP_NOSCALE, EGL_DESKTOP_UPSCALE, EGL_DESKTOP_DOWNSCALE, }; struct Option; bool egl_desktopScaleValidate(struct Option * opt, const char ** error); bool egl_desktopInit(EGL * egl, EGL_Desktop ** desktop, EGLDisplay * display, bool useDMA, int maxRects); void egl_desktopFree(EGL_Desktop ** desktop); void egl_desktopConfigUI(EGL_Desktop * desktop); bool egl_desktopSetup (EGL_Desktop * desktop, const LG_RendererFormat format); bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd, const FrameDamageRect * damageRects, int damageRectsCount); void egl_desktopResize(EGL_Desktop * desktop, int width, int height); bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth, unsigned int outputHeight, const float x, const float y, const float scaleX, const float scaleY, enum EGL_DesktopScaleType scaleType, LG_RendererRotate rotate, const struct DamageRects * rects); void egl_desktopSpiceConfigure(EGL_Desktop * desktop, int width, int height); void egl_desktopSpiceDrawFill(EGL_Desktop * desktop, int x, int y, int width, int height, uint32_t color); void egl_desktopSpiceDrawBitmap(EGL_Desktop * desktop, int x, int y, int width, int height, int stride, uint8_t * data, bool topDown); void egl_desktopSpiceShow(EGL_Desktop * desktop, bool show); looking-glass-B6/client/renderers/EGL/desktop_rects.c000066400000000000000000000212271434445012300227730ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "desktop_rects.h" #include "common/debug.h" #include "common/KVMFR.h" #include "common/locking.h" #include #include #include #include #include "util.h" struct EGL_DesktopRects { GLfloat * lastVertices; int lastVerticesCount; int lastVerticesSize; GLuint buffers[2]; GLuint vao; int count; int maxCount; }; bool egl_desktopRectsInit(EGL_DesktopRects ** rects_, int maxCount) { EGL_DesktopRects * rects = malloc(sizeof(*rects)); if (!rects) { DEBUG_ERROR("Failed to malloc EGL_DesktopRects"); return false; } *rects_ = rects; memset(rects, 0, sizeof(*rects)); glGenVertexArrays(1, &rects->vao); glBindVertexArray(rects->vao); glGenBuffers(2, rects->buffers); glBindBuffer(GL_ARRAY_BUFFER, rects->buffers[0]); glBufferData(GL_ARRAY_BUFFER, maxCount * 8 * sizeof(GLfloat), NULL, GL_STREAM_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL); glBindBuffer(GL_ARRAY_BUFFER, 0); GLushort indices[maxCount * 6]; for (int i = 0; i < maxCount; ++i) { indices[6 * i + 0] = 4 * i + 0; indices[6 * i + 1] = 4 * i + 1; indices[6 * i + 2] = 4 * i + 2; indices[6 * i + 3] = 4 * i + 0; indices[6 * i + 4] = 4 * i + 2; indices[6 * i + 5] = 4 * i + 3; } glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, rects->buffers[1]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof indices, indices, GL_STATIC_DRAW); glBindVertexArray(0); rects->count = 0; rects->maxCount = maxCount; return true; } void egl_desktopRectsFree(EGL_DesktopRects ** rects_) { EGL_DesktopRects * rects = *rects_; if (!rects) return; glDeleteVertexArrays(1, &rects->vao); glDeleteBuffers(2, rects->buffers); free(rects->lastVertices); free(rects); *rects_ = NULL; } inline static void rectToVertices(GLfloat * vertex, const FrameDamageRect * rect) { vertex[0] = rect->x; vertex[1] = rect->y; vertex[2] = rect->x + rect->width; vertex[3] = rect->y; vertex[4] = rect->x + rect->width; vertex[5] = rect->y + rect->height; vertex[6] = rect->x; vertex[7] = rect->y + rect->height; } void egl_desktopRectsUpdate(EGL_DesktopRects * rects, const struct DamageRects * data, int width, int height) { if (data && data->count == 0) { rects->count = 0; return; } const int count = (!data || data->count < 0 ? 1 : data->count) * 8; GLfloat vertices[count]; if (!data || data->count < 0) { FrameDamageRect full = { .x = 0, .y = 0, .width = width, .height = height, }; rects->count = 1; rectToVertices(vertices, &full); } else { rects->count = data->count; DEBUG_ASSERT(rects->count <= rects->maxCount); for (int i = 0; i < rects->count; ++i) rectToVertices(vertices + i * 8, data->rects + i); } // check if the value actually changed and needs updating if (count == rects->lastVerticesCount && memcmp(rects->lastVertices, vertices, sizeof(GLfloat) * count) == 0) return; // ensure the local storage is large enough if (count > rects->lastVerticesSize) { if (rects->lastVertices) free(rects->lastVertices); rects->lastVertices = malloc(sizeof(GLfloat) * count); if (!rects->lastVertices) { DEBUG_ERROR("out of memory"); return; } rects->lastVerticesSize = count; } // copy the last value for later comparison rects->lastVerticesCount = count; memcpy(rects->lastVertices, vertices, sizeof(GLfloat) * count); glBindBuffer(GL_ARRAY_BUFFER, rects->buffers[0]); glBufferSubData(GL_ARRAY_BUFFER, 0, rects->count * 8 * sizeof(GLfloat), vertices); glBindBuffer(GL_ARRAY_BUFFER, 0); } static void desktopToGLSpace(double matrix[6], int width, int height, double translateX, double translateY, double scaleX, double scaleY, LG_RendererRotate rotate) { switch (rotate) { case LG_ROTATE_0: matrix[0] = 2.0 * scaleX / width; matrix[1] = 0.0; matrix[2] = 0.0; matrix[3] = -2.0 * scaleY / height; matrix[4] = translateX - scaleX; matrix[5] = translateY + scaleY; return; case LG_ROTATE_90: matrix[0] = 0.0; matrix[1] = -2.0 * scaleY / width; matrix[2] = -2.0 * scaleX / height; matrix[3] = 0.0; matrix[4] = translateX + scaleX; matrix[5] = translateY + scaleY; return; case LG_ROTATE_180: matrix[0] = -2.0 * scaleX / width; matrix[1] = 0.0; matrix[2] = 0.0; matrix[3] = 2.0 * scaleY / height; matrix[4] = translateX + scaleX; matrix[5] = translateY - scaleY; return; case LG_ROTATE_270: matrix[0] = 0.0; matrix[1] = 2.0 * scaleY / width; matrix[2] = 2.0 * scaleX / height; matrix[3] = 0.0; matrix[4] = translateX - scaleX; matrix[5] = translateY - scaleY; } } void egl_desktopRectsMatrix(float matrix[6], int width, int height, float translateX, float translateY, float scaleX, float scaleY, LG_RendererRotate rotate) { double temp[6]; desktopToGLSpace(temp, width, height, translateX, translateY, scaleX, scaleY, rotate); for (int i = 0; i < 6; ++i) matrix[i] = temp[i]; } void egl_desktopToScreenMatrix(double matrix[6], int frameWidth, int frameHeight, double translateX, double translateY, double scaleX, double scaleY, LG_RendererRotate rotate, double windowWidth, double windowHeight) { desktopToGLSpace(matrix, frameWidth, frameHeight, translateX, translateY, scaleX, scaleY, rotate); double hw = windowWidth / 2; double hh = windowHeight / 2; matrix[0] *= hw; matrix[1] *= hh; matrix[2] *= hw; matrix[3] *= hh; matrix[4] = matrix[4] * hw + hw; matrix[5] = matrix[5] * hh + hh; } inline static void matrixMultiply(const double matrix[6], double * nx, double * ny, double x, double y) { *nx = matrix[0] * x + matrix[2] * y + matrix[4]; *ny = matrix[1] * x + matrix[3] * y + matrix[5]; } struct Rect egl_desktopToScreen(const double matrix[6], const struct FrameDamageRect * rect) { double x1, y1, x2, y2; matrixMultiply(matrix, &x1, &y1, rect->x, rect->y); matrixMultiply(matrix, &x2, &y2, rect->x + rect->width, rect->y + rect->height); int x3 = min(x1, x2); int y3 = min(y1, y2); return (struct Rect) { .x = x3, .y = y3, .w = ceil(max(x1, x2)) - x3, .h = ceil(max(y1, y2)) - y3, }; } void egl_screenToDesktopMatrix(double matrix[6], int frameWidth, int frameHeight, double translateX, double translateY, double scaleX, double scaleY, LG_RendererRotate rotate, double windowWidth, double windowHeight) { double inverted[6] = {0}; egl_desktopToScreenMatrix(inverted, frameWidth, frameHeight, translateX, translateY, scaleX, scaleY, rotate, windowWidth, windowHeight); double det = inverted[0] * inverted[3] - inverted[1] * inverted[2]; matrix[0] = inverted[3] / det; matrix[1] = -inverted[1] / det; matrix[2] = -inverted[2] / det; matrix[3] = inverted[0] / det; matrix[4] = (inverted[2] * inverted[5] - inverted[3] * inverted[4]) / det; matrix[5] = (inverted[1] * inverted[4] - inverted[0] * inverted[5]) / det; } bool egl_screenToDesktop(struct FrameDamageRect * output, const double matrix[6], const struct Rect * rect, int width, int height) { double x1, y1, x2, y2; matrixMultiply(matrix, &x1, &y1, rect->x - 1, rect->y - 1); matrixMultiply(matrix, &x2, &y2, rect->x + rect->w + 1, rect->y + rect->h + 1); int x3 = min(x1, x2); int y3 = min(y1, y2); int x4 = ceil(max(x1, x2)); int y4 = ceil(max(y1, y2)); if (x4 < 0 || y4 < 0 || x3 >= width || y3 >= height) return false; output->x = max(x3, 0); output->y = max(y3, 0); output->width = min(width, x4) - output->x; output->height = min(height, y4) - output->y; return true; } void egl_desktopRectsRender(EGL_DesktopRects * rects) { if (!rects->count) return; glBindVertexArray(rects->vao); glDrawElements(GL_TRIANGLES, 6 * rects->count, GL_UNSIGNED_SHORT, NULL); glBindVertexArray(0); } looking-glass-B6/client/renderers/EGL/desktop_rects.h000066400000000000000000000041701434445012300227760ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include "common/types.h" #include "interface/renderer.h" struct DamageRects { int count; FrameDamageRect rects[]; }; typedef struct EGL_DesktopRects EGL_DesktopRects; bool egl_desktopRectsInit(EGL_DesktopRects ** rects, int maxCount); void egl_desktopRectsFree(EGL_DesktopRects ** rects); void egl_desktopRectsMatrix(float matrix[6], int width, int height, float translateX, float translateY, float scaleX, float scaleY, LG_RendererRotate rotate); void egl_desktopToScreenMatrix(double matrix[6], int frameWidth, int frameHeight, double translateX, double translateY, double scaleX, double scaleY, LG_RendererRotate rotate, double windowWidth, double windowHeight); struct Rect egl_desktopToScreen(const double matrix[6], const struct FrameDamageRect * rect); void egl_screenToDesktopMatrix(double matrix[6], int frameWidth, int frameHeight, double translateX, double translateY, double scaleX, double scaleY, LG_RendererRotate rotate, double windowWidth, double windowHeight); bool egl_screenToDesktop(struct FrameDamageRect * output, const double matrix[6], const struct Rect * rect, int width, int height); void egl_desktopRectsUpdate(EGL_DesktopRects * rects, const struct DamageRects * data, int width, int height); void egl_desktopRectsRender(EGL_DesktopRects * rects);looking-glass-B6/client/renderers/EGL/egl.c000066400000000000000000001056311434445012300206730ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/renderer.h" #include "common/debug.h" #include "common/KVMFR.h" #include "common/option.h" #include "common/sysinfo.h" #include "common/rects.h" #include "common/time.h" #include "common/locking.h" #include "app.h" #include "util.h" #include #include #include "cimgui.h" #include "generator/output/cimgui_impl.h" #include #include #include "egl_dynprocs.h" #include "model.h" #include "shader.h" #include "damage.h" #include "desktop.h" #include "cursor.h" #include "postprocess.h" #include "util.h" #define MAX_BUFFER_AGE 3 #define DESKTOP_DAMAGE_COUNT 4 #define MAX_ACCUMULATED_DAMAGE ((KVMFR_MAX_DAMAGE_RECTS + MAX_OVERLAY_RECTS + 2) * MAX_BUFFER_AGE) #define IDX_AGO(counter, i, total) (((counter) + (total) - (i)) % (total)) struct Options { bool vsync; bool doubleBuffer; }; struct Inst { LG_Renderer base; bool dmaSupport; LG_RendererParams params; struct Options opt; EGLNativeWindowType nativeWind; EGLDisplay display; EGLConfig configs; EGLSurface surface; EGLContext context, frameContext; EGL_Desktop * desktop; // the desktop EGL_Cursor * cursor; // the mouse cursor EGL_Damage * damage; // the damage display bool imgui; // if imgui was initialized LG_RendererFormat format; bool formatValid; int width, height; float uiScale; struct DoubleRect destRect; LG_RendererRotate rotate; //client side rotation float translateX , translateY; float scaleX , scaleY; float splashRatio; float screenScaleX, screenScaleY; int viewportWidth, viewportHeight; enum EGL_DesktopScaleType scaleType; bool cursorVisible; int cursorX , cursorY; int cursorHX , cursorHY; float mouseWidth , mouseHeight; float mouseScaleX, mouseScaleY; bool showDamage; bool scalePointer; struct CursorState cursorLast; bool hadOverlay; bool noSwapDamage; struct DesktopDamage desktopDamage[DESKTOP_DAMAGE_COUNT]; unsigned int desktopDamageIdx; LG_Lock desktopDamageLock; bool hasBufferAge; struct Rect overlayHistory[DESKTOP_DAMAGE_COUNT][MAX_OVERLAY_RECTS + 1]; int overlayHistoryCount[DESKTOP_DAMAGE_COUNT]; unsigned int overlayHistoryIdx; RingBuffer importTimings; GraphHandle importGraph; bool showSpice; int spiceWidth, spiceHeight; }; static struct Option egl_options[] = { { .module = "egl", .name = "vsync", .description = "Enable vsync", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "egl", .name = "doubleBuffer", .description = "Enable double buffering", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "egl", .name = "multisample", .description = "Enable Multisampling", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "egl", .name = "nvGainMax", .description = "The maximum night vision gain", .type = OPTION_TYPE_INT, .value.x_int = 1 }, { .module = "egl", .name = "nvGain", .description = "The initial night vision gain at startup", .type = OPTION_TYPE_INT, .value.x_int = 0 }, { .module = "egl", .name = "cbMode", .description = "Color Blind Mode (0 = Off, 1 = Protanope, 2 = Deuteranope, 3 = Tritanope)", .type = OPTION_TYPE_INT, .value.x_int = 0 }, { .module = "egl", .name = "scale", .description = "Set the scale algorithm (0 = auto, 1 = nearest, 2 = linear)", .type = OPTION_TYPE_INT, .validator = egl_desktopScaleValidate, .value.x_int = 0 }, { .module = "egl", .name = "debug", .description = "Enable debug output", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "egl", .name = "noBufferAge", .description = "Disable partial rendering based on buffer age", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "egl", .name = "noSwapDamage", .description = "Disable swapping with damage", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "egl", .name = "scalePointer", .description = "Keep the pointer size 1:1 when downscaling", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, {0} }; static const char * egl_getName(void) { return "EGL"; } static void egl_setup(void) { option_register(egl_options); egl_postProcessEarlyInit(); } static bool egl_create(LG_Renderer ** renderer, const LG_RendererParams params, bool * needsOpenGL) { // check if EGL is even available if (!eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS)) { DEBUG_WARN("EGL is not available"); return false; } // create our local storage struct Inst * this = calloc(1, sizeof(*this)); if (!this) { DEBUG_INFO("Failed to allocate %lu bytes", sizeof(*this)); return false; } *renderer = &this->base; // safe off parameteres and init our default option values memcpy(&this->params, ¶ms, sizeof(LG_RendererParams)); this->opt.vsync = option_get_bool("egl", "vsync"); this->opt.doubleBuffer = option_get_bool("egl", "doubleBuffer"); this->translateX = 0; this->translateY = 0; this->scaleX = 1.0f; this->scaleY = 1.0f; this->screenScaleX = 1.0f; this->screenScaleY = 1.0f; this->uiScale = 1.0; LG_LOCK_INIT(this->desktopDamageLock); this->desktopDamage[0].count = -1; this->importTimings = ringbuffer_new(256, sizeof(float)); this->importGraph = app_registerGraph("IMPORT", this->importTimings, 0.0f, 5.0f, NULL); *needsOpenGL = false; return true; } static bool egl_initialize(LG_Renderer * renderer) { struct Inst * this = UPCAST(struct Inst, renderer); DEBUG_INFO("Double buffering is %s", this->opt.doubleBuffer ? "on" : "off"); return true; } static void egl_deinitialize(LG_Renderer * renderer) { struct Inst * this = UPCAST(struct Inst, renderer); if (this->imgui) ImGui_ImplOpenGL3_Shutdown(); ringbuffer_free(&this->importTimings); egl_desktopFree(&this->desktop); egl_cursorFree (&this->cursor); egl_damageFree (&this->damage); LG_LOCK_FREE(this->lock); LG_LOCK_FREE(this->desktopDamageLock); eglMakeCurrent(this->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (this->frameContext) eglDestroyContext(this->display, this->frameContext); if (this->context) eglDestroyContext(this->display, this->context); eglTerminate(this->display); free(this); } static bool egl_supports(LG_Renderer * renderer, LG_RendererSupport flag) { struct Inst * this = UPCAST(struct Inst, renderer); switch(flag) { case LG_SUPPORTS_DMABUF: return this->dmaSupport; default: return false; } } static void egl_onRestart(LG_Renderer * renderer) { struct Inst * this = UPCAST(struct Inst, renderer); eglDestroyContext(this->display, this->frameContext); this->frameContext = NULL; INTERLOCKED_SECTION(this->desktopDamageLock, { this->desktopDamage[this->desktopDamageIdx].count = -1; }); } static void egl_calc_mouse_size(struct Inst * this) { if (this->showSpice) { this->mouseScaleX = 2.0f / this->spiceWidth; this->mouseScaleY = 2.0f / this->spiceHeight; egl_cursorSetSize(this->cursor, (this->mouseWidth * (1.0f / this->spiceWidth )) * this->scaleX, (this->mouseHeight * (1.0f / this->spiceHeight)) * this->scaleY ); return; } if (!this->formatValid) return; int w, h; switch(this->format.rotate) { case LG_ROTATE_0: case LG_ROTATE_180: this->mouseScaleX = 2.0f / this->format.screenWidth; this->mouseScaleY = 2.0f / this->format.screenHeight; w = this->format.screenWidth; h = this->format.screenHeight; break; case LG_ROTATE_90: case LG_ROTATE_270: this->mouseScaleX = 2.0f / this->format.screenHeight; this->mouseScaleY = 2.0f / this->format.screenWidth; w = this->format.screenHeight; h = this->format.screenWidth; break; default: DEBUG_UNREACHABLE(); } switch((this->format.rotate + this->rotate) % LG_ROTATE_MAX) { case LG_ROTATE_0: case LG_ROTATE_180: egl_cursorSetSize(this->cursor, (this->mouseWidth * (1.0f / w)) * this->scaleX, (this->mouseHeight * (1.0f / h)) * this->scaleY ); break; case LG_ROTATE_90: case LG_ROTATE_270: egl_cursorSetSize(this->cursor, (this->mouseWidth * (1.0f / w)) * this->scaleY, (this->mouseHeight * (1.0f / h)) * this->scaleX ); break; } } static void egl_calc_mouse_state(struct Inst * this) { if (this->showSpice) { egl_cursorSetState( this->cursor, this->cursorVisible, (((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX, (((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY, ((float)this->cursorHX * this->mouseScaleX) * this->scaleX, ((float)this->cursorHY * this->mouseScaleY) * this->scaleY ); return; } if (!this->formatValid) return; switch((this->format.rotate + this->rotate) % LG_ROTATE_MAX) { case LG_ROTATE_0: case LG_ROTATE_180: egl_cursorSetState( this->cursor, this->cursorVisible, (((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX, (((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY, ((float)this->cursorHX * this->mouseScaleX) * this->scaleX, ((float)this->cursorHY * this->mouseScaleY) * this->scaleY ); break; case LG_ROTATE_90: case LG_ROTATE_270: egl_cursorSetState( this->cursor, this->cursorVisible, (((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleY, (((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleX, ((float)this->cursorHX * this->mouseScaleX) * this->scaleY, ((float)this->cursorHY * this->mouseScaleY) * this->scaleX ); break; } } static void egl_update_scale_type(struct Inst * this) { int width = 0, height = 0; switch (this->rotate) { case LG_ROTATE_0: case LG_ROTATE_180: width = this->format.frameWidth; height = this->format.frameHeight; break; case LG_ROTATE_90: case LG_ROTATE_270: width = this->format.frameHeight; height = this->format.frameWidth; break; } if (width == this->viewportWidth || height == this->viewportHeight) this->scaleType = EGL_DESKTOP_NOSCALE; else if (width > this->viewportWidth || height > this->viewportHeight) this->scaleType = EGL_DESKTOP_DOWNSCALE; else this->scaleType = EGL_DESKTOP_UPSCALE; } void egl_resetViewport(EGL * this) { glViewport(0, 0, this->width, this->height); } static void egl_onResize(LG_Renderer * renderer, const int width, const int height, const double scale, const LG_RendererRect destRect, LG_RendererRotate rotate) { struct Inst * this = UPCAST(struct Inst, renderer); this->width = width * scale; this->height = height * scale; this->uiScale = (float) scale; this->rotate = rotate; this->destRect.x = destRect.x * scale; this->destRect.y = destRect.y * scale; this->destRect.w = destRect.w * scale; this->destRect.h = destRect.h * scale; glViewport(0, 0, this->width, this->height); if (destRect.valid) { this->translateX = -1.0f + (((this->destRect.w / 2) + this->destRect.x) * 2) / (float)this->width; this->translateY = 1.0f - (((this->destRect.h / 2) + this->destRect.y) * 2) / (float)this->height; this->scaleX = (float)this->destRect.w / (float)this->width; this->scaleY = (float)this->destRect.h / (float)this->height; this->viewportWidth = this->destRect.w; this->viewportHeight = this->destRect.h; } egl_update_scale_type(this); egl_calc_mouse_size(this); this->splashRatio = (float)width / (float)height; this->screenScaleX = 1.0f / this->width; this->screenScaleY = 1.0f / this->height; egl_calc_mouse_state(this); if (this->scalePointer) { float scale = max(1.0f, this->formatValid ? max( (float)this->format.screenWidth / this->width, (float)this->format.screenHeight / this->height) : 1.0f); egl_cursorSetScale(this->cursor, scale); } INTERLOCKED_SECTION(this->desktopDamageLock, { this->desktopDamage[this->desktopDamageIdx].count = -1; }); // this is needed to refresh the font atlas texture ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplOpenGL3_Init("#version 300 es"); ImGui_ImplOpenGL3_NewFrame(); egl_damageResize(this->damage, this->translateX, this->translateY, this->scaleX, this->scaleY); egl_desktopResize(this->desktop, this->width, this->height); } static bool egl_onMouseShape(LG_Renderer * renderer, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data) { struct Inst * this = UPCAST(struct Inst, renderer); if (!egl_cursorSetShape(this->cursor, cursor, width, height, pitch, data)) { DEBUG_ERROR("Failed to update the cursor shape"); return false; } this->mouseWidth = width; this->mouseHeight = height; egl_calc_mouse_size(this); return true; } static bool egl_onMouseEvent(LG_Renderer * renderer, const bool visible, int x, int y, const int hx, const int hy) { struct Inst * this = UPCAST(struct Inst, renderer); this->cursorVisible = visible; this->cursorX = x + hx; this->cursorY = y + hy; this->cursorHX = hx; this->cursorHY = hy; egl_calc_mouse_state(this); return true; } static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat format) { struct Inst * this = UPCAST(struct Inst, renderer); memcpy(&this->format, &format, sizeof(LG_RendererFormat)); this->formatValid = true; /* this event runs in a second thread so we need to init it here */ if (!this->frameContext) { static EGLint attrs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; if (!(this->frameContext = eglCreateContext(this->display, this->configs, this->context, attrs))) { DEBUG_ERROR("Failed to create the frame context"); return false; } if (!eglMakeCurrent(this->display, EGL_NO_SURFACE, EGL_NO_SURFACE, this->frameContext)) { DEBUG_ERROR("Failed to make the frame context current"); return false; } } if (this->scalePointer) { float scale = max(1.0f, (float)format.screenWidth / this->width); egl_cursorSetScale(this->cursor, scale); } egl_update_scale_type(this); egl_damageSetup(this->damage, format.frameWidth, format.frameHeight); /* we need full screen damage when the format changes */ INTERLOCKED_SECTION(this->desktopDamageLock, { this->desktopDamage[this->desktopDamageIdx].count = -1; }); return egl_desktopSetup(this->desktop, format); } static bool egl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame, int dmaFd, const FrameDamageRect * damageRects, int damageRectsCount) { struct Inst * this = UPCAST(struct Inst, renderer); uint64_t start = nanotime(); if (!egl_desktopUpdate(this->desktop, frame, dmaFd, damageRects, damageRectsCount)) { DEBUG_INFO("Failed to to update the desktop"); return false; } ringbuffer_push(this->importTimings, &(float){ (nanotime() - start) * 1e-6f }); INTERLOCKED_SECTION(this->desktopDamageLock, { struct DesktopDamage * damage = this->desktopDamage + this->desktopDamageIdx; if (damage->count == -1 || damageRectsCount == 0 || damage->count + damageRectsCount >= KVMFR_MAX_DAMAGE_RECTS) damage->count = -1; else { memcpy(damage->rects + damage->count, damageRects, damageRectsCount * sizeof(FrameDamageRect)); damage->count += damageRectsCount; } }); return true; } static void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar * message, const void * userParam) { enum DebugLevel level = DEBUG_LEVEL_FIXME; switch (severity) { case GL_DEBUG_SEVERITY_HIGH: level = DEBUG_LEVEL_ERROR; break; case GL_DEBUG_SEVERITY_MEDIUM: level = DEBUG_LEVEL_WARN; break; case GL_DEBUG_SEVERITY_LOW: case GL_DEBUG_SEVERITY_NOTIFICATION: level = DEBUG_LEVEL_INFO; break; } const char * sourceName = "unknown"; switch (source) { case GL_DEBUG_SOURCE_API: sourceName = "OpenGL API"; break; case GL_DEBUG_SOURCE_WINDOW_SYSTEM: sourceName = "window system"; break; case GL_DEBUG_SOURCE_SHADER_COMPILER: sourceName = "shader compiler"; break; case GL_DEBUG_SOURCE_THIRD_PARTY: sourceName = "third party"; break; case GL_DEBUG_SOURCE_APPLICATION: sourceName = "application"; break; case GL_DEBUG_SOURCE_OTHER: sourceName = "other"; break; } const char * typeName = "unknown"; switch (type) { case GL_DEBUG_TYPE_ERROR: typeName = "error"; break; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: typeName = "deprecated behaviour"; break; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: typeName = "undefined behaviour"; break; case GL_DEBUG_TYPE_PORTABILITY: typeName = "portability"; break; case GL_DEBUG_TYPE_PERFORMANCE: typeName = "performance"; break; case GL_DEBUG_TYPE_MARKER: typeName = "marker"; break; case GL_DEBUG_TYPE_PUSH_GROUP: typeName = "group pushing"; break; case GL_DEBUG_TYPE_POP_GROUP: typeName = "group popping"; break; case GL_DEBUG_TYPE_OTHER: typeName = "other"; break; } DEBUG_PRINT(level, "GL message (source: %s, type: %s): %s", sourceName, typeName, message); } static void egl_configUI(void * opaque, int * id) { struct Inst * this = opaque; egl_damageConfigUI(this->damage); igSeparator(); egl_desktopConfigUI(this->desktop); } static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA) { struct Inst * this = UPCAST(struct Inst, renderer); this->nativeWind = app_getEGLNativeWindow(); if (!this->nativeWind) { DEBUG_ERROR("Failed to get EGL native window"); return false; } this->display = app_getEGLDisplay(); if (this->display == EGL_NO_DISPLAY) { DEBUG_ERROR("Failed to get EGL display"); return false; } int maj, min; if (!eglInitialize(this->display, &maj, &min)) { DEBUG_ERROR("Unable to initialize EGL"); return false; } int maxSamples = 1; if (option_get_bool("egl", "multisample")) { if (app_getProp(LG_DS_MAX_MULTISAMPLE, &maxSamples) && maxSamples > 1) { if (maxSamples > 4) maxSamples = 4; DEBUG_INFO("Multisampling enabled, max samples: %d", maxSamples); } } EGLint attr[] = { EGL_BUFFER_SIZE , 24, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SAMPLE_BUFFERS , maxSamples > 0 ? 1 : 0, EGL_SAMPLES , maxSamples, EGL_NONE }; EGLint num_config; if (!eglChooseConfig(this->display, attr, &this->configs, 1, &num_config)) { DEBUG_ERROR("Failed to choose config (eglError: 0x%x)", eglGetError()); return false; } const EGLint surfattr[] = { EGL_RENDER_BUFFER, this->opt.doubleBuffer ? EGL_BACK_BUFFER : EGL_SINGLE_BUFFER, EGL_NONE }; this->surface = eglCreateWindowSurface(this->display, this->configs, this->nativeWind, surfattr); if (this->surface == EGL_NO_SURFACE) { // On Nvidia proprietary drivers on Wayland, specifying EGL_RENDER_BUFFER can cause // window creation to fail, so we try again without it. this->surface = eglCreateWindowSurface(this->display, this->configs, this->nativeWind, NULL); if (this->surface == EGL_NO_SURFACE) { DEBUG_ERROR("Failed to create EGL surface (eglError: 0x%x)", eglGetError()); return false; } else DEBUG_WARN("EGL surface creation with EGL_RENDER_BUFFER failed, " "egl:doubleBuffer setting may not be respected"); } const char * client_exts = eglQueryString(this->display, EGL_EXTENSIONS); if (!client_exts) { DEBUG_ERROR("Failed to query EGL_EXTENSIONS"); return false; } bool debug = option_get_bool("egl", "debug"); EGLint ctxattr[5]; int ctxidx = 0; ctxattr[ctxidx++] = EGL_CONTEXT_CLIENT_VERSION; ctxattr[ctxidx++] = 2; if (maj > 1 || (maj == 1 && min >= 5)) { ctxattr[ctxidx++] = EGL_CONTEXT_OPENGL_DEBUG; ctxattr[ctxidx++] = debug ? EGL_TRUE : EGL_FALSE; } else if (util_hasGLExt(client_exts, "EGL_KHR_create_context")) { ctxattr[ctxidx++] = EGL_CONTEXT_FLAGS_KHR; ctxattr[ctxidx++] = debug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0; } else if (debug) DEBUG_WARN("Cannot create debug contexts before EGL 1.5 without EGL_KHR_create_context"); ctxattr[ctxidx] = EGL_NONE; this->context = eglCreateContext(this->display, this->configs, EGL_NO_CONTEXT, ctxattr); if (this->context == EGL_NO_CONTEXT) { DEBUG_ERROR("Failed to create EGL context (eglError: 0x%x)", eglGetError()); return false; } EGLint rb = 0; eglQuerySurface(this->display, this->surface, EGL_RENDER_BUFFER, &rb); switch(rb) { case EGL_SINGLE_BUFFER: DEBUG_INFO("Single buffer mode"); break; case EGL_BACK_BUFFER: DEBUG_INFO("Back buffer mode"); break; default: DEBUG_WARN("Unknown render buffer mode: %d", rb); break; } eglMakeCurrent(this->display, this->surface, this->surface, this->context); const char * gl_exts = (const char *)glGetString(GL_EXTENSIONS); if (!gl_exts) { DEBUG_ERROR("Failed to query GL_EXTENSIONS"); return false; } const char * vendor = (const char *)glGetString(GL_VENDOR); if (!vendor) { DEBUG_ERROR("Failed to query GL_VENDOR"); return false; } DEBUG_INFO("EGL : %d.%d", maj, min); DEBUG_INFO("Vendor : %s", vendor); DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER)); DEBUG_INFO("Version : %s", glGetString(GL_VERSION )); DEBUG_INFO("EGL APIs: %s", eglQueryString(this->display, EGL_CLIENT_APIS)); if (debug) { DEBUG_INFO("EGL Exts: %s", client_exts); DEBUG_INFO("GL Exts : %s", gl_exts); } GLint esMaj, esMin; glGetIntegerv(GL_MAJOR_VERSION, &esMaj); glGetIntegerv(GL_MINOR_VERSION, &esMin); if (!util_hasGLExt(gl_exts, "GL_EXT_texture_format_BGRA8888")) { DEBUG_ERROR("GL_EXT_texture_format_BGRA8888 is needed to use EGL backend"); return false; } this->hasBufferAge = util_hasGLExt(client_exts, "EGL_EXT_buffer_age"); if (!this->hasBufferAge) DEBUG_WARN("GL_EXT_buffer_age is not supported, will not perform as well."); if (this->hasBufferAge && option_get_bool("egl", "noBufferAge")) { DEBUG_WARN("egl:noBufferAge specified, disabling buffer age."); this->hasBufferAge = false; } this->noSwapDamage = option_get_bool("egl", "noSwapDamage"); if (this->noSwapDamage) DEBUG_WARN("egl:noSwapDamage specified, disabling swap buffers with damage."); this->scalePointer = option_get_bool("egl", "scalePointer"); if (!g_egl_dynProcs.glEGLImageTargetTexture2DOES) DEBUG_INFO("glEGLImageTargetTexture2DOES unavilable, DMA support disabled"); else if (!g_egl_dynProcs.eglCreateImage || !g_egl_dynProcs.eglDestroyImage) DEBUG_INFO("eglCreateImage or eglDestroyImage unavailable, DMA support disabled"); else if (!util_hasGLExt(client_exts, "EGL_EXT_image_dma_buf_import")) DEBUG_INFO("EGL_EXT_image_dma_buf_import unavailable, DMA support disabled"); else if ((maj < 1 || (maj == 1 && min < 5)) && !util_hasGLExt(client_exts, "EGL_KHR_image_base")) DEBUG_INFO("Need EGL 1.5+ or EGL_KHR_image_base for eglCreateImage(KHR)"); else this->dmaSupport = true; if (!this->dmaSupport) { useDMA = false; if (!util_hasGLExt(gl_exts, "GL_EXT_buffer_storage")) { DEBUG_ERROR("GL_EXT_buffer_storage is needed to use EGL backend"); return false; } } if (debug) { if ((esMaj > 3 || (esMaj == 3 && esMin >= 2)) && g_egl_dynProcs.glDebugMessageCallback) { g_egl_dynProcs.glDebugMessageCallback(debugCallback, NULL); DEBUG_INFO("Using debug message callback from OpenGL ES 3.2+"); } else if (util_hasGLExt(gl_exts, "GL_KHR_debug") && g_egl_dynProcs.glDebugMessageCallbackKHR) { g_egl_dynProcs.glDebugMessageCallbackKHR(debugCallback, NULL); DEBUG_INFO("Using debug message callback from GL_KHR_debug"); } else DEBUG_INFO("Debug message callback not supported"); } else DEBUG_INFO("Debug messages disabled, enable with egl:debug=true"); eglSwapInterval(this->display, this->opt.vsync ? 1 : 0); if (!egl_desktopInit(this, &this->desktop, this->display, useDMA, MAX_ACCUMULATED_DAMAGE)) { DEBUG_ERROR("Failed to initialize the desktop"); return false; } if (!egl_cursorInit(&this->cursor)) { DEBUG_ERROR("Failed to initialize the cursor"); return false; } if (!egl_damageInit(&this->damage)) { DEBUG_ERROR("Failed to initialize the damage display"); return false; } if (!ImGui_ImplOpenGL3_Init("#version 300 es")) { DEBUG_ERROR("Failed to initialize ImGui"); return false; } app_overlayConfigRegister("EGL", egl_configUI, this); this->imgui = true; return true; } inline static EGLint egl_bufferAge(struct Inst * this) { if (!this->hasBufferAge) return 0; EGLint result; if (eglQuerySurface(this->display, this->surface, EGL_BUFFER_AGE_EXT, &result) == EGL_FALSE) { DEBUG_ERROR("eglQuerySurface(EGL_BUFFER_AGE_EXT) failed"); return 0; } return result; } inline static void renderLetterBox(struct Inst * this) { bool hLB = this->destRect.x > 0; bool vLB = this->destRect.y > 0; if (hLB || vLB) { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glEnable(GL_SCISSOR_TEST); if (hLB) { // left glScissor(0, 0, this->destRect.x, this->height); glClear(GL_COLOR_BUFFER_BIT); // right float x2 = this->destRect.x + this->destRect.w; glScissor(x2, 0, this->width - x2, this->height); glClear(GL_COLOR_BUFFER_BIT); } if (vLB) { // top glScissor(0, this->height - this->destRect.y, this->width, this->destRect.y); glClear(GL_COLOR_BUFFER_BIT); // bottom int y2 = this->destRect.y + this->destRect.h; glScissor(0, 0, this->width, this->height - y2); glClear(GL_COLOR_BUFFER_BIT); } glDisable(GL_SCISSOR_TEST); } } static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate, const bool newFrame, const bool invalidateWindow, void (*preSwap)(void * udata), void * udata) { struct Inst * this = UPCAST(struct Inst, renderer); EGLint bufferAge = egl_bufferAge(this); bool renderAll = invalidateWindow || this->hadOverlay || bufferAge <= 0 || bufferAge > MAX_BUFFER_AGE || this->showSpice; bool hasOverlay = false; struct CursorState cursorState = { .visible = false }; struct DesktopDamage * desktopDamage; struct DamageRects * accumulated = (struct DamageRects *)alloca( sizeof(struct DamageRects) + MAX_ACCUMULATED_DAMAGE * sizeof(struct FrameDamageRect) ); accumulated->count = 0; INTERLOCKED_SECTION(this->desktopDamageLock, { if (!renderAll) { for (int i = 0; i < bufferAge; ++i) { struct DesktopDamage * damage = this->desktopDamage + IDX_AGO(this->desktopDamageIdx, i, DESKTOP_DAMAGE_COUNT); if (damage->count < 0) { renderAll = true; break; } for (int j = 0; j < damage->count; ++j) { struct FrameDamageRect * rect = damage->rects + j; int x = rect->x > 0 ? rect->x - 1 : 0; int y = rect->y > 0 ? rect->y - 1 : 0; accumulated->rects[accumulated->count++] = (struct FrameDamageRect) { .x = x, .y = y, .width = min(this->format.frameWidth - x, rect->width + 2), .height = min(this->format.frameHeight - y, rect->height + 2), }; } } } desktopDamage = this->desktopDamage + this->desktopDamageIdx; this->desktopDamageIdx = (this->desktopDamageIdx + 1) % DESKTOP_DAMAGE_COUNT; this->desktopDamage[this->desktopDamageIdx].count = 0; }); if (!renderAll) { double matrix[6]; egl_screenToDesktopMatrix(matrix, this->format.frameWidth, this->format.frameHeight, this->translateX, this->translateY, this->scaleX, this->scaleY, rotate, this->width, this->height); for (int i = 0; i < bufferAge; ++i) { int idx = IDX_AGO(this->overlayHistoryIdx, i, DESKTOP_DAMAGE_COUNT); int count = this->overlayHistoryCount[idx]; struct Rect * damage = this->overlayHistory[idx]; if (count < 0) { renderAll = true; break; } for (int j = 0; j < count; ++j) accumulated->count += egl_screenToDesktop( accumulated->rects + accumulated->count, matrix, damage + j, this->format.frameWidth, this->format.frameHeight ); } accumulated->count = rectsMergeOverlapping(accumulated->rects, accumulated->count); } ++this->overlayHistoryIdx; if (this->destRect.w > 0 && this->destRect.h > 0) { if (egl_desktopRender(this->desktop, this->destRect.w, this->destRect.h, this->translateX, this->translateY, this->scaleX , this->scaleY , this->scaleType , rotate, renderAll ? NULL : accumulated)) { cursorState = egl_cursorRender(this->cursor, (this->format.rotate + rotate) % LG_ROTATE_MAX, this->width, this->height); } else hasOverlay = true; } renderLetterBox(this); hasOverlay |= egl_damageRender(this->damage, rotate, newFrame ? desktopDamage : NULL); hasOverlay |= invalidateWindow; struct Rect damage[KVMFR_MAX_DAMAGE_RECTS + MAX_OVERLAY_RECTS + 2]; int damageIdx = app_renderOverlay(damage, MAX_OVERLAY_RECTS); switch (damageIdx) { case 0: // no overlay break; case -1: // full damage hasOverlay = true; // fallthrough default: ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplOpenGL3_RenderDrawData(igGetDrawData()); for (int i = 0; i < damageIdx; ++i) damage[i].y = this->height - damage[i].y - damage[i].h; } if (damageIdx >= 0 && cursorState.visible) damage[damageIdx++] = cursorState.rect; int overlayHistoryIdx = this->overlayHistoryIdx % DESKTOP_DAMAGE_COUNT; if (hasOverlay) this->overlayHistoryCount[overlayHistoryIdx] = -1; else { if (damageIdx > 0) memcpy(this->overlayHistory[overlayHistoryIdx], damage, damageIdx * sizeof(struct Rect)); this->overlayHistoryCount[overlayHistoryIdx] = damageIdx; } if (!hasOverlay && !this->hadOverlay) { if (this->cursorLast.visible) damage[damageIdx++] = this->cursorLast.rect; if (desktopDamage->count == -1) // -1 damage count means invalidating entire window. damageIdx = 0; else { double matrix[6]; egl_desktopToScreenMatrix(matrix, this->format.frameWidth, this->format.frameHeight, this->translateX, this->translateY, this->scaleX, this->scaleY, rotate, this->width, this->height); for (int i = 0; i < desktopDamage->count; ++i) damage[damageIdx++] = egl_desktopToScreen(matrix, desktopDamage->rects + i); } } else damageIdx = 0; this->hadOverlay = hasOverlay; this->cursorLast = cursorState; preSwap(udata); app_eglSwapBuffers(this->display, this->surface, damage, this->noSwapDamage ? 0 : damageIdx); return true; } static void * egl_createTexture(LG_Renderer * renderer, int width, int height, uint8_t * data) { GLuint tex; glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glPixelStorei(GL_UNPACK_ROW_LENGTH, width); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); glBindTexture(GL_TEXTURE_2D, 0); return (void*)(intptr_t)tex; } static void egl_freeTexture(LG_Renderer * renderer, void * texture) { GLuint tex = (GLuint)(intptr_t)texture; glDeleteTextures(1, &tex); } static void egl_spiceConfigure(LG_Renderer * renderer, int width, int height) { struct Inst * this = UPCAST(struct Inst, renderer); this->spiceWidth = width; this->spiceHeight = height; egl_desktopSpiceConfigure(this->desktop, width, height); } static void egl_spiceDrawFill(LG_Renderer * renderer, int x, int y, int width, int height, uint32_t color) { struct Inst * this = UPCAST(struct Inst, renderer); egl_desktopSpiceDrawFill(this->desktop, x, y, width, height, color); } static void egl_spiceDrawBitmap(LG_Renderer * renderer, int x, int y, int width, int height, int stride, uint8_t * data, bool topDown) { struct Inst * this = UPCAST(struct Inst, renderer); egl_desktopSpiceDrawBitmap(this->desktop, x, y, width, height, stride, data, topDown); } static void egl_spiceShow(LG_Renderer * renderer, bool show) { struct Inst * this = UPCAST(struct Inst, renderer); this->showSpice = show; egl_calc_mouse_size(this); egl_desktopSpiceShow(this->desktop, show); } struct LG_RendererOps LGR_EGL = { .getName = egl_getName, .setup = egl_setup, .create = egl_create, .initialize = egl_initialize, .deinitialize = egl_deinitialize, .supports = egl_supports, .onRestart = egl_onRestart, .onResize = egl_onResize, .onMouseShape = egl_onMouseShape, .onMouseEvent = egl_onMouseEvent, .onFrameFormat = egl_onFrameFormat, .onFrame = egl_onFrame, .renderStartup = egl_renderStartup, .render = egl_render, .createTexture = egl_createTexture, .freeTexture = egl_freeTexture, .spiceConfigure = egl_spiceConfigure, .spiceDrawFill = egl_spiceDrawFill, .spiceDrawBitmap = egl_spiceDrawBitmap, .spiceShow = egl_spiceShow }; looking-glass-B6/client/renderers/EGL/egl.h000066400000000000000000000015751434445012300207020ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once typedef struct Inst EGL; void egl_resetViewport(EGL * egl); looking-glass-B6/client/renderers/EGL/egldebug.c000066400000000000000000000046771434445012300217120ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "egldebug.h" #include #include const char * egl_getErrorStr(void) { switch (eglGetError()) { case EGL_SUCCESS : return "EGL_SUCCESS"; case EGL_NOT_INITIALIZED : return "EGL_NOT_INITIALIZED"; case EGL_BAD_ACCESS : return "EGL_BAD_ACCESS"; case EGL_BAD_ALLOC : return "EGL_BAD_ALLOC"; case EGL_BAD_ATTRIBUTE : return "EGL_BAD_ATTRIBUTE"; case EGL_BAD_CONTEXT : return "EGL_BAD_CONTEXT"; case EGL_BAD_CONFIG : return "EGL_BAD_CONFIG"; case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE"; case EGL_BAD_DISPLAY : return "EGL_BAD_DISPLAY"; case EGL_BAD_SURFACE : return "EGL_BAD_SURFACE"; case EGL_BAD_MATCH : return "EGL_BAD_MATCH"; case EGL_BAD_PARAMETER : return "EGL_BAD_PARAMETER"; case EGL_BAD_NATIVE_PIXMAP : return "EGL_BAD_NATIVE_PIXMAP"; case EGL_BAD_NATIVE_WINDOW : return "EGL_BAD_NATIVE_WINDOW"; case EGL_CONTEXT_LOST : return "EGL_CONTEXT_LOST"; default : return "UNKNOWN"; } } const char * gl_getErrorStr(void) { switch (glGetError()) { case GL_NO_ERROR : return "GL_NO_ERROR"; case GL_INVALID_ENUM : return "GL_INVALID_ENUM"; case GL_INVALID_VALUE : return "GL_INVALID_VALUE"; case GL_INVALID_OPERATION : return "GL_INVALID_OPERATION"; case GL_INVALID_FRAMEBUFFER_OPERATION: return "GL_INVALID_FRAMEBUFFER_OPERATION"; case GL_OUT_OF_MEMORY : return "GL_OUT_OF_MEMORY"; default : return "UNKNOWN"; } } looking-glass-B6/client/renderers/EGL/egldebug.h000066400000000000000000000024251434445012300217040ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/debug.h" const char * egl_getErrorStr(void); const char * gl_getErrorStr(void); #define DEBUG_EGL_WARN(fmt, ...) \ DEBUG_WARN(fmt " (%s)", ##__VA_ARGS__, egl_getErrorStr()) #define DEBUG_EGL_ERROR(fmt, ...) \ DEBUG_ERROR(fmt " (%s)", ##__VA_ARGS__, egl_getErrorStr()) #define DEBUG_GL_WARN(fmt, ...) \ DEBUG_WARN(fmt " (%s)", ##__VA_ARGS__, gl_getErrorStr()) #define DEBUG_GL_ERROR(fmt, ...) \ DEBUG_ERROR(fmt " (%s)", ##__VA_ARGS__, gl_getErrorStr()) looking-glass-B6/client/renderers/EGL/egltypes.h000066400000000000000000000032171434445012300217620ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include typedef enum EGL_TexType { EGL_TEXTYPE_BUFFER, EGL_TEXTYPE_BUFFER_MAP, EGL_TEXTYPE_BUFFER_STREAM, EGL_TEXTYPE_FRAMEBUFFER, EGL_TEXTYPE_DMABUF } EGL_TexType; typedef enum EGL_PixelFormat { EGL_PF_RGBA, EGL_PF_BGRA, EGL_PF_RGBA10, EGL_PF_RGBA16F } EGL_PixelFormat; typedef enum EGL_TexStatus { EGL_TEX_STATUS_NOTREADY, EGL_TEX_STATUS_OK, EGL_TEX_STATUS_ERROR } EGL_TexStatus; typedef struct EGL_TexSetup { /* the pixel format of the texture */ EGL_PixelFormat pixFmt; /* the width of the texture in pixels */ size_t width; /* the height of the texture in pixels */ size_t height; /* the stide of the texture in bytes */ size_t stride; } EGL_TexSetup; typedef enum EGL_FilterType { EGL_FILTER_TYPE_EFFECT, EGL_FILTER_TYPE_UPSCALE, EGL_FILTER_TYPE_DOWNSCALE } EGL_FilterType; looking-glass-B6/client/renderers/EGL/ffx.c000066400000000000000000000030631434445012300207030ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "ffx.h" #include #include #define A_CPU #define A_RESTRICT #define A_STATIC inline static #include "shader/ffx_a.h" #include "shader/ffx_cas.h" #include "shader/ffx_fsr1.h" void ffxCasConst(uint32_t consts[8], float sharpness, float inputX, float inputY, float outputX, float outputY) { CasSetup(consts + 0, consts + 4, sharpness, inputX, inputY, outputX, outputY); } void ffxFsrEasuConst(uint32_t consts[16], float viewportX, float viewportY, float inputX, float inputY, float outputX, float outputY) { FsrEasuCon(consts + 0, consts + 4, consts + 8, consts + 12, viewportX, viewportY, inputX, inputY, outputX, outputY); } void ffxFsrRcasConst(uint32_t consts[4], float sharpness) { FsrRcasCon(consts, sharpness); } looking-glass-B6/client/renderers/EGL/ffx.h000066400000000000000000000022201434445012300207020ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include void ffxCasConst(uint32_t consts[8], float sharpness, float inputX, float inputY, float outputX, float outputY); void ffxFsrEasuConst(uint32_t consts[16], float viewportX, float viewportY, float inputX, float inputY, float outputX, float outputY); void ffxFsrRcasConst(uint32_t consts[4], float sharpness); looking-glass-B6/client/renderers/EGL/filter.c000066400000000000000000000022011434445012300213760ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "filter.h" void egl_filterRectsRender(EGL_Shader * shader, EGL_FilterRects * rects) { glUniformMatrix3x2fv(egl_shaderGetUniform(shader, "transform"), 1, GL_FALSE, rects->matrix); glUniform2f(egl_shaderGetUniform(shader, "desktopSize"), rects->width, rects->height); egl_desktopRectsRender(rects->rects); } looking-glass-B6/client/renderers/EGL/filter.h000066400000000000000000000105301434445012300214070ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include "util.h" #include "shader.h" #include "egltypes.h" #include "desktop_rects.h" #include "model.h" #include typedef struct EGL_FilterRects { EGL_DesktopRects * rects; GLfloat * matrix; int width, height; } EGL_FilterRects; typedef struct EGL_Filter EGL_Filter; typedef struct EGL_FilterOps { /* the identifier of this filter */ const char * id; /* the friendly name of this filter */ const char * name; /* the type of this filter */ EGL_FilterType type; /* early initialization for registration of options */ void (*earlyInit)(void); /* initialize the filter */ bool (*init)(EGL_Filter ** filter); /* free the filter */ void (*free)(EGL_Filter * filter); /* render any imgui config * Returns true if a redraw is required */ bool (*imguiConfig)(EGL_Filter * filter); /* writes filter state to options */ void (*saveState)(EGL_Filter * filter); /* reads filter state from options */ void (*loadState)(EGL_Filter * filter); /* set the input format of the filter */ bool (*setup)(EGL_Filter * filter, enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height); /* set the output resolution hint for the filter * this is optional and only a hint */ void (*setOutputResHint)(EGL_Filter * filter, unsigned int x, unsigned int y); /* returns the output resolution of the filter */ void (*getOutputRes)(EGL_Filter * filter, unsigned int *x, unsigned int *y); /* prepare the shader for use * A filter can return false to bypass it */ bool (*prepare)(EGL_Filter * filter); /* runs the filter on the provided texture * returns the processed texture as the output */ GLuint (*run)(EGL_Filter * filter, EGL_FilterRects * rects, GLuint texture); /* called when the filter output is no loger needed so it can release memory * this is optional */ void (*release)(EGL_Filter * filter); } EGL_FilterOps; typedef struct EGL_Filter { EGL_FilterOps ops; } EGL_Filter; static inline bool egl_filterInit(const EGL_FilterOps * ops, EGL_Filter ** filter) { if (!ops->init(filter)) return false; memcpy(&(*filter)->ops, ops, sizeof(*ops)); return true; } static inline void egl_filterFree(EGL_Filter ** filter) { (*filter)->ops.free(*filter); *filter = NULL; } static inline bool egl_filterImguiConfig(EGL_Filter * filter) { return filter->ops.imguiConfig(filter); } static inline void egl_filterSaveState(EGL_Filter * filter) { filter->ops.saveState(filter); } static inline void egl_filterLoadState(EGL_Filter * filter) { filter->ops.loadState(filter); } static inline bool egl_filterSetup(EGL_Filter * filter, enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height) { return filter->ops.setup(filter, pixFmt, width, height); } static inline void egl_filterSetOutputResHint(EGL_Filter * filter, unsigned int x, unsigned int y) { if (filter->ops.setOutputResHint) filter->ops.setOutputResHint(filter, x, y); } static inline void egl_filterGetOutputRes(EGL_Filter * filter, unsigned int *x, unsigned int *y) { return filter->ops.getOutputRes(filter, x, y); } static inline bool egl_filterPrepare(EGL_Filter * filter) { return filter->ops.prepare(filter); } static inline GLuint egl_filterRun(EGL_Filter * filter, EGL_FilterRects * rects, GLuint texture) { return filter->ops.run(filter, rects, texture); } static inline void egl_filterRelease(EGL_Filter * filter) { if (filter->ops.release) filter->ops.release(filter); } void egl_filterRectsRender(EGL_Shader * shader, EGL_FilterRects * rects); looking-glass-B6/client/renderers/EGL/filter_downscale.c000066400000000000000000000267401434445012300234530ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "filter.h" #include "framebuffer.h" #include #include "common/array.h" #include "common/debug.h" #include "common/option.h" #include "cimgui.h" #include "basic.vert.h" #include "downscale.frag.h" #include "downscale_lanczos2.frag.h" #include "downscale_linear.frag.h" typedef enum { DOWNSCALE_NEAREST = 0, DOWNSCALE_LINEAR, DOWNSCALE_LANCZOS2, } DownscaleFilter; #define DOWNSCALE_COUNT (DOWNSCALE_LANCZOS2 + 1) const char *filterNames[DOWNSCALE_COUNT] = { "Nearest pixel", "Linear", "Lanczos", }; typedef struct EGL_FilterDownscale { EGL_Filter base; bool enable; EGL_Shader * nearest; EGL_Uniform uNearest; EGL_Shader * linear; EGL_Shader * lanczos2; DownscaleFilter filter; enum EGL_PixelFormat pixFmt; unsigned int width, height; float pixelSize; float vOffset, hOffset; bool prepared; EGL_Framebuffer * fb; GLuint sampler[2]; } EGL_FilterDownscale; static void egl_filterDownscaleEarlyInit(void) { static struct Option options[] = { { .module = "eglFilter", .name = "downscale", .description = "Enable downscaling", .preset = true, .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "eglFilter", .name = "downscalePixelSize", .description = "Downscale filter pixel size", .preset = true, .type = OPTION_TYPE_FLOAT, .value.x_float = 2.0f }, { .module = "eglFilter", .name = "downscaleHOffset", .description = "Downscale filter horizontal offset", .preset = true, .type = OPTION_TYPE_FLOAT, .value.x_float = 0.0f }, { .module = "eglFilter", .name = "downscaleVOffset", .description = "Downscale filter vertical offset", .preset = true, .type = OPTION_TYPE_FLOAT, .value.x_float = 0.0f }, { .module = "eglFilter", .name = "downscaleFilter", .description = "Downscale filter type", .preset = true, .type = OPTION_TYPE_INT, .value.x_int = 0 }, { 0 } }; option_register(options); } static void egl_filterDownscaleSaveState(EGL_Filter * filter) { EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); option_set_bool ("eglFilter", "downscale", this->enable); option_set_float("eglFilter", "downscalePixelSize", this->pixelSize); option_set_float("eglFilter", "downscaleHOffset", this->vOffset); option_set_float("eglFilter", "downscaleVOffset", this->hOffset); option_set_int ("eglFilter", "downscaleFilter", this->filter); } static void egl_filterDownscaleLoadState(EGL_Filter * filter) { EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); this->enable = option_get_bool ("eglFilter", "downscale"); this->pixelSize = option_get_float("eglFilter", "downscalePixelSize"); this->vOffset = option_get_float("eglFilter", "downscaleHOffset"); this->hOffset = option_get_float("eglFilter", "downscaleVOffset"); this->filter = option_get_int ("eglFilter", "downscaleFilter"); if (this->filter < 0 || this->filter >= DOWNSCALE_COUNT) this->filter = 0; this->prepared = false; } static bool egl_filterDownscaleInit(EGL_Filter ** filter) { EGL_FilterDownscale * this = calloc(1, sizeof(*this)); if (!this) { DEBUG_ERROR("Failed to allocate ram"); return false; } if (!egl_shaderInit(&this->nearest)) { DEBUG_ERROR("Failed to initialize the shader"); goto error_this; } if (!egl_shaderCompile(this->nearest, b_shader_basic_vert , b_shader_basic_vert_size, b_shader_downscale_frag, b_shader_downscale_frag_size) ) { DEBUG_ERROR("Failed to compile the shader"); goto error_shader; } if (!egl_shaderInit(&this->linear)) { DEBUG_ERROR("Failed to initialize the shader"); goto error_this; } if (!egl_shaderCompile(this->linear, b_shader_basic_vert, b_shader_basic_vert_size, b_shader_downscale_linear_frag, b_shader_downscale_linear_frag_size) ) { DEBUG_ERROR("Failed to compile the shader"); goto error_shader; } if (!egl_shaderInit(&this->lanczos2)) { DEBUG_ERROR("Failed to initialize the shader"); goto error_this; } if (!egl_shaderCompile(this->lanczos2, b_shader_basic_vert, b_shader_basic_vert_size, b_shader_downscale_lanczos2_frag, b_shader_downscale_lanczos2_frag_size) ) { DEBUG_ERROR("Failed to compile the shader"); goto error_shader; } this->uNearest.type = EGL_UNIFORM_TYPE_3F; this->uNearest.location = egl_shaderGetUniform(this->nearest, "uConfig"); if (!egl_framebufferInit(&this->fb)) { DEBUG_ERROR("Failed to initialize the framebuffer"); goto error_shader; } glGenSamplers(ARRAY_LENGTH(this->sampler), this->sampler); glSamplerParameteri(this->sampler[0], GL_TEXTURE_MIN_FILTER, GL_NEAREST); glSamplerParameteri(this->sampler[0], GL_TEXTURE_MAG_FILTER, GL_NEAREST); glSamplerParameteri(this->sampler[0], GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE); glSamplerParameteri(this->sampler[0], GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE); glSamplerParameteri(this->sampler[1], GL_TEXTURE_MIN_FILTER, GL_LINEAR); glSamplerParameteri(this->sampler[1], GL_TEXTURE_MAG_FILTER, GL_LINEAR); glSamplerParameteri(this->sampler[1], GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE); glSamplerParameteri(this->sampler[1], GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE); egl_filterDownscaleLoadState(&this->base); *filter = &this->base; return true; error_shader: egl_shaderFree(&this->nearest); egl_shaderFree(&this->linear); egl_shaderFree(&this->lanczos2); error_this: free(this); return false; } static void egl_filterDownscaleFree(EGL_Filter * filter) { EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); egl_shaderFree(&this->nearest); egl_shaderFree(&this->linear); egl_shaderFree(&this->lanczos2); egl_framebufferFree(&this->fb); glDeleteSamplers(ARRAY_LENGTH(this->sampler), this->sampler); free(this); } static bool egl_filterDownscaleImguiConfig(EGL_Filter * filter) { EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); bool redraw = false; bool enable = this->enable; igCheckbox("Enable", &enable); if (enable != this->enable) { this->enable = enable; redraw = true; } if (igBeginCombo("Filter", filterNames[this->filter], 0)) { for (int i = 0; i < DOWNSCALE_COUNT; ++i) { bool selected = i == this->filter; if (igSelectable_Bool(filterNames[i], selected, 0, (ImVec2) { 0.0f, 0.0f })) { redraw = true; this->filter = i; } if (selected) igSetItemDefaultFocus(); } igEndCombo(); } float pixelSize = this->pixelSize; igInputFloat("Pixel size", &pixelSize, 0.1f, 1.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); pixelSize = util_clamp(pixelSize, 1.0f, 10.0f); igSliderFloat("##pixelsize", &pixelSize, 1.0f, 10.0f, "%.2f", ImGuiSliderFlags_Logarithmic | ImGuiSliderFlags_NoInput); igText("Resolution: %dx%d", this->width, this->height); if (pixelSize != this->pixelSize) { this->pixelSize = pixelSize; redraw = true; } switch (this->filter) { case DOWNSCALE_NEAREST: { float vOffset = this->vOffset; igSliderFloat("V-Offset", &vOffset, -2, 2, NULL, 0); if (vOffset != this->vOffset) { this->vOffset = vOffset; redraw = true; } float hOffset = this->hOffset; igSliderFloat("H-Offset", &hOffset, -2, 2, NULL, 0); if (hOffset != this->hOffset) { this->hOffset = hOffset; redraw = true; } break; } default: break; } if (redraw) this->prepared = false; return redraw; } static bool egl_filterDownscaleSetup(EGL_Filter * filter, enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height) { EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); width = (float)width / this->pixelSize; height = (float)height / this->pixelSize; if (!this->enable) return false; if (this->prepared && pixFmt == this->pixFmt && this->width == width && this->height == height) return this->pixelSize > 1.0f; if (!egl_framebufferSetup(this->fb, pixFmt, width, height)) return false; this->pixFmt = pixFmt; this->width = width; this->height = height; this->prepared = false; return this->pixelSize > 1.0f; } static void egl_filterDownscaleGetOutputRes(EGL_Filter * filter, unsigned int *width, unsigned int *height) { EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); *width = this->width; *height = this->height; } static bool egl_filterDownscalePrepare(EGL_Filter * filter) { EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); if (this->prepared) return true; switch (this->filter) { case DOWNSCALE_NEAREST: this->uNearest.f[0] = this->pixelSize; this->uNearest.f[1] = this->vOffset; this->uNearest.f[2] = this->hOffset; egl_shaderSetUniforms(this->nearest, &this->uNearest, 1); break; default: break; } this->prepared = true; return true; } static GLuint egl_filterDownscaleRun(EGL_Filter * filter, EGL_FilterRects * rects, GLuint texture) { EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); egl_framebufferBind(this->fb); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); EGL_Shader * shader; switch (this->filter) { case DOWNSCALE_NEAREST: glBindSampler(0, this->sampler[0]); shader = this->nearest; break; case DOWNSCALE_LINEAR: glBindSampler(0, this->sampler[1]); shader = this->linear; break; case DOWNSCALE_LANCZOS2: glBindSampler(0, this->sampler[0]); shader = this->lanczos2; break; default: DEBUG_UNREACHABLE(); } egl_shaderUse(shader); egl_filterRectsRender(shader, rects); return egl_framebufferGetTexture(this->fb); } EGL_FilterOps egl_filterDownscaleOps = { .id = "downscale", .name = "Downscaler", .type = EGL_FILTER_TYPE_DOWNSCALE, .earlyInit = egl_filterDownscaleEarlyInit, .init = egl_filterDownscaleInit, .free = egl_filterDownscaleFree, .imguiConfig = egl_filterDownscaleImguiConfig, .saveState = egl_filterDownscaleSaveState, .loadState = egl_filterDownscaleLoadState, .setup = egl_filterDownscaleSetup, .getOutputRes = egl_filterDownscaleGetOutputRes, .prepare = egl_filterDownscalePrepare, .run = egl_filterDownscaleRun }; looking-glass-B6/client/renderers/EGL/filter_ffx_cas.c000066400000000000000000000166601434445012300231050ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "filter.h" #include "framebuffer.h" #include "common/countedbuffer.h" #include "common/debug.h" #include "common/option.h" #include "cimgui.h" #include "ffx.h" #include "basic.vert.h" #include "ffx_cas.frag.h" typedef struct EGL_FilterFFXCAS { EGL_Filter base; EGL_Shader * shader; bool enable; enum EGL_PixelFormat pixFmt; unsigned int width, height; float sharpness; CountedBuffer * consts; bool prepared; EGL_Framebuffer * fb; GLuint sampler; } EGL_FilterFFXCAS; static void egl_filterFFXCASEarlyInit(void) { static struct Option options[] = { { .module = "eglFilter", .name = "ffxCAS", .description = "AMD FidelityFX CAS", .preset = true, .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "eglFilter", .name = "ffxCASSharpness", .description = "AMD FidelityFX CAS Sharpness", .preset = true, .type = OPTION_TYPE_FLOAT, .value.x_float = 0.0f }, { 0 } }; option_register(options); } static void casUpdateConsts(EGL_FilterFFXCAS * this) { ffxCasConst((uint32_t *) this->consts->data, this->sharpness, this->width, this->height, this->width, this->height); } static void egl_filterFFXCASSaveState(EGL_Filter * filter) { EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter); option_set_bool ("eglFilter", "ffxCAS", this->enable); option_set_float("eglFilter", "ffxCASSharpness", this->sharpness); } static void egl_filterFFXCASLoadState(EGL_Filter * filter) { EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter); this->enable = option_get_bool ("eglFilter", "ffxCAS"); this->sharpness = option_get_float("eglFilter", "ffxCASSharpness"); } static bool egl_filterFFXCASInit(EGL_Filter ** filter) { EGL_FilterFFXCAS * this = calloc(1, sizeof(*this)); if (!this) { DEBUG_ERROR("Failed to allocate ram"); return false; } if (!egl_shaderInit(&this->shader)) { DEBUG_ERROR("Failed to initialize the shader"); goto error_this; } if (!egl_shaderCompile(this->shader, b_shader_basic_vert , b_shader_basic_vert_size, b_shader_ffx_cas_frag, b_shader_ffx_cas_frag_size) ) { DEBUG_ERROR("Failed to compile the shader"); goto error_shader; } this->consts = countedBufferNew(8 * sizeof(GLuint)); if (!this->consts) { DEBUG_ERROR("Failed to allocate consts buffer"); goto error_shader; } egl_shaderSetUniforms(this->shader, &(EGL_Uniform) { .type = EGL_UNIFORM_TYPE_4UIV, .location = egl_shaderGetUniform(this->shader, "uConsts"), .v = this->consts, }, 1); egl_filterFFXCASLoadState(&this->base); if (!egl_framebufferInit(&this->fb)) { DEBUG_ERROR("Failed to initialize the framebuffer"); goto error_consts; } glGenSamplers(1, &this->sampler); glSamplerParameteri(this->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glSamplerParameteri(this->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glSamplerParameteri(this->sampler, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE); glSamplerParameteri(this->sampler, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE); *filter = &this->base; return true; error_consts: countedBufferRelease(&this->consts); error_shader: egl_shaderFree(&this->shader); error_this: free(this); return false; } static void egl_filterFFXCASFree(EGL_Filter * filter) { EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter); egl_shaderFree(&this->shader); countedBufferRelease(&this->consts); egl_framebufferFree(&this->fb); glDeleteSamplers(1, &this->sampler); free(this); } static bool egl_filterFFXCASImguiConfig(EGL_Filter * filter) { EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter); bool redraw = false; bool cas = this->enable; float casSharpness = this->sharpness; igCheckbox("Enabled", &cas); if (cas != this->enable) { this->enable = cas; redraw = true; } igText("Sharpness:"); igSameLine(0.0f, -1.0f); igPushItemWidth(igGetWindowWidth() - igGetCursorPosX() - igGetStyle()->WindowPadding.x); igSliderFloat("##casSharpness", &casSharpness, 0.0f, 1.0f, NULL, 0); casSharpness = util_clamp(casSharpness, 0.0f, 1.0f); if (igIsItemHovered(ImGuiHoveredFlags_None)) igSetTooltip("Ctrl+Click to enter a value"); igPopItemWidth(); if (casSharpness != this->sharpness) { // enable CAS if the sharpness was changed if (!cas) { cas = true; this->enable = true; } this->sharpness = casSharpness; casUpdateConsts(this); redraw = true; } if (redraw) this->prepared = false; return redraw; } static bool egl_filterFFXCASSetup(EGL_Filter * filter, enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height) { EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter); if (!this->enable) return false; if (pixFmt == this->pixFmt && this->width == width && this->height == height) return true; if (!egl_framebufferSetup(this->fb, pixFmt, width, height)) return false; this->pixFmt = pixFmt; this->width = width; this->height = height; this->prepared = false; casUpdateConsts(this); return true; } static void egl_filterFFXCASGetOutputRes(EGL_Filter * filter, unsigned int *width, unsigned int *height) { EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter); *width = this->width; *height = this->height; } static bool egl_filterFFXCASPrepare(EGL_Filter * filter) { EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter); if (this->prepared) return true; this->prepared = true; return true; } static GLuint egl_filterFFXCASRun(EGL_Filter * filter, EGL_FilterRects * rects, GLuint texture) { EGL_FilterFFXCAS * this = UPCAST(EGL_FilterFFXCAS, filter); egl_framebufferBind(this->fb); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); glBindSampler(0, this->sampler); egl_shaderUse(this->shader); egl_filterRectsRender(this->shader, rects); return egl_framebufferGetTexture(this->fb); } EGL_FilterOps egl_filterFFXCASOps = { .id = "ffxCAS", .name = "AMD FidelityFX CAS", .type = EGL_FILTER_TYPE_EFFECT, .earlyInit = egl_filterFFXCASEarlyInit, .init = egl_filterFFXCASInit, .free = egl_filterFFXCASFree, .imguiConfig = egl_filterFFXCASImguiConfig, .saveState = egl_filterFFXCASSaveState, .loadState = egl_filterFFXCASLoadState, .setup = egl_filterFFXCASSetup, .getOutputRes = egl_filterFFXCASGetOutputRes, .prepare = egl_filterFFXCASPrepare, .run = egl_filterFFXCASRun }; looking-glass-B6/client/renderers/EGL/filter_ffx_fsr1.c000066400000000000000000000301501434445012300232000ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "filter.h" #include "framebuffer.h" #include "common/array.h" #include "common/countedbuffer.h" #include "common/debug.h" #include "common/option.h" #include "cimgui.h" #include "ffx.h" #include "basic.vert.h" #include "ffx_fsr1_easu.frag.h" #include "ffx_fsr1_rcas.frag.h" typedef struct EGL_FilterFFXFSR1 { EGL_Filter base; EGL_Shader * easu, * rcas; bool enable, active; float sharpness; CountedBuffer * consts; EGL_Uniform easuUniform[2], rcasUniform; enum EGL_PixelFormat pixFmt; unsigned int width, height; unsigned int inWidth, inHeight; bool sizeChanged; bool prepared; EGL_Framebuffer * easuFb, * rcasFb; GLuint sampler; } EGL_FilterFFXFSR1; static void egl_filterFFXFSR1EarlyInit(void) { static struct Option options[] = { { .module = "eglFilter", .name = "ffxFSR", .description = "AMD FidelityFX FSR", .preset = true, .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "eglFilter", .name = "ffxFSRSharpness", .description = "AMD FidelityFX FSR Sharpness", .preset = true, .type = OPTION_TYPE_FLOAT, .value.x_float = 1.0f }, { 0 } }; option_register(options); } static void rcasUpdateUniform(EGL_FilterFFXFSR1 * this) { ffxFsrRcasConst(this->rcasUniform.ui, 2.0f - this->sharpness * 2.0f); } static void egl_filterFFXFSR1SaveState(EGL_Filter * filter) { EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter); option_set_bool ("eglFilter", "ffxFSR", this->enable); option_set_float("eglFilter", "ffxFSRSharpness", this->sharpness); } static void egl_filterFFXFSR1LoadState(EGL_Filter * filter) { EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter); this->enable = option_get_bool ("eglFilter", "ffxFSR"); this->sharpness = option_get_float("eglFilter", "ffxFSRSharpness"); } static bool egl_filterFFXFSR1Init(EGL_Filter ** filter) { EGL_FilterFFXFSR1 * this = calloc(1, sizeof(*this)); if (!this) { DEBUG_ERROR("Failed to allocate ram"); return false; } if (!egl_shaderInit(&this->easu)) { DEBUG_ERROR("Failed to initialize the Easu shader"); goto error_this; } if (!egl_shaderInit(&this->rcas)) { DEBUG_ERROR("Failed to initialize the Rcas shader"); goto error_esau; } if (!egl_shaderCompile(this->easu, b_shader_basic_vert , b_shader_basic_vert_size, b_shader_ffx_fsr1_easu_frag, b_shader_ffx_fsr1_easu_frag_size) ) { DEBUG_ERROR("Failed to compile the Easu shader"); goto error_rcas; } if (!egl_shaderCompile(this->rcas, b_shader_basic_vert , b_shader_basic_vert_size, b_shader_ffx_fsr1_rcas_frag, b_shader_ffx_fsr1_rcas_frag_size) ) { DEBUG_ERROR("Failed to compile the Rcas shader"); goto error_rcas; } this->consts = countedBufferNew(16 * sizeof(GLuint)); if (!this->consts) { DEBUG_ERROR("Failed to allocate consts buffer"); goto error_rcas; } egl_filterFFXFSR1LoadState(&this->base); this->easuUniform[0].type = EGL_UNIFORM_TYPE_4UIV; this->easuUniform[0].location = egl_shaderGetUniform(this->easu, "uConsts"); this->easuUniform[0].v = this->consts; this->easuUniform[1].type = EGL_UNIFORM_TYPE_2F; this->easuUniform[1].location = egl_shaderGetUniform(this->easu, "uOutRes"); this->rcasUniform.type = EGL_UNIFORM_TYPE_4UI; this->rcasUniform.location = egl_shaderGetUniform(this->rcas, "uConsts"); rcasUpdateUniform(this); if (!egl_framebufferInit(&this->easuFb)) { DEBUG_ERROR("Failed to initialize the Easu framebuffer"); goto error_consts; } if (!egl_framebufferInit(&this->rcasFb)) { DEBUG_ERROR("Failed to initialize the Rcas framebuffer"); goto error_easuFb; } glGenSamplers(1, &this->sampler); glSamplerParameteri(this->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glSamplerParameteri(this->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glSamplerParameteri(this->sampler, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE); glSamplerParameteri(this->sampler, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE); *filter = &this->base; return true; error_easuFb: egl_framebufferFree(&this->rcasFb); error_consts: countedBufferRelease(&this->consts); error_rcas: egl_shaderFree(&this->rcas); error_esau: egl_shaderFree(&this->easu); error_this: free(this); return false; } static void egl_filterFFXFSR1Free(EGL_Filter * filter) { EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter); egl_shaderFree(&this->easu); egl_shaderFree(&this->rcas); countedBufferRelease(&this->consts); egl_framebufferFree(&this->easuFb); egl_framebufferFree(&this->rcasFb); glDeleteSamplers(1, &this->sampler); free(this); } static bool egl_filterFFXFSR1ImguiConfig(EGL_Filter * filter) { EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter); bool redraw = false; bool enable = this->enable; float sharpness = this->sharpness; igCheckbox("Enabled", &enable); if (enable != this->enable) { this->enable = enable; redraw = true; } if (this->active) { double dimScale = (double) this->width / this->inWidth; const char * name; if (dimScale < 1.29) name = "better than Ultra Quality"; else if (dimScale < 1.31) name = "Ultra Quality"; else if (dimScale < 1.4) name = "slightly worse than Ultra Quality"; else if (dimScale < 1.49) name = "slightly better than Quality"; else if (dimScale < 1.51) name = "Quality"; else if (dimScale < 1.6) name = "slightly worse than Quality"; else if (dimScale < 1.69) name = "slightly better than Balanced"; else if (dimScale < 1.71) name = "Balanced"; else if (dimScale < 1.85) name = "slightly worse than Balanced"; else if (dimScale < 1.99) name = "slightly better than Performance"; else if (dimScale < 2.01) name = "Performance"; else name = "worse than Performance"; igText("Equivalent quality mode: %s%s", name, this->enable ? "" : ", inactive"); } else igText("Equivalent quality mode: not upscaling, inactive"); if (igIsItemHovered(ImGuiHoveredFlags_None)) { igBeginTooltip(); igText( "Equivalent quality mode is decided by the resolution in the guest VM or the output\n" "of the previous filter in the chain.\n\n" "Here are the input resolutions needed for each quality mode at current window size:\n" ); if (igBeginTable("Resolutions", 2, 0, (ImVec2) { 0.0f, 0.0f }, 0.0f)) { igTableNextColumn(); igText("Ultra Quality"); igTableNextColumn(); igText("%.0fx%.0f", this->width / 1.3, this->height / 1.3); igTableNextColumn(); igText("Quality"); igTableNextColumn(); igText("%.0fx%.0f", this->width / 1.5, this->height / 1.5); igTableNextColumn(); igText("Balanced"); igTableNextColumn(); igText("%.0fx%.0f", this->width / 1.7, this->height / 1.7); igTableNextColumn(); igText("Performance"); igTableNextColumn(); igText("%.0fx%.0f", this->width / 2.0, this->height / 2.0); igEndTable(); } igEndTooltip(); } igText("Sharpness:"); igSameLine(0.0f, -1.0f); igPushItemWidth(igGetWindowWidth() - igGetCursorPosX() - igGetStyle()->WindowPadding.x); igSliderFloat("##fsr1Sharpness", &sharpness, 0.0f, 1.0f, NULL, 0); sharpness = util_clamp(sharpness, 0.0f, 1.0f); if (igIsItemHovered(ImGuiHoveredFlags_None)) igSetTooltip("Ctrl+Click to enter a value"); igPopItemWidth(); if (sharpness != this->sharpness) { // enable FSR1 if the sharpness was changed if (!enable) { enable = true; this->enable = true; } this->sharpness = sharpness; rcasUpdateUniform(this); redraw = true; } if (redraw) this->prepared = false; return redraw; } static void egl_filterFFXFSR1SetOutputResHint(EGL_Filter * filter, unsigned int width, unsigned int height) { EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter); if (this->width == width && this->height == height) return; this->width = width; this->height = height; this->sizeChanged = true; this->prepared = false; } static bool egl_filterFFXFSR1Setup(EGL_Filter * filter, enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height) { EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter); if (!this->enable) return false; this->active = this->width > width && this->height > height; if (!this->active) return false; if (pixFmt == this->pixFmt && !this->sizeChanged && width == this->inWidth && height == this->inHeight) return true; if (!egl_framebufferSetup(this->easuFb, pixFmt, this->width, this->height)) return false; if (!egl_framebufferSetup(this->rcasFb, pixFmt, this->width, this->height)) return false; this->inWidth = width; this->inHeight = height; this->sizeChanged = false; this->pixFmt = pixFmt; this->prepared = false; this->easuUniform[1].f[0] = this->width; this->easuUniform[1].f[1] = this->height; ffxFsrEasuConst((uint32_t *)this->consts->data, this->inWidth, this->inHeight, this->inWidth, this->inHeight, this->width, this->height); return true; } static void egl_filterFFXFSR1GetOutputRes(EGL_Filter * filter, unsigned int *width, unsigned int *height) { EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter); *width = this->width; *height = this->height; } static bool egl_filterFFXFSR1Prepare(EGL_Filter * filter) { EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter); if (!this->active) return false; if (this->prepared) return true; egl_shaderSetUniforms(this->easu, this->easuUniform, ARRAY_LENGTH(this->easuUniform)); egl_shaderSetUniforms(this->rcas, &this->rcasUniform, 1); this->prepared = true; return true; } static GLuint egl_filterFFXFSR1Run(EGL_Filter * filter, EGL_FilterRects * rects, GLuint texture) { EGL_FilterFFXFSR1 * this = UPCAST(EGL_FilterFFXFSR1, filter); // pass 1, Easu egl_framebufferBind(this->easuFb); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); glBindSampler(0, this->sampler); egl_shaderUse(this->easu); egl_filterRectsRender(this->easu, rects); texture = egl_framebufferGetTexture(this->easuFb); // pass 2, Rcas egl_framebufferBind(this->rcasFb); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); glBindSampler(0, this->sampler); egl_shaderUse(this->rcas); egl_filterRectsRender(this->rcas, rects); texture = egl_framebufferGetTexture(this->rcasFb); return texture; } EGL_FilterOps egl_filterFFXFSR1Ops = { .id = "ffxFSR1", .name = "AMD FidelityFX FSR", .type = EGL_FILTER_TYPE_UPSCALE, .earlyInit = egl_filterFFXFSR1EarlyInit, .init = egl_filterFFXFSR1Init, .free = egl_filterFFXFSR1Free, .imguiConfig = egl_filterFFXFSR1ImguiConfig, .saveState = egl_filterFFXFSR1SaveState, .loadState = egl_filterFFXFSR1LoadState, .setup = egl_filterFFXFSR1Setup, .setOutputResHint = egl_filterFFXFSR1SetOutputResHint, .getOutputRes = egl_filterFFXFSR1GetOutputRes, .prepare = egl_filterFFXFSR1Prepare, .run = egl_filterFFXFSR1Run }; looking-glass-B6/client/renderers/EGL/filters.h000066400000000000000000000017031434445012300215740ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once extern EGL_FilterOps egl_filterDownscaleOps; extern EGL_FilterOps egl_filterFFXCASOps; extern EGL_FilterOps egl_filterFFXFSR1Ops; looking-glass-B6/client/renderers/EGL/framebuffer.c000066400000000000000000000054141434445012300224060ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "framebuffer.h" #include "texture.h" #include #include "common/debug.h" struct EGL_Framebuffer { GLuint fbo; EGL_Texture * tex; }; bool egl_framebufferInit(EGL_Framebuffer ** fb) { EGL_Framebuffer * this = calloc(1, sizeof(*this)); if (!this) { DEBUG_ERROR("Failed to allocate ram"); return false; } if (!egl_textureInit(&this->tex, NULL, EGL_TEXTYPE_BUFFER)) { DEBUG_ERROR("Failed to initialize the texture"); return false; } glGenFramebuffers(1, &this->fbo); *fb = this; return true; } void egl_framebufferFree(EGL_Framebuffer ** fb) { EGL_Framebuffer * this = *fb; egl_textureFree(&this->tex); free(this); *fb = NULL; } bool egl_framebufferSetup(EGL_Framebuffer * this, enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height) { if (!egl_textureSetup(this->tex, pixFmt, width, height, 0)) { DEBUG_ERROR("Failed to setup the texture"); return false; } GLuint tex; egl_textureGet(this->tex, &tex, NULL, NULL); glBindTexture(GL_TEXTURE_2D, tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glBindFramebuffer(GL_FRAMEBUFFER, this->fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); glDrawBuffers(1, &(GLenum){GL_COLOR_ATTACHMENT0}); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { glBindFramebuffer(GL_FRAMEBUFFER, 0); DEBUG_ERROR("Failed to setup the framebuffer: 0x%x", status); return false; } glBindFramebuffer(GL_FRAMEBUFFER, 0); return true; } void egl_framebufferBind(EGL_Framebuffer * this) { glBindFramebuffer(GL_FRAMEBUFFER, this->fbo); glViewport(0, 0, this->tex->format.width, this->tex->format.height); } GLuint egl_framebufferGetTexture(EGL_Framebuffer * this) { GLuint output; egl_textureGet(this->tex, &output, NULL, NULL); return output; } looking-glass-B6/client/renderers/EGL/framebuffer.h000066400000000000000000000023261434445012300224120ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include "texture.h" typedef struct EGL_Framebuffer EGL_Framebuffer; bool egl_framebufferInit(EGL_Framebuffer ** fb); void egl_framebufferFree(EGL_Framebuffer ** fb); bool egl_framebufferSetup(EGL_Framebuffer * this, enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height); void egl_framebufferBind(EGL_Framebuffer * this); GLuint egl_framebufferGetTexture(EGL_Framebuffer * this); looking-glass-B6/client/renderers/EGL/glsl.include.awk000066400000000000000000000003531434445012300230420ustar00rootroot00000000000000BEGIN { FS="\"" } function process(line, second) { if (line ~ /^#include[ \t]*".+"[ \t\r]*$/) { while (getline < second) { process($0, $2) } } else { print line } } { process($0, $2) } looking-glass-B6/client/renderers/EGL/model.c000066400000000000000000000130341434445012300212170ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "model.h" #include "shader.h" #include "texture.h" #include "common/debug.h" #include "common/ll.h" #include #include #include struct EGL_Model { bool rebuild; struct ll * verticies; size_t vertexCount; bool finish; GLuint buffer; GLuint vao; EGL_Shader * shader; EGL_Texture * texture; }; struct FloatList { GLfloat * v; GLfloat * u; size_t count; }; void update_uniform_bindings(EGL_Model * model); bool egl_modelInit(EGL_Model ** model) { *model = malloc(sizeof(**model)); if (!*model) { DEBUG_ERROR("Failed to malloc EGL_Model"); return false; } memset(*model, 0, sizeof(**model)); (*model)->verticies = ll_new(); return true; } void egl_modelFree(EGL_Model ** model) { if (!*model) return; struct FloatList * fl; while(ll_shift((*model)->verticies, (void **)&fl)) { free(fl->u); free(fl->v); free(fl); } ll_free((*model)->verticies); if ((*model)->buffer) glDeleteBuffers(1, &(*model)->buffer); if ((*model)->vao) glDeleteVertexArrays(1, &(*model)->vao); free(*model); *model = NULL; } void egl_modelSetDefault(EGL_Model * model, bool flipped) { static const GLfloat square[] = { -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f }; static const GLfloat uvsNormal[] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f }; static const GLfloat uvsFlipped[] = { 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f }; egl_modelAddVerts(model, square, flipped ? uvsFlipped : uvsNormal, 4); } void egl_modelAddVerts(EGL_Model * model, const GLfloat * verticies, const GLfloat * uvs, const size_t count) { struct FloatList * fl = malloc(sizeof(*fl)); if (!fl) { DEBUG_ERROR("out of memory"); return; } fl->count = count; fl->v = malloc(sizeof(GLfloat) * count * 3); if (!fl->v) { DEBUG_ERROR("out of memory"); free(fl); return; } fl->u = malloc(sizeof(GLfloat) * count * 2); if (!fl->u) { DEBUG_ERROR("out of memory"); free(fl->v); free(fl); return; } memcpy(fl->v, verticies, sizeof(GLfloat) * count * 3); if (uvs) memcpy(fl->u, uvs, sizeof(GLfloat) * count * 2); else memset(fl->u, 0 , sizeof(GLfloat) * count * 2); ll_push(model->verticies, fl); model->rebuild = true; model->vertexCount += count; } void egl_modelRender(EGL_Model * model) { if (!model->vertexCount) return; if (model->rebuild) { if (model->buffer) glDeleteBuffers(1, &model->buffer); if (!model->vao) glGenVertexArrays(1, &model->vao); glBindVertexArray(model->vao); /* create a buffer large enough */ glGenBuffers(1, &model->buffer); glBindBuffer(GL_ARRAY_BUFFER, model->buffer); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * (model->vertexCount * 5), NULL, GL_STATIC_DRAW); GLintptr offset = 0; /* buffer the verticies */ struct FloatList * fl; ll_lock(model->verticies); ll_forEachNL(model->verticies, item, fl) { glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * fl->count * 3, fl->v); offset += sizeof(GLfloat) * fl->count * 3; } /* buffer the uvs */ ll_forEachNL(model->verticies, item, fl) { glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * fl->count * 2, fl->u); offset += sizeof(GLfloat) * fl->count * 2; } ll_unlock(model->verticies); /* set up vertex arrays in the VAO */ glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(GLfloat) * model->vertexCount * 3)); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); model->rebuild = false; } glBindVertexArray(model->vao); if (model->shader) egl_shaderUse(model->shader); if (model->texture) egl_textureBind(model->texture); /* draw the arrays */ GLint offset = 0; struct FloatList * fl; ll_lock(model->verticies); ll_forEachNL(model->verticies, item, fl) { glDrawArrays(GL_TRIANGLE_STRIP, offset, fl->count); offset += fl->count; } ll_unlock(model->verticies); /* unbind and cleanup */ glBindTexture(GL_TEXTURE_2D, 0); glBindVertexArray(0); glUseProgram(0); } void egl_modelSetShader(EGL_Model * model, EGL_Shader * shader) { model->shader = shader; update_uniform_bindings(model); } void egl_modelSetTexture(EGL_Model * model, EGL_Texture * texture) { model->texture = texture; update_uniform_bindings(model); } void update_uniform_bindings(EGL_Model * model) { if (!model->shader || !model->texture) return; egl_shaderAssocTextures(model->shader, 1); } looking-glass-B6/client/renderers/EGL/model.h000066400000000000000000000026271434445012300212320ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include "shader.h" #include "texture.h" #include typedef struct EGL_Model EGL_Model; typedef struct EGL_Texture EGL_Texture; bool egl_modelInit(EGL_Model ** model); void egl_modelFree(EGL_Model ** model); void egl_modelSetDefault (EGL_Model * model, bool flipped); void egl_modelAddVerts(EGL_Model * model, const GLfloat * verticies, const GLfloat * uvs, const size_t count); void egl_modelSetShader (EGL_Model * model, EGL_Shader * shader); void egl_modelSetTexture (EGL_Model * model, EGL_Texture * texture); void egl_modelRender(EGL_Model * model); looking-glass-B6/client/renderers/EGL/postprocess.c000066400000000000000000000370771434445012300225200ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define _GNU_SOURCE #include "postprocess.h" #include "filters.h" #include "app.h" #include "cimgui.h" #include #include #include #include #include #include #include "common/debug.h" #include "common/array.h" #include "common/option.h" #include "common/paths.h" #include "common/stringlist.h" #include "common/stringutils.h" #include "common/vector.h" static const EGL_FilterOps * EGL_Filters[] = { &egl_filterDownscaleOps, &egl_filterFFXFSR1Ops, &egl_filterFFXCASOps }; struct EGL_PostProcess { Vector filters; GLuint output; unsigned int outputX, outputY; _Atomic(bool) modified; EGL_DesktopRects * rects; StringList presets; char * presetDir; int activePreset; char presetEdit[128]; char * presetError; }; void egl_postProcessEarlyInit(void) { static struct Option options[] = { { .module = "eglFilter", .name = "order", .description = "The order of filters to use", .preset = true, .type = OPTION_TYPE_STRING, .value.x_string = "" }, { .module = "egl", .name = "preset", .description = "The initial filter preset to load", .type = OPTION_TYPE_STRING }, { 0 } }; option_register(options); for (int i = 0; i < ARRAY_LENGTH(EGL_Filters); ++i) EGL_Filters[i]->earlyInit(); } static void loadPreset(struct EGL_PostProcess * this, const char * name); static void loadPresetList(struct EGL_PostProcess * this) { DIR * dir = NULL; alloc_sprintf(&this->presetDir, "%s/presets", lgConfigDir()); if (!this->presetDir) { DEBUG_ERROR("Failed to allocate memory for presets"); return; } if (mkdir(this->presetDir, S_IRWXU) < 0 && errno != EEXIST) { DEBUG_ERROR("Failed to create presets directory: %s", this->presetDir); goto fail; } dir = opendir(this->presetDir); if (!dir) { DEBUG_ERROR("Failed to open presets directory: %s", this->presetDir); goto fail; } this->presets = stringlist_new(true); if (!this->presets) { DEBUG_ERROR("Failed to allocate memory for preset list"); goto fail; } struct dirent * entry; const char * preset = option_get_string("egl", "preset"); this->activePreset = -1; while ((entry = readdir(dir)) != NULL) { if (entry->d_type != DT_REG) continue; DEBUG_INFO("Found preset: %s", entry->d_name); char * name = strdup(entry->d_name); if (!name) { DEBUG_ERROR("Failed to allocate memory"); goto fail; } stringlist_push(this->presets, name); if (preset && strcmp(preset, name) == 0) this->activePreset = stringlist_count(this->presets) - 1; } closedir(dir); if (preset) { if (this->activePreset > -1) loadPreset(this, preset); else DEBUG_WARN("egl:preset '%s' does not exist", preset); } return; fail: free(this->presetDir); this->presetDir = NULL; if (dir) closedir(dir); if (this->presets) stringlist_free(&this->presets); } static void presetError(struct EGL_PostProcess * this, char * message) { free(this->presetError); this->presetError = message; } static bool savePreset(struct EGL_PostProcess * this, const char * name) { EGL_Filter * filter; vector_forEach(filter, &this->filters) egl_filterSaveState(filter); size_t orderLen = 0; vector_forEach(filter, &this->filters) orderLen += strlen(filter->ops.id) + 1; char order[orderLen]; char * p = order; vector_forEach(filter, &this->filters) { strcpy(p, filter->ops.id); p += strlen(filter->ops.id); *p++ = ';'; } if (p > order) p[-1] = '\0'; option_set_string("eglFilter", "order", order); char * path; alloc_sprintf(&path, "%s/%s", this->presetDir, name); if (!path) { DEBUG_ERROR("Failed to allocate memory"); return false; } FILE * file = fopen(path, "w"); if (!file) { const char * strError = strerror(errno); DEBUG_ERROR("Failed to open preset \"%s\" for writing: %s", name, strError); free(path); char * error; alloc_sprintf(&error, "Failed to save preset: %s\nError: %s", name, strError); if (error) presetError(this, error); return false; } free(path); DEBUG_INFO("Saving preset: %s", name); option_dump_preset(file); fclose(file); return true; } static int stringListIndex(StringList list, const char * str) { unsigned int count = stringlist_count(list); for (unsigned int i = 0; i < count; ++i) if (strcmp(stringlist_at(list, i), str) == 0) return i; return INT_MAX; } static int compareFilterOrder(const void * a_, const void * b_, void * opaque) { const EGL_Filter * a = *(const EGL_Filter **)a_; const EGL_Filter * b = *(const EGL_Filter **)b_; StringList order = opaque; return stringListIndex(order, a->ops.id) - stringListIndex(order, b->ops.id); } static void reorderFilters(struct EGL_PostProcess * this) { StringList order = stringlist_new(false); if (!order) { DEBUG_ERROR("Failed to allocate memory"); return; } char * orderStr = strdup(option_get_string("eglFilter", "order")); if (!orderStr) { DEBUG_ERROR("Failed to allocate memory"); stringlist_free(&order); return; } char * p = orderStr; while (*p) { stringlist_push(order, p); char * end = strchr(p, ';'); if (!end) break; *end = '\0'; p = end + 1; } qsort_r(vector_data(&this->filters), vector_size(&this->filters), sizeof(EGL_Filter *), compareFilterOrder, order); stringlist_free(&order); free(orderStr); } static void loadPreset(struct EGL_PostProcess * this, const char * name) { char * path; alloc_sprintf(&path, "%s/%s", this->presetDir, name); if (!path) { DEBUG_ERROR("Failed to allocate memory"); return; } if (!option_load(path)) { DEBUG_ERROR("Failed to load preset: %s", name); free(path); char * error; alloc_sprintf(&error, "Failed to load preset: %s", name); if (error) presetError(this, error); return; } free(path); DEBUG_INFO("Loading preset: %s", name); EGL_Filter * filter; vector_forEach(filter, &this->filters) egl_filterLoadState(filter); reorderFilters(this); } static void savePresetAs(struct EGL_PostProcess * this) { if (!savePreset(this, this->presetEdit)) return; for (unsigned i = 0; i < stringlist_count(this->presets); ++i) { DEBUG_INFO("Saw preset: %s", stringlist_at(this->presets, i)); if (strcmp(stringlist_at(this->presets, i), this->presetEdit) == 0) { this->activePreset = i; return; } } this->activePreset = stringlist_push(this->presets, strdup(this->presetEdit)); } static void deletePreset(struct EGL_PostProcess * this) { char * path; alloc_sprintf(&path, "%s/%s", this->presetDir, stringlist_at(this->presets, this->activePreset)); if (!path) { DEBUG_ERROR("Failed to allocate memory"); return; } unlink(path); free(path); stringlist_remove(this->presets, this->activePreset); if (this->activePreset >= stringlist_count(this->presets)) this->activePreset = stringlist_count(this->presets) - 1; } static bool presetsUI(struct EGL_PostProcess * this) { if (!this->presets) return false; bool redraw = false; const char * active = ""; if (this->activePreset >= 0) active = stringlist_at(this->presets, this->activePreset); if (igBeginCombo("Preset name", active, 0)) { for (unsigned i = 0; i < stringlist_count(this->presets); ++i) { bool selected = i == this->activePreset; if (igSelectable_Bool(stringlist_at(this->presets, i), selected, 0, (ImVec2) { 0.0f, 0.0f })) { this->activePreset = i; redraw = true; loadPreset(this, stringlist_at(this->presets, this->activePreset)); } if (selected) igSetItemDefaultFocus(); } igEndCombo(); } if (igIsItemHovered(ImGuiHoveredFlags_None)) igSetTooltip("Selecting a preset will load it"); if (igButton("Save preset", (ImVec2) { 0.0f, 0.0f })) { if (this->activePreset >= 0) savePreset(this, stringlist_at(this->presets, this->activePreset)); else presetError(this, strdup("You must select a preset to save.")); } if (igIsItemHovered(ImGuiHoveredFlags_None) && this->activePreset >= 0) igSetTooltip("This will overwrite the preset named: %s", stringlist_at(this->presets, this->activePreset)); igSameLine(0.0f, -1.0f); if (igButton("Save preset as...", (ImVec2) { 0.0f, 0.0f })) { this->presetEdit[0] = '\0'; igOpenPopup_Str("Save preset as...", ImGuiPopupFlags_None); } igSameLine(0.0f, -1.0f); if (igButton("Delete preset", (ImVec2) { 0.0f, 0.0f }) && this->activePreset >= 0) deletePreset(this); if (igBeginPopupModal("Save preset as...", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { igText("Enter a name for the new preset:"); if (!igIsAnyItemActive()) igSetKeyboardFocusHere(0); if (igInputText("##name", this->presetEdit, sizeof(this->presetEdit), ImGuiInputTextFlags_EnterReturnsTrue, NULL, NULL)) { savePresetAs(this); igCloseCurrentPopup(); } if (igButton("Save", (ImVec2) { 0.0f, 0.0f })) { savePresetAs(this); igCloseCurrentPopup(); } igSameLine(0.0f, -1.0f); if (igButton("Cancel", (ImVec2) { 0.0f, 0.0f })) igCloseCurrentPopup(); igEndPopup(); } if (this->presetError) igOpenPopup_Str("Preset error", ImGuiPopupFlags_None); if (igBeginPopupModal("Preset error", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { igText("%s", this->presetError); if (!igIsAnyItemActive()) igSetKeyboardFocusHere(0); if (igButton("OK", (ImVec2) { 0.0f, 0.0f })) { free(this->presetError); this->presetError = NULL; igCloseCurrentPopup(); } igEndPopup(); } return redraw; } static void drawDropTarget(void) { igPushStyleColor_Vec4(ImGuiCol_Separator, (ImVec4) { 1.0f, 1.0f, 0.0f, 1.0f }); igSeparator(); igPopStyleColor(1); } static void configUI(void * opaque, int * id) { struct EGL_PostProcess * this = opaque; bool redraw = false; redraw |= presetsUI(this); igSeparator(); static size_t mouseIdx = -1; static bool moving = false; static size_t moveIdx = 0; bool doMove = false; ImVec2 window, pos; igGetWindowPos(&window); igGetMousePos(&pos); EGL_Filter ** filters = vector_data(&this->filters); size_t count = vector_size(&this->filters); for (size_t i = 0; i < count; ++i) { EGL_Filter * filter = filters[i]; if (moving && mouseIdx < moveIdx && i == mouseIdx) drawDropTarget(); igPushID_Ptr(filter); bool draw = igCollapsingHeader_BoolPtr(filter->ops.name, NULL, 0); if (igIsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) mouseIdx = i; bool active = igIsItemActive(); if (draw) redraw |= egl_filterImguiConfig(filter); igPopID(); if (moving) { if (!igIsMouseDragging(ImGuiMouseButton_Left, -1.0f)) { moving = false; doMove = true; } } else if (active && igIsMouseDragging(ImGuiMouseButton_Left, -1.0f)) { moveIdx = mouseIdx; moving = true; } if (moving && mouseIdx > moveIdx && i == mouseIdx) drawDropTarget(); } if (moving) { igSetMouseCursor(ImGuiMouseCursor_Hand); igSetTooltip(filters[moveIdx]->ops.name); } if (doMove) { EGL_Filter * tmp = filters[moveIdx]; if (mouseIdx > moveIdx) // moving down memmove(filters + moveIdx, filters + moveIdx + 1, (mouseIdx - moveIdx) * sizeof(EGL_Filter *)); else // moving up memmove(filters + mouseIdx + 1, filters + mouseIdx, (moveIdx - mouseIdx) * sizeof(EGL_Filter *)); filters[mouseIdx] = tmp; } if (redraw) { atomic_store(&this->modified, true); app_invalidateWindow(true); } } bool egl_postProcessInit(EGL_PostProcess ** pp) { EGL_PostProcess * this = calloc(1, sizeof(*this)); if (!this) { DEBUG_ERROR("Failed to allocate memory"); return false; } if (!vector_create(&this->filters, sizeof(EGL_Filter *), ARRAY_LENGTH(EGL_Filters))) { DEBUG_ERROR("Failed to allocate the filter list"); goto error_this; } if (!egl_desktopRectsInit(&this->rects, 1)) { DEBUG_ERROR("Failed to initialize the desktop rects"); goto error_filters; } loadPresetList(this); reorderFilters(this); app_overlayConfigRegisterTab("EGL Filters", configUI, this); *pp = this; return true; error_filters: vector_destroy(&this->filters); error_this: free(this); return false; } void egl_postProcessFree(EGL_PostProcess ** pp) { if (!*pp) return; EGL_PostProcess * this = *pp; EGL_Filter ** filter; vector_forEachRef(filter, &this->filters) egl_filterFree(filter); vector_destroy(&this->filters); free(this->presetDir); if (this->presets) stringlist_free(&this->presets); egl_desktopRectsFree(&this->rects); free(this->presetError); free(this); *pp = NULL; } bool egl_postProcessAdd(EGL_PostProcess * this, const EGL_FilterOps * ops) { EGL_Filter * filter; if (!egl_filterInit(ops, &filter)) return false; vector_push(&this->filters, &filter); return true; } bool egl_postProcessConfigModified(EGL_PostProcess * this) { return atomic_load(&this->modified); } bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex, EGL_DesktopRects * rects, int desktopWidth, int desktopHeight, unsigned int targetX, unsigned int targetY) { if (targetX == 0 && targetY == 0) DEBUG_FATAL("targetX || targetY == 0"); EGL_Filter * lastFilter = NULL; unsigned int sizeX, sizeY; GLuint texture; if (egl_textureGet(tex, &texture, &sizeX, &sizeY) != EGL_TEX_STATUS_OK) return false; if (atomic_exchange(&this->modified, false)) { rects = this->rects; egl_desktopRectsUpdate(rects, NULL, desktopWidth, desktopHeight); } GLfloat matrix[6]; egl_desktopRectsMatrix(matrix, desktopWidth, desktopHeight, 0.0f, 0.0f, 1.0f, 1.0f, LG_ROTATE_0); EGL_FilterRects filterRects = { .rects = rects, .matrix = matrix, .width = desktopWidth, .height = desktopHeight, }; EGL_Filter * filter; vector_forEach(filter, &this->filters) { egl_filterSetOutputResHint(filter, targetX, targetY); if (!egl_filterSetup(filter, tex->format.pixFmt, sizeX, sizeY) || !egl_filterPrepare(filter)) continue; texture = egl_filterRun(filter, &filterRects, texture); egl_filterGetOutputRes(filter, &sizeX, &sizeY); if (lastFilter) egl_filterRelease(lastFilter); lastFilter = filter; } this->output = texture; this->outputX = sizeX; this->outputY = sizeY; return true; } GLuint egl_postProcessGetOutput(EGL_PostProcess * this, unsigned int * outputX, unsigned int * outputY) { *outputX = this->outputX; *outputY = this->outputY; return this->output; } looking-glass-B6/client/renderers/EGL/postprocess.h000066400000000000000000000033451434445012300225140ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include "desktop_rects.h" #include "filter.h" #include "texture.h" typedef struct EGL_PostProcess EGL_PostProcess; void egl_postProcessEarlyInit(void); bool egl_postProcessInit(EGL_PostProcess ** pp); void egl_postProcessFree(EGL_PostProcess ** pp); /* create and add a filter to this processor */ bool egl_postProcessAdd(EGL_PostProcess * this, const EGL_FilterOps * ops); /* returns true if the configuration was modified since the last run */ bool egl_postProcessConfigModified(EGL_PostProcess * this); /* apply the filters to the supplied texture * targetX/Y is the final target output dimension hint if scalers are present */ bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex, EGL_DesktopRects * rects, int desktopWidth, int desktopHeight, unsigned int targetX, unsigned int targetY); GLuint egl_postProcessGetOutput(EGL_PostProcess * this, unsigned int * outputX, unsigned int * outputY); looking-glass-B6/client/renderers/EGL/shader.c000066400000000000000000000340331434445012300213670ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "shader.h" #include "common/debug.h" #include "util.h" #include #include #include struct EGL_Shader { bool hasShader; GLuint shader; EGL_Uniform * uniforms; int uniformCount; int uniformUsed; }; bool egl_shaderInit(EGL_Shader ** this) { *this = calloc(1, sizeof(EGL_Shader)); if (!*this) { DEBUG_ERROR("Failed to malloc EGL_Shader"); return false; } return true; } void egl_shaderFree(EGL_Shader ** shader) { EGL_Shader * this = *shader; if (!this) return; if (this->hasShader) glDeleteProgram(this->shader); egl_shaderFreeUniforms(this); free(this->uniforms); free(this); *shader = NULL; } bool egl_shaderLoad(EGL_Shader * this, const char * vertex_file, const char * fragment_file) { char * vertex_code, * fragment_code; size_t vertex_size, fragment_size; if (!util_fileGetContents(vertex_file, &vertex_code, &vertex_size)) { DEBUG_ERROR("Failed to read vertex shader"); return false; } DEBUG_INFO("Loaded vertex shader: %s", vertex_file); if (!util_fileGetContents(fragment_file, &fragment_code, &fragment_size)) { DEBUG_ERROR("Failed to read fragment shader"); free(vertex_code); return false; } DEBUG_INFO("Loaded fragment shader: %s", fragment_file); bool ret = egl_shaderCompile(this, vertex_code, vertex_size, fragment_code, fragment_size); free(vertex_code); free(fragment_code); return ret; } bool egl_shaderCompile(EGL_Shader * this, const char * vertex_code, size_t vertex_size, const char * fragment_code, size_t fragment_size) { if (this->hasShader) { glDeleteProgram(this->shader); this->hasShader = false; } GLint length; GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); length = vertex_size; glShaderSource(vertexShader, 1, (const char**)&vertex_code, &length); glCompileShader(vertexShader); GLint result = GL_FALSE; glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &result); if (result == GL_FALSE) { DEBUG_ERROR("Failed to compile vertex shader"); int logLength; glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 0) { char *log = malloc(logLength + 1); if (!log) DEBUG_ERROR("out of memory"); else { glGetShaderInfoLog(vertexShader, logLength, NULL, log); log[logLength] = 0; DEBUG_ERROR("%s", log); free(log); } } glDeleteShader(vertexShader); return false; } GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); length = fragment_size; glShaderSource(fragmentShader, 1, (const char**)&fragment_code, &length); glCompileShader(fragmentShader); glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &result); if (result == GL_FALSE) { DEBUG_ERROR("Failed to compile fragment shader"); int logLength; glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 0) { char *log = malloc(logLength + 1); if (!log) DEBUG_ERROR("out of memory"); else { glGetShaderInfoLog(fragmentShader, logLength, NULL, log); log[logLength] = 0; DEBUG_ERROR("%s", log); free(log); } } glDeleteShader(fragmentShader); glDeleteShader(vertexShader ); return false; } this->shader = glCreateProgram(); glAttachShader(this->shader, vertexShader ); glAttachShader(this->shader, fragmentShader); glLinkProgram(this->shader); glGetProgramiv(this->shader, GL_LINK_STATUS, &result); if (result == GL_FALSE) { DEBUG_ERROR("Failed to link shader program"); int logLength; glGetProgramiv(this->shader, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 0) { char *log = malloc(logLength + 1); glGetProgramInfoLog(this->shader, logLength, NULL, log); log[logLength] = 0; DEBUG_ERROR("%s", log); free(log); } glDetachShader(this->shader, vertexShader ); glDetachShader(this->shader, fragmentShader); glDeleteShader(fragmentShader); glDeleteShader(vertexShader ); glDeleteProgram(this->shader ); return false; } glDetachShader(this->shader, vertexShader ); glDetachShader(this->shader, fragmentShader); glDeleteShader(fragmentShader); glDeleteShader(vertexShader ); this->hasShader = true; return true; } void egl_shaderSetUniforms(EGL_Shader * this, EGL_Uniform * uniforms, int count) { egl_shaderFreeUniforms(this); if (count > this->uniformCount) { free(this->uniforms); this->uniforms = malloc(sizeof(*this->uniforms) * count); if (!this->uniforms) { DEBUG_ERROR("out of memory"); return; } this->uniformCount = count; } this->uniformUsed = count; memcpy(this->uniforms, uniforms, sizeof(*this->uniforms) * count); for(int i = 0; i < this->uniformUsed; ++i) { switch(this->uniforms[i].type) { case EGL_UNIFORM_TYPE_1FV: case EGL_UNIFORM_TYPE_2FV: case EGL_UNIFORM_TYPE_3FV: case EGL_UNIFORM_TYPE_4FV: case EGL_UNIFORM_TYPE_1IV: case EGL_UNIFORM_TYPE_2IV: case EGL_UNIFORM_TYPE_3IV: case EGL_UNIFORM_TYPE_4IV: case EGL_UNIFORM_TYPE_1UIV: case EGL_UNIFORM_TYPE_2UIV: case EGL_UNIFORM_TYPE_3UIV: case EGL_UNIFORM_TYPE_4UIV: countedBufferAddRef(this->uniforms[i].v); break; case EGL_UNIFORM_TYPE_M2FV: case EGL_UNIFORM_TYPE_M3FV: case EGL_UNIFORM_TYPE_M4FV: case EGL_UNIFORM_TYPE_M2x3FV: case EGL_UNIFORM_TYPE_M3x2FV: case EGL_UNIFORM_TYPE_M2x4FV: case EGL_UNIFORM_TYPE_M4x2FV: case EGL_UNIFORM_TYPE_M3x4FV: case EGL_UNIFORM_TYPE_M4x3FV: countedBufferAddRef(this->uniforms[i].m.v); break; default: break; } } }; void egl_shaderFreeUniforms(EGL_Shader * this) { for(int i = 0; i < this->uniformUsed; ++i) { switch(this->uniforms[i].type) { case EGL_UNIFORM_TYPE_1FV: case EGL_UNIFORM_TYPE_2FV: case EGL_UNIFORM_TYPE_3FV: case EGL_UNIFORM_TYPE_4FV: case EGL_UNIFORM_TYPE_1IV: case EGL_UNIFORM_TYPE_2IV: case EGL_UNIFORM_TYPE_3IV: case EGL_UNIFORM_TYPE_4IV: case EGL_UNIFORM_TYPE_1UIV: case EGL_UNIFORM_TYPE_2UIV: case EGL_UNIFORM_TYPE_3UIV: case EGL_UNIFORM_TYPE_4UIV: countedBufferRelease(&this->uniforms[i].v); break; case EGL_UNIFORM_TYPE_M2FV: case EGL_UNIFORM_TYPE_M3FV: case EGL_UNIFORM_TYPE_M4FV: case EGL_UNIFORM_TYPE_M2x3FV: case EGL_UNIFORM_TYPE_M3x2FV: case EGL_UNIFORM_TYPE_M2x4FV: case EGL_UNIFORM_TYPE_M4x2FV: case EGL_UNIFORM_TYPE_M3x4FV: case EGL_UNIFORM_TYPE_M4x3FV: countedBufferRelease(&this->uniforms[i].m.v); break; default: break; } } this->uniformUsed = 0; } void egl_shaderUse(EGL_Shader * this) { if (this->hasShader) glUseProgram(this->shader); else DEBUG_ERROR("Shader program has not been compiled"); for(int i = 0; i < this->uniformUsed; ++i) { EGL_Uniform * uniform = &this->uniforms[i]; switch(uniform->type) { case EGL_UNIFORM_TYPE_1F: glUniform1f(uniform->location, uniform->f[0]); break; case EGL_UNIFORM_TYPE_2F: glUniform2f(uniform->location, uniform->f[0], uniform->f[1]); break; case EGL_UNIFORM_TYPE_3F: glUniform3f(uniform->location, uniform->f[0], uniform->f[1], uniform->f[2]); break; case EGL_UNIFORM_TYPE_4F: glUniform4f(uniform->location, uniform->f[0], uniform->f[1], uniform->f[2], uniform->f[3]); break; case EGL_UNIFORM_TYPE_1I: glUniform1i(uniform->location, uniform->i[0]); break; case EGL_UNIFORM_TYPE_2I: glUniform2i(uniform->location, uniform->i[0], uniform->i[1]); break; case EGL_UNIFORM_TYPE_3I: glUniform3i(uniform->location, uniform->i[0], uniform->i[1], uniform->i[2]); break; case EGL_UNIFORM_TYPE_4I: glUniform4i(uniform->location, uniform->i[0], uniform->i[1], uniform->i[2], uniform->i[3]); break; case EGL_UNIFORM_TYPE_1UI: glUniform1ui(uniform->location, uniform->ui[0]); break; case EGL_UNIFORM_TYPE_2UI: glUniform2ui(uniform->location, uniform->ui[0], uniform->ui[1]); break; case EGL_UNIFORM_TYPE_3UI: glUniform3ui(uniform->location, uniform->ui[0], uniform->ui[1], uniform->ui[2]); break; case EGL_UNIFORM_TYPE_4UI: glUniform4ui(uniform->location, uniform->ui[0], uniform->ui[1], uniform->ui[2], uniform->ui[3]); break; case EGL_UNIFORM_TYPE_1FV: glUniform1fv(uniform->location, uniform->v->size / sizeof(GLfloat), (const GLfloat *)uniform->v->data); break; case EGL_UNIFORM_TYPE_2FV: glUniform2fv(uniform->location, uniform->v->size / sizeof(GLfloat) / 2, (const GLfloat *)uniform->v->data); break; case EGL_UNIFORM_TYPE_3FV: glUniform3fv(uniform->location, uniform->v->size / sizeof(GLfloat) / 3, (const GLfloat *)uniform->v->data); break; case EGL_UNIFORM_TYPE_4FV: glUniform4fv(uniform->location, uniform->v->size / sizeof(GLfloat) / 4, (const GLfloat *)uniform->v->data); break; case EGL_UNIFORM_TYPE_1IV: glUniform1iv(uniform->location, uniform->v->size / sizeof(GLint), (const GLint *)uniform->v->data); break; case EGL_UNIFORM_TYPE_2IV: glUniform2iv(uniform->location, uniform->v->size / sizeof(GLint) / 2, (const GLint *)uniform->v->data); break; case EGL_UNIFORM_TYPE_3IV: glUniform3iv(uniform->location, uniform->v->size / sizeof(GLint) / 3, (const GLint *)uniform->v->data); break; case EGL_UNIFORM_TYPE_4IV: glUniform4iv(uniform->location, uniform->v->size / sizeof(GLint) / 4, (const GLint *)uniform->v->data); break; case EGL_UNIFORM_TYPE_1UIV: glUniform1uiv(uniform->location, uniform->v->size / sizeof(GLuint), (const GLuint *)uniform->v->data); break; case EGL_UNIFORM_TYPE_2UIV: glUniform2uiv(uniform->location, uniform->v->size / sizeof(GLuint) / 2, (const GLuint *)uniform->v->data); break; case EGL_UNIFORM_TYPE_3UIV: glUniform3uiv(uniform->location, uniform->v->size / sizeof(GLuint) / 3, (const GLuint *)uniform->v->data); break; case EGL_UNIFORM_TYPE_4UIV: glUniform4uiv(uniform->location, uniform->v->size / sizeof(GLuint) / 4, (const GLuint *)uniform->v->data); break; case EGL_UNIFORM_TYPE_M2FV: glUniformMatrix2fv(uniform->location, uniform->v->size / sizeof(GLfloat) / 2, uniform->m.transpose, (const GLfloat *)uniform->m.v->data); break; case EGL_UNIFORM_TYPE_M3FV: glUniformMatrix3fv(uniform->location, uniform->v->size / sizeof(GLfloat) / 3, uniform->m.transpose, (const GLfloat *)uniform->m.v->data); break; case EGL_UNIFORM_TYPE_M4FV: glUniformMatrix4fv(uniform->location, uniform->v->size / sizeof(GLfloat) / 4, uniform->m.transpose, (const GLfloat *)uniform->m.v->data); break; case EGL_UNIFORM_TYPE_M2x3FV: glUniformMatrix2x3fv(uniform->location, uniform->v->size / sizeof(GLfloat) / 6, uniform->m.transpose, (const GLfloat *)uniform->m.v->data); break; case EGL_UNIFORM_TYPE_M3x2FV: glUniformMatrix3x2fv(uniform->location, uniform->v->size / sizeof(GLfloat) / 6, uniform->m.transpose, (const GLfloat *)uniform->m.v->data); break; case EGL_UNIFORM_TYPE_M2x4FV: glUniformMatrix2x4fv(uniform->location, uniform->v->size / sizeof(GLfloat) / 8, uniform->m.transpose, (const GLfloat *)uniform->m.v->data); break; case EGL_UNIFORM_TYPE_M4x2FV: glUniformMatrix4x2fv(uniform->location, uniform->v->size / sizeof(GLfloat) / 8, uniform->m.transpose, (const GLfloat *)uniform->m.v->data); break; case EGL_UNIFORM_TYPE_M3x4FV: glUniformMatrix3x4fv(uniform->location, uniform->v->size / sizeof(GLfloat) / 12, uniform->m.transpose, (const GLfloat *)uniform->m.v->data); break; case EGL_UNIFORM_TYPE_M4x3FV: glUniformMatrix4x3fv(uniform->location, uniform->v->size / sizeof(GLfloat) / 12, uniform->m.transpose, (const GLfloat *)uniform->m.v->data); break; } } } void egl_shaderAssocTextures(EGL_Shader * this, const int count) { char name[] = "sampler1"; glUseProgram(this->shader); for(int i = 0; i < count; ++i, name[7]++) { GLint loc = glGetUniformLocation(this->shader, name); if (loc == -1) { DEBUG_WARN("Shader uniform location `%s` not found", name); continue; } glUniform1i(loc, i); } glUseProgram(0); } GLint egl_shaderGetUniform(EGL_Shader * this, const char * name) { if (!this->shader) { DEBUG_ERROR("Shader program has not been compiled"); return 0; } return glGetUniformLocation(this->shader, name); } looking-glass-B6/client/renderers/EGL/shader.h000066400000000000000000000053111434445012300213710ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include #include #include "common/countedbuffer.h" typedef struct EGL_Shader EGL_Shader; enum EGL_UniformType { EGL_UNIFORM_TYPE_1F, EGL_UNIFORM_TYPE_2F, EGL_UNIFORM_TYPE_3F, EGL_UNIFORM_TYPE_4F, EGL_UNIFORM_TYPE_1I, EGL_UNIFORM_TYPE_2I, EGL_UNIFORM_TYPE_3I, EGL_UNIFORM_TYPE_4I, EGL_UNIFORM_TYPE_1UI, EGL_UNIFORM_TYPE_2UI, EGL_UNIFORM_TYPE_3UI, EGL_UNIFORM_TYPE_4UI, // vectors EGL_UNIFORM_TYPE_1FV, EGL_UNIFORM_TYPE_2FV, EGL_UNIFORM_TYPE_3FV, EGL_UNIFORM_TYPE_4FV, EGL_UNIFORM_TYPE_1IV, EGL_UNIFORM_TYPE_2IV, EGL_UNIFORM_TYPE_3IV, EGL_UNIFORM_TYPE_4IV, EGL_UNIFORM_TYPE_1UIV, EGL_UNIFORM_TYPE_2UIV, EGL_UNIFORM_TYPE_3UIV, EGL_UNIFORM_TYPE_4UIV, // matrices EGL_UNIFORM_TYPE_M2FV, EGL_UNIFORM_TYPE_M3FV, EGL_UNIFORM_TYPE_M4FV, EGL_UNIFORM_TYPE_M2x3FV, EGL_UNIFORM_TYPE_M3x2FV, EGL_UNIFORM_TYPE_M2x4FV, EGL_UNIFORM_TYPE_M4x2FV, EGL_UNIFORM_TYPE_M3x4FV, EGL_UNIFORM_TYPE_M4x3FV }; typedef struct EGL_Uniform { enum EGL_UniformType type; GLint location; union { GLfloat f [4]; GLint i [4]; GLuint ui[4]; CountedBuffer * v; struct { CountedBuffer * v; GLboolean transpose; } m; }; } EGL_Uniform; bool egl_shaderInit(EGL_Shader ** shader); void egl_shaderFree(EGL_Shader ** shader); bool egl_shaderLoad(EGL_Shader * model, const char * vertex_file, const char * fragment_file); bool egl_shaderCompile(EGL_Shader * model, const char * vertex_code, size_t vertex_size, const char * fragment_code, size_t fragment_size); void egl_shaderSetUniforms(EGL_Shader * shader, EGL_Uniform * uniforms, int count); void egl_shaderFreeUniforms(EGL_Shader * shader); void egl_shaderUse(EGL_Shader * shader); void egl_shaderAssocTextures(EGL_Shader * shader, const int count); GLint egl_shaderGetUniform(EGL_Shader * shader, const char * name); looking-glass-B6/client/renderers/EGL/shader/000077500000000000000000000000001434445012300212205ustar00rootroot00000000000000looking-glass-B6/client/renderers/EGL/shader/basic.vert000066400000000000000000000004571434445012300232110ustar00rootroot00000000000000#version 300 es precision mediump float; layout(location = 0) in vec2 vertex; out vec2 fragCoord; uniform vec2 desktopSize; uniform mat3x2 transform; void main() { vec2 pos = transform * vec3(vertex, 1.0); gl_Position = vec4(pos.x, -pos.y, 0.0, 1.0); fragCoord = vertex / desktopSize; } looking-glass-B6/client/renderers/EGL/shader/color_blind.h000066400000000000000000000023561434445012300236650ustar00rootroot00000000000000vec4 cbTransform(vec4 color, int cbMode) { float L = (17.8824000 * color.r) + (43.516100 * color.g) + (4.11935 * color.b); float M = (03.4556500 * color.r) + (27.155400 * color.g) + (3.86714 * color.b); float S = (00.0299566 * color.r) + (00.184309 * color.g) + (1.46709 * color.b); float l, m, s; if (cbMode == 1) // Protanope { l = 0.0f * L + 2.02344f * M + -2.52581f * S; m = 0.0f * L + 1.0f * M + 0.0f * S; s = 0.0f * L + 0.0f * M + 1.0f * S; } else if (cbMode == 2) // Deuteranope { l = 1.000000 * L + 0.0f * M + 0.00000 * S; m = 0.494207 * L + 0.0f * M + 1.24827 * S; s = 0.000000 * L + 0.0f * M + 1.00000 * S; } else if (cbMode == 3) // Tritanope { l = 1.000000 * L + 0.000000 * M + 0.0 * S; m = 0.000000 * L + 1.000000 * M + 0.0 * S; s = -0.395913 * L + 0.801109 * M + 0.0 * S; } vec4 error; error.r = ( 0.080944447900 * l) + (-0.13050440900 * m) + ( 0.116721066 * s); error.g = (-0.010248533500 * l) + ( 0.05401932660 * m) + (-0.113614708 * s); error.b = (-0.000365296938 * l) + (-0.00412161469 * m) + ( 0.693511405 * s); error.a = 0.0; error = color - error; color.g += (error.r * 0.7) + (error.g * 1.0); color.b += (error.r * 0.7) + (error.b * 1.0); return color; } looking-glass-B6/client/renderers/EGL/shader/compat.h000066400000000000000000000015241434445012300226560ustar00rootroot00000000000000#if __VERSION__ == 300 vec4 textureGather(sampler2D tex, vec2 uv, int comp) { vec4 c0 = textureOffset(tex, uv, ivec2(0,1)); vec4 c1 = textureOffset(tex, uv, ivec2(1,1)); vec4 c2 = textureOffset(tex, uv, ivec2(1,0)); vec4 c3 = textureOffset(tex, uv, ivec2(0,0)); return vec4(c0[comp], c1[comp], c2[comp],c3[comp]); } #elif __VERSION__ < 300 vec4 textureGather(sampler2D tex, vec2 uv, int comp) { vec4 c3 = texture2D(tex, uv); return vec4(c3[comp], c3[comp], c3[comp],c3[comp]); } #endif #if __VERSION__ < 310 uint bitfieldExtract(uint val, int off, int size) { uint mask = uint((1 << size) - 1); return uint(val >> off) & mask; } uint bitfieldInsert(uint a, uint b, int c, int d) { uint mask = ~(0xffffffffu << d) << c; mask = ~mask; a &= mask; return a | (b << c); } #endif looking-glass-B6/client/renderers/EGL/shader/cursor.vert000066400000000000000000000013721434445012300234420ustar00rootroot00000000000000#version 300 es precision mediump float; layout(location = 0) in vec3 vertexPosition_modelspace; layout(location = 1) in vec2 vertexUV; uniform vec4 mouse; uniform int rotate; out vec2 uv; void main() { vec2 muv = vertexPosition_modelspace.xy; muv.x += 1.0f; muv.y -= 1.0f; muv.x *= mouse.z; muv.y *= mouse.w; muv.x += mouse.x; muv.y -= mouse.y; if (rotate == 0) // 0 { gl_Position.xy = muv; } else if (rotate == 1) // 90 { gl_Position.x = muv.y; gl_Position.y = -muv.x; } else if (rotate == 2) // 180 { gl_Position.x = -muv.x; gl_Position.y = -muv.y; } else if (rotate == 3) // 270 { gl_Position.x = -muv.y; gl_Position.y = muv.x; } gl_Position.zw = vec2(0.0, 1.0); uv = vertexUV; } looking-glass-B6/client/renderers/EGL/shader/cursor_mono.frag000066400000000000000000000007251434445012300244320ustar00rootroot00000000000000#version 300 es precision mediump float; in vec2 uv; out vec4 color; uniform sampler2D sampler1; uniform float scale; void main() { vec4 tmp; if (scale > 1.0) { vec2 ts = vec2(textureSize(sampler1, 0)); vec2 px = (uv - (0.5 / ts)) * ts; if (px.x < 0.0 || px.y < 0.0) discard; tmp = texelFetch(sampler1, ivec2(px), 0); } else tmp = texture(sampler1, uv); if (tmp.rgb == vec3(0.0, 0.0, 0.0)) discard; color = tmp; } looking-glass-B6/client/renderers/EGL/shader/cursor_rgb.frag000066400000000000000000000007721434445012300242360ustar00rootroot00000000000000#version 300 es precision mediump float; #include "color_blind.h" in vec2 uv; out vec4 color; uniform sampler2D sampler1; uniform float scale; uniform int cbMode; void main() { if (scale > 1.0) { vec2 ts = vec2(textureSize(sampler1, 0)); vec2 px = (uv - (0.5 / ts)) * ts; if (px.x < 0.0 || px.y < 0.0) discard; color = texelFetch(sampler1, ivec2(px), 0); } else color = texture(sampler1, uv); if (cbMode > 0) color = cbTransform(color, cbMode); } looking-glass-B6/client/renderers/EGL/shader/damage.frag000066400000000000000000000001571434445012300233020ustar00rootroot00000000000000#version 300 es precision mediump float; out vec4 color; void main() { color = vec4(1.0, 1.0, 0.0, 0.5); } looking-glass-B6/client/renderers/EGL/shader/damage.vert000066400000000000000000000002411434445012300233350ustar00rootroot00000000000000#version 300 es layout(location = 0) in vec2 vertex; uniform mat3x2 transform; void main() { gl_Position = vec4(transform * vec3(vertex, 1.0), 0.0, 1.0); } looking-glass-B6/client/renderers/EGL/shader/desktop.vert000066400000000000000000000004001434445012300235650ustar00rootroot00000000000000#version 300 es precision mediump float; layout(location = 0) in vec2 vertex; out vec2 uv; uniform vec2 desktopSize; uniform mat3x2 transform; void main() { gl_Position = vec4(transform * vec3(vertex, 1.0), 0.0, 1.0); uv = vertex / desktopSize; } looking-glass-B6/client/renderers/EGL/shader/desktop_rgb.frag000066400000000000000000000015361434445012300243710ustar00rootroot00000000000000#version 300 es precision mediump float; #define EGL_SCALE_AUTO 0 #define EGL_SCALE_NEAREST 1 #define EGL_SCALE_LINEAR 2 #define EGL_SCALE_MAX 3 #include "color_blind.h" in vec2 uv; out vec4 color; uniform sampler2D sampler1; uniform int scaleAlgo; uniform float nvGain; uniform int cbMode; void main() { switch (scaleAlgo) { case EGL_SCALE_NEAREST: vec2 ts = vec2(textureSize(sampler1, 0)); color = texelFetch(sampler1, ivec2(uv * ts), 0); break; case EGL_SCALE_LINEAR: color = texture(sampler1, uv); break; } if (cbMode > 0) color = cbTransform(color, cbMode); if (nvGain > 0.0) { highp float lumi = (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b); if (lumi < 0.5) color *= atanh((1.0 - lumi) * 2.0 - 1.0) + 1.0; color *= nvGain; } color.a = 1.0; } looking-glass-B6/client/renderers/EGL/shader/downscale.frag000066400000000000000000000010221434445012300240330ustar00rootroot00000000000000#version 300 es precision mediump float; in vec2 fragCoord; out vec4 fragColor; uniform sampler2D texture; uniform vec3 uConfig; void main() { float pixelSize = uConfig.x; float vOffset = uConfig.y; float hOffset = uConfig.z; vec2 inRes = vec2(textureSize(texture, 0)); ivec2 point = ivec2( (floor((fragCoord * inRes) / pixelSize) * pixelSize) + pixelSize / 2.0f ); point.x += int(pixelSize * hOffset); point.y += int(pixelSize * vOffset); fragColor = texelFetch(texture, point, 0); } looking-glass-B6/client/renderers/EGL/shader/downscale_lanczos2.frag000066400000000000000000000022021434445012300256470ustar00rootroot00000000000000#version 300 es precision mediump float; #define PI 3.141592653589793 in vec2 fragCoord; out vec4 fragColor; uniform sampler2D texture; float sinc(float x) { return x == 0.0 ? 1.0 : sin(x * PI) / (x * PI); } float lanczos(float x) { return sinc(x) * sinc(x * 0.5); } float lanczos(vec2 v) { return lanczos(v.x) * lanczos(v.y); } void main() { vec2 size = vec2(textureSize(texture, 0)); vec2 pos = fragCoord * size; vec2 invSize = 1.0 / size; vec2 uvc = floor(pos) + vec2(0.5, 0.5); vec2 uvs[9] = vec2[]( uvc + vec2(-1.0, -1.0), uvc + vec2(-1.0, 0.0), uvc + vec2(-1.0, 1.0), uvc + vec2( 0.0, -1.0), uvc + vec2( 0.0, 0.0), uvc + vec2( 0.0, 1.0), uvc + vec2( 1.0, -1.0), uvc + vec2( 1.0, 0.0), uvc + vec2( 1.0, 1.0) ); float factors[9]; float sum = 0.0; for (int i = 0; i < 9; ++i) { factors[i] = lanczos(uvs[i] - fragCoord * size); sum += factors[i]; } for (int i = 0; i < 9; ++i) factors[i] /= sum; vec3 color = vec3(0.0); for (int i = 0; i < 9; ++i) color += texture2D(texture, uvs[i] * invSize).rgb * factors[i]; fragColor = vec4(color, 1.0); } looking-glass-B6/client/renderers/EGL/shader/downscale_linear.frag000066400000000000000000000002561434445012300253750ustar00rootroot00000000000000#version 300 es precision mediump float; in vec2 fragCoord; out vec4 fragColor; uniform sampler2D texture; void main() { fragColor = texture2D(texture, fragCoord); } looking-glass-B6/client/renderers/EGL/shader/ffx_a.h000066400000000000000000005432331434445012300224660ustar00rootroot00000000000000//============================================================================================================================== // // [A] SHADER PORTABILITY 1.20210629 // //============================================================================================================================== // FidelityFX Super Resolution Sample // // Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files(the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and / or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions : // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. //------------------------------------------------------------------------------------------------------------------------------ // MIT LICENSE // =========== // Copyright (c) 2014 Michal Drobot (for concepts used in "FLOAT APPROXIMATIONS"). // ----------- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // ----------- // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the // Software. // ----------- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. //------------------------------------------------------------------------------------------------------------------------------ // ABOUT // ===== // Common central point for high-level shading language and C portability for various shader headers. //------------------------------------------------------------------------------------------------------------------------------ // DEFINES // ======= // A_CPU ..... Include the CPU related code. // A_GPU ..... Include the GPU related code. // A_GLSL .... Using GLSL. // A_HLSL .... Using HLSL. // A_HLSL_6_2 Using HLSL 6.2 with new 'uint16_t' and related types (requires '-enable-16bit-types'). // A_NO_16_BIT_CAST Don't use instructions that are not availabe in SPIR-V (needed for running A_HLSL_6_2 on Vulkan) // A_GCC ..... Using a GCC compatible compiler (else assume MSVC compatible compiler by default). // ======= // A_BYTE .... Support 8-bit integer. // A_HALF .... Support 16-bit integer and floating point. // A_LONG .... Support 64-bit integer. // A_DUBL .... Support 64-bit floating point. // ======= // A_WAVE .... Support wave-wide operations. //------------------------------------------------------------------------------------------------------------------------------ // To get #include "ffx_a.h" working in GLSL use '#extension GL_GOOGLE_include_directive:require'. //------------------------------------------------------------------------------------------------------------------------------ // SIMPLIFIED TYPE SYSTEM // ====================== // - All ints will be unsigned with exception of when signed is required. // - Type naming simplified and shortened "A<#components>", // - H = 16-bit float (half) // - F = 32-bit float (float) // - D = 64-bit float (double) // - P = 1-bit integer (predicate, not using bool because 'B' is used for byte) // - B = 8-bit integer (byte) // - W = 16-bit integer (word) // - U = 32-bit integer (unsigned) // - L = 64-bit integer (long) // - Using "AS<#components>" for signed when required. //------------------------------------------------------------------------------------------------------------------------------ // TODO // ==== // - Make sure 'ALerp*(a,b,m)' does 'b*m+(-a*m+a)' (2 ops). //------------------------------------------------------------------------------------------------------------------------------ // CHANGE LOG // ========== // 20200914 - Expanded wave ops and prx code. // 20200713 - Added [ZOL] section, fixed serious bugs in sRGB and Rec.709 color conversion code, etc. //============================================================================================================================== //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // COMMON //============================================================================================================================== #define A_2PI 6.28318530718 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // // // CPU // // //============================================================================================================================== #ifdef A_CPU // Supporting user defined overrides. #ifndef A_RESTRICT #define A_RESTRICT __restrict #endif //------------------------------------------------------------------------------------------------------------------------------ #ifndef A_STATIC #define A_STATIC static #endif //------------------------------------------------------------------------------------------------------------------------------ // Same types across CPU and GPU. // Predicate uses 32-bit integer (C friendly bool). typedef uint32_t AP1; typedef float AF1; typedef double AD1; typedef uint8_t AB1; typedef uint16_t AW1; typedef uint32_t AU1; typedef uint64_t AL1; typedef int8_t ASB1; typedef int16_t ASW1; typedef int32_t ASU1; typedef int64_t ASL1; //------------------------------------------------------------------------------------------------------------------------------ #define AD1_(a) ((AD1)(a)) #define AF1_(a) ((AF1)(a)) #define AL1_(a) ((AL1)(a)) #define AU1_(a) ((AU1)(a)) //------------------------------------------------------------------------------------------------------------------------------ #define ASL1_(a) ((ASL1)(a)) #define ASU1_(a) ((ASU1)(a)) //------------------------------------------------------------------------------------------------------------------------------ A_STATIC AU1 AU1_AF1(AF1 a){union{AF1 f;AU1 u;}bits;bits.f=a;return bits.u;} //------------------------------------------------------------------------------------------------------------------------------ #define A_TRUE 1 #define A_FALSE 0 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // // CPU/GPU PORTING // //------------------------------------------------------------------------------------------------------------------------------ // Get CPU and GPU to share all setup code, without duplicate code paths. // This uses a lower-case prefix for special vector constructs. // - In C restrict pointers are used. // - In the shading language, in/inout/out arguments are used. // This depends on the ability to access a vector value in both languages via array syntax (aka color[2]). //============================================================================================================================== //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY //============================================================================================================================== #define retAD2 AD1 *A_RESTRICT #define retAD3 AD1 *A_RESTRICT #define retAD4 AD1 *A_RESTRICT #define retAF2 AF1 *A_RESTRICT #define retAF3 AF1 *A_RESTRICT #define retAF4 AF1 *A_RESTRICT #define retAL2 AL1 *A_RESTRICT #define retAL3 AL1 *A_RESTRICT #define retAL4 AL1 *A_RESTRICT #define retAU2 AU1 *A_RESTRICT #define retAU3 AU1 *A_RESTRICT #define retAU4 AU1 *A_RESTRICT //------------------------------------------------------------------------------------------------------------------------------ #define inAD2 AD1 *A_RESTRICT #define inAD3 AD1 *A_RESTRICT #define inAD4 AD1 *A_RESTRICT #define inAF2 AF1 *A_RESTRICT #define inAF3 AF1 *A_RESTRICT #define inAF4 AF1 *A_RESTRICT #define inAL2 AL1 *A_RESTRICT #define inAL3 AL1 *A_RESTRICT #define inAL4 AL1 *A_RESTRICT #define inAU2 AU1 *A_RESTRICT #define inAU3 AU1 *A_RESTRICT #define inAU4 AU1 *A_RESTRICT //------------------------------------------------------------------------------------------------------------------------------ #define inoutAD2 AD1 *A_RESTRICT #define inoutAD3 AD1 *A_RESTRICT #define inoutAD4 AD1 *A_RESTRICT #define inoutAF2 AF1 *A_RESTRICT #define inoutAF3 AF1 *A_RESTRICT #define inoutAF4 AF1 *A_RESTRICT #define inoutAL2 AL1 *A_RESTRICT #define inoutAL3 AL1 *A_RESTRICT #define inoutAL4 AL1 *A_RESTRICT #define inoutAU2 AU1 *A_RESTRICT #define inoutAU3 AU1 *A_RESTRICT #define inoutAU4 AU1 *A_RESTRICT //------------------------------------------------------------------------------------------------------------------------------ #define outAD2 AD1 *A_RESTRICT #define outAD3 AD1 *A_RESTRICT #define outAD4 AD1 *A_RESTRICT #define outAF2 AF1 *A_RESTRICT #define outAF3 AF1 *A_RESTRICT #define outAF4 AF1 *A_RESTRICT #define outAL2 AL1 *A_RESTRICT #define outAL3 AL1 *A_RESTRICT #define outAL4 AL1 *A_RESTRICT #define outAU2 AU1 *A_RESTRICT #define outAU3 AU1 *A_RESTRICT #define outAU4 AU1 *A_RESTRICT //------------------------------------------------------------------------------------------------------------------------------ #define varAD2(x) AD1 x[2] #define varAD3(x) AD1 x[3] #define varAD4(x) AD1 x[4] #define varAF2(x) AF1 x[2] #define varAF3(x) AF1 x[3] #define varAF4(x) AF1 x[4] #define varAL2(x) AL1 x[2] #define varAL3(x) AL1 x[3] #define varAL4(x) AL1 x[4] #define varAU2(x) AU1 x[2] #define varAU3(x) AU1 x[3] #define varAU4(x) AU1 x[4] //------------------------------------------------------------------------------------------------------------------------------ #define initAD2(x,y) {x,y} #define initAD3(x,y,z) {x,y,z} #define initAD4(x,y,z,w) {x,y,z,w} #define initAF2(x,y) {x,y} #define initAF3(x,y,z) {x,y,z} #define initAF4(x,y,z,w) {x,y,z,w} #define initAL2(x,y) {x,y} #define initAL3(x,y,z) {x,y,z} #define initAL4(x,y,z,w) {x,y,z,w} #define initAU2(x,y) {x,y} #define initAU3(x,y,z) {x,y,z} #define initAU4(x,y,z,w) {x,y,z,w} //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // SCALAR RETURN OPS //------------------------------------------------------------------------------------------------------------------------------ // TODO // ==== // - Replace transcendentals with manual versions. //============================================================================================================================== #ifdef A_GCC A_STATIC AD1 AAbsD1(AD1 a){return __builtin_fabs(a);} A_STATIC AF1 AAbsF1(AF1 a){return __builtin_fabsf(a);} A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(__builtin_abs(ASU1_(a)));} A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(__builtin_llabs(ASL1_(a)));} #else A_STATIC AD1 AAbsD1(AD1 a){return fabs(a);} A_STATIC AF1 AAbsF1(AF1 a){return fabsf(a);} A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(abs(ASU1_(a)));} A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(labs((long)ASL1_(a)));} #endif //------------------------------------------------------------------------------------------------------------------------------ #ifdef A_GCC A_STATIC AD1 ACosD1(AD1 a){return __builtin_cos(a);} A_STATIC AF1 ACosF1(AF1 a){return __builtin_cosf(a);} #else A_STATIC AD1 ACosD1(AD1 a){return cos(a);} A_STATIC AF1 ACosF1(AF1 a){return cosf(a);} #endif //------------------------------------------------------------------------------------------------------------------------------ A_STATIC AD1 ADotD2(inAD2 a,inAD2 b){return a[0]*b[0]+a[1]*b[1];} A_STATIC AD1 ADotD3(inAD3 a,inAD3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} A_STATIC AD1 ADotD4(inAD4 a,inAD4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} A_STATIC AF1 ADotF2(inAF2 a,inAF2 b){return a[0]*b[0]+a[1]*b[1];} A_STATIC AF1 ADotF3(inAF3 a,inAF3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} A_STATIC AF1 ADotF4(inAF4 a,inAF4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} //------------------------------------------------------------------------------------------------------------------------------ #ifdef A_GCC A_STATIC AD1 AExp2D1(AD1 a){return __builtin_exp2(a);} A_STATIC AF1 AExp2F1(AF1 a){return __builtin_exp2f(a);} #else A_STATIC AD1 AExp2D1(AD1 a){return exp2(a);} A_STATIC AF1 AExp2F1(AF1 a){return exp2f(a);} #endif //------------------------------------------------------------------------------------------------------------------------------ #ifdef A_GCC A_STATIC AD1 AFloorD1(AD1 a){return __builtin_floor(a);} A_STATIC AF1 AFloorF1(AF1 a){return __builtin_floorf(a);} #else A_STATIC AD1 AFloorD1(AD1 a){return floor(a);} A_STATIC AF1 AFloorF1(AF1 a){return floorf(a);} #endif //------------------------------------------------------------------------------------------------------------------------------ A_STATIC AD1 ALerpD1(AD1 a,AD1 b,AD1 c){return b*c+(-a*c+a);} A_STATIC AF1 ALerpF1(AF1 a,AF1 b,AF1 c){return b*c+(-a*c+a);} //------------------------------------------------------------------------------------------------------------------------------ #ifdef A_GCC A_STATIC AD1 ALog2D1(AD1 a){return __builtin_log2(a);} A_STATIC AF1 ALog2F1(AF1 a){return __builtin_log2f(a);} #else A_STATIC AD1 ALog2D1(AD1 a){return log2(a);} A_STATIC AF1 ALog2F1(AF1 a){return log2f(a);} #endif //------------------------------------------------------------------------------------------------------------------------------ A_STATIC AD1 AMaxD1(AD1 a,AD1 b){return a>b?a:b;} A_STATIC AF1 AMaxF1(AF1 a,AF1 b){return a>b?a:b;} A_STATIC AL1 AMaxL1(AL1 a,AL1 b){return a>b?a:b;} A_STATIC AU1 AMaxU1(AU1 a,AU1 b){return a>b?a:b;} //------------------------------------------------------------------------------------------------------------------------------ // These follow the convention that A integer types don't have signage, until they are operated on. A_STATIC AL1 AMaxSL1(AL1 a,AL1 b){return (ASL1_(a)>ASL1_(b))?a:b;} A_STATIC AU1 AMaxSU1(AU1 a,AU1 b){return (ASU1_(a)>ASU1_(b))?a:b;} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC AD1 AMinD1(AD1 a,AD1 b){return a>ASL1_(b));} A_STATIC AU1 AShrSU1(AU1 a,AU1 b){return AU1_(ASU1_(a)>>ASU1_(b));} //------------------------------------------------------------------------------------------------------------------------------ #ifdef A_GCC A_STATIC AD1 ASinD1(AD1 a){return __builtin_sin(a);} A_STATIC AF1 ASinF1(AF1 a){return __builtin_sinf(a);} #else A_STATIC AD1 ASinD1(AD1 a){return sin(a);} A_STATIC AF1 ASinF1(AF1 a){return sinf(a);} #endif //------------------------------------------------------------------------------------------------------------------------------ #ifdef A_GCC A_STATIC AD1 ASqrtD1(AD1 a){return __builtin_sqrt(a);} A_STATIC AF1 ASqrtF1(AF1 a){return __builtin_sqrtf(a);} #else A_STATIC AD1 ASqrtD1(AD1 a){return sqrt(a);} A_STATIC AF1 ASqrtF1(AF1 a){return sqrtf(a);} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // SCALAR RETURN OPS - DEPENDENT //============================================================================================================================== A_STATIC AD1 AClampD1(AD1 x,AD1 n,AD1 m){return AMaxD1(n,AMinD1(x,m));} A_STATIC AF1 AClampF1(AF1 x,AF1 n,AF1 m){return AMaxF1(n,AMinF1(x,m));} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC AD1 AFractD1(AD1 a){return a-AFloorD1(a);} A_STATIC AF1 AFractF1(AF1 a){return a-AFloorF1(a);} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC AD1 APowD1(AD1 a,AD1 b){return AExp2D1(b*ALog2D1(a));} A_STATIC AF1 APowF1(AF1 a,AF1 b){return AExp2F1(b*ALog2F1(a));} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC AD1 ARsqD1(AD1 a){return ARcpD1(ASqrtD1(a));} A_STATIC AF1 ARsqF1(AF1 a){return ARcpF1(ASqrtF1(a));} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC AD1 ASatD1(AD1 a){return AMinD1(1.0,AMaxD1(0.0,a));} A_STATIC AF1 ASatF1(AF1 a){return AMinF1(1.0f,AMaxF1(0.0f,a));} //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // VECTOR OPS //------------------------------------------------------------------------------------------------------------------------------ // These are added as needed for production or prototyping, so not necessarily a complete set. // They follow a convention of taking in a destination and also returning the destination value to increase utility. //============================================================================================================================== A_STATIC retAD2 opAAbsD2(outAD2 d,inAD2 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);return d;} A_STATIC retAD3 opAAbsD3(outAD3 d,inAD3 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);return d;} A_STATIC retAD4 opAAbsD4(outAD4 d,inAD4 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);d[3]=AAbsD1(a[3]);return d;} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC retAF2 opAAbsF2(outAF2 d,inAF2 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);return d;} A_STATIC retAF3 opAAbsF3(outAF3 d,inAF3 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);return d;} A_STATIC retAF4 opAAbsF4(outAF4 d,inAF4 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);d[3]=AAbsF1(a[3]);return d;} //============================================================================================================================== A_STATIC retAD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} A_STATIC retAD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} A_STATIC retAD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC retAF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} A_STATIC retAF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} A_STATIC retAF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} //============================================================================================================================== A_STATIC retAD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} A_STATIC retAD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} A_STATIC retAD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC retAF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} A_STATIC retAF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} A_STATIC retAF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} //============================================================================================================================== A_STATIC retAD2 opACpyD2(outAD2 d,inAD2 a){d[0]=a[0];d[1]=a[1];return d;} A_STATIC retAD3 opACpyD3(outAD3 d,inAD3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} A_STATIC retAD4 opACpyD4(outAD4 d,inAD4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC retAF2 opACpyF2(outAF2 d,inAF2 a){d[0]=a[0];d[1]=a[1];return d;} A_STATIC retAF3 opACpyF3(outAF3 d,inAF3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} A_STATIC retAF4 opACpyF4(outAF4 d,inAF4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} //============================================================================================================================== A_STATIC retAD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);return d;} A_STATIC retAD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);return d;} A_STATIC retAD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);d[3]=ALerpD1(a[3],b[3],c[3]);return d;} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC retAF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);return d;} A_STATIC retAF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);return d;} A_STATIC retAF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);d[3]=ALerpF1(a[3],b[3],c[3]);return d;} //============================================================================================================================== A_STATIC retAD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);return d;} A_STATIC retAD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);return d;} A_STATIC retAD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);d[3]=ALerpD1(a[3],b[3],c);return d;} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC retAF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);return d;} A_STATIC retAF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);return d;} A_STATIC retAF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);d[3]=ALerpF1(a[3],b[3],c);return d;} //============================================================================================================================== A_STATIC retAD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);return d;} A_STATIC retAD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);return d;} A_STATIC retAD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);d[3]=AMaxD1(a[3],b[3]);return d;} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC retAF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);return d;} A_STATIC retAF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);return d;} A_STATIC retAF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);d[3]=AMaxF1(a[3],b[3]);return d;} //============================================================================================================================== A_STATIC retAD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);return d;} A_STATIC retAD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);return d;} A_STATIC retAD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);d[3]=AMinD1(a[3],b[3]);return d;} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC retAF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);return d;} A_STATIC retAF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);return d;} A_STATIC retAF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);d[3]=AMinF1(a[3],b[3]);return d;} //============================================================================================================================== A_STATIC retAD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} A_STATIC retAD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} A_STATIC retAD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC retAF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} A_STATIC retAF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} A_STATIC retAF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} //============================================================================================================================== A_STATIC retAD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} A_STATIC retAD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} A_STATIC retAD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC retAF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} A_STATIC retAF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} A_STATIC retAF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} //============================================================================================================================== A_STATIC retAD2 opANegD2(outAD2 d,inAD2 a){d[0]=-a[0];d[1]=-a[1];return d;} A_STATIC retAD3 opANegD3(outAD3 d,inAD3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} A_STATIC retAD4 opANegD4(outAD4 d,inAD4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC retAF2 opANegF2(outAF2 d,inAF2 a){d[0]=-a[0];d[1]=-a[1];return d;} A_STATIC retAF3 opANegF3(outAF3 d,inAF3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} A_STATIC retAF4 opANegF4(outAF4 d,inAF4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} //============================================================================================================================== A_STATIC retAD2 opARcpD2(outAD2 d,inAD2 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);return d;} A_STATIC retAD3 opARcpD3(outAD3 d,inAD3 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);return d;} A_STATIC retAD4 opARcpD4(outAD4 d,inAD4 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);d[3]=ARcpD1(a[3]);return d;} //------------------------------------------------------------------------------------------------------------------------------ A_STATIC retAF2 opARcpF2(outAF2 d,inAF2 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);return d;} A_STATIC retAF3 opARcpF3(outAF3 d,inAF3 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);return d;} A_STATIC retAF4 opARcpF4(outAF4 d,inAF4 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);d[3]=ARcpF1(a[3]);return d;} //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // HALF FLOAT PACKING //============================================================================================================================== // Convert float to half (in lower 16-bits of output). // Same fast technique as documented here: ftp://ftp.fox-toolkit.org/pub/fasthalffloatconversion.pdf // Supports denormals. // Conversion rules are to make computations possibly "safer" on the GPU, // -INF & -NaN -> -65504 // +INF & +NaN -> +65504 A_STATIC AU1 AU1_AH1_AF1(AF1 f){ static AW1 base[512]={ 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0001,0x0002,0x0004,0x0008,0x0010,0x0020,0x0040,0x0080,0x0100, 0x0200,0x0400,0x0800,0x0c00,0x1000,0x1400,0x1800,0x1c00,0x2000,0x2400,0x2800,0x2c00,0x3000,0x3400,0x3800,0x3c00, 0x4000,0x4400,0x4800,0x4c00,0x5000,0x5400,0x5800,0x5c00,0x6000,0x6400,0x6800,0x6c00,0x7000,0x7400,0x7800,0x7bff, 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8001,0x8002,0x8004,0x8008,0x8010,0x8020,0x8040,0x8080,0x8100, 0x8200,0x8400,0x8800,0x8c00,0x9000,0x9400,0x9800,0x9c00,0xa000,0xa400,0xa800,0xac00,0xb000,0xb400,0xb800,0xbc00, 0xc000,0xc400,0xc800,0xcc00,0xd000,0xd400,0xd800,0xdc00,0xe000,0xe400,0xe800,0xec00,0xf000,0xf400,0xf800,0xfbff, 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff}; static AB1 shift[512]={ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18}; union{AF1 f;AU1 u;}bits;bits.f=f;AU1 u=bits.u;AU1 i=u>>23;return (AU1)(base[i])+((u&0x7fffff)>>shift[i]);} //------------------------------------------------------------------------------------------------------------------------------ // Used to output packed constant. A_STATIC AU1 AU1_AH2_AF2(inAF2 a){return AU1_AH1_AF1(a[0])+(AU1_AH1_AF1(a[1])<<16);} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // // // GLSL // // //============================================================================================================================== #if defined(A_GLSL) && defined(A_GPU) #ifndef A_SKIP_EXT #ifdef A_HALF #extension GL_EXT_shader_16bit_storage:require #extension GL_EXT_shader_explicit_arithmetic_types:require #endif //------------------------------------------------------------------------------------------------------------------------------ #ifdef A_LONG #extension GL_ARB_gpu_shader_int64:require #extension GL_NV_shader_atomic_int64:require #endif //------------------------------------------------------------------------------------------------------------------------------ #ifdef A_WAVE #extension GL_KHR_shader_subgroup_arithmetic:require #extension GL_KHR_shader_subgroup_ballot:require #extension GL_KHR_shader_subgroup_quad:require #extension GL_KHR_shader_subgroup_shuffle:require #endif #endif //============================================================================================================================== #define AP1 bool #define AP2 bvec2 #define AP3 bvec3 #define AP4 bvec4 //------------------------------------------------------------------------------------------------------------------------------ #define AF1 float #define AF2 vec2 #define AF3 vec3 #define AF4 vec4 //------------------------------------------------------------------------------------------------------------------------------ #define AU1 uint #define AU2 uvec2 #define AU3 uvec3 #define AU4 uvec4 //------------------------------------------------------------------------------------------------------------------------------ #define ASU1 int #define ASU2 ivec2 #define ASU3 ivec3 #define ASU4 ivec4 //============================================================================================================================== #define AF1_AU1(x) uintBitsToFloat(AU1(x)) #define AF2_AU2(x) uintBitsToFloat(AU2(x)) #define AF3_AU3(x) uintBitsToFloat(AU3(x)) #define AF4_AU4(x) uintBitsToFloat(AU4(x)) //------------------------------------------------------------------------------------------------------------------------------ #define AU1_AF1(x) floatBitsToUint(AF1(x)) #define AU2_AF2(x) floatBitsToUint(AF2(x)) #define AU3_AF3(x) floatBitsToUint(AF3(x)) #define AU4_AF4(x) floatBitsToUint(AF4(x)) //------------------------------------------------------------------------------------------------------------------------------ AU1 AU1_AH1_AF1_x(AF1 a){return packHalf2x16(AF2(a,0.0));} #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) //------------------------------------------------------------------------------------------------------------------------------ #define AU1_AH2_AF2 packHalf2x16 #define AU1_AW2Unorm_AF2 packUnorm2x16 #define AU1_AB4Unorm_AF4 packUnorm4x8 //------------------------------------------------------------------------------------------------------------------------------ #define AF2_AH2_AU1 unpackHalf2x16 #define AF2_AW2Unorm_AU1 unpackUnorm2x16 #define AF4_AB4Unorm_AU1 unpackUnorm4x8 //============================================================================================================================== AF1 AF1_x(AF1 a){return AF1(a);} AF2 AF2_x(AF1 a){return AF2(a,a);} AF3 AF3_x(AF1 a){return AF3(a,a,a);} AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} #define AF1_(a) AF1_x(AF1(a)) #define AF2_(a) AF2_x(AF1(a)) #define AF3_(a) AF3_x(AF1(a)) #define AF4_(a) AF4_x(AF1(a)) //------------------------------------------------------------------------------------------------------------------------------ AU1 AU1_x(AU1 a){return AU1(a);} AU2 AU2_x(AU1 a){return AU2(a,a);} AU3 AU3_x(AU1 a){return AU3(a,a,a);} AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} #define AU1_(a) AU1_x(AU1(a)) #define AU2_(a) AU2_x(AU1(a)) #define AU3_(a) AU3_x(AU1(a)) #define AU4_(a) AU4_x(AU1(a)) //============================================================================================================================== AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} //------------------------------------------------------------------------------------------------------------------------------ AU1 ABfe(AU1 src,AU1 off,AU1 bits){return bitfieldExtract(src,ASU1(off),ASU1(bits));} AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} // Proxy for V_BFI_B32 where the 'mask' is set as 'bits', 'mask=(1<>ASU1(b));} AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // GLSL BYTE //============================================================================================================================== #ifdef A_BYTE #define AB1 uint8_t #define AB2 u8vec2 #define AB3 u8vec3 #define AB4 u8vec4 //------------------------------------------------------------------------------------------------------------------------------ #define ASB1 int8_t #define ASB2 i8vec2 #define ASB3 i8vec3 #define ASB4 i8vec4 //------------------------------------------------------------------------------------------------------------------------------ AB1 AB1_x(AB1 a){return AB1(a);} AB2 AB2_x(AB1 a){return AB2(a,a);} AB3 AB3_x(AB1 a){return AB3(a,a,a);} AB4 AB4_x(AB1 a){return AB4(a,a,a,a);} #define AB1_(a) AB1_x(AB1(a)) #define AB2_(a) AB2_x(AB1(a)) #define AB3_(a) AB3_x(AB1(a)) #define AB4_(a) AB4_x(AB1(a)) #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // GLSL HALF //============================================================================================================================== #ifdef A_HALF #define AH1 float16_t #define AH2 f16vec2 #define AH3 f16vec3 #define AH4 f16vec4 //------------------------------------------------------------------------------------------------------------------------------ #define AW1 uint16_t #define AW2 u16vec2 #define AW3 u16vec3 #define AW4 u16vec4 //------------------------------------------------------------------------------------------------------------------------------ #define ASW1 int16_t #define ASW2 i16vec2 #define ASW3 i16vec3 #define ASW4 i16vec4 //============================================================================================================================== #define AH2_AU1(x) unpackFloat2x16(AU1(x)) AH4 AH4_AU2_x(AU2 x){return AH4(unpackFloat2x16(x.x),unpackFloat2x16(x.y));} #define AH4_AU2(x) AH4_AU2_x(AU2(x)) #define AW2_AU1(x) unpackUint2x16(AU1(x)) #define AW4_AU2(x) unpackUint4x16(pack64(AU2(x))) //------------------------------------------------------------------------------------------------------------------------------ #define AU1_AH2(x) packFloat2x16(AH2(x)) AU2 AU2_AH4_x(AH4 x){return AU2(packFloat2x16(x.xy),packFloat2x16(x.zw));} #define AU2_AH4(x) AU2_AH4_x(AH4(x)) #define AU1_AW2(x) packUint2x16(AW2(x)) #define AU2_AW4(x) unpack32(packUint4x16(AW4(x))) //============================================================================================================================== #define AW1_AH1(x) halfBitsToUint16(AH1(x)) #define AW2_AH2(x) halfBitsToUint16(AH2(x)) #define AW3_AH3(x) halfBitsToUint16(AH3(x)) #define AW4_AH4(x) halfBitsToUint16(AH4(x)) //------------------------------------------------------------------------------------------------------------------------------ #define AH1_AW1(x) uint16BitsToHalf(AW1(x)) #define AH2_AW2(x) uint16BitsToHalf(AW2(x)) #define AH3_AW3(x) uint16BitsToHalf(AW3(x)) #define AH4_AW4(x) uint16BitsToHalf(AW4(x)) //============================================================================================================================== AH1 AH1_x(AH1 a){return AH1(a);} AH2 AH2_x(AH1 a){return AH2(a,a);} AH3 AH3_x(AH1 a){return AH3(a,a,a);} AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} #define AH1_(a) AH1_x(AH1(a)) #define AH2_(a) AH2_x(AH1(a)) #define AH3_(a) AH3_x(AH1(a)) #define AH4_(a) AH4_x(AH1(a)) //------------------------------------------------------------------------------------------------------------------------------ AW1 AW1_x(AW1 a){return AW1(a);} AW2 AW2_x(AW1 a){return AW2(a,a);} AW3 AW3_x(AW1 a){return AW3(a,a,a);} AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} #define AW1_(a) AW1_x(AW1(a)) #define AW2_(a) AW2_x(AW1(a)) #define AW3_(a) AW3_x(AW1(a)) #define AW4_(a) AW4_x(AW1(a)) //============================================================================================================================== AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} //------------------------------------------------------------------------------------------------------------------------------ AH1 AClampH1(AH1 x,AH1 n,AH1 m){return clamp(x,n,m);} AH2 AClampH2(AH2 x,AH2 n,AH2 m){return clamp(x,n,m);} AH3 AClampH3(AH3 x,AH3 n,AH3 m){return clamp(x,n,m);} AH4 AClampH4(AH4 x,AH4 n,AH4 m){return clamp(x,n,m);} //------------------------------------------------------------------------------------------------------------------------------ AH1 AFractH1(AH1 x){return fract(x);} AH2 AFractH2(AH2 x){return fract(x);} AH3 AFractH3(AH3 x){return fract(x);} AH4 AFractH4(AH4 x){return fract(x);} //------------------------------------------------------------------------------------------------------------------------------ AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return mix(x,y,a);} AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return mix(x,y,a);} AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return mix(x,y,a);} AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return mix(x,y,a);} //------------------------------------------------------------------------------------------------------------------------------ // No packed version of max3. AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} //------------------------------------------------------------------------------------------------------------------------------ AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} //------------------------------------------------------------------------------------------------------------------------------ // No packed version of min3. AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} //------------------------------------------------------------------------------------------------------------------------------ AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} //------------------------------------------------------------------------------------------------------------------------------ AH1 ARcpH1(AH1 x){return AH1_(1.0)/x;} AH2 ARcpH2(AH2 x){return AH2_(1.0)/x;} AH3 ARcpH3(AH3 x){return AH3_(1.0)/x;} AH4 ARcpH4(AH4 x){return AH4_(1.0)/x;} //------------------------------------------------------------------------------------------------------------------------------ AH1 ARsqH1(AH1 x){return AH1_(1.0)/sqrt(x);} AH2 ARsqH2(AH2 x){return AH2_(1.0)/sqrt(x);} AH3 ARsqH3(AH3 x){return AH3_(1.0)/sqrt(x);} AH4 ARsqH4(AH4 x){return AH4_(1.0)/sqrt(x);} //------------------------------------------------------------------------------------------------------------------------------ AH1 ASatH1(AH1 x){return clamp(x,AH1_(0.0),AH1_(1.0));} AH2 ASatH2(AH2 x){return clamp(x,AH2_(0.0),AH2_(1.0));} AH3 ASatH3(AH3 x){return clamp(x,AH3_(0.0),AH3_(1.0));} AH4 ASatH4(AH4 x){return clamp(x,AH4_(0.0),AH4_(1.0));} //------------------------------------------------------------------------------------------------------------------------------ AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // GLSL DOUBLE //============================================================================================================================== #ifdef A_DUBL #define AD1 double #define AD2 dvec2 #define AD3 dvec3 #define AD4 dvec4 //------------------------------------------------------------------------------------------------------------------------------ AD1 AD1_x(AD1 a){return AD1(a);} AD2 AD2_x(AD1 a){return AD2(a,a);} AD3 AD3_x(AD1 a){return AD3(a,a,a);} AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} #define AD1_(a) AD1_x(AD1(a)) #define AD2_(a) AD2_x(AD1(a)) #define AD3_(a) AD3_x(AD1(a)) #define AD4_(a) AD4_x(AD1(a)) //============================================================================================================================== AD1 AFractD1(AD1 x){return fract(x);} AD2 AFractD2(AD2 x){return fract(x);} AD3 AFractD3(AD3 x){return fract(x);} AD4 AFractD4(AD4 x){return fract(x);} //------------------------------------------------------------------------------------------------------------------------------ AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return mix(x,y,a);} AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return mix(x,y,a);} AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return mix(x,y,a);} AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return mix(x,y,a);} //------------------------------------------------------------------------------------------------------------------------------ AD1 ARcpD1(AD1 x){return AD1_(1.0)/x;} AD2 ARcpD2(AD2 x){return AD2_(1.0)/x;} AD3 ARcpD3(AD3 x){return AD3_(1.0)/x;} AD4 ARcpD4(AD4 x){return AD4_(1.0)/x;} //------------------------------------------------------------------------------------------------------------------------------ AD1 ARsqD1(AD1 x){return AD1_(1.0)/sqrt(x);} AD2 ARsqD2(AD2 x){return AD2_(1.0)/sqrt(x);} AD3 ARsqD3(AD3 x){return AD3_(1.0)/sqrt(x);} AD4 ARsqD4(AD4 x){return AD4_(1.0)/sqrt(x);} //------------------------------------------------------------------------------------------------------------------------------ AD1 ASatD1(AD1 x){return clamp(x,AD1_(0.0),AD1_(1.0));} AD2 ASatD2(AD2 x){return clamp(x,AD2_(0.0),AD2_(1.0));} AD3 ASatD3(AD3 x){return clamp(x,AD3_(0.0),AD3_(1.0));} AD4 ASatD4(AD4 x){return clamp(x,AD4_(0.0),AD4_(1.0));} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // GLSL LONG //============================================================================================================================== #ifdef A_LONG #define AL1 uint64_t #define AL2 u64vec2 #define AL3 u64vec3 #define AL4 u64vec4 //------------------------------------------------------------------------------------------------------------------------------ #define ASL1 int64_t #define ASL2 i64vec2 #define ASL3 i64vec3 #define ASL4 i64vec4 //------------------------------------------------------------------------------------------------------------------------------ #define AL1_AU2(x) packUint2x32(AU2(x)) #define AU2_AL1(x) unpackUint2x32(AL1(x)) //------------------------------------------------------------------------------------------------------------------------------ AL1 AL1_x(AL1 a){return AL1(a);} AL2 AL2_x(AL1 a){return AL2(a,a);} AL3 AL3_x(AL1 a){return AL3(a,a,a);} AL4 AL4_x(AL1 a){return AL4(a,a,a,a);} #define AL1_(a) AL1_x(AL1(a)) #define AL2_(a) AL2_x(AL1(a)) #define AL3_(a) AL3_x(AL1(a)) #define AL4_(a) AL4_x(AL1(a)) //============================================================================================================================== AL1 AAbsSL1(AL1 a){return AL1(abs(ASL1(a)));} AL2 AAbsSL2(AL2 a){return AL2(abs(ASL2(a)));} AL3 AAbsSL3(AL3 a){return AL3(abs(ASL3(a)));} AL4 AAbsSL4(AL4 a){return AL4(abs(ASL4(a)));} //------------------------------------------------------------------------------------------------------------------------------ AL1 AMaxSL1(AL1 a,AL1 b){return AL1(max(ASU1(a),ASU1(b)));} AL2 AMaxSL2(AL2 a,AL2 b){return AL2(max(ASU2(a),ASU2(b)));} AL3 AMaxSL3(AL3 a,AL3 b){return AL3(max(ASU3(a),ASU3(b)));} AL4 AMaxSL4(AL4 a,AL4 b){return AL4(max(ASU4(a),ASU4(b)));} //------------------------------------------------------------------------------------------------------------------------------ AL1 AMinSL1(AL1 a,AL1 b){return AL1(min(ASU1(a),ASU1(b)));} AL2 AMinSL2(AL2 a,AL2 b){return AL2(min(ASU2(a),ASU2(b)));} AL3 AMinSL3(AL3 a,AL3 b){return AL3(min(ASU3(a),ASU3(b)));} AL4 AMinSL4(AL4 a,AL4 b){return AL4(min(ASU4(a),ASU4(b)));} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // WAVE OPERATIONS //============================================================================================================================== #ifdef A_WAVE // Where 'x' must be a compile time literal. AF1 AWaveXorF1(AF1 v,AU1 x){return subgroupShuffleXor(v,x);} AF2 AWaveXorF2(AF2 v,AU1 x){return subgroupShuffleXor(v,x);} AF3 AWaveXorF3(AF3 v,AU1 x){return subgroupShuffleXor(v,x);} AF4 AWaveXorF4(AF4 v,AU1 x){return subgroupShuffleXor(v,x);} AU1 AWaveXorU1(AU1 v,AU1 x){return subgroupShuffleXor(v,x);} AU2 AWaveXorU2(AU2 v,AU1 x){return subgroupShuffleXor(v,x);} AU3 AWaveXorU3(AU3 v,AU1 x){return subgroupShuffleXor(v,x);} AU4 AWaveXorU4(AU4 v,AU1 x){return subgroupShuffleXor(v,x);} //------------------------------------------------------------------------------------------------------------------------------ #ifdef A_HALF AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(subgroupShuffleXor(AU1_AH2(v),x));} AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(subgroupShuffleXor(AU2_AH4(v),x));} AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(subgroupShuffleXor(AU1_AW2(v),x));} AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU2(subgroupShuffleXor(AU2_AW4(v),x));} #endif #endif //============================================================================================================================== #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // // // HLSL // // //============================================================================================================================== #if defined(A_HLSL) && defined(A_GPU) #ifdef A_HLSL_6_2 #define AP1 bool #define AP2 bool2 #define AP3 bool3 #define AP4 bool4 //------------------------------------------------------------------------------------------------------------------------------ #define AF1 float32_t #define AF2 float32_t2 #define AF3 float32_t3 #define AF4 float32_t4 //------------------------------------------------------------------------------------------------------------------------------ #define AU1 uint32_t #define AU2 uint32_t2 #define AU3 uint32_t3 #define AU4 uint32_t4 //------------------------------------------------------------------------------------------------------------------------------ #define ASU1 int32_t #define ASU2 int32_t2 #define ASU3 int32_t3 #define ASU4 int32_t4 #else #define AP1 bool #define AP2 bool2 #define AP3 bool3 #define AP4 bool4 //------------------------------------------------------------------------------------------------------------------------------ #define AF1 float #define AF2 float2 #define AF3 float3 #define AF4 float4 //------------------------------------------------------------------------------------------------------------------------------ #define AU1 uint #define AU2 uint2 #define AU3 uint3 #define AU4 uint4 //------------------------------------------------------------------------------------------------------------------------------ #define ASU1 int #define ASU2 int2 #define ASU3 int3 #define ASU4 int4 #endif //============================================================================================================================== #define AF1_AU1(x) asfloat(AU1(x)) #define AF2_AU2(x) asfloat(AU2(x)) #define AF3_AU3(x) asfloat(AU3(x)) #define AF4_AU4(x) asfloat(AU4(x)) //------------------------------------------------------------------------------------------------------------------------------ #define AU1_AF1(x) asuint(AF1(x)) #define AU2_AF2(x) asuint(AF2(x)) #define AU3_AF3(x) asuint(AF3(x)) #define AU4_AF4(x) asuint(AF4(x)) //------------------------------------------------------------------------------------------------------------------------------ AU1 AU1_AH1_AF1_x(AF1 a){return f32tof16(a);} #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) //------------------------------------------------------------------------------------------------------------------------------ AU1 AU1_AH2_AF2_x(AF2 a){return f32tof16(a.x)|(f32tof16(a.y)<<16);} #define AU1_AH2_AF2(a) AU1_AH2_AF2_x(AF2(a)) #define AU1_AB4Unorm_AF4(x) D3DCOLORtoUBYTE4(AF4(x)) //------------------------------------------------------------------------------------------------------------------------------ AF2 AF2_AH2_AU1_x(AU1 x){return AF2(f16tof32(x&0xFFFF),f16tof32(x>>16));} #define AF2_AH2_AU1(x) AF2_AH2_AU1_x(AU1(x)) //============================================================================================================================== AF1 AF1_x(AF1 a){return AF1(a);} AF2 AF2_x(AF1 a){return AF2(a,a);} AF3 AF3_x(AF1 a){return AF3(a,a,a);} AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} #define AF1_(a) AF1_x(AF1(a)) #define AF2_(a) AF2_x(AF1(a)) #define AF3_(a) AF3_x(AF1(a)) #define AF4_(a) AF4_x(AF1(a)) //------------------------------------------------------------------------------------------------------------------------------ AU1 AU1_x(AU1 a){return AU1(a);} AU2 AU2_x(AU1 a){return AU2(a,a);} AU3 AU3_x(AU1 a){return AU3(a,a,a);} AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} #define AU1_(a) AU1_x(AU1(a)) #define AU2_(a) AU2_x(AU1(a)) #define AU3_(a) AU3_x(AU1(a)) #define AU4_(a) AU4_x(AU1(a)) //============================================================================================================================== AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} //------------------------------------------------------------------------------------------------------------------------------ AU1 ABfe(AU1 src,AU1 off,AU1 bits){AU1 mask=(1u<>off)&mask;} AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} AU1 ABfiM(AU1 src,AU1 ins,AU1 bits){AU1 mask=(1u<>ASU1(b));} AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // HLSL BYTE //============================================================================================================================== #ifdef A_BYTE #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // HLSL HALF //============================================================================================================================== #ifdef A_HALF #ifdef A_HLSL_6_2 #define AH1 float16_t #define AH2 float16_t2 #define AH3 float16_t3 #define AH4 float16_t4 //------------------------------------------------------------------------------------------------------------------------------ #define AW1 uint16_t #define AW2 uint16_t2 #define AW3 uint16_t3 #define AW4 uint16_t4 //------------------------------------------------------------------------------------------------------------------------------ #define ASW1 int16_t #define ASW2 int16_t2 #define ASW3 int16_t3 #define ASW4 int16_t4 #else #define AH1 min16float #define AH2 min16float2 #define AH3 min16float3 #define AH4 min16float4 //------------------------------------------------------------------------------------------------------------------------------ #define AW1 min16uint #define AW2 min16uint2 #define AW3 min16uint3 #define AW4 min16uint4 //------------------------------------------------------------------------------------------------------------------------------ #define ASW1 min16int #define ASW2 min16int2 #define ASW3 min16int3 #define ASW4 min16int4 #endif //============================================================================================================================== // Need to use manual unpack to get optimal execution (don't use packed types in buffers directly). // Unpack requires this pattern: https://gpuopen.com/first-steps-implementing-fp16/ AH2 AH2_AU1_x(AU1 x){AF2 t=f16tof32(AU2(x&0xFFFF,x>>16));return AH2(t);} AH4 AH4_AU2_x(AU2 x){return AH4(AH2_AU1_x(x.x),AH2_AU1_x(x.y));} AW2 AW2_AU1_x(AU1 x){AU2 t=AU2(x&0xFFFF,x>>16);return AW2(t);} AW4 AW4_AU2_x(AU2 x){return AW4(AW2_AU1_x(x.x),AW2_AU1_x(x.y));} #define AH2_AU1(x) AH2_AU1_x(AU1(x)) #define AH4_AU2(x) AH4_AU2_x(AU2(x)) #define AW2_AU1(x) AW2_AU1_x(AU1(x)) #define AW4_AU2(x) AW4_AU2_x(AU2(x)) //------------------------------------------------------------------------------------------------------------------------------ AU1 AU1_AH2_x(AH2 x){return f32tof16(x.x)+(f32tof16(x.y)<<16);} AU2 AU2_AH4_x(AH4 x){return AU2(AU1_AH2_x(x.xy),AU1_AH2_x(x.zw));} AU1 AU1_AW2_x(AW2 x){return AU1(x.x)+(AU1(x.y)<<16);} AU2 AU2_AW4_x(AW4 x){return AU2(AU1_AW2_x(x.xy),AU1_AW2_x(x.zw));} #define AU1_AH2(x) AU1_AH2_x(AH2(x)) #define AU2_AH4(x) AU2_AH4_x(AH4(x)) #define AU1_AW2(x) AU1_AW2_x(AW2(x)) #define AU2_AW4(x) AU2_AW4_x(AW4(x)) //============================================================================================================================== #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) #define AW1_AH1(x) asuint16(x) #define AW2_AH2(x) asuint16(x) #define AW3_AH3(x) asuint16(x) #define AW4_AH4(x) asuint16(x) #else #define AW1_AH1(a) AW1(f32tof16(AF1(a))) #define AW2_AH2(a) AW2(AW1_AH1((a).x),AW1_AH1((a).y)) #define AW3_AH3(a) AW3(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z)) #define AW4_AH4(a) AW4(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z),AW1_AH1((a).w)) #endif //------------------------------------------------------------------------------------------------------------------------------ #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) #define AH1_AW1(x) asfloat16(x) #define AH2_AW2(x) asfloat16(x) #define AH3_AW3(x) asfloat16(x) #define AH4_AW4(x) asfloat16(x) #else #define AH1_AW1(a) AH1(f16tof32(AU1(a))) #define AH2_AW2(a) AH2(AH1_AW1((a).x),AH1_AW1((a).y)) #define AH3_AW3(a) AH3(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z)) #define AH4_AW4(a) AH4(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z),AH1_AW1((a).w)) #endif //============================================================================================================================== AH1 AH1_x(AH1 a){return AH1(a);} AH2 AH2_x(AH1 a){return AH2(a,a);} AH3 AH3_x(AH1 a){return AH3(a,a,a);} AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} #define AH1_(a) AH1_x(AH1(a)) #define AH2_(a) AH2_x(AH1(a)) #define AH3_(a) AH3_x(AH1(a)) #define AH4_(a) AH4_x(AH1(a)) //------------------------------------------------------------------------------------------------------------------------------ AW1 AW1_x(AW1 a){return AW1(a);} AW2 AW2_x(AW1 a){return AW2(a,a);} AW3 AW3_x(AW1 a){return AW3(a,a,a);} AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} #define AW1_(a) AW1_x(AW1(a)) #define AW2_(a) AW2_x(AW1(a)) #define AW3_(a) AW3_x(AW1(a)) #define AW4_(a) AW4_x(AW1(a)) //============================================================================================================================== AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} //------------------------------------------------------------------------------------------------------------------------------ AH1 AClampH1(AH1 x,AH1 n,AH1 m){return max(n,min(x,m));} AH2 AClampH2(AH2 x,AH2 n,AH2 m){return max(n,min(x,m));} AH3 AClampH3(AH3 x,AH3 n,AH3 m){return max(n,min(x,m));} AH4 AClampH4(AH4 x,AH4 n,AH4 m){return max(n,min(x,m));} //------------------------------------------------------------------------------------------------------------------------------ // V_FRACT_F16 (note DX frac() is different). AH1 AFractH1(AH1 x){return x-floor(x);} AH2 AFractH2(AH2 x){return x-floor(x);} AH3 AFractH3(AH3 x){return x-floor(x);} AH4 AFractH4(AH4 x){return x-floor(x);} //------------------------------------------------------------------------------------------------------------------------------ AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return lerp(x,y,a);} AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return lerp(x,y,a);} AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return lerp(x,y,a);} AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return lerp(x,y,a);} //------------------------------------------------------------------------------------------------------------------------------ AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} //------------------------------------------------------------------------------------------------------------------------------ AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} //------------------------------------------------------------------------------------------------------------------------------ AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} //------------------------------------------------------------------------------------------------------------------------------ AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} //------------------------------------------------------------------------------------------------------------------------------ AH1 ARcpH1(AH1 x){return rcp(x);} AH2 ARcpH2(AH2 x){return rcp(x);} AH3 ARcpH3(AH3 x){return rcp(x);} AH4 ARcpH4(AH4 x){return rcp(x);} //------------------------------------------------------------------------------------------------------------------------------ AH1 ARsqH1(AH1 x){return rsqrt(x);} AH2 ARsqH2(AH2 x){return rsqrt(x);} AH3 ARsqH3(AH3 x){return rsqrt(x);} AH4 ARsqH4(AH4 x){return rsqrt(x);} //------------------------------------------------------------------------------------------------------------------------------ AH1 ASatH1(AH1 x){return saturate(x);} AH2 ASatH2(AH2 x){return saturate(x);} AH3 ASatH3(AH3 x){return saturate(x);} AH4 ASatH4(AH4 x){return saturate(x);} //------------------------------------------------------------------------------------------------------------------------------ AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // HLSL DOUBLE //============================================================================================================================== #ifdef A_DUBL #ifdef A_HLSL_6_2 #define AD1 float64_t #define AD2 float64_t2 #define AD3 float64_t3 #define AD4 float64_t4 #else #define AD1 double #define AD2 double2 #define AD3 double3 #define AD4 double4 #endif //------------------------------------------------------------------------------------------------------------------------------ AD1 AD1_x(AD1 a){return AD1(a);} AD2 AD2_x(AD1 a){return AD2(a,a);} AD3 AD3_x(AD1 a){return AD3(a,a,a);} AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} #define AD1_(a) AD1_x(AD1(a)) #define AD2_(a) AD2_x(AD1(a)) #define AD3_(a) AD3_x(AD1(a)) #define AD4_(a) AD4_x(AD1(a)) //============================================================================================================================== AD1 AFractD1(AD1 a){return a-floor(a);} AD2 AFractD2(AD2 a){return a-floor(a);} AD3 AFractD3(AD3 a){return a-floor(a);} AD4 AFractD4(AD4 a){return a-floor(a);} //------------------------------------------------------------------------------------------------------------------------------ AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return lerp(x,y,a);} AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return lerp(x,y,a);} AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return lerp(x,y,a);} AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return lerp(x,y,a);} //------------------------------------------------------------------------------------------------------------------------------ AD1 ARcpD1(AD1 x){return rcp(x);} AD2 ARcpD2(AD2 x){return rcp(x);} AD3 ARcpD3(AD3 x){return rcp(x);} AD4 ARcpD4(AD4 x){return rcp(x);} //------------------------------------------------------------------------------------------------------------------------------ AD1 ARsqD1(AD1 x){return rsqrt(x);} AD2 ARsqD2(AD2 x){return rsqrt(x);} AD3 ARsqD3(AD3 x){return rsqrt(x);} AD4 ARsqD4(AD4 x){return rsqrt(x);} //------------------------------------------------------------------------------------------------------------------------------ AD1 ASatD1(AD1 x){return saturate(x);} AD2 ASatD2(AD2 x){return saturate(x);} AD3 ASatD3(AD3 x){return saturate(x);} AD4 ASatD4(AD4 x){return saturate(x);} #endif //============================================================================================================================== // HLSL WAVE //============================================================================================================================== #ifdef A_WAVE // Where 'x' must be a compile time literal. AF1 AWaveXorF1(AF1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} AF2 AWaveXorF2(AF2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} AF3 AWaveXorF3(AF3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} AF4 AWaveXorF4(AF4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} AU1 AWaveXorU1(AU1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} AU2 AWaveXorU1(AU2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} AU3 AWaveXorU1(AU3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} AU4 AWaveXorU1(AU4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} //------------------------------------------------------------------------------------------------------------------------------ #ifdef A_HALF AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(WaveReadLaneAt(AU1_AH2(v),WaveGetLaneIndex()^x));} AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(WaveReadLaneAt(AU2_AH4(v),WaveGetLaneIndex()^x));} AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(WaveReadLaneAt(AU1_AW2(v),WaveGetLaneIndex()^x));} AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU1(WaveReadLaneAt(AU1_AW4(v),WaveGetLaneIndex()^x));} #endif #endif //============================================================================================================================== #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // // // GPU COMMON // // //============================================================================================================================== #ifdef A_GPU // Negative and positive infinity. #define A_INFP_F AF1_AU1(0x7f800000u) #define A_INFN_F AF1_AU1(0xff800000u) //------------------------------------------------------------------------------------------------------------------------------ // Copy sign from 's' to positive 'd'. AF1 ACpySgnF1(AF1 d,AF1 s){return AF1_AU1(AU1_AF1(d)|(AU1_AF1(s)&AU1_(0x80000000u)));} AF2 ACpySgnF2(AF2 d,AF2 s){return AF2_AU2(AU2_AF2(d)|(AU2_AF2(s)&AU2_(0x80000000u)));} AF3 ACpySgnF3(AF3 d,AF3 s){return AF3_AU3(AU3_AF3(d)|(AU3_AF3(s)&AU3_(0x80000000u)));} AF4 ACpySgnF4(AF4 d,AF4 s){return AF4_AU4(AU4_AF4(d)|(AU4_AF4(s)&AU4_(0x80000000u)));} //------------------------------------------------------------------------------------------------------------------------------ // Single operation to return (useful to create a mask to use in lerp for branch free logic), // m=NaN := 0 // m>=0 := 0 // m<0 := 1 // Uses the following useful floating point logic, // saturate(+a*(-INF)==-INF) := 0 // saturate( 0*(-INF)== NaN) := 0 // saturate(-a*(-INF)==+INF) := 1 AF1 ASignedF1(AF1 m){return ASatF1(m*AF1_(A_INFN_F));} AF2 ASignedF2(AF2 m){return ASatF2(m*AF2_(A_INFN_F));} AF3 ASignedF3(AF3 m){return ASatF3(m*AF3_(A_INFN_F));} AF4 ASignedF4(AF4 m){return ASatF4(m*AF4_(A_INFN_F));} //------------------------------------------------------------------------------------------------------------------------------ AF1 AGtZeroF1(AF1 m){return ASatF1(m*AF1_(A_INFP_F));} AF2 AGtZeroF2(AF2 m){return ASatF2(m*AF2_(A_INFP_F));} AF3 AGtZeroF3(AF3 m){return ASatF3(m*AF3_(A_INFP_F));} AF4 AGtZeroF4(AF4 m){return ASatF4(m*AF4_(A_INFP_F));} //============================================================================================================================== #ifdef A_HALF #ifdef A_HLSL_6_2 #define A_INFP_H AH1_AW1((uint16_t)0x7c00u) #define A_INFN_H AH1_AW1((uint16_t)0xfc00u) #else #define A_INFP_H AH1_AW1(0x7c00u) #define A_INFN_H AH1_AW1(0xfc00u) #endif //------------------------------------------------------------------------------------------------------------------------------ AH1 ACpySgnH1(AH1 d,AH1 s){return AH1_AW1(AW1_AH1(d)|(AW1_AH1(s)&AW1_(0x8000u)));} AH2 ACpySgnH2(AH2 d,AH2 s){return AH2_AW2(AW2_AH2(d)|(AW2_AH2(s)&AW2_(0x8000u)));} AH3 ACpySgnH3(AH3 d,AH3 s){return AH3_AW3(AW3_AH3(d)|(AW3_AH3(s)&AW3_(0x8000u)));} AH4 ACpySgnH4(AH4 d,AH4 s){return AH4_AW4(AW4_AH4(d)|(AW4_AH4(s)&AW4_(0x8000u)));} //------------------------------------------------------------------------------------------------------------------------------ AH1 ASignedH1(AH1 m){return ASatH1(m*AH1_(A_INFN_H));} AH2 ASignedH2(AH2 m){return ASatH2(m*AH2_(A_INFN_H));} AH3 ASignedH3(AH3 m){return ASatH3(m*AH3_(A_INFN_H));} AH4 ASignedH4(AH4 m){return ASatH4(m*AH4_(A_INFN_H));} //------------------------------------------------------------------------------------------------------------------------------ AH1 AGtZeroH1(AH1 m){return ASatH1(m*AH1_(A_INFP_H));} AH2 AGtZeroH2(AH2 m){return ASatH2(m*AH2_(A_INFP_H));} AH3 AGtZeroH3(AH3 m){return ASatH3(m*AH3_(A_INFP_H));} AH4 AGtZeroH4(AH4 m){return ASatH4(m*AH4_(A_INFP_H));} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // [FIS] FLOAT INTEGER SORTABLE //------------------------------------------------------------------------------------------------------------------------------ // Float to integer sortable. // - If sign bit=0, flip the sign bit (positives). // - If sign bit=1, flip all bits (negatives). // Integer sortable to float. // - If sign bit=1, flip the sign bit (positives). // - If sign bit=0, flip all bits (negatives). // Has nice side effects. // - Larger integers are more positive values. // - Float zero is mapped to center of integers (so clear to integer zero is a nice default for atomic max usage). // Burns 3 ops for conversion {shift,or,xor}. //============================================================================================================================== AU1 AFisToU1(AU1 x){return x^(( AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} AU1 AFisFromU1(AU1 x){return x^((~AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} //------------------------------------------------------------------------------------------------------------------------------ // Just adjust high 16-bit value (useful when upper part of 32-bit word is a 16-bit float value). AU1 AFisToHiU1(AU1 x){return x^(( AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} AU1 AFisFromHiU1(AU1 x){return x^((~AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} //------------------------------------------------------------------------------------------------------------------------------ #ifdef A_HALF AW1 AFisToW1(AW1 x){return x^(( AShrSW1(x,AW1_(15)))|AW1_(0x8000));} AW1 AFisFromW1(AW1 x){return x^((~AShrSW1(x,AW1_(15)))|AW1_(0x8000));} //------------------------------------------------------------------------------------------------------------------------------ AW2 AFisToW2(AW2 x){return x^(( AShrSW2(x,AW2_(15)))|AW2_(0x8000));} AW2 AFisFromW2(AW2 x){return x^((~AShrSW2(x,AW2_(15)))|AW2_(0x8000));} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // [PERM] V_PERM_B32 //------------------------------------------------------------------------------------------------------------------------------ // Support for V_PERM_B32 started in the 3rd generation of GCN. //------------------------------------------------------------------------------------------------------------------------------ // yyyyxxxx - The 'i' input. // 76543210 // ======== // HGFEDCBA - Naming on permutation. //------------------------------------------------------------------------------------------------------------------------------ // TODO // ==== // - Make sure compiler optimizes this. //============================================================================================================================== #ifdef A_HALF AU1 APerm0E0A(AU2 i){return((i.x )&0xffu)|((i.y<<16)&0xff0000u);} AU1 APerm0F0B(AU2 i){return((i.x>> 8)&0xffu)|((i.y<< 8)&0xff0000u);} AU1 APerm0G0C(AU2 i){return((i.x>>16)&0xffu)|((i.y )&0xff0000u);} AU1 APerm0H0D(AU2 i){return((i.x>>24)&0xffu)|((i.y>> 8)&0xff0000u);} //------------------------------------------------------------------------------------------------------------------------------ AU1 APermHGFA(AU2 i){return((i.x )&0x000000ffu)|(i.y&0xffffff00u);} AU1 APermHGFC(AU2 i){return((i.x>>16)&0x000000ffu)|(i.y&0xffffff00u);} AU1 APermHGAE(AU2 i){return((i.x<< 8)&0x0000ff00u)|(i.y&0xffff00ffu);} AU1 APermHGCE(AU2 i){return((i.x>> 8)&0x0000ff00u)|(i.y&0xffff00ffu);} AU1 APermHAFE(AU2 i){return((i.x<<16)&0x00ff0000u)|(i.y&0xff00ffffu);} AU1 APermHCFE(AU2 i){return((i.x )&0x00ff0000u)|(i.y&0xff00ffffu);} AU1 APermAGFE(AU2 i){return((i.x<<24)&0xff000000u)|(i.y&0x00ffffffu);} AU1 APermCGFE(AU2 i){return((i.x<< 8)&0xff000000u)|(i.y&0x00ffffffu);} //------------------------------------------------------------------------------------------------------------------------------ AU1 APermGCEA(AU2 i){return((i.x)&0x00ff00ffu)|((i.y<<8)&0xff00ff00u);} AU1 APermGECA(AU2 i){return(((i.x)&0xffu)|((i.x>>8)&0xff00u)|((i.y<<16)&0xff0000u)|((i.y<<8)&0xff000000u));} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // [BUC] BYTE UNSIGNED CONVERSION //------------------------------------------------------------------------------------------------------------------------------ // Designed to use the optimal conversion, enables the scaling to possibly be factored into other computation. // Works on a range of {0 to A_BUC_<32,16>}, for <32-bit, and 16-bit> respectively. //------------------------------------------------------------------------------------------------------------------------------ // OPCODE NOTES // ============ // GCN does not do UNORM or SNORM for bytes in opcodes. // - V_CVT_F32_UBYTE{0,1,2,3} - Unsigned byte to float. // - V_CVT_PKACC_U8_F32 - Float to unsigned byte (does bit-field insert into 32-bit integer). // V_PERM_B32 does byte packing with ability to zero fill bytes as well. // - Can pull out byte values from two sources, and zero fill upper 8-bits of packed hi and lo. //------------------------------------------------------------------------------------------------------------------------------ // BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U1() - Designed for V_CVT_F32_UBYTE* and V_CVT_PKACCUM_U8_F32 ops. // ==== ===== // 0 : 0 // 1 : 1 // ... // 255 : 255 // : 256 (just outside the encoding range) //------------------------------------------------------------------------------------------------------------------------------ // BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. // ==== ===== // 0 : 0 // 1 : 1/512 // 2 : 1/256 // ... // 64 : 1/8 // 128 : 1/4 // 255 : 255/512 // : 1/2 (just outside the encoding range) //------------------------------------------------------------------------------------------------------------------------------ // OPTIMAL IMPLEMENTATIONS ON AMD ARCHITECTURES // ============================================ // r=ABuc0FromU1(i) // V_CVT_F32_UBYTE0 r,i // -------------------------------------------- // r=ABuc0ToU1(d,i) // V_CVT_PKACCUM_U8_F32 r,i,0,d // -------------------------------------------- // d=ABuc0FromU2(i) // Where 'k0' is an SGPR with 0x0E0A // Where 'k1' is an SGPR with {32768.0} packed into the lower 16-bits // V_PERM_B32 d,i.x,i.y,k0 // V_PK_FMA_F16 d,d,k1.x,0 // -------------------------------------------- // r=ABuc0ToU2(d,i) // Where 'k0' is an SGPR with {1.0/32768.0} packed into the lower 16-bits // Where 'k1' is an SGPR with 0x???? // Where 'k2' is an SGPR with 0x???? // V_PK_FMA_F16 i,i,k0.x,0 // V_PERM_B32 r.x,i,i,k1 // V_PERM_B32 r.y,i,i,k2 //============================================================================================================================== // Peak range for 32-bit and 16-bit operations. #define A_BUC_32 (255.0) #define A_BUC_16 (255.0/512.0) //============================================================================================================================== #if 1 // Designed to be one V_CVT_PKACCUM_U8_F32. // The extra min is required to pattern match to V_CVT_PKACCUM_U8_F32. AU1 ABuc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i),255u) )&(0x000000ffu));} AU1 ABuc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i),255u)<< 8)&(0x0000ff00u));} AU1 ABuc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i),255u)<<16)&(0x00ff0000u));} AU1 ABuc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i),255u)<<24)&(0xff000000u));} //------------------------------------------------------------------------------------------------------------------------------ // Designed to be one V_CVT_F32_UBYTE*. AF1 ABuc0FromU1(AU1 i){return AF1((i )&255u);} AF1 ABuc1FromU1(AU1 i){return AF1((i>> 8)&255u);} AF1 ABuc2FromU1(AU1 i){return AF1((i>>16)&255u);} AF1 ABuc3FromU1(AU1 i){return AF1((i>>24)&255u);} #endif //============================================================================================================================== #ifdef A_HALF // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. AW2 ABuc01ToW2(AH2 x,AH2 y){x*=AH2_(1.0/32768.0);y*=AH2_(1.0/32768.0); return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} //------------------------------------------------------------------------------------------------------------------------------ // Designed for 3 ops to do SOA to AOS and conversion. AU2 ABuc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} AU2 ABuc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} AU2 ABuc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} AU2 ABuc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} //------------------------------------------------------------------------------------------------------------------------------ // Designed for 2 ops to do both AOS to SOA, and conversion. AH2 ABuc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0);} AH2 ABuc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0);} AH2 ABuc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0);} AH2 ABuc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0);} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // [BSC] BYTE SIGNED CONVERSION //------------------------------------------------------------------------------------------------------------------------------ // Similar to [BUC]. // Works on a range of {-/+ A_BSC_<32,16>}, for <32-bit, and 16-bit> respectively. //------------------------------------------------------------------------------------------------------------------------------ // ENCODING (without zero-based encoding) // ======== // 0 = unused (can be used to mean something else) // 1 = lowest value // 128 = exact zero center (zero based encoding // 255 = highest value //------------------------------------------------------------------------------------------------------------------------------ // Zero-based [Zb] flips the MSB bit of the byte (making 128 "exact zero" actually zero). // This is useful if there is a desire for cleared values to decode as zero. //------------------------------------------------------------------------------------------------------------------------------ // BYTE : FLOAT - ABsc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. // ==== ===== // 0 : -127/512 (unused) // 1 : -126/512 // 2 : -125/512 // ... // 128 : 0 // ... // 255 : 127/512 // : 1/4 (just outside the encoding range) //============================================================================================================================== // Peak range for 32-bit and 16-bit operations. #define A_BSC_32 (127.0) #define A_BSC_16 (127.0/512.0) //============================================================================================================================== #if 1 AU1 ABsc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i+128.0),255u) )&(0x000000ffu));} AU1 ABsc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i+128.0),255u)<< 8)&(0x0000ff00u));} AU1 ABsc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i+128.0),255u)<<16)&(0x00ff0000u));} AU1 ABsc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i+128.0),255u)<<24)&(0xff000000u));} //------------------------------------------------------------------------------------------------------------------------------ AU1 ABsc0ToZbU1(AU1 d,AF1 i){return ((d&0xffffff00u)|((min(AU1(trunc(i)+128.0),255u) )&(0x000000ffu)))^0x00000080u;} AU1 ABsc1ToZbU1(AU1 d,AF1 i){return ((d&0xffff00ffu)|((min(AU1(trunc(i)+128.0),255u)<< 8)&(0x0000ff00u)))^0x00008000u;} AU1 ABsc2ToZbU1(AU1 d,AF1 i){return ((d&0xff00ffffu)|((min(AU1(trunc(i)+128.0),255u)<<16)&(0x00ff0000u)))^0x00800000u;} AU1 ABsc3ToZbU1(AU1 d,AF1 i){return ((d&0x00ffffffu)|((min(AU1(trunc(i)+128.0),255u)<<24)&(0xff000000u)))^0x80000000u;} //------------------------------------------------------------------------------------------------------------------------------ AF1 ABsc0FromU1(AU1 i){return AF1((i )&255u)-128.0;} AF1 ABsc1FromU1(AU1 i){return AF1((i>> 8)&255u)-128.0;} AF1 ABsc2FromU1(AU1 i){return AF1((i>>16)&255u)-128.0;} AF1 ABsc3FromU1(AU1 i){return AF1((i>>24)&255u)-128.0;} //------------------------------------------------------------------------------------------------------------------------------ AF1 ABsc0FromZbU1(AU1 i){return AF1(((i )&255u)^0x80u)-128.0;} AF1 ABsc1FromZbU1(AU1 i){return AF1(((i>> 8)&255u)^0x80u)-128.0;} AF1 ABsc2FromZbU1(AU1 i){return AF1(((i>>16)&255u)^0x80u)-128.0;} AF1 ABsc3FromZbU1(AU1 i){return AF1(((i>>24)&255u)^0x80u)-128.0;} #endif //============================================================================================================================== #ifdef A_HALF // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. AW2 ABsc01ToW2(AH2 x,AH2 y){x=x*AH2_(1.0/32768.0)+AH2_(0.25/32768.0);y=y*AH2_(1.0/32768.0)+AH2_(0.25/32768.0); return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} //------------------------------------------------------------------------------------------------------------------------------ AU2 ABsc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} AU2 ABsc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} AU2 ABsc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} AU2 ABsc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} //------------------------------------------------------------------------------------------------------------------------------ AU2 ABsc0ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} AU2 ABsc1ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} AU2 ABsc2ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} AU2 ABsc3ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} //------------------------------------------------------------------------------------------------------------------------------ AH2 ABsc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0)-AH2_(0.25);} AH2 ABsc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0)-AH2_(0.25);} AH2 ABsc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0)-AH2_(0.25);} AH2 ABsc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0)-AH2_(0.25);} //------------------------------------------------------------------------------------------------------------------------------ AH2 ABsc0FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} AH2 ABsc1FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} AH2 ABsc2FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} AH2 ABsc3FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // HALF APPROXIMATIONS //------------------------------------------------------------------------------------------------------------------------------ // These support only positive inputs. // Did not see value yet in specialization for range. // Using quick testing, ended up mostly getting the same "best" approximation for various ranges. // With hardware that can co-execute transcendentals, the value in approximations could be less than expected. // However from a latency perspective, if execution of a transcendental is 4 clk, with no packed support, -> 8 clk total. // And co-execution would require a compiler interleaving a lot of independent work for packed usage. //------------------------------------------------------------------------------------------------------------------------------ // The one Newton Raphson iteration form of rsq() was skipped (requires 6 ops total). // Same with sqrt(), as this could be x*rsq() (7 ops). //============================================================================================================================== #ifdef A_HALF // Minimize squared error across full positive range, 2 ops. // The 0x1de2 based approximation maps {0 to 1} input maps to < 1 output. AH1 APrxLoSqrtH1(AH1 a){return AH1_AW1((AW1_AH1(a)>>AW1_(1))+AW1_(0x1de2));} AH2 APrxLoSqrtH2(AH2 a){return AH2_AW2((AW2_AH2(a)>>AW2_(1))+AW2_(0x1de2));} AH3 APrxLoSqrtH3(AH3 a){return AH3_AW3((AW3_AH3(a)>>AW3_(1))+AW3_(0x1de2));} AH4 APrxLoSqrtH4(AH4 a){return AH4_AW4((AW4_AH4(a)>>AW4_(1))+AW4_(0x1de2));} //------------------------------------------------------------------------------------------------------------------------------ // Lower precision estimation, 1 op. // Minimize squared error across {smallest normal to 16384.0}. AH1 APrxLoRcpH1(AH1 a){return AH1_AW1(AW1_(0x7784)-AW1_AH1(a));} AH2 APrxLoRcpH2(AH2 a){return AH2_AW2(AW2_(0x7784)-AW2_AH2(a));} AH3 APrxLoRcpH3(AH3 a){return AH3_AW3(AW3_(0x7784)-AW3_AH3(a));} AH4 APrxLoRcpH4(AH4 a){return AH4_AW4(AW4_(0x7784)-AW4_AH4(a));} //------------------------------------------------------------------------------------------------------------------------------ // Medium precision estimation, one Newton Raphson iteration, 3 ops. AH1 APrxMedRcpH1(AH1 a){AH1 b=AH1_AW1(AW1_(0x778d)-AW1_AH1(a));return b*(-b*a+AH1_(2.0));} AH2 APrxMedRcpH2(AH2 a){AH2 b=AH2_AW2(AW2_(0x778d)-AW2_AH2(a));return b*(-b*a+AH2_(2.0));} AH3 APrxMedRcpH3(AH3 a){AH3 b=AH3_AW3(AW3_(0x778d)-AW3_AH3(a));return b*(-b*a+AH3_(2.0));} AH4 APrxMedRcpH4(AH4 a){AH4 b=AH4_AW4(AW4_(0x778d)-AW4_AH4(a));return b*(-b*a+AH4_(2.0));} //------------------------------------------------------------------------------------------------------------------------------ // Minimize squared error across {smallest normal to 16384.0}, 2 ops. AH1 APrxLoRsqH1(AH1 a){return AH1_AW1(AW1_(0x59a3)-(AW1_AH1(a)>>AW1_(1)));} AH2 APrxLoRsqH2(AH2 a){return AH2_AW2(AW2_(0x59a3)-(AW2_AH2(a)>>AW2_(1)));} AH3 APrxLoRsqH3(AH3 a){return AH3_AW3(AW3_(0x59a3)-(AW3_AH3(a)>>AW3_(1)));} AH4 APrxLoRsqH4(AH4 a){return AH4_AW4(AW4_(0x59a3)-(AW4_AH4(a)>>AW4_(1)));} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // FLOAT APPROXIMATIONS //------------------------------------------------------------------------------------------------------------------------------ // Michal Drobot has an excellent presentation on these: "Low Level Optimizations For GCN", // - Idea dates back to SGI, then to Quake 3, etc. // - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf // - sqrt(x)=rsqrt(x)*x // - rcp(x)=rsqrt(x)*rsqrt(x) for positive x // - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h //------------------------------------------------------------------------------------------------------------------------------ // These below are from perhaps less complete searching for optimal. // Used FP16 normal range for testing with +4096 32-bit step size for sampling error. // So these match up well with the half approximations. //============================================================================================================================== AF1 APrxLoSqrtF1(AF1 a){return AF1_AU1((AU1_AF1(a)>>AU1_(1))+AU1_(0x1fbc4639));} AF1 APrxLoRcpF1(AF1 a){return AF1_AU1(AU1_(0x7ef07ebb)-AU1_AF1(a));} AF1 APrxMedRcpF1(AF1 a){AF1 b=AF1_AU1(AU1_(0x7ef19fff)-AU1_AF1(a));return b*(-b*a+AF1_(2.0));} AF1 APrxLoRsqF1(AF1 a){return AF1_AU1(AU1_(0x5f347d74)-(AU1_AF1(a)>>AU1_(1)));} //------------------------------------------------------------------------------------------------------------------------------ AF2 APrxLoSqrtF2(AF2 a){return AF2_AU2((AU2_AF2(a)>>AU2_(1))+AU2_(0x1fbc4639));} AF2 APrxLoRcpF2(AF2 a){return AF2_AU2(AU2_(0x7ef07ebb)-AU2_AF2(a));} AF2 APrxMedRcpF2(AF2 a){AF2 b=AF2_AU2(AU2_(0x7ef19fff)-AU2_AF2(a));return b*(-b*a+AF2_(2.0));} AF2 APrxLoRsqF2(AF2 a){return AF2_AU2(AU2_(0x5f347d74)-(AU2_AF2(a)>>AU2_(1)));} //------------------------------------------------------------------------------------------------------------------------------ AF3 APrxLoSqrtF3(AF3 a){return AF3_AU3((AU3_AF3(a)>>AU3_(1))+AU3_(0x1fbc4639));} AF3 APrxLoRcpF3(AF3 a){return AF3_AU3(AU3_(0x7ef07ebb)-AU3_AF3(a));} AF3 APrxMedRcpF3(AF3 a){AF3 b=AF3_AU3(AU3_(0x7ef19fff)-AU3_AF3(a));return b*(-b*a+AF3_(2.0));} AF3 APrxLoRsqF3(AF3 a){return AF3_AU3(AU3_(0x5f347d74)-(AU3_AF3(a)>>AU3_(1)));} //------------------------------------------------------------------------------------------------------------------------------ AF4 APrxLoSqrtF4(AF4 a){return AF4_AU4((AU4_AF4(a)>>AU4_(1))+AU4_(0x1fbc4639));} AF4 APrxLoRcpF4(AF4 a){return AF4_AU4(AU4_(0x7ef07ebb)-AU4_AF4(a));} AF4 APrxMedRcpF4(AF4 a){AF4 b=AF4_AU4(AU4_(0x7ef19fff)-AU4_AF4(a));return b*(-b*a+AF4_(2.0));} AF4 APrxLoRsqF4(AF4 a){return AF4_AU4(AU4_(0x5f347d74)-(AU4_AF4(a)>>AU4_(1)));} //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // PQ APPROXIMATIONS //------------------------------------------------------------------------------------------------------------------------------ // PQ is very close to x^(1/8). The functions below Use the fast float approximation method to do // PQ<~>Gamma2 (4th power and fast 4th root) and PQ<~>Linear (8th power and fast 8th root). Maximum error is ~0.2%. //============================================================================================================================== // Helpers AF1 Quart(AF1 a) { a = a * a; return a * a;} AF1 Oct(AF1 a) { a = a * a; a = a * a; return a * a; } AF2 Quart(AF2 a) { a = a * a; return a * a; } AF2 Oct(AF2 a) { a = a * a; a = a * a; return a * a; } AF3 Quart(AF3 a) { a = a * a; return a * a; } AF3 Oct(AF3 a) { a = a * a; a = a * a; return a * a; } AF4 Quart(AF4 a) { a = a * a; return a * a; } AF4 Oct(AF4 a) { a = a * a; a = a * a; return a * a; } //------------------------------------------------------------------------------------------------------------------------------ AF1 APrxPQToGamma2(AF1 a) { return Quart(a); } AF1 APrxPQToLinear(AF1 a) { return Oct(a); } AF1 APrxLoGamma2ToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); } AF1 APrxMedGamma2ToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); AF1 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } AF1 APrxHighGamma2ToPQ(AF1 a) { return sqrt(sqrt(a)); } AF1 APrxLoLinearToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); } AF1 APrxMedLinearToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); AF1 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } AF1 APrxHighLinearToPQ(AF1 a) { return sqrt(sqrt(sqrt(a))); } //------------------------------------------------------------------------------------------------------------------------------ AF2 APrxPQToGamma2(AF2 a) { return Quart(a); } AF2 APrxPQToLinear(AF2 a) { return Oct(a); } AF2 APrxLoGamma2ToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); } AF2 APrxMedGamma2ToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); AF2 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } AF2 APrxHighGamma2ToPQ(AF2 a) { return sqrt(sqrt(a)); } AF2 APrxLoLinearToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); } AF2 APrxMedLinearToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); AF2 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } AF2 APrxHighLinearToPQ(AF2 a) { return sqrt(sqrt(sqrt(a))); } //------------------------------------------------------------------------------------------------------------------------------ AF3 APrxPQToGamma2(AF3 a) { return Quart(a); } AF3 APrxPQToLinear(AF3 a) { return Oct(a); } AF3 APrxLoGamma2ToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); } AF3 APrxMedGamma2ToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); AF3 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } AF3 APrxHighGamma2ToPQ(AF3 a) { return sqrt(sqrt(a)); } AF3 APrxLoLinearToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); } AF3 APrxMedLinearToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); AF3 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } AF3 APrxHighLinearToPQ(AF3 a) { return sqrt(sqrt(sqrt(a))); } //------------------------------------------------------------------------------------------------------------------------------ AF4 APrxPQToGamma2(AF4 a) { return Quart(a); } AF4 APrxPQToLinear(AF4 a) { return Oct(a); } AF4 APrxLoGamma2ToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); } AF4 APrxMedGamma2ToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); AF4 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } AF4 APrxHighGamma2ToPQ(AF4 a) { return sqrt(sqrt(a)); } AF4 APrxLoLinearToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); } AF4 APrxMedLinearToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); AF4 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } AF4 APrxHighLinearToPQ(AF4 a) { return sqrt(sqrt(sqrt(a))); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // PARABOLIC SIN & COS //------------------------------------------------------------------------------------------------------------------------------ // Approximate answers to transcendental questions. //------------------------------------------------------------------------------------------------------------------------------ //============================================================================================================================== #if 1 // Valid input range is {-1 to 1} representing {0 to 2 pi}. // Output range is {-1/4 to 1/4} representing {-1 to 1}. AF1 APSinF1(AF1 x){return x*abs(x)-x;} // MAD. AF2 APSinF2(AF2 x){return x*abs(x)-x;} AF1 APCosF1(AF1 x){x=AFractF1(x*AF1_(0.5)+AF1_(0.75));x=x*AF1_(2.0)-AF1_(1.0);return APSinF1(x);} // 3x MAD, FRACT AF2 APCosF2(AF2 x){x=AFractF2(x*AF2_(0.5)+AF2_(0.75));x=x*AF2_(2.0)-AF2_(1.0);return APSinF2(x);} AF2 APSinCosF1(AF1 x){AF1 y=AFractF1(x*AF1_(0.5)+AF1_(0.75));y=y*AF1_(2.0)-AF1_(1.0);return APSinF2(AF2(x,y));} #endif //------------------------------------------------------------------------------------------------------------------------------ #ifdef A_HALF // For a packed {sin,cos} pair, // - Native takes 16 clocks and 4 issue slots (no packed transcendentals). // - Parabolic takes 8 clocks and 8 issue slots (only fract is non-packed). AH1 APSinH1(AH1 x){return x*abs(x)-x;} AH2 APSinH2(AH2 x){return x*abs(x)-x;} // AND,FMA AH1 APCosH1(AH1 x){x=AFractH1(x*AH1_(0.5)+AH1_(0.75));x=x*AH1_(2.0)-AH1_(1.0);return APSinH1(x);} AH2 APCosH2(AH2 x){x=AFractH2(x*AH2_(0.5)+AH2_(0.75));x=x*AH2_(2.0)-AH2_(1.0);return APSinH2(x);} // 3x FMA, 2xFRACT, AND AH2 APSinCosH1(AH1 x){AH1 y=AFractH1(x*AH1_(0.5)+AH1_(0.75));y=y*AH1_(2.0)-AH1_(1.0);return APSinH2(AH2(x,y));} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // [ZOL] ZERO ONE LOGIC //------------------------------------------------------------------------------------------------------------------------------ // Conditional free logic designed for easy 16-bit packing, and backwards porting to 32-bit. //------------------------------------------------------------------------------------------------------------------------------ // 0 := false // 1 := true //------------------------------------------------------------------------------------------------------------------------------ // AndNot(x,y) -> !(x&y) .... One op. // AndOr(x,y,z) -> (x&y)|z ... One op. // GtZero(x) -> x>0.0 ..... One op. // Sel(x,y,z) -> x?y:z ..... Two ops, has no precision loss. // Signed(x) -> x<0.0 ..... One op. // ZeroPass(x,y) -> x?0:y ..... Two ops, 'y' is a pass through safe for aliasing as integer. //------------------------------------------------------------------------------------------------------------------------------ // OPTIMIZATION NOTES // ================== // - On Vega to use 2 constants in a packed op, pass in as one AW2 or one AH2 'k.xy' and use as 'k.xx' and 'k.yy'. // For example 'a.xy*k.xx+k.yy'. //============================================================================================================================== #if 1 AU1 AZolAndU1(AU1 x,AU1 y){return min(x,y);} AU2 AZolAndU2(AU2 x,AU2 y){return min(x,y);} AU3 AZolAndU3(AU3 x,AU3 y){return min(x,y);} AU4 AZolAndU4(AU4 x,AU4 y){return min(x,y);} //------------------------------------------------------------------------------------------------------------------------------ AU1 AZolNotU1(AU1 x){return x^AU1_(1);} AU2 AZolNotU2(AU2 x){return x^AU2_(1);} AU3 AZolNotU3(AU3 x){return x^AU3_(1);} AU4 AZolNotU4(AU4 x){return x^AU4_(1);} //------------------------------------------------------------------------------------------------------------------------------ AU1 AZolOrU1(AU1 x,AU1 y){return max(x,y);} AU2 AZolOrU2(AU2 x,AU2 y){return max(x,y);} AU3 AZolOrU3(AU3 x,AU3 y){return max(x,y);} AU4 AZolOrU4(AU4 x,AU4 y){return max(x,y);} //============================================================================================================================== AU1 AZolF1ToU1(AF1 x){return AU1(x);} AU2 AZolF2ToU2(AF2 x){return AU2(x);} AU3 AZolF3ToU3(AF3 x){return AU3(x);} AU4 AZolF4ToU4(AF4 x){return AU4(x);} //------------------------------------------------------------------------------------------------------------------------------ // 2 ops, denormals don't work in 32-bit on PC (and if they are enabled, OMOD is disabled). AU1 AZolNotF1ToU1(AF1 x){return AU1(AF1_(1.0)-x);} AU2 AZolNotF2ToU2(AF2 x){return AU2(AF2_(1.0)-x);} AU3 AZolNotF3ToU3(AF3 x){return AU3(AF3_(1.0)-x);} AU4 AZolNotF4ToU4(AF4 x){return AU4(AF4_(1.0)-x);} //------------------------------------------------------------------------------------------------------------------------------ AF1 AZolU1ToF1(AU1 x){return AF1(x);} AF2 AZolU2ToF2(AU2 x){return AF2(x);} AF3 AZolU3ToF3(AU3 x){return AF3(x);} AF4 AZolU4ToF4(AU4 x){return AF4(x);} //============================================================================================================================== AF1 AZolAndF1(AF1 x,AF1 y){return min(x,y);} AF2 AZolAndF2(AF2 x,AF2 y){return min(x,y);} AF3 AZolAndF3(AF3 x,AF3 y){return min(x,y);} AF4 AZolAndF4(AF4 x,AF4 y){return min(x,y);} //------------------------------------------------------------------------------------------------------------------------------ AF1 ASolAndNotF1(AF1 x,AF1 y){return (-x)*y+AF1_(1.0);} AF2 ASolAndNotF2(AF2 x,AF2 y){return (-x)*y+AF2_(1.0);} AF3 ASolAndNotF3(AF3 x,AF3 y){return (-x)*y+AF3_(1.0);} AF4 ASolAndNotF4(AF4 x,AF4 y){return (-x)*y+AF4_(1.0);} //------------------------------------------------------------------------------------------------------------------------------ AF1 AZolAndOrF1(AF1 x,AF1 y,AF1 z){return ASatF1(x*y+z);} AF2 AZolAndOrF2(AF2 x,AF2 y,AF2 z){return ASatF2(x*y+z);} AF3 AZolAndOrF3(AF3 x,AF3 y,AF3 z){return ASatF3(x*y+z);} AF4 AZolAndOrF4(AF4 x,AF4 y,AF4 z){return ASatF4(x*y+z);} //------------------------------------------------------------------------------------------------------------------------------ AF1 AZolGtZeroF1(AF1 x){return ASatF1(x*AF1_(A_INFP_F));} AF2 AZolGtZeroF2(AF2 x){return ASatF2(x*AF2_(A_INFP_F));} AF3 AZolGtZeroF3(AF3 x){return ASatF3(x*AF3_(A_INFP_F));} AF4 AZolGtZeroF4(AF4 x){return ASatF4(x*AF4_(A_INFP_F));} //------------------------------------------------------------------------------------------------------------------------------ AF1 AZolNotF1(AF1 x){return AF1_(1.0)-x;} AF2 AZolNotF2(AF2 x){return AF2_(1.0)-x;} AF3 AZolNotF3(AF3 x){return AF3_(1.0)-x;} AF4 AZolNotF4(AF4 x){return AF4_(1.0)-x;} //------------------------------------------------------------------------------------------------------------------------------ AF1 AZolOrF1(AF1 x,AF1 y){return max(x,y);} AF2 AZolOrF2(AF2 x,AF2 y){return max(x,y);} AF3 AZolOrF3(AF3 x,AF3 y){return max(x,y);} AF4 AZolOrF4(AF4 x,AF4 y){return max(x,y);} //------------------------------------------------------------------------------------------------------------------------------ AF1 AZolSelF1(AF1 x,AF1 y,AF1 z){AF1 r=(-x)*z+z;return x*y+r;} AF2 AZolSelF2(AF2 x,AF2 y,AF2 z){AF2 r=(-x)*z+z;return x*y+r;} AF3 AZolSelF3(AF3 x,AF3 y,AF3 z){AF3 r=(-x)*z+z;return x*y+r;} AF4 AZolSelF4(AF4 x,AF4 y,AF4 z){AF4 r=(-x)*z+z;return x*y+r;} //------------------------------------------------------------------------------------------------------------------------------ AF1 AZolSignedF1(AF1 x){return ASatF1(x*AF1_(A_INFN_F));} AF2 AZolSignedF2(AF2 x){return ASatF2(x*AF2_(A_INFN_F));} AF3 AZolSignedF3(AF3 x){return ASatF3(x*AF3_(A_INFN_F));} AF4 AZolSignedF4(AF4 x){return ASatF4(x*AF4_(A_INFN_F));} //------------------------------------------------------------------------------------------------------------------------------ AF1 AZolZeroPassF1(AF1 x,AF1 y){return AF1_AU1((AU1_AF1(x)!=AU1_(0))?AU1_(0):AU1_AF1(y));} AF2 AZolZeroPassF2(AF2 x,AF2 y){return AF2_AU2((AU2_AF2(x)!=AU2_(0))?AU2_(0):AU2_AF2(y));} AF3 AZolZeroPassF3(AF3 x,AF3 y){return AF3_AU3((AU3_AF3(x)!=AU3_(0))?AU3_(0):AU3_AF3(y));} AF4 AZolZeroPassF4(AF4 x,AF4 y){return AF4_AU4((AU4_AF4(x)!=AU4_(0))?AU4_(0):AU4_AF4(y));} #endif //============================================================================================================================== #ifdef A_HALF AW1 AZolAndW1(AW1 x,AW1 y){return min(x,y);} AW2 AZolAndW2(AW2 x,AW2 y){return min(x,y);} AW3 AZolAndW3(AW3 x,AW3 y){return min(x,y);} AW4 AZolAndW4(AW4 x,AW4 y){return min(x,y);} //------------------------------------------------------------------------------------------------------------------------------ AW1 AZolNotW1(AW1 x){return x^AW1_(1);} AW2 AZolNotW2(AW2 x){return x^AW2_(1);} AW3 AZolNotW3(AW3 x){return x^AW3_(1);} AW4 AZolNotW4(AW4 x){return x^AW4_(1);} //------------------------------------------------------------------------------------------------------------------------------ AW1 AZolOrW1(AW1 x,AW1 y){return max(x,y);} AW2 AZolOrW2(AW2 x,AW2 y){return max(x,y);} AW3 AZolOrW3(AW3 x,AW3 y){return max(x,y);} AW4 AZolOrW4(AW4 x,AW4 y){return max(x,y);} //============================================================================================================================== // Uses denormal trick. AW1 AZolH1ToW1(AH1 x){return AW1_AH1(x*AH1_AW1(AW1_(1)));} AW2 AZolH2ToW2(AH2 x){return AW2_AH2(x*AH2_AW2(AW2_(1)));} AW3 AZolH3ToW3(AH3 x){return AW3_AH3(x*AH3_AW3(AW3_(1)));} AW4 AZolH4ToW4(AH4 x){return AW4_AH4(x*AH4_AW4(AW4_(1)));} //------------------------------------------------------------------------------------------------------------------------------ // AMD arch lacks a packed conversion opcode. AH1 AZolW1ToH1(AW1 x){return AH1_AW1(x*AW1_AH1(AH1_(1.0)));} AH2 AZolW2ToH2(AW2 x){return AH2_AW2(x*AW2_AH2(AH2_(1.0)));} AH3 AZolW1ToH3(AW3 x){return AH3_AW3(x*AW3_AH3(AH3_(1.0)));} AH4 AZolW2ToH4(AW4 x){return AH4_AW4(x*AW4_AH4(AH4_(1.0)));} //============================================================================================================================== AH1 AZolAndH1(AH1 x,AH1 y){return min(x,y);} AH2 AZolAndH2(AH2 x,AH2 y){return min(x,y);} AH3 AZolAndH3(AH3 x,AH3 y){return min(x,y);} AH4 AZolAndH4(AH4 x,AH4 y){return min(x,y);} //------------------------------------------------------------------------------------------------------------------------------ AH1 ASolAndNotH1(AH1 x,AH1 y){return (-x)*y+AH1_(1.0);} AH2 ASolAndNotH2(AH2 x,AH2 y){return (-x)*y+AH2_(1.0);} AH3 ASolAndNotH3(AH3 x,AH3 y){return (-x)*y+AH3_(1.0);} AH4 ASolAndNotH4(AH4 x,AH4 y){return (-x)*y+AH4_(1.0);} //------------------------------------------------------------------------------------------------------------------------------ AH1 AZolAndOrH1(AH1 x,AH1 y,AH1 z){return ASatH1(x*y+z);} AH2 AZolAndOrH2(AH2 x,AH2 y,AH2 z){return ASatH2(x*y+z);} AH3 AZolAndOrH3(AH3 x,AH3 y,AH3 z){return ASatH3(x*y+z);} AH4 AZolAndOrH4(AH4 x,AH4 y,AH4 z){return ASatH4(x*y+z);} //------------------------------------------------------------------------------------------------------------------------------ AH1 AZolGtZeroH1(AH1 x){return ASatH1(x*AH1_(A_INFP_H));} AH2 AZolGtZeroH2(AH2 x){return ASatH2(x*AH2_(A_INFP_H));} AH3 AZolGtZeroH3(AH3 x){return ASatH3(x*AH3_(A_INFP_H));} AH4 AZolGtZeroH4(AH4 x){return ASatH4(x*AH4_(A_INFP_H));} //------------------------------------------------------------------------------------------------------------------------------ AH1 AZolNotH1(AH1 x){return AH1_(1.0)-x;} AH2 AZolNotH2(AH2 x){return AH2_(1.0)-x;} AH3 AZolNotH3(AH3 x){return AH3_(1.0)-x;} AH4 AZolNotH4(AH4 x){return AH4_(1.0)-x;} //------------------------------------------------------------------------------------------------------------------------------ AH1 AZolOrH1(AH1 x,AH1 y){return max(x,y);} AH2 AZolOrH2(AH2 x,AH2 y){return max(x,y);} AH3 AZolOrH3(AH3 x,AH3 y){return max(x,y);} AH4 AZolOrH4(AH4 x,AH4 y){return max(x,y);} //------------------------------------------------------------------------------------------------------------------------------ AH1 AZolSelH1(AH1 x,AH1 y,AH1 z){AH1 r=(-x)*z+z;return x*y+r;} AH2 AZolSelH2(AH2 x,AH2 y,AH2 z){AH2 r=(-x)*z+z;return x*y+r;} AH3 AZolSelH3(AH3 x,AH3 y,AH3 z){AH3 r=(-x)*z+z;return x*y+r;} AH4 AZolSelH4(AH4 x,AH4 y,AH4 z){AH4 r=(-x)*z+z;return x*y+r;} //------------------------------------------------------------------------------------------------------------------------------ AH1 AZolSignedH1(AH1 x){return ASatH1(x*AH1_(A_INFN_H));} AH2 AZolSignedH2(AH2 x){return ASatH2(x*AH2_(A_INFN_H));} AH3 AZolSignedH3(AH3 x){return ASatH3(x*AH3_(A_INFN_H));} AH4 AZolSignedH4(AH4 x){return ASatH4(x*AH4_(A_INFN_H));} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // COLOR CONVERSIONS //------------------------------------------------------------------------------------------------------------------------------ // These are all linear to/from some other space (where 'linear' has been shortened out of the function name). // So 'ToGamma' is 'LinearToGamma', and 'FromGamma' is 'LinearFromGamma'. // These are branch free implementations. // The AToSrgbF1() function is useful for stores for compute shaders for GPUs without hardware linear->sRGB store conversion. //------------------------------------------------------------------------------------------------------------------------------ // TRANSFER FUNCTIONS // ================== // 709 ..... Rec709 used for some HDTVs // Gamma ... Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native // Pq ...... PQ native for HDR10 // Srgb .... The sRGB output, typical of PC displays, useful for 10-bit output, or storing to 8-bit UNORM without SRGB type // Two ..... Gamma 2.0, fastest conversion (useful for intermediate pass approximations) // Three ... Gamma 3.0, less fast, but good for HDR. //------------------------------------------------------------------------------------------------------------------------------ // KEEPING TO SPEC // =============== // Both Rec.709 and sRGB have a linear segment which as spec'ed would intersect the curved segment 2 times. // (a.) For 8-bit sRGB, steps {0 to 10.3} are in the linear region (4% of the encoding range). // (b.) For 8-bit 709, steps {0 to 20.7} are in the linear region (8% of the encoding range). // Also there is a slight step in the transition regions. // Precision of the coefficients in the spec being the likely cause. // Main usage case of the sRGB code is to do the linear->sRGB converstion in a compute shader before store. // This is to work around lack of hardware (typically only ROP does the conversion for free). // To "correct" the linear segment, would be to introduce error, because hardware decode of sRGB->linear is fixed (and free). // So this header keeps with the spec. // For linear->sRGB transforms, the linear segment in some respects reduces error, because rounding in that region is linear. // Rounding in the curved region in hardware (and fast software code) introduces error due to rounding in non-linear. //------------------------------------------------------------------------------------------------------------------------------ // FOR PQ // ====== // Both input and output is {0.0-1.0}, and where output 1.0 represents 10000.0 cd/m^2. // All constants are only specified to FP32 precision. // External PQ source reference, // - https://github.com/ampas/aces-dev/blob/master/transforms/ctl/utilities/ACESlib.Utilities_Color.a1.0.1.ctl //------------------------------------------------------------------------------------------------------------------------------ // PACKED VERSIONS // =============== // These are the A*H2() functions. // There is no PQ functions as FP16 seemed to not have enough precision for the conversion. // The remaining functions are "good enough" for 8-bit, and maybe 10-bit if not concerned about a few 1-bit errors. // Precision is lowest in the 709 conversion, higher in sRGB, higher still in Two and Gamma (when using 2.2 at least). //------------------------------------------------------------------------------------------------------------------------------ // NOTES // ===== // Could be faster for PQ conversions to be in ALU or a texture lookup depending on usage case. //============================================================================================================================== #if 1 AF1 ATo709F1(AF1 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} AF2 ATo709F2(AF2 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} AF3 ATo709F3(AF3 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} //------------------------------------------------------------------------------------------------------------------------------ // Note 'rcpX' is '1/x', where the 'x' is what would be used in AFromGamma(). AF1 AToGammaF1(AF1 c,AF1 rcpX){return pow(c,AF1_(rcpX));} AF2 AToGammaF2(AF2 c,AF1 rcpX){return pow(c,AF2_(rcpX));} AF3 AToGammaF3(AF3 c,AF1 rcpX){return pow(c,AF3_(rcpX));} //------------------------------------------------------------------------------------------------------------------------------ AF1 AToPqF1(AF1 x){AF1 p=pow(x,AF1_(0.159302)); return pow((AF1_(0.835938)+AF1_(18.8516)*p)/(AF1_(1.0)+AF1_(18.6875)*p),AF1_(78.8438));} AF2 AToPqF1(AF2 x){AF2 p=pow(x,AF2_(0.159302)); return pow((AF2_(0.835938)+AF2_(18.8516)*p)/(AF2_(1.0)+AF2_(18.6875)*p),AF2_(78.8438));} AF3 AToPqF1(AF3 x){AF3 p=pow(x,AF3_(0.159302)); return pow((AF3_(0.835938)+AF3_(18.8516)*p)/(AF3_(1.0)+AF3_(18.6875)*p),AF3_(78.8438));} //------------------------------------------------------------------------------------------------------------------------------ AF1 AToSrgbF1(AF1 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} AF2 AToSrgbF2(AF2 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} AF3 AToSrgbF3(AF3 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} //------------------------------------------------------------------------------------------------------------------------------ AF1 AToTwoF1(AF1 c){return sqrt(c);} AF2 AToTwoF2(AF2 c){return sqrt(c);} AF3 AToTwoF3(AF3 c){return sqrt(c);} //------------------------------------------------------------------------------------------------------------------------------ AF1 AToThreeF1(AF1 c){return pow(c,AF1_(1.0/3.0));} AF2 AToThreeF2(AF2 c){return pow(c,AF2_(1.0/3.0));} AF3 AToThreeF3(AF3 c){return pow(c,AF3_(1.0/3.0));} #endif //============================================================================================================================== #if 1 // Unfortunately median won't work here. AF1 AFrom709F1(AF1 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} AF2 AFrom709F2(AF2 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} AF3 AFrom709F3(AF3 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} //------------------------------------------------------------------------------------------------------------------------------ AF1 AFromGammaF1(AF1 c,AF1 x){return pow(c,AF1_(x));} AF2 AFromGammaF2(AF2 c,AF1 x){return pow(c,AF2_(x));} AF3 AFromGammaF3(AF3 c,AF1 x){return pow(c,AF3_(x));} //------------------------------------------------------------------------------------------------------------------------------ AF1 AFromPqF1(AF1 x){AF1 p=pow(x,AF1_(0.0126833)); return pow(ASatF1(p-AF1_(0.835938))/(AF1_(18.8516)-AF1_(18.6875)*p),AF1_(6.27739));} AF2 AFromPqF1(AF2 x){AF2 p=pow(x,AF2_(0.0126833)); return pow(ASatF2(p-AF2_(0.835938))/(AF2_(18.8516)-AF2_(18.6875)*p),AF2_(6.27739));} AF3 AFromPqF1(AF3 x){AF3 p=pow(x,AF3_(0.0126833)); return pow(ASatF3(p-AF3_(0.835938))/(AF3_(18.8516)-AF3_(18.6875)*p),AF3_(6.27739));} //------------------------------------------------------------------------------------------------------------------------------ // Unfortunately median won't work here. AF1 AFromSrgbF1(AF1 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} AF2 AFromSrgbF2(AF2 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} AF3 AFromSrgbF3(AF3 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} //------------------------------------------------------------------------------------------------------------------------------ AF1 AFromTwoF1(AF1 c){return c*c;} AF2 AFromTwoF2(AF2 c){return c*c;} AF3 AFromTwoF3(AF3 c){return c*c;} //------------------------------------------------------------------------------------------------------------------------------ AF1 AFromThreeF1(AF1 c){return c*c*c;} AF2 AFromThreeF2(AF2 c){return c*c*c;} AF3 AFromThreeF3(AF3 c){return c*c*c;} #endif //============================================================================================================================== #ifdef A_HALF AH1 ATo709H1(AH1 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} AH2 ATo709H2(AH2 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} AH3 ATo709H3(AH3 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} //------------------------------------------------------------------------------------------------------------------------------ AH1 AToGammaH1(AH1 c,AH1 rcpX){return pow(c,AH1_(rcpX));} AH2 AToGammaH2(AH2 c,AH1 rcpX){return pow(c,AH2_(rcpX));} AH3 AToGammaH3(AH3 c,AH1 rcpX){return pow(c,AH3_(rcpX));} //------------------------------------------------------------------------------------------------------------------------------ AH1 AToSrgbH1(AH1 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} AH2 AToSrgbH2(AH2 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} AH3 AToSrgbH3(AH3 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} //------------------------------------------------------------------------------------------------------------------------------ AH1 AToTwoH1(AH1 c){return sqrt(c);} AH2 AToTwoH2(AH2 c){return sqrt(c);} AH3 AToTwoH3(AH3 c){return sqrt(c);} //------------------------------------------------------------------------------------------------------------------------------ AH1 AToThreeF1(AH1 c){return pow(c,AH1_(1.0/3.0));} AH2 AToThreeF2(AH2 c){return pow(c,AH2_(1.0/3.0));} AH3 AToThreeF3(AH3 c){return pow(c,AH3_(1.0/3.0));} #endif //============================================================================================================================== #ifdef A_HALF AH1 AFrom709H1(AH1 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} AH2 AFrom709H2(AH2 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} AH3 AFrom709H3(AH3 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} //------------------------------------------------------------------------------------------------------------------------------ AH1 AFromGammaH1(AH1 c,AH1 x){return pow(c,AH1_(x));} AH2 AFromGammaH2(AH2 c,AH1 x){return pow(c,AH2_(x));} AH3 AFromGammaH3(AH3 c,AH1 x){return pow(c,AH3_(x));} //------------------------------------------------------------------------------------------------------------------------------ AH1 AHromSrgbF1(AH1 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} AH2 AHromSrgbF2(AH2 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} AH3 AHromSrgbF3(AH3 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} //------------------------------------------------------------------------------------------------------------------------------ AH1 AFromTwoH1(AH1 c){return c*c;} AH2 AFromTwoH2(AH2 c){return c*c;} AH3 AFromTwoH3(AH3 c){return c*c;} //------------------------------------------------------------------------------------------------------------------------------ AH1 AFromThreeH1(AH1 c){return c*c*c;} AH2 AFromThreeH2(AH2 c){return c*c*c;} AH3 AFromThreeH3(AH3 c){return c*c*c;} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // CS REMAP //============================================================================================================================== // Simple remap 64x1 to 8x8 with rotated 2x2 pixel quads in quad linear. // 543210 // ====== // ..xxx. // yy...y AU2 ARmp8x8(AU1 a){return AU2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} //============================================================================================================================== // More complex remap 64x1 to 8x8 which is necessary for 2D wave reductions. // 543210 // ====== // .xx..x // y..yy. // Details, // LANE TO 8x8 MAPPING // =================== // 00 01 08 09 10 11 18 19 // 02 03 0a 0b 12 13 1a 1b // 04 05 0c 0d 14 15 1c 1d // 06 07 0e 0f 16 17 1e 1f // 20 21 28 29 30 31 38 39 // 22 23 2a 2b 32 33 3a 3b // 24 25 2c 2d 34 35 3c 3d // 26 27 2e 2f 36 37 3e 3f AU2 ARmpRed8x8(AU1 a){return AU2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} //============================================================================================================================== #ifdef A_HALF AW2 ARmp8x8H(AU1 a){return AW2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} AW2 ARmpRed8x8H(AU1 a){return AW2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} #endif #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // // REFERENCE // //------------------------------------------------------------------------------------------------------------------------------ // IEEE FLOAT RULES // ================ // - saturate(NaN)=0, saturate(-INF)=0, saturate(+INF)=1 // - {+/-}0 * {+/-}INF = NaN // - -INF + (+INF) = NaN // - {+/-}0 / {+/-}0 = NaN // - {+/-}INF / {+/-}INF = NaN // - a<(-0) := sqrt(a) = NaN (a=-0.0 won't NaN) // - 0 == -0 // - 4/0 = +INF // - 4/-0 = -INF // - 4+INF = +INF // - 4-INF = -INF // - 4*(+INF) = +INF // - 4*(-INF) = -INF // - -4*(+INF) = -INF // - sqrt(+INF) = +INF //------------------------------------------------------------------------------------------------------------------------------ // FP16 ENCODING // ============= // fedcba9876543210 // ---------------- // ......mmmmmmmmmm 10-bit mantissa (encodes 11-bit 0.5 to 1.0 except for denormals) // .eeeee.......... 5-bit exponent // .00000.......... denormals // .00001.......... -14 exponent // .11110.......... 15 exponent // .111110000000000 infinity // .11111nnnnnnnnnn NaN with n!=0 // s............... sign //------------------------------------------------------------------------------------------------------------------------------ // FP16/INT16 ALIASING DENORMAL // ============================ // 11-bit unsigned integers alias with half float denormal/normal values, // 1 = 2^(-24) = 1/16777216 ....................... first denormal value // 2 = 2^(-23) // ... // 1023 = 2^(-14)*(1-2^(-10)) = 2^(-14)*(1-1/1024) ... last denormal value // 1024 = 2^(-14) = 1/16384 .......................... first normal value that still maps to integers // 2047 .............................................. last normal value that still maps to integers // Scaling limits, // 2^15 = 32768 ...................................... largest power of 2 scaling // Largest pow2 conversion mapping is at *32768, // 1 : 2^(-9) = 1/512 // 2 : 1/256 // 4 : 1/128 // 8 : 1/64 // 16 : 1/32 // 32 : 1/16 // 64 : 1/8 // 128 : 1/4 // 256 : 1/2 // 512 : 1 // 1024 : 2 // 2047 : a little less than 4 //============================================================================================================================== //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // // // GPU/CPU PORTABILITY // // //------------------------------------------------------------------------------------------------------------------------------ // This is the GPU implementation. // See the CPU implementation for docs. //============================================================================================================================== #ifdef A_GPU #define A_TRUE true #define A_FALSE false #define A_STATIC //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY //============================================================================================================================== #define retAD2 AD2 #define retAD3 AD3 #define retAD4 AD4 #define retAF2 AF2 #define retAF3 AF3 #define retAF4 AF4 #define retAL2 AL2 #define retAL3 AL3 #define retAL4 AL4 #define retAU2 AU2 #define retAU3 AU3 #define retAU4 AU4 //------------------------------------------------------------------------------------------------------------------------------ #define inAD2 in AD2 #define inAD3 in AD3 #define inAD4 in AD4 #define inAF2 in AF2 #define inAF3 in AF3 #define inAF4 in AF4 #define inAL2 in AL2 #define inAL3 in AL3 #define inAL4 in AL4 #define inAU2 in AU2 #define inAU3 in AU3 #define inAU4 in AU4 //------------------------------------------------------------------------------------------------------------------------------ #define inoutAD2 inout AD2 #define inoutAD3 inout AD3 #define inoutAD4 inout AD4 #define inoutAF2 inout AF2 #define inoutAF3 inout AF3 #define inoutAF4 inout AF4 #define inoutAL2 inout AL2 #define inoutAL3 inout AL3 #define inoutAL4 inout AL4 #define inoutAU2 inout AU2 #define inoutAU3 inout AU3 #define inoutAU4 inout AU4 //------------------------------------------------------------------------------------------------------------------------------ #define outAD2 out AD2 #define outAD3 out AD3 #define outAD4 out AD4 #define outAF2 out AF2 #define outAF3 out AF3 #define outAF4 out AF4 #define outAL2 out AL2 #define outAL3 out AL3 #define outAL4 out AL4 #define outAU2 out AU2 #define outAU3 out AU3 #define outAU4 out AU4 //------------------------------------------------------------------------------------------------------------------------------ #define varAD2(x) AD2 x #define varAD3(x) AD3 x #define varAD4(x) AD4 x #define varAF2(x) AF2 x #define varAF3(x) AF3 x #define varAF4(x) AF4 x #define varAL2(x) AL2 x #define varAL3(x) AL3 x #define varAL4(x) AL4 x #define varAU2(x) AU2 x #define varAU3(x) AU3 x #define varAU4(x) AU4 x //------------------------------------------------------------------------------------------------------------------------------ #define initAD2(x,y) AD2(x,y) #define initAD3(x,y,z) AD3(x,y,z) #define initAD4(x,y,z,w) AD4(x,y,z,w) #define initAF2(x,y) AF2(x,y) #define initAF3(x,y,z) AF3(x,y,z) #define initAF4(x,y,z,w) AF4(x,y,z,w) #define initAL2(x,y) AL2(x,y) #define initAL3(x,y,z) AL3(x,y,z) #define initAL4(x,y,z,w) AL4(x,y,z,w) #define initAU2(x,y) AU2(x,y) #define initAU3(x,y,z) AU3(x,y,z) #define initAU4(x,y,z,w) AU4(x,y,z,w) //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // SCALAR RETURN OPS //============================================================================================================================== #define AAbsD1(a) abs(AD1(a)) #define AAbsF1(a) abs(AF1(a)) //------------------------------------------------------------------------------------------------------------------------------ #define ACosD1(a) cos(AD1(a)) #define ACosF1(a) cos(AF1(a)) //------------------------------------------------------------------------------------------------------------------------------ #define ADotD2(a,b) dot(AD2(a),AD2(b)) #define ADotD3(a,b) dot(AD3(a),AD3(b)) #define ADotD4(a,b) dot(AD4(a),AD4(b)) #define ADotF2(a,b) dot(AF2(a),AF2(b)) #define ADotF3(a,b) dot(AF3(a),AF3(b)) #define ADotF4(a,b) dot(AF4(a),AF4(b)) //------------------------------------------------------------------------------------------------------------------------------ #define AExp2D1(a) exp2(AD1(a)) #define AExp2F1(a) exp2(AF1(a)) //------------------------------------------------------------------------------------------------------------------------------ #define AFloorD1(a) floor(AD1(a)) #define AFloorF1(a) floor(AF1(a)) //------------------------------------------------------------------------------------------------------------------------------ #define ALog2D1(a) log2(AD1(a)) #define ALog2F1(a) log2(AF1(a)) //------------------------------------------------------------------------------------------------------------------------------ #define AMaxD1(a,b) max(a,b) #define AMaxF1(a,b) max(a,b) #define AMaxL1(a,b) max(a,b) #define AMaxU1(a,b) max(a,b) //------------------------------------------------------------------------------------------------------------------------------ #define AMinD1(a,b) min(a,b) #define AMinF1(a,b) min(a,b) #define AMinL1(a,b) min(a,b) #define AMinU1(a,b) min(a,b) //------------------------------------------------------------------------------------------------------------------------------ #define ASinD1(a) sin(AD1(a)) #define ASinF1(a) sin(AF1(a)) //------------------------------------------------------------------------------------------------------------------------------ #define ASqrtD1(a) sqrt(AD1(a)) #define ASqrtF1(a) sqrt(AF1(a)) //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // SCALAR RETURN OPS - DEPENDENT //============================================================================================================================== #define APowD1(a,b) pow(AD1(a),AF1(b)) #define APowF1(a,b) pow(AF1(a),AF1(b)) //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // VECTOR OPS //------------------------------------------------------------------------------------------------------------------------------ // These are added as needed for production or prototyping, so not necessarily a complete set. // They follow a convention of taking in a destination and also returning the destination value to increase utility. //============================================================================================================================== #ifdef A_DUBL AD2 opAAbsD2(outAD2 d,inAD2 a){d=abs(a);return d;} AD3 opAAbsD3(outAD3 d,inAD3 a){d=abs(a);return d;} AD4 opAAbsD4(outAD4 d,inAD4 a){d=abs(a);return d;} //------------------------------------------------------------------------------------------------------------------------------ AD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d=a+b;return d;} AD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d=a+b;return d;} AD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d=a+b;return d;} //------------------------------------------------------------------------------------------------------------------------------ AD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d=a+AD2_(b);return d;} AD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d=a+AD3_(b);return d;} AD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d=a+AD4_(b);return d;} //------------------------------------------------------------------------------------------------------------------------------ AD2 opACpyD2(outAD2 d,inAD2 a){d=a;return d;} AD3 opACpyD3(outAD3 d,inAD3 a){d=a;return d;} AD4 opACpyD4(outAD4 d,inAD4 a){d=a;return d;} //------------------------------------------------------------------------------------------------------------------------------ AD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d=ALerpD2(a,b,c);return d;} AD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d=ALerpD3(a,b,c);return d;} AD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d=ALerpD4(a,b,c);return d;} //------------------------------------------------------------------------------------------------------------------------------ AD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d=ALerpD2(a,b,AD2_(c));return d;} AD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d=ALerpD3(a,b,AD3_(c));return d;} AD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d=ALerpD4(a,b,AD4_(c));return d;} //------------------------------------------------------------------------------------------------------------------------------ AD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d=max(a,b);return d;} AD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d=max(a,b);return d;} AD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d=max(a,b);return d;} //------------------------------------------------------------------------------------------------------------------------------ AD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d=min(a,b);return d;} AD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d=min(a,b);return d;} AD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d=min(a,b);return d;} //------------------------------------------------------------------------------------------------------------------------------ AD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d=a*b;return d;} AD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d=a*b;return d;} AD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d=a*b;return d;} //------------------------------------------------------------------------------------------------------------------------------ AD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d=a*AD2_(b);return d;} AD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d=a*AD3_(b);return d;} AD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d=a*AD4_(b);return d;} //------------------------------------------------------------------------------------------------------------------------------ AD2 opANegD2(outAD2 d,inAD2 a){d=-a;return d;} AD3 opANegD3(outAD3 d,inAD3 a){d=-a;return d;} AD4 opANegD4(outAD4 d,inAD4 a){d=-a;return d;} //------------------------------------------------------------------------------------------------------------------------------ AD2 opARcpD2(outAD2 d,inAD2 a){d=ARcpD2(a);return d;} AD3 opARcpD3(outAD3 d,inAD3 a){d=ARcpD3(a);return d;} AD4 opARcpD4(outAD4 d,inAD4 a){d=ARcpD4(a);return d;} #endif //============================================================================================================================== AF2 opAAbsF2(outAF2 d,inAF2 a){d=abs(a);return d;} AF3 opAAbsF3(outAF3 d,inAF3 a){d=abs(a);return d;} AF4 opAAbsF4(outAF4 d,inAF4 a){d=abs(a);return d;} //------------------------------------------------------------------------------------------------------------------------------ AF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d=a+b;return d;} AF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d=a+b;return d;} AF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d=a+b;return d;} //------------------------------------------------------------------------------------------------------------------------------ AF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d=a+AF2_(b);return d;} AF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d=a+AF3_(b);return d;} AF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d=a+AF4_(b);return d;} //------------------------------------------------------------------------------------------------------------------------------ AF2 opACpyF2(outAF2 d,inAF2 a){d=a;return d;} AF3 opACpyF3(outAF3 d,inAF3 a){d=a;return d;} AF4 opACpyF4(outAF4 d,inAF4 a){d=a;return d;} //------------------------------------------------------------------------------------------------------------------------------ AF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d=ALerpF2(a,b,c);return d;} AF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d=ALerpF3(a,b,c);return d;} AF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d=ALerpF4(a,b,c);return d;} //------------------------------------------------------------------------------------------------------------------------------ AF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d=ALerpF2(a,b,AF2_(c));return d;} AF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d=ALerpF3(a,b,AF3_(c));return d;} AF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d=ALerpF4(a,b,AF4_(c));return d;} //------------------------------------------------------------------------------------------------------------------------------ AF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d=max(a,b);return d;} AF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d=max(a,b);return d;} AF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d=max(a,b);return d;} //------------------------------------------------------------------------------------------------------------------------------ AF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d=min(a,b);return d;} AF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d=min(a,b);return d;} AF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d=min(a,b);return d;} //------------------------------------------------------------------------------------------------------------------------------ AF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d=a*b;return d;} AF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d=a*b;return d;} AF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d=a*b;return d;} //------------------------------------------------------------------------------------------------------------------------------ AF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d=a*AF2_(b);return d;} AF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d=a*AF3_(b);return d;} AF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d=a*AF4_(b);return d;} //------------------------------------------------------------------------------------------------------------------------------ AF2 opANegF2(outAF2 d,inAF2 a){d=-a;return d;} AF3 opANegF3(outAF3 d,inAF3 a){d=-a;return d;} AF4 opANegF4(outAF4 d,inAF4 a){d=-a;return d;} //------------------------------------------------------------------------------------------------------------------------------ AF2 opARcpF2(outAF2 d,inAF2 a){d=ARcpF2(a);return d;} AF3 opARcpF3(outAF3 d,inAF3 a){d=ARcpF3(a);return d;} AF4 opARcpF4(outAF4 d,inAF4 a){d=ARcpF4(a);return d;} #endif looking-glass-B6/client/renderers/EGL/shader/ffx_cas.frag000066400000000000000000000011621434445012300234720ustar00rootroot00000000000000#version 300 es precision mediump float; #include "compat.h" in vec2 fragCoord; out vec4 fragColor; uniform sampler2D texture; uniform uvec4 uConsts[2]; #define A_GPU 1 #define A_GLSL 1 #include "ffx_a.h" vec3 imageLoad(ivec2 point) { return texelFetch(texture, point, 0).rgb; } AF3 CasLoad(ASU2 p) { return imageLoad(p).rgb; } void CasInput(inout AF1 r,inout AF1 g,inout AF1 b) {} #include "ffx_cas.h" void main() { vec2 res = vec2(textureSize(texture, 0)); uvec2 point = uvec2(fragCoord * res); CasFilter( fragColor.r, fragColor.g, fragColor.b, point, uConsts[0], uConsts[1], true); } looking-glass-B6/client/renderers/EGL/shader/ffx_cas.h000066400000000000000000001666261434445012300230230ustar00rootroot00000000000000//_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // // [CAS] FIDELITY FX - CONSTRAST ADAPTIVE SHARPENING 1.20190610 // //============================================================================================================================== // LICENSE // ======= // Copyright (c) 2017-2019 Advanced Micro Devices, Inc. All rights reserved. // ------- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // ------- // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the // Software. // ------- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. //------------------------------------------------------------------------------------------------------------------------------ // ABOUT // ===== // CAS is a spatial only filter. // CAS takes RGB color input. // CAS enchances sharpness and local high-frequency contrast, and with or without added upsampling. // CAS outputs RGB color. //------------------------------------------------------------------------------------------------------------------------------ // SUGGESTIONS FOR INTEGRATION // =========================== // Best for performance, run CAS in sharpen-only mode, choose a video mode to have scan-out or the display scale. // - Sharpen-only mode is faster, and provides a better quality sharpening. // The scaling support in CAS was designed for when the application wants to do Dynamic Resolution Scaling (DRS). // - With DRS, the render resolution can change per frame. // - Use CAS to sharpen and upsample to the fixed output resolution, then composite the full resolution UI over CAS output. // - This can all happen in one compute dispatch. // It is likely better to reduce the amount of film grain which happens before CAS (as CAS will amplify grain). // - An alternative would be to add grain after CAS. // It is best to run CAS after tonemapping. // - CAS needs to have input value 1.0 at the peak of the display output. // It is ok to run CAS after compositing UI (it won't harm the UI). //------------------------------------------------------------------------------------------------------------------------------ // EXECUTION // ========= // CAS runs as a compute shader. // CAS is designed to be run either in a 32-bit, CasFilter(), or packed 16-bit, CasFilterH(), form. // The 32-bit form works on 8x8 tiles via one {64,1,1} workgroup. // The 16-bit form works on a pair of 8x8 tiles in a 16x8 configuration via one {64,1,1} workgroup. // CAS is designed to work best in semi-persistent form if running not async with graphics. // For 32-bit this means looping across a collection of 4 8x8 tiles in a 2x2 tile foot-print. // For 16-bit this means looping 2 times, once for the top 16x8 region and once for the bottom 16x8 region. //------------------------------------------------------------------------------------------------------------------------------ // INTEGRATION SUMMARY FOR CPU // =========================== // // Make sure has already been included. // // Setup pre-portability-header defines. // #define A_CPU 1 // // Include the portability header (requires version 1.20190530 or later which is backwards compatible). // #include "ffx_a.h" // // Include the CAS header. // #include "ffx_cas.h" // ... // // Call the setup function to build out the constants for the shader, pass these to the shader. // // The 'varAU4(const0);' expands into 'uint32_t const0[4];' on the CPU. // varAU4(const0); // varAU4(const1); // CasSetup(const0,const1, // 0.0f, // Sharpness tuning knob (0.0 to 1.0). // 1920.0f,1080.0f, // Example input size. // 2560.0f,1440.0f); // Example output size. // ... // // Later dispatch the shader based on the amount of semi-persistent loop unrolling. // // Here is an example for running with the 16x16 (4-way unroll for 32-bit or 2-way unroll for 16-bit) // vkCmdDispatch(cmdBuf,(widthInPixels+15)>>4,(heightInPixels+15)>>4,1); //------------------------------------------------------------------------------------------------------------------------------ // INTEGRATION SUMMARY FOR GPU // =========================== // // Setup layout. Example below for VK_FORMAT_R16G16B16A16_SFLOAT. // layout(set=0,binding=0,rgba16f)uniform image2D imgSrc; // layout(set=0,binding=1,rgba16f)uniform image2D imgDst; // ... // // Setup pre-portability-header defines (sets up GLSL/HLSL path, packed math support, etc) // #define A_GPU 1 // #define A_GLSL 1 // #define A_HALF 1 // ... // // Include the portability header (or copy it in without an include). // #include "ffx_a.h" // ... // // Define the fetch function(s). // // CasLoad() takes a 32-bit unsigned integer 2D coordinate and loads color. // AF3 CasLoad(ASU2 p){return imageLoad(imgSrc,p).rgb;} // // CasLoadH() is the 16-bit version taking 16-bit unsigned integer 2D coordinate and loading 16-bit float color. // // The ASU2() typecast back to 32-bit is a NO-OP, the compiler pattern matches and uses A16 opcode support instead. // // The AH3() typecast to 16-bit float is a NO-OP, the compiler pattern matches and uses D16 opcode support instead. // AH3 CasLoadH(ASW2 p){return AH3(imageLoad(imgSrc,ASU2(p)).rgb);} // ... // // Define the input modifiers as nop's initially. // // See "INPUT FORMAT SPECIFIC CASES" below for specifics on what to place in these functions. // void CasInput(inout AF1 r,inout AF1 g,inout AF1 b){} // void CasInputH(inout AH2 r,inout AH2 g,inout AH2 b){} // ... // // Include this CAS header file (or copy it in without an include). // #include "ffx_cas.h" // ... // // Example in shader integration for loop-unrolled 16x16 case for 32-bit. // layout(local_size_x=64)in; // void main(){ // // Fetch constants from CasSetup(). // AU4 const0=cb.const0; // AU4 const1=cb.const1; // // Do remapping of local xy in workgroup for a more PS-like swizzle pattern. // AU2 gxy=ARmp8x8(gl_LocalInvocationID.x)+AU2(gl_WorkGroupID.x<<4u,gl_WorkGroupID.y<<4u); // // Filter. // AF4 c; // CasFilter(c.r,c.g,c.b,gxy,const0,const1,false);imageStore(imgDst,ASU2(gxy),c); // gxy.x+=8u; // CasFilter(c.r,c.g,c.b,gxy,const0,const1,false);imageStore(imgDst,ASU2(gxy),c); // gxy.y+=8u; // CasFilter(c.r,c.g,c.b,gxy,const0,const1,false);imageStore(imgDst,ASU2(gxy),c); // gxy.x-=8u; // CasFilter(c.r,c.g,c.b,gxy,const0,const1,false);imageStore(imgDst,ASU2(gxy),c);} // ... // // Example for semi-persistent 16x16 but this time for packed math. // // Use this before including 'cas.h' if not using the non-packed filter function. // #define CAS_PACKED_ONLY 1 // ... // layout(local_size_x=64)in; // void main(){ // // Fetch constants from CasSetup(). // AU4 const0=cb.const0; // AU4 const1=cb.const1; // // Do remapping of local xy in workgroup for a more PS-like swizzle pattern. // AU2 gxy=ARmp8x8(gl_LocalInvocationID.x)+AU2(gl_WorkGroupID.x<<4u,gl_WorkGroupID.y<<4u); // // Filter. // AH4 c0,c1;AH2 cR,cG,cB; // CasFilterH(cR,cG,cB,gxy,const0,const1,false); // // Extra work integrated after CAS would go here. // ... // // Suggest only running CasDepack() right before stores, to maintain packed math for any work after CasFilterH(). // CasDepack(c0,c1,cR,cG,cB); // imageStore(imgDst,ASU2(gxy),AF4(c0)); // imageStore(imgDst,ASU2(gxy)+ASU2(8,0),AF4(c1)); // gxy.y+=8u; // CasFilterH(cR,cG,cB,gxy,const0,const1,false); // ... // CasDepack(c0,c1,cR,cG,cB); // imageStore(imgDst,ASU2(gxy),AF4(c0)); // imageStore(imgDst,ASU2(gxy)+ASU2(8,0),AF4(c1));} //------------------------------------------------------------------------------------------------------------------------------ // CAS FILTERING LOGIC // =================== // CAS uses the minimal nearest 3x3 source texel window for filtering. // The filter coefficients are radially symmetric (phase adaptive, computed per pixel based on output pixel center). // The filter kernel adapts to local contrast (adjusting the negative lobe strength of the filter kernel). //------------------------------------------------------------------------------------------------------------------------------ // CAS INPUT REQUIREMENTS // ====================== // This is designed to be a linear filter. // Running CAS on perceptual inputs will yield over-sharpening. // Input must range between {0 to 1} for each color channel. // CAS output will be {0 to 1} ranged as well. // CAS does 5 loads, so any conversion applied during CasLoad() or CasInput() has a 5 load * 3 channel = 15x cost amplifier. // - So input conversions need to be factored into the prior pass's output. // - But if necessary use CasInput() instead of CasLoad(), as CasInput() works with packed color. // - For CAS with scaling the amplifier is 12 load * 3 channel = 36x cost amplifier. // Any conversion applied to output has a 3x cost amplifier (3 color channels). // - Output conversions are substantially less expensive. // Added VALU ops due to conversions will have visible cost as this shader is already quite VALU heavy. // This filter does not function well on sRGB or gamma 2.2 non-linear data. // This filter does not function on PQ non-linear data. // - Due to the shape of PQ, the positive side of the ring created by the negative lobe tends to become over-bright. //------------------------------------------------------------------------------------------------------------------------------ // INPUT FORMAT SPECIFIC CASES // =========================== // - FP16 with all non-negative values ranging {0 to 1}. // - Use as is, filter is designed for linear input and output ranging {0 to 1}. // --------------------------- // - UNORM with linear conversion approximation. // - This could be used for both sRGB or FreeSync2 native (gamma 2.2) cases. // - Load/store with either 10:10:10:2 UNORM or 8:8:8:8 UNORM (aka VK_FORMAT_R8G8B8A8_UNORM). // - Use gamma 2.0 conversion in CasInput(), as an approximation. // - Modifications: // // Change the CasInput*() function to square the inputs. // void CasInput(inout AF1 r,inout AF1 g,inout AF1 b){r*=r;g*=g;b*=b;} // void CasInputH(inout AH2 r,inout AH2 g,inout AH2 b){r*=r;g*=g;b*=b;} // ... // // Do linear to gamma 2.0 before store. // // Since it will be common to do processing after CAS, the filter function returns linear. // c.r=sqrt(c.r);c.g=sqrt(c.g);c.b=sqrt(c.b); // imageStore(imgDst,ASU2(gxy),c); // ... // // And for packed. // CasFilterH(cR,cG,cB,gxy,const0,const1,true); // cR=sqrt(cR);cG=sqrt(cG);cB=sqrt(cB); // CasDepack(c0,c1,cR,cG,cB); // imageStore(img[0],ASU2(gxy),AF4(c0)); // imageStore(img[0],ASU2(gxy+AU2(8,0)),AF4(c1)); // --------------------------- // - sRGB with slightly better quality and higher cost. // - Use texelFetch() with sRGB format (VK_FORMAT_R8G8B8A8_SRGB) for loads (gets linear into shader). // - Store to destination using UNORM (not sRGB) stores and do the linear to sRGB conversion in the shader. // - Modifications: // // Use texel fetch instead of image load (on GCN this will translate into an image load in the driver). // // Hardware has sRGB to linear on loads (but in API only for read-only, aka texture instead of UAV/image). // AF3 CasLoad(ASU2 p){return texelFetch(texSrc,p,0).rgb;} // ... // // Do linear to sRGB before store (GPU lacking hardware conversion support for linear to sRGB on store). // c.r=AToSrgbF1(c.r);c.g=AToSrgbF1(c.g);c.b=AToSrgbF1(c.b); // imageStore(imgDst,ASU2(gxy),c); // ... // // And for packed. // CasFilterH(cR,cG,cB,gxy,const0,const1,true); // cR=AToSrgbH2(cR);cG=AToSrgbH2(cG);cB=AToSrgbH2(cB); // CasDepack(c0,c1,cR,cG,cB); // imageStore(img[0],ASU2(gxy),AF4(c0)); // imageStore(img[0],ASU2(gxy+AU2(8,0)),AF4(c1)); // --------------------------- // - HDR10 output via scRGB. // - Pass before CAS needs to write out linear Rec.2020 colorspace output (all positive values). // - Write to FP16 with {0 to 1} mapped to {0 to maxNits} nits. // - Where 'maxNits' is typically not 10000. // - Instead set 'maxNits' to the nits level that the HDR TV starts to clip white. // - This can be even as low as 1000 nits on some HDR TVs. // - After CAS do matrix multiply to take Rec.2020 back to sRGB and multiply by 'maxNits/80.0'. // - Showing GPU code below to generate constants, likely most need to use CPU code instead. // - Keeping the GPU code here because it is easier to read in these docs. // - Can use 'lpm.h' source to generate the conversion matrix for Rec.2020 to sRGB: // // Output conversion matrix from sRGB to Rec.2020. // AF3 conR,conG,conB; // // Working space temporaries (Rec.2020). // AF3 rgbToXyzXW;AF3 rgbToXyzYW;AF3 rgbToXyzZW; // LpmColRgbToXyz(rgbToXyzXW,rgbToXyzYW,rgbToXyzZW,lpmCol2020R,lpmCol2020G,lpmCol2020B,lpmColD65); // // Output space temporaries (Rec.709, same as sRGB primaries). // AF3 rgbToXyzXO;AF3 rgbToXyzYO;AF3 rgbToXyzZO; // LpmColRgbToXyz(rgbToXyzXO,rgbToXyzYO,rgbToXyzZO,lpmCol709R,lpmCol709G,lpmCol709B,lpmColD65); // AF3 xyzToRgbRO;AF3 xyzToRgbGO;AF3 xyzToRgbBO; // LpmMatInv3x3(xyzToRgbRO,xyzToRgbGO,xyzToRgbBO,rgbToXyzXO,rgbToXyzYO,rgbToXyzZO); // // Generate the matrix. // LpmMatMul3x3(conR,conG,conB,xyzToRgbRO,xyzToRgbGO,xyzToRgbBO,rgbToXyzXW,rgbToXyzYW,rgbToXyzZW); // - Adjust the conversion matrix for the multiply by 'maxNits/80.0'. // // After this the constants can be stored into a constant buffer. // AF1 conScale=maxNits*ARcpF1(80.0); // conR*=conScale;conG*=conScale;conB*=conScale; // - After CAS do the matrix multiply (passing the fetched constants into the shader). // outputR=dot(AF3(colorR,colorG,colorB),conR); // outputG=dot(AF3(colorR,colorG,colorB),conG); // outputB=dot(AF3(colorR,colorG,colorB),conB); // - Hopefully no developer is taking scRGB as input to CAS. // - If that was the case, the conversion matrix from sRGB to Rec.2020 can be built changing the above code. // - Swap the 'lpmCol709*' and 'lpmCol2020*' inputs to LpmColRgbToXyz(). // - Then scale by '80.0/maxNits' instead of 'maxNits/80.0'. // --------------------------- // - HDR10 output via native 10:10:10:2. // - Pass before CAS needs to write out linear Rec.2020 colorspace output (all positive values). // - Write to FP16 with {0 to 1} mapped to {0 to maxNits} nits. // - Where 'maxNits' is typically not 10000. // - Instead set 'maxNits' to the nits level that the HDR TV starts to clip white. // - This can be even as low as 1000 nits on some HDR TVs. // - Hopefully no developer needs to take PQ as input here, but if so can use A to convert PQ to linear: // // Where 'k0' is a constant of 'maxNits/10000.0'. // colorR=AFromPqF1(colorR*k0); // colorG=AFromPqF1(colorG*k0); // colorB=AFromPqF1(colorB*k0); // - After CAS convert from linear to PQ. // // Where 'k1' is a constant of '10000.0/maxNits'. // colorR=AToPqF1(colorR*k1); // colorG=AToPqF1(colorG*k1); // colorB=AToPqF1(colorB*k1); // --------------------------- // - Example of a bad idea for CAS input design. // - Have the pass before CAS store out in 10:10:10:2 UNORM with gamma 2.0. // - Store the output of CAS with sRGB to linear conversion, or with a gamma 2.2 conversion for FreeSync2 native. // - This will drop precision because the inputs had been quantized to 10-bit, // and the output is using a different tonal transform, // so inputs and outputs won't align for similar values. // - It might be "ok" for 8-bit/channel CAS output, but definately not a good idea for 10-bit/channel output. //------------------------------------------------------------------------------------------------------------------------------ // ALGORITHM DESCRIPTION // ===================== // This describes the algorithm with CAS_BETTER_DIAGONALS defined. // The default is with CAS_BETTER_DIAGONALS not defined (which is faster). // Starting with no scaling. // CAS fetches a 3x3 neighborhood around the pixel 'e', // a b c // d(e)f // g h i // It then computes a 'soft' minimum and maximum, // a b c b // d e f * 0.5 + d e f * 0.5 // g h i h // The minimum and maximums give an idea of local contrast. // --- 1.0 ^ // | | <-- This minimum distance to the signal limit is divided by MAX to get a base sharpening amount 'A'. // --- MAX v // | // | // --- MIN ^ // | | <-- The MIN side is more distant in this example so it is not used, but for dark colors it would be used. // | | // --- 0.0 v // The base sharpening amount 'A' from above is shaped with a sqrt(). // This 'A' ranges from 0 := no sharpening, to 1 := full sharpening. // Then 'A' is scaled by the sharpness knob while being transformed to a negative lobe (values from -1/5 to -1/8 for A=1). // The final filter kernel looks like this, // 0 A 0 // A 1 A <-- Center is always 1.0, followed by the negative lobe 'A' in a ring, and windowed into a circle with the 0.0s. // 0 A 0 // The local neighborhood is then multiplied by the kernel weights, summed and divided by the sum of the kernel weights. // The high quality path computes filter weights per channel. // The low quality path uses the green channel's filter weights to compute the 'A' factor for all channels. // --------------------- // The scaling path is a little more complex. // It starts by fetching the 4x4 neighborhood around the pixel centered between centers of pixels {f,g,j,k}, // a b c d // e(f g)h // i(j k)l // m n o p // The algorithm then computes the no-scaling result for {f,g,j,k}. // It then interpolates between those no-scaling results. // The interpolation is adaptive. // To hide bilinear interpolation and restore diagonals, it weights bilinear weights by 1/(const+contrast). // Where 'contrast' is the soft 'max-min'. // This makes edges thin out a little. // --------------------- // Without CAS_BETTER_DIAGONALS defined, the algorithm is a little faster. // Instead of using the 3x3 "box" with the 5-tap "circle" this uses just the "circle". // Drops to 5 texture fetches for no-scaling. // Drops to 12 texture fetches for scaling. // Drops a bunch of math. //------------------------------------------------------------------------------------------------------------------------------ // IDEAS FOR FUTURE // ================ // - Avoid V_CVT's by using denormals. // - Manually pack FP16 literals. //------------------------------------------------------------------------------------------------------------------------------ // CHANGE LOG // ========== // 20190610 - Misc documentation cleanup. // 20190609 - Removed lowQuality bool, improved scaling logic. // 20190530 - Unified CPU/GPU setup code, using new ffx_a.h, faster, define CAS_BETTER_DIAGONALS to get older slower one. // 20190529 - Missing a good way to re-interpret packed in HLSL, so disabling approximation optimizations for now. // 20190528 - Fixed so GPU CasSetup() generates half data all the time. // 20190527 - Implemented approximations for rcp() and sqrt(). // 20190524 - New algorithm, adjustable sharpness, scaling to 4x area. Fixed checker debug for no-scaling only. // 20190521 - Updated file naming. // 20190516 - Updated docs, fixed workaround, fixed no-scaling quality issue, removed gamma2 and generalized as CasInput*(). // 20190510 - Made the dispatch example safely round up for images that are not a multiple of 16x16. // 20190507 - Fixed typo bug in CAS_DEBUG_CHECKER, fixed sign typo in the docs. // 20190503 - Setup temporary workaround for compiler bug. // 20190502 - Added argument for 'gamma2' path so input transform in that case runs packed. // 20190426 - Improved documentation on format specific cases, etc. // 20190425 - Updated/corrected documentation. // 20190405 - Added CAS_PACKED_ONLY, misc bug fixes. // 20190404 - Updated for the new a.h header. //============================================================================================================================== // This is the practical limit for the algorithm's scaling ability (quality is limited by 3x3 taps). Example resolutions, // 1280x720 -> 1080p = 2.25x area // 1536x864 -> 1080p = 1.56x area // 1792x1008 -> 1440p = 2.04x area // 1920x1080 -> 1440p = 1.78x area // 1920x1080 -> 4K = 4.0x area // 2048x1152 -> 1440p = 1.56x area // 2560x1440 -> 4K = 2.25x area // 3072x1728 -> 4K = 1.56x area #define CAS_AREA_LIMIT 4.0 //------------------------------------------------------------------------------------------------------------------------------ // Pass in output and input resolution in pixels. // This returns true if CAS supports scaling in the given configuration. AP1 CasSupportScaling(AF1 outX,AF1 outY,AF1 inX,AF1 inY){return ((outX*outY)*ARcpF1(inX*inY))<=CAS_AREA_LIMIT;} //============================================================================================================================== // Call to setup required constant values (works on CPU or GPU). A_STATIC void CasSetup( outAU4 const0, outAU4 const1, AF1 sharpness, // 0 := default (lower ringing), 1 := maximum (higest ringing) AF1 inputSizeInPixelsX, AF1 inputSizeInPixelsY, AF1 outputSizeInPixelsX, AF1 outputSizeInPixelsY){ // Scaling terms. const0[0]=AU1_AF1(inputSizeInPixelsX*ARcpF1(outputSizeInPixelsX)); const0[1]=AU1_AF1(inputSizeInPixelsY*ARcpF1(outputSizeInPixelsY)); const0[2]=AU1_AF1(AF1_(0.5)*inputSizeInPixelsX*ARcpF1(outputSizeInPixelsX)-AF1_(0.5)); const0[3]=AU1_AF1(AF1_(0.5)*inputSizeInPixelsY*ARcpF1(outputSizeInPixelsY)-AF1_(0.5)); // Sharpness value. AF1 sharp=-ARcpF1(ALerpF1(8.0,5.0,ASatF1(sharpness))); varAF2(hSharp)=initAF2(sharp,0.0); const1[0]=AU1_AF1(sharp); const1[1]=AU1_AH2_AF2(hSharp); const1[2]=AU1_AF1(AF1_(8.0)*inputSizeInPixelsX*ARcpF1(outputSizeInPixelsX)); const1[3]=0u;} //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // NON-PACKED VERSION //============================================================================================================================== #ifdef A_GPU #ifdef CAS_PACKED_ONLY // Avoid compiler error. AF3 CasLoad(ASU2 p){return AF3(0.0,0.0,0.0);} void CasInput(inout AF1 r,inout AF1 g,inout AF1 b){} #endif //------------------------------------------------------------------------------------------------------------------------------ void CasFilter( out AF1 pixR, // Output values, non-vector so port between CasFilter() and CasFilterH() is easy. out AF1 pixG, out AF1 pixB, AU2 ip, // Integer pixel position in output. AU4 const0, // Constants generated by CasSetup(). AU4 const1, AP1 noScaling){ // Must be a compile-time literal value, true = sharpen only (no resize). //------------------------------------------------------------------------------------------------------------------------------ // Debug a checker pattern of on/off tiles for visual inspection. #ifdef CAS_DEBUG_CHECKER if((((ip.x^ip.y)>>8u)&1u)==0u){AF3 pix0=CasLoad(ASU2(ip)); pixR=pix0.r;pixG=pix0.g;pixB=pix0.b;CasInput(pixR,pixG,pixB);return;} #endif //------------------------------------------------------------------------------------------------------------------------------ // No scaling algorithm uses minimal 3x3 pixel neighborhood. if(noScaling){ // a b c // d e f // g h i ASU2 sp=ASU2(ip); AF3 a=CasLoad(sp+ASU2(-1,-1)); AF3 b=CasLoad(sp+ASU2( 0,-1)); AF3 c=CasLoad(sp+ASU2( 1,-1)); AF3 d=CasLoad(sp+ASU2(-1, 0)); AF3 e=CasLoad(sp); AF3 f=CasLoad(sp+ASU2( 1, 0)); AF3 g=CasLoad(sp+ASU2(-1, 1)); AF3 h=CasLoad(sp+ASU2( 0, 1)); AF3 i=CasLoad(sp+ASU2( 1, 1)); // Run optional input transform. CasInput(a.r,a.g,a.b); CasInput(b.r,b.g,b.b); CasInput(c.r,c.g,c.b); CasInput(d.r,d.g,d.b); CasInput(e.r,e.g,e.b); CasInput(f.r,f.g,f.b); CasInput(g.r,g.g,g.b); CasInput(h.r,h.g,h.b); CasInput(i.r,i.g,i.b); // Soft min and max. // a b c b // d e f * 0.5 + d e f * 0.5 // g h i h // These are 2.0x bigger (factored out the extra multiply). AF1 mnR=AMin3F1(AMin3F1(d.r,e.r,f.r),b.r,h.r); AF1 mnG=AMin3F1(AMin3F1(d.g,e.g,f.g),b.g,h.g); AF1 mnB=AMin3F1(AMin3F1(d.b,e.b,f.b),b.b,h.b); #ifdef CAS_BETTER_DIAGONALS AF1 mnR2=AMin3F1(AMin3F1(mnR,a.r,c.r),g.r,i.r); AF1 mnG2=AMin3F1(AMin3F1(mnG,a.g,c.g),g.g,i.g); AF1 mnB2=AMin3F1(AMin3F1(mnB,a.b,c.b),g.b,i.b); mnR=mnR+mnR2; mnG=mnG+mnG2; mnB=mnB+mnB2; #endif AF1 mxR=AMax3F1(AMax3F1(d.r,e.r,f.r),b.r,h.r); AF1 mxG=AMax3F1(AMax3F1(d.g,e.g,f.g),b.g,h.g); AF1 mxB=AMax3F1(AMax3F1(d.b,e.b,f.b),b.b,h.b); #ifdef CAS_BETTER_DIAGONALS AF1 mxR2=AMax3F1(AMax3F1(mxR,a.r,c.r),g.r,i.r); AF1 mxG2=AMax3F1(AMax3F1(mxG,a.g,c.g),g.g,i.g); AF1 mxB2=AMax3F1(AMax3F1(mxB,a.b,c.b),g.b,i.b); mxR=mxR+mxR2; mxG=mxG+mxG2; mxB=mxB+mxB2; #endif // Smooth minimum distance to signal limit divided by smooth max. #ifdef CAS_GO_SLOWER AF1 rcpMR=ARcpF1(mxR); AF1 rcpMG=ARcpF1(mxG); AF1 rcpMB=ARcpF1(mxB); #else AF1 rcpMR=APrxLoRcpF1(mxR); AF1 rcpMG=APrxLoRcpF1(mxG); AF1 rcpMB=APrxLoRcpF1(mxB); #endif #ifdef CAS_BETTER_DIAGONALS AF1 ampR=ASatF1(min(mnR,AF1_(2.0)-mxR)*rcpMR); AF1 ampG=ASatF1(min(mnG,AF1_(2.0)-mxG)*rcpMG); AF1 ampB=ASatF1(min(mnB,AF1_(2.0)-mxB)*rcpMB); #else AF1 ampR=ASatF1(min(mnR,AF1_(1.0)-mxR)*rcpMR); AF1 ampG=ASatF1(min(mnG,AF1_(1.0)-mxG)*rcpMG); AF1 ampB=ASatF1(min(mnB,AF1_(1.0)-mxB)*rcpMB); #endif // Shaping amount of sharpening. #ifdef CAS_GO_SLOWER ampR=sqrt(ampR); ampG=sqrt(ampG); ampB=sqrt(ampB); #else ampR=APrxLoSqrtF1(ampR); ampG=APrxLoSqrtF1(ampG); ampB=APrxLoSqrtF1(ampB); #endif // Filter shape. // 0 w 0 // w 1 w // 0 w 0 AF1 peak=AF1_AU1(const1.x); AF1 wR=ampR*peak; AF1 wG=ampG*peak; AF1 wB=ampB*peak; // Filter. #ifndef CAS_SLOW // Using green coef only, depending on dead code removal to strip out the extra overhead. #ifdef CAS_GO_SLOWER AF1 rcpWeight=ARcpF1(AF1_(1.0)+AF1_(4.0)*wG); #else AF1 rcpWeight=APrxMedRcpF1(AF1_(1.0)+AF1_(4.0)*wG); #endif pixR=ASatF1((b.r*wG+d.r*wG+f.r*wG+h.r*wG+e.r)*rcpWeight); pixG=ASatF1((b.g*wG+d.g*wG+f.g*wG+h.g*wG+e.g)*rcpWeight); pixB=ASatF1((b.b*wG+d.b*wG+f.b*wG+h.b*wG+e.b)*rcpWeight); #else #ifdef CAS_GO_SLOWER AF1 rcpWeightR=ARcpF1(AF1_(1.0)+AF1_(4.0)*wR); AF1 rcpWeightG=ARcpF1(AF1_(1.0)+AF1_(4.0)*wG); AF1 rcpWeightB=ARcpF1(AF1_(1.0)+AF1_(4.0)*wB); #else AF1 rcpWeightR=APrxMedRcpF1(AF1_(1.0)+AF1_(4.0)*wR); AF1 rcpWeightG=APrxMedRcpF1(AF1_(1.0)+AF1_(4.0)*wG); AF1 rcpWeightB=APrxMedRcpF1(AF1_(1.0)+AF1_(4.0)*wB); #endif pixR=ASatF1((b.r*wR+d.r*wR+f.r*wR+h.r*wR+e.r)*rcpWeightR); pixG=ASatF1((b.g*wG+d.g*wG+f.g*wG+h.g*wG+e.g)*rcpWeightG); pixB=ASatF1((b.b*wB+d.b*wB+f.b*wB+h.b*wB+e.b)*rcpWeightB); #endif return;} //------------------------------------------------------------------------------------------------------------------------------ // Scaling algorithm adaptively interpolates between nearest 4 results of the non-scaling algorithm. // a b c d // e f g h // i j k l // m n o p // Working these 4 results. // +-----+-----+ // | | | // | f..|..g | // | . | . | // +-----+-----+ // | . | . | // | j..|..k | // | | | // +-----+-----+ AF2 pp=AF2(ip)*AF2_AU2(const0.xy)+AF2_AU2(const0.zw); AF2 fp=floor(pp); pp-=fp; ASU2 sp=ASU2(fp); AF3 a=CasLoad(sp+ASU2(-1,-1)); AF3 b=CasLoad(sp+ASU2( 0,-1)); AF3 e=CasLoad(sp+ASU2(-1, 0)); AF3 f=CasLoad(sp); AF3 c=CasLoad(sp+ASU2( 1,-1)); AF3 d=CasLoad(sp+ASU2( 2,-1)); AF3 g=CasLoad(sp+ASU2( 1, 0)); AF3 h=CasLoad(sp+ASU2( 2, 0)); AF3 i=CasLoad(sp+ASU2(-1, 1)); AF3 j=CasLoad(sp+ASU2( 0, 1)); AF3 m=CasLoad(sp+ASU2(-1, 2)); AF3 n=CasLoad(sp+ASU2( 0, 2)); AF3 k=CasLoad(sp+ASU2( 1, 1)); AF3 l=CasLoad(sp+ASU2( 2, 1)); AF3 o=CasLoad(sp+ASU2( 1, 2)); AF3 p=CasLoad(sp+ASU2( 2, 2)); // Run optional input transform. CasInput(a.r,a.g,a.b); CasInput(b.r,b.g,b.b); CasInput(c.r,c.g,c.b); CasInput(d.r,d.g,d.b); CasInput(e.r,e.g,e.b); CasInput(f.r,f.g,f.b); CasInput(g.r,g.g,g.b); CasInput(h.r,h.g,h.b); CasInput(i.r,i.g,i.b); CasInput(j.r,j.g,j.b); CasInput(k.r,k.g,k.b); CasInput(l.r,l.g,l.b); CasInput(m.r,m.g,m.b); CasInput(n.r,n.g,n.b); CasInput(o.r,o.g,o.b); CasInput(p.r,p.g,p.b); // Soft min and max. // These are 2.0x bigger (factored out the extra multiply). // a b c b // e f g * 0.5 + e f g * 0.5 [F] // i j k j AF1 mnfR=AMin3F1(AMin3F1(b.r,e.r,f.r),g.r,j.r); AF1 mnfG=AMin3F1(AMin3F1(b.g,e.g,f.g),g.g,j.g); AF1 mnfB=AMin3F1(AMin3F1(b.b,e.b,f.b),g.b,j.b); #ifdef CAS_BETTER_DIAGONALS AF1 mnfR2=AMin3F1(AMin3F1(mnfR,a.r,c.r),i.r,k.r); AF1 mnfG2=AMin3F1(AMin3F1(mnfG,a.g,c.g),i.g,k.g); AF1 mnfB2=AMin3F1(AMin3F1(mnfB,a.b,c.b),i.b,k.b); mnfR=mnfR+mnfR2; mnfG=mnfG+mnfG2; mnfB=mnfB+mnfB2; #endif AF1 mxfR=AMax3F1(AMax3F1(b.r,e.r,f.r),g.r,j.r); AF1 mxfG=AMax3F1(AMax3F1(b.g,e.g,f.g),g.g,j.g); AF1 mxfB=AMax3F1(AMax3F1(b.b,e.b,f.b),g.b,j.b); #ifdef CAS_BETTER_DIAGONALS AF1 mxfR2=AMax3F1(AMax3F1(mxfR,a.r,c.r),i.r,k.r); AF1 mxfG2=AMax3F1(AMax3F1(mxfG,a.g,c.g),i.g,k.g); AF1 mxfB2=AMax3F1(AMax3F1(mxfB,a.b,c.b),i.b,k.b); mxfR=mxfR+mxfR2; mxfG=mxfG+mxfG2; mxfB=mxfB+mxfB2; #endif // b c d c // f g h * 0.5 + f g h * 0.5 [G] // j k l k AF1 mngR=AMin3F1(AMin3F1(c.r,f.r,g.r),h.r,k.r); AF1 mngG=AMin3F1(AMin3F1(c.g,f.g,g.g),h.g,k.g); AF1 mngB=AMin3F1(AMin3F1(c.b,f.b,g.b),h.b,k.b); #ifdef CAS_BETTER_DIAGONALS AF1 mngR2=AMin3F1(AMin3F1(mngR,b.r,d.r),j.r,l.r); AF1 mngG2=AMin3F1(AMin3F1(mngG,b.g,d.g),j.g,l.g); AF1 mngB2=AMin3F1(AMin3F1(mngB,b.b,d.b),j.b,l.b); mngR=mngR+mngR2; mngG=mngG+mngG2; mngB=mngB+mngB2; #endif AF1 mxgR=AMax3F1(AMax3F1(c.r,f.r,g.r),h.r,k.r); AF1 mxgG=AMax3F1(AMax3F1(c.g,f.g,g.g),h.g,k.g); AF1 mxgB=AMax3F1(AMax3F1(c.b,f.b,g.b),h.b,k.b); #ifdef CAS_BETTER_DIAGONALS AF1 mxgR2=AMax3F1(AMax3F1(mxgR,b.r,d.r),j.r,l.r); AF1 mxgG2=AMax3F1(AMax3F1(mxgG,b.g,d.g),j.g,l.g); AF1 mxgB2=AMax3F1(AMax3F1(mxgB,b.b,d.b),j.b,l.b); mxgR=mxgR+mxgR2; mxgG=mxgG+mxgG2; mxgB=mxgB+mxgB2; #endif // e f g f // i j k * 0.5 + i j k * 0.5 [J] // m n o n AF1 mnjR=AMin3F1(AMin3F1(f.r,i.r,j.r),k.r,n.r); AF1 mnjG=AMin3F1(AMin3F1(f.g,i.g,j.g),k.g,n.g); AF1 mnjB=AMin3F1(AMin3F1(f.b,i.b,j.b),k.b,n.b); #ifdef CAS_BETTER_DIAGONALS AF1 mnjR2=AMin3F1(AMin3F1(mnjR,e.r,g.r),m.r,o.r); AF1 mnjG2=AMin3F1(AMin3F1(mnjG,e.g,g.g),m.g,o.g); AF1 mnjB2=AMin3F1(AMin3F1(mnjB,e.b,g.b),m.b,o.b); mnjR=mnjR+mnjR2; mnjG=mnjG+mnjG2; mnjB=mnjB+mnjB2; #endif AF1 mxjR=AMax3F1(AMax3F1(f.r,i.r,j.r),k.r,n.r); AF1 mxjG=AMax3F1(AMax3F1(f.g,i.g,j.g),k.g,n.g); AF1 mxjB=AMax3F1(AMax3F1(f.b,i.b,j.b),k.b,n.b); #ifdef CAS_BETTER_DIAGONALS AF1 mxjR2=AMax3F1(AMax3F1(mxjR,e.r,g.r),m.r,o.r); AF1 mxjG2=AMax3F1(AMax3F1(mxjG,e.g,g.g),m.g,o.g); AF1 mxjB2=AMax3F1(AMax3F1(mxjB,e.b,g.b),m.b,o.b); mxjR=mxjR+mxjR2; mxjG=mxjG+mxjG2; mxjB=mxjB+mxjB2; #endif // f g h g // j k l * 0.5 + j k l * 0.5 [K] // n o p o AF1 mnkR=AMin3F1(AMin3F1(g.r,j.r,k.r),l.r,o.r); AF1 mnkG=AMin3F1(AMin3F1(g.g,j.g,k.g),l.g,o.g); AF1 mnkB=AMin3F1(AMin3F1(g.b,j.b,k.b),l.b,o.b); #ifdef CAS_BETTER_DIAGONALS AF1 mnkR2=AMin3F1(AMin3F1(mnkR,f.r,h.r),n.r,p.r); AF1 mnkG2=AMin3F1(AMin3F1(mnkG,f.g,h.g),n.g,p.g); AF1 mnkB2=AMin3F1(AMin3F1(mnkB,f.b,h.b),n.b,p.b); mnkR=mnkR+mnkR2; mnkG=mnkG+mnkG2; mnkB=mnkB+mnkB2; #endif AF1 mxkR=AMax3F1(AMax3F1(g.r,j.r,k.r),l.r,o.r); AF1 mxkG=AMax3F1(AMax3F1(g.g,j.g,k.g),l.g,o.g); AF1 mxkB=AMax3F1(AMax3F1(g.b,j.b,k.b),l.b,o.b); #ifdef CAS_BETTER_DIAGONALS AF1 mxkR2=AMax3F1(AMax3F1(mxkR,f.r,h.r),n.r,p.r); AF1 mxkG2=AMax3F1(AMax3F1(mxkG,f.g,h.g),n.g,p.g); AF1 mxkB2=AMax3F1(AMax3F1(mxkB,f.b,h.b),n.b,p.b); mxkR=mxkR+mxkR2; mxkG=mxkG+mxkG2; mxkB=mxkB+mxkB2; #endif // Smooth minimum distance to signal limit divided by smooth max. #ifdef CAS_GO_SLOWER AF1 rcpMfR=ARcpF1(mxfR); AF1 rcpMfG=ARcpF1(mxfG); AF1 rcpMfB=ARcpF1(mxfB); AF1 rcpMgR=ARcpF1(mxgR); AF1 rcpMgG=ARcpF1(mxgG); AF1 rcpMgB=ARcpF1(mxgB); AF1 rcpMjR=ARcpF1(mxjR); AF1 rcpMjG=ARcpF1(mxjG); AF1 rcpMjB=ARcpF1(mxjB); AF1 rcpMkR=ARcpF1(mxkR); AF1 rcpMkG=ARcpF1(mxkG); AF1 rcpMkB=ARcpF1(mxkB); #else AF1 rcpMfR=APrxLoRcpF1(mxfR); AF1 rcpMfG=APrxLoRcpF1(mxfG); AF1 rcpMfB=APrxLoRcpF1(mxfB); AF1 rcpMgR=APrxLoRcpF1(mxgR); AF1 rcpMgG=APrxLoRcpF1(mxgG); AF1 rcpMgB=APrxLoRcpF1(mxgB); AF1 rcpMjR=APrxLoRcpF1(mxjR); AF1 rcpMjG=APrxLoRcpF1(mxjG); AF1 rcpMjB=APrxLoRcpF1(mxjB); AF1 rcpMkR=APrxLoRcpF1(mxkR); AF1 rcpMkG=APrxLoRcpF1(mxkG); AF1 rcpMkB=APrxLoRcpF1(mxkB); #endif #ifdef CAS_BETTER_DIAGONALS AF1 ampfR=ASatF1(min(mnfR,AF1_(2.0)-mxfR)*rcpMfR); AF1 ampfG=ASatF1(min(mnfG,AF1_(2.0)-mxfG)*rcpMfG); AF1 ampfB=ASatF1(min(mnfB,AF1_(2.0)-mxfB)*rcpMfB); AF1 ampgR=ASatF1(min(mngR,AF1_(2.0)-mxgR)*rcpMgR); AF1 ampgG=ASatF1(min(mngG,AF1_(2.0)-mxgG)*rcpMgG); AF1 ampgB=ASatF1(min(mngB,AF1_(2.0)-mxgB)*rcpMgB); AF1 ampjR=ASatF1(min(mnjR,AF1_(2.0)-mxjR)*rcpMjR); AF1 ampjG=ASatF1(min(mnjG,AF1_(2.0)-mxjG)*rcpMjG); AF1 ampjB=ASatF1(min(mnjB,AF1_(2.0)-mxjB)*rcpMjB); AF1 ampkR=ASatF1(min(mnkR,AF1_(2.0)-mxkR)*rcpMkR); AF1 ampkG=ASatF1(min(mnkG,AF1_(2.0)-mxkG)*rcpMkG); AF1 ampkB=ASatF1(min(mnkB,AF1_(2.0)-mxkB)*rcpMkB); #else AF1 ampfR=ASatF1(min(mnfR,AF1_(1.0)-mxfR)*rcpMfR); AF1 ampfG=ASatF1(min(mnfG,AF1_(1.0)-mxfG)*rcpMfG); AF1 ampfB=ASatF1(min(mnfB,AF1_(1.0)-mxfB)*rcpMfB); AF1 ampgR=ASatF1(min(mngR,AF1_(1.0)-mxgR)*rcpMgR); AF1 ampgG=ASatF1(min(mngG,AF1_(1.0)-mxgG)*rcpMgG); AF1 ampgB=ASatF1(min(mngB,AF1_(1.0)-mxgB)*rcpMgB); AF1 ampjR=ASatF1(min(mnjR,AF1_(1.0)-mxjR)*rcpMjR); AF1 ampjG=ASatF1(min(mnjG,AF1_(1.0)-mxjG)*rcpMjG); AF1 ampjB=ASatF1(min(mnjB,AF1_(1.0)-mxjB)*rcpMjB); AF1 ampkR=ASatF1(min(mnkR,AF1_(1.0)-mxkR)*rcpMkR); AF1 ampkG=ASatF1(min(mnkG,AF1_(1.0)-mxkG)*rcpMkG); AF1 ampkB=ASatF1(min(mnkB,AF1_(1.0)-mxkB)*rcpMkB); #endif // Shaping amount of sharpening. #ifdef CAS_GO_SLOWER ampfR=sqrt(ampfR); ampfG=sqrt(ampfG); ampfB=sqrt(ampfB); ampgR=sqrt(ampgR); ampgG=sqrt(ampgG); ampgB=sqrt(ampgB); ampjR=sqrt(ampjR); ampjG=sqrt(ampjG); ampjB=sqrt(ampjB); ampkR=sqrt(ampkR); ampkG=sqrt(ampkG); ampkB=sqrt(ampkB); #else ampfR=APrxLoSqrtF1(ampfR); ampfG=APrxLoSqrtF1(ampfG); ampfB=APrxLoSqrtF1(ampfB); ampgR=APrxLoSqrtF1(ampgR); ampgG=APrxLoSqrtF1(ampgG); ampgB=APrxLoSqrtF1(ampgB); ampjR=APrxLoSqrtF1(ampjR); ampjG=APrxLoSqrtF1(ampjG); ampjB=APrxLoSqrtF1(ampjB); ampkR=APrxLoSqrtF1(ampkR); ampkG=APrxLoSqrtF1(ampkG); ampkB=APrxLoSqrtF1(ampkB); #endif // Filter shape. // 0 w 0 // w 1 w // 0 w 0 AF1 peak=AF1_AU1(const1.x); AF1 wfR=ampfR*peak; AF1 wfG=ampfG*peak; AF1 wfB=ampfB*peak; AF1 wgR=ampgR*peak; AF1 wgG=ampgG*peak; AF1 wgB=ampgB*peak; AF1 wjR=ampjR*peak; AF1 wjG=ampjG*peak; AF1 wjB=ampjB*peak; AF1 wkR=ampkR*peak; AF1 wkG=ampkG*peak; AF1 wkB=ampkB*peak; // Blend between 4 results. // s t // u v AF1 s=(AF1_(1.0)-pp.x)*(AF1_(1.0)-pp.y); AF1 t= pp.x *(AF1_(1.0)-pp.y); AF1 u=(AF1_(1.0)-pp.x)* pp.y ; AF1 v= pp.x * pp.y ; // Thin edges to hide bilinear interpolation (helps diagonals). AF1 thinB=1.0/32.0; #ifdef CAS_GO_SLOWER s*=ARcpF1(thinB+(mxfG-mnfG)); t*=ARcpF1(thinB+(mxgG-mngG)); u*=ARcpF1(thinB+(mxjG-mnjG)); v*=ARcpF1(thinB+(mxkG-mnkG)); #else s*=APrxLoRcpF1(thinB+(mxfG-mnfG)); t*=APrxLoRcpF1(thinB+(mxgG-mngG)); u*=APrxLoRcpF1(thinB+(mxjG-mnjG)); v*=APrxLoRcpF1(thinB+(mxkG-mnkG)); #endif // Final weighting. // b c // e f g h // i j k l // n o // _____ _____ _____ _____ // fs gt // // _____ _____ _____ _____ // fs s gt fs t gt // ju kv // _____ _____ _____ _____ // fs gt // ju u kv ju v kv // _____ _____ _____ _____ // // ju kv AF1 qbeR=wfR*s; AF1 qbeG=wfG*s; AF1 qbeB=wfB*s; AF1 qchR=wgR*t; AF1 qchG=wgG*t; AF1 qchB=wgB*t; AF1 qfR=wgR*t+wjR*u+s; AF1 qfG=wgG*t+wjG*u+s; AF1 qfB=wgB*t+wjB*u+s; AF1 qgR=wfR*s+wkR*v+t; AF1 qgG=wfG*s+wkG*v+t; AF1 qgB=wfB*s+wkB*v+t; AF1 qjR=wfR*s+wkR*v+u; AF1 qjG=wfG*s+wkG*v+u; AF1 qjB=wfB*s+wkB*v+u; AF1 qkR=wgR*t+wjR*u+v; AF1 qkG=wgG*t+wjG*u+v; AF1 qkB=wgB*t+wjB*u+v; AF1 qinR=wjR*u; AF1 qinG=wjG*u; AF1 qinB=wjB*u; AF1 qloR=wkR*v; AF1 qloG=wkG*v; AF1 qloB=wkB*v; // Filter. #ifndef CAS_SLOW // Using green coef only, depending on dead code removal to strip out the extra overhead. #ifdef CAS_GO_SLOWER AF1 rcpWG=ARcpF1(AF1_(2.0)*qbeG+AF1_(2.0)*qchG+AF1_(2.0)*qinG+AF1_(2.0)*qloG+qfG+qgG+qjG+qkG); #else AF1 rcpWG=APrxMedRcpF1(AF1_(2.0)*qbeG+AF1_(2.0)*qchG+AF1_(2.0)*qinG+AF1_(2.0)*qloG+qfG+qgG+qjG+qkG); #endif pixR=ASatF1((b.r*qbeG+e.r*qbeG+c.r*qchG+h.r*qchG+i.r*qinG+n.r*qinG+l.r*qloG+o.r*qloG+f.r*qfG+g.r*qgG+j.r*qjG+k.r*qkG)*rcpWG); pixG=ASatF1((b.g*qbeG+e.g*qbeG+c.g*qchG+h.g*qchG+i.g*qinG+n.g*qinG+l.g*qloG+o.g*qloG+f.g*qfG+g.g*qgG+j.g*qjG+k.g*qkG)*rcpWG); pixB=ASatF1((b.b*qbeG+e.b*qbeG+c.b*qchG+h.b*qchG+i.b*qinG+n.b*qinG+l.b*qloG+o.b*qloG+f.b*qfG+g.b*qgG+j.b*qjG+k.b*qkG)*rcpWG); #else #ifdef CAS_GO_SLOWER AF1 rcpWR=ARcpF1(AF1_(2.0)*qbeR+AF1_(2.0)*qchR+AF1_(2.0)*qinR+AF1_(2.0)*qloR+qfR+qgR+qjR+qkR); AF1 rcpWG=ARcpF1(AF1_(2.0)*qbeG+AF1_(2.0)*qchG+AF1_(2.0)*qinG+AF1_(2.0)*qloG+qfG+qgG+qjG+qkG); AF1 rcpWB=ARcpF1(AF1_(2.0)*qbeB+AF1_(2.0)*qchB+AF1_(2.0)*qinB+AF1_(2.0)*qloB+qfB+qgB+qjB+qkB); #else AF1 rcpWR=APrxMedRcpF1(AF1_(2.0)*qbeR+AF1_(2.0)*qchR+AF1_(2.0)*qinR+AF1_(2.0)*qloR+qfR+qgR+qjR+qkR); AF1 rcpWG=APrxMedRcpF1(AF1_(2.0)*qbeG+AF1_(2.0)*qchG+AF1_(2.0)*qinG+AF1_(2.0)*qloG+qfG+qgG+qjG+qkG); AF1 rcpWB=APrxMedRcpF1(AF1_(2.0)*qbeB+AF1_(2.0)*qchB+AF1_(2.0)*qinB+AF1_(2.0)*qloB+qfB+qgB+qjB+qkB); #endif pixR=ASatF1((b.r*qbeR+e.r*qbeR+c.r*qchR+h.r*qchR+i.r*qinR+n.r*qinR+l.r*qloR+o.r*qloR+f.r*qfR+g.r*qgR+j.r*qjR+k.r*qkR)*rcpWR); pixG=ASatF1((b.g*qbeG+e.g*qbeG+c.g*qchG+h.g*qchG+i.g*qinG+n.g*qinG+l.g*qloG+o.g*qloG+f.g*qfG+g.g*qgG+j.g*qjG+k.g*qkG)*rcpWG); pixB=ASatF1((b.b*qbeB+e.b*qbeB+c.b*qchB+h.b*qchB+i.b*qinB+n.b*qinB+l.b*qloB+o.b*qloB+f.b*qfB+g.b*qgB+j.b*qjB+k.b*qkB)*rcpWB); #endif } #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // PACKED VERSION //============================================================================================================================== #if defined(A_GPU) && defined(A_HALF) // Missing a way to do packed re-interpetation, so must disable approximation optimizations. #ifdef A_HLSL #ifndef CAS_GO_SLOWER #define CAS_GO_SLOWER 1 #endif #endif //============================================================================================================================== // Can be used to convert from packed SOA to AOS for store. void CasDepack(out AH4 pix0,out AH4 pix1,AH2 pixR,AH2 pixG,AH2 pixB){ #ifdef A_HLSL // Invoke a slower path for DX only, since it won't allow uninitialized values. pix0.a=pix1.a=0.0; #endif pix0.rgb=AH3(pixR.x,pixG.x,pixB.x); pix1.rgb=AH3(pixR.y,pixG.y,pixB.y);} //============================================================================================================================== void CasFilterH( // Output values are for 2 8x8 tiles in a 16x8 region. // pix.x = right 8x8 tile // pix.y = left 8x8 tile // This enables later processing to easily be packed as well. out AH2 pixR, out AH2 pixG, out AH2 pixB, AU2 ip, // Integer pixel position in output. AU4 const0, // Constants generated by CasSetup(). AU4 const1, AP1 noScaling){ // Must be a compile-time literal value, true = sharpen only (no resize). //------------------------------------------------------------------------------------------------------------------------------ // Debug a checker pattern of on/off tiles for visual inspection. #ifdef CAS_DEBUG_CHECKER if((((ip.x^ip.y)>>8u)&1u)==0u){AH3 pix0=CasLoadH(ASW2(ip));AH3 pix1=CasLoadH(ASW2(ip)+ASW2(8,0)); pixR=AH2(pix0.r,pix1.r);pixG=AH2(pix0.g,pix1.g);pixB=AH2(pix0.b,pix1.b);CasInputH(pixR,pixG,pixB);return;} #endif //------------------------------------------------------------------------------------------------------------------------------ // No scaling algorithm uses minimal 3x3 pixel neighborhood. if(noScaling){ ASW2 sp0=ASW2(ip); AH3 a0=CasLoadH(sp0+ASW2(-1,-1)); AH3 b0=CasLoadH(sp0+ASW2( 0,-1)); AH3 c0=CasLoadH(sp0+ASW2( 1,-1)); AH3 d0=CasLoadH(sp0+ASW2(-1, 0)); AH3 e0=CasLoadH(sp0); AH3 f0=CasLoadH(sp0+ASW2( 1, 0)); AH3 g0=CasLoadH(sp0+ASW2(-1, 1)); AH3 h0=CasLoadH(sp0+ASW2( 0, 1)); AH3 i0=CasLoadH(sp0+ASW2( 1, 1)); ASW2 sp1=sp0+ASW2(8,0); AH3 a1=CasLoadH(sp1+ASW2(-1,-1)); AH3 b1=CasLoadH(sp1+ASW2( 0,-1)); AH3 c1=CasLoadH(sp1+ASW2( 1,-1)); AH3 d1=CasLoadH(sp1+ASW2(-1, 0)); AH3 e1=CasLoadH(sp1); AH3 f1=CasLoadH(sp1+ASW2( 1, 0)); AH3 g1=CasLoadH(sp1+ASW2(-1, 1)); AH3 h1=CasLoadH(sp1+ASW2( 0, 1)); AH3 i1=CasLoadH(sp1+ASW2( 1, 1)); // AOS to SOA conversion. AH2 aR=AH2(a0.r,a1.r); AH2 aG=AH2(a0.g,a1.g); AH2 aB=AH2(a0.b,a1.b); AH2 bR=AH2(b0.r,b1.r); AH2 bG=AH2(b0.g,b1.g); AH2 bB=AH2(b0.b,b1.b); AH2 cR=AH2(c0.r,c1.r); AH2 cG=AH2(c0.g,c1.g); AH2 cB=AH2(c0.b,c1.b); AH2 dR=AH2(d0.r,d1.r); AH2 dG=AH2(d0.g,d1.g); AH2 dB=AH2(d0.b,d1.b); AH2 eR=AH2(e0.r,e1.r); AH2 eG=AH2(e0.g,e1.g); AH2 eB=AH2(e0.b,e1.b); AH2 fR=AH2(f0.r,f1.r); AH2 fG=AH2(f0.g,f1.g); AH2 fB=AH2(f0.b,f1.b); AH2 gR=AH2(g0.r,g1.r); AH2 gG=AH2(g0.g,g1.g); AH2 gB=AH2(g0.b,g1.b); AH2 hR=AH2(h0.r,h1.r); AH2 hG=AH2(h0.g,h1.g); AH2 hB=AH2(h0.b,h1.b); AH2 iR=AH2(i0.r,i1.r); AH2 iG=AH2(i0.g,i1.g); AH2 iB=AH2(i0.b,i1.b); // Run optional input transform. CasInputH(aR,aG,aB); CasInputH(bR,bG,bB); CasInputH(cR,cG,cB); CasInputH(dR,dG,dB); CasInputH(eR,eG,eB); CasInputH(fR,fG,fB); CasInputH(gR,gG,gB); CasInputH(hR,hG,hB); CasInputH(iR,iG,iB); // Soft min and max. AH2 mnR=min(min(fR,hR),min(min(bR,dR),eR)); AH2 mnG=min(min(fG,hG),min(min(bG,dG),eG)); AH2 mnB=min(min(fB,hB),min(min(bB,dB),eB)); #ifdef CAS_BETTER_DIAGONALS AH2 mnR2=min(min(gR,iR),min(min(aR,cR),mnR)); AH2 mnG2=min(min(gG,iG),min(min(aG,cG),mnG)); AH2 mnB2=min(min(gB,iB),min(min(aB,cB),mnB)); mnR=mnR+mnR2; mnG=mnG+mnG2; mnB=mnB+mnB2; #endif AH2 mxR=max(max(fR,hR),max(max(bR,dR),eR)); AH2 mxG=max(max(fG,hG),max(max(bG,dG),eG)); AH2 mxB=max(max(fB,hB),max(max(bB,dB),eB)); #ifdef CAS_BETTER_DIAGONALS AH2 mxR2=max(max(gR,iR),max(max(aR,cR),mxR)); AH2 mxG2=max(max(gG,iG),max(max(aG,cG),mxG)); AH2 mxB2=max(max(gB,iB),max(max(aB,cB),mxB)); mxR=mxR+mxR2; mxG=mxG+mxG2; mxB=mxB+mxB2; #endif // Smooth minimum distance to signal limit divided by smooth max. #ifdef CAS_GO_SLOWER AH2 rcpMR=ARcpH2(mxR); AH2 rcpMG=ARcpH2(mxG); AH2 rcpMB=ARcpH2(mxB); #else AH2 rcpMR=APrxLoRcpH2(mxR); AH2 rcpMG=APrxLoRcpH2(mxG); AH2 rcpMB=APrxLoRcpH2(mxB); #endif #ifdef CAS_BETTER_DIAGONALS AH2 ampR=ASatH2(min(mnR,AH2_(2.0)-mxR)*rcpMR); AH2 ampG=ASatH2(min(mnG,AH2_(2.0)-mxG)*rcpMG); AH2 ampB=ASatH2(min(mnB,AH2_(2.0)-mxB)*rcpMB); #else AH2 ampR=ASatH2(min(mnR,AH2_(1.0)-mxR)*rcpMR); AH2 ampG=ASatH2(min(mnG,AH2_(1.0)-mxG)*rcpMG); AH2 ampB=ASatH2(min(mnB,AH2_(1.0)-mxB)*rcpMB); #endif // Shaping amount of sharpening. #ifdef CAS_GO_SLOWER ampR=sqrt(ampR); ampG=sqrt(ampG); ampB=sqrt(ampB); #else ampR=APrxLoSqrtH2(ampR); ampG=APrxLoSqrtH2(ampG); ampB=APrxLoSqrtH2(ampB); #endif // Filter shape. AH1 peak=AH2_AU1(const1.y).x; AH2 wR=ampR*AH2_(peak); AH2 wG=ampG*AH2_(peak); AH2 wB=ampB*AH2_(peak); // Filter. #ifndef CAS_SLOW #ifdef CAS_GO_SLOWER AH2 rcpWeight=ARcpH2(AH2_(1.0)+AH2_(4.0)*wG); #else AH2 rcpWeight=APrxMedRcpH2(AH2_(1.0)+AH2_(4.0)*wG); #endif pixR=ASatH2((bR*wG+dR*wG+fR*wG+hR*wG+eR)*rcpWeight); pixG=ASatH2((bG*wG+dG*wG+fG*wG+hG*wG+eG)*rcpWeight); pixB=ASatH2((bB*wG+dB*wG+fB*wG+hB*wG+eB)*rcpWeight); #else #ifdef CAS_GO_SLOWER AH2 rcpWeightR=ARcpH2(AH2_(1.0)+AH2_(4.0)*wR); AH2 rcpWeightG=ARcpH2(AH2_(1.0)+AH2_(4.0)*wG); AH2 rcpWeightB=ARcpH2(AH2_(1.0)+AH2_(4.0)*wB); #else AH2 rcpWeightR=APrxMedRcpH2(AH2_(1.0)+AH2_(4.0)*wR); AH2 rcpWeightG=APrxMedRcpH2(AH2_(1.0)+AH2_(4.0)*wG); AH2 rcpWeightB=APrxMedRcpH2(AH2_(1.0)+AH2_(4.0)*wB); #endif pixR=ASatH2((bR*wR+dR*wR+fR*wR+hR*wR+eR)*rcpWeightR); pixG=ASatH2((bG*wG+dG*wG+fG*wG+hG*wG+eG)*rcpWeightG); pixB=ASatH2((bB*wB+dB*wB+fB*wB+hB*wB+eB)*rcpWeightB); #endif return;} //------------------------------------------------------------------------------------------------------------------------------ // Scaling algorithm adaptively interpolates between nearest 4 results of the non-scaling algorithm. AF2 pp=AF2(ip)*AF2_AU2(const0.xy)+AF2_AU2(const0.zw); // Tile 0. // Fractional position is needed in high precision here. AF2 fp0=floor(pp); AH2 ppX; ppX.x=AH1(pp.x-fp0.x); AH1 ppY=AH1(pp.y-fp0.y); ASW2 sp0=ASW2(fp0); AH3 a0=CasLoadH(sp0+ASW2(-1,-1)); AH3 b0=CasLoadH(sp0+ASW2( 0,-1)); AH3 e0=CasLoadH(sp0+ASW2(-1, 0)); AH3 f0=CasLoadH(sp0); AH3 c0=CasLoadH(sp0+ASW2( 1,-1)); AH3 d0=CasLoadH(sp0+ASW2( 2,-1)); AH3 g0=CasLoadH(sp0+ASW2( 1, 0)); AH3 h0=CasLoadH(sp0+ASW2( 2, 0)); AH3 i0=CasLoadH(sp0+ASW2(-1, 1)); AH3 j0=CasLoadH(sp0+ASW2( 0, 1)); AH3 m0=CasLoadH(sp0+ASW2(-1, 2)); AH3 n0=CasLoadH(sp0+ASW2( 0, 2)); AH3 k0=CasLoadH(sp0+ASW2( 1, 1)); AH3 l0=CasLoadH(sp0+ASW2( 2, 1)); AH3 o0=CasLoadH(sp0+ASW2( 1, 2)); AH3 p0=CasLoadH(sp0+ASW2( 2, 2)); // Tile 1 (offset only in x). AF1 pp1=pp.x+AF1_AU1(const1.z); AF1 fp1=floor(pp1); ppX.y=AH1(pp1-fp1); ASW2 sp1=ASW2(fp1,sp0.y); AH3 a1=CasLoadH(sp1+ASW2(-1,-1)); AH3 b1=CasLoadH(sp1+ASW2( 0,-1)); AH3 e1=CasLoadH(sp1+ASW2(-1, 0)); AH3 f1=CasLoadH(sp1); AH3 c1=CasLoadH(sp1+ASW2( 1,-1)); AH3 d1=CasLoadH(sp1+ASW2( 2,-1)); AH3 g1=CasLoadH(sp1+ASW2( 1, 0)); AH3 h1=CasLoadH(sp1+ASW2( 2, 0)); AH3 i1=CasLoadH(sp1+ASW2(-1, 1)); AH3 j1=CasLoadH(sp1+ASW2( 0, 1)); AH3 m1=CasLoadH(sp1+ASW2(-1, 2)); AH3 n1=CasLoadH(sp1+ASW2( 0, 2)); AH3 k1=CasLoadH(sp1+ASW2( 1, 1)); AH3 l1=CasLoadH(sp1+ASW2( 2, 1)); AH3 o1=CasLoadH(sp1+ASW2( 1, 2)); AH3 p1=CasLoadH(sp1+ASW2( 2, 2)); // AOS to SOA conversion. AH2 aR=AH2(a0.r,a1.r); AH2 aG=AH2(a0.g,a1.g); AH2 aB=AH2(a0.b,a1.b); AH2 bR=AH2(b0.r,b1.r); AH2 bG=AH2(b0.g,b1.g); AH2 bB=AH2(b0.b,b1.b); AH2 cR=AH2(c0.r,c1.r); AH2 cG=AH2(c0.g,c1.g); AH2 cB=AH2(c0.b,c1.b); AH2 dR=AH2(d0.r,d1.r); AH2 dG=AH2(d0.g,d1.g); AH2 dB=AH2(d0.b,d1.b); AH2 eR=AH2(e0.r,e1.r); AH2 eG=AH2(e0.g,e1.g); AH2 eB=AH2(e0.b,e1.b); AH2 fR=AH2(f0.r,f1.r); AH2 fG=AH2(f0.g,f1.g); AH2 fB=AH2(f0.b,f1.b); AH2 gR=AH2(g0.r,g1.r); AH2 gG=AH2(g0.g,g1.g); AH2 gB=AH2(g0.b,g1.b); AH2 hR=AH2(h0.r,h1.r); AH2 hG=AH2(h0.g,h1.g); AH2 hB=AH2(h0.b,h1.b); AH2 iR=AH2(i0.r,i1.r); AH2 iG=AH2(i0.g,i1.g); AH2 iB=AH2(i0.b,i1.b); AH2 jR=AH2(j0.r,j1.r); AH2 jG=AH2(j0.g,j1.g); AH2 jB=AH2(j0.b,j1.b); AH2 kR=AH2(k0.r,k1.r); AH2 kG=AH2(k0.g,k1.g); AH2 kB=AH2(k0.b,k1.b); AH2 lR=AH2(l0.r,l1.r); AH2 lG=AH2(l0.g,l1.g); AH2 lB=AH2(l0.b,l1.b); AH2 mR=AH2(m0.r,m1.r); AH2 mG=AH2(m0.g,m1.g); AH2 mB=AH2(m0.b,m1.b); AH2 nR=AH2(n0.r,n1.r); AH2 nG=AH2(n0.g,n1.g); AH2 nB=AH2(n0.b,n1.b); AH2 oR=AH2(o0.r,o1.r); AH2 oG=AH2(o0.g,o1.g); AH2 oB=AH2(o0.b,o1.b); AH2 pR=AH2(p0.r,p1.r); AH2 pG=AH2(p0.g,p1.g); AH2 pB=AH2(p0.b,p1.b); // Run optional input transform. CasInputH(aR,aG,aB); CasInputH(bR,bG,bB); CasInputH(cR,cG,cB); CasInputH(dR,dG,dB); CasInputH(eR,eG,eB); CasInputH(fR,fG,fB); CasInputH(gR,gG,gB); CasInputH(hR,hG,hB); CasInputH(iR,iG,iB); CasInputH(jR,jG,jB); CasInputH(kR,kG,kB); CasInputH(lR,lG,lB); CasInputH(mR,mG,mB); CasInputH(nR,nG,nB); CasInputH(oR,oG,oB); CasInputH(pR,pG,pB); // Soft min and max. // These are 2.0x bigger (factored out the extra multiply). // a b c b // e f g * 0.5 + e f g * 0.5 [F] // i j k j AH2 mnfR=AMin3H2(AMin3H2(bR,eR,fR),gR,jR); AH2 mnfG=AMin3H2(AMin3H2(bG,eG,fG),gG,jG); AH2 mnfB=AMin3H2(AMin3H2(bB,eB,fB),gB,jB); #ifdef CAS_BETTER_DIAGONALS AH2 mnfR2=AMin3H2(AMin3H2(mnfR,aR,cR),iR,kR); AH2 mnfG2=AMin3H2(AMin3H2(mnfG,aG,cG),iG,kG); AH2 mnfB2=AMin3H2(AMin3H2(mnfB,aB,cB),iB,kB); mnfR=mnfR+mnfR2; mnfG=mnfG+mnfG2; mnfB=mnfB+mnfB2; #endif AH2 mxfR=AMax3H2(AMax3H2(bR,eR,fR),gR,jR); AH2 mxfG=AMax3H2(AMax3H2(bG,eG,fG),gG,jG); AH2 mxfB=AMax3H2(AMax3H2(bB,eB,fB),gB,jB); #ifdef CAS_BETTER_DIAGONALS AH2 mxfR2=AMax3H2(AMax3H2(mxfR,aR,cR),iR,kR); AH2 mxfG2=AMax3H2(AMax3H2(mxfG,aG,cG),iG,kG); AH2 mxfB2=AMax3H2(AMax3H2(mxfB,aB,cB),iB,kB); mxfR=mxfR+mxfR2; mxfG=mxfG+mxfG2; mxfB=mxfB+mxfB2; #endif // b c d c // f g h * 0.5 + f g h * 0.5 [G] // j k l k AH2 mngR=AMin3H2(AMin3H2(cR,fR,gR),hR,kR); AH2 mngG=AMin3H2(AMin3H2(cG,fG,gG),hG,kG); AH2 mngB=AMin3H2(AMin3H2(cB,fB,gB),hB,kB); #ifdef CAS_BETTER_DIAGONALS AH2 mngR2=AMin3H2(AMin3H2(mngR,bR,dR),jR,lR); AH2 mngG2=AMin3H2(AMin3H2(mngG,bG,dG),jG,lG); AH2 mngB2=AMin3H2(AMin3H2(mngB,bB,dB),jB,lB); mngR=mngR+mngR2; mngG=mngG+mngG2; mngB=mngB+mngB2; #endif AH2 mxgR=AMax3H2(AMax3H2(cR,fR,gR),hR,kR); AH2 mxgG=AMax3H2(AMax3H2(cG,fG,gG),hG,kG); AH2 mxgB=AMax3H2(AMax3H2(cB,fB,gB),hB,kB); #ifdef CAS_BETTER_DIAGONALS AH2 mxgR2=AMax3H2(AMax3H2(mxgR,bR,dR),jR,lR); AH2 mxgG2=AMax3H2(AMax3H2(mxgG,bG,dG),jG,lG); AH2 mxgB2=AMax3H2(AMax3H2(mxgB,bB,dB),jB,lB); mxgR=mxgR+mxgR2; mxgG=mxgG+mxgG2; mxgB=mxgB+mxgB2; #endif // e f g f // i j k * 0.5 + i j k * 0.5 [J] // m n o n AH2 mnjR=AMin3H2(AMin3H2(fR,iR,jR),kR,nR); AH2 mnjG=AMin3H2(AMin3H2(fG,iG,jG),kG,nG); AH2 mnjB=AMin3H2(AMin3H2(fB,iB,jB),kB,nB); #ifdef CAS_BETTER_DIAGONALS AH2 mnjR2=AMin3H2(AMin3H2(mnjR,eR,gR),mR,oR); AH2 mnjG2=AMin3H2(AMin3H2(mnjG,eG,gG),mG,oG); AH2 mnjB2=AMin3H2(AMin3H2(mnjB,eB,gB),mB,oB); mnjR=mnjR+mnjR2; mnjG=mnjG+mnjG2; mnjB=mnjB+mnjB2; #endif AH2 mxjR=AMax3H2(AMax3H2(fR,iR,jR),kR,nR); AH2 mxjG=AMax3H2(AMax3H2(fG,iG,jG),kG,nG); AH2 mxjB=AMax3H2(AMax3H2(fB,iB,jB),kB,nB); #ifdef CAS_BETTER_DIAGONALS AH2 mxjR2=AMax3H2(AMax3H2(mxjR,eR,gR),mR,oR); AH2 mxjG2=AMax3H2(AMax3H2(mxjG,eG,gG),mG,oG); AH2 mxjB2=AMax3H2(AMax3H2(mxjB,eB,gB),mB,oB); mxjR=mxjR+mxjR2; mxjG=mxjG+mxjG2; mxjB=mxjB+mxjB2; #endif // f g h g // j k l * 0.5 + j k l * 0.5 [K] // n o p o AH2 mnkR=AMin3H2(AMin3H2(gR,jR,kR),lR,oR); AH2 mnkG=AMin3H2(AMin3H2(gG,jG,kG),lG,oG); AH2 mnkB=AMin3H2(AMin3H2(gB,jB,kB),lB,oB); #ifdef CAS_BETTER_DIAGONALS AH2 mnkR2=AMin3H2(AMin3H2(mnkR,fR,hR),nR,pR); AH2 mnkG2=AMin3H2(AMin3H2(mnkG,fG,hG),nG,pG); AH2 mnkB2=AMin3H2(AMin3H2(mnkB,fB,hB),nB,pB); mnkR=mnkR+mnkR2; mnkG=mnkG+mnkG2; mnkB=mnkB+mnkB2; #endif AH2 mxkR=AMax3H2(AMax3H2(gR,jR,kR),lR,oR); AH2 mxkG=AMax3H2(AMax3H2(gG,jG,kG),lG,oG); AH2 mxkB=AMax3H2(AMax3H2(gB,jB,kB),lB,oB); #ifdef CAS_BETTER_DIAGONALS AH2 mxkR2=AMax3H2(AMax3H2(mxkR,fR,hR),nR,pR); AH2 mxkG2=AMax3H2(AMax3H2(mxkG,fG,hG),nG,pG); AH2 mxkB2=AMax3H2(AMax3H2(mxkB,fB,hB),nB,pB); mxkR=mxkR+mxkR2; mxkG=mxkG+mxkG2; mxkB=mxkB+mxkB2; #endif // Smooth minimum distance to signal limit divided by smooth max. #ifdef CAS_GO_SLOWER AH2 rcpMfR=ARcpH2(mxfR); AH2 rcpMfG=ARcpH2(mxfG); AH2 rcpMfB=ARcpH2(mxfB); AH2 rcpMgR=ARcpH2(mxgR); AH2 rcpMgG=ARcpH2(mxgG); AH2 rcpMgB=ARcpH2(mxgB); AH2 rcpMjR=ARcpH2(mxjR); AH2 rcpMjG=ARcpH2(mxjG); AH2 rcpMjB=ARcpH2(mxjB); AH2 rcpMkR=ARcpH2(mxkR); AH2 rcpMkG=ARcpH2(mxkG); AH2 rcpMkB=ARcpH2(mxkB); #else AH2 rcpMfR=APrxLoRcpH2(mxfR); AH2 rcpMfG=APrxLoRcpH2(mxfG); AH2 rcpMfB=APrxLoRcpH2(mxfB); AH2 rcpMgR=APrxLoRcpH2(mxgR); AH2 rcpMgG=APrxLoRcpH2(mxgG); AH2 rcpMgB=APrxLoRcpH2(mxgB); AH2 rcpMjR=APrxLoRcpH2(mxjR); AH2 rcpMjG=APrxLoRcpH2(mxjG); AH2 rcpMjB=APrxLoRcpH2(mxjB); AH2 rcpMkR=APrxLoRcpH2(mxkR); AH2 rcpMkG=APrxLoRcpH2(mxkG); AH2 rcpMkB=APrxLoRcpH2(mxkB); #endif #ifdef CAS_BETTER_DIAGONALS AH2 ampfR=ASatH2(min(mnfR,AH2_(2.0)-mxfR)*rcpMfR); AH2 ampfG=ASatH2(min(mnfG,AH2_(2.0)-mxfG)*rcpMfG); AH2 ampfB=ASatH2(min(mnfB,AH2_(2.0)-mxfB)*rcpMfB); AH2 ampgR=ASatH2(min(mngR,AH2_(2.0)-mxgR)*rcpMgR); AH2 ampgG=ASatH2(min(mngG,AH2_(2.0)-mxgG)*rcpMgG); AH2 ampgB=ASatH2(min(mngB,AH2_(2.0)-mxgB)*rcpMgB); AH2 ampjR=ASatH2(min(mnjR,AH2_(2.0)-mxjR)*rcpMjR); AH2 ampjG=ASatH2(min(mnjG,AH2_(2.0)-mxjG)*rcpMjG); AH2 ampjB=ASatH2(min(mnjB,AH2_(2.0)-mxjB)*rcpMjB); AH2 ampkR=ASatH2(min(mnkR,AH2_(2.0)-mxkR)*rcpMkR); AH2 ampkG=ASatH2(min(mnkG,AH2_(2.0)-mxkG)*rcpMkG); AH2 ampkB=ASatH2(min(mnkB,AH2_(2.0)-mxkB)*rcpMkB); #else AH2 ampfR=ASatH2(min(mnfR,AH2_(1.0)-mxfR)*rcpMfR); AH2 ampfG=ASatH2(min(mnfG,AH2_(1.0)-mxfG)*rcpMfG); AH2 ampfB=ASatH2(min(mnfB,AH2_(1.0)-mxfB)*rcpMfB); AH2 ampgR=ASatH2(min(mngR,AH2_(1.0)-mxgR)*rcpMgR); AH2 ampgG=ASatH2(min(mngG,AH2_(1.0)-mxgG)*rcpMgG); AH2 ampgB=ASatH2(min(mngB,AH2_(1.0)-mxgB)*rcpMgB); AH2 ampjR=ASatH2(min(mnjR,AH2_(1.0)-mxjR)*rcpMjR); AH2 ampjG=ASatH2(min(mnjG,AH2_(1.0)-mxjG)*rcpMjG); AH2 ampjB=ASatH2(min(mnjB,AH2_(1.0)-mxjB)*rcpMjB); AH2 ampkR=ASatH2(min(mnkR,AH2_(1.0)-mxkR)*rcpMkR); AH2 ampkG=ASatH2(min(mnkG,AH2_(1.0)-mxkG)*rcpMkG); AH2 ampkB=ASatH2(min(mnkB,AH2_(1.0)-mxkB)*rcpMkB); #endif // Shaping amount of sharpening. #ifdef CAS_GO_SLOWER ampfR=sqrt(ampfR); ampfG=sqrt(ampfG); ampfB=sqrt(ampfB); ampgR=sqrt(ampgR); ampgG=sqrt(ampgG); ampgB=sqrt(ampgB); ampjR=sqrt(ampjR); ampjG=sqrt(ampjG); ampjB=sqrt(ampjB); ampkR=sqrt(ampkR); ampkG=sqrt(ampkG); ampkB=sqrt(ampkB); #else ampfR=APrxLoSqrtH2(ampfR); ampfG=APrxLoSqrtH2(ampfG); ampfB=APrxLoSqrtH2(ampfB); ampgR=APrxLoSqrtH2(ampgR); ampgG=APrxLoSqrtH2(ampgG); ampgB=APrxLoSqrtH2(ampgB); ampjR=APrxLoSqrtH2(ampjR); ampjG=APrxLoSqrtH2(ampjG); ampjB=APrxLoSqrtH2(ampjB); ampkR=APrxLoSqrtH2(ampkR); ampkG=APrxLoSqrtH2(ampkG); ampkB=APrxLoSqrtH2(ampkB); #endif // Filter shape. AH1 peak=AH2_AU1(const1.y).x; AH2 wfR=ampfR*AH2_(peak); AH2 wfG=ampfG*AH2_(peak); AH2 wfB=ampfB*AH2_(peak); AH2 wgR=ampgR*AH2_(peak); AH2 wgG=ampgG*AH2_(peak); AH2 wgB=ampgB*AH2_(peak); AH2 wjR=ampjR*AH2_(peak); AH2 wjG=ampjG*AH2_(peak); AH2 wjB=ampjB*AH2_(peak); AH2 wkR=ampkR*AH2_(peak); AH2 wkG=ampkG*AH2_(peak); AH2 wkB=ampkB*AH2_(peak); // Blend between 4 results. AH2 s=(AH2_(1.0)-ppX)*(AH2_(1.0)-AH2_(ppY)); AH2 t= ppX *(AH2_(1.0)-AH2_(ppY)); AH2 u=(AH2_(1.0)-ppX)* AH2_(ppY) ; AH2 v= ppX * AH2_(ppY) ; // Thin edges to hide bilinear interpolation (helps diagonals). AH2 thinB=AH2_(1.0/32.0); #ifdef CAS_GO_SLOWER s*=ARcpH2(thinB+(mxfG-mnfG)); t*=ARcpH2(thinB+(mxgG-mngG)); u*=ARcpH2(thinB+(mxjG-mnjG)); v*=ARcpH2(thinB+(mxkG-mnkG)); #else s*=APrxLoRcpH2(thinB+(mxfG-mnfG)); t*=APrxLoRcpH2(thinB+(mxgG-mngG)); u*=APrxLoRcpH2(thinB+(mxjG-mnjG)); v*=APrxLoRcpH2(thinB+(mxkG-mnkG)); #endif // Final weighting. AH2 qbeR=wfR*s; AH2 qbeG=wfG*s; AH2 qbeB=wfB*s; AH2 qchR=wgR*t; AH2 qchG=wgG*t; AH2 qchB=wgB*t; AH2 qfR=wgR*t+wjR*u+s; AH2 qfG=wgG*t+wjG*u+s; AH2 qfB=wgB*t+wjB*u+s; AH2 qgR=wfR*s+wkR*v+t; AH2 qgG=wfG*s+wkG*v+t; AH2 qgB=wfB*s+wkB*v+t; AH2 qjR=wfR*s+wkR*v+u; AH2 qjG=wfG*s+wkG*v+u; AH2 qjB=wfB*s+wkB*v+u; AH2 qkR=wgR*t+wjR*u+v; AH2 qkG=wgG*t+wjG*u+v; AH2 qkB=wgB*t+wjB*u+v; AH2 qinR=wjR*u; AH2 qinG=wjG*u; AH2 qinB=wjB*u; AH2 qloR=wkR*v; AH2 qloG=wkG*v; AH2 qloB=wkB*v; // Filter. #ifndef CAS_SLOW #ifdef CAS_GO_SLOWER AH2 rcpWG=ARcpH2(AH2_(2.0)*qbeG+AH2_(2.0)*qchG+AH2_(2.0)*qinG+AH2_(2.0)*qloG+qfG+qgG+qjG+qkG); #else AH2 rcpWG=APrxMedRcpH2(AH2_(2.0)*qbeG+AH2_(2.0)*qchG+AH2_(2.0)*qinG+AH2_(2.0)*qloG+qfG+qgG+qjG+qkG); #endif pixR=ASatH2((bR*qbeG+eR*qbeG+cR*qchG+hR*qchG+iR*qinG+nR*qinG+lR*qloG+oR*qloG+fR*qfG+gR*qgG+jR*qjG+kR*qkG)*rcpWG); pixG=ASatH2((bG*qbeG+eG*qbeG+cG*qchG+hG*qchG+iG*qinG+nG*qinG+lG*qloG+oG*qloG+fG*qfG+gG*qgG+jG*qjG+kG*qkG)*rcpWG); pixB=ASatH2((bB*qbeG+eB*qbeG+cB*qchG+hB*qchG+iB*qinG+nB*qinG+lB*qloG+oB*qloG+fB*qfG+gB*qgG+jB*qjG+kB*qkG)*rcpWG); #else #ifdef CAS_GO_SLOWER AH2 rcpWR=ARcpH2(AH2_(2.0)*qbeR+AH2_(2.0)*qchR+AH2_(2.0)*qinR+AH2_(2.0)*qloR+qfR+qgR+qjR+qkR); AH2 rcpWG=ARcpH2(AH2_(2.0)*qbeG+AH2_(2.0)*qchG+AH2_(2.0)*qinG+AH2_(2.0)*qloG+qfG+qgG+qjG+qkG); AH2 rcpWB=ARcpH2(AH2_(2.0)*qbeB+AH2_(2.0)*qchB+AH2_(2.0)*qinB+AH2_(2.0)*qloB+qfB+qgB+qjB+qkB); #else AH2 rcpWR=APrxMedRcpH2(AH2_(2.0)*qbeR+AH2_(2.0)*qchR+AH2_(2.0)*qinR+AH2_(2.0)*qloR+qfR+qgR+qjR+qkR); AH2 rcpWG=APrxMedRcpH2(AH2_(2.0)*qbeG+AH2_(2.0)*qchG+AH2_(2.0)*qinG+AH2_(2.0)*qloG+qfG+qgG+qjG+qkG); AH2 rcpWB=APrxMedRcpH2(AH2_(2.0)*qbeB+AH2_(2.0)*qchB+AH2_(2.0)*qinB+AH2_(2.0)*qloB+qfB+qgB+qjB+qkB); #endif pixR=ASatH2((bR*qbeR+eR*qbeR+cR*qchR+hR*qchR+iR*qinR+nR*qinR+lR*qloR+oR*qloR+fR*qfR+gR*qgR+jR*qjR+kR*qkR)*rcpWR); pixG=ASatH2((bG*qbeG+eG*qbeG+cG*qchG+hG*qchG+iG*qinG+nG*qinG+lG*qloG+oG*qloG+fG*qfG+gG*qgG+jG*qjG+kG*qkG)*rcpWG); pixB=ASatH2((bB*qbeB+eB*qbeB+cB*qchB+hB*qchB+iB*qinB+nB*qinB+lB*qloB+oB*qloB+fB*qfB+gB*qgB+jB*qjB+kB*qkB)*rcpWB); #endif } #endif looking-glass-B6/client/renderers/EGL/shader/ffx_fsr1.h000066400000000000000000001656061434445012300231250ustar00rootroot00000000000000//_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // // // AMD FidelityFX SUPER RESOLUTION [FSR 1] ::: SPATIAL SCALING & EXTRAS - v1.20210629 // // //------------------------------------------------------------------------------------------------------------------------------ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------------------------------------ // FidelityFX Super Resolution Sample // // Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files(the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and / or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions : // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. //------------------------------------------------------------------------------------------------------------------------------ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------------------------------------ // ABOUT // ===== // FSR is a collection of algorithms relating to generating a higher resolution image. // This specific header focuses on single-image non-temporal image scaling, and related tools. // // The core functions are EASU and RCAS: // [EASU] Edge Adaptive Spatial Upsampling ....... 1x to 4x area range spatial scaling, clamped adaptive elliptical filter. // [RCAS] Robust Contrast Adaptive Sharpening .... A non-scaling variation on CAS. // RCAS needs to be applied after EASU as a separate pass. // // Optional utility functions are: // [LFGA] Linear Film Grain Applicator ........... Tool to apply film grain after scaling. // [SRTM] Simple Reversible Tone-Mapper .......... Linear HDR {0 to FP16_MAX} to {0 to 1} and back. // [TEPD] Temporal Energy Preserving Dither ...... Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. // See each individual sub-section for inline documentation. //------------------------------------------------------------------------------------------------------------------------------ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------------------------------------ // FUNCTION PERMUTATIONS // ===================== // *F() ..... Single item computation with 32-bit. // *H() ..... Single item computation with 16-bit, with packing (aka two 16-bit ops in parallel) when possible. // *Hx2() ... Processing two items in parallel with 16-bit, easier packing. // Not all interfaces in this file have a *Hx2() form. //============================================================================================================================== //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // // FSR - [EASU] EDGE ADAPTIVE SPATIAL UPSAMPLING // //------------------------------------------------------------------------------------------------------------------------------ // EASU provides a high quality spatial-only scaling at relatively low cost. // Meaning EASU is appropiate for laptops and other low-end GPUs. // Quality from 1x to 4x area scaling is good. //------------------------------------------------------------------------------------------------------------------------------ // The scalar uses a modified fast approximation to the standard lanczos(size=2) kernel. // EASU runs in a single pass, so it applies a directionally and anisotropically adaptive radial lanczos. // This is also kept as simple as possible to have minimum runtime. //------------------------------------------------------------------------------------------------------------------------------ // The lanzcos filter has negative lobes, so by itself it will introduce ringing. // To remove all ringing, the algorithm uses the nearest 2x2 input texels as a neighborhood, // and limits output to the minimum and maximum of that neighborhood. //------------------------------------------------------------------------------------------------------------------------------ // Input image requirements: // // Color needs to be encoded as 3 channel[red, green, blue](e.g.XYZ not supported) // Each channel needs to be in the range[0, 1] // Any color primaries are supported // Display / tonemapping curve needs to be as if presenting to sRGB display or similar(e.g.Gamma 2.0) // There should be no banding in the input // There should be no high amplitude noise in the input // There should be no noise in the input that is not at input pixel granularity // For performance purposes, use 32bpp formats //------------------------------------------------------------------------------------------------------------------------------ // Best to apply EASU at the end of the frame after tonemapping // but before film grain or composite of the UI. //------------------------------------------------------------------------------------------------------------------------------ // Example of including this header for D3D HLSL : // // #define A_GPU 1 // #define A_HLSL 1 // #define A_HALF 1 // #include "ffx_a.h" // #define FSR_EASU_H 1 // #define FSR_RCAS_H 1 // //declare input callbacks // #include "ffx_fsr1.h" // // Example of including this header for Vulkan GLSL : // // #define A_GPU 1 // #define A_GLSL 1 // #define A_HALF 1 // #include "ffx_a.h" // #define FSR_EASU_H 1 // #define FSR_RCAS_H 1 // //declare input callbacks // #include "ffx_fsr1.h" // // Example of including this header for Vulkan HLSL : // // #define A_GPU 1 // #define A_HLSL 1 // #define A_HLSL_6_2 1 // #define A_NO_16_BIT_CAST 1 // #define A_HALF 1 // #include "ffx_a.h" // #define FSR_EASU_H 1 // #define FSR_RCAS_H 1 // //declare input callbacks // #include "ffx_fsr1.h" // // Example of declaring the required input callbacks for GLSL : // The callbacks need to gather4 for each color channel using the specified texture coordinate 'p'. // EASU uses gather4 to reduce position computation logic and for free Arrays of Structures to Structures of Arrays conversion. // // AH4 FsrEasuRH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,0));} // AH4 FsrEasuGH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,1));} // AH4 FsrEasuBH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,2));} // ... // The FsrEasuCon function needs to be called from the CPU or GPU to set up constants. // The difference in viewport and input image size is there to support Dynamic Resolution Scaling. // To use FsrEasuCon() on the CPU, define A_CPU before including ffx_a and ffx_fsr1. // Including a GPU example here, the 'con0' through 'con3' values would be stored out to a constant buffer. // AU4 con0,con1,con2,con3; // FsrEasuCon(con0,con1,con2,con3, // 1920.0,1080.0, // Viewport size (top left aligned) in the input image which is to be scaled. // 3840.0,2160.0, // The size of the input image. // 2560.0,1440.0); // The output resolution. //============================================================================================================================== //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // CONSTANT SETUP //============================================================================================================================== // Call to setup required constant values (works on CPU or GPU). A_STATIC void FsrEasuCon( outAU4 con0, outAU4 con1, outAU4 con2, outAU4 con3, // This the rendered image resolution being upscaled AF1 inputViewportInPixelsX, AF1 inputViewportInPixelsY, // This is the resolution of the resource containing the input image (useful for dynamic resolution) AF1 inputSizeInPixelsX, AF1 inputSizeInPixelsY, // This is the display resolution which the input image gets upscaled to AF1 outputSizeInPixelsX, AF1 outputSizeInPixelsY){ // Output integer position to a pixel position in viewport. con0[0]=AU1_AF1(inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)); con0[1]=AU1_AF1(inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)); con0[2]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)-AF1_(0.5)); con0[3]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)-AF1_(0.5)); // Viewport pixel position to normalized image space. // This is used to get upper-left of 'F' tap. con1[0]=AU1_AF1(ARcpF1(inputSizeInPixelsX)); con1[1]=AU1_AF1(ARcpF1(inputSizeInPixelsY)); // Centers of gather4, first offset from upper-left of 'F'. // +---+---+ // | | | // +--(0)--+ // | b | c | // +---F---+---+---+ // | e | f | g | h | // +--(1)--+--(2)--+ // | i | j | k | l | // +---+---+---+---+ // | n | o | // +--(3)--+ // | | | // +---+---+ con1[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); con1[3]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsY)); // These are from (0) instead of 'F'. con2[0]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsX)); con2[1]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); con2[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); con2[3]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); con3[0]=AU1_AF1(AF1_( 0.0)*ARcpF1(inputSizeInPixelsX)); con3[1]=AU1_AF1(AF1_( 4.0)*ARcpF1(inputSizeInPixelsY)); con3[2]=con3[3]=0u;} //If the an offset into the input image resource A_STATIC void FsrEasuConOffset( outAU4 con0, outAU4 con1, outAU4 con2, outAU4 con3, // This the rendered image resolution being upscaled AF1 inputViewportInPixelsX, AF1 inputViewportInPixelsY, // This is the resolution of the resource containing the input image (useful for dynamic resolution) AF1 inputSizeInPixelsX, AF1 inputSizeInPixelsY, // This is the display resolution which the input image gets upscaled to AF1 outputSizeInPixelsX, AF1 outputSizeInPixelsY, // This is the input image offset into the resource containing it (useful for dynamic resolution) AF1 inputOffsetInPixelsX, AF1 inputOffsetInPixelsY) { FsrEasuCon(con0, con1, con2, con3, inputViewportInPixelsX, inputViewportInPixelsY, inputSizeInPixelsX, inputSizeInPixelsY, outputSizeInPixelsX, outputSizeInPixelsY); con0[2] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsX * ARcpF1(outputSizeInPixelsX) - AF1_(0.5) + inputOffsetInPixelsX); con0[3] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsY * ARcpF1(outputSizeInPixelsY) - AF1_(0.5) + inputOffsetInPixelsY); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // NON-PACKED 32-BIT VERSION //============================================================================================================================== #if defined(A_GPU)&&defined(FSR_EASU_F) // Input callback prototypes, need to be implemented by calling shader AF4 FsrEasuRF(AF2 p); AF4 FsrEasuGF(AF2 p); AF4 FsrEasuBF(AF2 p); //------------------------------------------------------------------------------------------------------------------------------ // Filtering for a given tap for the scalar. void FsrEasuTapF( inout AF3 aC, // Accumulated color, with negative lobe. inout AF1 aW, // Accumulated weight. AF2 off, // Pixel offset from resolve position to tap. AF2 dir, // Gradient direction. AF2 len, // Length. AF1 lob, // Negative lobe strength. AF1 clp, // Clipping point. AF3 c){ // Tap color. // Rotate offset by direction. AF2 v; v.x=(off.x*( dir.x))+(off.y*dir.y); v.y=(off.x*(-dir.y))+(off.y*dir.x); // Anisotropy. v*=len; // Compute distance^2. AF1 d2=v.x*v.x+v.y*v.y; // Limit to the window as at corner, 2 taps can easily be outside. d2=min(d2,clp); // Approximation of lancos2 without sin() or rcp(), or sqrt() to get x. // (25/16 * (2/5 * x^2 - 1)^2 - (25/16 - 1)) * (1/4 * x^2 - 1)^2 // |_______________________________________| |_______________| // base window // The general form of the 'base' is, // (a*(b*x^2-1)^2-(a-1)) // Where 'a=1/(2*b-b^2)' and 'b' moves around the negative lobe. AF1 wB=AF1_(2.0/5.0)*d2+AF1_(-1.0); AF1 wA=lob*d2+AF1_(-1.0); wB*=wB; wA*=wA; wB=AF1_(25.0/16.0)*wB+AF1_(-(25.0/16.0-1.0)); AF1 w=wB*wA; // Do weighted average. aC+=c*w;aW+=w;} //------------------------------------------------------------------------------------------------------------------------------ // Accumulate direction and length. void FsrEasuSetF( inout AF2 dir, inout AF1 len, AF2 pp, AP1 biS,AP1 biT,AP1 biU,AP1 biV, AF1 lA,AF1 lB,AF1 lC,AF1 lD,AF1 lE){ // Compute bilinear weight, branches factor out as predicates are compiler time immediates. // s t // u v AF1 w = AF1_(0.0); if(biS)w=(AF1_(1.0)-pp.x)*(AF1_(1.0)-pp.y); if(biT)w= pp.x *(AF1_(1.0)-pp.y); if(biU)w=(AF1_(1.0)-pp.x)* pp.y ; if(biV)w= pp.x * pp.y ; // Direction is the '+' diff. // a // b c d // e // Then takes magnitude from abs average of both sides of 'c'. // Length converts gradient reversal to 0, smoothly to non-reversal at 1, shaped, then adding horz and vert terms. AF1 dc=lD-lC; AF1 cb=lC-lB; AF1 lenX=max(abs(dc),abs(cb)); lenX=APrxLoRcpF1(lenX); AF1 dirX=lD-lB; dir.x+=dirX*w; lenX=ASatF1(abs(dirX)*lenX); lenX*=lenX; len+=lenX*w; // Repeat for the y axis. AF1 ec=lE-lC; AF1 ca=lC-lA; AF1 lenY=max(abs(ec),abs(ca)); lenY=APrxLoRcpF1(lenY); AF1 dirY=lE-lA; dir.y+=dirY*w; lenY=ASatF1(abs(dirY)*lenY); lenY*=lenY; len+=lenY*w;} //------------------------------------------------------------------------------------------------------------------------------ void FsrEasuF( out AF3 pix, AU2 ip, // Integer pixel position in output. AU4 con0, // Constants generated by FsrEasuCon(). AU4 con1, AU4 con2, AU4 con3){ //------------------------------------------------------------------------------------------------------------------------------ // Get position of 'f'. AF2 pp=AF2(ip)*AF2_AU2(con0.xy)+AF2_AU2(con0.zw); AF2 fp=floor(pp); pp-=fp; //------------------------------------------------------------------------------------------------------------------------------ // 12-tap kernel. // b c // e f g h // i j k l // n o // Gather 4 ordering. // a b // r g // For packed FP16, need either {rg} or {ab} so using the following setup for gather in all versions, // a b <- unused (z) // r g // a b a b // r g r g // a b // r g <- unused (z) // Allowing dead-code removal to remove the 'z's. AF2 p0=fp*AF2_AU2(con1.xy)+AF2_AU2(con1.zw); // These are from p0 to avoid pulling two constants on pre-Navi hardware. AF2 p1=p0+AF2_AU2(con2.xy); AF2 p2=p0+AF2_AU2(con2.zw); AF2 p3=p0+AF2_AU2(con3.xy); AF4 bczzR=FsrEasuRF(p0); AF4 bczzG=FsrEasuGF(p0); AF4 bczzB=FsrEasuBF(p0); AF4 ijfeR=FsrEasuRF(p1); AF4 ijfeG=FsrEasuGF(p1); AF4 ijfeB=FsrEasuBF(p1); AF4 klhgR=FsrEasuRF(p2); AF4 klhgG=FsrEasuGF(p2); AF4 klhgB=FsrEasuBF(p2); AF4 zzonR=FsrEasuRF(p3); AF4 zzonG=FsrEasuGF(p3); AF4 zzonB=FsrEasuBF(p3); //------------------------------------------------------------------------------------------------------------------------------ // Simplest multi-channel approximate luma possible (luma times 2, in 2 FMA/MAD). AF4 bczzL=bczzB*AF4_(0.5)+(bczzR*AF4_(0.5)+bczzG); AF4 ijfeL=ijfeB*AF4_(0.5)+(ijfeR*AF4_(0.5)+ijfeG); AF4 klhgL=klhgB*AF4_(0.5)+(klhgR*AF4_(0.5)+klhgG); AF4 zzonL=zzonB*AF4_(0.5)+(zzonR*AF4_(0.5)+zzonG); // Rename. AF1 bL=bczzL.x; AF1 cL=bczzL.y; AF1 iL=ijfeL.x; AF1 jL=ijfeL.y; AF1 fL=ijfeL.z; AF1 eL=ijfeL.w; AF1 kL=klhgL.x; AF1 lL=klhgL.y; AF1 hL=klhgL.z; AF1 gL=klhgL.w; AF1 oL=zzonL.z; AF1 nL=zzonL.w; // Accumulate for bilinear interpolation. AF2 dir=AF2_(0.0); AF1 len=AF1_(0.0); FsrEasuSetF(dir,len,pp,true, false,false,false,bL,eL,fL,gL,jL); FsrEasuSetF(dir,len,pp,false,true ,false,false,cL,fL,gL,hL,kL); FsrEasuSetF(dir,len,pp,false,false,true ,false,fL,iL,jL,kL,nL); FsrEasuSetF(dir,len,pp,false,false,false,true ,gL,jL,kL,lL,oL); //------------------------------------------------------------------------------------------------------------------------------ // Normalize with approximation, and cleanup close to zero. AF2 dir2=dir*dir; AF1 dirR=dir2.x+dir2.y; AP1 zro=dirR w = -m/(n+e+w+s) // 1 == (w*(n+e+w+s)+m)/(4*w+1) -> w = (1-m)/(n+e+w+s-4*1) // Then chooses the 'w' which results in no clipping, limits 'w', and multiplies by the 'sharp' amount. // This solution above has issues with MSAA input as the steps along the gradient cause edge detection issues. // So RCAS uses 4x the maximum and 4x the minimum (depending on equation)in place of the individual taps. // As well as switching from 'm' to either the minimum or maximum (depending on side), to help in energy conservation. // This stabilizes RCAS. // RCAS does a simple highpass which is normalized against the local contrast then shaped, // 0.25 // 0.25 -1 0.25 // 0.25 // This is used as a noise detection filter, to reduce the effect of RCAS on grain, and focus on real edges. // // GLSL example for the required callbacks : // // AH4 FsrRcasLoadH(ASW2 p){return AH4(imageLoad(imgSrc,ASU2(p)));} // void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b) // { // //do any simple input color conversions here or leave empty if none needed // } // // FsrRcasCon need to be called from the CPU or GPU to set up constants. // Including a GPU example here, the 'con' value would be stored out to a constant buffer. // // AU4 con; // FsrRcasCon(con, // 0.0); // The scale is {0.0 := maximum sharpness, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. // --------------- // RCAS sharpening supports a CAS-like pass-through alpha via, // #define FSR_RCAS_PASSTHROUGH_ALPHA 1 // RCAS also supports a define to enable a more expensive path to avoid some sharpening of noise. // Would suggest it is better to apply film grain after RCAS sharpening (and after scaling) instead of using this define, // #define FSR_RCAS_DENOISE 1 //============================================================================================================================== // This is set at the limit of providing unnatural results for sharpening. #define FSR_RCAS_LIMIT (0.25-(1.0/16.0)) //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // CONSTANT SETUP //============================================================================================================================== // Call to setup required constant values (works on CPU or GPU). A_STATIC void FsrRcasCon( outAU4 con, // The scale is {0.0 := maximum, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. AF1 sharpness){ // Transform from stops to linear value. sharpness=AExp2F1(-sharpness); varAF2(hSharp)=initAF2(sharpness,sharpness); con[0]=AU1_AF1(sharpness); con[1]=AU1_AH2_AF2(hSharp); con[2]=0u; con[3]=0u;} //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // NON-PACKED 32-BIT VERSION //============================================================================================================================== #if defined(A_GPU)&&defined(FSR_RCAS_F) // Input callback prototypes that need to be implemented by calling shader AF4 FsrRcasLoadF(ASU2 p); void FsrRcasInputF(inout AF1 r,inout AF1 g,inout AF1 b); //------------------------------------------------------------------------------------------------------------------------------ void FsrRcasF( out AF1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. out AF1 pixG, out AF1 pixB, #ifdef FSR_RCAS_PASSTHROUGH_ALPHA out AF1 pixA, #endif AU2 ip, // Integer pixel position in output. AU4 con){ // Constant generated by RcasSetup(). // Algorithm uses minimal 3x3 pixel neighborhood. // b // d e f // h ASU2 sp=ASU2(ip); AF3 b=FsrRcasLoadF(sp+ASU2( 0,-1)).rgb; AF3 d=FsrRcasLoadF(sp+ASU2(-1, 0)).rgb; #ifdef FSR_RCAS_PASSTHROUGH_ALPHA AF4 ee=FsrRcasLoadF(sp); AF3 e=ee.rgb;pixA=ee.a; #else AF3 e=FsrRcasLoadF(sp).rgb; #endif AF3 f=FsrRcasLoadF(sp+ASU2( 1, 0)).rgb; AF3 h=FsrRcasLoadF(sp+ASU2( 0, 1)).rgb; // Rename (32-bit) or regroup (16-bit). AF1 bR=b.r; AF1 bG=b.g; AF1 bB=b.b; AF1 dR=d.r; AF1 dG=d.g; AF1 dB=d.b; AF1 eR=e.r; AF1 eG=e.g; AF1 eB=e.b; AF1 fR=f.r; AF1 fG=f.g; AF1 fB=f.b; AF1 hR=h.r; AF1 hG=h.g; AF1 hB=h.b; // Run optional input transform. FsrRcasInputF(bR,bG,bB); FsrRcasInputF(dR,dG,dB); FsrRcasInputF(eR,eG,eB); FsrRcasInputF(fR,fG,fB); FsrRcasInputF(hR,hG,hB); // Luma times 2. AF1 bL=bB*AF1_(0.5)+(bR*AF1_(0.5)+bG); AF1 dL=dB*AF1_(0.5)+(dR*AF1_(0.5)+dG); AF1 eL=eB*AF1_(0.5)+(eR*AF1_(0.5)+eG); AF1 fL=fB*AF1_(0.5)+(fR*AF1_(0.5)+fG); AF1 hL=hB*AF1_(0.5)+(hR*AF1_(0.5)+hG); // Noise detection. AF1 nz=AF1_(0.25)*bL+AF1_(0.25)*dL+AF1_(0.25)*fL+AF1_(0.25)*hL-eL; nz=ASatF1(abs(nz)*APrxMedRcpF1(AMax3F1(AMax3F1(bL,dL,eL),fL,hL)-AMin3F1(AMin3F1(bL,dL,eL),fL,hL))); nz=AF1_(-0.5)*nz+AF1_(1.0); // Min and max of ring. AF1 mn4R=min(AMin3F1(bR,dR,fR),hR); AF1 mn4G=min(AMin3F1(bG,dG,fG),hG); AF1 mn4B=min(AMin3F1(bB,dB,fB),hB); AF1 mx4R=max(AMax3F1(bR,dR,fR),hR); AF1 mx4G=max(AMax3F1(bG,dG,fG),hG); AF1 mx4B=max(AMax3F1(bB,dB,fB),hB); // Immediate constants for peak range. AF2 peakC=AF2(1.0,-1.0*4.0); // Limiters, these need to be high precision RCPs. AF1 hitMinR=min(mn4R,eR)*ARcpF1(AF1_(4.0)*mx4R); AF1 hitMinG=min(mn4G,eG)*ARcpF1(AF1_(4.0)*mx4G); AF1 hitMinB=min(mn4B,eB)*ARcpF1(AF1_(4.0)*mx4B); AF1 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpF1(AF1_(4.0)*mn4R+peakC.y); AF1 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpF1(AF1_(4.0)*mn4G+peakC.y); AF1 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpF1(AF1_(4.0)*mn4B+peakC.y); AF1 lobeR=max(-hitMinR,hitMaxR); AF1 lobeG=max(-hitMinG,hitMaxG); AF1 lobeB=max(-hitMinB,hitMaxB); AF1 lobe=max(AF1_(-FSR_RCAS_LIMIT),min(AMax3F1(lobeR,lobeG,lobeB),AF1_(0.0)))*AF1_AU1(con.x); // Apply noise removal. #ifdef FSR_RCAS_DENOISE lobe*=nz; #endif // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. AF1 rcpL=APrxMedRcpF1(AF1_(4.0)*lobe+AF1_(1.0)); pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL; return;} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // NON-PACKED 16-BIT VERSION //============================================================================================================================== #if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_H) // Input callback prototypes that need to be implemented by calling shader AH4 FsrRcasLoadH(ASW2 p); void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b); //------------------------------------------------------------------------------------------------------------------------------ void FsrRcasH( out AH1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. out AH1 pixG, out AH1 pixB, #ifdef FSR_RCAS_PASSTHROUGH_ALPHA out AH1 pixA, #endif AU2 ip, // Integer pixel position in output. AU4 con){ // Constant generated by RcasSetup(). // Sharpening algorithm uses minimal 3x3 pixel neighborhood. // b // d e f // h ASW2 sp=ASW2(ip); AH3 b=FsrRcasLoadH(sp+ASW2( 0,-1)).rgb; AH3 d=FsrRcasLoadH(sp+ASW2(-1, 0)).rgb; #ifdef FSR_RCAS_PASSTHROUGH_ALPHA AH4 ee=FsrRcasLoadH(sp); AH3 e=ee.rgb;pixA=ee.a; #else AH3 e=FsrRcasLoadH(sp).rgb; #endif AH3 f=FsrRcasLoadH(sp+ASW2( 1, 0)).rgb; AH3 h=FsrRcasLoadH(sp+ASW2( 0, 1)).rgb; // Rename (32-bit) or regroup (16-bit). AH1 bR=b.r; AH1 bG=b.g; AH1 bB=b.b; AH1 dR=d.r; AH1 dG=d.g; AH1 dB=d.b; AH1 eR=e.r; AH1 eG=e.g; AH1 eB=e.b; AH1 fR=f.r; AH1 fG=f.g; AH1 fB=f.b; AH1 hR=h.r; AH1 hG=h.g; AH1 hB=h.b; // Run optional input transform. FsrRcasInputH(bR,bG,bB); FsrRcasInputH(dR,dG,dB); FsrRcasInputH(eR,eG,eB); FsrRcasInputH(fR,fG,fB); FsrRcasInputH(hR,hG,hB); // Luma times 2. AH1 bL=bB*AH1_(0.5)+(bR*AH1_(0.5)+bG); AH1 dL=dB*AH1_(0.5)+(dR*AH1_(0.5)+dG); AH1 eL=eB*AH1_(0.5)+(eR*AH1_(0.5)+eG); AH1 fL=fB*AH1_(0.5)+(fR*AH1_(0.5)+fG); AH1 hL=hB*AH1_(0.5)+(hR*AH1_(0.5)+hG); // Noise detection. AH1 nz=AH1_(0.25)*bL+AH1_(0.25)*dL+AH1_(0.25)*fL+AH1_(0.25)*hL-eL; nz=ASatH1(abs(nz)*APrxMedRcpH1(AMax3H1(AMax3H1(bL,dL,eL),fL,hL)-AMin3H1(AMin3H1(bL,dL,eL),fL,hL))); nz=AH1_(-0.5)*nz+AH1_(1.0); // Min and max of ring. AH1 mn4R=min(AMin3H1(bR,dR,fR),hR); AH1 mn4G=min(AMin3H1(bG,dG,fG),hG); AH1 mn4B=min(AMin3H1(bB,dB,fB),hB); AH1 mx4R=max(AMax3H1(bR,dR,fR),hR); AH1 mx4G=max(AMax3H1(bG,dG,fG),hG); AH1 mx4B=max(AMax3H1(bB,dB,fB),hB); // Immediate constants for peak range. AH2 peakC=AH2(1.0,-1.0*4.0); // Limiters, these need to be high precision RCPs. AH1 hitMinR=min(mn4R,eR)*ARcpH1(AH1_(4.0)*mx4R); AH1 hitMinG=min(mn4G,eG)*ARcpH1(AH1_(4.0)*mx4G); AH1 hitMinB=min(mn4B,eB)*ARcpH1(AH1_(4.0)*mx4B); AH1 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpH1(AH1_(4.0)*mn4R+peakC.y); AH1 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpH1(AH1_(4.0)*mn4G+peakC.y); AH1 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpH1(AH1_(4.0)*mn4B+peakC.y); AH1 lobeR=max(-hitMinR,hitMaxR); AH1 lobeG=max(-hitMinG,hitMaxG); AH1 lobeB=max(-hitMinB,hitMaxB); AH1 lobe=max(AH1_(-FSR_RCAS_LIMIT),min(AMax3H1(lobeR,lobeG,lobeB),AH1_(0.0)))*AH2_AU1(con.y).x; // Apply noise removal. #ifdef FSR_RCAS_DENOISE lobe*=nz; #endif // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. AH1 rcpL=APrxMedRcpH1(AH1_(4.0)*lobe+AH1_(1.0)); pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // PACKED 16-BIT VERSION //============================================================================================================================== #if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_HX2) // Input callback prototypes that need to be implemented by the calling shader AH4 FsrRcasLoadHx2(ASW2 p); void FsrRcasInputHx2(inout AH2 r,inout AH2 g,inout AH2 b); //------------------------------------------------------------------------------------------------------------------------------ // Can be used to convert from packed Structures of Arrays to Arrays of Structures for store. void FsrRcasDepackHx2(out AH4 pix0,out AH4 pix1,AH2 pixR,AH2 pixG,AH2 pixB){ #ifdef A_HLSL // Invoke a slower path for DX only, since it won't allow uninitialized values. pix0.a=pix1.a=0.0; #endif pix0.rgb=AH3(pixR.x,pixG.x,pixB.x); pix1.rgb=AH3(pixR.y,pixG.y,pixB.y);} //------------------------------------------------------------------------------------------------------------------------------ void FsrRcasHx2( // Output values are for 2 8x8 tiles in a 16x8 region. // pix.x = left 8x8 tile // pix.y = right 8x8 tile // This enables later processing to easily be packed as well. out AH2 pixR, out AH2 pixG, out AH2 pixB, #ifdef FSR_RCAS_PASSTHROUGH_ALPHA out AH2 pixA, #endif AU2 ip, // Integer pixel position in output. AU4 con){ // Constant generated by RcasSetup(). // No scaling algorithm uses minimal 3x3 pixel neighborhood. ASW2 sp0=ASW2(ip); AH3 b0=FsrRcasLoadHx2(sp0+ASW2( 0,-1)).rgb; AH3 d0=FsrRcasLoadHx2(sp0+ASW2(-1, 0)).rgb; #ifdef FSR_RCAS_PASSTHROUGH_ALPHA AH4 ee0=FsrRcasLoadHx2(sp0); AH3 e0=ee0.rgb;pixA.r=ee0.a; #else AH3 e0=FsrRcasLoadHx2(sp0).rgb; #endif AH3 f0=FsrRcasLoadHx2(sp0+ASW2( 1, 0)).rgb; AH3 h0=FsrRcasLoadHx2(sp0+ASW2( 0, 1)).rgb; ASW2 sp1=sp0+ASW2(8,0); AH3 b1=FsrRcasLoadHx2(sp1+ASW2( 0,-1)).rgb; AH3 d1=FsrRcasLoadHx2(sp1+ASW2(-1, 0)).rgb; #ifdef FSR_RCAS_PASSTHROUGH_ALPHA AH4 ee1=FsrRcasLoadHx2(sp1); AH3 e1=ee1.rgb;pixA.g=ee1.a; #else AH3 e1=FsrRcasLoadHx2(sp1).rgb; #endif AH3 f1=FsrRcasLoadHx2(sp1+ASW2( 1, 0)).rgb; AH3 h1=FsrRcasLoadHx2(sp1+ASW2( 0, 1)).rgb; // Arrays of Structures to Structures of Arrays conversion. AH2 bR=AH2(b0.r,b1.r); AH2 bG=AH2(b0.g,b1.g); AH2 bB=AH2(b0.b,b1.b); AH2 dR=AH2(d0.r,d1.r); AH2 dG=AH2(d0.g,d1.g); AH2 dB=AH2(d0.b,d1.b); AH2 eR=AH2(e0.r,e1.r); AH2 eG=AH2(e0.g,e1.g); AH2 eB=AH2(e0.b,e1.b); AH2 fR=AH2(f0.r,f1.r); AH2 fG=AH2(f0.g,f1.g); AH2 fB=AH2(f0.b,f1.b); AH2 hR=AH2(h0.r,h1.r); AH2 hG=AH2(h0.g,h1.g); AH2 hB=AH2(h0.b,h1.b); // Run optional input transform. FsrRcasInputHx2(bR,bG,bB); FsrRcasInputHx2(dR,dG,dB); FsrRcasInputHx2(eR,eG,eB); FsrRcasInputHx2(fR,fG,fB); FsrRcasInputHx2(hR,hG,hB); // Luma times 2. AH2 bL=bB*AH2_(0.5)+(bR*AH2_(0.5)+bG); AH2 dL=dB*AH2_(0.5)+(dR*AH2_(0.5)+dG); AH2 eL=eB*AH2_(0.5)+(eR*AH2_(0.5)+eG); AH2 fL=fB*AH2_(0.5)+(fR*AH2_(0.5)+fG); AH2 hL=hB*AH2_(0.5)+(hR*AH2_(0.5)+hG); // Noise detection. AH2 nz=AH2_(0.25)*bL+AH2_(0.25)*dL+AH2_(0.25)*fL+AH2_(0.25)*hL-eL; nz=ASatH2(abs(nz)*APrxMedRcpH2(AMax3H2(AMax3H2(bL,dL,eL),fL,hL)-AMin3H2(AMin3H2(bL,dL,eL),fL,hL))); nz=AH2_(-0.5)*nz+AH2_(1.0); // Min and max of ring. AH2 mn4R=min(AMin3H2(bR,dR,fR),hR); AH2 mn4G=min(AMin3H2(bG,dG,fG),hG); AH2 mn4B=min(AMin3H2(bB,dB,fB),hB); AH2 mx4R=max(AMax3H2(bR,dR,fR),hR); AH2 mx4G=max(AMax3H2(bG,dG,fG),hG); AH2 mx4B=max(AMax3H2(bB,dB,fB),hB); // Immediate constants for peak range. AH2 peakC=AH2(1.0,-1.0*4.0); // Limiters, these need to be high precision RCPs. AH2 hitMinR=min(mn4R,eR)*ARcpH2(AH2_(4.0)*mx4R); AH2 hitMinG=min(mn4G,eG)*ARcpH2(AH2_(4.0)*mx4G); AH2 hitMinB=min(mn4B,eB)*ARcpH2(AH2_(4.0)*mx4B); AH2 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpH2(AH2_(4.0)*mn4R+peakC.y); AH2 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpH2(AH2_(4.0)*mn4G+peakC.y); AH2 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpH2(AH2_(4.0)*mn4B+peakC.y); AH2 lobeR=max(-hitMinR,hitMaxR); AH2 lobeG=max(-hitMinG,hitMaxG); AH2 lobeB=max(-hitMinB,hitMaxB); AH2 lobe=max(AH2_(-FSR_RCAS_LIMIT),min(AMax3H2(lobeR,lobeG,lobeB),AH2_(0.0)))*AH2_(AH2_AU1(con.y).x); // Apply noise removal. #ifdef FSR_RCAS_DENOISE lobe*=nz; #endif // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. AH2 rcpL=APrxMedRcpH2(AH2_(4.0)*lobe+AH2_(1.0)); pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // // FSR - [LFGA] LINEAR FILM GRAIN APPLICATOR // //------------------------------------------------------------------------------------------------------------------------------ // Adding output-resolution film grain after scaling is a good way to mask both rendering and scaling artifacts. // Suggest using tiled blue noise as film grain input, with peak noise frequency set for a specific look and feel. // The 'Lfga*()' functions provide a convenient way to introduce grain. // These functions limit grain based on distance to signal limits. // This is done so that the grain is temporally energy preserving, and thus won't modify image tonality. // Grain application should be done in a linear colorspace. // The grain should be temporally changing, but have a temporal sum per pixel that adds to zero (non-biased). //------------------------------------------------------------------------------------------------------------------------------ // Usage, // FsrLfga*( // color, // In/out linear colorspace color {0 to 1} ranged. // grain, // Per pixel grain texture value {-0.5 to 0.5} ranged, input is 3-channel to support colored grain. // amount); // Amount of grain (0 to 1} ranged. //------------------------------------------------------------------------------------------------------------------------------ // Example if grain texture is monochrome: 'FsrLfgaF(color,AF3_(grain),amount)' //============================================================================================================================== #if defined(A_GPU) // Maximum grain is the minimum distance to the signal limit. void FsrLfgaF(inout AF3 c,AF3 t,AF1 a){c+=(t*AF3_(a))*min(AF3_(1.0)-c,c);} #endif //============================================================================================================================== #if defined(A_GPU)&&defined(A_HALF) // Half precision version (slower). void FsrLfgaH(inout AH3 c,AH3 t,AH1 a){c+=(t*AH3_(a))*min(AH3_(1.0)-c,c);} //------------------------------------------------------------------------------------------------------------------------------ // Packed half precision version (faster). void FsrLfgaHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 tR,AH2 tG,AH2 tB,AH1 a){ cR+=(tR*AH2_(a))*min(AH2_(1.0)-cR,cR);cG+=(tG*AH2_(a))*min(AH2_(1.0)-cG,cG);cB+=(tB*AH2_(a))*min(AH2_(1.0)-cB,cB);} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // // FSR - [SRTM] SIMPLE REVERSIBLE TONE-MAPPER // //------------------------------------------------------------------------------------------------------------------------------ // This provides a way to take linear HDR color {0 to FP16_MAX} and convert it into a temporary {0 to 1} ranged post-tonemapped linear. // The tonemapper preserves RGB ratio, which helps maintain HDR color bleed during filtering. //------------------------------------------------------------------------------------------------------------------------------ // Reversible tonemapper usage, // FsrSrtm*(color); // {0 to FP16_MAX} converted to {0 to 1}. // FsrSrtmInv*(color); // {0 to 1} converted into {0 to 32768, output peak safe for FP16}. //============================================================================================================================== #if defined(A_GPU) void FsrSrtmF(inout AF3 c){c*=AF3_(ARcpF1(AMax3F1(c.r,c.g,c.b)+AF1_(1.0)));} // The extra max solves the c=1.0 case (which is a /0). void FsrSrtmInvF(inout AF3 c){c*=AF3_(ARcpF1(max(AF1_(1.0/32768.0),AF1_(1.0)-AMax3F1(c.r,c.g,c.b))));} #endif //============================================================================================================================== #if defined(A_GPU)&&defined(A_HALF) void FsrSrtmH(inout AH3 c){c*=AH3_(ARcpH1(AMax3H1(c.r,c.g,c.b)+AH1_(1.0)));} void FsrSrtmInvH(inout AH3 c){c*=AH3_(ARcpH1(max(AH1_(1.0/32768.0),AH1_(1.0)-AMax3H1(c.r,c.g,c.b))));} //------------------------------------------------------------------------------------------------------------------------------ void FsrSrtmHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ AH2 rcp=ARcpH2(AMax3H2(cR,cG,cB)+AH2_(1.0));cR*=rcp;cG*=rcp;cB*=rcp;} void FsrSrtmInvHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ AH2 rcp=ARcpH2(max(AH2_(1.0/32768.0),AH2_(1.0)-AMax3H2(cR,cG,cB)));cR*=rcp;cG*=rcp;cB*=rcp;} #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ //============================================================================================================================== // // FSR - [TEPD] TEMPORAL ENERGY PRESERVING DITHER // //------------------------------------------------------------------------------------------------------------------------------ // Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. // Gamma 2.0 is used so that the conversion back to linear is just to square the color. // The conversion comes in 8-bit and 10-bit modes, designed for output to 8-bit UNORM or 10:10:10:2 respectively. // Given good non-biased temporal blue noise as dither input, // the output dither will temporally conserve energy. // This is done by choosing the linear nearest step point instead of perceptual nearest. // See code below for details. //------------------------------------------------------------------------------------------------------------------------------ // DX SPEC RULES FOR FLOAT->UNORM 8-BIT CONVERSION // =============================================== // - Output is 'uint(floor(saturate(n)*255.0+0.5))'. // - Thus rounding is to nearest. // - NaN gets converted to zero. // - INF is clamped to {0.0 to 1.0}. //============================================================================================================================== #if defined(A_GPU) // Hand tuned integer position to dither value, with more values than simple checkerboard. // Only 32-bit has enough precision for this compddation. // Output is {0 to <1}. AF1 FsrTepdDitF(AU2 p,AU1 f){ AF1 x=AF1_(p.x+f); AF1 y=AF1_(p.y); // The 1.61803 golden ratio. AF1 a=AF1_((1.0+sqrt(5.0))/2.0); // Number designed to provide a good visual pattern. AF1 b=AF1_(1.0/3.69); x=x*a+(y*b); return AFractF1(x);} //------------------------------------------------------------------------------------------------------------------------------ // This version is 8-bit gamma 2.0. // The 'c' input is {0 to 1}. // Output is {0 to 1} ready for image store. void FsrTepdC8F(inout AF3 c,AF1 dit){ AF3 n=sqrt(c); n=floor(n*AF3_(255.0))*AF3_(1.0/255.0); AF3 a=n*n; AF3 b=n+AF3_(1.0/255.0);b=b*b; // Ratio of 'a' to 'b' required to produce 'c'. // APrxLoRcpF1() won't work here (at least for very high dynamic ranges). // APrxMedRcpF1() is an IADD,FMA,MUL. AF3 r=(c-b)*APrxMedRcpF3(a-b); // Use the ratio as a cutoff to choose 'a' or 'b'. // AGtZeroF1() is a MUL. c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/255.0));} //------------------------------------------------------------------------------------------------------------------------------ // This version is 10-bit gamma 2.0. // The 'c' input is {0 to 1}. // Output is {0 to 1} ready for image store. void FsrTepdC10F(inout AF3 c,AF1 dit){ AF3 n=sqrt(c); n=floor(n*AF3_(1023.0))*AF3_(1.0/1023.0); AF3 a=n*n; AF3 b=n+AF3_(1.0/1023.0);b=b*b; AF3 r=(c-b)*APrxMedRcpF3(a-b); c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/1023.0));} #endif //============================================================================================================================== #if defined(A_GPU)&&defined(A_HALF) AH1 FsrTepdDitH(AU2 p,AU1 f){ AF1 x=AF1_(p.x+f); AF1 y=AF1_(p.y); AF1 a=AF1_((1.0+sqrt(5.0))/2.0); AF1 b=AF1_(1.0/3.69); x=x*a+(y*b); return AH1(AFractF1(x));} //------------------------------------------------------------------------------------------------------------------------------ void FsrTepdC8H(inout AH3 c,AH1 dit){ AH3 n=sqrt(c); n=floor(n*AH3_(255.0))*AH3_(1.0/255.0); AH3 a=n*n; AH3 b=n+AH3_(1.0/255.0);b=b*b; AH3 r=(c-b)*APrxMedRcpH3(a-b); c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/255.0));} //------------------------------------------------------------------------------------------------------------------------------ void FsrTepdC10H(inout AH3 c,AH1 dit){ AH3 n=sqrt(c); n=floor(n*AH3_(1023.0))*AH3_(1.0/1023.0); AH3 a=n*n; AH3 b=n+AH3_(1.0/1023.0);b=b*b; AH3 r=(c-b)*APrxMedRcpH3(a-b); c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/1023.0));} //============================================================================================================================== // This computes dither for positions 'p' and 'p+{8,0}'. AH2 FsrTepdDitHx2(AU2 p,AU1 f){ AF2 x; x.x=AF1_(p.x+f); x.y=x.x+AF1_(8.0); AF1 y=AF1_(p.y); AF1 a=AF1_((1.0+sqrt(5.0))/2.0); AF1 b=AF1_(1.0/3.69); x=x*AF2_(a)+AF2_(y*b); return AH2(AFractF2(x));} //------------------------------------------------------------------------------------------------------------------------------ void FsrTepdC8Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ AH2 nR=sqrt(cR); AH2 nG=sqrt(cG); AH2 nB=sqrt(cB); nR=floor(nR*AH2_(255.0))*AH2_(1.0/255.0); nG=floor(nG*AH2_(255.0))*AH2_(1.0/255.0); nB=floor(nB*AH2_(255.0))*AH2_(1.0/255.0); AH2 aR=nR*nR; AH2 aG=nG*nG; AH2 aB=nB*nB; AH2 bR=nR+AH2_(1.0/255.0);bR=bR*bR; AH2 bG=nG+AH2_(1.0/255.0);bG=bG*bG; AH2 bB=nB+AH2_(1.0/255.0);bB=bB*bB; AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/255.0)); cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/255.0)); cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/255.0));} //------------------------------------------------------------------------------------------------------------------------------ void FsrTepdC10Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ AH2 nR=sqrt(cR); AH2 nG=sqrt(cG); AH2 nB=sqrt(cB); nR=floor(nR*AH2_(1023.0))*AH2_(1.0/1023.0); nG=floor(nG*AH2_(1023.0))*AH2_(1.0/1023.0); nB=floor(nB*AH2_(1023.0))*AH2_(1.0/1023.0); AH2 aR=nR*nR; AH2 aG=nG*nG; AH2 aB=nB*nB; AH2 bR=nR+AH2_(1.0/1023.0);bR=bR*bR; AH2 bG=nG+AH2_(1.0/1023.0);bG=bG*bG; AH2 bB=nB+AH2_(1.0/1023.0);bB=bB*bB; AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/1023.0)); cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/1023.0)); cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/1023.0));} #endif looking-glass-B6/client/renderers/EGL/shader/ffx_fsr1_easu.frag000066400000000000000000000021011434445012300246060ustar00rootroot00000000000000#version 300 es precision mediump float; #include "compat.h" in vec2 fragCoord; out vec4 fragColor; uniform sampler2D texture; uniform vec2 uOutRes; uniform uvec4 uConsts[4]; #define A_GPU 1 #define A_GLSL 1 #define A_FULL 1 #include "ffx_a.h" #define FSR_EASU_F 1 vec4 _textureGather(sampler2D tex, vec2 uv, int comp) { vec2 res = vec2(textureSize(tex, 0)); ivec2 p = ivec2((uv * res) - 0.5f); vec4 c0 = texelFetchOffset(tex, p, 0, ivec2(0,1)); vec4 c1 = texelFetchOffset(tex, p, 0, ivec2(1,1)); vec4 c2 = texelFetchOffset(tex, p, 0, ivec2(1,0)); vec4 c3 = texelFetchOffset(tex, p, 0, ivec2(0,0)); return vec4(c0[comp], c1[comp], c2[comp],c3[comp]); } AF4 FsrEasuRF(AF2 p){return AF4(_textureGather(texture, p, 0));} AF4 FsrEasuGF(AF2 p){return AF4(_textureGather(texture, p, 1));} AF4 FsrEasuBF(AF2 p){return AF4(_textureGather(texture, p, 2));} #include "ffx_fsr1.h" void main() { vec3 color; uvec2 point = uvec2(fragCoord * uOutRes); FsrEasuF(color, point, uConsts[0], uConsts[1], uConsts[2], uConsts[3]); fragColor = vec4(color.rgb, 1); } looking-glass-B6/client/renderers/EGL/shader/ffx_fsr1_rcas.frag000066400000000000000000000012051434445012300246050ustar00rootroot00000000000000#version 300 es precision mediump float; #include "compat.h" in vec2 fragCoord; out vec4 fragColor; uniform sampler2D texture; uniform uvec4 uConsts; #define A_GPU 1 #define A_GLSL 1 #define A_FULL 1 #include "ffx_a.h" AF4 FsrRcasLoadF(ASU2 p) { return texelFetch(texture, ASU2(p), 0); } void FsrRcasInputF(inout AF1 r, inout AF1 g, inout AF1 b) {} #define FSR_RCAS_F 1 #define FSR_RCAS_DENOISE 1 #include "ffx_fsr1.h" void main() { vec2 inRes = vec2(textureSize(texture, 0)); uvec2 point = uvec2(fragCoord * (inRes + 0.5f)); FsrRcasF(fragColor.r, fragColor.g, fragColor.b, point, uConsts); fragColor.a = 1.0f; } looking-glass-B6/client/renderers/EGL/texture.c000066400000000000000000000125421434445012300216220ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "texture.h" #include #include #include "shader.h" #include "common/framebuffer.h" #include "common/debug.h" #include #include #include "texture_buffer.h" extern const EGL_TextureOps EGL_TextureBuffer; extern const EGL_TextureOps EGL_TextureBufferStream; extern const EGL_TextureOps EGL_TextureFrameBuffer; extern const EGL_TextureOps EGL_TextureDMABUF; bool egl_textureInit(EGL_Texture ** texture_, EGLDisplay * display, EGL_TexType type) { const EGL_TextureOps * ops; switch(type) { case EGL_TEXTYPE_BUFFER: ops = &EGL_TextureBuffer; break; case EGL_TEXTYPE_BUFFER_MAP: case EGL_TEXTYPE_BUFFER_STREAM: ops = &EGL_TextureBufferStream; break; case EGL_TEXTYPE_FRAMEBUFFER: ops = &EGL_TextureFrameBuffer; break; case EGL_TEXTYPE_DMABUF: ops = &EGL_TextureDMABUF; break; default: return false; } *texture_ = NULL; if (!ops->init(texture_, type, display)) return false; EGL_Texture * this = *texture_; memcpy(&this->ops, ops, sizeof(*ops)); glGenSamplers(1, &this->sampler); glSamplerParameteri(this->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glSamplerParameteri(this->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glSamplerParameteri(this->sampler, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE); glSamplerParameteri(this->sampler, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE); return true; } void egl_textureFree(EGL_Texture ** tex) { EGL_Texture * this = *tex; if (!this) return; glDeleteSamplers(1, &this->sampler); this->ops.free(this); *tex = NULL; } bool egl_textureSetup(EGL_Texture * this, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride) { const struct EGL_TexSetup setup = { .pixFmt = pixFmt, .width = width, .height = height, .stride = stride }; if (!egl_texUtilGetFormat(&setup, &this->format)) return false; return this->ops.setup(this, &setup); } bool egl_textureUpdate(EGL_Texture * this, const uint8_t * buffer, bool topDown) { const struct EGL_TexUpdate update = { .type = EGL_TEXTYPE_BUFFER, .x = 0, .y = 0, .width = this->format.width, .height = this->format.height, .pitch = this->format.pitch, .stride = this->format.stride, .topDown = topDown, .buffer = buffer }; return this->ops.update(this, &update); } bool egl_textureUpdateRect(EGL_Texture * this, int x, int y, int width, int height, int stride, const uint8_t * buffer, bool topDown) { x = clamp(x , 0, this->format.width ); y = clamp(y , 0, this->format.height ); width = clamp(width , 0, this->format.width - x); height = clamp(height, 0, this->format.height - y); if (!width || !height) return true; const struct EGL_TexUpdate update = { .type = EGL_TEXTYPE_BUFFER, .x = x, .y = y, .width = width, .height = height, .pitch = stride / this->format.bpp, .stride = stride, .topDown = topDown, .buffer = buffer }; return this->ops.update(this, &update); } bool egl_textureUpdateFromFrame(EGL_Texture * this, const FrameBuffer * frame, const FrameDamageRect * damageRects, int damageRectsCount) { const struct EGL_TexUpdate update = { .type = EGL_TEXTYPE_FRAMEBUFFER, .x = 0, .y = 0, .width = this->format.width, .height = this->format.height, .pitch = this->format.pitch, .stride = this->format.stride, .frame = frame, .rects = damageRects, .rectCount = damageRectsCount, }; return this->ops.update(this, &update); } bool egl_textureUpdateFromDMA(EGL_Texture * this, const FrameBuffer * frame, const int dmaFd) { const struct EGL_TexUpdate update = { .type = EGL_TEXTYPE_DMABUF, .x = 0, .y = 0, .width = this->format.width, .height = this->format.height, .pitch = this->format.pitch, .stride = this->format.stride, .dmaFD = dmaFd }; /* wait for completion */ framebuffer_wait(frame, this->format.bufferSize); return this->ops.update(this, &update); } enum EGL_TexStatus egl_textureProcess(EGL_Texture * this) { return this->ops.process(this); } enum EGL_TexStatus egl_textureBind(EGL_Texture * this) { GLuint tex; EGL_TexStatus status; if ((status = this->ops.get(this, &tex)) != EGL_TEX_STATUS_OK) return status; glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex); glBindSampler(0, this->sampler); return EGL_TEX_STATUS_OK; } looking-glass-B6/client/renderers/EGL/texture.h000066400000000000000000000100371434445012300216240ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include "egl.h" #include "egltypes.h" #include "shader.h" #include "model.h" #include "common/framebuffer.h" #include "common/types.h" #include "util.h" #include #include #include "texture_util.h" typedef struct EGL_Model EGL_Model; typedef struct EGL_TexUpdate { /* the type of this update */ EGL_TexType type; int x, y, width, height; //pitch = row length in pixels //stride = row length in bytes int pitch, stride; union { /* EGL_TEXTYPE_BUFFER */ struct { // true if row 0 is the top of the image bool topDown; const uint8_t * buffer; }; /* EGL_TEXTYPE_FRAMEBUFFER */ struct { const FrameBuffer * frame; const FrameDamageRect * rects; int rectCount; }; /* EGL_TEXTYPE_DMABUF */ int dmaFD; }; } EGL_TexUpdate; typedef struct EGL_Texture EGL_Texture; typedef struct EGL_TextureOps { /* allocate & initialize an EGL_Texture */ bool (*init)(EGL_Texture ** texture, EGL_TexType type, EGLDisplay * display); /* free the EGL_Texture */ void (*free)(EGL_Texture * texture); /* setup/reconfigure the texture format */ bool (*setup)(EGL_Texture * texture, const EGL_TexSetup * setup); /* update the texture */ bool (*update)(EGL_Texture * texture, const EGL_TexUpdate * update); /* called from a background job to prepare the texture for use before bind */ enum EGL_TexStatus (*process)(EGL_Texture * texture); /* get the texture for use */ enum EGL_TexStatus (*get)(EGL_Texture * texture, GLuint * tex); } EGL_TextureOps; struct EGL_Texture { struct EGL_TextureOps ops; EGL_TexType type; GLuint sampler; EGL_TexFormat format; }; bool egl_textureInit(EGL_Texture ** texture, EGLDisplay * display, EGL_TexType type); void egl_textureFree(EGL_Texture ** tex); bool egl_textureSetup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride); bool egl_textureUpdate(EGL_Texture * texture, const uint8_t * buffer, bool topDown); bool egl_textureUpdateRect(EGL_Texture * texture, int x, int y, int width, int height, int stride, const uint8_t * buffer, bool topDown); bool egl_textureUpdateFromFrame(EGL_Texture * texture, const FrameBuffer * frame, const FrameDamageRect * damageRects, int damageRectsCount); bool egl_textureUpdateFromDMA(EGL_Texture * texture, const FrameBuffer * frame, const int dmaFd); enum EGL_TexStatus egl_textureProcess(EGL_Texture * texture); static inline EGL_TexStatus egl_textureGet(EGL_Texture * texture, GLuint * tex, unsigned int * sizeX, unsigned int * sizeY) { if (sizeX) *sizeX = texture->format.width; if (sizeY) *sizeY = texture->format.height; return texture->ops.get(texture, tex); } enum EGL_TexStatus egl_textureBind(EGL_Texture * texture); typedef void * PostProcessHandle; PostProcessHandle egl_textureAddFilter(EGL_Texture * texture, EGL_Shader * shader, bool enabled); void egl_textureEnableFilter(PostProcessHandle * handle, bool enable); void egl_textureSetFilterRes(PostProcessHandle * handle, unsigned int x, unsigned int y); void egl_textureInvalidate(EGL_Texture * texture); void egl_textureGetFinalSize(EGL_Texture * texture, struct Rect * rect); looking-glass-B6/client/renderers/EGL/texture_buffer.c000066400000000000000000000166521434445012300231610ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "texture_buffer.h" #include "egldebug.h" #include // forwards extern const EGL_TextureOps EGL_TextureBuffer; extern const EGL_TextureOps EGL_TextureBufferStream; // internal functions static void egl_texBuffer_cleanup(TextureBuffer * this) { egl_texUtilFreeBuffers(this->buf, this->texCount); if (this->tex[0]) glDeleteTextures(this->texCount, this->tex); if (this->sync) { glDeleteSync(this->sync); this->sync = 0; } } // common functions bool egl_texBufferInit(EGL_Texture ** texture, EGL_TexType type, EGLDisplay * display) { TextureBuffer * this; if (!*texture) { this = calloc(1, sizeof(*this)); if (!this) { DEBUG_ERROR("Failed to malloc TexB"); return false; } *texture = &this->base; this->free = true; } else this = UPCAST(TextureBuffer, *texture); this->texCount = 1; return true; } void egl_texBufferFree(EGL_Texture * texture) { TextureBuffer * this = UPCAST(TextureBuffer, texture); egl_texBuffer_cleanup(this); LG_LOCK_FREE(this->copyLock); if (this->free) free(this); } bool egl_texBufferSetup(EGL_Texture * texture, const EGL_TexSetup * setup) { TextureBuffer * this = UPCAST(TextureBuffer, texture); egl_texBuffer_cleanup(this); glGenTextures(this->texCount, this->tex); for(int i = 0; i < this->texCount; ++i) { glBindTexture(GL_TEXTURE_2D, this->tex[i]); glTexImage2D(GL_TEXTURE_2D, 0, texture->format.intFormat, texture->format.width, texture->format.height, 0, texture->format.format, texture->format.dataType, NULL); } glBindTexture(GL_TEXTURE_2D, 0); this->rIndex = -1; return true; } static bool egl_texBufferUpdate(EGL_Texture * texture, const EGL_TexUpdate * update) { TextureBuffer * this = UPCAST(TextureBuffer, texture); DEBUG_ASSERT(update->type == EGL_TEXTYPE_BUFFER); glBindTexture(GL_TEXTURE_2D, this->tex[0]); glPixelStorei(GL_UNPACK_ROW_LENGTH, update->pitch); glTexSubImage2D(GL_TEXTURE_2D, 0, update->x, update->y, update->width, update->height, texture->format.format, texture->format.dataType, update->buffer); glBindTexture(GL_TEXTURE_2D, 0); return true; } EGL_TexStatus egl_texBufferProcess(EGL_Texture * texture) { return EGL_TEX_STATUS_OK; } EGL_TexStatus egl_texBufferGet(EGL_Texture * texture, GLuint * tex) { TextureBuffer * this = UPCAST(TextureBuffer, texture); *tex = this->tex[0]; return EGL_TEX_STATUS_OK; } // streaming functions bool egl_texBufferStreamInit(EGL_Texture ** texture, EGL_TexType type, EGLDisplay * display) { if (!egl_texBufferInit(texture, type, display)) return false; TextureBuffer * this = UPCAST(TextureBuffer, *texture); switch(type) { case EGL_TEXTYPE_BUFFER_STREAM: case EGL_TEXTYPE_FRAMEBUFFER: case EGL_TEXTYPE_DMABUF: this->texCount = 2; break; case EGL_TEXTYPE_BUFFER_MAP: this->texCount = 1; break; default: DEBUG_UNREACHABLE(); } LG_LOCK_INIT(this->copyLock); return true; } bool egl_texBufferStreamSetup(EGL_Texture * texture, const EGL_TexSetup * setup) { if (!egl_texBufferSetup(texture, setup)) return false; TextureBuffer * this = UPCAST(TextureBuffer, texture); return egl_texUtilGenBuffers(&texture->format, this->buf, this->texCount); } static bool egl_texBufferStreamUpdate(EGL_Texture * texture, const EGL_TexUpdate * update) { TextureBuffer * this = UPCAST(TextureBuffer, texture); DEBUG_ASSERT(update->type == EGL_TEXTYPE_BUFFER); LG_LOCK(this->copyLock); uint8_t * dst = this->buf[this->bufIndex].map + texture->format.stride * update->y + update->x * texture->format.bpp; if (update->topDown) { const uint8_t * src = update->buffer; for(int y = 0; y < update->height; ++y) { memcpy(dst, src, update->stride); dst += texture->format.stride; src += update->stride; } } else { const uint8_t * src = update->buffer + update->stride * update->height; for(int y = 0; y < update->height; ++y) { src -= update->stride; memcpy(dst, src, update->stride); dst += texture->format.stride; } } this->buf[this->bufIndex].updated = true; LG_UNLOCK(this->copyLock); return true; } EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture) { TextureBuffer * this = UPCAST(TextureBuffer, texture); LG_LOCK(this->copyLock); GLuint tex = this->tex[this->bufIndex]; EGL_TexBuffer * buffer = &this->buf[this->bufIndex]; if (buffer->updated && this->sync == 0) { this->rIndex = this->bufIndex; if (++this->bufIndex == this->texCount) this->bufIndex = 0; } LG_UNLOCK(this->copyLock); if (buffer->updated) { buffer->updated = false; glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo); glBindTexture(GL_TEXTURE_2D, tex); glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->format.pitch); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->format.width, texture->format.height, texture->format.format, texture->format.dataType, (const void *)0); glBindTexture(GL_TEXTURE_2D, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); this->sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); } return EGL_TEX_STATUS_OK; } EGL_TexStatus egl_texBufferStreamGet(EGL_Texture * texture, GLuint * tex) { TextureBuffer * this = UPCAST(TextureBuffer, texture); if (this->rIndex == -1) return EGL_TEX_STATUS_NOTREADY; if (this->sync) { switch(glClientWaitSync(this->sync, 0, 40000000)) // 40ms { case GL_ALREADY_SIGNALED: case GL_CONDITION_SATISFIED: glDeleteSync(this->sync); this->sync = 0; break; case GL_TIMEOUT_EXPIRED: return EGL_TEX_STATUS_NOTREADY; case GL_WAIT_FAILED: case GL_INVALID_VALUE: glDeleteSync(this->sync); this->sync = 0; DEBUG_GL_ERROR("glClientWaitSync failed"); return EGL_TEX_STATUS_ERROR; } } *tex = this->tex[this->rIndex]; return EGL_TEX_STATUS_OK; } const EGL_TextureOps EGL_TextureBuffer = { .init = egl_texBufferInit, .free = egl_texBufferFree, .setup = egl_texBufferSetup, .update = egl_texBufferUpdate, .process = egl_texBufferProcess, .get = egl_texBufferGet }; const EGL_TextureOps EGL_TextureBufferStream = { .init = egl_texBufferStreamInit, .free = egl_texBufferFree, .setup = egl_texBufferStreamSetup, .update = egl_texBufferStreamUpdate, .process = egl_texBufferStreamProcess, .get = egl_texBufferStreamGet }; looking-glass-B6/client/renderers/EGL/texture_buffer.h000066400000000000000000000036121434445012300231560ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include "texture.h" #include "texture_util.h" #include "common/locking.h" #define EGL_TEX_BUFFER_MAX 2 typedef struct TextureBuffer { EGL_Texture base; bool free; int texCount; GLuint tex[EGL_TEX_BUFFER_MAX]; EGL_TexBuffer buf[EGL_TEX_BUFFER_MAX]; int bufFree; GLsync sync; LG_Lock copyLock; int bufIndex; int rIndex; } TextureBuffer; bool egl_texBufferInit(EGL_Texture ** texture_, EGL_TexType type, EGLDisplay * display); void egl_texBufferFree(EGL_Texture * texture_); bool egl_texBufferSetup(EGL_Texture * texture_, const EGL_TexSetup * setup); EGL_TexStatus egl_texBufferProcess(EGL_Texture * texture_); EGL_TexStatus egl_texBufferGet(EGL_Texture * texture_, GLuint * tex); bool egl_texBufferStreamInit(EGL_Texture ** texture_, EGL_TexType type, EGLDisplay * display); bool egl_texBufferStreamSetup(EGL_Texture * texture_, const EGL_TexSetup * setup); EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture_); EGL_TexStatus egl_texBufferStreamGet(EGL_Texture * texture_, GLuint * tex); looking-glass-B6/client/renderers/EGL/texture_dmabuf.c000066400000000000000000000143441434445012300231420ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "texture.h" #include "texture_buffer.h" #include "util.h" #include "common/vector.h" #include "egl_dynprocs.h" #include "egldebug.h" struct FdImage { int fd; EGLImage image; }; typedef struct TexDMABUF { TextureBuffer base; EGLDisplay display; bool hasImportModifiers; Vector images; } TexDMABUF; EGL_TextureOps EGL_TextureDMABUF; // internal functions static void egl_texDMABUFCleanup(TexDMABUF * this) { struct FdImage * image; vector_forEachRef(image, &this->images) g_egl_dynProcs.eglDestroyImage(this->display, image->image); vector_clear(&this->images); } // dmabuf functions static bool egl_texDMABUFInit(EGL_Texture ** texture, EGL_TexType type, EGLDisplay * display) { TexDMABUF * this = calloc(1, sizeof(*this)); *texture = &this->base.base; if (!vector_create(&this->images, sizeof(struct FdImage), 2)) { free(this); *texture = NULL; return false; } EGL_Texture * parent = &this->base.base; if (!egl_texBufferStreamInit(&parent, type, display)) { vector_destroy(&this->images); free(this); *texture = NULL; return false; } this->display = display; const char * client_exts = eglQueryString(this->display, EGL_EXTENSIONS); this->hasImportModifiers = util_hasGLExt(client_exts, "EGL_EXT_image_dma_buf_import_modifiers"); return true; } static void egl_texDMABUFFree(EGL_Texture * texture) { TextureBuffer * parent = UPCAST(TextureBuffer, texture); TexDMABUF * this = UPCAST(TexDMABUF , parent); egl_texDMABUFCleanup(this); vector_destroy(&this->images); egl_texBufferFree(&parent->base); free(this); } static bool egl_texDMABUFSetup(EGL_Texture * texture, const EGL_TexSetup * setup) { TextureBuffer * parent = UPCAST(TextureBuffer, texture); TexDMABUF * this = UPCAST(TexDMABUF , parent); egl_texDMABUFCleanup(this); return egl_texBufferSetup(&parent->base, setup); } static bool egl_texDMABUFUpdate(EGL_Texture * texture, const EGL_TexUpdate * update) { TextureBuffer * parent = UPCAST(TextureBuffer, texture); TexDMABUF * this = UPCAST(TexDMABUF , parent); DEBUG_ASSERT(update->type == EGL_TEXTYPE_DMABUF); EGLImage image = EGL_NO_IMAGE; struct FdImage * fdImage; vector_forEachRef(fdImage, &this->images) if (fdImage->fd == update->dmaFD) { image = fdImage->image; break; } if (image == EGL_NO_IMAGE) { const uint64_t modifier = DRM_FORMAT_MOD_LINEAR; EGLAttrib attribs[] = { EGL_WIDTH , texture->format.width, EGL_HEIGHT , texture->format.height, EGL_LINUX_DRM_FOURCC_EXT , texture->format.fourcc, EGL_DMA_BUF_PLANE0_FD_EXT , update->dmaFD, EGL_DMA_BUF_PLANE0_OFFSET_EXT , 0, EGL_DMA_BUF_PLANE0_PITCH_EXT , texture->format.stride, EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, (modifier & 0xffffffff), EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, (modifier >> 32), EGL_NONE , EGL_NONE }; if (!this->hasImportModifiers) attribs[12] = attribs[13] = attribs[14] = attribs[15] = EGL_NONE; image = g_egl_dynProcs.eglCreateImage( this->display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)NULL, attribs); if (image == EGL_NO_IMAGE) { DEBUG_EGL_ERROR("Failed to create EGLImage for DMA transfer"); return false; } if (!vector_push(&this->images, &(struct FdImage) { .fd = update->dmaFD, .image = image, })) { DEBUG_ERROR("Failed to store EGLImage"); g_egl_dynProcs.eglDestroyImage(this->display, image); return false; } } INTERLOCKED_SECTION(parent->copyLock, { glBindTexture(GL_TEXTURE_2D, parent->tex[parent->bufIndex]); g_egl_dynProcs.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); if (parent->sync) glDeleteSync(parent->sync); parent->sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); }); glFlush(); return true; } static EGL_TexStatus egl_texDMABUFProcess(EGL_Texture * texture) { return EGL_TEX_STATUS_OK; } static EGL_TexStatus egl_texDMABUFGet(EGL_Texture * texture, GLuint * tex) { TextureBuffer * parent = UPCAST(TextureBuffer, texture); GLsync sync = 0; INTERLOCKED_SECTION(parent->copyLock, { if (parent->sync) { sync = parent->sync; parent->sync = 0; parent->rIndex = parent->bufIndex; if (++parent->bufIndex == parent->texCount) parent->bufIndex = 0; } }); if (sync) { switch(glClientWaitSync(sync, 0, 20000000)) // 20ms { case GL_ALREADY_SIGNALED: case GL_CONDITION_SATISFIED: glDeleteSync(sync); break; case GL_TIMEOUT_EXPIRED: INTERLOCKED_SECTION(parent->copyLock, { if (!parent->sync) parent->sync = sync; else glDeleteSync(sync); }); return EGL_TEX_STATUS_NOTREADY; case GL_WAIT_FAILED: case GL_INVALID_VALUE: glDeleteSync(sync); DEBUG_GL_ERROR("glClientWaitSync failed"); return EGL_TEX_STATUS_ERROR; } } *tex = parent->tex[parent->rIndex]; return EGL_TEX_STATUS_OK; } EGL_TextureOps EGL_TextureDMABUF = { .init = egl_texDMABUFInit, .free = egl_texDMABUFFree, .setup = egl_texDMABUFSetup, .update = egl_texDMABUFUpdate, .process = egl_texDMABUFProcess, .get = egl_texDMABUFGet }; looking-glass-B6/client/renderers/EGL/texture_framebuffer.c000066400000000000000000000100241434445012300241570ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "texture.h" #include "texture_buffer.h" #include "common/debug.h" #include "common/KVMFR.h" #include "common/rects.h" struct TexDamage { int count; FrameDamageRect rects[KVMFR_MAX_DAMAGE_RECTS]; }; typedef struct TexFB { TextureBuffer base; struct TexDamage damage[EGL_TEX_BUFFER_MAX]; } TexFB; static bool egl_texFBInit(EGL_Texture ** texture, EGL_TexType type, EGLDisplay * display) { TexFB * this = calloc(1, sizeof(*this)); *texture = &this->base.base; EGL_Texture * parent = &this->base.base; if (!egl_texBufferStreamInit(&parent, type, display)) { free(this); *texture = NULL; return false; } for (int i = 0; i < EGL_TEX_BUFFER_MAX; ++i) this->damage[i].count = -1; return true; } void egl_texFBFree(EGL_Texture * texture) { TextureBuffer * parent = UPCAST(TextureBuffer, texture); TexFB * this = UPCAST(TexFB , parent ); egl_texBufferFree(texture); free(this); } bool egl_texFBSetup(EGL_Texture * texture, const EGL_TexSetup * setup) { TextureBuffer * parent = UPCAST(TextureBuffer, texture); TexFB * this = UPCAST(TexFB , parent ); for (int i = 0; i < EGL_TEX_BUFFER_MAX; ++i) this->damage[i].count = -1; return egl_texBufferStreamSetup(texture, setup); } static bool egl_texFBUpdate(EGL_Texture * texture, const EGL_TexUpdate * update) { TextureBuffer * parent = UPCAST(TextureBuffer, texture); TexFB * this = UPCAST(TexFB , parent ); DEBUG_ASSERT(update->type == EGL_TEXTYPE_FRAMEBUFFER); LG_LOCK(parent->copyLock); struct TexDamage * damage = this->damage + parent->bufIndex; bool damageAll = !update->rects || update->rectCount == 0 || damage->count < 0 || damage->count + update->rectCount > KVMFR_MAX_DAMAGE_RECTS; if (damageAll) framebuffer_read( update->frame, parent->buf[parent->bufIndex].map, texture->format.stride, texture->format.height, texture->format.width, texture->format.bpp, texture->format.stride ); else { memcpy(damage->rects + damage->count, update->rects, update->rectCount * sizeof(FrameDamageRect)); damage->count += update->rectCount; rectsFramebufferToBuffer( damage->rects, damage->count, parent->buf[parent->bufIndex].map, texture->format.stride, texture->format.height, update->frame, texture->format.stride ); } parent->buf[parent->bufIndex].updated = true; for (int i = 0; i < EGL_TEX_BUFFER_MAX; ++i) { struct TexDamage * damage = this->damage + i; if (i == parent->bufIndex) damage->count = 0; else if (update->rects && update->rectCount > 0 && damage->count >= 0 && damage->count + update->rectCount <= KVMFR_MAX_DAMAGE_RECTS) { memcpy(damage->rects + damage->count, update->rects, update->rectCount * sizeof(FrameDamageRect)); damage->count += update->rectCount; } else damage->count = -1; } LG_UNLOCK(parent->copyLock); return true; } EGL_TextureOps EGL_TextureFrameBuffer = { .init = egl_texFBInit, .free = egl_texFBFree, .setup = egl_texFBSetup, .update = egl_texFBUpdate, .process = egl_texBufferStreamProcess, .get = egl_texBufferStreamGet }; looking-glass-B6/client/renderers/EGL/texture_util.c000066400000000000000000000100111434445012300226440ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "texture.h" #include "texture_util.h" #include #include #include #include "egldebug.h" #include "egl_dynprocs.h" bool egl_texUtilGetFormat(const EGL_TexSetup * setup, EGL_TexFormat * fmt) { switch(setup->pixFmt) { case EGL_PF_BGRA: fmt->bpp = 4; fmt->format = GL_BGRA_EXT; fmt->intFormat = GL_BGRA_EXT; fmt->dataType = GL_UNSIGNED_BYTE; fmt->fourcc = DRM_FORMAT_ARGB8888; break; case EGL_PF_RGBA: fmt->bpp = 4; fmt->format = GL_RGBA; fmt->intFormat = GL_RGBA; fmt->dataType = GL_UNSIGNED_BYTE; fmt->fourcc = DRM_FORMAT_ABGR8888; break; case EGL_PF_RGBA10: fmt->bpp = 4; fmt->format = GL_RGBA; fmt->intFormat = GL_RGB10_A2; fmt->dataType = GL_UNSIGNED_INT_2_10_10_10_REV; fmt->fourcc = DRM_FORMAT_BGRA1010102; break; case EGL_PF_RGBA16F: fmt->bpp = 8; fmt->format = GL_RGBA; fmt->intFormat = GL_RGBA16F; fmt->dataType = GL_HALF_FLOAT; fmt->fourcc = DRM_FORMAT_ABGR16161616F; break; default: DEBUG_ERROR("Unsupported pixel format"); return false; } fmt->pixFmt = setup->pixFmt; fmt->width = setup->width; fmt->height = setup->height; if (setup->stride == 0) { fmt->stride = fmt->width * fmt->bpp; fmt->pitch = fmt->width; } else { fmt->stride = setup->stride; fmt->pitch = setup->stride / fmt->bpp; } fmt->bufferSize = fmt->height * fmt->stride; return true; } bool egl_texUtilGenBuffers(const EGL_TexFormat * fmt, EGL_TexBuffer * buffers, int count) { for(int i = 0; i < count; ++i) { EGL_TexBuffer *buffer = &buffers[i]; buffer->size = fmt->bufferSize; glGenBuffers(1, &buffer->pbo); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo); g_egl_dynProcs.glBufferStorageEXT( GL_PIXEL_UNPACK_BUFFER, fmt->bufferSize, NULL, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT_EXT | GL_MAP_COHERENT_BIT_EXT ); if (!egl_texUtilMapBuffer(buffer)) return false; } return true; } void egl_texUtilFreeBuffers(EGL_TexBuffer * buffers, int count) { for(int i = 0; i < count; ++i) { EGL_TexBuffer *buffer = &buffers[i]; if (!buffer->pbo) continue; egl_texUtilUnmapBuffer(buffer); glDeleteBuffers(1, &buffer->pbo); buffer->pbo = 0; } } bool egl_texUtilMapBuffer(EGL_TexBuffer * buffer) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo); buffer->map = glMapBufferRange( GL_PIXEL_UNPACK_BUFFER, 0, buffer->size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_PERSISTENT_BIT_EXT | GL_MAP_COHERENT_BIT_EXT); if (!buffer->map) DEBUG_GL_ERROR("glMapBufferRange failed of %lu bytes", buffer->size); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); return buffer->map; } void egl_texUtilUnmapBuffer(EGL_TexBuffer * buffer) { if (!buffer->map) return; glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo); glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); buffer->map = NULL; } looking-glass-B6/client/renderers/EGL/texture_util.h000066400000000000000000000045471434445012300226720ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include "egltypes.h" //typedef struct EGL_TexSetup EGL_TexSetup; typedef struct EGL_TexFormat { EGL_PixelFormat pixFmt; size_t bpp; GLenum format; GLenum intFormat; GLenum dataType; unsigned int fourcc; size_t bufferSize; size_t width , height; size_t stride, pitch; } EGL_TexFormat; typedef struct EGL_TexBuffer { size_t size; GLuint pbo; void * map; bool updated; } EGL_TexBuffer; bool egl_texUtilGetFormat(const EGL_TexSetup * setup, EGL_TexFormat * fmt); bool egl_texUtilGenBuffers(const EGL_TexFormat * fmt, EGL_TexBuffer * buffers, int count); void egl_texUtilFreeBuffers(EGL_TexBuffer * buffers, int count); bool egl_texUtilMapBuffer(EGL_TexBuffer * buffer); void egl_texUtilUnmapBuffer(EGL_TexBuffer * buffer); /** * the following comes from drm_fourcc.h and is included here to avoid the * external dependency for the few simple defines we need */ #define fourcc_code(a, b, c, d) ((uint32_t)(a) | ((uint32_t)(b) << 8) | \ ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24)) #define DRM_FORMAT_ARGB8888 fourcc_code('A', 'R', '2', '4') #define DRM_FORMAT_ABGR8888 fourcc_code('A', 'B', '2', '4') #define DRM_FORMAT_BGRA1010102 fourcc_code('B', 'A', '3', '0') #define DRM_FORMAT_ABGR16161616F fourcc_code('A', 'B', '4', 'H') #define DRM_FORMAT_MOD_VENDOR_NONE 0 #define fourcc_mod_code(vendor, val) \ ((((uint64_t)DRM_FORMAT_MOD_VENDOR_## vendor) << 56) | \ ((val) & 0x00ffffffffffffffULL)) #define DRM_FORMAT_MOD_LINEAR fourcc_mod_code(NONE, 0) looking-glass-B6/client/renderers/OpenGL/000077500000000000000000000000001434445012300204675ustar00rootroot00000000000000looking-glass-B6/client/renderers/OpenGL/CMakeLists.txt000066400000000000000000000010201434445012300232200ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(renderer_Opengl LANGUAGES C CXX) find_package(PkgConfig) pkg_check_modules(RENDERER_OPENGL REQUIRED IMPORTED_TARGET gl ) add_library(renderer_OpenGL STATIC opengl.c ${PROJECT_TOP}/repos/cimgui/imgui/backends/imgui_impl_opengl2.cpp ) target_compile_definitions(renderer_OpenGL PRIVATE CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1) target_link_libraries(renderer_OpenGL PkgConfig::RENDERER_OPENGL lg_common cimgui ) target_include_directories(renderer_OpenGL PRIVATE src ) looking-glass-B6/client/renderers/OpenGL/opengl.c000066400000000000000000000770531434445012300221330ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/renderer.h" #include #include #include #include #include #include #include #include "cimgui.h" #include "generator/output/cimgui_impl.h" #include "common/debug.h" #include "common/option.h" #include "common/framebuffer.h" #include "common/locking.h" #include "gl_dynprocs.h" #include "util.h" #define BUFFER_COUNT 2 #define FPS_TEXTURE 0 #define MOUSE_TEXTURE 1 #define SPICE_TEXTURE 2 #define TEXTURE_COUNT 3 static struct Option opengl_options[] = { { .module = "opengl", .name = "mipmap", .description = "Enable mipmapping", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "opengl", .name = "vsync", .description = "Enable vsync", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "opengl", .name = "preventBuffer", .description = "Prevent the driver from buffering frames", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "opengl", .name = "amdPinnedMem", .description = "Use GL_AMD_pinned_memory if it is available", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, {0} }; struct IntPoint { int x; int y; }; struct IntRect { int x; int y; int w; int h; }; struct OpenGL_Options { bool mipmap; bool vsync; bool preventBuffer; bool amdPinnedMem; }; struct Inst { LG_Renderer base; LG_RendererParams params; struct OpenGL_Options opt; bool amdPinnedMemSupport; bool renderStarted; bool configured; bool reconfigure; LG_DSGLContext glContext; struct IntPoint window; float uiScale; _Atomic(bool) frameUpdate; LG_Lock formatLock; LG_RendererFormat format; GLuint intFormat; GLuint vboFormat; GLuint dataFormat; size_t texSize; size_t texPos; const FrameBuffer * frame; uint64_t drawStart; bool hasBuffers; GLuint vboID[BUFFER_COUNT]; uint8_t * texPixels[BUFFER_COUNT]; LG_Lock frameLock; bool texReady; int texWIndex, texRIndex; int texList; int mouseList; int spiceList; LG_RendererRect destRect; struct IntPoint spiceSize; bool spiceShow; bool hasTextures, hasFrames; GLuint frames[BUFFER_COUNT]; GLsync fences[BUFFER_COUNT]; GLuint textures[TEXTURE_COUNT]; LG_Lock mouseLock; LG_RendererCursor mouseCursor; int mouseWidth; int mouseHeight; int mousePitch; uint8_t * mouseData; size_t mouseDataSize; bool mouseUpdate; bool newShape; LG_RendererCursor mouseType; bool mouseVisible; struct IntRect mousePos; }; static bool _checkGLError(unsigned int line, const char * name); #define check_gl_error(name) _checkGLError(__LINE__, name) enum ConfigStatus { CONFIG_STATUS_OK, CONFIG_STATUS_ERROR, CONFIG_STATUS_NOOP }; static void deconfigure(struct Inst * this); static enum ConfigStatus configure(struct Inst * this); static void updateMouseShape(struct Inst * this); static bool drawFrame(struct Inst * this); static void drawMouse(struct Inst * this); const char * opengl_getName(void) { return "OpenGL"; } static void opengl_setup(void) { option_register(opengl_options); } bool opengl_create(LG_Renderer ** renderer, const LG_RendererParams params, bool * needsOpenGL) { // create our local storage struct Inst * this = calloc(1, sizeof(*this)); if (!this) { DEBUG_INFO("Failed to allocate %lu bytes", sizeof(*this)); return false; } *renderer = &this->base; memcpy(&this->params, ¶ms, sizeof(LG_RendererParams)); this->opt.mipmap = option_get_bool("opengl", "mipmap" ); this->opt.vsync = option_get_bool("opengl", "vsync" ); this->opt.preventBuffer = option_get_bool("opengl", "preventBuffer"); this->opt.amdPinnedMem = option_get_bool("opengl", "amdPinnedMem" ); LG_LOCK_INIT(this->formatLock); LG_LOCK_INIT(this->frameLock ); LG_LOCK_INIT(this->mouseLock ); *needsOpenGL = true; return true; } bool opengl_initialize(LG_Renderer * renderer) { // struct Inst * this = UPCAST(struct Inst, renderer); return true; } void opengl_deinitialize(LG_Renderer * renderer) { struct Inst * this = UPCAST(struct Inst, renderer); if (this->renderStarted) { ImGui_ImplOpenGL2_Shutdown(); glDeleteLists(this->texList , BUFFER_COUNT); glDeleteLists(this->mouseList, 1); glDeleteLists(this->spiceList, 1); } deconfigure(this); if (this->hasTextures) { glDeleteTextures(TEXTURE_COUNT, this->textures); this->hasTextures = false; } if (this->mouseData) free(this->mouseData); if (this->glContext) { app_glDeleteContext(this->glContext); this->glContext = NULL; } LG_LOCK_FREE(this->formatLock); LG_LOCK_FREE(this->frameLock ); LG_LOCK_FREE(this->mouseLock ); free(this); } void opengl_onRestart(LG_Renderer * renderer) { // struct Inst * this = UPCAST(struct Inst, renderer); } static void setupModelView(struct Inst * this) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); if (!this->destRect.valid) return; int fw, fh; if (this->spiceShow) { fw = this->spiceSize.x; fh = this->spiceSize.y; } else { fw = this->format.frameWidth; fh = this->format.frameHeight; } glTranslatef(this->destRect.x, this->destRect.y, 0.0f); glScalef( (float)this->destRect.w / (float)fw, (float)this->destRect.h / (float)fh, 1.0f ); } void opengl_onResize(LG_Renderer * renderer, const int width, const int height, const double scale, const LG_RendererRect destRect, LG_RendererRotate rotate) { struct Inst * this = UPCAST(struct Inst, renderer); this->window.x = width * scale; this->window.y = height * scale; this->uiScale = (float) scale; if (destRect.valid) { this->destRect.valid = true; this->destRect.x = destRect.x * scale; this->destRect.y = destRect.y * scale; this->destRect.w = destRect.w * scale; this->destRect.h = destRect.h * scale; } // setup the projection matrix glViewport(0, 0, this->window.x, this->window.y); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, this->window.x, this->window.y, 0, -1, 1); // this is needed to refresh the font atlas texture ImGui_ImplOpenGL2_Shutdown(); ImGui_ImplOpenGL2_Init(); ImGui_ImplOpenGL2_NewFrame(); } bool opengl_onMouseShape(LG_Renderer * renderer, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data) { struct Inst * this = UPCAST(struct Inst, renderer); LG_LOCK(this->mouseLock); this->mouseCursor = cursor; this->mouseWidth = width; this->mouseHeight = height; this->mousePitch = pitch; const size_t size = height * pitch; if (size > this->mouseDataSize) { if (this->mouseData) free(this->mouseData); this->mouseData = malloc(size); if (!this->mouseData) { DEBUG_ERROR("out of memory"); return false; } this->mouseDataSize = size; } memcpy(this->mouseData, data, size); this->newShape = true; LG_UNLOCK(this->mouseLock); return true; } bool opengl_onMouseEvent(LG_Renderer * renderer, const bool visible, int x, int y, const int hx, const int hy) { struct Inst * this = UPCAST(struct Inst, renderer); if (this->mousePos.x == x && this->mousePos.y == y && this->mouseVisible == visible) return true; this->mouseVisible = visible; this->mousePos.x = x; this->mousePos.y = y; this->mouseUpdate = true; return false; } bool opengl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat format) { struct Inst * this = UPCAST(struct Inst, renderer); LG_LOCK(this->formatLock); memcpy(&this->format, &format, sizeof(LG_RendererFormat)); this->reconfigure = true; LG_UNLOCK(this->formatLock); return true; } bool opengl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame, int dmaFd, const FrameDamageRect * damage, int damageCount) { struct Inst * this = UPCAST(struct Inst, renderer); LG_LOCK(this->frameLock); this->frame = frame; atomic_store_explicit(&this->frameUpdate, true, memory_order_release); LG_UNLOCK(this->frameLock); return true; } bool opengl_renderStartup(LG_Renderer * renderer, bool useDMA) { struct Inst * this = UPCAST(struct Inst, renderer); this->glContext = app_glCreateContext(); if (!this->glContext) return false; app_glMakeCurrent(this->glContext); DEBUG_INFO("Vendor : %s", glGetString(GL_VENDOR )); DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER)); DEBUG_INFO("Version : %s", glGetString(GL_VERSION )); const char * exts = (const char *)glGetString(GL_EXTENSIONS); if (util_hasGLExt(exts, "GL_AMD_pinned_memory")) { if (this->opt.amdPinnedMem) { this->amdPinnedMemSupport = true; DEBUG_INFO("Using GL_AMD_pinned_memory"); } else DEBUG_INFO("GL_AMD_pinned_memory is available but not in use"); } GLint maj, min; glGetIntegerv(GL_MAJOR_VERSION, &maj); glGetIntegerv(GL_MINOR_VERSION, &min); if ((maj < 3 || (maj == 3 && min < 2)) && !util_hasGLExt(exts, "GL_ARB_sync")) { DEBUG_ERROR("Need OpenGL 3.2+ or GL_ARB_sync for sync objects"); return false; } if (maj < 2 && !util_hasGLExt(exts, "GL_ARB_pixel_buffer_object")) { DEBUG_ERROR("Need OpenGL 2.0+ or GL_ARB_pixel_buffer_object"); return false; } if (this->opt.mipmap && maj < 3 && !util_hasGLExt(exts, "GL_ARB_framebuffer_object") && !util_hasGLExt(exts, "GL_EXT_framebuffer_object")) { DEBUG_WARN("Need OpenGL 3.0+ or GL_ARB_framebuffer_object or " "GL_EXT_framebuffer_object for glGenerateMipmap, disabling mipmaps"); this->opt.mipmap = false; } glEnable(GL_TEXTURE_2D); glEnable(GL_COLOR_MATERIAL); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); glEnable(GL_MULTISAMPLE); // generate lists for drawing this->texList = glGenLists(BUFFER_COUNT); this->mouseList = glGenLists(1); this->spiceList = glGenLists(1); // create the overlay textures glGenTextures(TEXTURE_COUNT, this->textures); if (check_gl_error("glGenTextures")) { LG_UNLOCK(this->formatLock); return false; } this->hasTextures = true; app_glSetSwapInterval(this->opt.vsync ? 1 : 0); if (!ImGui_ImplOpenGL2_Init()) { DEBUG_ERROR("Failed to initialize ImGui"); return false; } this->renderStarted = true; return true; } bool opengl_render(LG_Renderer * renderer, LG_RendererRotate rotate, const bool newFrame, const bool invalidateWindow, void (*preSwap)(void * udata), void * udata) { struct Inst * this = UPCAST(struct Inst, renderer); setupModelView(this); switch(configure(this)) { case CONFIG_STATUS_ERROR: DEBUG_ERROR("configure failed"); return false; case CONFIG_STATUS_NOOP : case CONFIG_STATUS_OK : if (!drawFrame(this)) return false; } glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); updateMouseShape(this); if (this->spiceShow) glCallList(this->spiceList); else glCallList(this->texList + this->texRIndex); drawMouse(this); if (app_renderOverlay(NULL, 0) != 0) { ImGui_ImplOpenGL2_NewFrame(); ImGui_ImplOpenGL2_RenderDrawData(igGetDrawData()); } preSwap(udata); if (this->opt.preventBuffer) { app_glSwapBuffers(); glFinish(); } else app_glSwapBuffers(); this->mouseUpdate = false; return true; } static void * opengl_createTexture(LG_Renderer * renderer, int width, int height, uint8_t * data) { GLuint tex; glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glPixelStorei(GL_UNPACK_ROW_LENGTH, width); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); glBindTexture(GL_TEXTURE_2D, 0); return (void*)(intptr_t)tex; } static void opengl_freeTexture(LG_Renderer * renderer, void * texture) { GLuint tex = (GLuint)(intptr_t)texture; glDeleteTextures(1, &tex); } static void opengl_spiceConfigure(LG_Renderer * renderer, int width, int height) { struct Inst * this = UPCAST(struct Inst, renderer); this->spiceSize.x = width; this->spiceSize.y = height; glBindTexture(GL_TEXTURE_2D, this->textures[SPICE_TEXTURE]); glTexImage2D ( GL_TEXTURE_2D, 0 , GL_RGBA, width , height , 0 , GL_BGRA, GL_UNSIGNED_BYTE, 0 ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); // create the display lists glNewList(this->spiceList, GL_COMPILE); glBindTexture(GL_TEXTURE_2D, this->textures[SPICE_TEXTURE]); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); glTexCoord2f(1.0f, 0.0f); glVertex2i(this->spiceSize.x, 0); glTexCoord2f(0.0f, 1.0f); glVertex2i(0, this->spiceSize.y); glTexCoord2f(1.0f, 1.0f); glVertex2i(this->spiceSize.x, this->spiceSize.y); glEnd(); glBindTexture(GL_TEXTURE_2D, 0); glEndList(); } static void opengl_spiceDrawFill(LG_Renderer * renderer, int x, int y, int width, int height, uint32_t color) { struct Inst * this = UPCAST(struct Inst, renderer); /* this is a fairly hacky way to do this, but since it's only for the fallback * spice display it's not really an issue */ uint32_t line[width]; for(int x = 0; x < width; ++x) line[x] = color; glBindTexture(GL_TEXTURE_2D, this->textures[SPICE_TEXTURE]); glPixelStorei(GL_UNPACK_ALIGNMENT , 4 ); glPixelStorei(GL_UNPACK_ROW_LENGTH, width); for(; y < height; ++y) glTexSubImage2D ( GL_TEXTURE_2D, 0 , x , y , width , 1 , GL_BGRA, GL_UNSIGNED_BYTE, line ); glBindTexture(GL_TEXTURE_2D, 0); } static void opengl_spiceDrawBitmap(LG_Renderer * renderer, int x, int y, int width, int height, int stride, uint8_t * data, bool topDown) { struct Inst * this = UPCAST(struct Inst, renderer); if (!topDown) { // this is non-optimal but as spice is a fallback it's not too critical uint8_t line[stride]; for(int y = 0; y < height / 2; ++y) { uint8_t * top = data + y * stride; uint8_t * btm = data + (height - y - 1) * stride; memcpy(line, top , stride); memcpy(top , btm , stride); memcpy(btm , line, stride); } } glBindTexture(GL_TEXTURE_2D, this->textures[SPICE_TEXTURE]); glPixelStorei(GL_UNPACK_ALIGNMENT , 4 ); glPixelStorei(GL_UNPACK_ROW_LENGTH, stride / 4); glTexSubImage2D ( GL_TEXTURE_2D, 0 , x , y , width , height , GL_BGRA, GL_UNSIGNED_BYTE, data ); glBindTexture(GL_TEXTURE_2D, 0); } static void opengl_spiceShow(LG_Renderer * renderer, bool show) { struct Inst * this = UPCAST(struct Inst, renderer); this->spiceShow = show; } const LG_RendererOps LGR_OpenGL = { .getName = opengl_getName, .setup = opengl_setup, .create = opengl_create, .initialize = opengl_initialize, .deinitialize = opengl_deinitialize, .onRestart = opengl_onRestart, .onResize = opengl_onResize, .onMouseShape = opengl_onMouseShape, .onMouseEvent = opengl_onMouseEvent, .onFrameFormat = opengl_onFrameFormat, .onFrame = opengl_onFrame, .renderStartup = opengl_renderStartup, .render = opengl_render, .createTexture = opengl_createTexture, .freeTexture = opengl_freeTexture, .spiceConfigure = opengl_spiceConfigure, .spiceDrawFill = opengl_spiceDrawFill, .spiceDrawBitmap = opengl_spiceDrawBitmap, .spiceShow = opengl_spiceShow }; static bool _checkGLError(unsigned int line, const char * name) { GLenum error = glGetError(); if (error == GL_NO_ERROR) return false; const char * errStr; switch (error) { case GL_INVALID_ENUM: errStr = "GL_INVALID_ENUM"; break; case GL_INVALID_VALUE: errStr = "GL_INVALID_VALUE"; break; case GL_INVALID_OPERATION: errStr = "GL_INVALID_OPERATION"; break; case GL_STACK_OVERFLOW: errStr = "GL_STACK_OVERFLOW"; break; case GL_STACK_UNDERFLOW: errStr = "GL_STACK_UNDERFLOW"; break; case GL_OUT_OF_MEMORY: errStr = "GL_OUT_OF_MEMORY"; break; case GL_TABLE_TOO_LARGE: errStr = "GL_TABLE_TOO_LARGE"; break; default: errStr = "unknown error"; } DEBUG_ERROR("%d: %s = %d (%s)", line, name, error, errStr); return true; } static enum ConfigStatus configure(struct Inst * this) { LG_LOCK(this->formatLock); if (!this->reconfigure) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_NOOP; } deconfigure(this); switch(this->format.type) { case FRAME_TYPE_BGRA: this->intFormat = GL_RGBA8; this->vboFormat = GL_BGRA; this->dataFormat = GL_UNSIGNED_BYTE; break; case FRAME_TYPE_RGBA: this->intFormat = GL_RGBA8; this->vboFormat = GL_RGBA; this->dataFormat = GL_UNSIGNED_BYTE; break; case FRAME_TYPE_RGBA10: this->intFormat = GL_RGB10_A2; this->vboFormat = GL_RGBA; this->dataFormat = GL_UNSIGNED_INT_2_10_10_10_REV; break; case FRAME_TYPE_RGBA16F: this->intFormat = GL_RGB16F; this->vboFormat = GL_RGBA; this->dataFormat = GL_HALF_FLOAT; break; default: DEBUG_ERROR("Unknown/unsupported compression type"); return CONFIG_STATUS_ERROR; } // calculate the texture size in bytes this->texSize = this->format.frameHeight * this->format.pitch; this->texPos = 0; g_gl_dynProcs.glGenBuffers(BUFFER_COUNT, this->vboID); if (check_gl_error("glGenBuffers")) { LG_UNLOCK(this->formatLock); return false; } this->hasBuffers = true; if (this->amdPinnedMemSupport) { const int pagesize = getpagesize(); for(int i = 0; i < BUFFER_COUNT; ++i) { this->texPixels[i] = aligned_alloc(pagesize, this->texSize); if (!this->texPixels[i]) { DEBUG_ERROR("Failed to allocate memory for texture"); return CONFIG_STATUS_ERROR; } memset(this->texPixels[i], 0, this->texSize); g_gl_dynProcs.glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->vboID[i]); if (check_gl_error("glBindBuffer")) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_ERROR; } g_gl_dynProcs.glBufferData( GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->texSize, this->texPixels[i], GL_STREAM_DRAW ); if (check_gl_error("glBufferData")) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_ERROR; } } g_gl_dynProcs.glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0); } else { for(int i = 0; i < BUFFER_COUNT; ++i) { g_gl_dynProcs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[i]); if (check_gl_error("glBindBuffer")) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_ERROR; } g_gl_dynProcs.glBufferData( GL_PIXEL_UNPACK_BUFFER, this->texSize, NULL, GL_STREAM_DRAW ); if (check_gl_error("glBufferData")) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_ERROR; } } g_gl_dynProcs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } // create the frame textures glGenTextures(BUFFER_COUNT, this->frames); if (check_gl_error("glGenTextures")) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_ERROR; } this->hasFrames = true; for(int i = 0; i < BUFFER_COUNT; ++i) { // bind and create the new texture glBindTexture(GL_TEXTURE_2D, this->frames[i]); if (check_gl_error("glBindTexture")) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_ERROR; } glTexImage2D( GL_TEXTURE_2D, 0, this->intFormat, this->format.frameWidth, this->format.frameHeight, 0, this->vboFormat, this->dataFormat, (void*)0 ); if (check_gl_error("glTexImage2D")) { LG_UNLOCK(this->formatLock); return CONFIG_STATUS_ERROR; } // configure the texture glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // create the display lists glNewList(this->texList + i, GL_COMPILE); glBindTexture(GL_TEXTURE_2D, this->frames[i]); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); glTexCoord2f(1.0f, 0.0f); glVertex2i(this->format.frameWidth, 0); glTexCoord2f(0.0f, 1.0f); glVertex2i(0, this->format.frameHeight); glTexCoord2f(1.0f, 1.0f); glVertex2i(this->format.frameWidth, this->format.frameHeight); glEnd(); glBindTexture(GL_TEXTURE_2D, 0); glEndList(); } glBindTexture(GL_TEXTURE_2D, 0); g_gl_dynProcs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); this->drawStart = nanotime(); this->configured = true; this->reconfigure = false; LG_UNLOCK(this->formatLock); return CONFIG_STATUS_OK; } static void deconfigure(struct Inst * this) { if (this->hasFrames) { glDeleteTextures(BUFFER_COUNT, this->frames); this->hasFrames = false; } if (this->hasBuffers) { g_gl_dynProcs.glDeleteBuffers(BUFFER_COUNT, this->vboID); this->hasBuffers = false; } if (this->amdPinnedMemSupport) { for(int i = 0; i < BUFFER_COUNT; ++i) { if (this->fences[i]) { g_gl_dynProcs.glDeleteSync(this->fences[i]); this->fences[i] = NULL; } if (this->texPixels[i]) { free(this->texPixels[i]); this->texPixels[i] = NULL; } } } this->configured = false; } static void updateMouseShape(struct Inst * this) { LG_LOCK(this->mouseLock); if (!this->newShape) { LG_UNLOCK(this->mouseLock); return; } const LG_RendererCursor cursor = this->mouseCursor; const int width = this->mouseWidth; const int height = this->mouseHeight; const int pitch = this->mousePitch; const uint8_t * data = this->mouseData; // tmp buffer for masked colour uint32_t tmp[width * height]; this->mouseType = cursor; switch(cursor) { case LG_CURSOR_MASKED_COLOR: for(int i = 0; i < width * height; ++i) { const uint32_t c = ((uint32_t *)data)[i]; tmp[i] = (c & ~0xFF000000) | (c & 0xFF000000 ? 0x0 : 0xFF000000); } data = (uint8_t *)tmp; // fall through to LG_CURSOR_COLOR // // technically we should also create an XOR texture from the data but this // usage seems very rare in modern software. case LG_CURSOR_COLOR: { glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]); glPixelStorei(GL_UNPACK_ALIGNMENT , 4 ); glPixelStorei(GL_UNPACK_ROW_LENGTH, width); glTexImage2D ( GL_TEXTURE_2D, 0 , GL_RGBA, width , height , 0 , GL_BGRA, // windows cursors are in BGRA format GL_UNSIGNED_BYTE, data ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); this->mousePos.w = width; this->mousePos.h = height; glNewList(this->mouseList, GL_COMPILE); glEnable(GL_BLEND); glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 ); glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 ); glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , height); glTexCoord2f(1.0f, 1.0f); glVertex2i(width, height); glEnd(); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_BLEND); glEndList(); break; } case LG_CURSOR_MONOCHROME: { const int hheight = height / 2; uint32_t d[width * height]; for(int y = 0; y < hheight; ++y) for(int x = 0; x < width; ++x) { const uint8_t * srcAnd = data + (pitch * y) + (x / 8); const uint8_t * srcXor = srcAnd + pitch * hheight; const uint8_t mask = 0x80 >> (x % 8); const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000; const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000; d[y * width + x ] = andMask; d[y * width + x + width * hheight] = xorMask; } glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]); glPixelStorei(GL_UNPACK_ALIGNMENT , 4 ); glPixelStorei(GL_UNPACK_ROW_LENGTH, width); glTexImage2D ( GL_TEXTURE_2D, 0 , GL_RGBA, width , height , 0 , GL_RGBA, GL_UNSIGNED_BYTE, d ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); this->mousePos.w = width; this->mousePos.h = hheight; glNewList(this->mouseList, GL_COMPILE); glEnable(GL_COLOR_LOGIC_OP); glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]); glLogicOp(GL_AND); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 ); glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 ); glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , hheight); glTexCoord2f(1.0f, 0.5f); glVertex2i(width, hheight); glEnd(); glLogicOp(GL_XOR); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , 0 ); glTexCoord2f(1.0f, 0.5f); glVertex2i(width, 0 ); glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , hheight); glTexCoord2f(1.0f, 1.0f); glVertex2i(width, hheight); glEnd(); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_COLOR_LOGIC_OP); glEndList(); break; } } this->mouseUpdate = true; LG_UNLOCK(this->mouseLock); } static bool opengl_bufferFn(void * opaque, const void * data, size_t size) { struct Inst * this = (struct Inst *)opaque; // update the buffer, this performs a DMA transfer if possible g_gl_dynProcs.glBufferSubData( GL_PIXEL_UNPACK_BUFFER, this->texPos, size, data ); check_gl_error("glBufferSubData"); this->texPos += size; return true; } static bool drawFrame(struct Inst * this) { if (g_gl_dynProcs.glIsSync(this->fences[this->texWIndex])) { switch(g_gl_dynProcs.glClientWaitSync(this->fences[this->texWIndex], 0, GL_TIMEOUT_IGNORED)) { case GL_ALREADY_SIGNALED: break; case GL_CONDITION_SATISFIED: DEBUG_WARN("Had to wait for the sync"); break; case GL_TIMEOUT_EXPIRED: DEBUG_WARN("Timeout expired, DMA transfers are too slow!"); break; case GL_WAIT_FAILED: DEBUG_ERROR("Wait failed %d", glGetError()); break; } g_gl_dynProcs.glDeleteSync(this->fences[this->texWIndex]); this->fences[this->texWIndex] = NULL; this->texRIndex = this->texWIndex; if (++this->texWIndex == BUFFER_COUNT) this->texWIndex = 0; } LG_LOCK(this->frameLock); if (!atomic_exchange_explicit(&this->frameUpdate, false, memory_order_acquire)) { LG_UNLOCK(this->frameLock); return true; } LG_LOCK(this->formatLock); glBindTexture(GL_TEXTURE_2D, this->frames[this->texWIndex]); g_gl_dynProcs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[this->texWIndex]); const int bpp = this->format.bpp / 8; glPixelStorei(GL_UNPACK_ALIGNMENT , bpp); glPixelStorei(GL_UNPACK_ROW_LENGTH, this->format.frameWidth); this->texPos = 0; framebuffer_read_fn( this->frame, this->format.frameHeight, this->format.frameWidth, bpp, this->format.pitch, opengl_bufferFn, this ); LG_UNLOCK(this->frameLock); // update the texture glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, this->format.frameWidth , this->format.frameHeight, this->vboFormat, this->dataFormat, (void*)0 ); if (check_gl_error("glTexSubImage2D")) { DEBUG_ERROR("texWIndex: %u, width: %u, height: %u, vboFormat: %x, texSize: %lu", this->texWIndex, this->format.frameWidth, this->format.frameHeight, this->vboFormat, this->texSize ); } // unbind the buffer g_gl_dynProcs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); const bool mipmap = this->opt.mipmap && ( (this->format.frameWidth > this->destRect.w) || (this->format.frameHeight > this->destRect.h)); if (mipmap) { g_gl_dynProcs.glGenerateMipmap(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } glBindTexture(GL_TEXTURE_2D, 0); // set a fence so we don't overwrite a buffer in use this->fences[this->texWIndex] = g_gl_dynProcs.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); LG_UNLOCK(this->formatLock); this->texReady = true; return true; } static void drawMouse(struct Inst * this) { if (!this->mouseVisible) return; glPushMatrix(); glTranslatef(this->mousePos.x, this->mousePos.y, 0.0f); glCallList(this->mouseList); glPopMatrix(); } looking-glass-B6/client/src/000077500000000000000000000000001434445012300161415ustar00rootroot00000000000000looking-glass-B6/client/src/app.c000066400000000000000000000603271434445012300170750ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "app.h" #include "main.h" #include "core.h" #include "util.h" #include "clipboard.h" #include "render_queue.h" #include "kb.h" #include "common/debug.h" #include "common/stringutils.h" #include "interface/overlay.h" #include "overlays.h" #include "cimgui.h" #include #include #include #include bool app_isRunning(void) { return g_state.state == APP_STATE_RUNNING || g_state.state == APP_STATE_RESTART; } bool app_isCaptureMode(void) { return g_cursor.grab; } bool app_isCaptureOnlyMode(void) { return g_params.captureInputOnly; } bool app_isFormatValid(void) { return g_state.formatValid; } bool app_isOverlayMode(void) { if (g_state.overlayInput) return true; bool result = false; struct Overlay * overlay; ll_lock(g_state.overlays); ll_forEachNL(g_state.overlays, item, overlay) { if (overlay->ops->needs_overlay && overlay->ops->needs_overlay(overlay)) { result = true; break; } } ll_unlock(g_state.overlays); return result; } void app_updateCursorPos(double x, double y) { g_cursor.pos.x = x; g_cursor.pos.y = y; g_cursor.valid = true; if (app_isOverlayMode()) g_state.io->MousePos = (ImVec2) { x, y }; } void app_handleFocusEvent(bool focused) { g_state.focused = focused; // release any imgui buttons/keys if we lost focus if (!focused && app_isOverlayMode()) core_resetOverlayInputState(); if (!core_inputEnabled()) { if (!focused && g_params.minimizeOnFocusLoss && app_getFullscreen()) g_state.ds->minimize(); return; } if (!focused) { core_setGrabQuiet(false); core_setCursorInView(false); if (g_params.releaseKeysOnFocusLoss) for (int key = 0; key < KEY_MAX; key++) if (g_state.keyDown[key]) app_handleKeyRelease(key, 0); g_state.escapeActive = false; if (!g_params.showCursorDot) g_state.ds->setPointer(LG_POINTER_NONE); if (g_params.minimizeOnFocusLoss) g_state.ds->minimize(); } g_cursor.realign = true; g_state.ds->realignPointer(); } void app_handleEnterEvent(bool entered) { if (entered) { g_cursor.inWindow = true; if (!core_inputEnabled()) return; g_cursor.realign = true; } else { g_cursor.inWindow = false; core_setCursorInView(false); // stop the user being able to drag windows off the screen and work around // the mouse button release being missed due to not being in capture mode. if (app_isOverlayMode()) { g_state.io->MouseDown[ImGuiMouseButton_Left ] = false; g_state.io->MouseDown[ImGuiMouseButton_Right ] = false; g_state.io->MouseDown[ImGuiMouseButton_Middle] = false; } if (!core_inputEnabled()) return; if (!g_params.alwaysShowCursor) g_cursor.draw = false; g_cursor.redraw = true; } } void app_clipboardRelease(void) { if (!g_params.clipboardToVM) return; purespice_clipboardRelease(); } void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count) { if (!g_params.clipboardToVM) return; if (count == 0) { purespice_clipboardRelease(); return; } PSDataType conv[count]; for(int i = 0; i < count; ++i) conv[i] = cb_lgTypeToSpiceType(types[i]); purespice_clipboardGrab(conv, count); } void app_clipboardNotifySize(const LG_ClipboardData type, size_t size) { if (!g_params.clipboardToVM) return; if (type == LG_CLIPBOARD_DATA_NONE) { purespice_clipboardRelease(); return; } g_state.cbType = cb_lgTypeToSpiceType(type); g_state.cbChunked = size > 0; g_state.cbXfer = size; purespice_clipboardDataStart(g_state.cbType, size); } void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size) { if (!g_params.clipboardToVM) return; if (g_state.cbChunked && size > g_state.cbXfer) { DEBUG_ERROR("refusing to send more then cbXfer bytes for chunked xfer"); size = g_state.cbXfer; } if (!g_state.cbChunked) purespice_clipboardDataStart(g_state.cbType, size); purespice_clipboardData(g_state.cbType, data, (uint32_t)size); g_state.cbXfer -= size; } void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque) { if (!g_params.clipboardToLocal) return; struct CBRequest * cbr = malloc(sizeof(*cbr)); if (!cbr) { DEBUG_ERROR("out of memory"); return; } cbr->type = g_state.cbType; cbr->replyFn = replyFn; cbr->opaque = opaque; ll_push(g_state.cbRequestList, cbr); purespice_clipboardRequest(g_state.cbType); } static int mapSpiceToImGuiButton(uint32_t button) { switch (button) { case 1: // SPICE_MOUSE_BUTTON_LEFT return ImGuiMouseButton_Left; case 2: // SPICE_MOUSE_BUTTON_MIDDLE return ImGuiMouseButton_Middle; case 3: // SPICE_MOUSE_BUTTON_RIGHT return ImGuiMouseButton_Right; } return -1; } void app_handleButtonPress(int button) { g_cursor.buttons |= (1U << button); if (app_isOverlayMode()) { int igButton = mapSpiceToImGuiButton(button); if (igButton != -1) g_state.io->MouseDown[igButton] = true; return; } if (!core_inputEnabled() || !g_cursor.inView) return; if (!purespice_mousePress(button)) DEBUG_ERROR("app_handleButtonPress: failed to send message"); } void app_handleButtonRelease(int button) { g_cursor.buttons &= ~(1U << button); if (app_isOverlayMode()) { int igButton = mapSpiceToImGuiButton(button); if (igButton != -1) g_state.io->MouseDown[igButton] = false; return; } if (!core_inputEnabled()) return; if (!purespice_mouseRelease(button)) DEBUG_ERROR("app_handleButtonRelease: failed to send message"); } void app_handleWheelMotion(double motion) { if (app_isOverlayMode()) g_state.io->MouseWheel -= motion; } void app_handleKeyPress(int sc, int charcode) { if (!app_isOverlayMode() || !g_state.io->WantCaptureKeyboard) { if (sc == g_params.escapeKey && !g_state.escapeActive) { g_state.escapeActive = true; g_state.escapeTime = microtime(); g_state.escapeAction = -1; return; } if (g_state.escapeActive) { g_state.escapeAction = sc; KeybindHandle handle; ll_forEachNL(g_state.bindings, item, handle) { if ((handle->sc && handle->sc == sc ) || (handle->charcode && handle->charcode == charcode)) { handle->callback(sc, handle->opaque); break; } } return; } } if (app_isOverlayMode()) { if (sc == KEY_ESC) app_setOverlay(false); else g_state.io->KeysDown[sc] = true; return; } if (!core_inputEnabled()) return; if (g_params.ignoreWindowsKeys && (sc == KEY_LEFTMETA || sc == KEY_RIGHTMETA)) return; if (!g_state.keyDown[sc]) { uint32_t ps2 = linux_to_ps2[sc]; if (!ps2) return; if (purespice_keyDown(ps2)) g_state.keyDown[sc] = true; else { DEBUG_ERROR("app_handleKeyPress: failed to send message"); return; } } } void app_handleKeyRelease(int sc, int charcode) { if (g_state.escapeActive) { if (g_state.escapeAction == -1) { if (!g_state.escapeHelp && g_params.useSpiceInput && !app_isOverlayMode()) core_setGrab(!g_cursor.grab); } if (sc == g_params.escapeKey) g_state.escapeActive = false; } if (app_isOverlayMode()) { g_state.io->KeysDown[sc] = false; return; } if (!core_inputEnabled()) return; // avoid sending key up events when we didn't send a down if (!g_state.keyDown[sc]) return; if (g_params.ignoreWindowsKeys && (sc == KEY_LEFTMETA || sc == KEY_RIGHTMETA)) return; uint32_t ps2 = linux_to_ps2[sc]; if (!ps2) return; if (purespice_keyUp(ps2)) g_state.keyDown[sc] = false; else { DEBUG_ERROR("app_handleKeyRelease: failed to send message"); return; } } void app_handleKeyboardTyped(const char * typed) { ImGuiIO_AddInputCharactersUTF8(g_state.io, typed); } void app_handleKeyboardModifiers(bool ctrl, bool shift, bool alt, bool super) { g_state.modCtrl = ctrl; g_state.modShift = shift; g_state.modAlt = alt; g_state.modSuper = super; } void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock) { if (!core_inputEnabled()) return; uint32_t modifiers = (scrollLock ? 1 /* SPICE_SCROLL_LOCK_MODIFIER */ : 0) | (numLock ? 2 /* SPICE_NUM_LOCK_MODIFIER */ : 0) | (capsLock ? 4 /* SPICE_CAPS_LOCK_MODIFIER */ : 0); if (!purespice_keyModifiers(modifiers)) DEBUG_ERROR("app_handleKeyboardLEDs: failed to send message"); } void app_handleMouseRelative(double normx, double normy, double rawx, double rawy) { if (app_isOverlayMode()) return; if (g_cursor.grab) { if (g_params.rawMouse) core_handleMouseGrabbed(rawx, rawy); else core_handleMouseGrabbed(normx, normy); } else if (g_cursor.inWindow) core_handleMouseNormal(normx, normy); } // On some display servers normal cursor logic does not work due to the lack of // cursor warp support. Instead, we attempt a best-effort emulation which works // with a 1:1 mouse movement patch applied in the guest. For anything fancy, use // capture mode. void app_handleMouseBasic(void) { /* do not pass mouse events to the guest if we do not have focus */ if (!g_cursor.guest.valid || !g_state.haveSrcSize || !g_state.focused || app_isOverlayMode()) return; if (!core_inputEnabled()) return; const bool inView = g_cursor.pos.x >= g_state.dstRect.x && g_cursor.pos.x < g_state.dstRect.x + g_state.dstRect.w && g_cursor.pos.y >= g_state.dstRect.y && g_cursor.pos.y < g_state.dstRect.y + g_state.dstRect.h; core_setCursorInView(inView); /* translate the current position to guest coordinate space */ struct DoublePoint guest; util_localCurToGuest(&guest); int x = (int) round(util_clamp(guest.x, 0, g_state.srcSize.x) - g_cursor.projected.x); int y = (int) round(util_clamp(guest.y, 0, g_state.srcSize.y) - g_cursor.projected.y); if (!x && !y) return; g_cursor.projected.x += x; g_cursor.projected.y += y; if (!purespice_mouseMotion(x, y)) DEBUG_ERROR("failed to send mouse motion message"); } void app_resyncMouseBasic(void) { if (!g_cursor.guest.valid) return; g_cursor.projected.x = g_cursor.guest.x + g_cursor.guest.hx; g_cursor.projected.y = g_cursor.guest.y + g_cursor.guest.hy; } void app_updateWindowPos(int x, int y) { g_state.windowPos.x = x; g_state.windowPos.y = y; } void app_handleResizeEvent(int w, int h, double scale, const struct Border border) { memcpy(&g_state.border, &border, sizeof(border)); /* don't do anything else if the window dimensions have not changed */ if (g_state.windowW == w && g_state.windowH == h && g_state.windowScale == scale) return; g_state.windowW = w; g_state.windowH = h; g_state.windowCX = w / 2; g_state.windowCY = h / 2; g_state.windowScale = scale; core_updatePositionInfo(); if (core_inputEnabled()) { /* if the window is moved/resized causing a loss of focus while grabbed, it * makes it impossible to re-focus the window, so we quietly re-enter * capture if we were already in it */ if (g_cursor.grab) { core_setGrabQuiet(false); core_setGrabQuiet(true); } core_alignToGuest(); } } void app_invalidateWindow(bool full) { if (full) atomic_store(&g_state.invalidateWindow, true); if (g_state.dsInitialized && g_state.jitRender && g_state.ds->stopWaitFrame) g_state.ds->stopWaitFrame(); lgSignalEvent(g_state.frameEvent); } void app_handleCloseEvent(void) { if (!g_params.ignoreQuit || !g_cursor.inView) g_state.state = APP_STATE_SHUTDOWN; } void app_handleRenderEvent(const uint64_t timeUs) { bool invalidate = false; if (!g_state.escapeActive) { if (g_state.escapeHelp) { g_state.escapeHelp = false; invalidate = true; } } else { if (!g_state.escapeHelp && timeUs - g_state.escapeTime > g_params.helpMenuDelayUs) { g_state.escapeHelp = true; invalidate = true; } } if (invalidate) app_invalidateWindow(false); } void app_setFullscreen(bool fs) { g_state.ds->setFullscreen(fs); } bool app_getFullscreen(void) { return g_state.ds->getFullscreen(); } bool app_getProp(LG_DSProperty prop, void * ret) { return g_state.ds->getProp(prop, ret); } #ifdef ENABLE_EGL EGLDisplay app_getEGLDisplay(void) { return g_state.ds->getEGLDisplay(); } EGLNativeWindowType app_getEGLNativeWindow(void) { return g_state.ds->getEGLNativeWindow(); } void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count) { g_state.ds->eglSwapBuffers(display, surface, damage, count); } #endif #ifdef ENABLE_OPENGL LG_DSGLContext app_glCreateContext(void) { return g_state.ds->glCreateContext(); } void app_glDeleteContext(LG_DSGLContext context) { g_state.ds->glDeleteContext(context); } void app_glMakeCurrent(LG_DSGLContext context) { g_state.ds->glMakeCurrent(context); } void app_glSetSwapInterval(int interval) { g_state.ds->glSetSwapInterval(interval); } void app_glSwapBuffers(void) { g_state.ds->glSwapBuffers(); } #endif void app_alert(LG_MsgAlert type, const char * fmt, ...) { if (!g_state.lgr || !g_params.showAlerts) return; va_list args; va_start(args, fmt); overlayAlert_show(type, fmt, args); va_end(args); } MsgBoxHandle app_msgBox(const char * caption, const char * fmt, ...) { va_list args; va_start(args, fmt); MsgBoxHandle handle = overlayMsg_show(caption, NULL, NULL, fmt, args); va_end(args); core_updateOverlayState(); return handle; } MsgBoxHandle app_confirmMsgBox(const char * caption, MsgBoxConfirmCallback callback, void * opaque, const char * fmt, ...) { va_list args; va_start(args, fmt); MsgBoxHandle handle = overlayMsg_show(caption, callback, opaque, fmt, args); va_end(args); core_updateOverlayState(); return handle; } void app_msgBoxClose(MsgBoxHandle handle) { if (!handle) return; overlayMsg_close(handle); } void app_showRecord(bool show) { overlayStatus_set(LG_USER_STATUS_RECORDING, show); } KeybindHandle app_registerKeybind(int sc, int charcode, KeybindFn callback, void * opaque, const char * description) { if (charcode != 0 && sc != 0) { DEBUG_ERROR("invalid keybind, one of scancode or charcode must be 0"); return NULL; } if (charcode && islower(charcode)) { DEBUG_ERROR("invalid keybind, charcode must be uppercase"); return NULL; } KeybindHandle handle; // don't allow duplicate binds ll_forEachNL(g_state.bindings, item, handle) { if ((sc && handle->sc == sc ) || (charcode && handle->charcode == charcode)) { DEBUG_INFO("Key already bound"); return NULL; } } handle = malloc(sizeof(*handle)); if (!handle) { DEBUG_ERROR("out of memory"); return NULL; } handle->sc = sc; handle->charcode = charcode; handle->callback = callback; handle->description = description; handle->opaque = opaque; ll_push(g_state.bindings, handle); return handle; } void app_releaseKeybind(KeybindHandle * handle) { if (!*handle) return; ll_removeData(g_state.bindings, *handle); free(*handle); *handle = NULL; } void app_releaseAllKeybinds(void) { KeybindHandle * handle; while(ll_shift(g_state.bindings, (void **)&handle)) free(handle); } GraphHandle app_registerGraph(const char * name, RingBuffer buffer, float min, float max, GraphFormatFn formatFn) { return overlayGraph_register(name, buffer, min, max, formatFn); } void app_unregisterGraph(GraphHandle handle) { overlayGraph_unregister(handle); } void app_invalidateGraph(GraphHandle handle) { overlayGraph_invalidate(handle); } void app_registerOverlay(const struct LG_OverlayOps * ops, const void * params) { ASSERT_LG_OVERLAY_VALID(ops); struct Overlay * overlay = malloc(sizeof(*overlay)); if (!overlay) { DEBUG_ERROR("out of ram"); return; } overlay->ops = ops; overlay->params = params; overlay->udata = NULL; overlay->lastRectCount = 0; ll_push(g_state.overlays, overlay); if (ops->earlyInit) ops->earlyInit(); } void app_initOverlays(void) { struct Overlay * overlay; ll_lock(g_state.overlays); ll_forEachNL(g_state.overlays, item, overlay) { DEBUG_ASSERT(overlay->ops); if (!overlay->ops->init(&overlay->udata, overlay->params)) { DEBUG_ERROR("Overlay `%s` failed to initialize", overlay->ops->name); overlay->ops = NULL; } } ll_unlock(g_state.overlays); } static inline void mergeRect(struct Rect * dest, const struct Rect * a, const struct Rect * b) { int x2 = max(a->x + a->w, b->x + b->w); int y2 = max(a->y + a->h, b->y + b->h); dest->x = min(a->x, b->x); dest->y = min(a->y, b->y); dest->w = x2 - dest->x; dest->h = y2 - dest->y; } static inline LG_DSPointer mapImGuiCursor(ImGuiMouseCursor cursor) { switch (cursor) { case ImGuiMouseCursor_None: return LG_POINTER_NONE; case ImGuiMouseCursor_Arrow: return LG_POINTER_ARROW; case ImGuiMouseCursor_TextInput: return LG_POINTER_INPUT; case ImGuiMouseCursor_ResizeAll: return LG_POINTER_MOVE; case ImGuiMouseCursor_ResizeNS: return LG_POINTER_RESIZE_NS; case ImGuiMouseCursor_ResizeEW: return LG_POINTER_RESIZE_EW; case ImGuiMouseCursor_ResizeNESW: return LG_POINTER_RESIZE_NESW; case ImGuiMouseCursor_ResizeNWSE: return LG_POINTER_RESIZE_NWSE; case ImGuiMouseCursor_Hand: return LG_POINTER_HAND; case ImGuiMouseCursor_NotAllowed: return LG_POINTER_NOT_ALLOWED; default: return LG_POINTER_ARROW; } } bool app_overlayNeedsRender(void) { if (app_isOverlayMode()) return true; bool result = false; struct Overlay * overlay; ll_lock(g_state.overlays); ll_forEachNL(g_state.overlays, item, overlay) { if (!overlay->ops->needs_render) continue; if (overlay->ops->needs_render(overlay->udata, false)) { result = true; break; } } ll_unlock(g_state.overlays); return result; } int app_renderOverlay(struct Rect * rects, int maxRects) { int totalRects = 0; bool totalDamage = false; struct Overlay * overlay; struct Rect buffer[MAX_OVERLAY_RECTS]; g_state.io->KeyCtrl = g_state.modCtrl; g_state.io->KeyShift = g_state.modShift; g_state.io->KeyAlt = g_state.modAlt; g_state.io->KeySuper = g_state.modSuper; uint64_t now = nanotime(); g_state.io->DeltaTime = (now - g_state.lastImGuiFrame) * 1e-9f; g_state.lastImGuiFrame = now; render_again: igNewFrame(); const bool overlayMode = app_isOverlayMode(); if (overlayMode && g_params.overlayDim) { totalDamage = true; ImDrawList_AddRectFilled(igGetBackgroundDrawList_Nil(), (ImVec2) { 0.0f , 0.0f }, g_state.io->DisplaySize, igGetColorU32_Col(ImGuiCol_ModalWindowDimBg, 1.0f), 0, 0); } const bool msgModal = overlayMsg_modal(); // render the overlays ll_lock(g_state.overlays); ll_forEachNL(g_state.overlays, item, overlay) { if (msgModal && overlay->ops != &LGOverlayMsg) continue; const int written = overlay->ops->render(overlay->udata, overlayMode, buffer, MAX_OVERLAY_RECTS); for (int i = 0; i < written; ++i) { buffer[i].x *= g_state.windowScale; buffer[i].y *= g_state.windowScale; buffer[i].w *= g_state.windowScale; buffer[i].h *= g_state.windowScale; } // It is an error to run out of rectangles, because we will not be able to // correctly calculate the damage of the next frame. DEBUG_ASSERT(written >= 0); const int toAdd = max(written, overlay->lastRectCount); totalDamage |= toAdd > maxRects; if (!totalDamage && toAdd) { int i = 0; for (; i < overlay->lastRectCount && i < written; ++i) mergeRect(rects + i, buffer + i, overlay->lastRects + i); // only one of the following memcpys will copy non-zero bytes. memcpy(rects + i, buffer + i, (written - i) * sizeof(struct Rect)); memcpy(rects + i, overlay->lastRects + i, (overlay->lastRectCount - i) * sizeof(struct Rect)); rects += toAdd; totalRects += toAdd; maxRects -= toAdd; } memcpy(overlay->lastRects, buffer, sizeof(struct Rect) * written); overlay->lastRectCount = written; } ll_unlock(g_state.overlays); if (overlayMode) { ImGuiMouseCursor cursor = igGetMouseCursor(); if (cursor != g_state.cursorLast) { g_state.ds->setPointer(mapImGuiCursor(cursor)); g_state.cursorLast = cursor; } } igRender(); /* imgui requires two passes to calculate the bounding box of auto sized * windows, this is by design * ref: https://github.com/ocornut/imgui/issues/2158#issuecomment-434223618 */ if (g_state.renderImGuiTwice) { g_state.renderImGuiTwice = false; goto render_again; } return totalDamage ? -1 : totalRects; } void app_freeOverlays(void) { struct Overlay * overlay; while(ll_shift(g_state.overlays, (void **)&overlay)) { overlay->ops->free(overlay->udata); free(overlay); } } void app_setOverlay(bool enable) { if (g_state.overlayInput == enable) return; g_state.overlayInput = enable; core_updateOverlayState(); } void app_overlayConfigRegister(const char * title, void (*callback)(void * udata, int * id), void * udata) { overlayConfig_register(title, callback, udata); } void app_overlayConfigRegisterTab(const char * title, void (*callback)(void * udata, int * id), void * udata) { overlayConfig_registerTab(title, callback, udata); } void app_invalidateOverlay(bool renderTwice) { if (g_state.state == APP_STATE_SHUTDOWN) return; if (renderTwice) g_state.renderImGuiTwice = true; app_invalidateWindow(false); } bool app_guestIsLinux(void) { return g_state.guestOS == KVMFR_OS_LINUX; } bool app_guestIsWindows(void) { return g_state.guestOS == KVMFR_OS_WINDOWS; } bool app_guestIsOSX(void) { return g_state.guestOS == KVMFR_OS_OSX; } bool app_guestIsBSD(void) { return g_state.guestOS == KVMFR_OS_BSD; } bool app_guestIsOther(void) { return g_state.guestOS == KVMFR_OS_OTHER; } void app_stopVideo(bool stop) { if (g_state.stopVideo == stop) return; // do not change the state if the host app is not connected if (!g_state.lgHostConnected) return; g_state.stopVideo = stop; app_alert( LG_ALERT_INFO, stop ? "Video Stream Disabled" : "Video Stream Enabled" ); if (stop) { core_stopCursorThread(); core_stopFrameThread(); } else { core_startCursorThread(); core_startFrameThread(); } } bool app_useSpiceDisplay(bool enable) { static bool lastState = false; if (!g_params.useSpice || lastState == enable) return g_params.useSpice && lastState; // if spice is not yet ready, flag the state we want for when it is if (!g_state.spiceReady) { g_state.initialSpiceDisplay = enable; return false; } if (!purespice_hasChannel(PS_CHANNEL_DISPLAY)) return false; // do not allow stopping of the host app if not connected if (!enable && !g_state.lgHostConnected) return false; lastState = enable; if (enable) { purespice_connectChannel(PS_CHANNEL_DISPLAY); purespice_connectChannel(PS_CHANNEL_CURSOR); renderQueue_spiceShow(true); } else { renderQueue_spiceShow(false); purespice_disconnectChannel(PS_CHANNEL_DISPLAY); purespice_disconnectChannel(PS_CHANNEL_CURSOR); } overlayStatus_set(LG_USER_STATUS_SPICE, enable); return enable; } looking-glass-B6/client/src/audio.c000066400000000000000000000670071434445012300174200ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #if ENABLE_AUDIO #include "audio.h" #include "main.h" #include "common/array.h" #include "common/util.h" #include "common/ringbuffer.h" #include "dynamic/audiodev.h" #include #include #include #include #include typedef enum { STREAM_STATE_STOP, STREAM_STATE_SETUP_SPICE, STREAM_STATE_SETUP_DEVICE, STREAM_STATE_RUN, STREAM_STATE_KEEP_ALIVE } StreamState; #define STREAM_ACTIVE(state) \ (state == STREAM_STATE_RUN || state == STREAM_STATE_KEEP_ALIVE) typedef struct { int periodFrames; double periodSec; int64_t nextTime; int64_t nextPosition; double b; double c; } PlaybackDeviceData; typedef struct { float * framesIn; float * framesOut; int framesOutSize; int periodFrames; double periodSec; int64_t nextTime; int64_t nextPosition; double b; double c; int devPeriodFrames; int64_t devLastTime; int64_t devNextTime; int64_t devLastPosition; int64_t devNextPosition; double offsetError; double offsetErrorIntegral; double ratioIntegral; SRC_STATE * src; } PlaybackSpiceData; typedef struct { struct LG_AudioDevOps * audioDev; struct { StreamState state; int volumeChannels; uint16_t volume[8]; bool mute; int channels; int sampleRate; int stride; int deviceMaxPeriodFrames; int deviceStartFrames; int targetStartFrames; RingBuffer buffer; RingBuffer deviceTiming; RingBuffer timings; GraphHandle graph; /* These two structs contain data specifically for use in the device and * Spice data threads respectively. Keep them on separate cache lines to * avoid false sharing. */ alignas(64) PlaybackDeviceData deviceData; alignas(64) PlaybackSpiceData spiceData; } playback; struct { bool requested; bool started; int volumeChannels; uint16_t volume[8]; bool mute; int stride; uint32_t time; int lastChannels; int lastSampleRate; PSAudioFormat lastFormat; MsgBoxHandle confirmHandle; int confirmChannels; int confirmSampleRate; PSAudioFormat confirmFormat; } record; } AudioState; static AudioState audio = { 0 }; typedef struct { int periodFrames; int64_t nextTime; int64_t nextPosition; } PlaybackDeviceTick; static void playbackStop(void); void audio_init(void) { // search for the best audiodev to use for(int i = 0; i < LG_AUDIODEV_COUNT; ++i) if (LG_AudioDevs[i]->init()) { audio.audioDev = LG_AudioDevs[i]; DEBUG_INFO("Using AudioDev: %s", audio.audioDev->name); return; } DEBUG_WARN("Failed to initialize an audio backend"); } void audio_free(void) { if (!audio.audioDev) return; // immediate stop of the stream, do not wait for drain playbackStop(); audio_recordStop(); audio.audioDev->free(); audio.audioDev = NULL; } bool audio_supportsPlayback(void) { return audio.audioDev && audio.audioDev->playback.start; } static const char * audioGraphFormatFn(const char * name, float min, float max, float avg, float freq, float last) { static char title[64]; snprintf(title, sizeof(title), "%s: min:%4.2f max:%4.2f avg:%4.2f now:%4.2f", name, min, max, avg, last); return title; } static void playbackStop(void) { if (audio.playback.state == STREAM_STATE_STOP) return; audio.playback.state = STREAM_STATE_STOP; audio.audioDev->playback.stop(); ringbuffer_free(&audio.playback.buffer); ringbuffer_free(&audio.playback.deviceTiming); audio.playback.spiceData.src = src_delete(audio.playback.spiceData.src); if (audio.playback.spiceData.framesIn) { free(audio.playback.spiceData.framesIn); free(audio.playback.spiceData.framesOut); audio.playback.spiceData.framesIn = NULL; audio.playback.spiceData.framesOut = NULL; } if (audio.playback.timings) { app_unregisterGraph(audio.playback.graph); ringbuffer_free(&audio.playback.timings); } } static int playbackPullFrames(uint8_t * dst, int frames) { DEBUG_ASSERT(frames >= 0); if (frames == 0) return frames; PlaybackDeviceData * data = &audio.playback.deviceData; int64_t now = nanotime(); if (audio.playback.buffer) { if (audio.playback.state == STREAM_STATE_SETUP_DEVICE) { /* If necessary, slew backwards to play silence until we reach the target * startup latency. This avoids underrunning the buffer if the audio * device starts earlier than required. */ int offset = ringbuffer_getCount(audio.playback.buffer) - audio.playback.targetStartFrames; if (offset < 0) { data->nextPosition += offset; ringbuffer_consume(audio.playback.buffer, NULL, offset); } audio.playback.state = STREAM_STATE_RUN; } // Measure the device clock and post to the Spice thread if (frames != data->periodFrames) { double newPeriodSec = (double) frames / audio.playback.sampleRate; bool init = data->periodFrames == 0; if (init) data->nextTime = now + llrint(newPeriodSec * 1.0e9); else /* Due to the double-buffered nature of audio playback, we are filling * in the next buffer while the device is playing the previous buffer. * This results in slightly unintuitive behaviour when the period size * changes. The device will request enough samples for the new period * size, but won't call us again until the previous buffer at the old * size has finished playing. So, to avoid a blip in the timing * calculations, we must set the estimated next wakeup time based upon * the previous period size, not the new one. */ data->nextTime += llrint(data->periodSec * 1.0e9); data->periodFrames = frames; data->periodSec = newPeriodSec; data->nextPosition += frames; double bandwidth = 0.05; double omega = 2.0 * M_PI * bandwidth * data->periodSec; data->b = M_SQRT2 * omega; data->c = omega * omega; } else { double error = (now - data->nextTime) * 1.0e-9; if (fabs(error) >= 0.2) { // Clock error is too high; slew the read pointer and reset the timing // parameters to avoid getting too far out of sync int slewFrames = round(error * audio.playback.sampleRate); ringbuffer_consume(audio.playback.buffer, NULL, slewFrames); data->periodSec = (double) frames / audio.playback.sampleRate; data->nextTime = now + llrint(data->periodSec * 1.0e9); data->nextPosition += slewFrames + frames; } else { data->nextTime += llrint((data->b * error + data->periodSec) * 1.0e9); data->periodSec += data->c * error; data->nextPosition += frames; } } PlaybackDeviceTick tick = { .periodFrames = data->periodFrames, .nextTime = data->nextTime, .nextPosition = data->nextPosition }; ringbuffer_push(audio.playback.deviceTiming, &tick); ringbuffer_consume(audio.playback.buffer, dst, frames); } else frames = 0; // Close the stream if nothing has played for a while if (audio.playback.state == STREAM_STATE_KEEP_ALIVE) { int stopTimeSec = 30; int stopTimeFrames = stopTimeSec * audio.playback.sampleRate; if (ringbuffer_getCount(audio.playback.buffer) <= -stopTimeFrames) playbackStop(); } return frames; } void audio_playbackStart(int channels, int sampleRate, PSAudioFormat format, uint32_t time) { if (!audio.audioDev) return; static int lastChannels = 0; static int lastSampleRate = 0; if (audio.playback.state == STREAM_STATE_KEEP_ALIVE && channels == lastChannels && sampleRate == lastSampleRate) return; if (audio.playback.state != STREAM_STATE_STOP) playbackStop(); int srcError; audio.playback.spiceData.src = src_new(SRC_SINC_FASTEST, channels, &srcError); if (!audio.playback.spiceData.src) { DEBUG_ERROR("Failed to create resampler: %s", src_strerror(srcError)); return; } const int bufferFrames = sampleRate; audio.playback.buffer = ringbuffer_newUnbounded(bufferFrames, channels * sizeof(float)); audio.playback.deviceTiming = ringbuffer_new(16, sizeof(PlaybackDeviceTick)); lastChannels = channels; lastSampleRate = sampleRate; audio.playback.channels = channels; audio.playback.sampleRate = sampleRate; audio.playback.stride = channels * sizeof(float); audio.playback.state = STREAM_STATE_SETUP_SPICE; audio.playback.deviceData.periodFrames = 0; audio.playback.deviceData.nextPosition = 0; audio.playback.spiceData.periodFrames = 0; audio.playback.spiceData.nextPosition = 0; audio.playback.spiceData.devPeriodFrames = 0; audio.playback.spiceData.devLastTime = INT64_MIN; audio.playback.spiceData.devNextTime = INT64_MIN; audio.playback.spiceData.offsetError = 0.0; audio.playback.spiceData.offsetErrorIntegral = 0.0; audio.playback.spiceData.ratioIntegral = 0.0; int requestedPeriodFrames = max(g_params.audioPeriodSize, 1); audio.playback.deviceMaxPeriodFrames = 0; audio.playback.deviceStartFrames = 0; audio.audioDev->playback.setup(channels, sampleRate, requestedPeriodFrames, &audio.playback.deviceMaxPeriodFrames, &audio.playback.deviceStartFrames, playbackPullFrames); DEBUG_ASSERT(audio.playback.deviceMaxPeriodFrames > 0); // if a volume level was stored, set it before we return if (audio.playback.volumeChannels) audio.audioDev->playback.volume( audio.playback.volumeChannels, audio.playback.volume); // set the inital mute state if (audio.audioDev->playback.mute) audio.audioDev->playback.mute(audio.playback.mute); // if the audio dev can report it's latency setup a timing graph audio.playback.timings = ringbuffer_new(1200, sizeof(float)); audio.playback.graph = app_registerGraph("PLAYBACK", audio.playback.timings, 0.0f, 200.0f, audioGraphFormatFn); } void audio_playbackStop(void) { if (!audio.audioDev) return; switch (audio.playback.state) { case STREAM_STATE_RUN: { // Keep the audio device open for a while to reduce startup latency if // playback starts again audio.playback.state = STREAM_STATE_KEEP_ALIVE; // Reset the resampler so it is safe to use for the next playback int error = src_reset(audio.playback.spiceData.src); if (error) { DEBUG_ERROR("Failed to reset resampler: %s", src_strerror(error)); playbackStop(); } break; } case STREAM_STATE_SETUP_SPICE: case STREAM_STATE_SETUP_DEVICE: // Playback hasn't actually started yet so just clean up playbackStop(); break; case STREAM_STATE_KEEP_ALIVE: case STREAM_STATE_STOP: // Nothing to do break; } } void audio_playbackVolume(int channels, const uint16_t volume[]) { if (!audio.audioDev || !audio.audioDev->playback.volume) return; // store the values so we can restore the state if the stream is restarted channels = min(ARRAY_LENGTH(audio.playback.volume), channels); memcpy(audio.playback.volume, volume, sizeof(uint16_t) * channels); audio.playback.volumeChannels = channels; if (!STREAM_ACTIVE(audio.playback.state)) return; audio.audioDev->playback.volume(channels, volume); } void audio_playbackMute(bool mute) { if (!audio.audioDev || !audio.audioDev->playback.mute) return; // store the value so we can restore it if the stream is restarted audio.playback.mute = mute; if (!STREAM_ACTIVE(audio.playback.state)) return; audio.audioDev->playback.mute(mute); } static double computeDevicePosition(int64_t curTime) { // Interpolate to calculate the current device position PlaybackSpiceData * spiceData = &audio.playback.spiceData; return spiceData->devLastPosition + (spiceData->devNextPosition - spiceData->devLastPosition) * ((double) (curTime - spiceData->devLastTime) / (spiceData->devNextTime - spiceData->devLastTime)); } void audio_playbackData(uint8_t * data, size_t size) { if (audio.playback.state == STREAM_STATE_STOP || !audio.audioDev || size == 0) return; PlaybackSpiceData * spiceData = &audio.playback.spiceData; int64_t now = nanotime(); // Convert from s16 to f32 samples int spiceStride = audio.playback.channels * sizeof(int16_t); int frames = size / spiceStride; bool periodChanged = frames != spiceData->periodFrames; bool init = spiceData->periodFrames == 0; if (periodChanged) { if (spiceData->framesIn) { free(spiceData->framesIn); free(spiceData->framesOut); } spiceData->periodFrames = frames; spiceData->framesIn = malloc(frames * audio.playback.stride); if (!spiceData->framesIn) { DEBUG_ERROR("Failed to malloc framesIn"); playbackStop(); return; } spiceData->framesOutSize = round(frames * 1.1); spiceData->framesOut = malloc(spiceData->framesOutSize * audio.playback.stride); if (!spiceData->framesOut) { DEBUG_ERROR("Failed to malloc framesOut"); playbackStop(); return; } } src_short_to_float_array((int16_t *) data, spiceData->framesIn, frames * audio.playback.channels); // Receive timing information from the audio device thread PlaybackDeviceTick deviceTick; while (ringbuffer_consume(audio.playback.deviceTiming, &deviceTick, 1)) { spiceData->devPeriodFrames = deviceTick.periodFrames; spiceData->devLastTime = spiceData->devNextTime; spiceData->devLastPosition = spiceData->devNextPosition; spiceData->devNextTime = deviceTick.nextTime; spiceData->devNextPosition = deviceTick.nextPosition; } /* Determine the target latency. This is made up of the maximum audio device * period (or the current actual period, if larger than the expected maximum), * plus a little extra to absorb timing jitter, and a configurable * additional buffer period. The default is set high enough to absorb typical * timing jitter from qemu. */ int configLatencyMs = max(g_params.audioBufferLatency, 0); int maxPeriodFrames = max(audio.playback.deviceMaxPeriodFrames, spiceData->devPeriodFrames); double targetLatencyFrames = maxPeriodFrames * 1.1 + configLatencyMs * audio.playback.sampleRate / 1000.0; /* If the device is currently at a lower period size than its maximum (which * can happen, for example, if another application has requested a lower * latency) then we need to take that into account in our target latency. * * The reason to do this is not necessarily obvious, since we already set the * target latency based upon the maximum period size. The problem stems from * the way the device changes the period size. When the period size is * reduced, there will be a transitional period where `playbackPullFrames` is * invoked with the new smaller period size, but the time until the next * invocation is based upon the previous size. This happens because the device * is preparing the next small buffer while still playing back the previous * large buffer. The result of this is that we end up with a surplus of data * in the ring buffer. The overall latency is unchanged, but the balance has * shifted: there is more data in our ring buffer and less in the device * buffer. * * Unaccounted for, this would be detected as an offset error and playback * would be sped up to bring things back in line. In isolation, this is not * inherently problematic, and may even be desirable because it would reduce * the overall latency. The real problem occurs when the period size goes back * up. * * When the period size increases, the exact opposite happens. The device will * suddenly request data at the new period size, but the timing interval will * be based upon the previous period size during the transition. If there is * not enough data to satisfy this then playback will start severely * underrunning until the timing loop can correct for the error. * * To counteract this issue, if the current period size is smaller than the * maximum period size then we increase the target latency by the difference. * This keeps the offset error stable and ensures we have enough data in the * buffer to absorb rate increases. */ if (spiceData->devPeriodFrames != 0 && spiceData->devPeriodFrames < audio.playback.deviceMaxPeriodFrames) targetLatencyFrames += audio.playback.deviceMaxPeriodFrames - spiceData->devPeriodFrames; // Measure the Spice audio clock int64_t curTime; int64_t curPosition; double devPosition = DBL_MIN; if (periodChanged) { if (init) spiceData->nextTime = now; curTime = spiceData->nextTime; curPosition = spiceData->nextPosition; spiceData->periodSec = (double) frames / audio.playback.sampleRate; spiceData->nextTime += llrint(spiceData->periodSec * 1.0e9); double bandwidth = 0.05; double omega = 2.0 * M_PI * bandwidth * spiceData->periodSec; spiceData->b = M_SQRT2 * omega; spiceData->c = omega * omega; } else { double error = (now - spiceData->nextTime) * 1.0e-9; if (fabs(error) >= 0.2 || audio.playback.state == STREAM_STATE_KEEP_ALIVE) { /* Clock error is too high or we are starting a new playback; slew the * write pointer and reset the timing parameters to get back in sync. If * we know the device playback position then we can slew directly to the * target latency, otherwise just slew based upon the error amount */ int slewFrames; if (spiceData->devLastTime != INT64_MIN) { devPosition = computeDevicePosition(now); double targetPosition = devPosition + targetLatencyFrames; // If starting a new playback we need to allow a little extra time for // the resampler startup latency if (audio.playback.state == STREAM_STATE_KEEP_ALIVE) { int resamplerLatencyFrames = 20; targetPosition += resamplerLatencyFrames; } slewFrames = round(targetPosition - spiceData->nextPosition); } else slewFrames = round(error * audio.playback.sampleRate); ringbuffer_append(audio.playback.buffer, NULL, slewFrames); curTime = now; curPosition = spiceData->nextPosition + slewFrames; spiceData->periodSec = (double) frames / audio.playback.sampleRate; spiceData->nextTime = now + llrint(spiceData->periodSec * 1.0e9); spiceData->nextPosition = curPosition; spiceData->offsetError = 0.0; spiceData->offsetErrorIntegral = 0.0; spiceData->ratioIntegral = 0.0; audio.playback.state = STREAM_STATE_RUN; } else { curTime = spiceData->nextTime; curPosition = spiceData->nextPosition; spiceData->nextTime += llrint((spiceData->b * error + spiceData->periodSec) * 1.0e9); spiceData->periodSec += spiceData->c * error; } } /* Measure the offset between the Spice position and the device position, * and how far away this is from the target latency. We use this to adjust * the playback speed to bring them back in line. This value can change * quite rapidly, particularly at the start of playback, so filter it to * avoid sudden pitch shifts which will be noticeable to the user. */ double actualOffset = 0.0; double offsetError = spiceData->offsetError; if (spiceData->devLastTime != INT64_MIN) { if (devPosition == DBL_MIN) devPosition = computeDevicePosition(curTime); actualOffset = curPosition - devPosition; double actualOffsetError = -(actualOffset - targetLatencyFrames); double error = actualOffsetError - offsetError; spiceData->offsetError += spiceData->b * error + spiceData->offsetErrorIntegral; spiceData->offsetErrorIntegral += spiceData->c * error; } // Resample the audio to adjust the playback speed. Use a PI controller to // adjust the resampling ratio based upon the measured offset double kp = 0.5e-6; double ki = 1.0e-16; spiceData->ratioIntegral += offsetError * spiceData->periodSec; double piOutput = kp * offsetError + ki * spiceData->ratioIntegral; double ratio = 1.0 + piOutput; int consumed = 0; while (consumed < frames) { SRC_DATA srcData = { .data_in = spiceData->framesIn + consumed * audio.playback.channels, .data_out = spiceData->framesOut, .input_frames = frames - consumed, .output_frames = spiceData->framesOutSize, .input_frames_used = 0, .output_frames_gen = 0, .end_of_input = 0, .src_ratio = ratio }; int error = src_process(spiceData->src, &srcData); if (error) { DEBUG_ERROR("Resampling failed: %s", src_strerror(error)); return; } ringbuffer_append(audio.playback.buffer, spiceData->framesOut, srcData.output_frames_gen); consumed += srcData.input_frames_used; spiceData->nextPosition += srcData.output_frames_gen; } if (audio.playback.state == STREAM_STATE_SETUP_SPICE) { /* Latency corrections at startup can be quite significant due to poor * packet pacing from Spice, so require at least two full Spice periods' * worth of data in addition to the startup delay requested by the device * before starting playback to minimise the chances of underrunning. */ int startFrames = spiceData->periodFrames * 2 + audio.playback.deviceStartFrames; audio.playback.targetStartFrames = startFrames; /* The actual time between opening the device and the device starting to * pull data can range anywhere between nearly instant and hundreds of * milliseconds. To minimise startup latency, we open the device * immediately. If the device starts earlier than required (as per the * `startFrames` value we just calculated), then a period of silence will be * inserted at the beginning of playback to avoid underrunning. If it starts * later, then we just accept the higher latency and let the adaptive * resampling deal with it. */ audio.playback.state = STREAM_STATE_SETUP_DEVICE; audio.audioDev->playback.start(); } double latencyFrames = actualOffset; if (audio.audioDev->playback.latency) latencyFrames += audio.audioDev->playback.latency(); const float latency = latencyFrames * 1000.0 / audio.playback.sampleRate; ringbuffer_push(audio.playback.timings, &latency); app_invalidateGraph(audio.playback.graph); } bool audio_supportsRecord(void) { return audio.audioDev && audio.audioDev->record.start; } static void recordPushFrames(uint8_t * data, int frames) { purespice_writeAudio(data, frames * audio.record.stride, 0); } static void realRecordStart(int channels, int sampleRate, PSAudioFormat format) { audio.record.started = true; audio.record.stride = channels * sizeof(uint16_t); audio.audioDev->record.start(channels, sampleRate, recordPushFrames); // if a volume level was stored, set it before we return if (audio.record.volumeChannels) audio.audioDev->record.volume( audio.playback.volumeChannels, audio.playback.volume); // set the inital mute state if (audio.audioDev->record.mute) audio.audioDev->record.mute(audio.playback.mute); if (g_params.micShowIndicator) app_showRecord(true); } struct AudioFormat { int channels; int sampleRate; PSAudioFormat format; }; static void recordConfirm(bool yes, void * opaque) { if (yes) { DEBUG_INFO("Microphone access granted"); realRecordStart( audio.record.confirmChannels, audio.record.confirmSampleRate, audio.record.confirmFormat ); } else DEBUG_INFO("Microphone access denied"); audio.record.confirmHandle = NULL; } void audio_recordStart(int channels, int sampleRate, PSAudioFormat format) { if (!audio.audioDev) return; static int lastChannels = 0; static int lastSampleRate = 0; if (audio.record.started) { if (channels != lastChannels || sampleRate != lastSampleRate) audio.audioDev->record.stop(); else return; } audio.record.requested = true; audio.record.lastChannels = channels; audio.record.lastSampleRate = sampleRate; audio.record.lastFormat = format; if (audio.record.started) realRecordStart(channels, sampleRate, format); else if (g_state.micDefaultState == MIC_DEFAULT_DENY) DEBUG_INFO("Microphone access denied by default"); else if (g_state.micDefaultState == MIC_DEFAULT_ALLOW) { DEBUG_INFO("Microphone access granted by default"); realRecordStart(channels, sampleRate, format); } else { if (audio.record.confirmHandle) app_msgBoxClose(audio.record.confirmHandle); audio.record.confirmChannels = channels; audio.record.confirmSampleRate = sampleRate; audio.record.confirmFormat = format; audio.record.confirmHandle = app_confirmMsgBox( "Microphone", recordConfirm, NULL, "An application just opened the microphone!\n" "Do you want it to access your microphone?"); } } static void realRecordStop(void) { audio.audioDev->record.stop(); audio.record.started = false; if (g_params.micShowIndicator) app_showRecord(false); } void audio_recordStop(void) { audio.record.requested = false; if (!audio.audioDev || !audio.record.started) return; DEBUG_INFO("Microphone recording stopped"); realRecordStop(); } void audio_recordToggleKeybind(int sc, void * opaque) { if (!audio.audioDev) return; if (!audio.record.requested) { app_alert(LG_ALERT_WARNING, "No application is requesting microphone access."); return; } if (audio.record.started) { app_alert(LG_ALERT_INFO, "Microphone disabled"); DEBUG_INFO("Microphone recording stopped by user"); realRecordStop(); } else { app_alert(LG_ALERT_INFO, "Microphone enabled"); DEBUG_INFO("Microphone recording started by user"); realRecordStart(audio.record.lastChannels, audio.record.lastSampleRate, audio.record.lastFormat); } } void audio_recordVolume(int channels, const uint16_t volume[]) { if (!audio.audioDev || !audio.audioDev->record.volume) return; // store the values so we can restore the state if the stream is restarted channels = min(ARRAY_LENGTH(audio.record.volume), channels); memcpy(audio.record.volume, volume, sizeof(uint16_t) * channels); audio.record.volumeChannels = channels; if (!audio.record.started) return; audio.audioDev->record.volume(channels, volume); } void audio_recordMute(bool mute) { if (!audio.audioDev || !audio.audioDev->record.mute) return; // store the value so we can restore it if the stream is restarted audio.record.mute = mute; if (!audio.record.started) return; audio.audioDev->record.mute(mute); } #endif looking-glass-B6/client/src/audio.h000066400000000000000000000031401434445012300174110ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #if ENABLE_AUDIO #include #include void audio_init(void); void audio_free(void); bool audio_supportsPlayback(void); void audio_playbackStart(int channels, int sampleRate, PSAudioFormat format, uint32_t time); void audio_playbackStop(void); void audio_playbackVolume(int channels, const uint16_t volume[]); void audio_playbackMute(bool mute); void audio_playbackData(uint8_t * data, size_t size); bool audio_supportsRecord(void); void audio_recordStart(int channels, int sampleRate, PSAudioFormat format); void audio_recordToggleKeybind(int sc, void * opaque); void audio_recordStop(void); void audio_recordVolume(int channels, const uint16_t volume[]); void audio_recordMute(bool mute); #else static inline void audio_init(void) {} static inline void audio_free(void) {} #endif looking-glass-B6/client/src/clipboard.c000066400000000000000000000057261434445012300202560ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "clipboard.h" #include "main.h" #include "common/debug.h" LG_ClipboardData cb_spiceTypeToLGType(const PSDataType type) { switch(type) { case SPICE_DATA_TEXT: return LG_CLIPBOARD_DATA_TEXT; break; case SPICE_DATA_PNG : return LG_CLIPBOARD_DATA_PNG ; break; case SPICE_DATA_BMP : return LG_CLIPBOARD_DATA_BMP ; break; case SPICE_DATA_TIFF: return LG_CLIPBOARD_DATA_TIFF; break; case SPICE_DATA_JPEG: return LG_CLIPBOARD_DATA_JPEG; break; default: DEBUG_ERROR("invalid spice data type"); return LG_CLIPBOARD_DATA_NONE; } } PSDataType cb_lgTypeToSpiceType(const LG_ClipboardData type) { switch(type) { case LG_CLIPBOARD_DATA_TEXT: return SPICE_DATA_TEXT; break; case LG_CLIPBOARD_DATA_PNG : return SPICE_DATA_PNG ; break; case LG_CLIPBOARD_DATA_BMP : return SPICE_DATA_BMP ; break; case LG_CLIPBOARD_DATA_TIFF: return SPICE_DATA_TIFF; break; case LG_CLIPBOARD_DATA_JPEG: return SPICE_DATA_JPEG; break; default: DEBUG_ERROR("invalid clipboard data type"); return SPICE_DATA_NONE; } } void cb_spiceNotice(const PSDataType type) { if (!g_params.clipboardToLocal) return; if (!g_state.cbAvailable) return; g_state.cbType = type; g_state.ds->cbNotice(cb_spiceTypeToLGType(type)); } void cb_spiceData(const PSDataType type, uint8_t * buffer, uint32_t size) { if (!g_params.clipboardToLocal) return; if (type == SPICE_DATA_TEXT) { // dos2unix uint8_t * p = buffer; uint32_t newSize = size; for(uint32_t i = 0; i < size; ++i) { uint8_t c = buffer[i]; if (c == '\r') { --newSize; continue; } *p++ = c; } size = newSize; } struct CBRequest * cbr; if (ll_shift(g_state.cbRequestList, (void **)&cbr)) { cbr->replyFn(cbr->opaque, cb_spiceTypeToLGType(type), buffer, size); free(cbr); } } void cb_spiceRelease(void) { if (!g_params.clipboardToLocal) return; if (g_state.cbAvailable) g_state.ds->cbRelease(); } void cb_spiceRequest(const PSDataType type) { if (!g_params.clipboardToVM) return; if (g_state.cbAvailable) g_state.ds->cbRequest(cb_spiceTypeToLGType(type)); } looking-glass-B6/client/src/clipboard.h000066400000000000000000000022551434445012300202550ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include "interface/displayserver.h" LG_ClipboardData cb_spiceTypeToLGType(const PSDataType type); PSDataType cb_lgTypeToSpiceType(const LG_ClipboardData type); void cb_spiceNotice(const PSDataType type); void cb_spiceData(const PSDataType type, uint8_t * buffer, uint32_t size); void cb_spiceRelease(void); void cb_spiceRequest(const PSDataType type); looking-glass-B6/client/src/config.c000066400000000000000000000722671434445012300175700ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "main.h" #include "config.h" #include "kb.h" #include "common/option.h" #include "common/debug.h" #include "common/paths.h" #include "common/stringutils.h" #include #include #include #include // forwards static bool optRendererParse (struct Option * opt, const char * str); static StringList optRendererValues (struct Option * opt); static char * optRendererToString (struct Option * opt); static bool optPosParse (struct Option * opt, const char * str); static StringList optPosValues (struct Option * opt); static char * optPosToString (struct Option * opt); static bool optSizeParse (struct Option * opt, const char * str); static StringList optSizeValues (struct Option * opt); static char * optSizeToString (struct Option * opt); static bool optScancodeParse (struct Option * opt, const char * str); static StringList optScancodeValues (struct Option * opt); static bool optScancodeValidate (struct Option * opt, const char ** error); static char * optScancodeToString (struct Option * opt); static bool optRotateValidate (struct Option * opt, const char ** error); static bool optMicDefaultParse (struct Option * opt, const char * str); static StringList optMicDefaultValues (struct Option * opt); static char * optMicDefaultToString(struct Option * opt); static void doLicense(void); static struct Option options[] = { // app options { .module = "app", .name = "configFile", .description = "A file to read additional configuration from", .shortopt = 'C', .type = OPTION_TYPE_STRING, .value.x_string = NULL, }, { .module = "app", .name = "renderer", .description = "Specify the renderer to use", .shortopt = 'g', .type = OPTION_TYPE_CUSTOM, .parser = optRendererParse, .getValues = optRendererValues, .toString = optRendererToString }, { .module = "app", .name = "license", .description = "Show the license for this application and then terminate", .shortopt = 'l', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "app", .name = "cursorPollInterval", .description = "How often to check for a cursor update in microseconds", .type = OPTION_TYPE_INT, .value.x_int = 1000 }, { .module = "app", .name = "framePollInterval", .description = "How often to check for a frame update in microseconds", .type = OPTION_TYPE_INT, .value.x_int = 1000 }, { .module = "app", .name = "allowDMA", .description = "Allow direct DMA transfers if supported (see `README.md` in the `module` dir)", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, // window options { .module = "win", .name = "title", .description = "The window title", .type = OPTION_TYPE_STRING, .value.x_string = "Looking Glass (client)" }, { .module = "win", .name = "position", .description = "Initial window position at startup", .type = OPTION_TYPE_CUSTOM, .parser = optPosParse, .getValues = optPosValues, .toString = optPosToString }, { .module = "win", .name = "size", .description = "Initial window size at startup", .type = OPTION_TYPE_CUSTOM, .parser = optSizeParse, .getValues = optSizeValues, .toString = optSizeToString }, { .module = "win", .name = "autoResize", .description = "Auto resize the window to the guest", .shortopt = 'a', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "allowResize", .description = "Allow the window to be manually resized", .shortopt = 'n', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "win", .name = "keepAspect", .description = "Maintain the correct aspect ratio", .shortopt = 'r', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "win", .name = "forceAspect", .description = "Force the window to maintain the aspect ratio", .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "win", .name = "dontUpscale", .description = "Never try to upscale the window", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "intUpscale", .description = "Allow only integer upscaling", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "shrinkOnUpscale", .description = "Limit the window dimensions when dontUpscale is enabled", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "borderless", .description = "Borderless mode", .shortopt = 'd', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "fullScreen", .description = "Launch in fullscreen borderless mode", .shortopt = 'F', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "maximize", .description = "Launch window maximized", .shortopt = 'T', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "minimizeOnFocusLoss", .description = "Minimize window on focus loss", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "fpsMin", .description = "Frame rate minimum (0 = disable - not recommended, -1 = auto detect)", .shortopt = 'K', .type = OPTION_TYPE_INT, .value.x_int = -1, }, { .module = "win", .name = "ignoreQuit", .description = "Ignore requests to quit (i.e. Alt+F4)", .shortopt = 'Q', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "noScreensaver", .description = "Prevent the screensaver from starting", .shortopt = 'S', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "autoScreensaver", .description = "Prevent the screensaver from starting when guest requests it", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "alerts", .description = "Show on screen alert messages", .shortopt = 'q', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "win", .name = "quickSplash", .description = "Skip fading out the splash screen when a connection is established", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "win", .name = "overlayDimsDesktop", .description = "Dim the desktop when in interactive overlay mode", .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "win", .name = "rotate", .description = "Rotate the displayed image (0, 90, 180, 270)", .type = OPTION_TYPE_INT, .validator = optRotateValidate, .value.x_int = 0, }, { .module = "win", .name = "uiFont", .description = "The font to use when rendering on-screen UI", .type = OPTION_TYPE_STRING, .value.x_string = "DejaVu Sans Mono", }, { .module = "win", .name = "uiSize", .description = "The font size to use when rendering on-screen UI", .type = OPTION_TYPE_INT, .value.x_int = 14 }, { .module = "win", .name = "jitRender", .description = "Enable just-in-time rendering", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, // input options { .module = "input", .name = "grabKeyboard", .description = "Grab the keyboard in capture mode", .shortopt = 'G', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "input", .name = "grabKeyboardOnFocus", .description = "Grab the keyboard when focused", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "input", .name = "releaseKeysOnFocusLoss", .description = "On focus loss, send key up events to guest for all held keys", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "input", .name = "escapeKey", .description = "Specify the escape/menu key to use (use \"help\" to see valid values)", .shortopt = 'm', .type = OPTION_TYPE_INT, .value.x_int = KEY_SCROLLLOCK, .parser = optScancodeParse, .getValues = optScancodeValues, .validator = optScancodeValidate, .toString = optScancodeToString, }, { .module = "input", .name = "ignoreWindowsKeys", .description = "Do not pass events for the windows keys to the guest", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "input", .name = "hideCursor", .description = "Hide the local mouse cursor", .shortopt = 'M', .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "input", .name = "mouseSens", .description = "Initial mouse sensitivity when in capture mode (-9 to 9)", .type = OPTION_TYPE_INT, .value.x_int = 0, }, { .module = "input", .name = "mouseSmoothing", .description = "Apply simple mouse smoothing when rawMouse is not in use (helps reduce aliasing)", .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "input", .name = "rawMouse", .description = "Use RAW mouse input when in capture mode (good for gaming)", .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { .module = "input", .name = "mouseRedraw", .description = "Mouse movements trigger redraws (ignores FPS minimum)", .type = OPTION_TYPE_BOOL, .value.x_bool = true, }, { .module = "input", .name = "autoCapture", .description = "Try to keep the mouse captured when needed", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "input", .name = "captureOnly", .description = "Only enable input via SPICE if in capture mode", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "input", .name = "helpMenuDelay", .description = "Show help menu after holding down the escape key for this many milliseconds", .type = OPTION_TYPE_INT, .value.x_int = 200 }, // spice options { .module = "spice", .name = "enable", .description = "Enable the built in SPICE client for input and/or clipboard support", .shortopt = 's', .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "host", .description = "The SPICE server host or UNIX socket", .shortopt = 'c', .type = OPTION_TYPE_STRING, .value.x_string = "127.0.0.1" }, { .module = "spice", .name = "port", .description = "The SPICE server port (0 = unix socket)", .shortopt = 'p', .type = OPTION_TYPE_INT, .value.x_int = 5900 }, { .module = "spice", .name = "input", .description = "Use SPICE to send keyboard and mouse input events to the guest", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "clipboard", .description = "Use SPICE to synchronize the clipboard contents with the guest", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "clipboardToVM", .description = "Allow the clipboard to be synchronized TO the VM", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "clipboardToLocal", .description = "Allow the clipboard to be synchronized FROM the VM", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "audio", .description = "Enable SPICE audio support", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "scaleCursor", .description = "Scale cursor input position to screen size when up/down scaled", .shortopt = 'j', .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "spice", .name = "captureOnStart", .description = "Capture mouse and keyboard on start", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "spice", .name = "alwaysShowCursor", .description = "Always show host cursor", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, { .module = "spice", .name = "showCursorDot", .description = "Use a \"dot\" cursor when the window does not have focus", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, // audio options { .module = "audio", .name = "periodSize", .description = "Requested audio device period size in samples", .type = OPTION_TYPE_INT, .value.x_int = 2048 }, { .module = "audio", .name = "bufferLatency", .description = "Additional buffer latency in milliseconds", .type = OPTION_TYPE_INT, .value.x_int = 13 }, { .module = "audio", .name = "micDefault", .description = "Default action when an application opens the microphone (prompt, allow, deny)", .type = OPTION_TYPE_CUSTOM, .parser = optMicDefaultParse, .getValues = optMicDefaultValues, .toString = optMicDefaultToString }, { .module = "audio", .name = "micShowIndicator", .description = "Display microphone usage indicator", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, {0} }; void config_init(void) { g_params.center = true; g_params.w = 1024; g_params.h = 768; option_register(options); } bool config_load(int argc, char * argv[]) { // load any global options first struct stat st; if (stat("/etc/looking-glass-client.ini", &st) >= 0 && S_ISREG(st.st_mode)) { DEBUG_INFO("Loading config from: /etc/looking-glass-client.ini"); if (!option_load("/etc/looking-glass-client.ini")) return false; } // load config from user's home directory struct passwd * pw = getpwuid(getuid()); if (!pw) DEBUG_WARN("getpwuid failed, unable to load user configuration"); else { char * localFile; alloc_sprintf(&localFile, "%s/.looking-glass-client.ini", pw->pw_dir); if (!localFile) { DEBUG_ERROR("out of memory"); return false; } if (stat(localFile, &st) >= 0 && S_ISREG(st.st_mode)) { DEBUG_INFO("Loading config from: %s", localFile); if (!option_load(localFile)) { free(localFile); return false; } } free(localFile); } // load config from XDG_CONFIG_HOME char * xdgFile; alloc_sprintf(&xdgFile, "%s/client.ini", lgConfigDir()); if (!xdgFile) { DEBUG_ERROR("out of memory"); return false; } if (xdgFile && stat(xdgFile, &st) >= 0 && S_ISREG(st.st_mode)) { DEBUG_INFO("Loading config from: %s", xdgFile); if (!option_load(xdgFile)) { free(xdgFile); return false; } } free(xdgFile); // parse the command line arguments if (!option_parse(argc, argv)) return false; // if a file was specified to also load, do it const char * configFile = option_get_string("app", "configFile"); if (configFile) { if (stat(configFile, &st) < 0 || !S_ISREG(st.st_mode)) { DEBUG_ERROR("app:configFile set to invalid file: %s", configFile); return false; } DEBUG_INFO("Loading config from: %s", configFile); if (!option_load(configFile)) return false; } // validate the values are sane if (!option_validate()) return false; if (option_get_bool("app", "license")) { doLicense(); return false; } // setup the application params for the basic types g_params.cursorPollInterval = option_get_int ("app" , "cursorPollInterval"); g_params.framePollInterval = option_get_int ("app" , "framePollInterval" ); g_params.allowDMA = option_get_bool ("app" , "allowDMA" ); g_params.windowTitle = option_get_string("win", "title" ); g_params.autoResize = option_get_bool ("win", "autoResize" ); g_params.allowResize = option_get_bool ("win", "allowResize" ); g_params.keepAspect = option_get_bool ("win", "keepAspect" ); g_params.forceAspect = option_get_bool ("win", "forceAspect" ); g_params.dontUpscale = option_get_bool ("win", "dontUpscale" ); g_params.intUpscale = option_get_bool ("win", "intUpscale" ); g_params.shrinkOnUpscale = option_get_bool ("win", "shrinkOnUpscale" ); g_params.borderless = option_get_bool ("win", "borderless" ); g_params.fullscreen = option_get_bool ("win", "fullScreen" ); g_params.maximize = option_get_bool ("win", "maximize" ); g_params.fpsMin = option_get_int ("win", "fpsMin" ); g_params.ignoreQuit = option_get_bool ("win", "ignoreQuit" ); g_params.noScreensaver = option_get_bool ("win", "noScreensaver" ); g_params.autoScreensaver = option_get_bool ("win", "autoScreensaver" ); g_params.showAlerts = option_get_bool ("win", "alerts" ); g_params.quickSplash = option_get_bool ("win", "quickSplash" ); g_params.overlayDim = option_get_bool ("win", "overlayDimsDesktop"); g_params.uiFont = option_get_string("win", "uiFont" ); g_params.uiSize = option_get_int ("win", "uiSize" ); g_params.jitRender = option_get_bool ("win", "jitRender" ); if (g_params.noScreensaver && g_params.autoScreensaver) { DEBUG_WARN("win:noScreensaver (-S) and win:autoScreensaver " "can't be used simultaneously"); return false; } switch(option_get_int("win", "rotate")) { case 0 : g_params.winRotate = LG_ROTATE_0 ; break; case 90 : g_params.winRotate = LG_ROTATE_90 ; break; case 180: g_params.winRotate = LG_ROTATE_180; break; case 270: g_params.winRotate = LG_ROTATE_270; break; } g_params.grabKeyboard = option_get_bool("input", "grabKeyboard" ); g_params.grabKeyboardOnFocus = option_get_bool("input", "grabKeyboardOnFocus" ); g_params.releaseKeysOnFocusLoss = option_get_bool("input", "releaseKeysOnFocusLoss"); g_params.escapeKey = option_get_int ("input", "escapeKey" ); g_params.ignoreWindowsKeys = option_get_bool("input", "ignoreWindowsKeys" ); g_params.hideMouse = option_get_bool("input", "hideCursor" ); g_params.mouseSens = option_get_int ("input", "mouseSens" ); g_params.mouseSmoothing = option_get_bool("input", "mouseSmoothing" ); g_params.rawMouse = option_get_bool("input", "rawMouse" ); g_params.mouseRedraw = option_get_bool("input", "mouseRedraw" ); g_params.autoCapture = option_get_bool("input", "autoCapture" ); g_params.captureInputOnly = option_get_bool("input", "captureOnly" ); if (g_params.jitRender && !g_params.mouseRedraw) { DEBUG_WARN("win:jitRender is enabled, forcing input:mouseRedraw"); g_params.mouseRedraw = true; } g_params.helpMenuDelayUs = option_get_int("input", "helpMenuDelay") * (uint64_t) 1000; g_params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss"); if ((g_params.useSpice = option_get_bool("spice", "enable"))) { g_params.spiceHost = option_get_string("spice", "host"); g_params.spicePort = option_get_int ("spice", "port"); g_params.useSpiceInput = option_get_bool("spice", "input" ); g_params.useSpiceClipboard = option_get_bool("spice", "clipboard"); g_params.useSpiceAudio = option_get_bool("spice", "audio" ); if (g_params.useSpiceClipboard) { g_params.clipboardToVM = option_get_bool("spice", "clipboardToVM" ); g_params.clipboardToLocal = option_get_bool("spice", "clipboardToLocal"); g_params.useSpiceClipboard = g_params.clipboardToVM || g_params.clipboardToLocal; } else { g_params.clipboardToVM = false; g_params.clipboardToLocal = false; } g_params.scaleMouseInput = option_get_bool("spice", "scaleCursor"); g_params.captureOnStart = option_get_bool("spice", "captureOnStart"); g_params.alwaysShowCursor = option_get_bool("spice", "alwaysShowCursor"); g_params.showCursorDot = option_get_bool("spice", "showCursorDot"); } g_params.audioPeriodSize = option_get_int("audio", "periodSize"); g_params.audioBufferLatency = option_get_int("audio", "bufferLatency"); g_params.micShowIndicator = option_get_bool("audio", "micShowIndicator"); return true; } void config_free(void) { option_free(); } static void doLicense(void) { fprintf(stderr, // BEGIN LICENSE BLOCK "\n" "Looking Glass\n" "Copyright © 2017-2022 The Looking Glass Authors\n" "https://looking-glass.io\n" "\n" "This program is free software; you can redistribute it and/or modify it under\n" "the terms of the GNU General Public License as published by the Free Software\n" "Foundation; either version 2 of the License, or (at your option) any later\n" "version.\n" "\n" "This program is distributed in the hope that it will be useful, but WITHOUT ANY\n" "WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n" "PARTICULAR PURPOSE. See the GNU General Public License for more details.\n" "\n" "You should have received a copy of the GNU General Public License along with\n" "this program; if not, write to the Free Software Foundation, Inc., 59 Temple\n" "Place, Suite 330, Boston, MA 02111-1307 USA\n" "\n" // END LICENSE BLOCK ); } static bool optRendererParse(struct Option * opt, const char * str) { if (!str) return false; if (strcasecmp(str, "auto") == 0) { g_params.forceRenderer = false; return true; } for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) if (strcasecmp(str, LG_Renderers[i]->getName()) == 0) { g_params.forceRenderer = true; g_params.forceRendererIndex = i; return true; } return false; } static StringList optRendererValues(struct Option * opt) { StringList sl = stringlist_new(false); if (!sl) return NULL; // this typecast is safe as the stringlist doesn't own the values for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) stringlist_push(sl, (char *)LG_Renderers[i]->getName()); return sl; } static char * optRendererToString(struct Option * opt) { if (!g_params.forceRenderer) return strdup("auto"); if (g_params.forceRendererIndex >= LG_RENDERER_COUNT) return NULL; return strdup(LG_Renderers[g_params.forceRendererIndex]->getName()); } static bool optPosParse(struct Option * opt, const char * str) { if (!str) return false; if (strcmp(str, "center") == 0) { g_params.center = true; return true; } if (sscanf(str, "%dx%d", &g_params.x, &g_params.y) == 2) { g_params.center = false; return true; } return false; } static StringList optPosValues(struct Option * opt) { StringList sl = stringlist_new(false); if (!sl) return NULL; stringlist_push(sl, "center"); stringlist_push(sl, "x, e.g. 100x100"); return sl; } static char * optPosToString(struct Option * opt) { if (g_params.center) return strdup("center"); int len = snprintf(NULL, 0, "%dx%d", g_params.x, g_params.y); char * str = malloc(len + 1); if (!str) { DEBUG_ERROR("out of memory"); return NULL; } sprintf(str, "%dx%d", g_params.x, g_params.y); return str; } static bool optSizeParse(struct Option * opt, const char * str) { if (!str) return false; if (sscanf(str, "%ux%u", &g_params.w, &g_params.h) == 2) { if (g_params.w < 1 || g_params.h < 1) return false; return true; } return false; } static StringList optSizeValues(struct Option * opt) { StringList sl = stringlist_new(false); if (!sl) return NULL; stringlist_push(sl, "x, e.g. 100x100"); return sl; } static char * optSizeToString(struct Option * opt) { int len = snprintf(NULL, 0, "%ux%u", g_params.w, g_params.h); char * str = malloc(len + 1); if (!str) { DEBUG_ERROR("out of memory"); return NULL; } sprintf(str, "%ux%u", g_params.w, g_params.h); return str; } static StringList optScancodeValues(struct Option * opt) { StringList sl = stringlist_new(false); if (!sl) return NULL; // this typecast is safe as the stringlist doesn't own the values for(unsigned int i = 0; i < KEY_MAX; ++i) if (linux_to_str[i]) stringlist_push(sl, (char *)linux_to_str[i]); return sl; } static bool optScancodeValidate(struct Option * opt, const char ** error) { if (opt->value.x_int >= 0 && opt->value.x_int < KEY_MAX) return true; *error = "Out of range"; return false; } static bool optScancodeParse(struct Option * opt, const char * str) { for(unsigned int i = 0; i < KEY_MAX; ++i) { if (!linux_to_str[i]) continue; if (strcasecmp(linux_to_str[i], str) == 0) { opt->value.x_int = i; return true; } } // fallback for pre-keycode name support char * end; opt->value.x_int = strtol(str, &end, 10); if (*end) return false; return true; } static char * optScancodeToString(struct Option * opt) { char * str; alloc_sprintf(&str, "%d = %s", opt->value.x_int, linux_to_str[opt->value.x_int]); return str; } static bool optRotateValidate(struct Option * opt, const char ** error) { switch(opt->value.x_int) { case 0: case 90: case 180: case 270: return true; } *error = "Rotation angle must be one of 0, 90, 180 or 270"; return false; } static bool optMicDefaultParse(struct Option * opt, const char * str) { if (!str) return false; if (strcasecmp(str, "prompt") == 0) g_params.micDefaultState = MIC_DEFAULT_PROMPT; else if (strcasecmp(str, "allow") == 0) g_params.micDefaultState = MIC_DEFAULT_ALLOW; else if (strcasecmp(str, "deny") == 0) g_params.micDefaultState = MIC_DEFAULT_DENY; else return false; return true; } static StringList optMicDefaultValues(struct Option * opt) { StringList sl = stringlist_new(false); if (!sl) return NULL; stringlist_push(sl, (char *)"prompt"); stringlist_push(sl, (char *)"allow"); stringlist_push(sl, (char *)"deny"); return sl; } static char * optMicDefaultToString(struct Option * opt) { switch (g_params.micDefaultState) { case MIC_DEFAULT_PROMPT: return strdup("prompt"); case MIC_DEFAULT_ALLOW: return strdup("allow"); case MIC_DEFAULT_DENY: return strdup("deny"); } return NULL; } looking-glass-B6/client/src/config.h000066400000000000000000000016441434445012300175640ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include void config_init(void); bool config_load(int argc, char * argv[]); void config_free(void); looking-glass-B6/client/src/core.c000066400000000000000000000435331434445012300172450ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "core.h" #include "main.h" #include "app.h" #include "util.h" #include "common/time.h" #include "common/debug.h" #include "common/array.h" #include #define RESIZE_TIMEOUT (10 * 1000) // 10ms static bool isInView(void) { return g_cursor.pos.x >= g_state.dstRect.x && g_cursor.pos.x < g_state.dstRect.x + g_state.dstRect.w && g_cursor.pos.y >= g_state.dstRect.y && g_cursor.pos.y < g_state.dstRect.y + g_state.dstRect.h; } bool core_inputEnabled(void) { return g_params.useSpiceInput && !g_state.ignoreInput && ((g_cursor.grab && g_params.captureInputOnly) || !g_params.captureInputOnly); } void core_invalidatePointer(bool detectInView) { /* if the display server does not support warp, then we can not operate in * always relative mode and we should not grab the pointer */ enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE; app_getProp(LG_DS_WARP_SUPPORT, &warpSupport); if (detectInView) { bool inView = isInView(); // do not allow the view to become active if any mouse buttons are being held, // this fixes issues with meta window resizing. if (inView && g_cursor.buttons) return; g_cursor.inView = inView; } g_cursor.draw = (g_params.alwaysShowCursor || g_params.captureInputOnly) ? true : g_cursor.inView; g_cursor.redraw = true; g_cursor.warpState = g_cursor.inView ? WARP_STATE_ON : WARP_STATE_OFF; if (g_cursor.inView) { if (g_params.hideMouse) g_state.ds->setPointer(LG_POINTER_NONE); if (warpSupport != LG_DS_WARP_NONE && !g_params.captureInputOnly) g_state.ds->grabPointer(); if (g_params.grabKeyboardOnFocus) g_state.ds->grabKeyboard(); } else { if (g_params.hideMouse) g_state.ds->setPointer(LG_POINTER_SQUARE); if (warpSupport != LG_DS_WARP_NONE) g_state.ds->ungrabPointer(); g_state.ds->ungrabKeyboard(); } g_cursor.warpState = WARP_STATE_ON; } void core_setCursorInView(bool enable) { // if the state has not changed, don't do anything else if (g_cursor.inView == enable) return; if (enable && !g_state.focused) return; g_cursor.inView = enable; core_invalidatePointer(false); } void core_setGrab(bool enable) { core_setGrabQuiet(enable); app_alert( g_cursor.grab ? LG_ALERT_SUCCESS : LG_ALERT_WARNING, g_cursor.grab ? "Capture Enabled" : "Capture Disabled" ); } void core_setGrabQuiet(bool enable) { /* we always do this so that at init the cursor is in the right state */ if (g_params.captureInputOnly && g_params.hideMouse) g_state.ds->setPointer(enable ? LG_POINTER_NONE : LG_POINTER_SQUARE); if (g_cursor.grab == enable) return; g_cursor.grab = enable; g_cursor.acc.x = 0.0; g_cursor.acc.y = 0.0; /* if the display server does not support warp we need to ungrab the pointer * here instead of in the move handler */ enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE; app_getProp(LG_DS_WARP_SUPPORT, &warpSupport); if (enable) { core_setCursorInView(true); g_state.ignoreInput = false; if (g_params.grabKeyboard) g_state.ds->grabKeyboard(); g_state.ds->capturePointer(); } else { if (g_params.grabKeyboard) { if (!g_params.grabKeyboardOnFocus || !g_state.focused || g_params.captureInputOnly) g_state.ds->ungrabKeyboard(); } g_state.ds->uncapturePointer(); /* if exiting capture when input on capture only we need to align the local * cursor to the guest's location before it is shown. */ if (g_params.captureInputOnly || !g_params.hideMouse) core_alignToGuest(); } } bool core_warpPointer(int x, int y, bool exiting) { if ((!g_cursor.inWindow && !exiting) || app_isOverlayMode() || g_cursor.warpState == WARP_STATE_OFF) return false; if (exiting) g_cursor.warpState = WARP_STATE_OFF; if (g_cursor.pos.x == x && g_cursor.pos.y == y) return true; g_state.ds->warpPointer(x, y, exiting); return true; } void core_updatePositionInfo(void) { if (!g_state.haveSrcSize) goto done; float srcW; float srcH; switch(g_params.winRotate) { case LG_ROTATE_0: case LG_ROTATE_180: srcW = g_state.srcSize.x; srcH = g_state.srcSize.y; break; case LG_ROTATE_90: case LG_ROTATE_270: srcW = g_state.srcSize.y; srcH = g_state.srcSize.x; break; default: DEBUG_UNREACHABLE(); } if (g_params.keepAspect) { const float srcAspect = srcH / srcW; const float wndAspect = (float)g_state.windowH / (float)g_state.windowW; bool force = true; if (g_params.dontUpscale && srcW <= g_state.windowW && srcH <= g_state.windowH) { force = false; g_state.dstRect.w = srcW; g_state.dstRect.h = srcH; g_state.dstRect.x = g_state.windowCX - srcW / 2; g_state.dstRect.y = g_state.windowCY - srcH / 2; } else if (g_params.intUpscale && srcW <= g_state.windowW && srcH <= g_state.windowH) { force = false; const int scale = min( floor(g_state.windowW / srcW), floor(g_state.windowH / srcH)); g_state.dstRect.w = srcW * scale; g_state.dstRect.h = srcH * scale; g_state.dstRect.x = g_state.windowCX - g_state.dstRect.w / 2; g_state.dstRect.y = g_state.windowCY - g_state.dstRect.h / 2; } else if ((int)(wndAspect * 1000) == (int)(srcAspect * 1000)) { force = false; g_state.dstRect.w = g_state.windowW; g_state.dstRect.h = g_state.windowH; g_state.dstRect.x = 0; g_state.dstRect.y = 0; } else if (wndAspect < srcAspect) { g_state.dstRect.w = (float)g_state.windowH / srcAspect; g_state.dstRect.h = g_state.windowH; g_state.dstRect.x = (g_state.windowW >> 1) - (g_state.dstRect.w >> 1); g_state.dstRect.y = 0; } else { g_state.dstRect.w = g_state.windowW; g_state.dstRect.h = (float)g_state.windowW * srcAspect; g_state.dstRect.x = 0; g_state.dstRect.y = (g_state.windowH >> 1) - (g_state.dstRect.h >> 1); } if (g_params.dontUpscale && g_params.shrinkOnUpscale) { if (g_state.windowW > srcW) { force = true; g_state.dstRect.w = (int) (srcW + 0.5); } if (g_state.windowH > srcH) { force = true; g_state.dstRect.h = (int) (srcH + 0.5); } } if (force && g_params.forceAspect) { g_state.resizeTimeout = microtime() + RESIZE_TIMEOUT; g_state.resizeDone = false; } } else { g_state.dstRect.x = 0; g_state.dstRect.y = 0; g_state.dstRect.w = g_state.windowW; g_state.dstRect.h = g_state.windowH; } g_state.dstRect.valid = true; g_cursor.useScale = ( srcH != g_state.dstRect.h || srcW != g_state.dstRect.w); g_cursor.scale.x = (float)srcW / (float)g_state.dstRect.w; g_cursor.scale.y = (float)srcH / (float)g_state.dstRect.h; if (!g_state.posInfoValid) { g_state.posInfoValid = true; g_state.ds->realignPointer(); // g_cursor.guest.valid could have become true in the meantime. if (g_cursor.guest.valid) { // Since posInfoValid was false, core_handleGuestMouseUpdate becomes a // noop when called on the cursor thread, which means we need to call it // again in order for the cursor to show up. core_handleGuestMouseUpdate(); // Similarly, the position needs to be valid before the initial mouse // move, otherwise we wouldn't know if the cursor is in the viewport. app_handleMouseRelative(0.0, 0.0, 0.0, 0.0); } } done: atomic_fetch_add(&g_state.lgrResize, 1); } void core_alignToGuest(void) { if (!g_cursor.guest.valid || !g_state.focused) return; struct DoublePoint local; if (util_guestCurToLocal(&local)) if (core_warpPointer(round(local.x), round(local.y), false)) core_setCursorInView(true); } bool core_isValidPointerPos(int x, int y) { return g_state.ds->isValidPointerPos(x, y); } bool core_startCursorThread(void) { if (g_state.cursorThread) return true; g_state.stopVideo = false; if (!lgCreateThread("cursorThread", main_cursorThread, NULL, &g_state.cursorThread)) { DEBUG_ERROR("cursor create thread failed"); return false; } return true; } void core_stopCursorThread(void) { g_state.stopVideo = true; if (g_state.cursorThread) lgJoinThread(g_state.cursorThread, NULL); g_state.cursorThread = NULL; } bool core_startFrameThread(void) { if (g_state.frameThread) return true; g_state.stopVideo = false; if (!lgCreateThread("frameThread", main_frameThread, NULL, &g_state.frameThread)) { DEBUG_ERROR("frame create thread failed"); return false; } return true; } void core_stopFrameThread(void) { g_state.stopVideo = true; if (g_state.frameThread) lgJoinThread(g_state.frameThread, NULL); g_state.frameThread = NULL; } void core_handleGuestMouseUpdate(void) { struct DoublePoint localPos; if (!util_guestCurToLocal(&localPos)) return; if (app_isOverlayMode() || !g_cursor.inView) return; g_state.ds->guestPointerUpdated( g_cursor.guest.x, g_cursor.guest.y, util_clamp(localPos.x, g_state.dstRect.x, g_state.dstRect.x + g_state.dstRect.w), util_clamp(localPos.y, g_state.dstRect.y, g_state.dstRect.y + g_state.dstRect.h) ); } void core_handleMouseGrabbed(double ex, double ey) { if (!core_inputEnabled()) return; int x, y; if (g_params.rawMouse && !g_cursor.sens) { /* raw unscaled input are always round numbers */ x = floor(ex); y = floor(ey); } else { /* apply sensitivity */ ex = (ex / 10.0) * (g_cursor.sens + 10); ey = (ey / 10.0) * (g_cursor.sens + 10); util_cursorToInt(ex, ey, &x, &y); } if (x == 0 && y == 0) return; if (!purespice_mouseMotion(x, y)) DEBUG_ERROR("failed to send mouse motion message"); } void core_handleMouseNormal(double ex, double ey) { // prevent cursor handling outside of capture if the position is not known if (!g_cursor.guest.valid) return; if (g_cursor.realigning) return; if (!core_inputEnabled()) return; /* scale the movement to the guest */ if (g_cursor.useScale && g_params.scaleMouseInput) { ex *= g_cursor.scale.x; ey *= g_cursor.scale.y; } bool testExit = true; const bool inView = isInView(); if (!g_cursor.inView) { if (inView) g_cursor.realign = true; else /* nothing to do if we are outside the viewport */ return; } /* * do not pass mouse events to the guest if we do not have focus, this must be * done after the inView test has been performed so that when focus is gained * we know if we should be drawing the cursor. */ if (!g_state.focused) { core_setCursorInView(inView); return; } /* if we have been instructed to realign */ if (g_cursor.realign) { struct DoublePoint guest; util_localCurToGuest(&guest); if (!g_state.stopVideo && g_state.kvmfrFeatures & KVMFR_FEATURE_SETCURSORPOS) { const KVMFRSetCursorPos msg = { .msg.type = KVMFR_MESSAGE_SETCURSORPOS, .x = round(guest.x), .y = round(guest.y) }; uint32_t setPosSerial; LGMP_STATUS status; if ((status = lgmpClientSendData(g_state.pointerQueue, &msg, sizeof(msg), &setPosSerial)) != LGMP_OK) { DEBUG_WARN("Message send failed: %s", lgmpStatusString(status)); goto fallback; } else { /* wait for the move request to be processed */ g_cursor.realigning = true; do { LG_LOCK(g_state.pointerQueueLock); if (!g_state.pointerQueue) { /* the queue is nolonger valid, assume complete */ g_cursor.realigning = false; LG_UNLOCK(g_state.pointerQueueLock); break; } uint32_t hostSerial; if (lgmpClientGetSerial(g_state.pointerQueue, &hostSerial) != LGMP_OK) { g_cursor.realigning = false; LG_UNLOCK(g_state.pointerQueueLock); return; } LG_UNLOCK(g_state.pointerQueueLock); if (hostSerial >= setPosSerial) break; g_state.ds->wait(1); } while(app_isRunning()); g_cursor.guest.x = msg.x; g_cursor.guest.y = msg.y; g_cursor.realign = false; g_cursor.realigning = false; g_cursor.redraw = true; if (!g_cursor.inWindow) return; core_setCursorInView(true); return; } } else { fallback: /* add the difference to the offset */ ex += guest.x - (g_cursor.guest.x + g_cursor.guest.hx); ey += guest.y - (g_cursor.guest.y + g_cursor.guest.hy); core_setCursorInView(true); } g_cursor.realign = false; /* don't test for an exit as we just entered, we can get into a enter/exit * loop otherwise */ testExit = false; } /* if we are in "autoCapture" and the delta was large don't test for exit */ if (g_params.autoCapture && (fabs(ex) > 20.0 / g_cursor.scale.x || fabs(ey) > 20.0 / g_cursor.scale.y)) testExit = false; /* if any buttons are held we should not allow exit to happen */ if (g_cursor.buttons) testExit = false; if (testExit) { enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE; app_getProp(LG_DS_WARP_SUPPORT, &warpSupport); /* translate the move to the guests orientation */ struct DoublePoint move = {.x = ex, .y = ey}; util_rotatePoint(&move); /* translate the guests position to our coordinate space */ struct DoublePoint local; util_guestCurToLocal(&local); /* check if the move would push the cursor outside the guest's viewport */ if ( local.x + move.x < g_state.dstRect.x || local.y + move.y < g_state.dstRect.y || local.x + move.x >= g_state.dstRect.x + g_state.dstRect.w || local.y + move.y >= g_state.dstRect.y + g_state.dstRect.h) { local.x += move.x; local.y += move.y; const int tx = (local.x <= 0.0) ? floor(local.x) : ceil(local.x); const int ty = (local.y <= 0.0) ? floor(local.y) : ceil(local.y); switch (warpSupport) { case LG_DS_WARP_NONE: break; case LG_DS_WARP_SURFACE: g_state.ds->ungrabPointer(); core_warpPointer(tx, ty, true); if (!isInView() && tx >= 0 && tx < g_state.windowW && ty >= 0 && ty < g_state.windowH) core_setCursorInView(false); break; case LG_DS_WARP_SCREEN: if (core_isValidPointerPos( g_state.windowPos.x + g_state.border.left + tx, g_state.windowPos.y + g_state.border.top + ty)) { core_setCursorInView(false); /* preempt the window leave flag if the warp will leave our window */ if (tx < 0 || ty < 0 || tx > g_state.windowW || ty > g_state.windowH) g_cursor.inWindow = false; /* ungrab the pointer and move the local cursor to the exit point */ g_state.ds->ungrabPointer(); core_warpPointer(tx, ty, true); return; } } } else if (warpSupport == LG_DS_WARP_SURFACE && isInView()) { /* regrab the pointer in case the user did not move off the surface */ g_state.ds->grabPointer(); g_cursor.warpState = WARP_STATE_ON; } } int x, y; util_cursorToInt(ex, ey, &x, &y); if (x == 0 && y == 0) return; if (g_params.autoCapture) { g_cursor.delta.x += x; g_cursor.delta.y += y; if (fabs(g_cursor.delta.x) > 50.0 || fabs(g_cursor.delta.y) > 50.0) { g_cursor.delta.x = 0; g_cursor.delta.y = 0; } } else { /* assume the mouse will move to the location we attempt to move it to so we * avoid warp out of window issues. The cursorThread will correct this if * wrong after the movement has ocurred on the guest */ g_cursor.guest.x += x; g_cursor.guest.y += y; } if (!purespice_mouseMotion(x, y)) DEBUG_ERROR("failed to send mouse motion message"); } void core_resetOverlayInputState(void) { g_state.io->MouseDown[ImGuiMouseButton_Left ] = false; g_state.io->MouseDown[ImGuiMouseButton_Right ] = false; g_state.io->MouseDown[ImGuiMouseButton_Middle] = false; for(int key = 0; key < ARRAY_LENGTH(g_state.io->KeysDown); key++) g_state.io->KeysDown[key] = false; } void core_updateOverlayState(void) { g_state.cursorLast = -2; static bool wasGrabbed = false; if (app_isOverlayMode()) { wasGrabbed = g_cursor.grab; g_state.io->ConfigFlags &= ~ImGuiConfigFlags_NoMouse; g_state.io->MousePos = (ImVec2) { g_cursor.pos.x, g_cursor.pos.y }; core_setGrabQuiet(false); core_setCursorInView(false); } else { g_state.io->ConfigFlags |= ImGuiConfigFlags_NoMouse; core_resetOverlayInputState(); core_setGrabQuiet(wasGrabbed); core_invalidatePointer(true); app_invalidateWindow(false); if (!g_cursor.grab) { g_cursor.realign = true; core_handleMouseNormal(0, 0); } } } looking-glass-B6/client/src/core.h000066400000000000000000000030701434445012300172420ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_CORE_ #define _H_LG_CORE_ #include bool core_inputEnabled(void); void core_invalidatePointer(bool detectInView); void core_setCursorInView(bool enable); void core_setGrab(bool enable); void core_setGrabQuiet(bool enable); bool core_warpPointer(int x, int y, bool exiting); void core_updatePositionInfo(void); void core_alignToGuest(void); bool core_isValidPointerPos(int x, int y); bool core_startCursorThread(void); void core_stopCursorThread(void); bool core_startFrameThread(void); void core_stopFrameThread(void); void core_handleGuestMouseUpdate(void); void core_handleMouseGrabbed(double ex, double ey); void core_handleMouseNormal(double ex, double ey); void core_resetOverlayInputState(void); void core_updateOverlayState(void); #endif looking-glass-B6/client/src/egl_dynprocs.c000066400000000000000000000046521434445012300210040ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef ENABLE_EGL #include "egl_dynprocs.h" struct EGLDynProcs g_egl_dynProcs = {0}; void egl_dynProcsInit(void) { g_egl_dynProcs.eglGetPlatformDisplay = (PFNEGLGETPLATFORMDISPLAYPROC) eglGetProcAddress("eglGetPlatformDisplay"); g_egl_dynProcs.eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYPROC) eglGetProcAddress("eglGetPlatformDisplayEXT"); g_egl_dynProcs.glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) eglGetProcAddress("glEGLImageTargetTexture2DOES"); g_egl_dynProcs.eglSwapBuffersWithDamageKHR = (PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC) eglGetProcAddress("eglSwapBuffersWithDamageKHR"); g_egl_dynProcs.eglSwapBuffersWithDamageEXT = (PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC) eglGetProcAddress("eglSwapBuffersWithDamageEXT"); g_egl_dynProcs.glDebugMessageCallback = (PFNGLDEBUGMESSAGECALLBACKKHRPROC) eglGetProcAddress("glDebugMessageCallback"); g_egl_dynProcs.glDebugMessageCallbackKHR = (PFNGLDEBUGMESSAGECALLBACKKHRPROC) eglGetProcAddress("glDebugMessageCallbackKHR"); g_egl_dynProcs.glBufferStorageEXT = (PFNGLBUFFERSTORAGEEXTPROC) eglGetProcAddress("glBufferStorageEXT"); g_egl_dynProcs.eglCreateImage = (PFNEGLCREATEIMAGEPROC) eglGetProcAddress("eglCreateImage"); g_egl_dynProcs.eglDestroyImage = (PFNEGLDESTROYIMAGEPROC) eglGetProcAddress("eglDestroyImage"); if (!g_egl_dynProcs.eglCreateImage) g_egl_dynProcs.eglCreateImage = (PFNEGLCREATEIMAGEPROC) eglGetProcAddress("eglCreateImageKHR"); if (!g_egl_dynProcs.eglDestroyImage) g_egl_dynProcs.eglDestroyImage = (PFNEGLDESTROYIMAGEPROC) eglGetProcAddress("eglDestroyImageKHR"); }; #endif looking-glass-B6/client/src/eglutil.c000066400000000000000000000043161434445012300177560ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "eglutil.h" #include "common/debug.h" #include "egl_dynprocs.h" #include "util.h" void swapWithDamageInit(struct SwapWithDamageData * data, EGLDisplay display) { const char *exts = eglQueryString(display, EGL_EXTENSIONS); data->init = true; if (util_hasGLExt(exts, "EGL_KHR_swap_buffers_with_damage") && g_egl_dynProcs.eglSwapBuffersWithDamageKHR) { data->func = g_egl_dynProcs.eglSwapBuffersWithDamageKHR; DEBUG_INFO("Using EGL_KHR_swap_buffers_with_damage"); } else if (util_hasGLExt(exts, "EGL_EXT_swap_buffers_with_damage") && g_egl_dynProcs.eglSwapBuffersWithDamageEXT) { data->func = g_egl_dynProcs.eglSwapBuffersWithDamageEXT; DEBUG_INFO("Using EGL_EXT_swap_buffers_with_damage"); } else { data->func = NULL; DEBUG_INFO("Swapping buffers with damage: not supported"); } } void swapWithDamageDisable(struct SwapWithDamageData * data) { data->init = false; data->func = NULL; } void swapWithDamage(struct SwapWithDamageData * data, EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count) { if (!data->func || !count) { eglSwapBuffers(display, surface); return; } EGLint rects[count * 4]; for (int i = 0; i < count; ++i) { rects[i * 4 + 0] = damage[i].x; rects[i * 4 + 1] = damage[i].y; rects[i * 4 + 2] = damage[i].w; rects[i * 4 + 3] = damage[i].h; } data->func(display, surface, rects, count); } looking-glass-B6/client/src/gl_dynprocs.c000066400000000000000000000041741434445012300206360ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef ENABLE_OPENGL #include "gl_dynprocs.h" #include struct GLDynProcs g_gl_dynProcs = {0}; static void * getProcAddressGL(const char * name) { return (void *) glXGetProcAddressARB((const GLubyte *) name); } static void * getProcAddressGL2(const char * name, const char * backup) { void * func = getProcAddressGL(name); return func ? func : getProcAddressGL(backup); } void gl_dynProcsInit(void) { g_gl_dynProcs.glGenBuffers = getProcAddressGL2("glGenBuffers", "glGenBuffersARB"); g_gl_dynProcs.glBindBuffer = getProcAddressGL2("glBindBuffer", "glBindBufferARB"); g_gl_dynProcs.glBufferData = getProcAddressGL2("glBufferData", "glBufferDataARB"); g_gl_dynProcs.glBufferSubData = getProcAddressGL2("glBufferSubData", "glBufferSubDataARB"); g_gl_dynProcs.glDeleteBuffers = getProcAddressGL2("glDeleteBuffers", "glDeleteBuffersARB"); g_gl_dynProcs.glIsSync = getProcAddressGL("glIsSync"); g_gl_dynProcs.glFenceSync = getProcAddressGL("glFenceSync"); g_gl_dynProcs.glClientWaitSync = getProcAddressGL("glClientWaitSync"); g_gl_dynProcs.glDeleteSync = getProcAddressGL("glDeleteSync"); g_gl_dynProcs.glGenerateMipmap = getProcAddressGL("glGenerateMipmap"); if (!g_gl_dynProcs.glGenerateMipmap) g_gl_dynProcs.glGenerateMipmap = getProcAddressGL("glGenerateMipmapEXT"); }; #endif looking-glass-B6/client/src/kb.c000066400000000000000000000430131434445012300167020ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "kb.h" #include "cimgui.h" const uint32_t linux_to_ps2[KEY_MAX] = { [KEY_RESERVED] /* = USB 0 */ = 0x000000, [KEY_ESC] /* = USB 41 */ = 0x000001, [KEY_1] /* = USB 30 */ = 0x000002, [KEY_2] /* = USB 31 */ = 0x000003, [KEY_3] /* = USB 32 */ = 0x000004, [KEY_4] /* = USB 33 */ = 0x000005, [KEY_5] /* = USB 34 */ = 0x000006, [KEY_6] /* = USB 35 */ = 0x000007, [KEY_7] /* = USB 36 */ = 0x000008, [KEY_8] /* = USB 37 */ = 0x000009, [KEY_9] /* = USB 38 */ = 0x00000A, [KEY_0] /* = USB 39 */ = 0x00000B, [KEY_MINUS] /* = USB 45 */ = 0x00000C, [KEY_EQUAL] /* = USB 46 */ = 0x00000D, [KEY_BACKSPACE] /* = USB 42 */ = 0x00000E, [KEY_TAB] /* = USB 43 */ = 0x00000F, [KEY_Q] /* = USB 20 */ = 0x000010, [KEY_W] /* = USB 26 */ = 0x000011, [KEY_E] /* = USB 8 */ = 0x000012, [KEY_R] /* = USB 21 */ = 0x000013, [KEY_T] /* = USB 23 */ = 0x000014, [KEY_Y] /* = USB 28 */ = 0x000015, [KEY_U] /* = USB 24 */ = 0x000016, [KEY_I] /* = USB 12 */ = 0x000017, [KEY_O] /* = USB 18 */ = 0x000018, [KEY_P] /* = USB 19 */ = 0x000019, [KEY_LEFTBRACE] /* = USB 47 */ = 0x00001A, [KEY_RIGHTBRACE] /* = USB 48 */ = 0x00001B, [KEY_ENTER] /* = USB 40 */ = 0x00001C, [KEY_LEFTCTRL] /* = USB 224 */ = 0x00001D, [KEY_A] /* = USB 4 */ = 0x00001E, [KEY_S] /* = USB 22 */ = 0x00001F, [KEY_D] /* = USB 7 */ = 0x000020, [KEY_F] /* = USB 9 */ = 0x000021, [KEY_G] /* = USB 10 */ = 0x000022, [KEY_H] /* = USB 11 */ = 0x000023, [KEY_J] /* = USB 13 */ = 0x000024, [KEY_K] /* = USB 14 */ = 0x000025, [KEY_L] /* = USB 15 */ = 0x000026, [KEY_SEMICOLON] /* = USB 51 */ = 0x000027, [KEY_APOSTROPHE] /* = USB 52 */ = 0x000028, [KEY_GRAVE] /* = USB 53 */ = 0x000029, [KEY_LEFTSHIFT] /* = USB 225 */ = 0x00002A, [KEY_BACKSLASH] /* = USB 49 */ = 0x00002B, [KEY_Z] /* = USB 29 */ = 0x00002C, [KEY_X] /* = USB 27 */ = 0x00002D, [KEY_C] /* = USB 6 */ = 0x00002E, [KEY_V] /* = USB 25 */ = 0x00002F, [KEY_B] /* = USB 5 */ = 0x000030, [KEY_N] /* = USB 17 */ = 0x000031, [KEY_M] /* = USB 16 */ = 0x000032, [KEY_COMMA] /* = USB 54 */ = 0x000033, [KEY_DOT] /* = USB 55 */ = 0x000034, [KEY_SLASH] /* = USB 56 */ = 0x000035, [KEY_RIGHTSHIFT] /* = USB 229 */ = 0x000036, [KEY_KPASTERISK] /* = USB 85 */ = 0x000037, [KEY_LEFTALT] /* = USB 226 */ = 0x000038, [KEY_SPACE] /* = USB 44 */ = 0x000039, [KEY_CAPSLOCK] /* = USB 57 */ = 0x00003A, [KEY_F1] /* = USB 58 */ = 0x00003B, [KEY_F2] /* = USB 59 */ = 0x00003C, [KEY_F3] /* = USB 60 */ = 0x00003D, [KEY_F4] /* = USB 61 */ = 0x00003E, [KEY_F5] /* = USB 62 */ = 0x00003F, [KEY_F6] /* = USB 63 */ = 0x000040, [KEY_F7] /* = USB 64 */ = 0x000041, [KEY_F8] /* = USB 65 */ = 0x000042, [KEY_F9] /* = USB 66 */ = 0x000043, [KEY_F10] /* = USB 67 */ = 0x000044, [KEY_NUMLOCK] /* = USB 83 */ = 0x000045, [KEY_SCROLLLOCK] /* = USB 71 */ = 0x000046, [KEY_KP7] /* = USB 95 */ = 0x000047, [KEY_KP8] /* = USB 96 */ = 0x000048, [KEY_KP9] /* = USB 97 */ = 0x000049, [KEY_KPMINUS] /* = USB 86 */ = 0x00004A, [KEY_KP4] /* = USB 92 */ = 0x00004B, [KEY_KP5] /* = USB 93 */ = 0x00004C, [KEY_KP6] /* = USB 94 */ = 0x00004D, [KEY_KPPLUS] /* = USB 87 */ = 0x00004E, [KEY_KP1] /* = USB 89 */ = 0x00004F, [KEY_KP2] /* = USB 90 */ = 0x000050, [KEY_KP3] /* = USB 91 */ = 0x000051, [KEY_KP0] /* = USB 98 */ = 0x000052, [KEY_KPDOT] /* = USB 99 */ = 0x000053, [KEY_102ND] /* = USB 100 */ = 0x000056, [KEY_F11] /* = USB 68 */ = 0x000057, [KEY_F12] /* = USB 69 */ = 0x000058, [KEY_RO] /* = USB 135 */ = 0x000073, [KEY_HENKAN] /* = USB 138 */ = 0x000079, [KEY_KATAKANAHIRAGANA] /* = USB 136 */ = 0x000070, [KEY_MUHENKAN] /* = USB 139 */ = 0x00007B, [KEY_KPENTER] /* = USB 88 */ = 0x00E01C, [KEY_RIGHTCTRL] /* = USB 228 */ = 0x00E01D, [KEY_KPSLASH] /* = USB 84 */ = 0x00E035, [KEY_SYSRQ] /* = USB 70 */ = 0x00E037, [KEY_RIGHTALT] /* = USB 230 */ = 0x00E038, [KEY_HOME] /* = USB 74 */ = 0x00E047, [KEY_UP] /* = USB 82 */ = 0x00E048, [KEY_PAGEUP] /* = USB 75 */ = 0x00E049, [KEY_LEFT] /* = USB 80 */ = 0x00E04B, [KEY_RIGHT] /* = USB 79 */ = 0x00E04D, [KEY_END] /* = USB 77 */ = 0x00E04F, [KEY_DOWN] /* = USB 81 */ = 0x00E050, [KEY_PAGEDOWN] /* = USB 78 */ = 0x00E051, [KEY_INSERT] /* = USB 73 */ = 0x00E052, [KEY_DELETE] /* = USB 76 */ = 0x00E053, [KEY_KPEQUAL] /* = USB 103 */ = 0x000059, [KEY_PAUSE] /* = USB 72 */ = 0x00E046, [KEY_KPCOMMA] /* = USB 133 */ = 0x00007E, [KEY_HANGEUL] /* = USB 144 */ = 0x0000F2, [KEY_HANJA] /* = USB 145 */ = 0x0000F1, [KEY_YEN] /* = USB 137 */ = 0x00007D, [KEY_LEFTMETA] /* = USB 227 */ = 0x00E05B, [KEY_RIGHTMETA] /* = USB 231 */ = 0x00E05C, [KEY_COMPOSE] /* = USB 101 */ = 0x00E05D, [KEY_F13] /* = USB 104 */ = 0x00005D, [KEY_F14] /* = USB 105 */ = 0x00005E, [KEY_F15] /* = USB 106 */ = 0x00005F, [KEY_PRINT] /* = USB 70 */ = 0x00E037, [KEY_MUTE] /* = USB 127 */ = 0x00E020, [KEY_VOLUMEUP] /* = USB 128 */ = 0x00E030, [KEY_VOLUMEDOWN] /* = USB 129 */ = 0x00E02E, [KEY_NEXTSONG] /* = USB 235 */ = 0x00E019, [KEY_PLAYPAUSE] /* = USB 232 */ = 0x00E022, [KEY_PREVIOUSSONG] /* = USB 234 */ = 0x00E010, [KEY_STOPCD] /* = USB 233 */ = 0x00E024, }; const char * linux_to_str[KEY_MAX] = { [KEY_RESERVED] = "KEY_RESERVED", [KEY_ESC] = "KEY_ESC", [KEY_1] = "KEY_1", [KEY_2] = "KEY_2", [KEY_3] = "KEY_3", [KEY_4] = "KEY_4", [KEY_5] = "KEY_5", [KEY_6] = "KEY_6", [KEY_7] = "KEY_7", [KEY_8] = "KEY_8", [KEY_9] = "KEY_9", [KEY_0] = "KEY_0", [KEY_MINUS] = "KEY_MINUS", [KEY_EQUAL] = "KEY_EQUAL", [KEY_BACKSPACE] = "KEY_BACKSPACE", [KEY_TAB] = "KEY_TAB", [KEY_Q] = "KEY_Q", [KEY_W] = "KEY_W", [KEY_E] = "KEY_E", [KEY_R] = "KEY_R", [KEY_T] = "KEY_T", [KEY_Y] = "KEY_Y", [KEY_U] = "KEY_U", [KEY_I] = "KEY_I", [KEY_O] = "KEY_O", [KEY_P] = "KEY_P", [KEY_LEFTBRACE] = "KEY_LEFTBRACE", [KEY_RIGHTBRACE] = "KEY_RIGHTBRACE", [KEY_ENTER] = "KEY_ENTER", [KEY_LEFTCTRL] = "KEY_LEFTCTRL", [KEY_A] = "KEY_A", [KEY_S] = "KEY_S", [KEY_D] = "KEY_D", [KEY_F] = "KEY_F", [KEY_G] = "KEY_G", [KEY_H] = "KEY_H", [KEY_J] = "KEY_J", [KEY_K] = "KEY_K", [KEY_L] = "KEY_L", [KEY_SEMICOLON] = "KEY_SEMICOLON", [KEY_APOSTROPHE] = "KEY_APOSTROPHE", [KEY_GRAVE] = "KEY_GRAVE", [KEY_LEFTSHIFT] = "KEY_LEFTSHIFT", [KEY_BACKSLASH] = "KEY_BACKSLASH", [KEY_Z] = "KEY_Z", [KEY_X] = "KEY_X", [KEY_C] = "KEY_C", [KEY_V] = "KEY_V", [KEY_B] = "KEY_B", [KEY_N] = "KEY_N", [KEY_M] = "KEY_M", [KEY_COMMA] = "KEY_COMMA", [KEY_DOT] = "KEY_DOT", [KEY_SLASH] = "KEY_SLASH", [KEY_RIGHTSHIFT] = "KEY_RIGHTSHIFT", [KEY_KPASTERISK] = "KEY_KPASTERISK", [KEY_LEFTALT] = "KEY_LEFTALT", [KEY_SPACE] = "KEY_SPACE", [KEY_CAPSLOCK] = "KEY_CAPSLOCK", [KEY_F1] = "KEY_F1", [KEY_F2] = "KEY_F2", [KEY_F3] = "KEY_F3", [KEY_F4] = "KEY_F4", [KEY_F5] = "KEY_F5", [KEY_F6] = "KEY_F6", [KEY_F7] = "KEY_F7", [KEY_F8] = "KEY_F8", [KEY_F9] = "KEY_F9", [KEY_F10] = "KEY_F10", [KEY_NUMLOCK] = "KEY_NUMLOCK", [KEY_SCROLLLOCK] = "KEY_SCROLLLOCK", [KEY_KP7] = "KEY_KP7", [KEY_KP8] = "KEY_KP8", [KEY_KP9] = "KEY_KP9", [KEY_KPMINUS] = "KEY_KPMINUS", [KEY_KP4] = "KEY_KP4", [KEY_KP5] = "KEY_KP5", [KEY_KP6] = "KEY_KP6", [KEY_KPPLUS] = "KEY_KPPLUS", [KEY_KP1] = "KEY_KP1", [KEY_KP2] = "KEY_KP2", [KEY_KP3] = "KEY_KP3", [KEY_KP0] = "KEY_KP0", [KEY_KPDOT] = "KEY_KPDOT", [KEY_102ND] = "KEY_102ND", [KEY_F11] = "KEY_F11", [KEY_F12] = "KEY_F12", [KEY_RO] = "KEY_RO", [KEY_HENKAN] = "KEY_HENKAN", [KEY_KATAKANAHIRAGANA] = "KEY_KATAKANAHIRAGANA", [KEY_MUHENKAN] = "KEY_MUHENKAN", [KEY_KPENTER] = "KEY_KPENTER", [KEY_RIGHTCTRL] = "KEY_RIGHTCTRL", [KEY_KPSLASH] = "KEY_KPSLASH", [KEY_SYSRQ] = "KEY_SYSRQ", [KEY_RIGHTALT] = "KEY_RIGHTALT", [KEY_HOME] = "KEY_HOME", [KEY_UP] = "KEY_UP", [KEY_PAGEUP] = "KEY_PAGEUP", [KEY_LEFT] = "KEY_LEFT", [KEY_RIGHT] = "KEY_RIGHT", [KEY_END] = "KEY_END", [KEY_DOWN] = "KEY_DOWN", [KEY_PAGEDOWN] = "KEY_PAGEDOWN", [KEY_INSERT] = "KEY_INSERT", [KEY_DELETE] = "KEY_DELETE", [KEY_KPEQUAL] = "KEY_KPEQUAL", [KEY_PAUSE] = "KEY_PAUSE", [KEY_KPCOMMA] = "KEY_KPCOMMA", [KEY_HANGEUL] = "KEY_HANGEUL", [KEY_HANJA] = "KEY_HANJA", [KEY_YEN] = "KEY_YEN", [KEY_LEFTMETA] = "KEY_LEFTMETA", [KEY_RIGHTMETA] = "KEY_RIGHTMETA", [KEY_COMPOSE] = "KEY_COMPOSE", [KEY_F13] = "KEY_F13", [KEY_F14] = "KEY_F14", [KEY_F15] = "KEY_F15", [KEY_PRINT] = "KEY_PRINT", [KEY_MUTE] = "KEY_MUTE", [KEY_VOLUMEUP] = "KEY_VOLUMEUP", [KEY_VOLUMEDOWN] = "KEY_VOLUMEDOWN", [KEY_NEXTSONG] = "KEY_NEXTSONG", [KEY_PLAYPAUSE] = "KEY_PLAYPAUSE", [KEY_PREVIOUSSONG] = "KEY_PREVIOUSSONG", [KEY_STOPCD] = "KEY_STOPCD", }; const char * linux_to_display[KEY_MAX] = { [KEY_RESERVED] = "Reserved", [KEY_ESC] = "Esc", [KEY_1] = "1", [KEY_2] = "2", [KEY_3] = "3", [KEY_4] = "4", [KEY_5] = "5", [KEY_6] = "6", [KEY_7] = "7", [KEY_8] = "8", [KEY_9] = "9", [KEY_0] = "0", [KEY_MINUS] = "-", [KEY_EQUAL] = "=", [KEY_BACKSPACE] = "Backspace", [KEY_TAB] = "Tab", [KEY_Q] = "Q", [KEY_W] = "W", [KEY_E] = "E", [KEY_R] = "R", [KEY_T] = "T", [KEY_Y] = "Y", [KEY_U] = "U", [KEY_I] = "I", [KEY_O] = "O", [KEY_P] = "P", [KEY_LEFTBRACE] = "{", [KEY_RIGHTBRACE] = "}", [KEY_ENTER] = "Enter", [KEY_LEFTCTRL] = "LCtrl", [KEY_A] = "A", [KEY_S] = "S", [KEY_D] = "D", [KEY_F] = "F", [KEY_G] = "G", [KEY_H] = "H", [KEY_J] = "J", [KEY_K] = "K", [KEY_L] = "L", [KEY_SEMICOLON] = ";", [KEY_APOSTROPHE] = "'", [KEY_GRAVE] = "`", [KEY_LEFTSHIFT] = "LShift", [KEY_BACKSLASH] = "\\", [KEY_Z] = "Z", [KEY_X] = "X", [KEY_C] = "C", [KEY_V] = "V", [KEY_B] = "B", [KEY_N] = "N", [KEY_M] = "M", [KEY_COMMA] = ",", [KEY_DOT] = ".", [KEY_SLASH] = "/", [KEY_RIGHTSHIFT] = "RShift", [KEY_KPASTERISK] = "*", [KEY_LEFTALT] = "LAlt", [KEY_SPACE] = "Space", [KEY_CAPSLOCK] = "CapsLock", [KEY_F1] = "F1", [KEY_F2] = "F2", [KEY_F3] = "F3", [KEY_F4] = "F4", [KEY_F5] = "F5", [KEY_F6] = "F6", [KEY_F7] = "F7", [KEY_F8] = "F8", [KEY_F9] = "F9", [KEY_F10] = "F10", [KEY_NUMLOCK] = "NumLock", [KEY_SCROLLLOCK] = "ScrollLock", [KEY_KP7] = "KP7", [KEY_KP8] = "KP8", [KEY_KP9] = "KP9", [KEY_KPMINUS] = "KPMinus", [KEY_KP4] = "KP4", [KEY_KP5] = "KP5", [KEY_KP6] = "KP6", [KEY_KPPLUS] = "KPPlus", [KEY_KP1] = "KP1", [KEY_KP2] = "KP2", [KEY_KP3] = "KP3", [KEY_KP0] = "KP0", [KEY_KPDOT] = "KPDOT", [KEY_102ND] = "102ND", [KEY_F11] = "F11", [KEY_F12] = "F12", [KEY_RO] = "RO", [KEY_HENKAN] = "Henkan", [KEY_KATAKANAHIRAGANA] = "Kana", [KEY_MUHENKAN] = "Muhenkan", [KEY_KPENTER] = "KPEnter", [KEY_RIGHTCTRL] = "RCtrl", [KEY_KPSLASH] = "KPSlash", [KEY_SYSRQ] = "SysRQ", [KEY_RIGHTALT] = "RAlt", [KEY_HOME] = "Home", [KEY_UP] = "↑", [KEY_PAGEUP] = "PageUp", [KEY_LEFT] = "←", [KEY_RIGHT] = "→", [KEY_END] = "End", [KEY_DOWN] = "↓", [KEY_PAGEDOWN] = "PageDown", [KEY_INSERT] = "Insert", [KEY_DELETE] = "Delete", [KEY_KPEQUAL] = "KPEqual", [KEY_PAUSE] = "Pause", [KEY_KPCOMMA] = "KPComma", [KEY_HANGEUL] = "Hangul", [KEY_HANJA] = "Hanja", [KEY_YEN] = "Yen", [KEY_LEFTMETA] = "LWin", [KEY_RIGHTMETA] = "RWin", [KEY_COMPOSE] = "Compose", [KEY_F13] = "F13", [KEY_F14] = "F14", [KEY_F15] = "F15", [KEY_PRINT] = "Print", [KEY_MUTE] = "Mute", [KEY_VOLUMEUP] = "VolumeUp", [KEY_VOLUMEDOWN] = "VolumeDown", [KEY_NEXTSONG] = "NextSong", [KEY_PLAYPAUSE] = "PlayPause", [KEY_PREVIOUSSONG] = "PreviousSong", [KEY_STOPCD] = "StopMedia", }; void initImGuiKeyMap(int * keymap) { keymap[ImGuiKey_Tab ] = KEY_TAB; keymap[ImGuiKey_LeftArrow ] = KEY_LEFT; keymap[ImGuiKey_RightArrow ] = KEY_RIGHT; keymap[ImGuiKey_UpArrow ] = KEY_UP; keymap[ImGuiKey_DownArrow ] = KEY_DOWN; keymap[ImGuiKey_PageUp ] = KEY_PAGEUP; keymap[ImGuiKey_PageDown ] = KEY_PAGEDOWN; keymap[ImGuiKey_Home ] = KEY_HOME; keymap[ImGuiKey_End ] = KEY_END; keymap[ImGuiKey_Insert ] = KEY_INSERT; keymap[ImGuiKey_Delete ] = KEY_DELETE; keymap[ImGuiKey_Backspace ] = KEY_BACKSPACE; keymap[ImGuiKey_Space ] = KEY_SPACE; keymap[ImGuiKey_Enter ] = KEY_ENTER; keymap[ImGuiKey_Escape ] = KEY_SPACE; keymap[ImGuiKey_KeypadEnter] = KEY_KPENTER; keymap[ImGuiKey_A ] = KEY_A; keymap[ImGuiKey_C ] = KEY_C; keymap[ImGuiKey_V ] = KEY_V; keymap[ImGuiKey_X ] = KEY_X; keymap[ImGuiKey_Y ] = KEY_Y; keymap[ImGuiKey_Z ] = KEY_Z; } looking-glass-B6/client/src/kb.h000066400000000000000000000022341434445012300167070ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_KB_ #define _H_LG_KB_ #include #include #define PS2_MUTE 0xE020 #define PS2_VOLUME_UP 0xE030 #define PS2_VOLUME_DOWN 0xE02E extern const uint32_t linux_to_ps2[KEY_MAX]; extern const char * linux_to_str[KEY_MAX]; extern const char * linux_to_display[KEY_MAX]; void initImGuiKeyMap(int * keymap); #endif looking-glass-B6/client/src/keybind.c000066400000000000000000000155601434445012300177410ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "keybind.h" #include "main.h" #include "app.h" #include "audio.h" #include "core.h" #include "kb.h" #include #include static void bind_fullscreen(int sc, void * opaque) { app_setFullscreen(!app_getFullscreen()); } static void bind_video(int sc, void * opaque) { app_stopVideo(!g_state.stopVideo); } static void bind_rotate(int sc, void * opaque) { if (g_params.winRotate == LG_ROTATE_MAX-1) g_params.winRotate = 0; else ++g_params.winRotate; core_updatePositionInfo(); } static void bind_input(int sc, void * opaque) { g_state.ignoreInput = !g_state.ignoreInput; if (g_state.ignoreInput) core_setCursorInView(false); else g_state.ds->realignPointer(); app_alert( LG_ALERT_INFO, g_state.ignoreInput ? "Input Disabled" : "Input Enabled" ); } static void bind_quit(int sc, void * opaque) { g_state.state = APP_STATE_SHUTDOWN; } static void bind_mouseSens(int sc, void * opaque) { bool inc = (bool)opaque; if (inc) { if (g_cursor.sens < 9) ++g_cursor.sens; } else { if (g_cursor.sens > -9) --g_cursor.sens; } char msg[20]; snprintf(msg, sizeof(msg), "Sensitivity: %s%d", g_cursor.sens > 0 ? "+" : "", g_cursor.sens); app_alert( LG_ALERT_INFO, msg ); } static void bind_ctrlAltFn(int sc, void * opaque) { const uint32_t ctrl = linux_to_ps2[KEY_LEFTCTRL]; const uint32_t alt = linux_to_ps2[KEY_LEFTALT ]; const uint32_t fn = linux_to_ps2[sc]; purespice_keyDown(ctrl); purespice_keyDown(alt ); purespice_keyDown(fn ); purespice_keyUp(ctrl); purespice_keyUp(alt ); purespice_keyUp(fn ); } static void bind_passthrough(int sc, void * opaque) { sc = linux_to_ps2[sc]; purespice_keyDown(sc); purespice_keyUp (sc); } static void bind_toggleOverlay(int sc, void * opaque) { app_setOverlay(!g_state.overlayInput); } static void bind_toggleKey(int sc, void * opaque) { purespice_keyDown((uintptr_t) opaque); purespice_keyUp((uintptr_t) opaque); } void keybind_commonRegister(void) { app_registerKeybind(0, 'F', bind_fullscreen , NULL, "Full screen toggle"); app_registerKeybind(0, 'V', bind_video , NULL, "Video stream toggle"); app_registerKeybind(0, 'R', bind_rotate , NULL, "Rotate the output clockwise by 90° increments"); app_registerKeybind(0, 'Q', bind_quit , NULL, "Quit"); app_registerKeybind(0, 'O', bind_toggleOverlay, NULL, "Toggle overlay"); } #if ENABLE_AUDIO static void bind_toggleMicDefault(int sc, void * opaque) { g_state.micDefaultState = (g_state.micDefaultState + 1) % MIC_DEFAULT_MAX; switch (g_state.micDefaultState) { case MIC_DEFAULT_PROMPT: app_alert(LG_ALERT_INFO, "Microphone access will prompt"); break; case MIC_DEFAULT_ALLOW: app_alert(LG_ALERT_INFO, "Microphone access allowed by default"); break; case MIC_DEFAULT_DENY: app_alert(LG_ALERT_INFO, "Microphone access denied by default"); } } #endif void keybind_spiceRegister(void) { /* register the common keybinds for spice */ static bool firstTime = true; if (firstTime) { app_registerKeybind(0, 'I', bind_input, NULL, "Spice keyboard & mouse toggle"); app_registerKeybind(KEY_INSERT, 0, bind_mouseSens, (void *) true , "Increase mouse sensitivity in capture mode"); app_registerKeybind(KEY_DELETE, 0, bind_mouseSens, (void *) false, "Descrease mouse sensitivity in capture mode"); app_registerKeybind(KEY_UP , 0 , bind_toggleKey, (void *) PS2_VOLUME_UP , "Send volume up to the guest"); app_registerKeybind(KEY_DOWN, 0 , bind_toggleKey, (void *) PS2_VOLUME_DOWN, "Send volume down to the guest"); app_registerKeybind(0 , 'M', bind_toggleKey, (void *) PS2_MUTE , "Send mute to the guest"); app_registerKeybind(KEY_LEFTMETA , 0, bind_passthrough, NULL, "Send LWin to the guest"); app_registerKeybind(KEY_RIGHTMETA, 0, bind_passthrough, NULL, "Send RWin to the guest"); #if ENABLE_AUDIO if (audio_supportsRecord()) { app_registerKeybind(0, 'E', audio_recordToggleKeybind, NULL, "Toggle audio recording"); app_registerKeybind(0, 'C', bind_toggleMicDefault, NULL, "Cycle audio recording default"); } #endif firstTime = false; } /* release any OS based keybinds that have been bound */ static KeybindHandle handles[32] = { 0 }; // increase size as needed static int handleCount = 0; for(int i = 0; i < handleCount; ++i) app_releaseKeybind(&handles[i]); handleCount = 0; /* register OS based keybinds */ if (app_guestIsLinux()) { handles[handleCount++] = app_registerKeybind(KEY_F1 , 0, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F1 to the guest"); handles[handleCount++] = app_registerKeybind(KEY_F2 , 0, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F2 to the guest"); handles[handleCount++] = app_registerKeybind(KEY_F3 , 0, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F3 to the guest"); handles[handleCount++] = app_registerKeybind(KEY_F4 , 0, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F4 to the guest"); handles[handleCount++] = app_registerKeybind(KEY_F5 , 0, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F5 to the guest"); handles[handleCount++] = app_registerKeybind(KEY_F6 , 0, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F6 to the guest"); handles[handleCount++] = app_registerKeybind(KEY_F7 , 0, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F7 to the guest"); handles[handleCount++] = app_registerKeybind(KEY_F8 , 0, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F8 to the guest"); handles[handleCount++] = app_registerKeybind(KEY_F9 , 0, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F9 to the guest"); handles[handleCount++] = app_registerKeybind(KEY_F10, 0, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F10 to the guest"); handles[handleCount++] = app_registerKeybind(KEY_F11, 0, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F11 to the guest"); handles[handleCount++] = app_registerKeybind(KEY_F12, 0, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F12 to the guest"); } } looking-glass-B6/client/src/keybind.h000066400000000000000000000016571434445012300177500ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_KEYBIND_ #define _H_LG_KEYBIND_ void keybind_commonRegister(void); void keybind_spiceRegister(void); #endif looking-glass-B6/client/src/lg-renderer.c000066400000000000000000000015341434445012300205160ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include looking-glass-B6/client/src/main.c000066400000000000000000001345241434445012300172420ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "main.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common/array.h" #include "common/debug.h" #include "common/crash.h" #include "common/KVMFR.h" #include "common/stringutils.h" #include "common/thread.h" #include "common/locking.h" #include "common/event.h" #include "common/ivshmem.h" #include "common/time.h" #include "common/version.h" #include "common/paths.h" #include "common/cpuinfo.h" #include "common/ll.h" #include "core.h" #include "app.h" #include "audio.h" #include "keybind.h" #include "clipboard.h" #include "kb.h" #include "egl_dynprocs.h" #include "gl_dynprocs.h" #include "overlays.h" #include "overlay_utils.h" #include "util.h" #include "render_queue.h" // forwards static int renderThread(void * unused); static LGEvent *e_startup = NULL; static LGEvent *e_spice = NULL; static LGThread *t_spice = NULL; static LGThread *t_render = NULL; struct AppState g_state = { 0 }; struct CursorState g_cursor; // this structure is initialized in config.c struct AppParams g_params = { 0 }; static void lgInit(void) { g_state.formatValid = false; g_state.resizeDone = true; core_setCursorInView(false); if (g_cursor.grab) core_setGrab(false); g_cursor.useScale = false; g_cursor.scale.x = 1.0; g_cursor.scale.y = 1.0; g_cursor.draw = false; g_cursor.inView = false; g_cursor.guest.valid = false; // if spice is not in use, hide the local cursor if ((!g_params.useSpiceInput && g_params.hideMouse) || !g_params.showCursorDot) g_state.ds->setPointer(LG_POINTER_NONE); else g_state.ds->setPointer(LG_POINTER_SQUARE); } static bool fpsTimerFn(void * unused) { static uint64_t last; if (!last) { last = nanotime(); return true; } const uint64_t renderCount = atomic_exchange_explicit(&g_state.renderCount, 0, memory_order_acquire); float fps, ups; if (renderCount > 0) { const uint64_t frameCount = atomic_exchange_explicit(&g_state.frameCount, 0, memory_order_acquire); const uint64_t time = nanotime(); const uint64_t elapsedNs = time - last; const float elapsedMs = (float)elapsedNs / 1e6f; last = time; fps = 1e3f / (elapsedMs / (float)renderCount); ups = 1e3f / (elapsedMs / (float)frameCount); } else { last = nanotime(); fps = 0.0f; ups = 0.0f; } atomic_store_explicit(&g_state.fps, fps, memory_order_relaxed); atomic_store_explicit(&g_state.ups, ups, memory_order_relaxed); return true; } static bool tickTimerFn(void * unused) { static unsigned long long tickCount = 0; bool needsRender = false; struct Overlay * overlay; ll_lock(g_state.overlays); ll_forEachNL(g_state.overlays, item, overlay) { if (overlay->ops->tick && overlay->ops->tick(overlay->udata, tickCount)) needsRender = true; } ll_unlock(g_state.overlays); if (needsRender) app_invalidateWindow(false); ++tickCount; return true; } static void preSwapCallback(void * udata) { const uint64_t * renderStart = (const uint64_t *)udata; ringbuffer_push(g_state.renderDuration, &(float) {(nanotime() - *renderStart) * 1e-6f}); } static int renderThread(void * unused) { if (!RENDERER(renderStartup, g_state.useDMA)) { DEBUG_ERROR("EGL render failed to start"); g_state.state = APP_STATE_SHUTDOWN; /* unblock threads waiting on the condition */ lgSignalEvent(e_startup); return 1; } if (g_state.lgr->ops.supports && !RENDERER(supports, LG_SUPPORTS_DMABUF)) g_state.useDMA = false; /* start up the fps timer */ LGTimer * fpsTimer; if (!lgCreateTimer(500, fpsTimerFn, NULL, &fpsTimer)) { DEBUG_ERROR("Failed to create the fps timer"); return 1; } app_initOverlays(); LGTimer * tickTimer; if (!lgCreateTimer(1000 / TICK_RATE, tickTimerFn, NULL, &tickTimer)) { lgTimerDestroy(fpsTimer); DEBUG_ERROR("Failed to create the tick timer"); return 1; } LG_LOCK_INIT(g_state.lgrLock); /* signal to other threads that the renderer is ready */ lgSignalEvent(e_startup); struct timespec time; clock_gettime(CLOCK_MONOTONIC, &time); while(g_state.state != APP_STATE_SHUTDOWN) { bool forceRender = false; if (g_state.jitRender) forceRender = g_state.ds->waitFrame(); app_handleRenderEvent(microtime()); if (g_state.jitRender) { const uint64_t pending = atomic_load_explicit(&g_state.pendingCount, memory_order_acquire); if (!lgResetEvent(g_state.frameEvent) && !forceRender && !pending && !app_overlayNeedsRender()) { if (g_state.ds->skipFrame) g_state.ds->skipFrame(); continue; } if (pending > 0) atomic_fetch_sub(&g_state.pendingCount, 1); } else if (g_params.fpsMin != 0) { float ups = atomic_load_explicit(&g_state.ups, memory_order_relaxed); if (!lgWaitEventAbs(g_state.frameEvent, &time) || ups > g_params.fpsMin) { /* only update the time if we woke up early */ clock_gettime(CLOCK_MONOTONIC, &time); tsAdd(&time, app_isOverlayMode() ? g_state.overlayFrameTime : g_state.frameTime); } } int resize = atomic_load(&g_state.lgrResize); if (resize) { g_state.io->DisplaySize = (ImVec2) { .x = g_state.windowW, .y = g_state.windowH, }; g_state.io->DisplayFramebufferScale = (ImVec2) { .x = g_state.windowScale, .y = g_state.windowScale, }; g_state.io->FontGlobalScale = 1.0f / g_state.windowScale; ImFontAtlas_Clear(g_state.io->Fonts); ImFontAtlas_AddFontFromFileTTF(g_state.io->Fonts, g_state.fontName, g_params.uiSize * g_state.windowScale, NULL, g_state.fontRange.Data); g_state.fontLarge = ImFontAtlas_AddFontFromFileTTF(g_state.io->Fonts, g_state.fontName, 1.3f * g_params.uiSize * g_state.windowScale, NULL, g_state.fontRange.Data); if (!ImFontAtlas_Build(g_state.io->Fonts)) DEBUG_FATAL("Failed to build font atlas: %s (%s)", g_params.uiFont, g_state.fontName); if (g_state.lgr) RENDERER(onResize, g_state.windowW, g_state.windowH, g_state.windowScale, g_state.dstRect, g_params.winRotate); atomic_compare_exchange_weak(&g_state.lgrResize, &resize, 0); } static uint64_t lastFrameCount = 0; const uint64_t frameCount = atomic_load_explicit(&g_state.frameCount, memory_order_relaxed); const bool newFrame = frameCount != lastFrameCount; lastFrameCount = frameCount; const bool invalidate = atomic_exchange(&g_state.invalidateWindow, false); const uint64_t renderStart = nanotime(); LG_LOCK(g_state.lgrLock); renderQueue_process(); if (!RENDERER(render, g_params.winRotate, newFrame, invalidate, preSwapCallback, (void *)&renderStart)) { LG_UNLOCK(g_state.lgrLock); break; } LG_UNLOCK(g_state.lgrLock); const uint64_t t = nanotime(); const uint64_t delta = t - g_state.lastRenderTime; g_state.lastRenderTime = t; atomic_fetch_add_explicit(&g_state.renderCount, 1, memory_order_relaxed); if (g_state.lastRenderTimeValid) { const float fdelta = (float)delta / 1e6f; ringbuffer_push(g_state.renderTimings, &fdelta); } g_state.lastRenderTimeValid = true; const uint64_t now = microtime(); if (!g_state.resizeDone && g_state.resizeTimeout < now) { if (g_params.autoResize) { g_state.ds->setWindowSize( g_state.dstRect.w, g_state.dstRect.h ); } g_state.resizeDone = true; } } g_state.state = APP_STATE_SHUTDOWN; if (g_state.overlays) { app_freeOverlays(); ll_free(g_state.overlays); g_state.overlays = NULL; } lgTimerDestroy(tickTimer); lgTimerDestroy(fpsTimer); core_stopCursorThread(); core_stopFrameThread(); RENDERER(deinitialize); g_state.lgr = NULL; LG_LOCK_FREE(g_state.lgrLock); return 0; } int main_cursorThread(void * unused) { LGMP_STATUS status; LG_RendererCursor cursorType = LG_CURSOR_COLOR; KVMFRCursor * cursor = NULL; int cursorSize = 0; lgWaitEvent(e_startup, TIMEOUT_INFINITE); // subscribe to the pointer queue while(g_state.state == APP_STATE_RUNNING) { status = lgmpClientSubscribe(g_state.lgmp, LGMP_Q_POINTER, &g_state.pointerQueue); if (status == LGMP_OK) break; if (status == LGMP_ERR_NO_SUCH_QUEUE) { usleep(1000); continue; } DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status)); g_state.state = APP_STATE_SHUTDOWN; break; } while(g_state.state == APP_STATE_RUNNING && !g_state.stopVideo) { LGMPMessage msg; if ((status = lgmpClientProcess(g_state.pointerQueue, &msg)) != LGMP_OK) { if (status == LGMP_ERR_QUEUE_EMPTY) { if (g_cursor.redraw && g_cursor.guest.valid) { g_cursor.redraw = false; RENDERER(onMouseEvent, g_cursor.guest.visible && (g_cursor.draw || !g_params.useSpiceInput), g_cursor.guest.x, g_cursor.guest.y, g_cursor.guest.hx, g_cursor.guest.hy ); if (!g_state.stopVideo) lgSignalEvent(g_state.frameEvent); } struct timespec req = { .tv_sec = 0, .tv_nsec = g_params.cursorPollInterval * 1000L }; struct timespec rem; while(nanosleep(&req, &rem) < 0) { if (errno != -EINTR) { DEBUG_ERROR("nanosleep failed"); break; } req = rem; } continue; } if (status == LGMP_ERR_INVALID_SESSION) g_state.state = APP_STATE_RESTART; else { DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status)); g_state.state = APP_STATE_SHUTDOWN; } break; } KVMFRCursor * tmp = (KVMFRCursor *)msg.mem; const int neededSize = sizeof(*tmp) + (msg.udata & CURSOR_FLAG_SHAPE ? tmp->height * tmp->pitch : 0); if (cursor && neededSize > cursorSize) { free(cursor); cursor = NULL; } /* copy and release the message ASAP */ if (!cursor) { cursor = malloc(neededSize); if (!cursor) { DEBUG_ERROR("failed to allocate %d bytes for cursor", neededSize); g_state.state = APP_STATE_SHUTDOWN; break; } cursorSize = neededSize; } memcpy(cursor, msg.mem, neededSize); lgmpClientMessageDone(g_state.pointerQueue); g_cursor.guest.visible = msg.udata & CURSOR_FLAG_VISIBLE; if (msg.udata & CURSOR_FLAG_SHAPE) { switch(cursor->type) { case CURSOR_TYPE_COLOR : cursorType = LG_CURSOR_COLOR ; break; case CURSOR_TYPE_MONOCHROME : cursorType = LG_CURSOR_MONOCHROME ; break; case CURSOR_TYPE_MASKED_COLOR: cursorType = LG_CURSOR_MASKED_COLOR; break; default: DEBUG_ERROR("Invalid cursor type"); continue; } g_cursor.guest.hx = cursor->hx; g_cursor.guest.hy = cursor->hy; const uint8_t * data = (const uint8_t *)(cursor + 1); if (!RENDERER(onMouseShape, cursorType, cursor->width, cursor->height, cursor->pitch, data) ) { DEBUG_ERROR("Failed to update mouse shape"); continue; } } if (msg.udata & CURSOR_FLAG_POSITION) { bool valid = g_cursor.guest.valid; g_cursor.guest.x = cursor->x; g_cursor.guest.y = cursor->y; g_cursor.guest.valid = true; // if the state just became valid if (valid != true && core_inputEnabled()) { core_alignToGuest(); app_resyncMouseBasic(); } // tell the DS there was an update core_handleGuestMouseUpdate(); } g_cursor.redraw = false; RENDERER(onMouseEvent, g_cursor.guest.visible && (g_cursor.draw || !g_params.useSpiceInput), g_cursor.guest.x, g_cursor.guest.y, g_cursor.guest.hx, g_cursor.guest.hy ); if (g_params.mouseRedraw && g_cursor.guest.visible && !g_state.stopVideo) lgSignalEvent(g_state.frameEvent); } LG_LOCK(g_state.pointerQueueLock); lgmpClientUnsubscribe(&g_state.pointerQueue); LG_UNLOCK(g_state.pointerQueueLock); if (cursor) { free(cursor); cursor = NULL; } return 0; } int main_frameThread(void * unused) { struct DMAFrameInfo { KVMFRFrame * frame; size_t dataSize; int fd; }; LGMP_STATUS status; PLGMPClientQueue queue; uint32_t frameSerial = 0; uint32_t formatVer = 0; size_t dataSize = 0; LG_RendererFormat lgrFormat; struct DMAFrameInfo dmaInfo[LGMP_Q_FRAME_LEN] = {0}; if (g_state.useDMA) DEBUG_INFO("Using DMA buffer support"); lgWaitEvent(e_startup, TIMEOUT_INFINITE); if (g_state.state != APP_STATE_RUNNING) return 0; // subscribe to the frame queue while(g_state.state == APP_STATE_RUNNING) { status = lgmpClientSubscribe(g_state.lgmp, LGMP_Q_FRAME, &queue); if (status == LGMP_OK) break; if (status == LGMP_ERR_NO_SUCH_QUEUE) { usleep(1000); continue; } DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status)); g_state.state = APP_STATE_SHUTDOWN; break; } g_state.ds->requestActivation(); while(g_state.state == APP_STATE_RUNNING && !g_state.stopVideo) { LGMPMessage msg; if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK) { if (status == LGMP_ERR_QUEUE_EMPTY) { struct timespec req = { .tv_sec = 0, .tv_nsec = g_params.framePollInterval * 1000L }; struct timespec rem; while(nanosleep(&req, &rem) < 0) { if (errno != -EINTR) { DEBUG_ERROR("nanosleep failed"); break; } req = rem; } continue; } if (status == LGMP_ERR_INVALID_SESSION) g_state.state = APP_STATE_RESTART; else { DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status)); g_state.state = APP_STATE_SHUTDOWN; } break; } KVMFRFrame * frame = (KVMFRFrame *)msg.mem; // ignore any repeated frames, this happens when a new client connects to // the same host application. if (frame->frameSerial == frameSerial && g_state.formatValid) { lgmpClientMessageDone(queue); continue; } frameSerial = frame->frameSerial; struct DMAFrameInfo *dma = NULL; if (!g_state.formatValid || frame->formatVer != formatVer) { // setup the renderer format with the frame format details lgrFormat.type = frame->type; lgrFormat.screenWidth = frame->screenWidth; lgrFormat.screenHeight = frame->screenHeight; lgrFormat.frameWidth = frame->frameWidth; lgrFormat.frameHeight = frame->frameHeight; lgrFormat.stride = frame->stride; lgrFormat.pitch = frame->pitch; if (frame->flags & FRAME_FLAG_TRUNCATED) { const float needed = ((frame->screenHeight * frame->pitch * 2) / 1048576.0f) + 10.0f; const int size = (int)powf(2.0f, ceilf(logf(needed) / logf(2.0f))); DEBUG_BREAK(); DEBUG_WARN("IVSHMEM too small, screen truncated"); DEBUG_WARN("Recommend increase size to %d MiB", size); DEBUG_BREAK(); app_msgBox( "IVSHMEM too small", "IVSHMEM too small\n" "Please increase the size to %d MiB", size); } switch(frame->rotation) { case FRAME_ROT_0 : lgrFormat.rotate = LG_ROTATE_0 ; break; case FRAME_ROT_90 : lgrFormat.rotate = LG_ROTATE_90 ; break; case FRAME_ROT_180: lgrFormat.rotate = LG_ROTATE_180; break; case FRAME_ROT_270: lgrFormat.rotate = LG_ROTATE_270; break; default: DEBUG_ERROR("Unsupported/invalid frame rotation"); lgrFormat.rotate = LG_ROTATE_0; break; } g_state.rotate = lgrFormat.rotate; bool error = false; switch(frame->type) { case FRAME_TYPE_RGBA: case FRAME_TYPE_BGRA: case FRAME_TYPE_RGBA10: dataSize = lgrFormat.frameHeight * lgrFormat.pitch; lgrFormat.bpp = 32; break; case FRAME_TYPE_RGBA16F: dataSize = lgrFormat.frameHeight * lgrFormat.pitch; lgrFormat.bpp = 64; break; default: DEBUG_ERROR("Unsupported frameType"); error = true; break; } if (error) { lgmpClientMessageDone(queue); g_state.state = APP_STATE_SHUTDOWN; break; } g_state.formatValid = true; formatVer = frame->formatVer; DEBUG_INFO("Format: %s %ux%u stride:%u pitch:%u rotation:%d", FrameTypeStr[frame->type], frame->frameWidth, frame->frameHeight, frame->stride, frame->pitch, frame->rotation); LG_LOCK(g_state.lgrLock); if (!RENDERER(onFrameFormat, lgrFormat)) { DEBUG_ERROR("renderer failed to configure format"); g_state.state = APP_STATE_SHUTDOWN; LG_UNLOCK(g_state.lgrLock); break; } LG_UNLOCK(g_state.lgrLock); g_state.srcSize.x = lgrFormat.screenWidth; g_state.srcSize.y = lgrFormat.screenHeight; g_state.haveSrcSize = true; if (g_params.autoResize) g_state.ds->setWindowSize(lgrFormat.frameWidth, lgrFormat.frameHeight); core_updatePositionInfo(); } if (g_state.useDMA) { /* find the existing dma buffer if it exists */ for(int i = 0; i < ARRAY_LENGTH(dmaInfo); ++i) { if (dmaInfo[i].frame == frame) { dma = &dmaInfo[i]; /* if it's too small close it */ if (dma->dataSize < dataSize) { close(dma->fd); dma->fd = -1; } break; } } /* otherwise find a free buffer for use */ if (!dma) for(int i = 0; i < ARRAY_LENGTH(dmaInfo); ++i) { if (!dmaInfo[i].frame) { dma = &dmaInfo[i]; dma->frame = frame; dma->fd = -1; break; } } /* open the buffer */ if (dma->fd == -1) { const uintptr_t pos = (uintptr_t)msg.mem - (uintptr_t)g_state.shm.mem; const uintptr_t offset = (uintptr_t)frame->offset + sizeof(FrameBuffer); dma->dataSize = dataSize; dma->fd = ivshmemGetDMABuf(&g_state.shm, pos + offset, dataSize); if (dma->fd < 0) { DEBUG_ERROR("Failed to get the DMA buffer for the frame"); g_state.state = APP_STATE_SHUTDOWN; break; } } } FrameBuffer * fb = (FrameBuffer *)(((uint8_t*)frame) + frame->offset); if (!RENDERER(onFrame, fb, g_state.useDMA ? dma->fd : -1, frame->damageRects, frame->damageRectsCount)) { lgmpClientMessageDone(queue); DEBUG_ERROR("renderer on frame returned failure"); g_state.state = APP_STATE_SHUTDOWN; break; } overlaySplash_show(false); if (frame->flags & FRAME_FLAG_REQUEST_ACTIVATION) g_state.ds->requestActivation(); const bool blockScreensaver = frame->flags & FRAME_FLAG_BLOCK_SCREENSAVER; if (g_params.autoScreensaver && g_state.autoIdleInhibitState != blockScreensaver) { if (blockScreensaver) g_state.ds->inhibitIdle(); else g_state.ds->uninhibitIdle(); g_state.autoIdleInhibitState = blockScreensaver; } const uint64_t t = nanotime(); const uint64_t delta = t - g_state.lastFrameTime; g_state.lastFrameTime = t; if (g_state.lastFrameTimeValid) ringbuffer_push(g_state.uploadTimings, &(float) { delta * 1e-6f }); g_state.lastFrameTimeValid = true; atomic_fetch_add_explicit(&g_state.frameCount, 1, memory_order_relaxed); if (g_state.jitRender) { if (atomic_load_explicit(&g_state.pendingCount, memory_order_acquire) < 10) atomic_fetch_add_explicit(&g_state.pendingCount, 1, memory_order_release); } else lgSignalEvent(g_state.frameEvent); lgmpClientMessageDone(queue); // switch over to the LG stream app_useSpiceDisplay(false); } lgmpClientUnsubscribe(&queue); RENDERER(onRestart); if (g_state.state != APP_STATE_SHUTDOWN) { if (!app_useSpiceDisplay(true)) overlaySplash_show(true); } if (g_state.useDMA) { for(int i = 0; i < ARRAY_LENGTH(dmaInfo); ++i) if (dmaInfo[i].fd >= 0) close(dmaInfo[i].fd); } return 0; } static void checkUUID(void) { if (!g_state.spiceReady || !g_state.guestUUIDValid) return; if (memcmp(g_state.spiceUUID, g_state.guestUUID, sizeof(g_state.spiceUUID)) == 0) return; app_msgBox( "SPICE Configuration Error", "You have connected SPICE to the wrong guest.\n" "Input will not function until this is corrected."); g_params.useSpiceInput = false; g_state.spiceClose = true; purespice_disconnect(); } void spiceReady(void) { g_state.spiceReady = true; if (g_state.initialSpiceDisplay) app_useSpiceDisplay(true); // set the intial mouse mode purespice_mouseMode(true); PSServerInfo info; if (!purespice_getServerInfo(&info)) return; bool uuidValid = false; for(int i = 0; i < sizeof(info.uuid); ++i) if (info.uuid[i]) { uuidValid = true; break; } if (uuidValid) { memcpy(g_state.spiceUUID, info.uuid, sizeof(g_state.spiceUUID)); checkUUID(); } purespice_freeServerInfo(&info); if (g_params.useSpiceInput) keybind_spiceRegister(); lgSignalEvent(e_spice); } static void spice_surfaceCreate(unsigned int surfaceId, PSSurfaceFormat format, unsigned int width, unsigned int height) { DEBUG_INFO("Create SPICE surface: id: %d, size: %dx%d", surfaceId, width, height); g_state.srcSize.x = width; g_state.srcSize.y = height; g_state.haveSrcSize = true; core_updatePositionInfo(); renderQueue_spiceConfigure(width, height); renderQueue_spiceDrawFill(0, 0, width, height, 0x0); } static void spice_surfaceDestroy(unsigned int surfaceId) { DEBUG_INFO("Destroy spice surface %d", surfaceId); app_useSpiceDisplay(false); } static void spice_drawFill(unsigned int surfaceId, int x, int y, int width, int height, uint32_t color) { renderQueue_spiceDrawFill(x, y, width, height, color); } static void spice_drawBitmap(unsigned int surfaceId, PSBitmapFormat format, bool topDown, int x, int y, int width, int height, int stride, void * data) { renderQueue_spiceDrawBitmap(x, y, width, height, stride, data, topDown); } static void spice_setCursorRGBAImage(int width, int height, int hx, int hy, const void * data) { g_state.spiceHotX = hx; g_state.spiceHotY = hy; void * copied = malloc(width * height * 4); memcpy(copied, data, width * height * 4); renderQueue_cursorImage(false, width, height, width * 4, copied); } static void spice_setCursorMonoImage(int width, int height, int hx, int hy, const void * xorMask, const void * andMask) { g_state.spiceHotX = hx; g_state.spiceHotY = hy; int stride = (width + 7) / 8; uint8_t * buffer = malloc(stride * height * 2); memcpy(buffer, xorMask, stride * height); memcpy(buffer + stride * height, andMask, stride * height); renderQueue_cursorImage(true, width, height * 2, stride, buffer); } static void spice_setCursorState(bool visible, int x, int y) { renderQueue_cursorState(visible, x, y, g_state.spiceHotX, g_state.spiceHotY); } int spiceThread(void * arg) { if (g_params.useSpiceAudio) audio_init(); const struct PSConfig config = { .host = g_params.spiceHost, .port = g_params.spicePort, .password = "", .ready = spiceReady, .inputs = { .enable = g_params.useSpiceInput, .autoConnect = true }, .clipboard = { .enable = g_params.useSpiceClipboard, .notice = cb_spiceNotice, .data = cb_spiceData, .release = cb_spiceRelease, .request = cb_spiceRequest }, .display = { .enable = true, .autoConnect = false, .surfaceCreate = spice_surfaceCreate, .surfaceDestroy = spice_surfaceDestroy, .drawFill = spice_drawFill, .drawBitmap = spice_drawBitmap }, .cursor = { .enable = true, .autoConnect = false, .setRGBAImage = spice_setCursorRGBAImage, .setMonoImage = spice_setCursorMonoImage, .setState = spice_setCursorState, }, #if ENABLE_AUDIO .playback = { .enable = audio_supportsPlayback(), .autoConnect = true, .start = audio_playbackStart, .volume = audio_playbackVolume, .mute = audio_playbackMute, .stop = audio_playbackStop, .data = audio_playbackData }, .record = { .enable = audio_supportsRecord(), .autoConnect = true, .start = audio_recordStart, .volume = audio_recordVolume, .mute = audio_recordMute, .stop = audio_recordStop } #endif }; if (!purespice_connect(&config)) { DEBUG_ERROR("Failed to connect to spice server"); lgSignalEvent(e_spice); goto end; } // process all spice messages while(g_state.state != APP_STATE_SHUTDOWN) { PSStatus status; if ((status = purespice_process(100)) != PS_STATUS_RUN) { if (status != PS_STATUS_SHUTDOWN) DEBUG_ERROR("failed to process spice messages"); goto end; } } // send key up events for any pressed keys if (g_params.useSpiceInput) { for(int scancode = 0; scancode < KEY_MAX; ++scancode) if (g_state.keyDown[scancode]) { g_state.keyDown[scancode] = false; purespice_keyUp(scancode); } } purespice_disconnect(); end: audio_free(); // if the connection was disconnected intentionally we don't want to shutdown // so that the user can see the message box and take action if (!g_state.spiceClose) g_state.state = APP_STATE_SHUTDOWN; lgSignalEvent(e_spice); return 0; } void intHandler(int sig) { switch(sig) { case SIGINT: case SIGTERM: if (g_state.state != APP_STATE_SHUTDOWN) { DEBUG_INFO("Caught signal, shutting down..."); g_state.state = APP_STATE_SHUTDOWN; } else { DEBUG_INFO("Caught second signal, force quitting..."); signal(sig, SIG_DFL); raise(sig); } break; } } static bool tryRenderer(const int index, const LG_RendererParams lgrParams, bool * needsOpenGL) { const LG_RendererOps *r = LG_Renderers[index]; if (!IS_LG_RENDERER_VALID(r)) { DEBUG_ERROR("FIXME: Renderer %d is invalid, skipping", index); return false; } // create the renderer g_state.lgr = NULL; *needsOpenGL = false; if (!r->create(&g_state.lgr, lgrParams, needsOpenGL)) { g_state.lgr = NULL; return false; } // init the ops member memcpy(&g_state.lgr->ops, r, sizeof(*r)); // initialize the renderer if (!r->initialize(g_state.lgr)) { r->deinitialize(g_state.lgr); g_state.lgr = NULL; return false; } DEBUG_INFO("Using Renderer: %s", r->getName()); return true; } static void reportBadVersion(void) { DEBUG_BREAK(); DEBUG_ERROR("The host application is not compatible with this client"); DEBUG_ERROR("This is not a Looking Glass error, do not report this"); DEBUG_ERROR("Please install the matching host application for this client"); } static MsgBoxHandle showSpiceInputHelp(void) { static bool done = false; if (!g_params.useSpiceInput || done) return NULL; done = true; return app_msgBox( "Information", "Please note you can still control your guest\n" "through SPICE if you press the capture key."); } static int lg_run(void) { g_cursor.sens = g_params.mouseSens; if (g_cursor.sens < -9) g_cursor.sens = -9; else if (g_cursor.sens > 9) g_cursor.sens = 9; /* setup imgui */ igCreateContext(NULL); g_state.io = igGetIO(); g_state.style = igGetStyle(); g_state.style->Colors[ImGuiCol_ModalWindowDimBg] = (ImVec4) { 0.0f, 0.0f, 0.0f, 0.4f }; alloc_sprintf(&g_state.imGuiIni, "%s/imgui.ini", lgConfigDir()); g_state.io->IniFilename = g_state.imGuiIni; g_state.io->BackendFlags |= ImGuiBackendFlags_HasMouseCursors; g_state.windowScale = 1.0; if (util_initUIFonts()) { g_state.fontName = util_getUIFont(g_params.uiFont); DEBUG_INFO("Using font: %s", g_state.fontName); } ImVector_ImWchar_Init(&g_state.fontRange); ImFontGlyphRangesBuilder * rangeBuilder = ImFontGlyphRangesBuilder_ImFontGlyphRangesBuilder(); ImFontGlyphRangesBuilder_AddRanges(rangeBuilder, (ImWchar[]) { 0x0020, 0x00FF, // Basic Latin + Latin Supplement 0x2190, 0x2193, // four directional arrows 0, }); ImFontGlyphRangesBuilder_BuildRanges(rangeBuilder, &g_state.fontRange); ImFontGlyphRangesBuilder_destroy(rangeBuilder); // initialize metrics ringbuffers g_state.renderTimings = ringbuffer_new(256, sizeof(float)); g_state.uploadTimings = ringbuffer_new(256, sizeof(float)); g_state.renderDuration = ringbuffer_new(256, sizeof(float)); overlayGraph_register("FRAME" , g_state.renderTimings , 0.0f, 50.0f, NULL); overlayGraph_register("UPLOAD", g_state.uploadTimings , 0.0f, 50.0f, NULL); overlayGraph_register("RENDER", g_state.renderDuration, 0.0f, 10.0f, NULL); initImGuiKeyMap(g_state.io->KeyMap); // unknown guest OS at this time g_state.guestOS = KVMFR_OS_OTHER; // search for the best displayserver ops to use for(int i = 0; i < LG_DISPLAYSERVER_COUNT; ++i) if (LG_DisplayServers[i]->probe()) { g_state.ds = LG_DisplayServers[i]; break; } if (!g_state.ds) { DEBUG_ERROR("No display servers available, tried:"); for (int i = 0; i < LG_DISPLAYSERVER_COUNT; ++i) DEBUG_ERROR("* %s", LG_DisplayServers[i]->name); return -1; } ASSERT_LG_DS_VALID(g_state.ds); if (g_params.jitRender) { if (g_state.ds->waitFrame) g_state.jitRender = true; else DEBUG_WARN("JIT render not supported on display server backend, disabled"); } // init the subsystem if (!g_state.ds->earlyInit()) { DEBUG_ERROR("Subsystem early init failed"); return -1; } // override the SIGINIT handler so that we can tell the difference between // SIGINT and the user sending a close event, such as ALT+F4 signal(SIGINT , intHandler); signal(SIGTERM, intHandler); // try map the shared memory if (!ivshmemOpen(&g_state.shm)) { DEBUG_ERROR("Failed to map memory"); return -1; } // setup the spice startup condition if (!(e_spice = lgCreateEvent(false, 0))) { DEBUG_ERROR("failed to create the spice startup event"); return -1; } // setup the startup condition if (!(e_startup = lgCreateEvent(false, 0))) { DEBUG_ERROR("failed to create the startup event"); return -1; } // setup the new frame event if (!(g_state.frameEvent = lgCreateEvent(!g_state.jitRender, 0))) { DEBUG_ERROR("failed to create the frame event"); return -1; } //setup the render command queue renderQueue_init(); const PSInit psInit = { .log = { .info = debug_info, .warn = debug_warn, .error = debug_error, } }; purespice_init(&psInit); g_state.micDefaultState = g_params.micDefaultState; if (g_params.useSpiceInput || g_params.useSpiceClipboard || g_params.useSpiceAudio) { if (!lgCreateThread("spiceThread", spiceThread, NULL, &t_spice)) { DEBUG_ERROR("spice create thread failed"); return -1; } lgWaitEvent(e_spice, TIMEOUT_INFINITE); if (!g_state.spiceReady) return -1; } // select and init a renderer bool needsOpenGL = false; LG_RendererParams lgrParams; lgrParams.quickSplash = g_params.quickSplash; if (g_params.forceRenderer) { DEBUG_INFO("Trying forced renderer"); if (!tryRenderer(g_params.forceRendererIndex, lgrParams, &needsOpenGL)) { DEBUG_ERROR("Forced renderer failed to iniailize"); return -1; } } else { // probe for a a suitable renderer for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) { if (tryRenderer(i, lgrParams, &needsOpenGL)) break; } } if (!g_state.lgr) { DEBUG_ERROR("Unable to find a suitable renderer"); return -1; } g_state.useDMA = g_params.allowDMA && ivshmemHasDMA(&g_state.shm); // initialize the window dimensions at init for renderers g_state.windowW = g_params.w; g_state.windowH = g_params.h; g_state.windowCX = g_params.w / 2; g_state.windowCY = g_params.h / 2; core_updatePositionInfo(); const LG_DSInitParams params = { .title = g_params.windowTitle, .x = g_params.x, .y = g_params.y, .w = g_params.w, .h = g_params.h, .center = g_params.center, .fullscreen = g_params.fullscreen, .resizable = g_params.allowResize, .borderless = g_params.borderless, .maximize = g_params.maximize, .opengl = needsOpenGL, .jitRender = g_params.jitRender }; g_state.dsInitialized = g_state.ds->init(params); if (!g_state.dsInitialized) { DEBUG_ERROR("Failed to initialize the displayserver backend"); return -1; } if (g_params.noScreensaver) g_state.ds->inhibitIdle(); // ensure renderer viewport is aware of the current window size core_updatePositionInfo(); if (g_params.fpsMin <= 0) { // default 30 fps g_state.frameTime = 1000000000ULL / 30ULL; } else { DEBUG_INFO("Using the FPS minimum from args: %d", g_params.fpsMin); g_state.frameTime = 1000000000ULL / (unsigned long long)g_params.fpsMin; } // when the overlay is shown we should run at a minimum of 60 fps for // interactivity. g_state.overlayFrameTime = min(g_state.frameTime, 1000000000ULL / 60ULL); keybind_commonRegister(); if (g_state.jitRender) DEBUG_INFO("Using JIT render mode"); lgInit(); // start the renderThread so we don't just display junk if (!lgCreateThread("renderThread", renderThread, NULL, &t_render)) { DEBUG_ERROR("render create thread failed"); return -1; } // wait for startup to complete so that any error messages below are output at // the end of the output lgWaitEvent(e_startup, TIMEOUT_INFINITE); g_state.ds->startup(); g_state.cbAvailable = g_state.ds->cbInit && g_state.ds->cbInit(); if (g_state.cbAvailable) g_state.cbRequestList = ll_new(); LGMP_STATUS status; while(g_state.state == APP_STATE_RUNNING) { if ((status = lgmpClientInit(g_state.shm.mem, g_state.shm.size, &g_state.lgmp)) == LGMP_OK) break; DEBUG_ERROR("lgmpClientInit Failed: %s", lgmpStatusString(status)); return -1; } /* this short timeout is to allow the LGMP host to update the timestamp before * we start checking for a valid session */ g_state.ds->wait(200); if (g_params.captureOnStart) core_setGrab(true); uint32_t udataSize; KVMFR *udata; int waitCount = 0; MsgBoxHandle msgs[10]; int msgsCount; restart: msgsCount = 0; memset(msgs, 0, sizeof(msgs)); uint64_t initialSpiceEnable = microtime() + 1000 * 1000; while(g_state.state == APP_STATE_RUNNING) { if (initialSpiceEnable && microtime() > initialSpiceEnable) { /* use the spice display until we get frames from the LG host application * it is safe to call this before connect as it will be delayed until * spiceReady is called */ app_useSpiceDisplay(true); initialSpiceEnable = 0; } status = lgmpClientSessionInit(g_state.lgmp, &udataSize, (uint8_t **)&udata, NULL); switch(status) { case LGMP_OK: initialSpiceEnable = 0; break; case LGMP_ERR_INVALID_VERSION: { if (waitCount++ == 0) { reportBadVersion(); msgs[msgsCount++] = app_msgBox( "Incompatible LGMP Version", "The host application is not compatible with this client.\n" "Please download and install the matching version." ); DEBUG_INFO("Waiting for you to upgrade the host application"); } g_state.ds->wait(1000); continue; } case LGMP_ERR_INVALID_SESSION: case LGMP_ERR_INVALID_MAGIC: { if (waitCount++ == 0) { DEBUG_BREAK(); DEBUG_INFO("The host application seems to not be running"); DEBUG_INFO("Waiting for the host application to start..."); } if (waitCount == 30) { DEBUG_BREAK(); msgs[msgsCount++] = app_msgBox( "Host Application Not Running", "It seems the host application is not running or your\n" "virtual machine is still starting up\n" "\n" "If the the VM is running and booted please check the\n" "host application log for errors. You can find the\n" "log through the shortcut in your start menu\n" "\n" "Continuing to wait..."); msgs[msgsCount++] = showSpiceInputHelp(); DEBUG_INFO("Check the host log in your guest at %%ProgramData%%\\Looking Glass (host)\\looking-glass-host.txt"); DEBUG_INFO("Continuing to wait..."); } g_state.ds->wait(1000); continue; } default: DEBUG_ERROR("lgmpClientSessionInit Failed: %s", lgmpStatusString(status)); return -1; } if (g_state.state != APP_STATE_RUNNING) return -1; // dont show warnings again after the first successful startup waitCount = 100; const bool magicMatches = memcmp(udata->magic, KVMFR_MAGIC, sizeof(udata->magic)) == 0; if (udataSize < sizeof(*udata) || !magicMatches || udata->version != KVMFR_VERSION) { static bool alertsDone = false; if (alertsDone) { if(g_state.state == APP_STATE_RUNNING) g_state.ds->wait(1000); continue; } reportBadVersion(); if (magicMatches) { msgs[msgsCount++] = app_msgBox( "Incompatible KVMFR Version", "The host application is not compatible with this client.\n" "Please download and install the matching version.\n" "\n" "Client Version: %s\n" "Host Version: %s", BUILD_VERSION, udata->version >= 2 ? udata->hostver : NULL ); DEBUG_ERROR("Expected KVMFR version %d, got %d", KVMFR_VERSION, udata->version); DEBUG_ERROR("Client version: %s", BUILD_VERSION); if (udata->version >= 2) DEBUG_ERROR(" Host version: %s", udata->hostver); } else DEBUG_ERROR("Invalid KVMFR magic"); DEBUG_BREAK(); msgs[msgsCount++] = showSpiceInputHelp(); DEBUG_INFO("Waiting for you to upgrade the host application"); alertsDone = true; if(g_state.state == APP_STATE_RUNNING) g_state.ds->wait(1000); continue; } break; } if(g_state.state != APP_STATE_RUNNING) return -1; /* close any informational message boxes from above as we now connected * successfully */ for(int i = 0; i < msgsCount; ++i) if (msgs[i]) app_msgBoxClose(msgs[i]); DEBUG_INFO("Guest Information:"); DEBUG_INFO("Version : %s", udata->hostver); /* parse the kvmfr records from the userdata */ udataSize -= sizeof(*udata); uint8_t * p = (uint8_t *)(udata + 1); while(udataSize >= sizeof(KVMFRRecord)) { KVMFRRecord * record = (KVMFRRecord *)p; p += sizeof(*record); udataSize -= sizeof(*record); if (record->size > udataSize) { DEBUG_WARN("KVMFRecord size is invalid, aborting parsing."); break; } switch(record->type) { case KVMFR_RECORD_VMINFO: { KVMFRRecord_VMInfo * vmInfo = (KVMFRRecord_VMInfo *)p; DEBUG_INFO("UUID : " "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", vmInfo->uuid[ 0], vmInfo->uuid[ 1], vmInfo->uuid[ 2], vmInfo->uuid[ 3], vmInfo->uuid[ 4], vmInfo->uuid[ 5], vmInfo->uuid[ 6], vmInfo->uuid[ 7], vmInfo->uuid[ 8], vmInfo->uuid[ 9], vmInfo->uuid[10], vmInfo->uuid[11], vmInfo->uuid[12], vmInfo->uuid[13], vmInfo->uuid[14], vmInfo->uuid[15]); DEBUG_INFO("CPU Model: %s", vmInfo->model); DEBUG_INFO("CPU : %u sockets, %u cores, %u threads", vmInfo->sockets, vmInfo->cores, vmInfo->cpus); DEBUG_INFO("Using : %s", vmInfo->capture); bool uuidValid = false; for(int i = 0; i < sizeof(vmInfo->uuid); ++i) if (vmInfo->uuid[i]) { uuidValid = true; break; } if (!uuidValid) break; memcpy(g_state.guestUUID, vmInfo->uuid, sizeof(g_state.guestUUID)); g_state.guestUUIDValid = true; break; } case KVMFR_RECORD_OSINFO: { KVMFRRecord_OSInfo * osInfo = (KVMFRRecord_OSInfo *)p; static const char * typeStr[] = { "Linux", "BSD", "OSX", "Windows", "Other" }; const char * type; if (osInfo->os >= ARRAY_LENGTH(typeStr)) type = "Unknown"; else type = typeStr[osInfo->os]; DEBUG_INFO("OS : %s", type); if (osInfo->name[0]) DEBUG_INFO("OS Name : %s", osInfo->name); g_state.guestOS = osInfo->os; if (g_state.spiceReady && g_params.useSpiceInput) keybind_spiceRegister(); break; } default: DEBUG_WARN("Unhandled KVMFRecord type: %d", record->type); break; } p += record->size; udataSize -= record->size; } checkUUID(); if (g_state.state == APP_STATE_RUNNING) { DEBUG_INFO("Starting session"); g_state.lgHostConnected = true; } g_state.kvmfrFeatures = udata->features; LG_LOCK_INIT(g_state.pointerQueueLock); if (!core_startCursorThread() || !core_startFrameThread()) { LG_LOCK_FREE(g_state.pointerQueueLock); return -1; } while(g_state.state == APP_STATE_RUNNING) { if (!lgmpClientSessionValid(g_state.lgmp)) { g_state.lgHostConnected = false; DEBUG_INFO("Waiting for the host to restart..."); g_state.state = APP_STATE_RESTART; break; } g_state.ds->wait(100); } if (g_state.state == APP_STATE_RESTART) { lgSignalEvent(e_startup); lgSignalEvent(g_state.frameEvent); core_stopFrameThread(); core_stopCursorThread(); g_state.state = APP_STATE_RUNNING; lgInit(); goto restart; } LG_LOCK_FREE(g_state.pointerQueueLock); return 0; } static void lg_shutdown(void) { g_state.state = APP_STATE_SHUTDOWN; if (t_spice) lgJoinThread(t_spice, NULL); if (t_render) { if (g_state.jitRender && g_state.ds->stopWaitFrame) g_state.ds->stopWaitFrame(); lgSignalEvent(e_startup); lgSignalEvent(g_state.frameEvent); lgJoinThread(t_render, NULL); } lgmpClientFree(&g_state.lgmp); if (g_state.frameEvent) { lgFreeEvent(g_state.frameEvent); g_state.frameEvent = NULL; } if (e_startup) { lgFreeEvent(e_startup); e_startup = NULL; } if (e_spice) { lgFreeEvent(e_spice); e_startup = NULL; } if (g_state.ds) g_state.ds->shutdown(); if (g_state.cbRequestList) { ll_free(g_state.cbRequestList); g_state.cbRequestList = NULL; } app_releaseAllKeybinds(); ll_free(g_state.bindings); if (g_state.dsInitialized) g_state.ds->free(); ivshmemClose(&g_state.shm); renderQueue_free(); // free metrics ringbuffers ringbuffer_free(&g_state.renderTimings); ringbuffer_free(&g_state.uploadTimings); ringbuffer_free(&g_state.renderDuration); free(g_state.fontName); ImVector_ImWchar_UnInit(&g_state.fontRange); igDestroyContext(NULL); free(g_state.imGuiIni); } int main(int argc, char * argv[]) { // initialize for DEBUG_* macros debug_init(); if (getuid() == 0) { DEBUG_ERROR("Do not run looking glass as root!"); return -1; } if (getuid() != geteuid()) { DEBUG_ERROR("Do not run looking glass as setuid!"); return -1; } DEBUG_INFO("Looking Glass (%s)", BUILD_VERSION); DEBUG_INFO("Locking Method: " LG_LOCK_MODE); lgDebugCPU(); if (!installCrashHandler("/proc/self/exe")) DEBUG_WARN("Failed to install the crash handler"); lgPathsInit("looking-glass"); config_init(); ivshmemOptionsInit(); egl_dynProcsInit(); gl_dynProcsInit(); g_state.bindings = ll_new(); g_state.overlays = ll_new(); app_registerOverlay(&LGOverlaySplash, NULL); app_registerOverlay(&LGOverlayConfig, NULL); app_registerOverlay(&LGOverlayAlert , NULL); app_registerOverlay(&LGOverlayFPS , NULL); app_registerOverlay(&LGOverlayGraphs, NULL); app_registerOverlay(&LGOverlayHelp , NULL); app_registerOverlay(&LGOverlayMsg , NULL); app_registerOverlay(&LGOverlayStatus, NULL); // early renderer setup for option registration for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) LG_Renderers[i]->setup(); for(unsigned int i = 0; i < LG_DISPLAYSERVER_COUNT; ++i) LG_DisplayServers[i]->setup(); if (!config_load(argc, argv)) return -1; const int ret = lg_run(); lg_shutdown(); config_free(); util_freeUIFonts(); cleanupCrashHandler(); return ret; } looking-glass-B6/client/src/main.h000066400000000000000000000206531434445012300172440ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include "dynamic/displayservers.h" #include "dynamic/renderers.h" #include "common/thread.h" #include "common/types.h" #include "common/ivshmem.h" #include "common/locking.h" #include "common/ringbuffer.h" #include "common/event.h" #include "common/ll.h" #include #include #include "cimgui.h" enum RunState { APP_STATE_RUNNING, APP_STATE_RESTART, APP_STATE_SHUTDOWN }; enum MicDefaultState { MIC_DEFAULT_PROMPT, MIC_DEFAULT_ALLOW, MIC_DEFAULT_DENY }; #define MIC_DEFAULT_MAX (MIC_DEFAULT_DENY + 1) struct AppState { enum RunState state; ImGuiIO * io; ImGuiStyle * style; struct ll * overlays; char * fontName; ImFont * fontLarge; ImVector_ImWchar fontRange; bool overlayInput; ImGuiMouseCursor cursorLast; char * imGuiIni; bool modCtrl; bool modShift; bool modAlt; bool modSuper; uint64_t lastImGuiFrame; bool renderImGuiTwice; struct LG_DisplayServerOps * ds; bool dsInitialized; bool jitRender; uint8_t spiceUUID[16]; bool spiceReady; bool initialSpiceDisplay; uint8_t guestUUID[16]; bool guestUUIDValid; KVMFROS guestOS; bool lgHostConnected; bool stopVideo; bool ignoreInput; bool escapeActive; uint64_t escapeTime; int escapeAction; bool escapeHelp; struct ll * bindings; bool keyDown[KEY_MAX]; bool haveSrcSize; struct Point windowPos; int windowW, windowH; int windowCX, windowCY; double windowScale; LG_RendererRotate rotate; bool focused; struct Border border; struct Point srcSize; LG_RendererRect dstRect; bool posInfoValid; bool alignToGuest; bool spiceClose; LG_Renderer * lgr; atomic_int lgrResize; LG_Lock lgrLock; bool useDMA; bool cbAvailable; PSDataType cbType; bool cbChunked; size_t cbXfer; struct ll * cbRequestList; struct IVSHMEM shm; PLGMPClient lgmp; PLGMPClientQueue pointerQueue; LG_Lock pointerQueueLock; KVMFRFeatureFlags kvmfrFeatures; LGThread * cursorThread; LGThread * frameThread; LGEvent * frameEvent; atomic_bool invalidateWindow; bool formatValid; uint64_t frameTime; uint64_t overlayFrameTime; uint64_t lastFrameTime; bool lastFrameTimeValid; uint64_t lastRenderTime; bool lastRenderTimeValid; RingBuffer renderTimings; RingBuffer renderDuration; RingBuffer uploadTimings; atomic_uint_least64_t pendingCount; atomic_uint_least64_t renderCount, frameCount; _Atomic(float) fps, ups; uint64_t resizeTimeout; bool resizeDone; bool autoIdleInhibitState; enum MicDefaultState micDefaultState; int spiceHotX, spiceHotY; }; struct AppParams { bool autoResize; bool allowResize; bool keepAspect; bool forceAspect; bool dontUpscale; bool intUpscale; bool shrinkOnUpscale; bool borderless; bool fullscreen; bool maximize; bool minimizeOnFocusLoss; bool center; int x, y; unsigned int w, h; int fpsMin; LG_RendererRotate winRotate; bool useSpice; bool useSpiceInput; bool useSpiceClipboard; bool useSpiceAudio; const char * spiceHost; unsigned int spicePort; bool clipboardToVM; bool clipboardToLocal; bool scaleMouseInput; bool hideMouse; bool ignoreQuit; bool noScreensaver; bool autoScreensaver; bool grabKeyboard; bool grabKeyboardOnFocus; int escapeKey; bool ignoreWindowsKeys; bool releaseKeysOnFocusLoss; bool showAlerts; bool captureOnStart; bool quickSplash; bool overlayDim; bool alwaysShowCursor; uint64_t helpMenuDelayUs; const char * uiFont; int uiSize; bool jitRender; unsigned int cursorPollInterval; unsigned int framePollInterval; bool allowDMA; bool forceRenderer; unsigned int forceRendererIndex; const char * windowTitle; bool mouseRedraw; int mouseSens; bool mouseSmoothing; bool rawMouse; bool autoCapture; bool captureInputOnly; bool showCursorDot; int audioPeriodSize; int audioBufferLatency; bool micShowIndicator; enum MicDefaultState micDefaultState; }; struct CBRequest { PSDataType type; LG_ClipboardReplyFn replyFn; void * opaque; }; struct KeybindHandle { int sc; int charcode; KeybindFn callback; const char * description; void * opaque; }; enum WarpState { WARP_STATE_ON, WARP_STATE_OFF }; struct CursorInfo { /* x & y postiion */ int x , y; /* pointer hotspot offsets */ int hx, hy; /* true if the pointer is visible on the guest */ bool visible; /* true if the details in this struct are valid */ bool valid; }; struct CursorState { /* cursor is in grab mode */ bool grab; /* true if we are to draw the cursor on screen */ bool draw; /* true if the cursor is currently in our window */ bool inWindow; /* true if the cursor is currently in the guest view area */ bool inView; /* true if the guest should be realigned to the host when next drawn */ bool realign; /* true if the guest is currently realigning to the host */ bool realigning; /* true if the cursor needs re-drawing/updating */ bool redraw; /* true if the cursor movements should be scaled */ bool useScale; /* the amount to scale the X & Y movements by */ struct DoublePoint scale; /* the error accumulator */ struct DoublePoint acc; /* the local position */ struct DoublePoint pos; /* true if the position is valid */ bool valid; /* the button state */ unsigned int buttons; /* the delta since last warp when in auto capture mode */ struct DoublePoint delta; /* the scale factor for the mouse sensitiviy */ int sens; /* the mouse warp state */ enum WarpState warpState; /* the guest's cursor position */ struct CursorInfo guest; /* the projected position after move, for app_handleMouseBasic only */ struct Point projected; }; // forwards extern struct AppState g_state; extern struct CursorState g_cursor; extern struct AppParams g_params; int main_cursorThread(void * unused); int main_frameThread(void * unused); #define RENDERER(fn, ...) g_state.lgr->ops.fn(g_state.lgr, ##__VA_ARGS__) looking-glass-B6/client/src/overlay/000077500000000000000000000000001434445012300176225ustar00rootroot00000000000000looking-glass-B6/client/src/overlay/alert.c000066400000000000000000000063661434445012300211100ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/overlay.h" #include "cimgui.h" #include "overlay_utils.h" #include "common/stringutils.h" #include "../main.h" #define ALERT_TIMEOUT (2000 / (1000/25)) struct AlertState { bool show; char * message; LG_MsgAlert type; uint64_t timeout; bool redraw; }; struct AlertState l_alert = { 0 }; static bool alert_init(void ** udata, const void * params) { return true; } static void alert_free(void * udata) { free(l_alert.message); l_alert.message = NULL; } static const uint32_t colours[] = { 0xCC0000, // LG_ALERT_INFO 0x00CC00, // LG_ALERT_SUCCESS 0x007FCC, // LG_ALERT_WARNING 0x0000FF // LG_ALERT_ERROR }; static int alert_render(void * udata, bool interactive, struct Rect * windowRects, int maxRects) { if (!l_alert.show) return 0; ImVec2 * screen = overlayGetScreenSize(); igSetNextWindowBgAlpha(0.8f); igSetNextWindowPos((ImVec2) { screen->x / 2.0f, screen->y / 2.0f }, 0, (ImVec2) { 0.5f, 0.5f }); igPushStyleColor_U32(ImGuiCol_WindowBg, colours[l_alert.type]); igPushStyleVar_Vec2(ImGuiStyleVar_WindowPadding, (ImVec2) { 4.0f , 4.0f }); igPushStyleVar_Vec2(ImGuiStyleVar_WindowMinSize, (ImVec2) { 0.0f , 0.0f }); igBegin( "Alert", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoTitleBar ); igPushFont(g_state.fontLarge); igText("%s", l_alert.message); igPopFont(); overlayGetImGuiRect(windowRects); igEnd(); igPopStyleVar(2); igPopStyleColor(1); return 1; } static bool alert_tick(void * udata, unsigned long long tickCount) { if (l_alert.show && l_alert.timeout-- == 0) { l_alert.show = false; l_alert.redraw = true; } if (!l_alert.redraw) return false; l_alert.redraw = false; return true; } struct LG_OverlayOps LGOverlayAlert = { .name = "alert", .init = alert_init, .free = alert_free, .render = alert_render, .tick = alert_tick, }; void overlayAlert_show(LG_MsgAlert type, const char * fmt, va_list args) { if (!g_state.lgr || !g_params.showAlerts) return; char * buffer; valloc_sprintf(&buffer, fmt, args); free(l_alert.message); l_alert.message = buffer; l_alert.timeout = ALERT_TIMEOUT; l_alert.type = type; l_alert.show = true; app_invalidateOverlay(true); } looking-glass-B6/client/src/overlay/config.c000066400000000000000000000145301434445012300212360ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/overlay.h" #include "cimgui.h" #include "overlay_utils.h" #include "../main.h" #include "../overlays.h" #include "version.h" #include "common/debug.h" #include "common/appstrings.h" typedef struct ConfigCallback { const char * title; void * udata; void (*callback)(void * udata, int * id); } ConfigCallback; typedef struct OverlayConfig { struct ll * callbacks; struct ll * tabCallbacks; } OverlayConfig; static OverlayConfig cfg = { 0 }; static void config_earlyInit(void) { cfg.callbacks = ll_new(); cfg.tabCallbacks = ll_new(); } static bool config_init(void ** udata, const void * params) { return true; } static void config_freeList(struct ll * list) { ConfigCallback * cb; while(ll_shift(list, (void **)&cb)) free(cb); ll_free(list); } static void config_free(void * udata) { config_freeList(cfg.callbacks); config_freeList(cfg.tabCallbacks); } static void config_renderLGTab(void) { const float fontSize = igGetFontSize(); if (igCollapsingHeader_BoolPtr("Donations", NULL, ImGuiTreeNodeFlags_DefaultOpen)) { igTextWrapped(LG_DONATION_STR); igBeginTable("split", 2, 0, (ImVec2){}, 0.0f); igTableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, fontSize, 0); igTableNextColumn(); igBulletText(""); igTableNextColumn(); overlayTextURL(LG_DONATION_URL, NULL); igEndTable(); } if (igCollapsingHeader_BoolPtr("Help & Support", NULL, ImGuiTreeNodeFlags_DefaultOpen)) { igBeginTable("split", 2, 0, (ImVec2){}, 0.0f); igTableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, fontSize * 9.0f, 0); for(const StringPair * help = LG_HELP_LINKS; help->name; ++help) { igTableNextColumn(); igBulletText(help->name); igTableNextColumn(); overlayTextMaybeURL(help->value, true); } igEndTable(); } if (igCollapsingHeader_BoolPtr("The Looking Glass Team", NULL, ImGuiTreeNodeFlags_DefaultOpen)) { for(const struct LGTeamMember * member = LG_TEAM; member->name; ++member) { if (igTreeNode_Str(member->name)) { igSpacing(); igTextWrapped(member->blurb); if (member->donate[0].name) { igSeparator(); igTextWrapped( "If you would like to show financial support for his work you can " "do so directly via the following platform%s:", member->donate[1].name ? "s" : ""); igBeginTable("split", 2, 0, (ImVec2){}, 0.0f); igTableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, fontSize * 10.0f, 0); for(const StringPair * donate = member->donate; donate->name; ++donate) { igTableNextColumn(); igBulletText(donate->name); igTableNextColumn(); overlayTextMaybeURL(donate->value, false); } igEndTable(); } igTreePop(); igSeparator(); } } } } static void config_renderLicenseTab(void) { igText(LG_COPYRIGHT_STR); overlayTextURL(LG_WEBSITE_URL, NULL); igText(LG_VERSION_STR); igSeparator(); igTextWrapped(LG_LICENSE_STR); } static int config_render(void * udata, bool interactive, struct Rect * windowRects, int maxRects) { if (!interactive) return 0; int id = 1000; const ImGuiViewport * viewport = igGetMainViewport(); igSetNextWindowPos( (ImVec2){ viewport->WorkPos.x + 100, viewport->WorkPos.y + 30 }, ImGuiCond_FirstUseEver, (ImVec2){} ); igSetNextWindowSize( (ImVec2){550, 680}, ImGuiCond_FirstUseEver); igPushID_Int(id++); if (!igBegin("Configuration", NULL, 0)) { overlayGetImGuiRect(windowRects); igEnd(); igPopID(); return 1; } igBeginTabBar("Configuration#tabs", 0); if (igBeginTabItem("About", NULL, 0)) { config_renderLGTab(); igEndTabItem(); } ConfigCallback * cb; if (igBeginTabItem("Settings", NULL, 0)) { ll_lock(cfg.callbacks); ll_forEachNL(cfg.callbacks, item, cb) { if (!igCollapsingHeader_BoolPtr(cb->title, NULL, 0)) continue; igPushID_Int(id++); cb->callback(cb->udata, &id); igPopID(); } ll_unlock(cfg.callbacks); igEndTabItem(); } ll_lock(cfg.tabCallbacks); ll_forEachNL(cfg.tabCallbacks, item, cb) { if (!igBeginTabItem(cb->title, NULL, 0)) continue; igPushID_Int(id++); cb->callback(cb->udata, &id); igPopID(); igEndTabItem(); } ll_unlock(cfg.tabCallbacks); if (igBeginTabItem("License", NULL, 0)) { config_renderLicenseTab(); igEndTabItem(); } igEndTabBar(); overlayGetImGuiRect(windowRects); igEnd(); igPopID(); return 1; } struct LG_OverlayOps LGOverlayConfig = { .name = "Config", .earlyInit = config_earlyInit, .init = config_init, .free = config_free, .render = config_render }; static void config_addToList(struct ll * list, const char * title, void(*callback)(void * udata, int * id), void * udata) { ConfigCallback * cb = calloc(1, sizeof(*cb)); if (!cb) { DEBUG_ERROR("failed to allocate ram"); return; } cb->title = title; cb->udata = udata; cb->callback = callback; ll_push(list, cb); } void overlayConfig_register(const char * title, void (*callback)(void * udata, int * id), void * udata) { config_addToList(cfg.callbacks, title, callback, udata); }; void overlayConfig_registerTab(const char * title, void (*callback)(void * udata, int * id), void * udata) { config_addToList(cfg.tabCallbacks, title, callback, udata); }; looking-glass-B6/client/src/overlay/fps.c000066400000000000000000000052661434445012300205670ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/overlay.h" #include "cimgui.h" #include "overlay_utils.h" #include "common/option.h" #include "../main.h" static bool showFPS; static void showFPSKeybind(int sc, void * opaque) { showFPS ^= true; app_invalidateWindow(false); } static void fps_earlyInit(void) { static struct Option options[] = { { .module = "win", .name = "showFPS", .description = "Enable the FPS & UPS display", .shortopt = 'k', .type = OPTION_TYPE_BOOL, .value.x_bool = false, }, { 0 } }; option_register(options); } static bool fps_init(void ** udata, const void * params) { app_registerKeybind(0, 'D', showFPSKeybind, NULL, "FPS display toggle"); showFPS = option_get_bool("win", "showFPS"); return true; } static void fps_free(void * udata) { } static int fps_render(void * udata, bool interactive, struct Rect * windowRects, int maxRects) { if (!showFPS) return 0; ImVec2 pos = {0.0f, 0.0f}; igSetNextWindowBgAlpha(0.6f); igSetNextWindowPos(pos, ImGuiCond_FirstUseEver, pos); igPushStyleVar_Vec2(ImGuiStyleVar_WindowPadding, (ImVec2) { 4.0f , 4.0f }); igPushStyleVar_Vec2(ImGuiStyleVar_WindowMinSize, (ImVec2) { 0.0f , 0.0f }); igBegin( "FPS", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoTitleBar ); igText("FPS:%4.2f UPS:%4.2f", atomic_load_explicit(&g_state.fps, memory_order_relaxed), atomic_load_explicit(&g_state.ups, memory_order_relaxed)); overlayGetImGuiRect(windowRects); igEnd(); igPopStyleVar(2); return 1; } struct LG_OverlayOps LGOverlayFPS = { .name = "FPS", .earlyInit = fps_earlyInit, .init = fps_init, .free = fps_free, .render = fps_render }; looking-glass-B6/client/src/overlay/graphs.c000066400000000000000000000137201434445012300212550ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/overlay.h" #include "cimgui.h" #include "../main.h" #include "common/debug.h" #include "overlay_utils.h" struct GraphState { bool show; struct ll * graphs; }; static struct GraphState gs = {0}; struct OverlayGraph { const char * name; RingBuffer buffer; bool enabled; float min; float max; GraphFormatFn formatFn; }; static void configCallback(void * udata, int * id) { igCheckbox("Show timing graphs", &gs.show); igSeparator(); igBeginTable("split", 2, 0, (ImVec2){}, 0); GraphHandle graph; ll_lock(gs.graphs); ll_forEachNL(gs.graphs, item, graph) { igTableNextColumn(); igCheckbox(graph->name, &graph->enabled); } ll_unlock(gs.graphs); igEndTable(); } static void showTimingKeybind(int sc, void * opaque) { gs.show ^= true; app_invalidateWindow(false); } static void graphs_earlyInit(void) { gs.graphs = ll_new(); } static bool graphs_init(void ** udata, const void * params) { app_overlayConfigRegister("Performance Metrics", configCallback, NULL); app_registerKeybind(0, 'T', showTimingKeybind, NULL, "Show frame timing information"); return true; } static void graphs_free(void * udata) { struct OverlayGraph * graph; while(ll_shift(gs.graphs, (void **)&graph)) free(graph); ll_free(gs.graphs); gs.graphs = NULL; } struct BufferMetrics { float min; float max; float sum; float avg; float freq; float last; }; static bool rbCalcMetrics(int index, void * value_, void * udata_) { float * value = value_; struct BufferMetrics * udata = udata_; if (index == 0) { udata->min = *value; udata->max = *value; udata->sum = *value; return true; } if (udata->min > *value) udata->min = *value; if (udata->max < *value) udata->max = *value; udata->sum += *value; udata->last = *value; return true; } static int graphs_render(void * udata, bool interactive, struct Rect * windowRects, int maxRects) { if (!gs.show) return 0; float fontSize = igGetFontSize(); GraphHandle graph; int graphCount = 0; ll_lock(gs.graphs); ll_forEachNL(gs.graphs, item, graph) { if (graph->enabled) ++graphCount; } ImVec2 pos = {0.0f, 0.0f}; igSetNextWindowBgAlpha(0.4f); igSetNextWindowPos(pos, ImGuiCond_FirstUseEver, pos); igSetNextWindowSize( (ImVec2){ 28.0f * fontSize, 7.0f * fontSize * graphCount }, ImGuiCond_FirstUseEver); ImGuiWindowFlags flags = ImGuiWindowFlags_NoNav; if (!interactive) flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDecoration; igBegin("Performance Metrics", NULL, flags); ImVec2 winSize; igGetContentRegionAvail(&winSize); const float height = (winSize.y / graphCount) - igGetStyle()->ItemSpacing.y; ll_forEachNL(gs.graphs, item, graph) { if (!graph->enabled) continue; struct BufferMetrics metrics = {}; ringbuffer_forEach(graph->buffer, rbCalcMetrics, &metrics, false); if (metrics.sum > 0.0f) { metrics.avg = metrics.sum / ringbuffer_getCount(graph->buffer); metrics.freq = 1000.0f / metrics.avg; } const char * title; if (graph->formatFn) title = graph->formatFn(graph->name, metrics.min, metrics.max, metrics.avg, metrics.freq, metrics.last); else { static char _title[64]; snprintf(_title, sizeof(_title), "%s: min:%4.2f max:%4.2f avg:%4.2f/%4.2fHz", graph->name, metrics.min, metrics.max, metrics.avg, metrics.freq); title = _title; } igPlotLines_FloatPtr( "", (float *)ringbuffer_getValues(graph->buffer), ringbuffer_getLength(graph->buffer), ringbuffer_getStart (graph->buffer), title, graph->min, graph->max, (ImVec2){ winSize.x, height }, sizeof(float)); }; ll_unlock(gs.graphs); overlayGetImGuiRect(windowRects); igEnd(); return 1; } struct LG_OverlayOps LGOverlayGraphs = { .name = "Graphs", .earlyInit = graphs_earlyInit, .init = graphs_init, .free = graphs_free, .render = graphs_render }; GraphHandle overlayGraph_register(const char * name, RingBuffer buffer, float min, float max, GraphFormatFn formatFn) { struct OverlayGraph * graph = malloc(sizeof(*graph)); if (!graph) { DEBUG_ERROR("out of memory"); return NULL; } graph->name = name; graph->buffer = buffer; graph->enabled = true; graph->min = min; graph->max = max; graph->formatFn = formatFn; ll_push(gs.graphs, graph); return graph; } void overlayGraph_unregister(GraphHandle handle) { if (!gs.graphs) return; ll_removeData(gs.graphs, handle); free(handle); if (gs.show) app_invalidateWindow(false); } void overlayGraph_iterate(void (*callback)(GraphHandle handle, const char * name, bool * enabled, void * udata), void * udata) { GraphHandle graph; ll_lock(gs.graphs); ll_forEachNL(gs.graphs, item, graph) callback(graph, graph->name, &graph->enabled, udata); ll_unlock(gs.graphs); } void overlayGraph_invalidate(GraphHandle handle) { if (!gs.show) return; if (handle->enabled) app_invalidateWindow(false); } looking-glass-B6/client/src/overlay/help.c000066400000000000000000000047451434445012300207300ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/overlay.h" #include "cimgui.h" #include "overlay_utils.h" #include "common/debug.h" #include "../kb.h" #include "../main.h" static bool help_init(void ** udata, const void * params) { return true; } static void help_free(void * udata) { } static int help_render(void * udata, bool interactive, struct Rect * windowRects, int maxRects) { if (!g_state.escapeHelp) return 0; ImVec2 * screen = overlayGetScreenSize(); igSetNextWindowBgAlpha(0.6f); igSetNextWindowPos((ImVec2) { 0.0f, screen->y }, 0, (ImVec2) { 0.0f, 1.0f }); igBegin( "Help", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoTitleBar ); if (igBeginTable("Help", 2, 0, (ImVec2) { 0.0f, 0.0f }, 0.0f)) { const char * escapeName = linux_to_display[g_params.escapeKey]; igTableNextColumn(); igText("%s", escapeName); igTableNextColumn(); igText("Toggle capture mode"); KeybindHandle handle; ll_forEachNL(g_state.bindings, item, handle) { if (!handle->description) continue; igTableNextColumn(); if (handle->sc) igText("%s+%s", escapeName, linux_to_display[handle->sc]); else igText("%s+%c", escapeName, handle->charcode); igTableNextColumn(); igText(handle->description); } igEndTable(); } overlayGetImGuiRect(windowRects); igEnd(); return 1; } struct LG_OverlayOps LGOverlayHelp = { .name = "Help", .init = help_init, .free = help_free, .render = help_render }; looking-glass-B6/client/src/overlay/msg.c000066400000000000000000000121551434445012300205600ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "msg.h" #include "interface/overlay.h" #include "cimgui.h" #include "overlay_utils.h" #include "common/stringutils.h" #include "common/stringlist.h" #include "../main.h" #include struct Msg { char * caption; char * message; StringList lines; MsgBoxConfirmCallback confirm; void * opaque; }; struct MsgState { struct ll * messages; }; struct MsgState l_msg = { 0 }; static void msg_earlyInit(void) { l_msg.messages = ll_new(); } static bool msg_init(void ** udata, const void * params) { return true; } static void freeMsg(struct Msg * msg) { free(msg->caption); free(msg->message); stringlist_free(&msg->lines); free(msg); } static void msg_free(void * udata) { struct Msg * msg; while(ll_shift(l_msg.messages, (void **)&msg)) freeMsg(msg); ll_free(l_msg.messages); } static bool msg_needsOverlay(void * udata) { return ll_count(l_msg.messages) > 0; } static int msg_render(void * udata, bool interactive, struct Rect * windowRects, int maxRects) { struct Msg * msg; if (!ll_peek_head(l_msg.messages, (void **)&msg)) return 0; ImVec2 * screen = overlayGetScreenSize(); igSetNextWindowBgAlpha(0.8f); igSetNextWindowPos((ImVec2) { screen->x * 0.5f, screen->y * 0.5f }, 0, (ImVec2) { 0.5f, 0.5f }); igBegin( msg->caption, NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse ); ImVec2 textSize; const int lines = stringlist_count(msg->lines); for(int i = 0; i < lines; ++i) { const char * line = stringlist_at(msg->lines, i); if (line[0] == '\0') { igNewLine(); continue; } if (line[0] == '-' && line[1] == '\0') { igSeparator(); continue; } igCalcTextSize(&textSize, line, NULL, false, 0.0); igSetCursorPosX((igGetWindowWidth() * 0.5f) - (textSize.x * 0.5f)); igText("%s", stringlist_at(msg->lines, i)); } igNewLine(); bool destroy = false; if (msg->confirm) { igCalcTextSize(&textSize, "Yes", NULL, false, 0.0); ImGuiStyle * style = igGetStyle(); textSize.x += (style->FramePadding.x * 2.0f) * 8.0f; textSize.y += (style->FramePadding.y * 2.0f) * 1.5f; igSetCursorPosX((igGetWindowWidth() * 0.5f) - textSize.x); if (igButton("Yes", textSize)) { destroy = true; msg->confirm(true, msg->opaque); } igSameLine(0.0f, -1.0f); if (igButton("No", textSize)) { destroy = true; msg->confirm(false, msg->opaque); } } else { igCalcTextSize(&textSize, "OK", NULL, false, 0.0); ImGuiStyle * style = igGetStyle(); textSize.x += (style->FramePadding.x * 2.0f) * 8.0f; textSize.y += (style->FramePadding.y * 2.0f) * 1.5f; igSetCursorPosX((igGetWindowWidth() * 0.5f) - (textSize.x * 0.5f)); if (igButton("OK", textSize)) destroy = true; } if (destroy) { ll_shift(l_msg.messages, NULL); freeMsg(msg); app_invalidateOverlay(false); } overlayGetImGuiRect(windowRects); igEnd(); return 1; } struct LG_OverlayOps LGOverlayMsg = { .name = "msg", .earlyInit = msg_earlyInit, .init = msg_init, .free = msg_free, .needs_overlay = msg_needsOverlay, .render = msg_render }; bool overlayMsg_modal(void) { return ll_count(l_msg.messages) > 0; } MsgBoxHandle overlayMsg_show( const char * caption, MsgBoxConfirmCallback confirm, void * opaque, const char * fmt, va_list args) { struct Msg * msg = malloc(sizeof(*msg)); if (!msg) { DEBUG_ERROR("out of memory"); return NULL; } msg->caption = strdup(caption); msg->lines = stringlist_new(false); msg->confirm = confirm; msg->opaque = opaque; valloc_sprintf(&msg->message, fmt, args); char * token = msg->message; char * rest = msg->message; do { if (*rest == '\n') { *rest = '\0'; stringlist_push(msg->lines, token); token = rest + 1; } ++rest; } while(*rest != '\0'); if (*token) stringlist_push(msg->lines, token); ll_push(l_msg.messages, msg); app_invalidateOverlay(false); return (MsgBoxHandle)msg; } void overlayMsg_close(MsgBoxHandle handle) { if (ll_removeData(l_msg.messages, handle)) freeMsg((struct Msg *)handle); } looking-glass-B6/client/src/overlay/msg.h000066400000000000000000000022001434445012300205530ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_OVERLAY_MSG_H #define _H_OVERLAY_MSG_H #include #include #include "app.h" bool overlayMsg_modal(void); MsgBoxHandle overlayMsg_show( const char * caption, MsgBoxConfirmCallback confirm, void * opaque, const char * fmt, va_list args); void overlayMsg_close(MsgBoxHandle handle); #endif looking-glass-B6/client/src/overlay/splash.c000066400000000000000000000142071434445012300212640ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/overlay.h" #include "cimgui.h" #include "../overlays.h" #include "../main.h" #include "overlay_utils.h" #include "common/array.h" #include "version.h" #include "common/appstrings.h" #include "common/stringlist.h" #include "common/stringutils.h" #include "resources/lg-logo.svg.h" #include #include #define SEGMENTS 12 static bool l_show; static bool l_fadeDone; static float l_alpha; static OverlayImage l_logo; static float l_vectors[SEGMENTS][2]; static StringList l_tagline; static StringList l_footline; static void calcRadialVectors(float vectors[][2], int segments) { for (unsigned int i = 0; i < segments; ++i) { float angle = (i / (float)(segments - 1)) * M_PI * 2.0f; vectors[i][0] = cos(angle); vectors[i][1] = sin(angle); } } static void drawRadialGradient(ImDrawList * list, int x, int y, int w, int h, ImU32 innerColor, ImU32 outerColor, float vectors[0][2], int segments) { const ImVec2 uv = list->_Data->TexUvWhitePixel; ImDrawList_PrimReserve(list, (segments - 1) * 3, segments + 1); for(int i = 0; i < segments - 1; ++i) { ImDrawList_PrimWriteIdx(list, list->_VtxCurrentIdx); ImDrawList_PrimWriteIdx(list, list->_VtxCurrentIdx + i + 1); ImDrawList_PrimWriteIdx(list, list->_VtxCurrentIdx + i + 2); } ImDrawList_PrimWriteVtx(list, (ImVec2){x, y}, uv, innerColor); for (unsigned int i = 0; i < segments; ++i) ImDrawList_PrimWriteVtx(list, (ImVec2){ x + vectors[i][0] * w, y + vectors[i][1] * h }, uv, outerColor); } static bool splash_init(void ** udata, const void * params) { l_show = true; l_fadeDone = false; l_alpha = 1.0f; overlayLoadSVG(b_lg_logo_svg, b_lg_logo_svg_size, &l_logo, 200, 200); calcRadialVectors(l_vectors, ARRAY_LENGTH(l_vectors)); l_tagline = stringlist_new(false); l_footline = stringlist_new(false); stringlist_push(l_tagline, "Looking Glass"); stringlist_push(l_tagline, (char *)LG_WEBSITE_URL); stringlist_push(l_footline, (char *)LG_VERSION_STR); stringlist_push(l_footline, (char *)LG_COPYRIGHT_STR); return true; } static void splash_free(void * udata) { overlayFreeImage(&l_logo); stringlist_free(&l_tagline ); stringlist_free(&l_footline); } static void renderText(ImDrawList * list, int x, int y, ImU32 color, StringList lines, bool topAlign) { static float textHeight = 0.0f; ImVec2 size; if (textHeight == 0.0f) { const char * tmp = "W"; igCalcTextSize(&size, tmp, tmp + 1, false, 0.0f); textHeight = size.y; } float fy = y; const unsigned int count = stringlist_count(lines); for(int i = 0; i < count; ++i) { const char * text = stringlist_at(lines, topAlign ? i : count - i - 1); const int len = strlen(text); igCalcTextSize(&size, text, text + len, false, 0.0f); ImDrawList_AddText_Vec2( list, (ImVec2){ x - size.x / 2, topAlign ? fy : fy - size.y }, color, text, text + len ); if (topAlign) fy += textHeight; else fy -= textHeight; } } static int splash_render(void * udata, bool interactive, struct Rect * windowRects, int maxRects) { if (!l_show && l_fadeDone) return 0; const float alpha = l_fadeDone ? 1.0f : l_alpha; ImVec2 * screen = overlayGetScreenSize(); ImDrawList * list = igGetBackgroundDrawList_Nil(); struct Rect rect = { .x = 0, .y = 0, .w = screen->x, .h = screen->y }; struct Rect logoRect = { .x = screen->x / 2 - l_logo.width / 2, .y = screen->y / 2 - l_logo.height / 2, .w = l_logo.width, .h = l_logo.height }; const ImU32 innerColor = igColorConvertFloat4ToU32((ImVec4){ 0.234375f, 0.015625f, 0.425781f, alpha}); const ImU32 outerColor = igColorConvertFloat4ToU32((ImVec4){ 0.0f, 0.0f, 0.0f, alpha}); const ImU32 imageColor = igColorConvertFloat4ToU32((ImVec4){ 1.0f, 1.0f, 1.0f, alpha}); const ImU32 fontColor = igColorConvertFloat4ToU32((ImVec4){ 0.8f, 0.8f, 0.8f, alpha}); drawRadialGradient(list, screen->x / 2, screen->y / 2, screen->x , screen->y , innerColor, outerColor, l_vectors, ARRAY_LENGTH(l_vectors)); ImDrawList_AddImage( list, l_logo.tex, (ImVec2){ logoRect.x, logoRect.y }, (ImVec2){ logoRect.x + logoRect.w, logoRect.y + logoRect.h }, (ImVec2){ 0, 0 }, (ImVec2){ 1, 1 }, imageColor); renderText(list, screen->x / 2, logoRect.y + logoRect.h + 10, fontColor, l_tagline, true); renderText(list, screen->x / 2, screen->y - 10, fontColor, l_footline, false); *windowRects = rect; return 1; } static bool splash_tick(void * udata, unsigned long long tickCount) { if (!l_show && !l_fadeDone) { if (g_params.quickSplash) { l_fadeDone = true; return true; } l_alpha -= 1.0f / TICK_RATE; if (l_alpha <= 0.0f) l_fadeDone = true; return true; } return false; } struct LG_OverlayOps LGOverlaySplash = { .name = "splash", .init = splash_init, .free = splash_free, .render = splash_render, .tick = splash_tick, }; void overlaySplash_show(bool show) { if (l_show == show) return; l_show = show; app_invalidateOverlay(true); } looking-glass-B6/client/src/overlay/status.c000066400000000000000000000075231434445012300213200ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "interface/overlay.h" #include "math.h" #include "cimgui.h" #include "../overlays.h" #include "../main.h" #include "overlay_utils.h" #include "resources/status/recording.svg.h" #include "resources/status/spice.svg.h" //TODO: Make this user configurable? #define ICON_SIZE 32 static bool l_state[LG_USER_STATUS_MAX] = { 0 }; static OverlayImage l_image[LG_USER_STATUS_MAX] = { 0 }; static bool l_recordToggle; static double l_scale = 1.0; static void status_loadImage(const char * data, unsigned int size, OverlayImage * image, int width, int height) { overlayFreeImage(image); overlayLoadSVG(data, size, image, width, height); } static void status_loadIcons(double scale) { int iconSize = ceil(scale * ICON_SIZE); status_loadImage(b_status_recording_svg, b_status_recording_svg_size, &l_image[LG_USER_STATUS_RECORDING], iconSize, iconSize); status_loadImage(b_status_spice_svg, b_status_spice_svg_size, &l_image[LG_USER_STATUS_SPICE], iconSize, iconSize); } static bool status_init(void ** udata, const void * params) { status_loadIcons(l_scale); return true; } static void status_free(void * udata) { for(int i = 0; i < LG_USER_STATUS_MAX; ++i) overlayFreeImage(&l_image[i]); } static int status_render(void * udata, bool interactive, struct Rect * windowRects, int maxRects) { const int marginX = 10; const int marginY = 10; const int gapX = 5; if (g_state.windowScale > l_scale) { l_scale = g_state.windowScale; status_loadIcons(l_scale); } ImVec2 * screen = overlayGetScreenSize(); struct Rect rect = { .x = screen->x - LG_USER_STATUS_MAX * (ICON_SIZE + gapX) - marginX, .y = marginY, .w = LG_USER_STATUS_MAX * (ICON_SIZE + gapX), .h = ICON_SIZE }; int xPos = screen->x - marginX; for(int i = 0; i < LG_USER_STATUS_MAX; ++i) { OverlayImage * img = &l_image[i]; if (!l_state[i] || !img->tex) continue; // if the recording indicator is off, don't draw but reserve space if (i == LG_USER_STATUS_RECORDING && !l_recordToggle) goto next; ImDrawList_AddImage( igGetBackgroundDrawList_Nil(), img->tex, (ImVec2){ xPos, marginY }, (ImVec2){ xPos - ICON_SIZE, img->height / l_scale + marginY }, (ImVec2){ 0, 0 }, (ImVec2){ 1, 1 }, 0xFFFFFFFF); next: xPos -= ICON_SIZE + gapX; } *windowRects = rect; return 1; } static bool status_tick(void * udata, unsigned long long tickCount) { static unsigned long long lastTick = 0; if (tickCount - lastTick >= 25) { l_recordToggle = !l_recordToggle; lastTick = tickCount; return true; } return false; } struct LG_OverlayOps LGOverlayStatus = { .name = "status", .init = status_init, .free = status_free, .render = status_render, .tick = status_tick, }; void overlayStatus_set(LGUserStatus status, bool value) { if (l_state[status] == value) return; l_state[status] = value; app_invalidateOverlay(true); }; looking-glass-B6/client/src/overlay_utils.c000066400000000000000000000075301434445012300212130ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "overlay_utils.h" #include #include "common/open.h" #include "cimgui.h" #include "main.h" #define NANOSVG_IMPLEMENTATION #define NANOSVG_ALL_COLOR_KEYWORDS #define NANOSVGRAST_IMPLEMENTATION #include "nanosvgrast.h" void overlayGetImGuiRect(struct Rect * rect) { ImVec2 size; ImVec2 pos; igGetWindowPos(&pos); igGetWindowSize(&size); rect->x = pos.x; rect->y = pos.y; rect->w = size.x; rect->h = size.y; } ImVec2 * overlayGetScreenSize(void) { return &g_state.io->DisplaySize; } static void overlayAddUnderline(ImU32 color) { ImVec2 min, max; igGetItemRectMin(&min); igGetItemRectMax(&max); min.y = max.y; ImDrawList_AddLine(igGetWindowDrawList(), min, max, color, 1.0f); } void overlayTextURL(const char * url, const char * text) { igText(text ? text : url); if (igIsItemHovered(ImGuiHoveredFlags_None)) { if (igIsItemClicked(ImGuiMouseButton_Left)) lgOpenURL(url); overlayAddUnderline(igGetColorU32_Vec4( *igGetStyleColorVec4(ImGuiCol_ButtonHovered))); igSetMouseCursor(ImGuiMouseCursor_Hand); igSetTooltip("Open in browser: %s", url); } } void overlayTextMaybeURL(const char * text, bool wrapped) { if (strncmp(text, "https://", 8) == 0) overlayTextURL(text, NULL); else if (wrapped) igTextWrapped(text); else igText(text); } bool overlayLoadSVG(const char * data, unsigned int size, OverlayImage * image, int width, int height) { image->tex = NULL; //nsvgParse alters the data, we need to make a copy and null terminate it char * svg = malloc(size + 1); if (!svg) { DEBUG_ERROR("out of ram"); goto err; } memcpy(svg, data, size); svg[size] = 0; NSVGimage * nvi = nsvgParse(svg, "px", 96.0); if (!nvi) { free(svg); DEBUG_ERROR("nvsgParseFromData failed"); goto err; } free(svg); NSVGrasterizer * rast = nsvgCreateRasterizer(); if (!rast) { DEBUG_ERROR("nsvgCreateRasterizer failed"); goto err_image; } double srcAspect = nvi->width / nvi->height; double dstAspect = (double)width / (double)height; float scale; if (dstAspect > srcAspect) { image->width = (double)height * srcAspect; image->height = height; scale = (float)image->width / nvi->width; } else { image->width = width; image->height = (double)width / srcAspect; scale = (float)image->height / nvi->height; } uint8_t * img = malloc(image->width * image->height * 4); if (!img) { DEBUG_ERROR("out of ram"); goto err_rast; } nsvgRasterize(rast, nvi, 0.0f, 0.0f, scale, img, image->width, image->height, image->width * 4); image->tex = RENDERER(createTexture, image->width, image->height, img); free(img); if (!image->tex) { DEBUG_ERROR("renderer failed to create the texture"); goto err_rast; } err_rast: nsvgDeleteRasterizer(rast); err_image: nsvgDelete(nvi); err: return image->tex != NULL; } void overlayFreeImage(OverlayImage * image) { if (!image->tex) return; RENDERER(freeTexture, image->tex); } looking-glass-B6/client/src/overlays.h000066400000000000000000000044151434445012300201620ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_OVERLAYS_H #define _H_OVERLAYS_H #include "interface/overlay.h" #include "overlay/msg.h" struct Overlay { const struct LG_OverlayOps * ops; const void * params; void * udata; int lastRectCount; struct Rect lastRects[MAX_OVERLAY_RECTS]; }; extern struct LG_OverlayOps LGOverlaySplash; extern struct LG_OverlayOps LGOverlayAlert; extern struct LG_OverlayOps LGOverlayFPS; extern struct LG_OverlayOps LGOverlayGraphs; extern struct LG_OverlayOps LGOverlayHelp; extern struct LG_OverlayOps LGOverlayConfig; extern struct LG_OverlayOps LGOverlayMsg; extern struct LG_OverlayOps LGOverlayStatus; void overlayAlert_show(LG_MsgAlert type, const char * fmt, va_list args); GraphHandle overlayGraph_register(const char * name, RingBuffer buffer, float min, float max, GraphFormatFn formatFn); void overlayGraph_unregister(GraphHandle handle); void overlayGraph_iterate(void (*callback)(GraphHandle handle, const char * name, bool * enabled, void * udata), void * udata); void overlayGraph_invalidate(GraphHandle handle); void overlayConfig_register(const char * title, void (*callback)(void * udata, int * id), void * udata); void overlayConfig_registerTab(const char * title, void (*callback)(void * udata, int * id), void * udata); typedef enum LG_UserStatus { LG_USER_STATUS_SPICE, LG_USER_STATUS_RECORDING, LG_USER_STATUS_MAX } LGUserStatus; void overlaySplash_show(bool show); void overlayStatus_set(LGUserStatus, bool value); #endif looking-glass-B6/client/src/render_queue.c000066400000000000000000000123101434445012300207650ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "render_queue.h" #include #include "common/ll.h" #include "main.h" #include "overlays.h" struct ll * l_renderQueue = NULL; void renderQueue_init(void) { l_renderQueue = ll_new(); } void renderQueue_free(void) { if (!l_renderQueue) return; renderQueue_clear(); ll_free(l_renderQueue); } void renderQueue_clear(void) { RenderCommand * cmd; while(ll_shift(l_renderQueue, (void **)&cmd)) { if (cmd->op == SPICE_OP_DRAW_BITMAP) free(cmd->spiceDrawBitmap.data); free(cmd); } } void renderQueue_spiceConfigure(int width, int height) { RenderCommand * cmd = malloc(sizeof(*cmd)); cmd->op = SPICE_OP_CONFIGURE; cmd->spiceConfigure.width = width; cmd->spiceConfigure.height = height; ll_push(l_renderQueue, cmd); app_invalidateWindow(true); } void renderQueue_spiceDrawFill(int x, int y, int width, int height, uint32_t color) { RenderCommand * cmd = malloc(sizeof(*cmd)); cmd->op = SPICE_OP_DRAW_FILL; cmd->spiceFillRect.x = x; cmd->spiceFillRect.y = y; cmd->spiceFillRect.width = width; cmd->spiceFillRect.height = height; cmd->spiceFillRect.color = color; ll_push(l_renderQueue, cmd); app_invalidateWindow(true); } void renderQueue_spiceDrawBitmap(int x, int y, int width, int height, int stride, void * data, bool topDown) { RenderCommand * cmd = malloc(sizeof(*cmd)); cmd->op = SPICE_OP_DRAW_BITMAP; cmd->spiceDrawBitmap.x = x; cmd->spiceDrawBitmap.y = y; cmd->spiceDrawBitmap.width = width; cmd->spiceDrawBitmap.height = height; cmd->spiceDrawBitmap.stride = stride; cmd->spiceDrawBitmap.data = malloc(height * stride); cmd->spiceDrawBitmap.topDown = topDown; memcpy(cmd->spiceDrawBitmap.data, data, height * stride); ll_push(l_renderQueue, cmd); app_invalidateWindow(true); } void renderQueue_spiceShow(bool show) { RenderCommand * cmd = malloc(sizeof(*cmd)); cmd->op = SPICE_OP_SHOW; cmd->spiceShow.show = show; ll_push(l_renderQueue, cmd); app_invalidateWindow(true); } void renderQueue_cursorState(bool visible, int x, int y, int hx, int hy) { RenderCommand * cmd = malloc(sizeof(*cmd)); cmd->op = CURSOR_OP_STATE; cmd->cursorState.visible = visible; cmd->cursorState.x = x; cmd->cursorState.y = y; cmd->cursorState.hx = hx; cmd->cursorState.hy = hy; ll_push(l_renderQueue, cmd); } void renderQueue_cursorImage(bool monochrome, int width, int height, int pitch, uint8_t * data) { RenderCommand * cmd = malloc(sizeof(*cmd)); cmd->op = CURSOR_OP_IMAGE; cmd->cursorImage.monochrome = monochrome; cmd->cursorImage.width = width; cmd->cursorImage.height = height; cmd->cursorImage.pitch = pitch; cmd->cursorImage.data = data; ll_push(l_renderQueue, cmd); } void renderQueue_process(void) { RenderCommand * cmd; while(ll_shift(l_renderQueue, (void **)&cmd)) { switch(cmd->op) { case SPICE_OP_CONFIGURE: RENDERER(spiceConfigure, cmd->spiceConfigure.width, cmd->spiceConfigure.height); break; case SPICE_OP_DRAW_FILL: RENDERER(spiceDrawFill, cmd->spiceFillRect.x , cmd->spiceFillRect.y, cmd->spiceFillRect.width, cmd->spiceFillRect.height, cmd->spiceFillRect.color); break; case SPICE_OP_DRAW_BITMAP: RENDERER(spiceDrawBitmap, cmd->spiceDrawBitmap.x , cmd->spiceDrawBitmap.y, cmd->spiceDrawBitmap.width , cmd->spiceDrawBitmap.height, cmd->spiceDrawBitmap.stride, cmd->spiceDrawBitmap.data, cmd->spiceDrawBitmap.topDown); free(cmd->spiceDrawBitmap.data); break; case SPICE_OP_SHOW: RENDERER(spiceShow, cmd->spiceShow.show); if (cmd->spiceShow.show) overlaySplash_show(false); break; case CURSOR_OP_STATE: RENDERER(onMouseEvent, cmd->cursorState.visible, cmd->cursorState.x, cmd->cursorState.y, cmd->cursorState.hx, cmd->cursorState.hy); break; case CURSOR_OP_IMAGE: RENDERER(onMouseShape, cmd->cursorImage.monochrome ? LG_CURSOR_MONOCHROME : LG_CURSOR_COLOR, cmd->cursorImage.width, cmd->cursorImage.height, cmd->cursorImage.pitch, cmd->cursorImage.data); free(cmd->cursorImage.data); } free(cmd); } } looking-glass-B6/client/src/render_queue.h000066400000000000000000000044501434445012300210000ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/ll.h" typedef struct { enum { SPICE_OP_CONFIGURE, SPICE_OP_DRAW_FILL, SPICE_OP_DRAW_BITMAP, SPICE_OP_SHOW, CURSOR_OP_STATE, CURSOR_OP_IMAGE, } op; union { struct { int width, height; } spiceConfigure; struct { int x, y; int width, height; uint32_t color; } spiceFillRect; struct { int x , y; int width, height; int stride; uint8_t * data; bool topDown; } spiceDrawBitmap; struct { bool show; } spiceShow; struct { bool visible; int x; int y; int hx; int hy; } cursorState; struct { bool monochrome; int width; int height; int pitch; uint8_t * data; } cursorImage; }; } RenderCommand; void renderQueue_init(void); void renderQueue_free(void); void renderQueue_clear(void); void renderQueue_process(void); void renderQueue_spiceConfigure(int width, int height); void renderQueue_spiceDrawFill(int x, int y, int width, int height, uint32_t color); void renderQueue_spiceDrawBitmap(int x, int y, int width, int height, int stride, void * data, bool topDown); void renderQueue_spiceShow(bool show); void renderQueue_cursorState(bool visible, int x, int y, int hx, int hy); void renderQueue_cursorImage(bool monochrome, int width, int height, int pitch, uint8_t * data); looking-glass-B6/client/src/util.c000066400000000000000000000145511434445012300172700ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "util.h" #include "main.h" #include "common/debug.h" #include "common/stringutils.h" #include #include #include #include #include static FcConfig * FontConfig = NULL; bool util_fileGetContents(const char * filename, char ** buffer, size_t * length) { FILE * fh = fopen(filename, "r"); if (!fh) { DEBUG_ERROR("Failed to open the file: %s", filename); return false; } if (fseek(fh, 0, SEEK_END) != 0) { DEBUG_ERROR("Failed to seek"); fclose(fh); return false; } long fsize = ftell(fh); if (fseek(fh, 0, SEEK_SET) != 0) { DEBUG_ERROR("Failed to seek"); fclose(fh); return false; } *buffer = malloc(fsize + 1); if (!*buffer) { DEBUG_ERROR("Failed to allocate buffer of %lu bytes", fsize + 1); fclose(fh); return false; } if (fread(*buffer, 1, fsize, fh) != fsize) { DEBUG_ERROR("Failed to read the entire file"); fclose(fh); free(*buffer); return false; } fclose(fh); buffer[fsize] = 0; *length = fsize; return true; } void util_cursorToInt(double ex, double ey, int *x, int *y) { /* only smooth if enabled and not using raw mode */ if (g_params.mouseSmoothing && !(g_cursor.grab && g_params.rawMouse)) { static struct DoublePoint last = { 0 }; /* only apply smoothing to small deltas */ if (fabs(ex - last.x) < 5.0 && fabs(ey - last.y) < 5.0) { ex = last.x = (last.x + ex) / 2.0; ey = last.y = (last.y + ey) / 2.0; } else { last.x = ex; last.y = ey; } } /* convert to int accumulating the fractional error */ ex += g_cursor.acc.x; ey += g_cursor.acc.y; g_cursor.acc.x = modf(ex, &ex); g_cursor.acc.y = modf(ey, &ey); *x = (int)ex; *y = (int)ey; } bool util_guestCurToLocal(struct DoublePoint *local) { if (!g_cursor.guest.valid || !g_state.posInfoValid) return false; const struct DoublePoint point = { .x = g_cursor.guest.x + g_cursor.guest.hx, .y = g_cursor.guest.y + g_cursor.guest.hy }; switch((g_state.rotate + g_params.winRotate) % LG_ROTATE_MAX) { case LG_ROTATE_0: local->x = (point.x / g_cursor.scale.x) + g_state.dstRect.x; local->y = (point.y / g_cursor.scale.y) + g_state.dstRect.y;; break; case LG_ROTATE_90: local->x = (g_state.dstRect.x + g_state.dstRect.w) - point.y / g_cursor.scale.y; local->y = (point.x / g_cursor.scale.x) + g_state.dstRect.y; break; case LG_ROTATE_180: local->x = (g_state.dstRect.x + g_state.dstRect.w) - point.x / g_cursor.scale.x; local->y = (g_state.dstRect.y + g_state.dstRect.h) - point.y / g_cursor.scale.y; break; case LG_ROTATE_270: local->x = (point.y / g_cursor.scale.y) + g_state.dstRect.x; local->y = (g_state.dstRect.y + g_state.dstRect.h) - point.x / g_cursor.scale.x; break; } return true; } void util_localCurToGuest(struct DoublePoint *guest) { const struct DoublePoint point = g_cursor.pos; switch((g_state.rotate + g_params.winRotate) % LG_ROTATE_MAX) { case LG_ROTATE_0: guest->x = (point.x - g_state.dstRect.x) * g_cursor.scale.x; guest->y = (point.y - g_state.dstRect.y) * g_cursor.scale.y; break; case LG_ROTATE_90: guest->x = (point.y - g_state.dstRect.y) * g_cursor.scale.y; guest->y = (g_state.dstRect.w - point.x + g_state.dstRect.x) * g_cursor.scale.x; break; case LG_ROTATE_180: guest->x = (g_state.dstRect.w - point.x + g_state.dstRect.x) * g_cursor.scale.x; guest->y = (g_state.dstRect.h - point.y + g_state.dstRect.y) * g_cursor.scale.y; break; case LG_ROTATE_270: guest->x = (g_state.dstRect.h - point.y + g_state.dstRect.y) * g_cursor.scale.y; guest->y = (point.x - g_state.dstRect.x) * g_cursor.scale.x; break; default: DEBUG_UNREACHABLE(); } } void util_rotatePoint(struct DoublePoint *point) { double temp; switch((g_state.rotate + g_params.winRotate) % LG_ROTATE_MAX) { case LG_ROTATE_0: break; case LG_ROTATE_90: temp = point->x; point->x = point->y; point->y = -temp; break; case LG_ROTATE_180: point->x = -point->x; point->y = -point->y; break; case LG_ROTATE_270: temp = point->x; point->x = -point->y; point->y = temp; break; } } bool util_hasGLExt(const char * exts, const char * ext) { return str_containsValue(exts, ' ', ext); } bool util_initUIFonts(void) { if (FontConfig) return true; FontConfig = FcInitLoadConfigAndFonts(); if (!FontConfig) { DEBUG_ERROR("FcInitLoadConfigAndFonts Failed"); return false; } return true; } char * util_getUIFont(const char * fontName) { char * ttf = NULL; FcPattern * pat = FcNameParse((const FcChar8*) fontName); if (!pat) { DEBUG_ERROR("FCNameParse failed"); return NULL; } FcConfigSubstitute(FontConfig, pat, FcMatchPattern); FcDefaultSubstitute(pat); FcResult result; FcChar8 * file = NULL; FcPattern * match = FcFontMatch(FontConfig, pat, &result); if (!match) { DEBUG_ERROR("FcFontMatch Failed"); goto fail_parse; } if (FcPatternGetString(match, FC_FILE, 0, &file) == FcResultMatch) ttf = strdup((char *) file); else DEBUG_ERROR("Failed to locate the requested font: %s", fontName); FcPatternDestroy(match); fail_parse: FcPatternDestroy(pat); return ttf; } void util_freeUIFonts(void) { if (!FontConfig) return; FcConfigDestroy(FontConfig); FontConfig = NULL; FcFini(); } looking-glass-B6/cmake/000077500000000000000000000000001434445012300151545ustar00rootroot00000000000000looking-glass-B6/cmake/CheckSubmodule.cmake000066400000000000000000000015771434445012300210650ustar00rootroot00000000000000if (EXISTS "${PROJECT_TOP}/.git" AND ( (NOT EXISTS "${PROJECT_TOP}/repos/cimgui/.git") OR (NOT EXISTS "${PROJECT_TOP}/repos/LGMP/.git") OR (NOT EXISTS "${PROJECT_TOP}/repos/PureSpice/.git") OR (NOT EXISTS "${PROJECT_TOP}/repos/cimgui/imgui/.git") OR (NOT EXISTS "${PROJECT_TOP}/repos/wayland-protocols/.git") OR (NOT EXISTS "${PROJECT_TOP}/repos/nanosvg/.git") )) message(FATAL_ERROR "Submodules are not initialized. Run\n\tgit submodule update --init --recursive") endif() if (EXISTS "${PROJECT_TOP}/.git" AND NOT DEFINED DEVELOPER) execute_process( COMMAND git submodule summary WORKING_DIRECTORY "${PROJECT_TOP}" OUTPUT_VARIABLE SUBMODULE_SUMMARY ) if (NOT "${SUBMODULE_SUMMARY}" STREQUAL "") message(FATAL_ERROR "Wrong submodule version checked out. Run\n\tgit submodule update --init --recursive") endif() endif() looking-glass-B6/cmake/MakeObject.cmake000066400000000000000000000033351434445012300201660ustar00rootroot00000000000000function(make_object out_var) set(result) set(result_h) foreach(in_f ${ARGN}) file(RELATIVE_PATH out_f ${CMAKE_CURRENT_SOURCE_DIR} "${CMAKE_CURRENT_SOURCE_DIR}/${in_f}") set(out_h "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.h") set(out_f "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.o") string(REGEX REPLACE "[/.-]" "_" sym_in "${in_f}") add_custom_command(OUTPUT ${out_f} COMMAND ${CMAKE_LINKER} -r -b binary -o ${out_f} "${in_f}" COMMAND ${CMAKE_OBJCOPY} --rename-section .data=.rodata,CONTENTS,ALLOC,LOAD,READONLY,DATA ${out_f} ${out_f} COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_start=b_${sym_in} ${out_f} ${out_f} COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_end=b_${sym_in}_end ${out_f} ${out_f} COMMAND ${CMAKE_OBJCOPY} --strip-symbol _binary_${sym_in}_size ${out_f} ${out_f} DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${in_f}" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Creating object from ${in_f}" VERBATIM ) file(WRITE ${out_h} "extern const char b_${sym_in}[];\n") file(APPEND ${out_h} "extern const char b_${sym_in}_end[];\n") file(APPEND ${out_h} "#define b_${sym_in}_size (b_${sym_in}_end - b_${sym_in})\n") get_filename_component(h_dir ${out_h} DIRECTORY) list(APPEND result_h ${h_dir}) list(APPEND result ${out_f}) endforeach() list(REMOVE_DUPLICATES result_h) set(${out_var}_OBJS "${result}" PARENT_SCOPE) set(${out_var}_INCS "${result_h}" PARENT_SCOPE) endfunction() function(make_defines in_file out_file) add_custom_command(OUTPUT ${out_file} COMMAND grep "^#define" "${in_file}" > "${out_file}" DEPENDS ${in_file} COMMENT "Creating #defines from ${in_file}" ) endfunction() looking-glass-B6/cmake/OptimizeForNative.cmake000066400000000000000000000023451434445012300216000ustar00rootroot00000000000000set(OPTIMIZE_FOR_NATIVE AUTO CACHE STRING "Build with -march=native") set_property(CACHE OPTIMIZE_FOR_NATIVE PROPERTY STRINGS AUTO ON OFF) set(OPTIMIZE_FOR_NATIVE_ARCH "none") if(OPTIMIZE_FOR_NATIVE STREQUAL "ON" OR (OPTIMIZE_FOR_NATIVE STREQUAL "AUTO" AND OPTIMIZE_FOR_NATIVE_DEFAULT)) CHECK_C_COMPILER_FLAG("-march=native" COMPILER_SUPPORTS_MARCH_NATIVE) if(COMPILER_SUPPORTS_MARCH_NATIVE) set(OPTIMIZE_FOR_NATIVE_ARCH "native") set(OPTIMIZE_FOR_NATIVE_ON ON) endif() elseif(OPTIMIZE_FOR_NATIVE STREQUAL "AUTO" OR OPTIMIZE_FOR_NATIVE STREQUAL "OFF") CHECK_C_COMPILER_FLAG("-march=x86-64-v2" COMPILER_SUPPORTS_MARCH_X86_64_V2) if(COMPILER_SUPPORTS_MARCH_X86_64_V2) set(OPTIMIZE_FOR_NATIVE_ARCH "x86-64-v2") else() CHECK_C_COMPILER_FLAG("-march=nehalem" COMPILER_SUPPORTS_MARCH_NEHALEM) if(COMPILER_SUPPORTS_MARCH_NEHALEM) set(OPTIMIZE_FOR_NATIVE_ARCH "nehalem") endif() endif() else() set(OPTIMIZE_FOR_NATIVE_ARCH "${OPTIMIZE_FOR_NATIVE}") set(OPTIMIZE_FOR_NATIVE_ON ON) endif() if(NOT OPTIMIZE_FOR_NATIVE_ARCH STREQUAL "none") add_compile_options("-march=${OPTIMIZE_FOR_NATIVE_ARCH}") endif() add_feature_info(OPTIMIZE_FOR_NATIVE OPTIMIZE_FOR_NATIVE_ON "Optimized for ${OPTIMIZE_FOR_NATIVE_ARCH}") looking-glass-B6/cmake/UninstallTarget.cmake000066400000000000000000000004321434445012300212750ustar00rootroot00000000000000if (NOT TARGET uninstall) configure_file( "${CMAKE_CURRENT_LIST_DIR}/uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/uninstall.cmake" IMMEDIATE @ONLY) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/uninstall.cmake) endif() looking-glass-B6/cmake/uninstall.cmake.in000066400000000000000000000017051434445012300205770ustar00rootroot00000000000000# Uninstall file from https://gitlab.kitware.com/cmake/community/-/wikis/FAQ#can-i-do-make-uninstall-with-cmake if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") endif() file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) string(REGEX REPLACE "\n" ";" files "${files}") foreach(file ${files}) message(STATUS "Uninstalling $ENV{DESTDIR}${file}") if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") exec_program( "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" OUTPUT_VARIABLE rm_out RETURN_VALUE rm_retval ) if(NOT "${rm_retval}" STREQUAL 0) message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") endif() else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") message(STATUS "File $ENV{DESTDIR}${file} does not exist.") endif() endforeach() looking-glass-B6/common/000077500000000000000000000000001434445012300153645ustar00rootroot00000000000000looking-glass-B6/common/CMakeLists.txt000066400000000000000000000015501434445012300201250ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(lg_common LANGUAGES C) list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") include_directories( ${PROJECT_SOURCE_DIR}/include ) add_definitions(-D_GNU_SOURCE) if(ENABLE_BACKTRACE) add_definitions(-DENABLE_BACKTRACE) endif() if (CMAKE_C_COMPILER_ID STREQUAL "Clang") add_compile_options( "-Wno-unknown-warning-option" ) endif() add_subdirectory(src/platform) set(COMMON_SOURCES src/appstrings.c src/stringutils.c src/stringlist.c src/option.c src/framebuffer.c src/KVMFR.c src/countedbuffer.c src/rects.c src/runningavg.c src/ringbuffer.c src/vector.c src/cpuinfo.c src/debug.c src/ll.c ) add_library(lg_common STATIC ${COMMON_SOURCES}) target_link_libraries(lg_common lg_common_platform) target_include_directories(lg_common INTERFACE include PRIVATE src ) looking-glass-B6/common/cmake/000077500000000000000000000000001434445012300164445ustar00rootroot00000000000000looking-glass-B6/common/cmake/FindBFD.cmake000066400000000000000000000024751434445012300206520ustar00rootroot00000000000000# Try to find the BFD librairies # BFD_FOUND - system has BFD lib # BFD_INCLUDE_DIR - the BFD include directory # BFD_LIBRARIES - Libraries needed to use BFD if (BFD_INCLUDE_DIR AND BFD_LIBRARIES) # Already in cache, be silent set(BFD_FIND_QUIETLY TRUE) endif (BFD_INCLUDE_DIR AND BFD_LIBRARIES) find_path(BFD_INCLUDE_DIR NAMES bfd.h) find_library(BFD_LIBRARIES NAMES bfd) include(FindPackageHandleStandardArgs) if (";${BFD_LIBRARIES};" MATCHES "bfd.a;") MESSAGE(STATUS "Linking against static bfd") find_library(BFD_LIBIBERTY_LIBRARIES NAMES libiberty.a) find_package_handle_standard_args(BFD_LIBIBERTY DEFAULT_MSG BFD_LIBIBERTY_LIBRARIES) find_library(BFD_LIBZ_LIBRARIES NAMES libz.a) find_package_handle_standard_args(BFD_LIBZ DEFAULT_MSG BFD_LIBZ_LIBRARIES) if (NOT ${BFD_LIBIBERTY_FOUND}) message(FATAL_ERROR "Using static libbfd.a, but libiberty.a not available") elseif (NOT ${BFD_LIBZ_FOUND}) message(FATAL_ERROR "Using static libbfd.a, but libz.a not available") else() list(APPEND BFD_LIBRARIES ${BFD_LIBIBERTY_LIBRARIES} ${BFD_LIBZ_LIBRARIES}) endif() endif() MESSAGE(STATUS "BFD libs: " "${BFD_LIBRARIES}") find_package_handle_standard_args(BFD DEFAULT_MSG BFD_LIBRARIES BFD_INCLUDE_DIR) MESSAGE(STATUS "BFD libs: " "${BFD_LIBRARIES}") mark_as_advanced(BFD_INCLUDE_DIR BFD_LIBRARIES) looking-glass-B6/common/include/000077500000000000000000000000001434445012300170075ustar00rootroot00000000000000looking-glass-B6/common/include/common/000077500000000000000000000000001434445012300202775ustar00rootroot00000000000000looking-glass-B6/common/include/common/KVMFR.h000066400000000000000000000101131434445012300213310ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_KVMFR_ #define _H_LG_COMMON_KVMFR_ #pragma once #include #include #include "types.h" #define KVMFR_MAGIC "KVMFR---" #define KVMFR_VERSION 19 #define KVMFR_MAX_DAMAGE_RECTS 64 #define LGMP_Q_POINTER 1 #define LGMP_Q_FRAME 2 #define LGMP_Q_FRAME_LEN 2 #define LGMP_Q_POINTER_LEN 20 enum { CURSOR_FLAG_POSITION = 0x1, CURSOR_FLAG_VISIBLE = 0x2, CURSOR_FLAG_SHAPE = 0x4 }; typedef uint32_t KVMFRCursorFlags; enum { KVMFR_FEATURE_SETCURSORPOS = 0x1 }; typedef uint32_t KVMFRFeatureFlags; enum { KVMFR_MESSAGE_SETCURSORPOS }; typedef uint32_t KVMFRMessageType; typedef struct KVMFR { char magic[8]; uint32_t version; char hostver[32]; KVMFRFeatureFlags features; //KVMFRRecords start here if there are any } KVMFR; typedef struct KVMFRRecord { uint8_t type; uint32_t size; uint8_t data[]; } KVMFRRecord; enum { KVMFR_RECORD_VMINFO = 1, KVMFR_RECORD_OSINFO }; typedef enum { KVMFR_OS_LINUX, KVMFR_OS_BSD, KVMFR_OS_OSX, KVMFR_OS_WINDOWS, KVMFR_OS_OTHER } KVMFROS; typedef struct KVMFRRecord_VMInfo { uint8_t uuid [16]; // the guest's UUID char capture[32]; // the capture device in use uint8_t cpus; // number of CPUs uint8_t cores; // number of CPU cores uint8_t sockets; // number of CPU sockets char model[]; } KVMFRRecord_VMInfo; typedef struct KVMFRRecord_OSInfo { uint8_t os; // KVMFR_OS_* char name[]; // friendly name } KVMFRRecord_OSInfo; typedef struct KVMFRCursor { int16_t x, y; // cursor x & y position CursorType type; // shape buffer data type int8_t hx, hy; // shape hotspot x & y uint32_t width; // width of the shape uint32_t height; // height of the shape uint32_t pitch; // row length in bytes of the shape } KVMFRCursor; enum { FRAME_FLAG_BLOCK_SCREENSAVER = 0x1, FRAME_FLAG_REQUEST_ACTIVATION = 0x2, FRAME_FLAG_TRUNCATED = 0x4 // ivshmem was too small for the frame }; typedef uint32_t KVMFRFrameFlags; typedef struct KVMFRFrame { uint32_t formatVer; // the frame format version number uint32_t frameSerial; // the unique frame number FrameType type; // the frame data type uint32_t screenWidth; // the client's screen width uint32_t screenHeight; // the client's screen height uint32_t frameWidth; // the frame width uint32_t frameHeight; // the frame height FrameRotation rotation; // the frame rotation uint32_t stride; // the row stride (zero if compressed data) uint32_t pitch; // the row pitch (stride in bytes or the compressed frame size) uint32_t offset; // offset from the start of this header to the FrameBuffer header uint32_t damageRectsCount; // the number of damage rectangles (zero for full-frame damage) FrameDamageRect damageRects[KVMFR_MAX_DAMAGE_RECTS]; KVMFRFrameFlags flags; // bit field combination of FRAME_FLAG_* } KVMFRFrame; typedef struct KVMFRMessage { KVMFRMessageType type; } KVMFRMessage; typedef struct KVMFRSetCursorPos { KVMFRMessage msg; int32_t x, y; } KVMFRSetCursorPos; #endif looking-glass-B6/common/include/common/appstrings.h000066400000000000000000000023001434445012300226350ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/types.h" extern const char * LG_COPYRIGHT_STR; extern const char * LG_WEBSITE_URL; extern const char * LG_LICENSE_STR; extern const char * LG_DONATION_STR; extern const char * LG_DONATION_URL; extern const StringPair LG_HELP_LINKS[]; struct LGTeamMember { const char * name; const char * blurb; const StringPair donate[10]; }; extern const struct LGTeamMember LG_TEAM[]; looking-glass-B6/common/include/common/array.h000066400000000000000000000017441434445012300215740ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _LG_ARRAY_H_ #define _LG_ARRAY_H_ #define ARRAY_LENGTH(arr) (sizeof(arr) / sizeof(*(arr))) #define ALIGN_PAD(value, align) (((value) + (align) - 1) & -(align)) #endif looking-glass-B6/common/include/common/countedbuffer.h000066400000000000000000000022341434445012300233040ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_COUNTEDBUFFER_ #define _H_LG_COMMON_COUNTEDBUFFER_ #include typedef struct CountedBuffer { _Atomic(size_t) refs; size_t size; char data[]; } CountedBuffer; struct CountedBuffer * countedBufferNew(size_t size); void countedBufferAddRef(CountedBuffer * buffer); void countedBufferRelease(CountedBuffer ** buffer); #endif looking-glass-B6/common/include/common/cpuinfo.h000066400000000000000000000020241434445012300221110ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_CPUINFO_ #define _H_LG_COMMON_CPUINFO_ #include #include bool lgCPUInfo(char * model, size_t modelSize, int * procs, int * cores, int * sockets); void lgDebugCPU(void); #endif looking-glass-B6/common/include/common/crash.h000066400000000000000000000017261434445012300215560ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_CRASH_ #define _H_LG_COMMON_CRASH_ #include bool installCrashHandler(const char * exe); void cleanupCrashHandler(void); #endif looking-glass-B6/common/include/common/debug.h000066400000000000000000000125571434445012300215500ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_DEBUG_ #define _H_LG_COMMON_DEBUG_ #ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS #endif #include #include #include #include "time.h" enum DebugLevel { DEBUG_LEVEL_NONE, DEBUG_LEVEL_INFO, DEBUG_LEVEL_WARN, DEBUG_LEVEL_ERROR, DEBUG_LEVEL_FIXME, DEBUG_LEVEL_FATAL }; extern const char ** debug_lookup; void debug_init(void); #ifdef ENABLE_BACKTRACE void printBacktrace(void); #define DEBUG_PRINT_BACKTRACE() printBacktrace() #else #define DEBUG_PRINT_BACKTRACE() #endif #if defined(_WIN32) && !defined(__GNUC__) #define DIRECTORY_SEPARATOR '\\' #else #define DIRECTORY_SEPARATOR '/' #endif #ifdef __GNUC__ #define DEBUG_UNREACHABLE_MARKER() __builtin_unreachable() #elif defined(_MSC_VER) #define DEBUG_UNREACHABLE_MARKER() __assume(0) #else #define DEBUG_UNREACHABLE_MARKER() #endif #define STRIPPATH(s) ( \ sizeof(s) > 2 && (s)[sizeof(s)- 3] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 2 : \ sizeof(s) > 3 && (s)[sizeof(s)- 4] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 3 : \ sizeof(s) > 4 && (s)[sizeof(s)- 5] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 4 : \ sizeof(s) > 5 && (s)[sizeof(s)- 6] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 5 : \ sizeof(s) > 6 && (s)[sizeof(s)- 7] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 6 : \ sizeof(s) > 7 && (s)[sizeof(s)- 8] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 7 : \ sizeof(s) > 8 && (s)[sizeof(s)- 9] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 8 : \ sizeof(s) > 9 && (s)[sizeof(s)-10] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 9 : \ sizeof(s) > 10 && (s)[sizeof(s)-11] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 10 : \ sizeof(s) > 11 && (s)[sizeof(s)-12] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 11 : \ sizeof(s) > 12 && (s)[sizeof(s)-13] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 12 : \ sizeof(s) > 13 && (s)[sizeof(s)-14] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 13 : \ sizeof(s) > 14 && (s)[sizeof(s)-15] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 14 : \ sizeof(s) > 15 && (s)[sizeof(s)-16] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 15 : \ sizeof(s) > 16 && (s)[sizeof(s)-17] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 16 : \ sizeof(s) > 17 && (s)[sizeof(s)-18] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 17 : \ sizeof(s) > 18 && (s)[sizeof(s)-19] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 18 : \ sizeof(s) > 19 && (s)[sizeof(s)-20] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 19 : \ sizeof(s) > 20 && (s)[sizeof(s)-21] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 20 : \ sizeof(s) > 21 && (s)[sizeof(s)-22] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 21 : (s)) #define DEBUG_PRINT(level, fmt, ...) do { \ fprintf(stderr, "%s%12" PRId64 "%20s:%-4u | %-30s | " fmt "%s\n", \ debug_lookup[level], microtime(), STRIPPATH(__FILE__), \ __LINE__, __FUNCTION__, ##__VA_ARGS__, debug_lookup[DEBUG_LEVEL_NONE]); \ } while (0) #define DEBUG_BREAK() DEBUG_PRINT(DEBUG_LEVEL_INFO, "================================================================================") #define DEBUG_INFO(fmt, ...) DEBUG_PRINT(DEBUG_LEVEL_INFO, fmt, ##__VA_ARGS__) #define DEBUG_WARN(fmt, ...) DEBUG_PRINT(DEBUG_LEVEL_WARN, fmt, ##__VA_ARGS__) #define DEBUG_ERROR(fmt, ...) DEBUG_PRINT(DEBUG_LEVEL_ERROR, fmt, ##__VA_ARGS__) #define DEBUG_FIXME(fmt, ...) DEBUG_PRINT(DEBUG_LEVEL_FIXME, fmt, ##__VA_ARGS__) #define DEBUG_FATAL(fmt, ...) do { \ DEBUG_BREAK(); \ DEBUG_PRINT(DEBUG_LEVEL_FATAL, fmt, ##__VA_ARGS__); \ DEBUG_PRINT_BACKTRACE(); \ abort(); \ DEBUG_UNREACHABLE_MARKER(); \ } while(0) #define DEBUG_ASSERT_PRINT(...) DEBUG_ERROR("Assertion failed: %s", #__VA_ARGS__) #ifdef NDEBUG #define DEBUG_ASSERT(...) do { \ if (!(__VA_ARGS__)) \ DEBUG_ASSERT_PRINT(__VA_ARGS__); \ } while (0) #else #define DEBUG_ASSERT(...) do { \ if (!(__VA_ARGS__)) \ { \ DEBUG_ASSERT_PRINT(__VA_ARGS__); \ abort(); \ } \ } while (0) #endif #define DEBUG_UNREACHABLE() DEBUG_FATAL("Unreachable code reached") #if defined(DEBUG_SPICE) | defined(DEBUG_IVSHMEM) #define DEBUG_PROTO(fmt, args...) DEBUG_PRINT("[P]", fmt, ##args) #else #define DEBUG_PROTO(fmt, ...) do {} while(0) #endif void debug_info(const char * file, unsigned int line, const char * function, const char * format, ...) __attribute__((format (printf, 4, 5))); void debug_warn(const char * file, unsigned int line, const char * function, const char * format, ...) __attribute__((format (printf, 4, 5))); void debug_error(const char * file, unsigned int line, const char * function, const char * format, ...) __attribute__((format (printf, 4, 5))); #endif looking-glass-B6/common/include/common/event.h000066400000000000000000000030611434445012300215710ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_EVENT_ #define _H_LG_COMMON_EVENT_ #include #include #define TIMEOUT_INFINITE ((unsigned int)~0) typedef struct LGEvent LGEvent; LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime); void lgFreeEvent (LGEvent * handle); bool lgWaitEvent (LGEvent * handle, unsigned int timeout); bool lgSignalEvent(LGEvent * handle); bool lgResetEvent (LGEvent * handle); // os specific method to wrap/convert a native event into a LGEvent // for windows this is an event HANDLE LGEvent * lgWrapEvent(void * handle); // Posix specific, not implmented/possible in windows bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts); bool lgWaitEventNS (LGEvent * handle, unsigned int timeout); #endif looking-glass-B6/common/include/common/framebuffer.h000066400000000000000000000050421434445012300227350ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_FRAMEBUFFER_ #define _H_LG_COMMON_FRAMEBUFFER_ #include #include #include #include #define FB_CHUNK_SIZE 1048576 // 1MB #define FB_SPIN_LIMIT 10000 // 10ms #define FB_WP_TYPE atomic_uint_least32_t #define FB_WP_SIZE sizeof(FB_WP_TYPE) typedef struct stFrameBuffer { FB_WP_TYPE wp; uint8_t data[0]; } FrameBuffer; typedef bool (*FrameBufferReadFn)(void * opaque, const void * src, size_t size); /** * Wait for the framebuffer to fill to the specified size */ bool framebuffer_wait(const FrameBuffer * frame, size_t size); /** * Read data from the KVMFRFrame into the dst buffer */ bool framebuffer_read(const FrameBuffer * frame, void * dst, size_t dstpitch, size_t height, size_t width, size_t bpp, size_t pitch); /** * Read data from the KVMFRFrame using a callback */ bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width, size_t bpp, size_t pitch, FrameBufferReadFn fn, void * opaque); /** * Prepare the framebuffer for writing */ void framebuffer_prepare(FrameBuffer * frame); /** * Write data from the src buffer into the KVMFRFrame */ bool framebuffer_write(FrameBuffer * frame, const void * src, size_t size); /** * Gets the underlying data buffer of the framebuffer. * For custom read routines only. */ const uint8_t * framebuffer_get_buffer(const FrameBuffer * frame); /** * Gets the underlying data buffer of the framebuffer. * For custom write routines only. */ uint8_t * framebuffer_get_data(FrameBuffer * frame); /** * Sets the write pointer of the framebuffer. * For custom write routines only. */ void framebuffer_set_write_ptr(FrameBuffer * frame, size_t size); #endif looking-glass-B6/common/include/common/ivshmem.h000066400000000000000000000026631434445012300221270ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_IVSHMEM_ #define _H_LG_COMMON_IVSHMEM_ #include #include struct IVSHMEM { unsigned int size; void * mem; // internal use void * opaque; }; void ivshmemOptionsInit(void); bool ivshmemInit(struct IVSHMEM * dev); bool ivshmemOpen(struct IVSHMEM * dev); bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDev); void ivshmemClose(struct IVSHMEM * dev); void ivshmemFree(struct IVSHMEM * dev); /* Linux KVMFR support only for now (VM->VM) */ bool ivshmemHasDMA (struct IVSHMEM * dev); int ivshmemGetDMABuf(struct IVSHMEM * dev, uint64_t offset, uint64_t size); #endif looking-glass-B6/common/include/common/ll.h000066400000000000000000000047261434445012300210700ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LL_ #define _H_LL_ #include #include #include "common/locking.h" struct ll_item { void * data; struct ll_item * prev, * next; }; struct ll { struct ll_item * head; struct ll_item * tail; unsigned int count; LG_Lock lock; }; struct ll * ll_new(void); void ll_free (struct ll * list); void ll_push (struct ll * list, void * data); bool ll_shift (struct ll * list, void ** data); bool ll_peek_head(struct ll * list, void ** data); bool ll_peek_tail(struct ll * list, void ** data); #define ll_lock(ll) LG_LOCK((ll)->lock) #define ll_unlock(ll) LG_UNLOCK((ll)->lock) #define ll_forEachNL(ll, item, v) \ for(struct ll_item * item = (ll)->head, \ * _ = (item) ? (item)->next : NULL; (item); (item) = NULL) \ for((v) = (__typeof__(v))((item)->data); (item); (item) = _, \ _ = (item) ? (item)->next : NULL, \ (v) = (item) ? (__typeof__(v))((item)->data) : NULL) static inline unsigned int ll_count(struct ll * list) { return list->count; } static inline void ll_removeNL(struct ll * list, struct ll_item * item) { --list->count; if (list->head == item) list->head = item->next; if (list->tail == item) list->tail = item->prev; if (item->prev) item->prev->next = item->next; if (item->next) item->next->prev = item->prev; } static inline bool ll_removeData(struct ll * list, void * match) { void * data; ll_lock(list); ll_forEachNL(list, item, data) if (data == match) { ll_removeNL(list, item); ll_unlock(list); free(item); return true; } ll_unlock(list); return false; } #endif looking-glass-B6/common/include/common/locking.h000066400000000000000000000026401434445012300221000ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_LOCKING_ #define _H_LG_COMMON_LOCKING_ #include "time.h" #include #define LG_LOCK_MODE "Atomic" typedef atomic_flag LG_Lock; #define LG_LOCK_INIT(x) atomic_flag_clear(&(x)) #define LG_LOCK(x) \ while(atomic_flag_test_and_set_explicit(&(x), memory_order_acquire)) { ; } #define LG_UNLOCK(x) \ atomic_flag_clear_explicit(&(x), memory_order_release); #define LG_LOCK_FREE(x) #define INTERLOCKED_INC(x) atomic_fetch_add((x), 1) #define INTERLOCKED_DEC(x) atomic_fetch_sub((x), 1) #define INTERLOCKED_SECTION(lock, ...) \ LG_LOCK(lock) \ __VA_ARGS__ \ LG_UNLOCK(lock) #endif looking-glass-B6/common/include/common/open.h000066400000000000000000000016521434445012300214150ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _LG_COMMON_OPEN_H_ #define _LG_COMMON_OPEN_H_ #include bool lgOpenURL(const char * url); #endif looking-glass-B6/common/include/common/option.h000066400000000000000000000061241434445012300217630ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_COMMON_OPTION_ #define _H_COMMON_OPTION_ #include #include #include "common/stringlist.h" enum OptionType { OPTION_TYPE_NONE = 0, OPTION_TYPE_INT, OPTION_TYPE_STRING, OPTION_TYPE_BOOL, OPTION_TYPE_FLOAT, OPTION_TYPE_CUSTOM }; enum doHelpMode { DOHELP_MODE_NO = 0, DOHELP_MODE_YES, DOHELP_MODE_RST }; struct Option; struct Option { char * module; char * name; char * description; const char shortopt; bool preset; enum OptionType type; union { int x_int; char * x_string; bool x_bool; float x_float; void * x_custom; } value; bool (*parser )(struct Option * opt, const char * str); bool (*validator)(struct Option * opt, const char ** error); char * (*toString )(struct Option * opt); StringList (*getValues)(struct Option * opt); void (*printHelp)(void); // internal use only bool failed_set; }; // register an NULL terminated array of options bool option_register(struct Option options[]); // lookup the value of an option struct Option * option_get (const char * module, const char * name); int option_get_int (const char * module, const char * name); const char * option_get_string(const char * module, const char * name); bool option_get_bool (const char * module, const char * name); float option_get_float (const char * module, const char * name); // update the value of an option void option_set_int (const char * module, const char * name, int value); void option_set_string(const char * module, const char * name, const char * value); void option_set_bool (const char * module, const char * name, bool value); void option_set_float (const char * module, const char * name, float value); // called by the main application to parse the command line arguments bool option_parse(int argc, char * argv[]); // called by the main application to load configuration from a file bool option_load(const char * filename); // called by the main application to validate the option values bool option_validate(void); // print out the options, help, and their current values void option_print(void); // dump preset options in ini format into the file bool option_dump_preset(FILE * file); // final cleanup void option_free(void); #endif looking-glass-B6/common/include/common/paths.h000066400000000000000000000017301434445012300215700ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_DIRS_ #define _H_LG_COMMON_DIRS_ void lgPathsInit(const char * appName); const char * lgConfigDir(void); const char * lgDataDir(void); #endif looking-glass-B6/common/include/common/rects.h000066400000000000000000000033551434445012300215760ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _LG_COMMON_RECTS_H_ #define _LG_COMMON_RECTS_H_ #include #include #include "common/framebuffer.h" #include "common/types.h" inline static void rectCopyUnaligned(uint8_t * dest, const uint8_t * src, int ystart, int yend, int dx, int dstStride, int srcStride, int width) { for (int i = ystart; i < yend; ++i) { unsigned int srcOffset = i * srcStride + dx; unsigned int dstOffset = i * dstStride + dx; memcpy(dest + dstOffset, src + srcOffset, width); } } void rectsBufferToFramebuffer(FrameDamageRect * rects, int count, FrameBuffer * frame, int dstStride, int height, const uint8_t * src, int srcStride); void rectsFramebufferToBuffer(FrameDamageRect * rects, int count, uint8_t * dst, int dstStride, int height, const FrameBuffer * frame, int srcStride); int rectsMergeOverlapping(FrameDamageRect * rects, int count); int rectsRejectContained(FrameDamageRect * rects, int count); #endif looking-glass-B6/common/include/common/ringbuffer.h000066400000000000000000000063531434445012300226100ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include typedef struct RingBuffer * RingBuffer; RingBuffer ringbuffer_new(int length, size_t valueSize); /* In an unbounded ring buffer, the read and write pointers are free to move * independently of one another. This is useful if your input and output streams * are progressing at the same rate on average, and you want to keep the * latency stable in the event than an underrun or overrun occurs. * * If an underrun occurs (i.e., there is not enough data in the buffer to * satisfy a read request), the missing values with be filled with zeros. When * the writer catches up, the same number of values will be skipped from the * input. * * If an overrun occurs (i.e., there is not enough free space in the buffer to * satisfy a write request), excess values will be discarded. When the reader * catches up, the same number of values will be zeroed in the output. */ RingBuffer ringbuffer_newUnbounded(int length, size_t valueSize); void ringbuffer_free(RingBuffer * rb); void ringbuffer_push(RingBuffer rb, const void * value); void ringbuffer_reset(RingBuffer rb); /* Note that the following functions are NOT thread-safe */ int ringbuffer_getLength(const RingBuffer rb); int ringbuffer_getStart (const RingBuffer rb); int ringbuffer_getCount (const RingBuffer rb); void * ringbuffer_getValues(const RingBuffer rb); /* Appends up to count values to the buffer returning the number of values * appended. If the buffer is unbounded, the return value is always count; * excess values will be discarded if the buffer is full. Pass a null values * pointer to write zeros to the buffer. Count may be negative in unbounded mode * to seek backwards. * Note: This function is thread-safe */ int ringbuffer_append(const RingBuffer rb, const void * values, int count); /* Consumes up to count values from the buffer returning the number of values * consumed. If the buffer is unbounded, the return value is always count; * excess values will be zeroed if there is not enough data in the buffer. Pass * a null values pointer to move the read pointer without reading any data. * Count may be negative in unbounded mode to seek backwards. * Note: This function is thread-safe */ int ringbuffer_consume(const RingBuffer rb, void * values, int count); typedef bool (*RingBufferIterator)(int index, void * value, void * udata); void ringbuffer_forEach(const RingBuffer rb, RingBufferIterator fn, void * udata, bool reverse); looking-glass-B6/common/include/common/runningavg.h000066400000000000000000000021001434445012300226170ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include typedef struct RunningAvg * RunningAvg; RunningAvg runningavg_new(int length); void runningavg_free(RunningAvg * ra); void runningavg_push(RunningAvg ra, int64_t value); void runningavg_reset(RunningAvg ra); double runningavg_calc(RunningAvg ra); looking-glass-B6/common/include/common/stringlist.h000066400000000000000000000025001434445012300226470ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_STRINGLIST_ #define _H_LG_COMMON_STRINGLIST_ #include typedef struct StringList * StringList; StringList stringlist_new (bool owns_strings); void stringlist_free (StringList * sl); int stringlist_push (StringList sl, char * str); void stringlist_remove(StringList sl, unsigned int index); unsigned int stringlist_count (StringList sl); char * stringlist_at (StringList sl, unsigned int index); void stringlist_clear (StringList sl); #endif looking-glass-B6/common/include/common/stringutils.h000066400000000000000000000024621434445012300230430ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_STRINGUTILS #define _H_LG_COMMON_STRINGUTILS #include // vsprintf but with buffer allocation int valloc_sprintf(char ** str, const char * format, va_list ap) __attribute__ ((format (printf, 2, 0))); // sprintf but with buffer allocation int alloc_sprintf(char ** str, const char * format, ...) __attribute__ ((format (printf, 2, 3))); // Find value in a list separated by delimiter. bool str_containsValue(const char * list, char delimiter, const char * value); #endif looking-glass-B6/common/include/common/sysinfo.h000066400000000000000000000016611434445012300221460ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_SYSUTILS #define _H_LG_COMMON_SYSUTILS // returns the page size long sysinfo_getPageSize(void); #endif looking-glass-B6/common/include/common/thread.h000066400000000000000000000022031434445012300217140ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_THREAD_ #define _H_LG_COMMON_THREAD_ #include typedef struct LGThread LGThread; typedef int (*LGThreadFunction)(void * opaque); bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle); bool lgJoinThread (LGThread * handle, int * resultCode); #endif looking-glass-B6/common/include/common/time.h000066400000000000000000000064501434445012300214130ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include #include #if defined(_WIN32) #include void windowsSetTimerResolution(void); NTSYSCALLAPI NTSTATUS NTAPI NtDelayExecution( _In_ BOOLEAN Alertable, _In_opt_ PLARGE_INTEGER DelayInterval ); #endif typedef struct LGTimer LGTimer; static inline uint64_t microtime(void) { #if defined(_WIN32) static LARGE_INTEGER freq = { 0 }; if (!freq.QuadPart) QueryPerformanceFrequency(&freq); LARGE_INTEGER time; QueryPerformanceCounter(&time); return time.QuadPart / (freq.QuadPart / 1000000LL); #else struct timespec time; clock_gettime(CLOCK_MONOTONIC, &time); return (uint64_t)time.tv_sec * 1000000LL + time.tv_nsec / 1000LL; #endif } static inline void nsleep(uint64_t ns) { #if defined(_WIN32) LARGE_INTEGER interval = { .QuadPart = -(int64_t)(ns / 100LL) }; NtDelayExecution(FALSE, &interval); #else const struct timespec ts = { .tv_sec = ns / 1000000000, .tv_nsec = ns % 1000000000, }; nanosleep(&ts, NULL); #endif } static inline uint64_t nanotime(void) { #if defined(_WIN32) static double multiplier = 0.0; if (!multiplier) { LARGE_INTEGER freq = { 0 }; QueryPerformanceFrequency(&freq); multiplier = 1e9 / freq.QuadPart; } LARGE_INTEGER time; QueryPerformanceCounter(&time); return (uint64_t) (time.QuadPart * multiplier); #else struct timespec time; clock_gettime(CLOCK_MONOTONIC_RAW, &time); return ((uint64_t)time.tv_sec * 1000000000LL) + time.tv_nsec; #endif } static inline void tsDiff(struct timespec *diff, const struct timespec *left, const struct timespec *right) { diff->tv_sec = left->tv_sec - right->tv_sec; diff->tv_nsec = left->tv_nsec - right->tv_nsec; if (diff->tv_nsec < 0) { --diff->tv_sec; diff->tv_nsec += 1000000000; } } static inline uint32_t __iter_div_u64_rem(uint64_t dividend, uint32_t divisor, uint64_t *remainder) { uint32_t ret = 0; while (dividend >= divisor) { /* The following asm() prevents the compiler from optimising this loop into a modulo operation. */ asm("" : "+rm"(dividend)); dividend -= divisor; ret++; } *remainder = dividend; return ret; } static inline void tsAdd(struct timespec *a, uint64_t ns) { a->tv_sec += __iter_div_u64_rem(a->tv_nsec + ns, 1000000000L, &ns); a->tv_nsec = ns; } typedef bool (*LGTimerFn)(void * udata); bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn, void * udata, LGTimer ** result); void lgTimerDestroy(LGTimer * timer); looking-glass-B6/common/include/common/types.h000066400000000000000000000036441434445012300216230ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _LG_TYPES_H_ #define _LG_TYPES_H_ #include struct Point { int x, y; }; struct DoublePoint { double x, y; }; struct Rect { int x, y, w, h; }; struct DoubleRect { double x, y, w, h; }; struct Border { int left, top, right, bottom; }; typedef enum FrameType { FRAME_TYPE_INVALID , FRAME_TYPE_BGRA , // BGRA interleaved: B,G,R,A 32bpp FRAME_TYPE_RGBA , // RGBA interleaved: R,G,B,A 32bpp FRAME_TYPE_RGBA10 , // RGBA interleaved: R,G,B,A 10,10,10,2 bpp FRAME_TYPE_RGBA16F , // RGBA interleaved: R,G,B,A 16,16,16,16 bpp float FRAME_TYPE_MAX , // sentinel value } FrameType; typedef enum FrameRotation { FRAME_ROT_0, FRAME_ROT_90, FRAME_ROT_180, FRAME_ROT_270 } FrameRotation; typedef struct FrameDamageRect { uint32_t x; uint32_t y; uint32_t width; uint32_t height; } FrameDamageRect; extern const char * FrameTypeStr[FRAME_TYPE_MAX]; typedef enum CursorType { CURSOR_TYPE_COLOR , CURSOR_TYPE_MONOCHROME , CURSOR_TYPE_MASKED_COLOR } CursorType; typedef struct StringPair { const char * name; const char * value; } StringPair; #endif looking-glass-B6/common/include/common/util.h000066400000000000000000000023471434445012300214330ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_UTIL_ #define _H_LG_COMMON_UTIL_ #ifndef min #define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); \ _a < _b ? _a : _b; }) #endif #ifndef max #define max(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); \ _a > _b ? _a : _b; }) #endif #ifndef clamp #define clamp(v,a,b) min(max(v, a), b) #endif #define UPCAST(type, x) \ (type *)((uintptr_t)(x) - offsetof(type, base)) #endif looking-glass-B6/common/include/common/vector.h000066400000000000000000000052061434445012300217550ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include #include typedef struct Vector { size_t itemSize; size_t size; size_t capacity; void * data; } Vector; // Dynamically allocates the vector Vector * vector_alloc(size_t itemSize, size_t capacity); void vector_free(Vector * vector); // Uses existing vector, but dynamically allocates storage bool vector_create(Vector * vector, size_t itemSize, size_t capacity); void vector_destroy(Vector * vector); inline static size_t vector_size(Vector * vector) { return vector->size; } inline static void * vector_data(Vector * vector) { return vector->data; } void * vector_push(Vector * vector, void * item); void vector_pop(Vector * vector); void vector_remove(Vector * vector, size_t index); void vector_at(Vector * vector, size_t index, void * data); void * vector_ptrTo(Vector * vector, size_t index); void vector_clear(Vector * vector); #define vector_forEach(name, vector) \ for (char * vecIterCurrent = (vector)->data, \ * vecIterEnd = vecIterCurrent + (vector)->size * (vector)->itemSize; \ vecIterCurrent < vecIterEnd ? name = *(__typeof__(name) *)vecIterCurrent, true : false; \ vecIterCurrent += (vector)->itemSize) #define vector_forEachRef(name, vector) \ for (char * vecIterCurrent = (vector)->data, \ * vecIterEnd = vecIterCurrent + (vector)->size * (vector)->itemSize; \ vecIterCurrent < vecIterEnd ? name = (void *)vecIterCurrent, true : false; \ vecIterCurrent += (vector)->itemSize) #define vector_forEachIdx(index, name, vector) \ for (size_t index = 0; \ index < (vector)->size ? vector_at((vector), index, &(name)), true : false; \ ++index) #define vector_forEachRefIdx(index, name, vector) \ for (size_t index = 0; \ index < (vector)->size ? (name) = vector_ptrTo((vector), index), true : false; \ ++index) looking-glass-B6/common/include/common/version.h000066400000000000000000000016251434445012300221410ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_VERSION_ #define _H_LG_COMMON_VERSION_ extern char * BUILD_VERSION; #endif looking-glass-B6/common/include/common/windebug.h000066400000000000000000000023261434445012300222570ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _H_LG_COMMON_WINDEBUG_ #define _H_LG_COMMON_WINDEBUG_ #include "debug.h" #include #include #ifdef __cplusplus extern "C" { #endif void DebugWinError(const char * file, const unsigned int line, const char * function, const char * desc, HRESULT status); #define DEBUG_WINERROR(x, y) DebugWinError(STRIPPATH(__FILE__), __LINE__, __FUNCTION__, x, y) #ifdef __cplusplus } #endif #endif looking-glass-B6/common/src/000077500000000000000000000000001434445012300161535ustar00rootroot00000000000000looking-glass-B6/common/src/KVMFR.c000066400000000000000000000017571434445012300172160ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/KVMFR.h" const char * FrameTypeStr[FRAME_TYPE_MAX] = { "FRAME_TYPE_INVALID", "FRAME_TYPE_BGRA", "FRAME_TYPE_RGBA", "FRAME_TYPE_RGBA10", "FRAME_TYPE_RGBA16F" }; looking-glass-B6/common/src/appstrings.c000066400000000000000000000077741434445012300205300ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/appstrings.h" const char * LG_DONATION_STR = "Looking Glass is written and supported by a small group of developers in our " "free time and has been given to the world free of charge. If you find this " "program useful please consider supporting our efforts through the following " "address:"; const char * LG_DONATION_URL = "https://looking-glass.io/donations"; const char * LG_COPYRIGHT_STR = "Copyright © 2017-2022 The Looking Glass Authors"; const char * LG_WEBSITE_URL = "https://looking-glass.io"; const char * LG_LICENSE_STR = "This program is free software; you can redistribute it and/or modify it " "under the terms of the GNU General Public License as published by the Free " "Software Foundation; either version 2 of the License, or (at your option) " "any later version.\n" "\n" "This program is distributed in the hope that it will be useful, but WITHOUT " "ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or " "FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for " "more details.\n" "\n" "You should have received a copy of the GNU General Public License along " "with this program; if not, write to the Free Software Foundation, Inc., 59 " "Temple Place, Suite 330, Boston, MA 02111-1307 USA"; const StringPair LG_HELP_LINKS[] = { { .name = "Documentation", .value = "https://looking-glass.io/docs" }, { .name = "Wiki", .value = "https://looking-glass.io/wiki" }, { .name = "Discord", .value = "https://looking-glass.io/discord" }, { .name = "Level1Techs", .value = "https://level1techs.com/lg" }, { 0 } }; const struct LGTeamMember LG_TEAM[] = { { .name = "Geoffrey McRae (gnif)", .blurb = "Project lead and core developer of this program, from its initial " "creation through to what it is today. His roles include development " "and direction of the project as a whole, focusing on the X11 " "platform as well as maintaining the Looking Glass community on " "Discord and the Level1Tech forums.", .donate = { { 0 } } }, { .name = "Guanzhong Chen (quantum)", .blurb = "Major code contributor to Looking Glass, with a specific focus on " "improving Wayland support and the Windows side of things. He works " "on many things, from small cosmetic issues to major features. " "He implemented much of the Wayland backend, VM->Host DMABUF import, " "and damage tracking.", .donate = { { 0 } } }, { .name = "Tudor Brindus (Xyene)", .blurb = "Wayland developer. Thinks a lot about latency, and occasionally turns " "those thoughts into code.", .donate = { { 0 } } }, { .name = "Jonathan Rubenstein (JJRcop)", .blurb = "Documentation Guru and Discord Community Manager. Takes around four " "or five tries and weeks of delay to turn ideas and spitballing into " "tangible work, but tries to make the result look beautiful.", .donate = { { 0 } } }, { .name = "Chris Spencer (spencercw)", .blurb = "Developer. Knows enough about audio programming to cause problems for " "himself.", .donate = { { 0 } } }, { 0 } }; looking-glass-B6/common/src/countedbuffer.c000066400000000000000000000025511434445012300211550ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/countedbuffer.h" #include #include struct CountedBuffer * countedBufferNew(size_t size) { struct CountedBuffer * buffer = malloc(sizeof(*buffer) + size); if (!buffer) return NULL; atomic_init(&buffer->refs, 1); buffer->size = size; return buffer; } void countedBufferAddRef(struct CountedBuffer * buffer) { atomic_fetch_add(&buffer->refs, 1); } void countedBufferRelease(struct CountedBuffer ** buffer) { if (atomic_fetch_sub(&(*buffer)->refs, 1) == 1) { free(*buffer); *buffer = NULL; } } looking-glass-B6/common/src/cpuinfo.c000066400000000000000000000022751434445012300177700ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/cpuinfo.h" #include "common/debug.h" void lgDebugCPU(void) { char model[1024]; int procs; int cores; int sockets; if (!lgCPUInfo(model, sizeof model, &procs, &cores, &sockets)) { DEBUG_WARN("Failed to get CPU information"); return; } DEBUG_INFO("CPU Model: %s", model); DEBUG_INFO("CPU: %d sockets, %d cores, %d threads", sockets, cores, procs); } looking-glass-B6/common/src/debug.c000066400000000000000000000037571434445012300174210ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/debug.h" #include #include #include inline static void debug_level(enum DebugLevel level, const char * file, unsigned int line, const char * function, const char * format, va_list va) { const char * f = strrchr(file, DIRECTORY_SEPARATOR) + 1; fprintf(stderr, "%s%12" PRId64 "%20s:%-4u | %-30s | ", debug_lookup[level], microtime(), f, line, function); vfprintf(stderr, format, va); fprintf(stderr, "%s\n", debug_lookup[DEBUG_LEVEL_NONE]); } void debug_info(const char * file, unsigned int line, const char * function, const char * format, ...) { va_list va; va_start(va, format); debug_level(DEBUG_LEVEL_INFO, file, line, function, format, va); va_end(va); } void debug_warn(const char * file, unsigned int line, const char * function, const char * format, ...) { va_list va; va_start(va, format); debug_level(DEBUG_LEVEL_WARN, file, line, function, format, va); va_end(va); } void debug_error(const char * file, unsigned int line, const char * function, const char * format, ...) { va_list va; va_start(va, format); debug_level(DEBUG_LEVEL_ERROR, file, line, function, format, va); va_end(va); } looking-glass-B6/common/src/framebuffer.c000066400000000000000000000121461434445012300206070ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/framebuffer.h" #include "common/debug.h" //#define FB_PROFILE #ifdef FB_PROFILE #include "common/runningavg.h" #endif #include #include #include #include bool framebuffer_wait(const FrameBuffer * frame, size_t size) { while(atomic_load_explicit(&frame->wp, memory_order_acquire) < size) { int spinCount = 0; while(frame->wp < size) { if (++spinCount == FB_SPIN_LIMIT) return false; usleep(1); } } return true; } bool framebuffer_read(const FrameBuffer * frame, void * restrict dst, size_t dstpitch, size_t height, size_t width, size_t bpp, size_t pitch) { #ifdef FB_PROFILE static RunningAvg ra = NULL; static int raCount = 0; const uint64_t ts = microtime(); if (!ra) ra = runningavg_new(100); #endif uint8_t * restrict d = (uint8_t*)dst; uint_least32_t rp = 0; // copy in large 1MB chunks if the pitches match if (dstpitch == pitch) { size_t remaining = height * pitch; while(remaining) { const size_t copy = remaining < FB_CHUNK_SIZE ? remaining : FB_CHUNK_SIZE; if (!framebuffer_wait(frame, rp + copy)) return false; memcpy(d, frame->data + rp, copy); remaining -= copy; rp += copy; d += copy; } } else { // copy per line to match the pitch of the destination buffer const size_t linewidth = width * bpp; for(size_t y = 0; y < height; ++y) { if (!framebuffer_wait(frame, rp + linewidth)) return false; memcpy(d, frame->data + rp, dstpitch); rp += pitch; d += dstpitch; } } #ifdef FB_PROFILE runningavg_push(ra, microtime() - ts); if (++raCount % 100 == 0) DEBUG_INFO("Average Copy Time: %.2fμs", runningavg_calc(ra)); #endif return true; } bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width, size_t bpp, size_t pitch, FrameBufferReadFn fn, void * opaque) { #ifdef FB_PROFILE static RunningAvg ra = NULL; static int raCount = 0; const uint64_t ts = microtime(); if (!ra) ra = runningavg_new(100); #endif uint_least32_t rp = 0; size_t y = 0; const size_t linewidth = width * bpp; while(y < height) { if (!framebuffer_wait(frame, rp + linewidth)) return false; if (!fn(opaque, frame->data + rp, linewidth)) return false; rp += pitch; ++y; } #ifdef FB_PROFILE runningavg_push(ra, microtime() - ts); if (++raCount % 100 == 0) DEBUG_INFO("Average Copy Time: %.2fμs", runningavg_calc(ra)); #endif return true; } /** * Prepare the framebuffer for writing */ void framebuffer_prepare(FrameBuffer * frame) { atomic_store_explicit(&frame->wp, 0, memory_order_release); } bool framebuffer_write(FrameBuffer * frame, const void * restrict src, size_t size) { #ifdef FB_PROFILE static RunningAvg ra = NULL; static int raCount = 0; const uint64_t ts = microtime(); if (!ra) ra = runningavg_new(100); #endif __m128i * restrict s = (__m128i *)src; __m128i * restrict d = (__m128i *)frame->data; size_t wp = 0; _mm_mfence(); /* copy in chunks */ while(size > 63) { __m128i *_d = (__m128i *)d; __m128i *_s = (__m128i *)s; __m128i v1 = _mm_stream_load_si128(_s + 0); __m128i v2 = _mm_stream_load_si128(_s + 1); __m128i v3 = _mm_stream_load_si128(_s + 2); __m128i v4 = _mm_stream_load_si128(_s + 3); _mm_store_si128(_d + 0, v1); _mm_store_si128(_d + 1, v2); _mm_store_si128(_d + 2, v3); _mm_store_si128(_d + 3, v4); s += 4; d += 4; size -= 64; wp += 64; if (wp % FB_CHUNK_SIZE == 0) atomic_store_explicit(&frame->wp, wp, memory_order_release); } if(size) { memcpy(frame->data + wp, s, size); wp += size; } atomic_store_explicit(&frame->wp, wp, memory_order_release); #ifdef FB_PROFILE runningavg_push(ra, microtime() - ts); if (++raCount % 100 == 0) DEBUG_INFO("Average Copy Time: %.2fμs", runningavg_calc(ra)); #endif return true; } const uint8_t * framebuffer_get_buffer(const FrameBuffer * frame) { return frame->data; } uint8_t * framebuffer_get_data(FrameBuffer * frame) { return frame->data; } void framebuffer_set_write_ptr(FrameBuffer * frame, size_t size) { atomic_store_explicit(&frame->wp, size, memory_order_release); } looking-glass-B6/common/src/ll.c000066400000000000000000000047741434445012300167420ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/ll.h" #include "common/debug.h" #include "common/locking.h" #include struct ll * ll_new(void) { struct ll * list = malloc(sizeof(*list)); if (!list) { DEBUG_ERROR("out of memory"); return NULL; } list->head = NULL; list->tail = NULL; list->count = 0; LG_LOCK_INIT(list->lock); return list; } void ll_free(struct ll * list) { // never free a list with items in it! DEBUG_ASSERT(!list->head); LG_LOCK_FREE(list->lock); free(list); } void ll_push(struct ll * list, void * data) { struct ll_item * item = malloc(sizeof(*item)); if (!item) { DEBUG_ERROR("out of memory"); return; } item->data = data; item->next = NULL; LG_LOCK(list->lock); ++list->count; if (!list->head) { item->prev = NULL; list->head = item; list->tail = item; LG_UNLOCK(list->lock); return; } item->prev = list->tail; list->tail->next = item; list->tail = item; LG_UNLOCK(list->lock); } bool ll_shift(struct ll * list, void ** data) { LG_LOCK(list->lock); if (!list->head) { LG_UNLOCK(list->lock); return false; } struct ll_item * item = list->head; ll_removeNL(list, item); LG_UNLOCK(list->lock); if (data) *data = item->data; free(item); return true; } bool ll_peek_head(struct ll * list, void ** data) { LG_LOCK(list->lock); if (!list->head) { LG_UNLOCK(list->lock); return false; } *data = list->head->data; LG_UNLOCK(list->lock); return true; } bool ll_peek_tail(struct ll * list, void ** data) { LG_LOCK(list->lock); if (!list->tail) { LG_UNLOCK(list->lock); return false; } *data = list->tail->data; LG_UNLOCK(list->lock); return true; } looking-glass-B6/common/src/option.c000066400000000000000000000511371434445012300176360ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/option.h" #include "common/debug.h" #include "common/stringutils.h" #include #include #include #include #include #include struct OptionGroup { const char * module; struct Option ** options; int count; int pad; }; struct State { enum doHelpMode doHelp; struct Option ** options; int oCount; struct OptionGroup * groups; int gCount; }; static struct State state = { .doHelp = DOHELP_MODE_NO, .options = NULL, .oCount = 0, .groups = NULL, .gCount = 0 }; static bool int_parser(struct Option * opt, const char * str) { opt->value.x_int = atol(str); return true; } static bool bool_parser(struct Option * opt, const char * str) { opt->value.x_bool = strcasecmp(str, "1" ) == 0 || strcasecmp(str, "on" ) == 0 || strcasecmp(str, "yes" ) == 0 || strcasecmp(str, "true") == 0; return true; } static bool float_parser(struct Option * opt, const char * str) { opt->value.x_float = atof(str); return true; } static bool string_parser(struct Option * opt, const char * str) { free(opt->value.x_string); opt->value.x_string = strdup(str); return true; } static char * int_toString(struct Option * opt) { int len = snprintf(NULL, 0, "%d", opt->value.x_int); char * ret = malloc(len + 1); if (!ret) { DEBUG_ERROR("out of memory"); return NULL; } sprintf(ret, "%d", opt->value.x_int); return ret; } static char * bool_toString(struct Option * opt) { return strdup(opt->value.x_bool ? "yes" : "no"); } static char * float_toString(struct Option * opt) { int len = snprintf(NULL, 0, "%f", opt->value.x_float); char * ret = malloc(len + 1); if (!ret) { DEBUG_ERROR("out of memory"); return NULL; } sprintf(ret, "%f", opt->value.x_float); return ret; } static char * string_toString(struct Option * opt) { if (!opt->value.x_string) return NULL; return strdup(opt->value.x_string); } bool option_register(struct Option options[]) { int new = 0; for(int i = 0; options[i].type != OPTION_TYPE_NONE; ++i) ++new; state.options = realloc( state.options, sizeof(*state.options) * (state.oCount + new) ); if (!state.options) { DEBUG_ERROR("out of memory"); return false; } for(int i = 0; options[i].type != OPTION_TYPE_NONE; ++i) { struct Option * o = state.options[state.oCount + i] = malloc(sizeof(**state.options)); if (!o) { DEBUG_ERROR("out of memory"); return false; } memcpy(o, &options[i], sizeof(*o)); if (!o->parser) { switch(o->type) { case OPTION_TYPE_INT: o->parser = int_parser; break; case OPTION_TYPE_STRING: o->parser = string_parser; break; case OPTION_TYPE_BOOL: o->parser = bool_parser; break; case OPTION_TYPE_FLOAT: o->parser = float_parser; break; default: DEBUG_ERROR("BUG: Non int/string/bool option types must have a parser"); continue; } } if (!o->toString) { switch(o->type) { case OPTION_TYPE_INT: o->toString = int_toString; break; case OPTION_TYPE_STRING: o->toString = string_toString; break; case OPTION_TYPE_BOOL: o->toString = bool_toString; break; case OPTION_TYPE_FLOAT: o->toString = float_toString; break; default: DEBUG_ERROR("BUG: Non int/string/bool option types must implement toString"); continue; } } // ensure the string is locally allocated if (o->type == OPTION_TYPE_STRING) { if (o->value.x_string) { o->value.x_string = strdup(o->value.x_string); if (!o->value.x_string) { DEBUG_ERROR("out of memory"); return false; } } } // add the option to the correct group for help printout bool found = false; for(int g = 0; g < state.gCount; ++g) { struct OptionGroup * group = &state.groups[g]; if (strcmp(group->module, o->module) != 0) continue; found = true; group->options = realloc( group->options, sizeof(*group->options) * (group->count + 1) ); if (!group->options) { DEBUG_ERROR("out of memory"); return false; } group->options[group->count] = o; int len = strlen(o->name); if (len > group->pad) group->pad = len; ++group->count; break; } if (!found) { state.groups = realloc( state.groups, sizeof(*state.groups) * (state.gCount + 1) ); if (!state.groups) { DEBUG_ERROR("out of memory"); return false; } struct OptionGroup * group = &state.groups[state.gCount]; ++state.gCount; group->module = o->module; group->options = malloc(sizeof(*group->options)); group->options[0] = o; group->count = 1; group->pad = strlen(o->name); } } state.oCount += new; return true; }; void option_free(void) { for(int i = 0; i < state.oCount; ++i) { struct Option * o = state.options[i]; if (o->type == OPTION_TYPE_STRING && o->value.x_string) free(o->value.x_string); free(o); } free(state.options); state.options = NULL; state.oCount = 0; for(int g = 0; g < state.gCount; ++g) { struct OptionGroup * group = &state.groups[g]; if (group->options) free(group->options); } free(state.groups); state.groups = NULL; state.gCount = 0; } static bool option_set(struct Option * opt, const char * value) { if (!opt->parser(opt, value)) { opt->failed_set = true; return false; } opt->failed_set = false; return true; } bool option_parse(int argc, char * argv[]) { for(int a = 1; a < argc; ++a) { struct Option * o = NULL; char * value = NULL; // emulate getopt for backwards compatability if (argv[a][0] == '-') { if (strcasecmp(argv[a], "-h") == 0 || strcasecmp(argv[a], "--help") == 0) { state.doHelp = DOHELP_MODE_YES; continue; } if (strcasecmp(argv[a], "--rst-help") == 0) { state.doHelp = DOHELP_MODE_RST; continue; } if (strlen(argv[a]) != 2) { DEBUG_WARN("Ignored invalid argument: %s", argv[a]); continue; } for(int i = 0; i < state.oCount; ++i) { if (state.options[i]->shortopt == argv[a][1]) { o = state.options[i]; if (o->type != OPTION_TYPE_BOOL && a < argc - 1) { ++a; value = strdup(argv[a]); } break; } } } else { char * arg = strdup(argv[a]); char * module = strtok(arg , ":"); char * name = strtok(NULL, "="); value = strtok(NULL, "" ); if (!module || !name) { DEBUG_WARN("Ignored invalid argument: %s", argv[a]); free(arg); continue; } o = option_get(module, name); if (value) value = strdup(value); free(arg); } if (!o) { DEBUG_WARN("Ignored unknown argument: %s", argv[a]); free(value); continue; } if (!value) { if (o->type == OPTION_TYPE_BOOL) { o->value.x_bool = !o->value.x_bool; continue; } else if (o->type != OPTION_TYPE_CUSTOM) { DEBUG_WARN("Ignored invalid argument, missing value: %s", argv[a]); continue; } } option_set(o, value); free(value); } return true; } static char * file_parse_module(FILE * fp) { char * module = NULL; int len = 0; for(int c = fgetc(fp); !feof(fp); c = fgetc(fp)) { switch(c) { case ']': if (module) module[len] = '\0'; return module; case '\r': case '\n': free(module); return NULL; default: if (len % 32 == 0) { char * p = realloc(module, len + 32 + 1); if (!p) { DEBUG_ERROR("out of memory"); free(module); return NULL; } module = p; } module[len++] = c; } } if (module) free(module); return NULL; } static bool process_option_line(const char * module, const char * name, char * value, int valueLen, int lineno) { if (!module) { DEBUG_ERROR("Syntax error on line %d, module not specified for option", lineno); return false; } struct Option * o = option_get(module, name); if (!o) DEBUG_WARN("Ignored unknown option %s:%s", module, name); else { if (value) value[valueLen] = '\0'; if (!option_set(o, value)) DEBUG_ERROR("Failed to set the option value"); } return true; } bool option_load(const char * filename) { FILE * fp = fopen(filename, "r"); if (!fp) return false; bool result = true; int lineno = 1; char * module = NULL; bool line = true; bool comment = false; bool expectLine = false; bool expectValue = false; char * name = NULL; int nameLen = 0; char * value = NULL; int valueLen = 0; char ** p = &name; int * len = &nameLen; for(int c = fgetc(fp); !feof(fp); c = fgetc(fp)) { if (comment && c != '\n') continue; comment = false; switch(c) { case '[': if (expectLine) { DEBUG_ERROR("Syntax error on line %d, expected new line", lineno); result = false; goto exit; } if (line) { free(module); module = file_parse_module(fp); if (!module) { DEBUG_ERROR("Syntax error on line %d, failed to parse the module", lineno); result = false; goto exit; } line = false; expectLine = true; continue; } if (*len % 32 == 0) { char * tmp = realloc(*p, *len + 32 + 1); if (!tmp) { DEBUG_ERROR("out of memory"); result = false; goto exit; } *p = tmp; } (*p)[(*len)++] = c; break; case '\r': continue; case '\n': if (name && !process_option_line(module, name, value, valueLen, lineno)) { result = false; goto exit; } line = true; expectLine = false; expectValue = false; ++lineno; p = &name; len = &nameLen; free(name); name = NULL; nameLen = 0; free(value); value = NULL; valueLen = 0; break; case '=': if (!expectValue) { if (!name) { DEBUG_ERROR("Syntax error on line %d, expected option name", lineno); result = false; goto exit; } //rtrim while (nameLen > 1 && isspace(name[nameLen-1])) --nameLen; name[nameLen] = '\0'; expectValue = true; p = &value; len = &valueLen; continue; } if (*len % 32 == 0) { char * tmp = realloc(*p, *len + 32 + 1); if (!tmp) { DEBUG_ERROR("out of memory"); result = false; goto exit; } *p = tmp; } (*p)[(*len)++] = c; break; case ';': if (line) { comment = true; break; } // fallthrough default: // ignore non-typeable ascii characters if (c < 32 || c > 126) continue; if (expectLine) { DEBUG_ERROR("Syntax error on line %d, expected new line", lineno); result = false; goto exit; } line = false; //ltrim if (*len == 0 && isspace(c)) break; if (*len % 32 == 0) { char * tmp = realloc(*p, *len + 32 + 1); if (!tmp) { DEBUG_ERROR("out of memory"); result = false; goto exit; } *p = tmp; } (*p)[(*len)++] = c; break; } } if (name && !process_option_line(module, name, value, valueLen, lineno)) result = false; exit: fclose(fp); free(module); free(name ); free(value ); return result; } bool option_validate(void) { if (state.doHelp != DOHELP_MODE_NO) { option_print(); return false; } // validate the option values bool ok = true; for(int i = 0; i < state.oCount; ++i) { struct Option * o = state.options[i]; const char * error = NULL; bool invalid = o->failed_set; if (!invalid && o->validator) invalid = !o->validator(o, &error); if (invalid) { printf("\nInvalid value provided to the option: %s:%s\n", o->module, o->name); if (error) printf("\n Error: %s\n", error); if (o->getValues) { StringList values = o->getValues(o); printf("\nValid values are:\n\n"); for(unsigned int v = 0; v < stringlist_count(values); ++v) printf(" * %s\n", stringlist_at(values, v)); stringlist_free(&values); } if (o->printHelp) { printf("\n"); o->printHelp(); } ok = false; } } if (!ok) printf("\n"); return ok; } void option_print_hrule(char * headerLine, int maxLen, char ruleChar) { printf(" +%c", ruleChar); for (int i = 0; i < maxLen; i++) { if(i < strlen(headerLine)) { if (headerLine[i] == '|') { putc('+', stdout); continue; } } putc(ruleChar, stdout); } printf("%c+\n", ruleChar); } void option_print(void) { printf( "The following is a complete list of options accepted by this application\n\n" ); for(int g = 0; g < state.gCount; ++g) { StringList lines = stringlist_new(true); StringList values = stringlist_new(true); int len; int maxLen; int valueLen = 5; char * line; char * headerLine; // ensure the pad length is atleast as wide as the heading if (state.groups[g].pad < 4) state.groups[g].pad = 4; // get the values and the max value length for(int i = 0; i < state.groups[g].count; ++i) { struct Option * o = state.groups[g].options[i]; if (o->preset) continue; char * value = o->toString(o); if (!value) { value = strdup("NULL"); len = 4; } else len = strlen(value); if (len > valueLen) valueLen = len; stringlist_push(values, value); } // add the heading maxLen = alloc_sprintf( &line, "%-*s | Short | %-*s | Description", (int)(strlen(state.groups[g].module) + state.groups[g].pad + 1), "Long", valueLen, "Value" ); DEBUG_ASSERT(maxLen > 0); headerLine = line; stringlist_push(lines, line); for(int i = 0; i < state.groups[g].count; ++i) { struct Option * o = state.groups[g].options[i]; if (o->preset) continue; char * value = stringlist_at(values, i); len = alloc_sprintf( &line, "%s:%-*s | %c%c | %-*s | %s", o->module, state.groups[g].pad, o->name, o->shortopt ? '-' : ' ', o->shortopt ? o->shortopt : ' ', valueLen, value, o->description ); DEBUG_ASSERT(len > 0); stringlist_push(lines, line); if (len > maxLen) maxLen = len; } stringlist_free(&values); if (stringlist_count(lines) <= 1) { stringlist_free(&lines); continue; } // print out the lines for(int i = 0; i < stringlist_count(lines); ++i) { if (i == 0) { option_print_hrule(headerLine, maxLen, '-'); } char * line = stringlist_at(lines, i); printf(" | %-*s |\n", maxLen, line); if (i == 0) { option_print_hrule(headerLine, maxLen, state.doHelp == DOHELP_MODE_RST ? '=' : '-'); } else if (state.doHelp == DOHELP_MODE_RST && i < stringlist_count(lines) - 1) { option_print_hrule(headerLine, maxLen, '-'); } } option_print_hrule(headerLine, maxLen, '-'); stringlist_free(&lines); printf("\n"); } } // dump the options in ini format into the file bool option_dump_preset(FILE * file) { for (int g = 0; g < state.gCount; ++g) { bool hasPreset = false; for (int i = 0; i < state.groups[g].count; ++i) hasPreset |= state.groups[g].options[i]->preset; if (!hasPreset) continue; fprintf(file, "[%s]\n", state.groups[g].module); for (int i = 0; i < state.groups[g].count; ++i) { struct Option * o = state.groups[g].options[i]; if (!o->preset) continue; char * value = o->toString(o); fprintf(file, "%s=%s\n", o->name, value); free(value); } fputc('\n', file); } return true; } struct Option * option_get(const char * module, const char * name) { for(int i = 0; i < state.oCount; ++i) { struct Option * o = state.options[i]; if ((strcasecmp(o->module, module) == 0) && (strcasecmp(o->name, name) == 0)) return o; } return NULL; } int option_get_int(const char * module, const char * name) { struct Option * o = option_get(module, name); if (!o) { DEBUG_ERROR("BUG: Failed to get the value for option %s:%s", module, name); return -1; } DEBUG_ASSERT(o->type == OPTION_TYPE_INT); return o->value.x_int; } const char * option_get_string(const char * module, const char * name) { struct Option * o = option_get(module, name); if (!o) { DEBUG_ERROR("BUG: Failed to get the value for option %s:%s", module, name); return NULL; } DEBUG_ASSERT(o->type == OPTION_TYPE_STRING); return o->value.x_string; } bool option_get_bool(const char * module, const char * name) { struct Option * o = option_get(module, name); if (!o) { DEBUG_ERROR("BUG: Failed to get the value for option %s:%s", module, name); return false; } DEBUG_ASSERT(o->type == OPTION_TYPE_BOOL); return o->value.x_bool; } float option_get_float(const char * module, const char * name) { struct Option * o = option_get(module, name); if (!o) { DEBUG_ERROR("BUG: Failed to get the value for option %s:%s", module, name); return NAN; } DEBUG_ASSERT(o->type == OPTION_TYPE_FLOAT); return o->value.x_float; } void option_set_int(const char * module, const char * name, int value) { struct Option * o = option_get(module, name); if (!o) { DEBUG_ERROR("BUG: Failed to set the value for option %s:%s", module, name); return; } DEBUG_ASSERT(o->type == OPTION_TYPE_INT); o->value.x_int = value; } void option_set_string(const char * module, const char * name, const char * value) { struct Option * o = option_get(module, name); if (!o) { DEBUG_ERROR("BUG: Failed to set the value for option %s:%s", module, name); return; } DEBUG_ASSERT(o->type == OPTION_TYPE_STRING); free(o->value.x_string); o->value.x_string = strdup(value); } void option_set_bool(const char * module, const char * name, bool value) { struct Option * o = option_get(module, name); if (!o) { DEBUG_ERROR("BUG: Failed to set the value for option %s:%s", module, name); return; } DEBUG_ASSERT(o->type == OPTION_TYPE_BOOL); o->value.x_bool = value; } void option_set_float(const char * module, const char * name, float value) { struct Option * o = option_get(module, name); if (!o) { DEBUG_ERROR("BUG: Failed to set the value for option %s:%s", module, name); return; } DEBUG_ASSERT(o->type == OPTION_TYPE_FLOAT); o->value.x_float = value; } looking-glass-B6/common/src/platform/000077500000000000000000000000001434445012300177775ustar00rootroot00000000000000looking-glass-B6/common/src/platform/CMakeLists.txt000066400000000000000000000004361434445012300225420ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(lg_common_platform LANGUAGES C) if (UNIX) add_subdirectory("linux") elseif(WIN32) add_subdirectory("windows") endif() add_library(lg_common_platform INTERFACE) target_link_libraries(lg_common_platform INTERFACE lg_common_platform_code) looking-glass-B6/common/src/platform/linux/000077500000000000000000000000001434445012300211365ustar00rootroot00000000000000looking-glass-B6/common/src/platform/linux/CMakeLists.txt000066400000000000000000000007611434445012300237020ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(lg_common_platform_code LANGUAGES C) include_directories( ${PROJECT_SOURCE_DIR}/include ${PROJECT_TOP} ) add_library(lg_common_platform_code STATIC debug.c crash.c sysinfo.c thread.c event.c ivshmem.c time.c paths.c open.c cpuinfo.c ) if(ENABLE_BACKTRACE) find_package(BFD) target_link_libraries(lg_common_platform_code ${BFD_LIBRARIES}) endif() target_link_libraries(lg_common_platform_code lg_common pthread rt ) looking-glass-B6/common/src/platform/linux/bfd.inc.h000066400000000000000000000022121434445012300226070ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef PACKAGE #define PACKAGE #ifndef PACKAGE_VERSION #define PACKAGE_VERSION #include #undef PACKAGE_VERSION #else #include #endif #undef PACKAGE #else #ifndef PACKAGE_VERSION #define PACKAGE_VERSION #include #undef PACKAGE_VERSION #else #include #endif #endiflooking-glass-B6/common/src/platform/linux/cpuinfo.c000066400000000000000000000047441434445012300227560ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/cpuinfo.h" #include "common/debug.h" #include #include #include #include #include bool lgCPUInfo(char * model, size_t modelSize, int * procs, int * cores, int * sockets) { FILE * cpuinfo = fopen("/proc/cpuinfo", "r"); if (!cpuinfo) { DEBUG_ERROR("Failed to open /proc/cpuinfo: %s", strerror(errno)); return false; } int socketCount = 0; if (procs) *procs = 0; if (cores) *cores = 0; char buffer[1024]; while (fgets(buffer, sizeof(buffer), cpuinfo)) { if (procs && strncmp(buffer, "processor", 9) == 0) ++*procs; else if (model && strncmp(buffer, "model name", 10) == 0) { const char * name = strstr(buffer, ": "); if (name) name += 2; int len = snprintf(model, modelSize, "%s", name ? name : "Unknown"); // trim any whitespace while(len > 0 && isspace(model[len-1])) --len; model[len] = '\0'; model = NULL; } else if (cores && *cores == 0 && strncmp(buffer, "cpu cores", 9) == 0) { const char * num = strstr(buffer, ": "); if (num) *cores = atoi(num + 2); } else if (strncmp(buffer, "physical id", 11) == 0) { const char * num = strstr(buffer, ": "); if (num) { int id = atoi(num + 2); if (id >= socketCount) socketCount = id + 1; } } // If a line is too long, skip it. while (buffer[strlen(buffer) - 1] != '\n') if (!fgets(buffer, sizeof(buffer), cpuinfo)) goto done; } done: if (sockets) *sockets = socketCount; if (cores) *cores *= socketCount; fclose(cpuinfo); return true; } looking-glass-B6/common/src/platform/linux/crash.c000066400000000000000000000135761434445012300224160ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/crash.h" #include "common/debug.h" #include "common/version.h" #if defined(ENABLE_BACKTRACE) #include #include #include #include #include #include #include #include #include #include #include "bfd.inc.h" struct range { intptr_t start, end; }; struct crash { char * exe; struct range * ranges; int rangeCount; bfd * fd; asection * section; asymbol ** syms; long symCount; bool loaded; }; static struct crash crash = {0}; static bool load_symbols(void) { bfd_init(); crash.fd = bfd_openr(crash.exe, NULL); if (!crash.fd) { DEBUG_ERROR("failed to open '%s'", crash.exe); return false; } crash.fd->flags |= BFD_DECOMPRESS; char **matching; if (!bfd_check_format_matches(crash.fd, bfd_object, &matching)) { DEBUG_ERROR("executable is not a bfd_object"); return false; } crash.section = bfd_get_section_by_name(crash.fd, ".text"); if (!crash.section) { DEBUG_ERROR("failed to find .text section"); return false; } if ((bfd_get_file_flags(crash.fd) & HAS_SYMS) == 0) { DEBUG_ERROR("executable '%s' has no symbols", crash.exe); return false; } long storage = bfd_get_symtab_upper_bound(crash.fd); crash.syms = malloc(storage); crash.symCount = bfd_canonicalize_symtab(crash.fd, crash.syms); if (crash.symCount < 0) { DEBUG_ERROR("failed to get the symbol count"); return false; } return true; } static bool lookup_address(bfd_vma pc, const char ** filename, const char ** function, unsigned int * line, unsigned int * discriminator) { #ifdef bfd_get_section_flags if ((bfd_get_section_flags(crash.fd, crash.section) & SEC_ALLOC) == 0) #else if ((bfd_section_flags(crash.section) & SEC_ALLOC) == 0) #endif return false; #ifdef bfd_get_section_size bfd_size_type size = bfd_get_section_size(crash.section); #else bfd_size_type size = bfd_section_size(crash.section); #endif if (pc >= size) return false; if (!bfd_find_nearest_line_discriminator( crash.fd, crash.section, crash.syms, pc, filename, function, line, discriminator )) return false; if (!*filename) return false; return true; } void cleanupCrashHandler(void) { if (crash.syms) free(crash.syms); if (crash.fd) bfd_close(crash.fd); if (crash.ranges) free(crash.ranges); if (crash.exe) free(crash.exe); } static int dl_iterate_phdr_callback(struct dl_phdr_info * info, size_t size, void * data) { // we are not a module, and as such we don't have a name if (strlen(info->dlpi_name) != 0) return 0; size_t ttl = 0; for(int i = 0; i < info->dlpi_phnum; ++i) { const ElfW(Phdr) hdr = info->dlpi_phdr[i]; if (hdr.p_type == PT_LOAD && (hdr.p_flags & PF_X) == PF_X) ttl += hdr.p_memsz; } crash.ranges = realloc(crash.ranges, sizeof(*crash.ranges) * (crash.rangeCount + 1)); crash.ranges[crash.rangeCount].start = info->dlpi_addr; crash.ranges[crash.rangeCount].end = info->dlpi_addr + ttl; ++crash.rangeCount; return 0; } void printBacktrace(void) { void * array[50]; char ** messages; int size, i; size = backtrace(array, 50); messages = backtrace_symbols(array, size); for (i = 2; i < size && messages != NULL; ++i) { intptr_t base = -1; for(int c = 0; c < crash.rangeCount; ++c) { if ((intptr_t)array[i] >= crash.ranges[c].start && (intptr_t)array[i] < crash.ranges[c].end) { base = crash.ranges[c].start + crash.section->vma; break; } } if (base != -1) { const char * filename, * function; unsigned int line, discriminator; if (lookup_address((intptr_t)array[i] - base, &filename, &function, &line, &discriminator)) { DEBUG_ERROR("[trace]: (%d) %s:%u (%s)", i - 2, filename, line, function); continue; } } DEBUG_ERROR("[trace]: (%d) %s", i - 2, messages[i]); } free(messages); } static void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext) { DEBUG_ERROR("==== FATAL CRASH (%s) ====", BUILD_VERSION); DEBUG_ERROR("signal %d (%s), address is %p", sig_num, strsignal(sig_num), info->si_addr); printBacktrace(); cleanupCrashHandler(); abort(); } bool installCrashHandler(const char * exe) { struct sigaction sigact = { 0 }; crash.exe = realpath(exe, NULL); if (!load_symbols()) { DEBUG_WARN("Unable to load the binary symbols, not installing crash handler"); return true; } dl_iterate_phdr(dl_iterate_phdr_callback, NULL); sigact.sa_sigaction = crit_err_hdlr; sigact.sa_flags = SA_RESTART | SA_SIGINFO; if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0) { DEBUG_ERROR("Error setting signal handler for %d (%s)", SIGSEGV, strsignal(SIGSEGV)); return false; } return true; } #else //ENABLE_BACKTRACE bool installCrashHandler(const char * exe) { return true; } void cleanupCrashHandler(void) { } void printBacktrace(void) { } #endif looking-glass-B6/common/src/platform/linux/debug.c000066400000000000000000000033101434445012300223650ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/debug.h" #include #define COLOR_RESET "\033[0m" #define COLOR_YELLOW "\033[0;33m" #define COLOR_RED "\033[0;31m" #define COLOR_CYAN "\033[0;36m" #define COLOR_WHITE "\033[0;37m" const char ** debug_lookup = NULL; void debug_init(void) { static const char * colorLookup[] = { COLOR_RESET , // DEBUG_LEVEL_NONE COLOR_RESET "[I] ", // DEBUG_LEVEL_INFO COLOR_YELLOW "[W] ", // DEBUG_LEVEL_WARN COLOR_RED "[E] ", // DEBUG_LEVEL_ERROR COLOR_CYAN "[F] ", // DEBUG_LEVEL_FIXME COLOR_WHITE "[!] " // DEBUG_LEVEL_FATAL }; static const char * plainLookup[] = { "" , // DEBUG_LEVEL_NONE "[I] ", // DEBUG_LEVEL_INFO "[W] ", // DEBUG_LEVEL_WARN "[E] ", // DEBUG_LEVEL_ERROR "[F] ", // DEBUG_LEVEL_FIXME "[!] " // DEBUG_LEVEL_FATAL }; debug_lookup = (isatty(STDERR_FILENO) == 1) ? colorLookup : plainLookup; } looking-glass-B6/common/src/platform/linux/event.c000066400000000000000000000113541434445012300224270ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/event.h" #include "common/debug.h" #include #include #include #include #include struct LGEvent { pthread_mutex_t mutex; pthread_cond_t cond; atomic_int waiting; atomic_bool signaled; bool autoReset; }; LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime) { LGEvent * handle = calloc(1, sizeof(*handle)); if (!handle) { DEBUG_ERROR("Failed to allocate memory"); return NULL; } if (pthread_mutex_init(&handle->mutex, NULL) != 0) { DEBUG_ERROR("Failed to create the mutex"); free(handle); return NULL; } pthread_condattr_t cattr; pthread_condattr_init(&cattr); if (pthread_condattr_setclock(&cattr, CLOCK_MONOTONIC) != 0) { DEBUG_ERROR("Failed to set the condition clock to realtime"); pthread_mutex_destroy(&handle->mutex); free(handle); return NULL; } if (pthread_cond_init(&handle->cond, &cattr) != 0) { pthread_mutex_destroy(&handle->mutex); free(handle); return NULL; } handle->autoReset = autoReset; return handle; } void lgFreeEvent(LGEvent * handle) { DEBUG_ASSERT(handle); if (atomic_load_explicit(&handle->waiting, memory_order_acquire) != 0) DEBUG_ERROR("BUG: Freeing an event that still has threads waiting on it"); pthread_cond_destroy (&handle->cond ); pthread_mutex_destroy(&handle->mutex); free(handle); } bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts) { DEBUG_ASSERT(handle); bool ret = true; int res; if (pthread_mutex_lock(&handle->mutex) != 0) { DEBUG_ERROR("Failed to lock the mutex"); return false; } atomic_fetch_add_explicit(&handle->waiting, 1, memory_order_release); while(ret && !atomic_load_explicit(&handle->signaled, memory_order_acquire)) { if (!ts) { if ((res = pthread_cond_wait(&handle->cond, &handle->mutex)) != 0) { DEBUG_ERROR("Failed to wait on the condition (err: %d)", res); ret = false; } } else { switch((res = pthread_cond_timedwait(&handle->cond, &handle->mutex, ts))) { case 0: break; case ETIMEDOUT: ret = false; break; default: ret = false; DEBUG_ERROR("Timed wait failed (err: %d)", res); break; } } } atomic_fetch_sub_explicit(&handle->waiting, 1, memory_order_release); if (pthread_mutex_unlock(&handle->mutex) != 0) { DEBUG_ERROR("Failed to unlock the mutex"); return false; } if (ret && handle->autoReset) atomic_store_explicit(&handle->signaled, false, memory_order_release); return ret; } bool lgWaitEventNS(LGEvent * handle, unsigned int timeout) { if (timeout == TIMEOUT_INFINITE) return lgWaitEventAbs(handle, NULL); struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); uint64_t nsec = ts.tv_nsec + timeout; if(nsec > 1000000000UL) { ts.tv_nsec = nsec - 1000000000UL; ++ts.tv_sec; } else ts.tv_nsec = nsec; return lgWaitEventAbs(handle, &ts); } bool lgWaitEvent(LGEvent * handle, unsigned int timeout) { if (timeout == TIMEOUT_INFINITE) return lgWaitEventAbs(handle, NULL); return lgWaitEventNS(handle, timeout * 1000000U); } bool lgSignalEvent(LGEvent * handle) { DEBUG_ASSERT(handle); if (pthread_mutex_lock(&handle->mutex) != 0) { DEBUG_ERROR("Failed to lock the mutex"); return false; } const bool signaled = atomic_exchange_explicit(&handle->signaled, true, memory_order_release); if (!signaled) if (pthread_cond_broadcast(&handle->cond) != 0) { DEBUG_ERROR("Failed to signal the condition"); return false; } if (pthread_mutex_unlock(&handle->mutex) != 0) { DEBUG_ERROR("Failed to unlock the mutex"); return false; } return true; } bool lgResetEvent(LGEvent * handle) { DEBUG_ASSERT(handle); return atomic_exchange_explicit(&handle->signaled, false, memory_order_release); } looking-glass-B6/common/src/platform/linux/ivshmem.c000066400000000000000000000127471434445012300227650ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/ivshmem.h" #include #include #include #include #include #include #include #include #include #include "common/array.h" #include "common/debug.h" #include "common/option.h" #include "common/sysinfo.h" #include "common/stringutils.h" #include "module/kvmfr.h" struct IVSHMEMInfo { int devFd; int size; bool hasDMA; }; static bool ivshmemDeviceValidator(struct Option * opt, const char ** error) { // if it's not a kvmfr device, it must be a file on disk if (strlen(opt->value.x_string) > 3 && memcmp(opt->value.x_string, "kvmfr", 5) != 0) { struct stat st; if (stat(opt->value.x_string, &st) != 0) { *error = "Invalid path to the ivshmem file specified"; return false; } return true; } return true; } static StringList ivshmemDeviceGetValues(struct Option * option) { StringList sl = stringlist_new(true); DIR * d = opendir("/sys/class/kvmfr"); if (!d) return sl; struct dirent * dir; while((dir = readdir(d)) != NULL) { if (dir->d_name[0] == '.') continue; char * devName; alloc_sprintf(&devName, "/dev/%s", dir->d_name); stringlist_push(sl, devName); } closedir(d); return sl; } void ivshmemOptionsInit(void) { struct Option options[] = { { .module = "app", .name = "shmFile", .shortopt = 'f', .description = "The path to the shared memory file, or the name of the kvmfr device to use, e.g. kvmfr0", .type = OPTION_TYPE_STRING, .value.x_string = "/dev/shm/looking-glass", .validator = ivshmemDeviceValidator, .getValues = ivshmemDeviceGetValues }, {0} }; option_register(options); } bool ivshmemInit(struct IVSHMEM * dev) { // FIXME: split code from ivshmemOpen return true; } bool ivshmemOpen(struct IVSHMEM * dev) { return ivshmemOpenDev(dev, option_get_string("app", "shmFile")); } bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDevice) { DEBUG_ASSERT(dev); unsigned int devSize; int devFd; bool hasDMA; dev->opaque = NULL; DEBUG_INFO("KVMFR Device : %s", shmDevice); if (strlen(shmDevice) > 8 && memcmp(shmDevice, "/dev/kvmfr", 10) == 0) { devFd = open(shmDevice, O_RDWR, (mode_t)0600); if (devFd < 0) { DEBUG_ERROR("Failed to open: %s", shmDevice); DEBUG_ERROR("%s", strerror(errno)); return false; } // get the device size devSize = ioctl(devFd, KVMFR_DMABUF_GETSIZE, 0); hasDMA = true; } else { struct stat st; if (stat(shmDevice, &st) != 0) { DEBUG_ERROR("Failed to stat: %s", shmDevice); DEBUG_ERROR("%s", strerror(errno)); return false; } devSize = st.st_size; devFd = open(shmDevice, O_RDWR, (mode_t)0600); if (devFd < 0) { DEBUG_ERROR("Failed to open: %s", shmDevice); DEBUG_ERROR("%s", strerror(errno)); return false; } hasDMA = false; } void * map = mmap(0, devSize, PROT_READ | PROT_WRITE, MAP_SHARED, devFd, 0); if (map == MAP_FAILED) { DEBUG_ERROR("Failed to map the shared memory device: %s", shmDevice); DEBUG_ERROR("%s", strerror(errno)); return false; } struct IVSHMEMInfo * info = malloc(sizeof(*info)); info->size = devSize; info->devFd = devFd; info->hasDMA = hasDMA; dev->opaque = info; dev->size = devSize; dev->mem = map; return true; } void ivshmemClose(struct IVSHMEM * dev) { DEBUG_ASSERT(dev); if (!dev->opaque) return; struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque; munmap(dev->mem, info->size); close(info->devFd); free(info); dev->mem = NULL; dev->size = 0; dev->opaque = NULL; } void ivshmemFree(struct IVSHMEM * dev) { // FIXME: split code from ivshmemClose } bool ivshmemHasDMA(struct IVSHMEM * dev) { DEBUG_ASSERT(dev && dev->opaque); struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque; return info->hasDMA; } int ivshmemGetDMABuf(struct IVSHMEM * dev, uint64_t offset, uint64_t size) { DEBUG_ASSERT(ivshmemHasDMA(dev)); DEBUG_ASSERT(dev && dev->opaque); DEBUG_ASSERT(offset + size <= dev->size); static long pageSize = 0; if (!pageSize) pageSize = sysinfo_getPageSize(); struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque; // align to the page size size = ALIGN_PAD(size, pageSize); const struct kvmfr_dmabuf_create create = { .flags = KVMFR_DMABUF_FLAG_CLOEXEC, .offset = offset, .size = size }; int fd = ioctl(info->devFd, KVMFR_DMABUF_CREATE, &create); if (fd < 0) DEBUG_ERROR("Failed to create the dma buffer"); return fd; } looking-glass-B6/common/src/platform/linux/open.c000066400000000000000000000035411434445012300222460ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/open.h" #include "common/debug.h" #include #include #include #include static bool xdgOpen(const char * path) { pid_t pid = fork(); if (pid == 0) { // setsid and fork again to detach the xdg-open process. setsid(); pid_t pid = fork(); if (pid == 0) { execlp("xdg-open", "xdg-open", path, NULL); _exit(127); } _exit(pid < 0); } else if (pid < 0) { DEBUG_ERROR("Failed to launch xdg-open: %s", strerror(errno)); return false; } else { int status; if (waitpid(pid, &status, 0) < 0) { DEBUG_ERROR("waitpid failed: %s", strerror(errno)); return false; } if (WIFEXITED(status) && WEXITSTATUS(status) == 0) return true; if (WIFEXITED(status)) DEBUG_ERROR("helper process exited with code %d", WEXITSTATUS(status)); else DEBUG_ERROR("helper process exited with signal: %s", strsignal(WTERMSIG(status))); return false; } } bool lgOpenURL(const char * url) { return xdgOpen(url); } looking-glass-B6/common/src/platform/linux/paths.c000066400000000000000000000045031434445012300224230ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/paths.h" #include "common/debug.h" #include #include #include #include #include #include static char configDir[PATH_MAX]; static char dataDir[PATH_MAX]; static void ensureDir(char * path, mode_t mode) { struct stat st; if (stat(path, &st) >= 0) { if (S_ISDIR(st.st_mode)) return; DEBUG_ERROR("Expected to be a directory: %s", path); exit(2); } for (char * p = strchr(path + 1, '/'); p; p = strchr(p + 1, '/')) { *p = '\0'; if (mkdir(path, mode) < 0 && errno != EEXIST) { *p = '/'; DEBUG_ERROR("Failed to create directory: %s", path); return; } *p = '/'; } if (mkdir(path, mode) < 0 && errno != EEXIST) DEBUG_ERROR("Failed to create directory: %s", path); } void lgPathsInit(const char * appName) { const char * home = getenv("HOME"); if (!home) home = getpwuid(getuid())->pw_dir; const char * dir; if ((dir = getenv("XDG_CONFIG_HOME")) != NULL) snprintf(configDir, sizeof(configDir), "%s/%s", dir, appName); else snprintf(configDir, sizeof(configDir), "%s/.config/%s", home, appName); if ((dir = getenv("XDG_DATA_HOME")) != NULL) snprintf(dataDir, sizeof(configDir), "%s/%s", dir, appName); else snprintf(dataDir, sizeof(configDir), "%s/.local/share/%s", home, appName); ensureDir(configDir, S_IRWXU); ensureDir(dataDir, S_IRWXU); } const char * lgConfigDir(void) { return configDir; } const char * lgDataDir(void) { return dataDir; } looking-glass-B6/common/src/platform/linux/sysinfo.c000066400000000000000000000016131434445012300227750ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include long sysinfo_getPageSize(void) { return sysconf(_SC_PAGESIZE); } looking-glass-B6/common/src/platform/linux/thread.c000066400000000000000000000041371434445012300225560ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/thread.h" #include #include #include "common/debug.h" struct LGThread { const char * name; LGThreadFunction function; void * opaque; pthread_t handle; int resultCode; }; static void * threadWrapper(void * opaque) { LGThread * handle = (LGThread *)opaque; handle->resultCode = handle->function(handle->opaque); return NULL; } bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle) { *handle = malloc(sizeof(**handle)); if (!*handle) { DEBUG_ERROR("out of memory"); return false; } (*handle)->name = name; (*handle)->function = function; (*handle)->opaque = opaque; if (pthread_create(&(*handle)->handle, NULL, threadWrapper, *handle) != 0) { DEBUG_ERROR("pthread_create failed for thread: %s", name); free(*handle); *handle = NULL; return false; } pthread_setname_np((*handle)->handle, name); return true; } bool lgJoinThread(LGThread * handle, int * resultCode) { if (pthread_join(handle->handle, NULL) != 0) { DEBUG_ERROR("pthread_join failed for thread: %s", handle->name); free(handle); return false; } if (resultCode) *resultCode = handle->resultCode; free(handle); return true; } looking-glass-B6/common/src/platform/linux/time.c000066400000000000000000000061411434445012300222420ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/time.h" #include "common/debug.h" #include "common/thread.h" #include "common/ll.h" #include #include #include #include #include struct LGTimerState { bool running; struct LGThread * thread; struct ll * timers; }; struct LGTimer { unsigned int interval; unsigned int count; LGTimerFn fn; void * udata; }; static struct LGTimerState l_ts = { 0 }; static int timerFn(void * fn) { struct LGTimer * timer; struct timespec time; clock_gettime(CLOCK_MONOTONIC, &time); while(l_ts.running) { ll_lock(l_ts.timers); ll_forEachNL(l_ts.timers, item, timer) { if (timer->count++ == timer->interval) { timer->count = 0; if (!timer->fn(timer->udata)) ll_removeNL(l_ts.timers, item); } } ll_unlock(l_ts.timers); tsAdd(&time, 1000000); while(clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &time, NULL) != 0) {} } return 0; } static inline bool setupTimerThread(void) { if (l_ts.thread) return true; l_ts.timers = ll_new(); l_ts.running = true; if (!l_ts.timers) { DEBUG_ERROR("failed to create linked list"); goto err; } if (!lgCreateThread("TimerThread", timerFn, NULL, &l_ts.thread)) { DEBUG_ERROR("failed to create the timer thread"); goto err_thread; } return true; err_thread: ll_free(l_ts.timers); err: return false; } static void destroyTimerThread(void) { if (ll_count(l_ts.timers)) return; l_ts.running = false; lgJoinThread(l_ts.thread, NULL); l_ts.thread = NULL; } bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn, void * udata, LGTimer ** result) { struct LGTimer * timer = malloc(sizeof(*timer)); if (!timer) { DEBUG_ERROR("out of memory"); return false; } timer->interval = intervalMS; timer->count = 0; timer->fn = fn; timer->udata = udata; if (!setupTimerThread()) { DEBUG_ERROR("failed to setup the timer thread"); goto err_thread; } ll_push(l_ts.timers, timer); *result = timer; return true; err_thread: free(timer); return false; } void lgTimerDestroy(LGTimer * timer) { if (!l_ts.thread) return; ll_removeData(l_ts.timers, timer); free(timer); destroyTimerThread(); } looking-glass-B6/common/src/platform/windows/000077500000000000000000000000001434445012300214715ustar00rootroot00000000000000looking-glass-B6/common/src/platform/windows/CMakeLists.txt000066400000000000000000000026451434445012300242400ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0) project(lg_common_platform_code LANGUAGES C) include_directories( ${PROJECT_TOP}/vendor/ivshmem ) # allow use of functions for Windows 7 or later add_compile_definitions(WINVER=0x0601 _WIN32_WINNT=0x0601) if (MINGW) # Build our own ntdll.dll import library # This tricks MinGW into not linking stuff like memcpy from ntdll.dll instead of mscvrt.dll if(NOT CMAKE_DLLTOOL) # cmake older than 3.16 doesn't know how to find dlltool find_program(CMAKE_DLLTOOL NAMES "x86_64-w64-mingw32-dlltool" "dlltool.exe" DOC "dlltool executable") endif() add_custom_command(OUTPUT "${PROJECT_BINARY_DIR}/ntdll.a" COMMAND "${CMAKE_DLLTOOL}" -d "${PROJECT_SOURCE_DIR}/ntdll.def" -l "${PROJECT_BINARY_DIR}/ntdll.a" MAIN_DEPENDENCY "${PROJECT_SOURCE_DIR}/ntdll.def" COMMENT "Building import library ntdll.a" VERBATIM ) add_custom_target(ntdll_target DEPENDS "${PROJECT_BINARY_DIR}/ntdll.a") add_library(ntdll STATIC IMPORTED GLOBAL) add_dependencies(ntdll ntdll_target) set_target_properties(ntdll PROPERTIES IMPORTED_LOCATION "${PROJECT_BINARY_DIR}/ntdll.a") endif() add_library(lg_common_platform_code STATIC debug.c crash.c sysinfo.c thread.c event.c windebug.c ivshmem.c time.c cpuinfo.c ) target_link_libraries(lg_common_platform_code lg_common setupapi ntdll ) if (ENABLE_BACKTRACE) target_link_libraries(lg_common_platform_code dbghelp) endif() looking-glass-B6/common/src/platform/windows/cpuinfo.c000066400000000000000000000055431434445012300233070ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/cpuinfo.h" #include "common/debug.h" #include "common/windebug.h" #include static bool getCPUModel(char * model, size_t modelSize) { if (!model) return true; LRESULT lr; DWORD cb = modelSize; if ((lr = RegGetValueA(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\SYSTEM\\CentralProcessor\\0", "ProcessorNameString", RRF_RT_REG_SZ, NULL, model, &cb))) { DEBUG_WINERROR("Failed to query registry", lr); return false; } // trim any whitespace --cb; while (cb > 0 && isspace(model[cb-1])) --cb; model[cb] = '\0'; return true; } static bool getCoreCount(int * cores, int * procs, int * sockets) { if (!cores && !procs) return true; DWORD cb = 0; GetLogicalProcessorInformationEx(RelationAll, NULL, &cb); if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { DEBUG_WINERROR("Failed to call GetLogicalProcessorInformationEx", GetLastError()); return false; } BYTE buffer[cb]; if (!GetLogicalProcessorInformationEx(RelationAll, (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) buffer, &cb)) { DEBUG_WINERROR("Failed to call GetLogicalProcessorInformationEx", GetLastError()); return false; } if (cores) *cores = 0; if (procs) *procs = 0; if (sockets) *sockets = 0; DWORD offset = 0; while (offset < cb) { PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX lpi = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) (buffer + offset); switch (lpi->Relationship) { case RelationProcessorCore: if (cores) ++*cores; if (procs) for (int i = 0; i < lpi->Processor.GroupCount; ++i) *procs += __builtin_popcount(lpi->Processor.GroupMask[i].Mask); break; case RelationProcessorPackage: if (sockets) ++*sockets; break; default: break; } offset += lpi->Size; } return true; } bool lgCPUInfo(char * model, size_t modelSize, int * procs, int * cores, int * sockets) { return getCPUModel(model, modelSize) && getCoreCount(cores, procs, sockets); } looking-glass-B6/common/src/platform/windows/crash.c000066400000000000000000000120371434445012300227400ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/crash.h" #include "common/debug.h" #include "common/version.h" #include "common/windebug.h" #ifdef ENABLE_BACKTRACE #include #include #include #include static const char * exception_name(DWORD code) { switch (code) { case EXCEPTION_ACCESS_VIOLATION: return "ACCESS_VIOLATION"; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "ARRAY_BOUNDS_EXCEEDED"; case EXCEPTION_BREAKPOINT: return "BREAKPOINT"; case EXCEPTION_DATATYPE_MISALIGNMENT: return "DATATYPE_MISALIGNMENT"; case EXCEPTION_FLT_DENORMAL_OPERAND: return "FLT_DENORMAL_OPERAND"; case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "FLT_DIVIDE_BY_ZERO"; case EXCEPTION_FLT_INEXACT_RESULT: return "FLT_INEXACT_RESULT"; case EXCEPTION_FLT_INVALID_OPERATION: return "FLT_INVALID_OPERATION"; case EXCEPTION_FLT_OVERFLOW: return "FLT_OVERFLOW"; case EXCEPTION_FLT_STACK_CHECK: return "FLT_STACK_CHECK"; case EXCEPTION_FLT_UNDERFLOW: return "FLT_UNDERFLOW"; case EXCEPTION_ILLEGAL_INSTRUCTION: return "ILLEGAL_INSTRUCTION"; case EXCEPTION_IN_PAGE_ERROR: return "IN_PAGE_ERROR"; case EXCEPTION_INT_DIVIDE_BY_ZERO: return "INT_DIVIDE_BY_ZERO"; case EXCEPTION_INT_OVERFLOW: return "INT_OVERFLOW"; case EXCEPTION_INVALID_DISPOSITION: return "INVALID_DISPOSITION"; case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "NONCONTINUABLE_EXCEPTION"; case EXCEPTION_PRIV_INSTRUCTION: return "PRIV_INSTRUCTION"; case EXCEPTION_SINGLE_STEP: return "SINGLE_STEP"; case EXCEPTION_STACK_OVERFLOW: return "STACK_OVERFLOW"; default: return "unknown"; } } static LONG CALLBACK exception_filter(EXCEPTION_POINTERS * exc) { PEXCEPTION_RECORD excInfo = exc->ExceptionRecord; CONTEXT context; memcpy(&context, exc->ContextRecord, sizeof context); DEBUG_ERROR("==== FATAL CRASH (%s) ====", BUILD_VERSION); DEBUG_ERROR("exception 0x%08lx (%s), address is %p", excInfo->ExceptionCode, exception_name(excInfo->ExceptionCode), excInfo->ExceptionAddress); if (!SymInitialize(GetCurrentProcess(), NULL, TRUE)) { DEBUG_WINERROR("Failed to SymInitialize, could not generate stack trace", GetLastError()); goto fail; } SymSetOptions(SYMOPT_LOAD_LINES); STACKFRAME64 frame = { 0 }; frame.AddrPC.Offset = context.Rip; frame.AddrPC.Mode = AddrModeFlat; frame.AddrFrame.Offset = context.Rbp; frame.AddrFrame.Mode = AddrModeFlat; frame.AddrStack.Offset = context.Rsp; frame.AddrStack.Mode = AddrModeFlat; HANDLE hProcess = GetCurrentProcess(); HANDLE hThread = GetCurrentThread(); for (int i = 1; StackWalk64(IMAGE_FILE_MACHINE_AMD64, hProcess, hThread, &frame, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL); ++i) { DWORD64 moduleBase = SymGetModuleBase64(hProcess, frame.AddrPC.Offset); char moduleName[MAX_PATH]; if (moduleBase && GetModuleFileNameA((HMODULE) moduleBase, moduleName, MAX_PATH)) { DWORD64 disp; char symbolBuf[sizeof(SYMBOL_INFO) + 255]; PSYMBOL_INFO symbol = (PSYMBOL_INFO) symbolBuf; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); symbol->MaxNameLen = 256; if (SymFromAddr(hProcess, frame.AddrPC.Offset, &disp, symbol)) { IMAGEHLP_LINE line = { sizeof(IMAGEHLP_LINE), 0 }; DWORD lineDisp; if (SymGetLineFromAddr64(hProcess, frame.AddrPC.Offset, &lineDisp, &line)) DEBUG_ERROR("[trace]: %2d: %s:%s+0x%" PRIx64 " (%s:%ld+0x%lx)", i, moduleName, symbol->Name, disp, line.FileName, line.LineNumber, lineDisp); else DEBUG_ERROR("[trace]: %2d: %s:%s+0x%" PRIx64, i, moduleName, symbol->Name, disp); } else DEBUG_ERROR("[trace]: %2d: %s+0x%08" PRIx64, i, moduleName, frame.AddrPC.Offset - moduleBase); } else DEBUG_ERROR("[trace]: %2d: 0x%016" PRIx64, i, frame.AddrPC.Offset); } SymCleanup(hProcess); fail: fflush(stderr); return EXCEPTION_CONTINUE_SEARCH; } bool installCrashHandler(const char * exe) { SetUnhandledExceptionFilter(exception_filter); return true; } #else bool installCrashHandler(const char * exe) { return true; } #endif looking-glass-B6/common/src/platform/windows/debug.c000066400000000000000000000022351434445012300227250ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/debug.h" const char ** debug_lookup = NULL; void debug_init(void) { static const char * plainLookup[] = { "" , // DEBUG_LEVEL_NONE "[I] ", // DEBUG_LEVEL_INFO "[W] ", // DEBUG_LEVEL_WARN "[E] ", // DEBUG_LEVEL_ERROR "[F] ", // DEBUG_LEVEL_FIXME "[!] " // DEBUG_LEVEL_FATAL }; debug_lookup = plainLookup; } looking-glass-B6/common/src/platform/windows/event.c000066400000000000000000000041151434445012300227570ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/event.h" #include "common/windebug.h" #include "common/time.h" #include #include LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime) { HANDLE handle = CreateEvent(NULL, autoReset ? FALSE : TRUE, FALSE, NULL); if (!handle) { DEBUG_WINERROR("Failed to create the event", GetLastError()); return NULL; } return (LGEvent *)handle; } LGEvent * lgWrapEvent(void * handle) { return (LGEvent *)handle; } void lgFreeEvent(LGEvent * event) { CloseHandle((HANDLE)event); } bool lgWaitEvent(LGEvent * event, unsigned int timeout) { const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout; do { switch(WaitForSingleObject((HANDLE)event, to)) { case WAIT_OBJECT_0: return true; case WAIT_ABANDONED: continue; case WAIT_TIMEOUT: if (timeout == TIMEOUT_INFINITE) continue; return false; case WAIT_FAILED: DEBUG_WINERROR("Wait for event failed", GetLastError()); return false; } DEBUG_ERROR("Unknown wait event return code"); } while(0); return false; } bool lgSignalEvent(LGEvent * event) { return SetEvent((HANDLE)event); } bool lgResetEvent(LGEvent * event) { return ResetEvent((HANDLE)event); } looking-glass-B6/common/src/platform/windows/ivshmem.c000066400000000000000000000152461434445012300233150ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/ivshmem.h" #include "common/option.h" #include "common/vector.h" #include "common/windebug.h" #include #include "ivshmem.h" #include #include struct IVSHMEMInfo { HANDLE handle; }; void ivshmemOptionsInit(void) { static struct Option options[] = { { .module = "os", .name = "shmDevice", .description = "The IVSHMEM device to use", .type = OPTION_TYPE_INT, .value.x_int = 0 }, {0} }; option_register(options); } struct IVSHMEMData { SP_DEVINFO_DATA devInfoData; DWORD64 busAddr; }; static int ivshmemComparator(const void * a_, const void * b_) { const struct IVSHMEMData * a = a_; const struct IVSHMEMData * b = b_; if (a->busAddr < b->busAddr) return -1; if (a->busAddr > b->busAddr) return 1; return 0; } bool ivshmemInit(struct IVSHMEM * dev) { DEBUG_ASSERT(dev && !dev->opaque); HANDLE handle; HDEVINFO devInfoSet; PSP_DEVICE_INTERFACE_DETAIL_DATA infData = NULL; SP_DEVINFO_DATA devInfoData = {0}; SP_DEVICE_INTERFACE_DATA devInterfaceData = {0}; Vector devices; devInfoSet = SetupDiGetClassDevs(&GUID_DEVINTERFACE_IVSHMEM, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); if (!vector_create(&devices, sizeof(struct IVSHMEMData), 1)) { DEBUG_ERROR("Failed to allocate memory"); return false; } for (int i = 0; SetupDiEnumDeviceInfo(devInfoSet, i, &devInfoData); ++i) { struct IVSHMEMData * device = vector_push(&devices, NULL); DWORD bus, addr; if (!SetupDiGetDeviceRegistryProperty(devInfoSet, &devInfoData, SPDRP_BUSNUMBER, NULL, (void*) &bus, sizeof(bus), NULL)) { DEBUG_WINERROR("Failed to SetupDiGetDeviceRegistryProperty", GetLastError()); bus = 0xFFFF; } if (!SetupDiGetDeviceRegistryProperty(devInfoSet, &devInfoData, SPDRP_ADDRESS, NULL, (void*) &addr, sizeof(addr), NULL)) { DEBUG_WINERROR("Failed to SetupDiGetDeviceRegistryProperty", GetLastError()); addr = 0xFFFFFFFF; } device->busAddr = (((DWORD64) bus) << 32) | addr; memcpy(&device->devInfoData, &devInfoData, sizeof(SP_DEVINFO_DATA)); } if (GetLastError() != ERROR_NO_MORE_ITEMS) { vector_destroy(&devices); DEBUG_WINERROR("SetupDiEnumDeviceInfo failed", GetLastError()); return false; } const int shmDevice = option_get_int("os", "shmDevice"); qsort(vector_data(&devices), vector_size(&devices), sizeof(struct IVSHMEMData), ivshmemComparator); struct IVSHMEMData * device; vector_forEachRefIdx(i, device, &devices) { DWORD bus = device->busAddr >> 32; DWORD addr = device->busAddr & 0xFFFFFFFF; DEBUG_INFO("IVSHMEM %" PRIuPTR "%c on bus 0x%lx, device 0x%lx, function 0x%lx", i, i == shmDevice ? '*' : ' ', bus, addr >> 16, addr & 0xFFFF); } if (!device) { vector_destroy(&devices); DEBUG_ERROR("Unable to find a IVSHMEM device"); return false; } device = vector_ptrTo(&devices, shmDevice); memcpy(&devInfoData, &device->devInfoData, sizeof(SP_DEVINFO_DATA)); vector_destroy(&devices); if (SetupDiEnumDeviceInterfaces(devInfoSet, &devInfoData, &GUID_DEVINTERFACE_IVSHMEM, 0, &devInterfaceData) == FALSE) { DEBUG_WINERROR("SetupDiEnumDeviceInterfaces failed", GetLastError()); return false; } DWORD reqSize = 0; SetupDiGetDeviceInterfaceDetail(devInfoSet, &devInterfaceData, NULL, 0, &reqSize, NULL); if (!reqSize) { DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError()); return false; } infData = calloc(1, reqSize); infData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (!SetupDiGetDeviceInterfaceDetail(devInfoSet, &devInterfaceData, infData, reqSize, NULL, NULL)) { free(infData); DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError()); return false; } handle = CreateFile(infData->DevicePath, 0, 0, NULL, OPEN_EXISTING, 0, 0); if (handle == INVALID_HANDLE_VALUE) { SetupDiDestroyDeviceInfoList(devInfoSet); free(infData); DEBUG_WINERROR("CreateFile returned INVALID_HANDLE_VALUE", GetLastError()); return false; } free(infData); SetupDiDestroyDeviceInfoList(devInfoSet); struct IVSHMEMInfo * info = malloc(sizeof(*info)); info->handle = handle; dev->opaque = info; dev->size = 0; dev->mem = NULL; return true; } bool ivshmemOpen(struct IVSHMEM * dev) { DEBUG_ASSERT(dev && dev->opaque && !dev->mem); struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque; IVSHMEM_SIZE size; if (!DeviceIoControl(info->handle, IOCTL_IVSHMEM_REQUEST_SIZE, NULL, 0, &size, sizeof(IVSHMEM_SIZE), NULL, NULL)) { DEBUG_WINERROR("DeviceIoControl Failed", GetLastError()); return 0; } IVSHMEM_MMAP_CONFIG config = { .cacheMode = IVSHMEM_CACHE_WRITECOMBINED }; IVSHMEM_MMAP map = { 0 }; if (!DeviceIoControl( info->handle, IOCTL_IVSHMEM_REQUEST_MMAP, &config, sizeof(IVSHMEM_MMAP_CONFIG), &map , sizeof(IVSHMEM_MMAP), NULL, NULL)) { DEBUG_WINERROR("DeviceIoControl Failed", GetLastError()); return false; } dev->size = (unsigned int)size; dev->mem = map.ptr; return true; } void ivshmemClose(struct IVSHMEM * dev) { DEBUG_ASSERT(dev && dev->opaque && dev->mem); struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque; if (!DeviceIoControl(info->handle, IOCTL_IVSHMEM_RELEASE_MMAP, NULL, 0, NULL, 0, NULL, NULL)) DEBUG_WINERROR("DeviceIoControl failed", GetLastError()); dev->size = 0; dev->mem = NULL; } void ivshmemFree(struct IVSHMEM * dev) { DEBUG_ASSERT(dev && dev->opaque && !dev->mem); struct IVSHMEMInfo * info = (struct IVSHMEMInfo *)dev->opaque; free(info); dev->opaque = NULL; } looking-glass-B6/common/src/platform/windows/ntdll.def000066400000000000000000000003311434445012300232630ustar00rootroot00000000000000; This file is used to trick MinGW to not like stuff like memcpy from ntdll.dll. ; See CMakeLists.txt for how this is compiled. LIBRARY "ntdll.dll" EXPORTS NtDelayExecution NtSetTimerResolution RtlNtStatusToDosError looking-glass-B6/common/src/platform/windows/sysinfo.c000066400000000000000000000017741434445012300233400ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include int sysinfo_gfx_max_multisample(void) { //FIXME: Implement this return 4; } long sysinfo_getPageSize(void) { SYSTEM_INFO si; GetSystemInfo(&si); return si.dwPageSize; }looking-glass-B6/common/src/platform/windows/thread.c000066400000000000000000000046671434445012300231210ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/thread.h" #include "common/debug.h" #include "common/windebug.h" #include struct LGThread { const char * name; LGThreadFunction function; void * opaque; HANDLE handle; DWORD threadID; int resultCode; }; static DWORD WINAPI threadWrapper(LPVOID lpParameter) { LGThread * handle = (LGThread *)lpParameter; handle->resultCode = handle->function(handle->opaque); return 0; } bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle) { *handle = malloc(sizeof(**handle)); (*handle)->name = name; (*handle)->function = function; (*handle)->opaque = opaque; (*handle)->handle = CreateThread(NULL, 0, threadWrapper, *handle, 0, &(*handle)->threadID); if (!(*handle)->handle) { free(*handle); *handle = NULL; DEBUG_WINERROR("CreateThread failed", GetLastError()); return false; } return true; } bool lgJoinThread(LGThread * handle, int * resultCode) { while(true) { switch(WaitForSingleObject(handle->handle, INFINITE)) { case WAIT_OBJECT_0: if (resultCode) *resultCode = handle->resultCode; CloseHandle(handle->handle); free(handle); return true; case WAIT_ABANDONED: case WAIT_TIMEOUT: continue; case WAIT_FAILED: DEBUG_WINERROR("Wait for thread failed", GetLastError()); CloseHandle(handle->handle); free(handle); return false; } break; } DEBUG_WINERROR("Unknown failure waiting for thread", GetLastError()); return false; } looking-glass-B6/common/src/platform/windows/time.c000066400000000000000000000042221434445012300225730ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/time.h" #include "common/debug.h" // decared by the platform extern HWND MessageHWND; struct LGTimer { LGTimerFn fn; void * udata; UINT_PTR handle; bool running; }; static void TimerProc(HWND Arg1, UINT Arg2, UINT_PTR Arg3, DWORD Arg4) { LGTimer * timer = (LGTimer *)Arg3; if (!timer->fn(timer->udata)) { KillTimer(Arg1, timer->handle); timer->running = false; } } bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn, void * udata, LGTimer ** result) { LGTimer * ret = malloc(sizeof(*ret)); if (!ret) { DEBUG_ERROR("failed to malloc LGTimer struct"); return false; } ret->fn = fn; ret->udata = udata; ret->running = true; ret->handle = SetTimer(MessageHWND, (UINT_PTR)ret, intervalMS, TimerProc); *result = ret; return true; } void lgTimerDestroy(LGTimer * timer) { if (timer->running) { if (MessageHWND && !KillTimer(MessageHWND, timer->handle)) DEBUG_ERROR("failed to destroy the timer"); } free(timer); } NTSYSCALLAPI NTSTATUS NTAPI NtSetTimerResolution( _In_ ULONG DesiredTime, _In_ BOOLEAN SetResolution, _Out_ PULONG ActualTime ); void windowsSetTimerResolution(void) { ULONG actualResolution; NtSetTimerResolution(1, true, &actualResolution); DEBUG_INFO("System timer resolution: %.1f μs", actualResolution / 10.0); } looking-glass-B6/common/src/platform/windows/windebug.c000066400000000000000000000033551434445012300234470ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/debug.h" #include "common/windebug.h" #include void DebugWinError(const char * file, const unsigned int line, const char * function, const char * desc, HRESULT status) { char *buffer; if (!FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, status, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (char*)&buffer, 1024, NULL )) { DEBUG_ERROR("FormatMessage failed with code 0x%08lx", GetLastError()); fprintf(stderr, "%12" PRId64 " [E] %20s:%-4u | %-30s | %s: 0x%08x\n", microtime(), file, line, function, desc, (int)status); return; } for(size_t i = strlen(buffer) - 1; i > 0; --i) if (buffer[i] == '\n' || buffer[i] == '\r') buffer[i] = 0; fprintf(stderr, "%12" PRId64 " [E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", microtime(), file, line, function, desc, (int)status, buffer); LocalFree(buffer); } looking-glass-B6/common/src/rects.c000066400000000000000000000164171434445012300174500ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/rects.h" #include "common/util.h" #include struct Corner { int x; int y; int delta; }; struct Edge { int x; int delta; }; inline static bool rectIntersects(const FrameDamageRect * r1, const FrameDamageRect * r2) { return !( r1->x > r2->x + r2->width || r2->x > r1->x + r1->width || r1->y > r2->y + r2->height || r2->y > r1->y + r1->height); } inline static bool rectContains(const FrameDamageRect * r1, const FrameDamageRect * r2) { return !( r2->x < r1->x || r2->x + r2->width > r1->x + r1->width || r2->y < r1->y || r2->y + r2->height > r1->y + r1->height); } inline static int removeRects(FrameDamageRect * rects, int count, bool removed[]) { int o = 0; for (int i = 0; i < count; ++i) { if (removed[i] || i == o++) continue; rects[o-1] = rects[i]; } return o; } static int cornerCompare(const void * a_, const void * b_) { const struct Corner * a = a_; const struct Corner * b = b_; if (a->y < b->y) return -1; if (a->y > b->y) return +1; if (a->x < b->x) return -1; if (a->x > b->x) return +1; return 0; } inline static void rectsBufferCopy(FrameDamageRect * rects, int count, uint8_t * dst, int dstStride, int height, const uint8_t * src, int srcStride, void * opaque, void (*rowCopyStart)(int y, void * opaque), void (*rowCopyFinish)(int y, void * opaque)) { const int cornerCount = 4 * count; struct Corner corners[cornerCount]; for (int i = 0; i < count; ++i) { FrameDamageRect * rect = rects + i; corners[4 * i + 0] = (struct Corner) { .x = rect->x, .y = rect->y, .delta = 1 }; corners[4 * i + 1] = (struct Corner) { .x = rect->x + rect->width, .y = rect->y, .delta = -1 }; corners[4 * i + 2] = (struct Corner) { .x = rect->x, .y = rect->y + rect->height, .delta = -1 }; corners[4 * i + 3] = (struct Corner) { .x = rect->x + rect->width, .y = rect->y + rect->height, .delta = 1 }; } qsort(corners, cornerCount, sizeof(struct Corner), cornerCompare); struct Edge active_[2][cornerCount]; struct Edge change[cornerCount]; int prev_y = 0; int activeRow = 0; int actives = 0; for (int rs = 0;;) { int y = corners[rs].y; int re = rs; while (re < cornerCount && corners[re].y == y) ++re; if (y > height) y = height; int changes = 0; for (int i = rs; i < re; ) { int x = corners[i].x; int delta = 0; while (i < re && corners[i].x == x) delta += corners[i++].delta; change[changes++] = (struct Edge) { .x = x, .delta = delta }; } if (rowCopyStart) rowCopyStart(y, opaque); struct Edge * active = active_[activeRow]; int x1 = 0; int in_rect = 0; for (int i = 0; i < actives; ++i) { if (!in_rect) x1 = active[i].x; in_rect += active[i].delta; if (!in_rect) rectCopyUnaligned(dst, src, prev_y, y, x1 * 4, dstStride, srcStride, (active[i].x - x1) * 4); } if (re >= cornerCount || y == height) break; if (rowCopyFinish) rowCopyFinish(y, opaque); struct Edge * new = active_[activeRow ^ 1]; int ai = 0; int ci = 0; int ni = 0; while (ai < actives && ci < changes) { if (active[ai].x < change[ci].x) new[ni++] = active[ai++]; else if (active[ai].x > change[ci].x) new[ni++] = change[ci++]; else { active[ai].delta += change[ci++].delta; if (active[ai].delta != 0) new[ni++] = active[ai]; ++ai; } } // only one of (actives - ai) and (changes - ci) will be non-zero. memcpy(new + ni, active + ai, (actives - ai) * sizeof(struct Edge)); memcpy(new + ni, change + ci, (changes - ci) * sizeof(struct Edge)); ni += actives - ai; ni += changes - ci; actives = ni; prev_y = y; rs = re; activeRow ^= 1; } } struct ToFramebufferData { FrameBuffer * frame; int stride; }; static void fbRowFinish(int y, void * opaque) { struct ToFramebufferData * data = opaque; framebuffer_set_write_ptr(data->frame, y * data->stride); } void rectsBufferToFramebuffer(FrameDamageRect * rects, int count, FrameBuffer * frame, int dstStride, int height, const uint8_t * src, int srcStride) { struct ToFramebufferData data = { .frame = frame, .stride = dstStride }; rectsBufferCopy(rects, count, framebuffer_get_data(frame), dstStride, height, src, srcStride, &data, NULL, fbRowFinish); framebuffer_set_write_ptr(frame, height * dstStride); } struct FromFramebufferData { const FrameBuffer * frame; int stride; }; static void fbRowStart(int y, void * opaque) { struct FromFramebufferData * data = opaque; framebuffer_wait(data->frame, y * data->stride); } void rectsFramebufferToBuffer(FrameDamageRect * rects, int count, uint8_t * dst, int dstStride, int height, const FrameBuffer * frame, int srcStride) { struct FromFramebufferData data = { .frame = frame, .stride = srcStride }; rectsBufferCopy(rects, count, dst, dstStride, height, framebuffer_get_buffer(frame), srcStride, &data, fbRowStart, NULL); } int rectsMergeOverlapping(FrameDamageRect * rects, int count) { if (count == 0) return 0; bool removed[count]; bool changed; memset(removed, 0, sizeof(removed)); do { changed = false; for (int i = 0; i < count; ++i) { if (removed[i]) continue; for (int j = i + 1; j < count; ++j) { if (removed[j] || !rectIntersects(rects + i, rects + j)) continue; const uint32_t x2 = max(rects[i].x + rects[i].width, rects[j].x + rects[j].width); const uint32_t y2 = max(rects[i].y + rects[i].height, rects[j].y + rects[j].height); rects[i].x = min(rects[i].x, rects[j].x); rects[i].y = min(rects[i].y, rects[j].y); rects[i].width = x2 - rects[i].x; rects[i].height = y2 - rects[i].y; removed[j] = true; changed = true; } } } while (changed); return removeRects(rects, count, removed); } int rectsRejectContained(FrameDamageRect * rects, int count) { bool removed[count]; memset(removed, 0, sizeof(removed)); for (int i = 0; i < count; ++i) { if (removed[i]) continue; for (int j = 0; j < count; ++j) { if (j == i || removed[j]) continue; removed[j] = rectContains(rects + i, rects + j); } } return removeRects(rects, count, removed); } looking-glass-B6/common/src/ringbuffer.c000066400000000000000000000177701434445012300204640ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/ringbuffer.h" #include "common/debug.h" #include "common/util.h" #include #include #include struct RingBuffer { uint32_t length; uint32_t valueSize; _Atomic(uint32_t) readPos; _Atomic(uint32_t) writePos; bool unbounded; char values[0]; }; RingBuffer ringbuffer_newInternal(int length, size_t valueSize, bool unbounded) { DEBUG_ASSERT(valueSize > 0 && valueSize < UINT32_MAX); struct RingBuffer * rb = calloc(1, sizeof(*rb) + valueSize * length); if (!rb) { DEBUG_ERROR("out of memory"); return NULL; } rb->length = length; rb->valueSize = valueSize; atomic_store(&rb->readPos , 0); atomic_store(&rb->writePos, 0); rb->unbounded = unbounded; return rb; } RingBuffer ringbuffer_new(int length, size_t valueSize) { return ringbuffer_newInternal(length, valueSize, false); } RingBuffer ringbuffer_newUnbounded(int length, size_t valueSize) { return ringbuffer_newInternal(length, valueSize, true); } void ringbuffer_free(RingBuffer * rb) { if (!*rb) return; free(*rb); *rb = NULL; } void ringbuffer_push(RingBuffer rb, const void * value) { if (!rb->unbounded && ringbuffer_getCount(rb) == rb->length) ringbuffer_consume(rb, NULL, 1); ringbuffer_append(rb, value, 1); } void ringbuffer_reset(RingBuffer rb) { atomic_store(&rb->readPos, 0); atomic_store(&rb->writePos, 0); } int ringbuffer_getLength(const RingBuffer rb) { return rb->length; } int ringbuffer_getStart(const RingBuffer rb) { return atomic_load(&rb->readPos) % rb->length; } int ringbuffer_getCount(const RingBuffer rb) { uint32_t writePos = atomic_load(&rb->writePos); uint32_t readPos = atomic_load(&rb->readPos); return writePos - readPos; } void * ringbuffer_getValues(const RingBuffer rb) { return rb->values; } int ringbuffer_append(const RingBuffer rb, const void * values, int count) { if (count == 0) return 0; // Seeking backwards is only supported in unbounded mode at the moment if (count < 0 && !rb->unbounded) return 0; uint32_t readPos = atomic_load_explicit(&rb->readPos, memory_order_acquire); uint32_t writePos = atomic_load_explicit(&rb->writePos, memory_order_relaxed); uint32_t newWritePos = writePos; if (count < 0) { // Seeking backwards; just update the write pointer newWritePos += count; } else { int32_t writeOffset = writePos - readPos; if (writeOffset < 0) { DEBUG_ASSERT(rb->unbounded); // The reader is ahead of the writer; skip new values to remain in sync int32_t underrun = -writeOffset; int32_t skipLen = min(underrun, count); if (values) values += skipLen * rb->valueSize; count -= skipLen; newWritePos += skipLen; writeOffset = newWritePos - readPos; } if (count > 0) { DEBUG_ASSERT(writeOffset >= 0); // We may not be able to write anything if the writer is too far ahead of // the reader uint32_t writeLen = 0; if (writeOffset < rb->length) { uint32_t writeIndex = newWritePos % rb->length; uint32_t writeAvailable = rb->length - writeOffset; uint32_t writeAvailableBack = min(rb->length - writeIndex, writeAvailable); writeLen = min(count, writeAvailable); uint32_t writeLenBack = min(writeLen, writeAvailableBack); uint32_t writeLenFront = writeLen - writeLenBack; if (values) { memcpy(rb->values + writeIndex * rb->valueSize, values, writeLenBack * rb->valueSize); memcpy(rb->values, values + writeLenBack * rb->valueSize, writeLenFront * rb->valueSize); } else { memset(rb->values + writeIndex * rb->valueSize, 0, writeLenBack * rb->valueSize); memset(rb->values, 0, writeLenFront * rb->valueSize); } } if (rb->unbounded) newWritePos += count; else newWritePos += writeLen; } } atomic_store_explicit(&rb->writePos, newWritePos, memory_order_release); return newWritePos - writePos; } int ringbuffer_consume(const RingBuffer rb, void * values, int count) { if (count == 0) return 0; // Seeking backwards is only supported in unbounded mode at the moment if (count < 0 && !rb->unbounded) return 0; uint32_t readPos = atomic_load_explicit(&rb->readPos, memory_order_relaxed); uint32_t writePos = atomic_load_explicit(&rb->writePos, memory_order_acquire); uint32_t newReadPos = readPos; if (count < 0) { // Seeking backwards; just update the read pointer newReadPos += count; } else { int32_t writeOffset = writePos - newReadPos; if (writeOffset < 0) { DEBUG_ASSERT(rb->unbounded); // We are already in an underrun condition; just fill the buffer with // zeros newReadPos += count; if (values) memset(values, 0, count * rb->valueSize); } else { uint32_t readIndex = newReadPos % rb->length; uint32_t readAvailable = min(writeOffset, rb->length); uint32_t readLen = min(count, readAvailable); if (values) { uint32_t readAvailableBack = min(rb->length - readIndex, readAvailable); uint32_t readLenBack = min(readLen, readAvailableBack); uint32_t readLenFront = readLen - readLenBack; memcpy(values, rb->values + readIndex * rb->valueSize, readLenBack * rb->valueSize); memcpy(values + readLenBack * rb->valueSize, rb->values, readLenFront * rb->valueSize); if (rb->unbounded && readLen < count) { // One of two things has happened: we have caught up with the writer // and are starting to underrun, or we are really far behind the // writer and an overrun has occurred. Either way, the only thing left // to do is to fill the rest of the buffer with zeros uint32_t remaining = count - readLen; memset(values + readLen * rb->valueSize, 0, remaining * rb->valueSize); } } if (rb->unbounded) newReadPos += count; else newReadPos += readLen; } } atomic_store_explicit(&rb->readPos, newReadPos, memory_order_release); return newReadPos - readPos; } void ringbuffer_forEach(const RingBuffer rb, RingBufferIterator fn, void * udata, bool reverse) { uint32_t readPos = atomic_load_explicit(&rb->readPos, memory_order_relaxed); uint32_t writePos = atomic_load_explicit(&rb->writePos, memory_order_acquire); int32_t writeOffset = writePos - readPos; if (writeOffset < 0) { DEBUG_ASSERT(rb->unbounded); return; } uint32_t readAvailable = min(writeOffset, rb->length); if (reverse) { readPos = readPos + readAvailable - 1; for (int i = 0; i < readAvailable; ++i, --readPos) { uint32_t readIndex = readPos % rb->length; void * value = rb->values + readIndex * rb->valueSize; if (!fn(i, value, udata)) break; } } else { for (int i = 0; i < readAvailable; ++i, ++readPos) { uint32_t readIndex = readPos % rb->length; void * value = rb->values + readIndex * rb->valueSize; if (!fn(i, value, udata)) break; } } } looking-glass-B6/common/src/runningavg.c000066400000000000000000000033271434445012300205020ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/runningavg.h" #include "common/debug.h" #include struct RunningAvg { int length, samples; int pos; int64_t value; int64_t values[0]; }; RunningAvg runningavg_new(int length) { struct RunningAvg * ra = calloc(1, sizeof(*ra) + sizeof(*ra->values) * length); if (!ra) { DEBUG_ERROR("out of memory"); return NULL; } ra->length = length; return ra; } void runningavg_free(RunningAvg * ra) { free(*ra); *ra = NULL; } void runningavg_push(RunningAvg ra, int64_t value) { if (ra->samples == ra->length) ra->value -= ra->values[ra->pos]; else ++ra->samples; ra->value += value; ra->values[ra->pos++] = value; if (ra->pos == ra->length) ra->pos = 0; } void runningavg_reset(RunningAvg ra) { ra->samples = 0; ra->pos = 0; ra->value = 0; } double runningavg_calc(RunningAvg ra) { return (double)ra->value / ra->samples; } looking-glass-B6/common/src/stringlist.c000066400000000000000000000040771434445012300205310ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/stringlist.h" #include "common/vector.h" #include "common/debug.h" #include struct StringList { bool owns_strings; Vector vector; }; StringList stringlist_new(bool owns_strings) { StringList sl = malloc(sizeof(*sl)); if (!sl) { DEBUG_ERROR("out of memory"); return NULL; } sl->owns_strings = owns_strings; if (!vector_create(&sl->vector, sizeof(char *), 32)) { free(sl); return NULL; } return sl; } void stringlist_free(StringList * sl) { stringlist_clear(*sl); vector_destroy(&(*sl)->vector); free((*sl)); *sl = NULL; } int stringlist_push(StringList sl, char * str) { int index = vector_size(&sl->vector); vector_push(&sl->vector, &str); return index; } void stringlist_remove(StringList sl, unsigned int index) { vector_remove(&sl->vector, index); } unsigned int stringlist_count(StringList sl) { return vector_size(&sl->vector); } char * stringlist_at(StringList sl, unsigned int index) { if (index >= vector_size(&sl->vector)) return NULL; char * ptr; vector_at(&sl->vector, index, &ptr); return ptr; } void stringlist_clear(StringList sl) { if (sl->owns_strings) { char * ptr; vector_forEach(ptr, &sl->vector) free(ptr); } vector_clear(&sl->vector); } looking-glass-B6/common/src/stringutils.c000066400000000000000000000042061434445012300207100ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include "common/stringutils.h" int valloc_sprintf(char ** str, const char * format, va_list ap) { if (!str) return -1; *str = NULL; va_list ap1; va_copy(ap1, ap); // for some reason some versions of GCC warn about format being NULL when any // kind of optimization is enabled, this is a false positive. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-truncation" const int len = vsnprintf(*str, 0, format, ap1); #pragma GCC diagnostic pop va_end(ap1); if (len < 0) return len; *str = malloc(len + 1); int ret = vsnprintf(*str, len + 1, format, ap); if (ret < 0) { free(*str); *str = NULL; return ret; } return ret; } int alloc_sprintf(char ** str, const char * format, ...) { va_list ap; va_start(ap, format); int ret = valloc_sprintf(str, format, ap); va_end(ap); return ret; } bool str_containsValue(const char * list, char delimiter, const char * value) { size_t len = strlen(value); const char span[] = {delimiter, '\0'}; while (*list) { if (*list == delimiter) { ++list; continue; } size_t n = strcspn(list, span); if (n == len && strncmp(value, list, n) == 0) return true; list += n; } return false; } looking-glass-B6/common/src/vector.c000066400000000000000000000062661434445012300176330ustar00rootroot00000000000000/** * Looking Glass * Copyright © 2017-2022 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common/vector.h" #include "common/debug.h" #include #include #include Vector * vector_alloc(size_t itemSize, size_t capacity) { Vector * vector = malloc(sizeof(Vector)); if (!vector) return NULL; if (!vector_create(vector, itemSize, capacity)) { free(vector); return NULL; } return vector; } void vector_free(Vector * vector) { if (!vector) return; vector_destroy(vector); free(vector); } bool vector_create(Vector * vector, size_t itemSize, size_t capacity) { vector->itemSize = itemSize; vector->capacity = capacity; vector->size = 0; vector->data = NULL; if (capacity) { vector->data = malloc(itemSize * capacity); if (!vector->data) return false; } return true; } void vector_destroy(Vector * vector) { free(vector->data); vector->capacity = 0; vector->itemSize = 0; } void * vector_push(Vector * vector, void * item) { if (vector->size >= vector->capacity) { size_t newCapacity = vector->capacity < 4 ? 8 : vector->capacity * 2; void * new = realloc(vector->data, newCapacity * vector->itemSize); if (!new) { DEBUG_ERROR("Failed to allocate memory in vector: %" PRIuPTR " bytes", newCapacity * vector->itemSize); return NULL; } vector->capacity = newCapacity; vector->data = new; } void * ptr = (char *)vector->data + vector->size * vector->itemSize; if (item) memcpy(ptr, item, vector->itemSize); ++vector->size; return ptr; } void vector_pop(Vector * vector) { DEBUG_ASSERT(vector->size > 0 && "Attempting to pop from empty vector!"); --vector->size; } void vector_remove(Vector * vector, size_t index) { DEBUG_ASSERT(index < vector->size && "Attempting to remove non-existent index!"); memmove((char *)vector->data + index * vector->itemSize, (char *)vector->data + (index + 1) * vector->itemSize, (vector->size - index - 1) * vector->itemSize ); --vector->size; } void vector_at(Vector * vector, size_t index, void * data) { DEBUG_ASSERT(index < vector->size && "Out of bounds access"); memcpy(data, (char *)vector->data + index * vector->itemSize, vector->itemSize); } void * vector_ptrTo(Vector * vector, size_t index) { DEBUG_ASSERT(index < vector->size && "Out of bounds access"); return (char *)vector->data + index * vector->itemSize; } void vector_clear(Vector * vector) { vector->size = 0; } looking-glass-B6/contrib/000077500000000000000000000000001434445012300155345ustar00rootroot00000000000000looking-glass-B6/contrib/redhat/000077500000000000000000000000001434445012300170035ustar00rootroot00000000000000looking-glass-B6/contrib/redhat/qemu-ivshmem-sock.pp000066400000000000000000000034211434445012300227160ustar00rootroot00000000000000||SE Linux Moduleqemu-ivshmem-sock1.0@dirsearchfilemapgetattrreadopenunix_stream_socket connectto sock_filewriteobject_r@@@@ @user_tmp_t@svirt_t@virtd_t @unconfined_t@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@dirfileunix_stream_socket sock_fileobject_r user_tmp_tsvirt_tvirtd_t unconfined_tlooking-glass-B6/doc/000077500000000000000000000000001434445012300146415ustar00rootroot00000000000000looking-glass-B6/doc/Makefile000066400000000000000000000011721434445012300163020ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) looking-glass-B6/doc/build.rst000066400000000000000000000162211434445012300164740ustar00rootroot00000000000000.. _building: Building ######## The following instructions will help you build Looking Glass from source code. Before attempting this, you should have a basic understanding of how to use the shell. .. _download_source: Downloading ----------- Visit the Looking Glass `Download Page `__, and download the stable version (**recommended**). You can also download a *bleeding-edge version*, or the latest RC version during a Release Candidate cycle. Developers can clone the source code repo with ``git``. .. code:: bash git clone --recursive https://github.com/gnif/LookingGlass.git .. warning:: Please only clone from Git if you're a developer, and know what you're doing. Looking Glass requires git submodules that must be setup and updated when building. Source code downloads from the website come bundled with the necessary submodules. .. note:: When using the latest bleeding-edge client version, you *MUST* download and install the corresponding host application. .. _build_client_section: Client ------ .. _installing_build_dependencies: Installing build dependencies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ These required libraries and tools should be installed first. .. note:: The below list of dependencies is for Debian. A community maintained list of dependencies for other distributions for the current **stable** version of Looking Glass is maintained on the wiki at: https://looking-glass.io/wiki/Installation_on_other_distributions .. _client_dependencies: Required dependencies ^^^^^^^^^^^^^^^^^^^^^ .. Editor note: Listed dependencies are Debian packages containing the required resources. All dependencies must be explicitly defined. Omitting required dependencies that happen to be pulled in via Depends: or Recommends: from another listed package is not allowed. All required packages must be listed. - ``cmake`` - ``gcc``, ``g++`` \| ``clang`` - ``libegl-dev`` - ``libgl-dev`` - ``libgles-dev`` - ``libfontconfig-dev`` - ``libgmp-dev`` - ``libspice-protocol-dev`` - ``make`` - ``nettle-dev`` - ``pkg-config`` .. _client_deps_may_be_disabled: May be disabled <<<<<<<<<<<<<<< These dependencies are required by default, but may be omitted if their feature is disabled when running :ref:`cmake `. - Disable with ``cmake -DENABLE_BACKTRACE=no ..`` - ``binutils-dev`` - Disable with ``cmake -DENABLE_X11=no ..`` - ``libx11-dev`` - ``libxfixes-dev`` - ``libxi-dev`` - ``libxinerama-dev`` - ``libxss-dev`` - ``libxcursor-dev`` - ``libxpresent-dev`` - Disable with ``cmake -DENABLE_WAYLAND=no ..`` - ``libxkbcommon-dev`` - ``libwayland-bin`` - ``libwayland-dev`` - ``wayland-protocols`` - Disable with ``cmake -DENABLE_PIPEWIRE=no ..`` - ``libpipewire-0.3-dev`` - ``libsamplerate0-dev`` - Disable with ``cmake -DENABLE_PULSEAUDIO=no ..`` - ``libpulse-dev`` - ``libsamplerate0-dev`` .. _client_deps_recommended: Recommended <<<<<<<<<<< - ``fonts-dejavu-core`` (This is the default UI font, but a random font will be chosen if not available). .. _client_fetching_with_apt: Fetching with APT ^^^^^^^^^^^^^^^^^ You can fetch these dependencies with the following command: .. code:: bash apt-get install binutils-dev cmake fonts-dejavu-core libfontconfig-dev \ gcc g++ pkg-config libegl-dev libgl-dev libgles-dev libspice-protocol-dev \ nettle-dev libx11-dev libxcursor-dev libxi-dev libxinerama-dev \ libxpresent-dev libxss-dev libxkbcommon-dev libwayland-dev wayland-protocols \ libpipewire-0.3-dev libpulse-dev libsamplerate0-dev You may omit some dependencies if you disable the feature which requires them when running :ref:`cmake `. (See :ref:`client_deps_may_be_disabled`) .. _client_building: Building ~~~~~~~~ If you've downloaded the source code as a zip file, simply unzip and ``cd`` into the new directory. If you've cloned the repo with ``git``, then ``cd`` into the *LookingGlass* directory. .. code:: bash mkdir client/build cd client/build cmake ../ make This will build the ``looking-glass-client`` binary, which is used to display frames from the guest. You can then :ref:`continue installing Looking Glass `, or run it directly from the build directory: .. code:: bash ./looking-glass-client .. seealso:: - :ref:`Client installation ` - :ref:`Client usage ` .. note:: For users running GNOME on Wayland, you may want to enable ``libdecor`` when building. .. code:: bash cmake -DENABLE_LIBDECOR=ON ../ For details, see :ref:`the FAQ `. .. note:: The most common compile error is related to backtrace support. Try disabling this when building: .. code:: bash cmake -DENABLE_BACKTRACE=0 ../ If you disable this and need support for a crash, use ``gdb`` to obtain a backtrace manually. .. _host_building: Host ---- These instructions help you build the host yourself from the :ref:`downloaded source code `. .. warning:: Building the host from source code is not recommended for most purposes, and should only be attempted by users who are prepared to handle issues on their own. Please download the pre-built binary installers from https://looking-glass.io/downloads for stability, and increased support. .. note:: The pre-built binaries also include NvFBC support built in, which is only available to current Nvidia SDK license holders, and cannot be enabled when building the host without also having a license. .. _host_win_on_win: For Windows on Windows ~~~~~~~~~~~~~~~~~~~~~~ 1. Download and install msys2 x86_64 from `http://www.msys2.org/ `__ following the setup instructions provided 2. Run the MSYS2 shell 3. Download build dependencies with pacman .. code:: bash pacman -Fy pacman -Sy git make mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake 4. Change directory to the source tree with ``cd`` 5. Configure the project and build it .. code:: bash mkdir host/build cd host/build cmake -G "MSYS Makefiles" .. make .. _host_linux_on_linux: For Linux on Linux ~~~~~~~~~~~~~~~~~~ Make a ``host/build`` directory, then run ``cmake`` .. code:: bash mkdir host/build cd host/build cmake .. make .. _host_win_cross_on_linux: For Windows cross compiling on Linux ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Like :ref:`host_linux_on_linux`, but using the mingw64 toolchain to cross-compile a Windows ``.exe`` file. .. code:: bash mkdir host/build cd host/build cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw64.cmake .. make .. _host_build_installer: Building the Windows installer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. :ref:`Build ` the host on Linux. 2. Install ``nsis`` .. code:: bash apt-get install nsis 3. Use ``makensis`` to build the installer. .. code:: bash cd host/build/platform/Windows makensis installer.nsi .. _host_questions: This will build ``looking-glass-host-setup.exe`` under ``host/build/platform/Windows/looking-glass-host-setup.exe`` .. seealso:: :ref:`Installing the Host ` looking-glass-B6/doc/conf.py000066400000000000000000000054611434445012300161460ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Release import import sys, os sys.path.append(os.path.dirname(__file__)) from lgrelease import release # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- project = 'Looking Glass' copyright = '2022, Looking Glass team' author = 'Geoffrey McRae and the Looking Glass team' rst_prolog = """ .. |license| replace:: GPLv2 """ # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx_rtd_theme', ] try: from sphinxcontrib import spelling except ImportError: pass else: del spelling extensions += ['sphinxcontrib.spelling'] spelling_filters = [ 'lgspell.OptionFilter', 'lgspell.PackageFilter', 'lgspell.PathFilter', 'lgspell.CryptoAddressFilter', 'lgspell.VersionFilter' ] spelling_word_list_filename = [os.path.join(os.path.dirname(__file__), 'words.txt')] # Add any paths that contain templates here, relative to this directory. # templates_path = ['_templates'] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # Explicitly state master_doc instead of relying on default master_doc = 'index' # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' html_theme_options = { 'logo_only': True, 'style_nav_header_background': '#343131', } html_sidebars = { '**': [ 'about.html', 'navigation.html', 'relations.html', 'searchbox.html', ] } html_favicon = '../resources/icon.ico' html_logo = '../resources/icon-128x128.png' html_css_files = [ 'lg-custom.css', ] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = [ 'css/', ] looking-glass-B6/doc/credits.rst000066400000000000000000000005451434445012300170340ustar00rootroot00000000000000.. _looking_glass_team: Looking Glass team ################## A list of the wonderful people who make this possible. .. _lg_devs: Developers ------------------ * gnif (Geoffrey McRae) * quantum (Guanzhong Chen) * xyene (Tudor Brindus) * spencercw (Chris Spencer) .. _lg_documentation_guys: Documentation ------------- * JJRcop (Jonathan Rubenstein) looking-glass-B6/doc/css/000077500000000000000000000000001434445012300154315ustar00rootroot00000000000000looking-glass-B6/doc/css/lg-custom.css000066400000000000000000000012201434445012300200500ustar00rootroot00000000000000/* Center content in the RTD theme */ @media screen and (min-width: 1100px) { .wy-nav-content-wrap { margin-left: auto; } .wy-nav-content { margin: auto; } } @media screen and (min-width:769px) and (max-width:1420px) { .wy-nav-content-wrap { margin-left: 300px; } .wy-nav-content { margin: 0; } } /* Make look like a key */ kbd { background-color: #eee; border-radius: 3px; border: 1px solid #b4b4b4; box-shadow: 0 1px 1px rgba(0, 0, 0, .2), 0 2px 0 0 rgba(255, 255, 255, .7) inset; color: #333; display: inline-block; font-size: .85em; line-height: 1; padding: 2px 4px; white-space: nowrap; } looking-glass-B6/doc/faq.rst000066400000000000000000000221471434445012300161500ustar00rootroot00000000000000Frequently asked questions ########################## General ------- .. _how_does_looking_glass_work: How does Looking Glass work? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This YouTube video featured created by the author features a detailed explanation: https://www.youtube.com/watch?v=U44lihtNVVM .. _can_i_feed_the_vm_directly_into_obs: Can I feed the VM directly into OBS? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Looking Glass now features a functional :doc:`OBS plugin `, which acts as another Looking Glass client, but instead feeds the captured frames into OBS. .. _why_is_my_ups_so_low: Why is my UPS (Updates Per Second) so low? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are several reasons why this can happen, the most common are your capture resolution, or refresh rate. The windows capture methods currently struggle to capture high resolutions under certain circumstances. Some titles do some strange things at early initialization that cause capture performance issues. One such title is the Unigine Valley benchmark where the capture rate is limited to 1/2 the actual rate. For an unknown reason to both myself and the Unigine developers a simple task switch (alt+tab) in and out resolves the issue. This is not a Looking Glass bug. .. _is_my_gpu_supported: Is my GPU supported? ~~~~~~~~~~~~~~~~~~~~ Your guest GPU almost certainly supports DXGI. Use DxDiag to confirm that you have support for WDDM 1.2 or greater. The server-side (guest) probing error "Capture is not possible, unsupported device or driver" indicates NVidia duplication has failed, not that DXGI has failed. You can fix the error by specifying ``-c DXGI`` .. _why_do_i_need_spice_if_i_dont_want_a_spice_display_device: Why do I need Spice if I don't want a Spice display device? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You don't need Display Spice enabled. Looking Glass has a Spice client built in to provide some conveniences, but you can disable it with the "-s" argument. .. note:: Without Spice, Looking Glass cannot send mouse/keyboard input to the guest and clipboard synchronization is disabled. .. _where_is_the_host_application_for_linux: Where is the host application for Linux? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The "Windows host application" is actually the display server, which runs in the guest VM. The only thing that needs to run in your Linux host OS is the ``looking-glass-client`` application. You can :ref:`build ` a version of the host for Linux as well. .. _gnome_wayland_decorations: Why is there no title bar on GNOME? / Why can't I resize the window on GNOME? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This happens because GNOME on Wayland doesn't support the `standard protocol`_ for server-side decorations, and Looking Glass doesn't implement its own decorations. The easiest solution is to build Looking Glass with `libdecor`_ support. If your distribution lacks a ``libdecor`` package, you must build it from `source code `_. You can then build the the client with libdecor support by passing ``-DENABLE_LIBDECOR=ON`` to ``cmake``. An alternative solution is to hold down the Super key (Windows key on most keyboards), then right click Looking Glass. This should bring up a menu, which will allow you to move the window and resize it. .. warning:: Libdecor support is provided for the convenience of our Wayland users on GNOME, however it is not a priority feature and may break, please seek alternatives if you require stable operation. .. _standard protocol: https://wayland.app/protocols/xdg-decoration-unstable-v1 .. _libdecor: https://gitlab.gnome.org/jadahl/libdecor Mouse ----- .. _the_mouse_is_jumpy_slow_laggy_when_using_spice: The mouse is jumpy, slow, laggy when using SPICE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Please be sure to install the SPICE guest tools from https://www.spice-space.org/download.html#windows-binaries. .. _mouse_desync_when_entering: The mouse position is wrong when entering the window ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is due to windows mouse acceleration, it can be disabled by following one of these methods: - Disabling pointer precision (Control Panel > Mouse > Pointer Options > Uncheck Enhance pointer precision) - By changing the acceleration behavior with the following registry magic: http://donewmouseaccel.blogspot.com.au/2010/03/markc-windows-7-mouse-acceleration-fix.html (Contrary to the title this works just fine on Windows 10) .. _the_cursor_position_doesnt_update_until_i_click: The cursor position doesn't update until I click ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Make sure you have removed the Virtual Tablet Device from the Virtual Machine. Due to the design of Windows, absolute pointing devices break applications/games that require cursor capture, and as such Looking Glass does not support them. Audio ----- As of B6 Looking Glass supports audio input and output via SPICE. .. _faq_win: Windows ------- .. _nvfbc_nvidia_capture_api_doesnt_work: NvFBC (NVIDIA Capture API) doesn't work ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ NvFBC is only supported on professional-grade GPUs, and will not function on consumer-grade cards like those from the GeForce series. If you have a supported card, you can enable NVFBC by adding the following to the host ini file, found at ``%ProgramFiles%\Looking Glass (host)\looking-glass-host.ini`` (create one if it doesn't exist): .. code:: INI [app] capture=nvfbc .. _the_screen_stops_updating_when_left_idle_for_a_time: The screen stops updating when left idle for a time ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Windows is likely turning off the display to save power, you can prevent this by adjusting the ``Power Options`` in the control panel. .. _faq_host: Host ---- Where is the log? ~~~~~~~~~~~~~~~~~ The log file for the host application is located at:: %ProgramData%\Looking Glass (host)\looking-glass-host.txt You can also open the log file by right clicking on the Looking Glass system tray icon, then clicking *Open Log File*. This opens the log file in Notepad. The log file for the looking glass service is located at:: %ProgramData%\Looking Glass (host)\looking-glass-host-service.txt This is useful for troubleshooting errors related to the host application not starting. High priority capture using DXGI and Secure Desktop (UAC) capture support ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ By default Windows gives priority to the foreground application for any GPU work which causes issues with capture if the foreground application is consuming 100% of the available GPU resources. The looking glass host application is able to increase the kernel GPU thread to realtime priority which fixes this, but in order to do so it must run as the ``SYSTEM`` user account. To do this, Looking Glass needs to run as a service. This can be accomplished by either using the NSIS installer which will do this for you, or you can use the following command to Install the service manually: :: looking-glass-host.exe InstallService To remove the service use the following command: :: looking-glass-host.exe UninstallService This will also enable the host application to capture the secure desktop which includes things like the lock screen and UAC prompts. .. _faq_host_admin_privs: Why does the host require Administrator privileges? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is intentional for several reasons. 1. NvFBC requires a system wide hook to correctly obtain the cursor position as NVIDIA decided to not provide this as part of the cursor updates. 2. NvFBC requires administrator level access to enable the interface in the first place. 3. General capture performance is boosted by taking advantage of high priority scheduling with SYSTEM level privileges. NvFBC (NVIDIA Frame Buffer Capture) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Why can't I compile NvFBC support into the host? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You must download and install the NVidia Capture SDK. Please note that by doing so you will be agreeing to NVIDIA's SDK License agreement. .. _a_note_about_ivshmem_and_scream_audio: Why doesn't Looking Glass work with Scream over IVSHMEM? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. warning:: Using IVSHMEM with Scream may interfere with Looking Glass, as they may try to use the same device. Please do not use the IVSHMEM plugin for Scream. To fix this issue, use the default network transfer method. The IVSHMEM method induces additional latency that is built into its implementation. When using VirtIO for a network device the VM is already using a highly optimized memory copy anyway so there is no need to make another one. If you insist on using IVSHMEM for Scream—despite its inferiority to the default network implementation—the Windows Host Application can be told what device to use. Edit the file ``%ProgramFiles%\Looking Glass (host)\looking-glass-host.ini``, (create one if it doesn't exist) then, you can add the ``os:shmDevice`` option like so: .. code:: INI [os] shmDevice=1 looking-glass-B6/doc/images/000077500000000000000000000000001434445012300161065ustar00rootroot00000000000000looking-glass-B6/doc/images/Looking-Glass-OBS-Source-Add.png000066400000000000000000000410451434445012300236360ustar00rootroot00000000000000PNG  IHDR,Fh9 pHYs+ IDATx^w\aT* KbuT@@Ņ緸֊XԊ`EZu`8P@d  $<&y>w9^.y AV4rȵk_EEɓ'dݺux[nqI&h47mfllWvssڵ޽{'OQ  8PCC#33~555Gب2hл1ѣG]vӧ;88񤤤.]8;;3 addBtWRRpuujdzl&9i$"7m4k׮ܹE=233gff+++Xӧ߹sƏaX^^8p'O]0`i444-[{nj,N81o޼ÇWWW'$$ :]f͚p‚6 UUUfJyyy!!!"r.==}8uܹsx̙7oӧO@PPZuu  V3gg0%%%'Nhy 7o^hhH$JIIxjj+2l0|ChԀ|i9n[b~7j⫀RO>ХKC>}ZhGIIڵkJ J .B!AE($H  "$\BPHp .B!AE($H =񢢢 ̛7oĉ5 Pf͛7sU ÇWUUu Ƹq㼼f͚5f̘^zeddˆ'OTTTO?UVV8pڵkݍSιh3IF}Y *++󫫫-?Ā|X,%y\gg眜WW7nb }},UUU333>One``rϟD#GڵTs1000aBll,%~uC=<<֯_/ erI.F6lXhhŋMLL: JBɭtuuW^}رǏlllH߿ Ülmmkjj,_~9 Ǝkii?WZW^^N?(GGG +--x.\ ^~~j)_Z9 /^uuuŃ{NKK# k׮={lkA׏|{۶mF8p0<<ߟ2`cc3s;vxyy,]Ꚛ\r…4mĉ$886l/(Ç_~bccX,S 444(//qD"ϟ?744o߾}6a]hy?) :4>>ðK.jkkEgϞD"ÇS :VrN ~zuu5Omʉ'***VZfeX,Ư|sssbFL=f"HMM1U,3 OÇן>}뫩YYY~ s-[l͚5ےL3 NOO׷D"!p\| NIA \rOv pWW :P_ݼy:u255}AAA5k,--l6ñ4?w\QQ%J]\\t3.u]MM ,,,\.WSS2f? \]]{Aєj B{Њ_#.FJt:%޽5119sH$p~7 55511188ݻx_~|D'RI`'HӴ{4Zeꅲ_+(i _n1f&255LM}=卂׹u.K]zmP}" P.ޛiZjfm;ks_j4jgڜĂ~ݽN;AQMW@{QZ:te(8,w$iX/~>WDXY?GrP&n}}&3tof LXc3Zi܃_&`왕$ :ۨa][Q Y~ew>ڻHOu WMڊVvEgՙjڌ0z֋g'7i`qT FS3qP7VބF"*:c! 뼈Ģ+4z7uM5M5u(ה2 ̪?S_߫6c (ե"Vצr:>(iv& %wnT^nLY |rïF򍒦>R DFFfxzpf7@I]PkYտyV0̢1d{1)i 42&,ͅ ך,f/m|sJ6ݣэ,샓 O.n*!`k@cU 9U%Ҭ̟Ri;R%ND5C 4_*\&(xagd$?%Q)`R\ BByLuo]o>D'芫!~g&x'`s4iLkAUOuqot2XF`> 3BTQ'a mV,֥h?yQNC0I% ao[.)I6^Mffw e޲кn$Mh(ϭLH?zmJok$bĵe:ݙj:aGg &iOTQd5 F]O,~QPgvMAU6K &cRI}3i72 m&&?A[##Wk6f'sw[ŗO5xðݻw\StWE^T:u:zL.3\^t6PE0_;5AFs#CRQ ]k2%q[=#l-3Rۘ_Do_ҘZ];,#>]g,oXT4j;'2L5ns\V|q3T7~E'T?AfLA6kLj0L*6>;~x"d2׮][M0{dfۘ_$έ %Uh=b~2 6ӧaV_ܔ64KO5OE4 W 6m2S)^3Iğ]A闻='?[,?. >/sj|TfoٲvJi f3Wӈha]8jaRW^F$ܖZnݔY41黛y TVV߿Ub$\y x%K\ðODC|qgaD"`;x<ޖ-[GD^.A~'Zb۶m":8 ᶄӧt:`F=~… D̙3ǎ#W!"y {T* \_zxxEO:pqE2 :lz…lhhHKK_ Cl,i;^RLSS󗝿Xܻ{%r?Bd0 0L*jhKht:3޻`nn~;ߚ~gϞ6C|u:Dz^zY&77:ŋǏ?0^EGGɞBݽK.|YhOC \\\=JD OI'%us8+W߾}f3L:pݻ7f̘Ǐ766vjj*QG;w.//LKK}}}///ccc@pu}͛ץK&J~ҽ{}Ԫ/\PPP@m־hn?"* H  "$\BPHp .B!AE($H  "$\BPHp .B!3 L ɴKڿpnM]]i49{9:;;q8찰0uh9nsv24hP^^_``=nacc3s;vxyy,]9i•i'ӹs߸qC"ϟ_Cbv%SSSmmmfmKO@T*mLEEQG,3 H$RSS# ڔ!\ݻw[#x@ rJtt4UhL;suuѣFSVVo5mIqL? xð{?7wz}qCCT*} >9UUUb8::i-aXXXXMMMCCC chhHN4 ߼yL4dCI&$ѭ[7d #Lc젠 rܶ)O>$W^^iӦFWWW X,Cv‘H$Liaql6y۷'&&5n}}@ W q^xhoo?`+WRJl]h~J++ϴɆLc.]8;;3 addBm)YҒfs8 nllTQQ4=׈#Fp\:>cƌFPؽ{whEEEݻwWSS 沲sQQzyy9sϏ`<Z IDAT&66vUUU.]rG\UUUccc2Z,ɆPMUUUppY<<:!!aСDwnذڵ DO< 7nLMMMLL .--{.і9--ի:uڵkr劣[rrcJO~-j-9h=ߎƚb999-+KK˧Ow;LAٷo߯ԁ-ؽ{w``W666^vŋ[ͧ>JҜ윜TjllJB/ڔ(jA[BfϞo}j2> W;%&&&&&mK0 ۴i5jZ+\WߟBhmЀ1nReTYFݍ '' G999&rrrG(fggcaXvvv 6#F\W^!-\@E* r.B!AE($H  "W{PxLЈPH>bݝ:u3ZD,S srrիWՋ|dTTT܆ f"##?r7o޼yf]Ai]bD#Gmۆ*AƍT&&&WO8q%|s|w}gmm}|NMSVV *++1bʕ+}||իW̙/P%OsnjVF|¦[3]*e0}={37njǏ'Y`7n :0exk׮ J˗/r ˗An444O2666/^p!jkk]TTdmmmjjO82EPPP/߾}{ܹZZZeeeӦMmZ"0ϣ,j9pŁSL9qđ#G|||VZ5a>5 ̮^zmrsѳgϦ+L:588I-[뫣y7o<~XnP;HOOǽo߾ލ;v˖-[lي+dn)ٳ w*ɱ9Qg2]j={b ggTTTg >IIIѣ%%%"SNfff۷>߿?4M0͛...ү_X()))//ð5p\0~ק2R.\رcŋ[tcX" ]1_ WL'OhJllɓ-,,޼yx٩kii5- \.wݺugϞ%թj/T2]jh4ǭ[bcc zeeeAhbBB*S)s7dR]]4x` 8"NuKOOon[dtuu׬Y_#7'|}:h ӥcG^x1j9h^ZZ*[oΙ3bh4 FsܸqcРAwtpp044LIIin[6lpBbb*I3|e>tmR&MڰaD"3f?~… >>>NNNǎ##?nkk[PP@BBB̙4ȑ#eeeMw={u &&&!!!emMƏG.\fMLLo<9ֻWoj󨨨 ߴ"Kk#̙b D[@s!7̋~'2fkk۷S *|>==z4͘0aP()444FN-W.U={?_~cK {mJ,HK7n֭OiO0sHHH2d!\%%`)p8[/ZZZzzz#555?2d7|Pda&&&ׯWVV萴Ν;O>B9ȉ'ܹ)..[{xx:u W!C>|( \\\@__X _ .`dN>>7n4hQlٲr__ߍ7N8wfeeaoo'OY2޻wEIIZYY͙3-222"5ҥKرDCC#00ŋ'O\b? C ӻwxj'6X,{.[޽)#QttX,"|32dȹs*++%M('%%رc5k[`R `VSS3X\XXa}ii)mmm]?Uooo333##_nѣGܹC\n]]]]]J| 3!`V^^?LٳgϞ=x?æMtttiCii;^%%%}&ZImhhHQVVƿ!rKncccbbQ -<}ʕ+ի٭_RZYYfUTTD"Z4g0#hKKKO:uV:"x<K$:߳(%ɟ"􊊊l^$`̙3t:~^ھ};eH?>éKOO߽{7GFFkjj޽RSSKKK)Rkiio۶۷fJyyy!!!IC>u: ⿥ v.B!AE($H  "$\BPHp .B!AE($H  "$\Bq9=A;t_WiI_ fWZZt>Lt]͂ ΝRC]]]( Ƹq㼼f͚5f̘^zedd|LLLWc|)zI&M<88k:99X#t3{NKK9<333((ڵk$J5jƍ}||rrr/_N###sss#"" DGGgԩWC{urV#FFFw!7 /z*L<, `SL!7550 ڂ-PN&MtA5/\QWWoUUU#Gį_^UU% m!=ZRR">ae~޻wv]OOSRR)iΝ#""jkk ''֊Dׯ׏_l ƢE;ւ`OCN6_{9;1~^z1LAK+0HTWWr:6C'C|ԩ4555C 9s#""m\P($/C)l6L[ɓ'd!۲y&رcm۶*;wEY,_L 5d>ť+++qqqg~葪*bX, @ptRgĪL[1c444 0TUUi4%tTw6yAAA5k,--l6ñMM7oTUUhC13f#huuuFh=z8p 9A i =<<\]]áϛb(>EEE3gTUUUVVҥ M6 O8N׮])M g@K,V^z7o&&&_Euw6JSNjhhx֭[/_RSSs5ݻ6lrv .899;vGYpa}}}MMM\\E8UUU4QQQ1 ~?⾩+JƏ@gΝMcbbN> AAAjjj.\((( gee1ݻ@Mmgzeeef̘1c @N6}~έ폙3gr8_Z1tTAiOٳfff&&&Ԃ A(:> $j!PPHp .B!AE($H  "$\BPHp .B!AE($H駟馭)/K?0}tbuÆ 6m"V[|tt'[|&$;v;vСCׯڼ5t֍l\]]voOXppU611144$&Z[[@JJ eׁFX)""wժU111f{ŅpfΜyтk3g|1~+󸩩sgEիW7飸ž}7nX,? w6ϟ??<,Z0SSӵkz{{o߾wĈxN62۷??~NNNѣGMfii) O8Ob+**655'N'7o޼)//{{Ǐ'''wﮤ;v3gϟ믿Λ7M6m+.]ڭ[7UUULcT| ֶbmmN++϶7666İ*JCCCLFGod#[֭[]oۛ|?vǏ{zzFGG/[ iܴ/\HKKa0{NIIIIIbXbR?99˗xZ}}}0`sjjjϜ9CTilfnn`0,--KJJRRRʊrgCCC7o(++xty@111555 O<[RRR^^a<˳gO --mԩfffUUUe~z?D8b`&1. bԔilR[[7?zٳgs' qf3eʔ(hFW K%DB| ?@D>(#ꓜlcccnn.̐b{SYYI(f6_m,-->|ӿ7666M qۛ۷\emo|a+#uFKK=,,ѣÇ766 Us O״!ڜA;`X <|pl6dN47glXXX(J?~}Ms$$$B>5mo444vH~BW^^M-hBH_***ÃZ tTiP$\}ބRC̀RBPHp .B!AE($HD!o5 㠐@tdR!:&(E($H  "$\BPHp .B!AE($H ( sssruuuGm BڵkPPн{o[cffL.m{|?ߺL<==>D|/##!,,ێ&L)++NJJUV=}[yyy#ښr8ssk׮Q NLL3g΁<==#""-Zdd*FĈ w$ :tСC&L#/^zH$C 0,===%%eȐ!DW2iDhkڿp R|F6Ҧεd#ښ/ܪb଩īG}a0x<ܔlb>2mM.DFF3fʔ) FjllLѬgϞMQMd#Ÿ6b b}m`ZZږ-[FbB7o^zEﯩYRR{n.ӧQ#555kjj6@cXlp8DyS37n1kJJJ3g[xN&4{w***sB111gϜmqndnU͛7qp8bfXp8߲j?ȑ#q>}ZhllܿVVL<ř\A0 ;uݻ0p@\nj;~nU,:իצ͛233L]]|>IDATdƌ/^=JQ-NߒKLLLƌ{+ZERidD$??$$$DGG˳j s`C=`Ȑ!׮^Gqig϶6}{Mrɥ Ë1K.ɹj sKƷo] k1\.w!yyy'@|\<5$ PxhJW._F$O?Al܆3+peXL~PܿD"xRRD"0ѣGxPՏPSSh"j6(ƈ@P@E($H  "fs1|ۭ8|,_F( ͉  pesL>A$4/O/j!IR|Nw]UUZͧ[0!_6OߎM4QIENDB`looking-glass-B6/doc/images/Looking-Glass-OBS-config.png000066400000000000000000002254411434445012300231610ustar00rootroot00000000000000PNG  IHDR5 pHYs+ IDATx^}y&EusAT1QD\"Kbn\Ѩ} *q!F%hl7n 2l30v=SU]U];t:uԩS 5`r€ X) !x<`[ C0`{ 0``0`V 0`a 0 jBl5r‚p9$_=Q9iC,Sf 0 jBl5ȣZ%0`=f հ4;" ;x? l4JT !i8{He1hҒ@K(2d0,-MQ l. ES#ÔypJV1LF]<35hr_\bb[m,xB,<)ZԤgn,Dے'%'k.KN cj777h]n @TllᨈVh@H.,Tg{/DP**,1"2 @Kں7m-Yjf`r!%fzH5mlD8| 'F7BO"%01 k6iGFq"c&DO[C :dJdֶΚ$h$hՊpy]4D$f-8 7_EO[TBJ p!n\j3t1DDwvt)pm:1A MhhBKhD IW$i{Dy'v~TP#WTȂb0(ޭՊ u+Ck5Z夥X"RkmmimEcY31PG#`EG Ӧ< wb -~  M((bƴԞ$@ʌD0LYED:Qo,"Z;+{,i6X,+abrhTEA2pJz˜6Sq%! YPJ5-j^*͉CB_JFff9uqNm+)"""21"ں7oe=TzQbbp=/Y l3,/ _ XY m:YYVѐ+so#|,2{S;ZyݡN3G Qe1ƈZYZmc ]&V+ qW]a Z{c=-9q eWx^Ylm%،su=1F KK @-@^תؔEt%AObbKY趉#\ _?zMh łܾ F1TT2Q*`l\!(57ߦ6֎mypJ虴ҡϊhKPWve5Rg u!ӧJnLeЦX/+֨4]kD!B6ČJXkóL#aPVtEAjuU-1(PS+Jgx1ǺㆢER0)b yx NV6UFmhׂhT5Cg-oѤU\vQafXa 1,#&8aKROD}I@̬pDZ$U~!ïDo/ȻBRDRQLY~/PӠ!°|!x:&T"-2ӗRYN >X^LP8B̢ћ$ogXE*3U ,-hxέ'%E5ಢ1HzhjOv(h2 9kRtpUBj\d\RxXr rM61D܎EtţEdE(?zQ^؉* wYWxY@Q^z|6&[\Z> _]υlbĈVX[% 4|&:|UU;l v]z4<[(9GaZ H?MlKlҬ-SΙMi%)@:Ջ+g +3j`<[koܩ3FZvL6Ef*..eGw^u4nD vd.g^)L?Xg6{a7rf ]Hc:Y׳i$6*`Z+rLxK 54Sbm}!8nb bj #٭`148A6R0B9I0,bHUm2$m<5e;0"'Oұ0 +' ]Jˌe P p Fr\DRDhs%O7njvܮpCY )ERW^шB"b*cfvE]עXGl_<@T2I I0b[*GH)i9 IcEqAfǯ uPPTffCSQHN-DkҖ-B[.T,kŠl?Io8v(b-c*!a%*;B t; 5`ρMߣK'P%F'|eAW.'J좯pҷN֘dwp- / icڊ@1jI}R\\|l#lvQR$"gl"q1Im;,& (-yCpOm79dL9%=K"ֹS2TmtP;-Z7h>kyEm#0ژ$zY(Q[*@-]k'j;~651O8K."]9/=H(c@IP2fv%[D1 N.g0,ѴZ+`Y\Li DvV!dĥI=e_#i5oSm+M zE2/(X$Z [[[c̪Ug*"fvA,k[o"RSʓi؁l,,Pݾx,}h"07\\b1Uq8F[ *#HrX'Skl!tsֶ1>Qjd!X'ȐЊ+SD 30? /=C-%F >T/g>uBp1 D G:TL^PYo!f~ Ӡ8 !4/:-3d;ammLUj4[-II+f'@6nR|;+K¸, QB_P5Ͳ37 f˙VH z׋n8nbBQ&F,FY +Z˱5!"ƘY:fm%BD:Q%_`*GBLrv4٢MLkŘc/FGVۤHj, /-*.>5i&W0`&2G6:ҦAxl 34~A -"^Bd ]6O |;}2jF-,da7b9) Bg񯨰3 T$w1_'2b$xXdw8 1\!8 /6 R)Q.Ro u_PuE,5}AI1JQâVT#756װm*S&AvZ\o [׷%żD@\kġ-ZDHh_R$:3qX˓S<}je%3$LV9%>~v cw^q+mtg2.jPk139+㱥>GhmM1ҎcD]UZ9w9Pl5YUNgSiAb4%?ASQs{d+ENliA6<  $mPkb6G'@.-J3 R1¹ QumDS,uQЉUN]TH,,+-3+e#"Bo窛SP0&Ĉ1f4;IKZ ɚt> ~P1&j;"7QY1bNw-4 UPW.@w3;!=s.z%) j$eI:JxV*4`v#L1 Ufw#u+:P.M:$"q1Mv|RELH㦍mvp;.D>acfT3vnl5u!v^&"bֺofI9OΤc.=_g@!^LOITLӦru].d) JID eu tfARPhr~{Mm(+6k| *-CVhZ HvZMdžxJc6 '5B#4Fu͹1hAcXM@:!枆ĖHNB%ҫiwҪ [6' Ѿ..o$0%@Uv@@NEoO 2R$t%"⇵No qIj^E_ArO8&@Xlcy ^V` Za]J5ZkcKcY[1b@ka@4I_Qݑ| B0 ҽH[Pœ ]Q֚W-a#p8OGFM%S eM@DĈHںu ?IIEK^Ըr}&mC[|!ʂVZHNy{ eR26`8n*=FA~30=l<::^!dˆQA8c8A[]r7d{Gڊꪵo#TfTfg@[㺮a-DXba1oZPdɥ~{X#i z(2ȧz"l"/$RP)(%GbaP6e" +PxQ.eY\ 9;Cc"7| RVeLI2ESFKrAEb !{Y۝pziPRbc*32R Icmmi@ nǺZ<*Yp*@WR44z"JL zRImJiSej,*9'84+  (9{t# eP .u2*;FQr?Mefԭe@"Ż ֭,z?U`e . FRUb@kckn ÔPrD`EOaPLVnbi ˒(D&dE1F+W6#QDwb!XZioJPtCCK?Igyᝧ1Hbh4v7} nA"Ibn"Rff[ۺzbОNuSһRћK)bbMz'aEBo#) wPoK5 [[ @N+ B1l_W"-los Hu0)Kv@Q.LZBa1>4)SX==9VVw}Z[Xq0Ѭ뺮iɱ՛b,A݅fD;QⅠ@~FNy2Z˘_gѝ(aFjDl ŅՉsEs4NDi28k%'?l.X75hU,EG|'5q#/Iis~<kckmN%ZDFd-tf颷05<8=k %;ч:*("CF35HCB[n}jRo( Ა<})LCدR"qڣNms r>nme}B̘*wѣ@O\ja2n=GT;);S`rƥe&"I_P-Z֙Cpg.6ʫfd(Y`l"DѶgpM/"Q RiEF"E@\7,SYCwm@H]kB*#ѪQ=+WgۨK KRLdPLɦhE׉u:E!8s҇nes%Rm5wPd25I?! ҳ^4S)1jYun-)sXN~?r"I%>o% ɀBZ?ԫ49BzRcԆ(A@LvbsKmM؊EjTju=c+vc@ ݊AZUgn~ ZmS'"S-(DFڅQť -ًW)$KHjDe 2ǭX. pQ5wO $4df)$5%M̓1Wg\zDTvfxK&142 I g㴦5mS""šҪ1̨Zk뺶%(5048r ln2>Dǥ| SL0W eSsmX:/UGIO\\ J\7ތNmۚ6Dm`*%W ]zF׾I!Ӿ9%şgI('ǹ@Gўe*x e$},XMUȻ(K$VΚ9ִb4ݲTn })(m9-6I4JW-CA݄2PZc*13Sښ֒㚤,k .#{rZJ#'ޕKd,6nwf? n6"yhw~eϡ:#乐z#bt@2fs!PQb4] :Jʠg$$0T [:$DZyh\Dmq s"+(k'И+[y4Y܅,Mexb-}E`RU">Uvb ,0 -rMZ8Ѽ$'#rsrnDD3 6 Rqu_7ܪD m.u OXz.q@R$W+6MȻX4+HC4N&'F%E" (ʭ2EB#q S13;Yk9m]շ7IU̥XLޕ" @ZAҌFMb'҅͂:SEzR3(rm55Ri*J/}z[.Gs;o8 2&#S^BYড়}KZ0ѴA9X@-9q [S6t,V-혀1b cj^cKNn^ne4[#z +{@hXӤ gMaܖÆ'íOrmq @='i6,^?WAݛM@CNOu݇^8OC|.D%cY~@DŽNm3gXr 1Ɲ+`1Q~fy>".>Tƌf*#;׵5Ɲ5JuKW -"$KrD\3DUpB$@R=}D{0+AO1zѰIͲ.rA ֚Ә=s,DZKҊ7xGzU?Z(Wmr&Ps坯 !=T9v\sN@jFf4Z v5fJO IRX;ˣ//dFv| SPS]M8aEk 9fsuMDAD<+EsdYs"ރ<c(tht͂ ^2/YpWQQug޼;d `|sI"FzLcV^mZkQנ"t&8~ V1&\E6i\ŋNWz D[3QBd%r\U>k{eT(t ֌uǍ\BC3ēe=>$ұ)kM6'z[åWAm(wS0Z4UiSP~;.VnrsPL!@0s[gizkNC\CJ&TQi?TR"b*SffEH5-kuBPlN!MQeu r][I dp bXZ&#)JYwtQUDBij$ rШۨtlUS4# ac-@K|Y?("}BRc|9kKkD-N`=<4(\;ԘNbt Fvɘm#5G` _Ea!{@:lǨژZd]׬kOY`4S)_rꥈ,@ +-+Nl4NmDt+%1,q.(X!% +!xԯ!(_5 |FT %VN [\Z$F5_+-K"@K0KOJt$ExP(Knb/^MðeM"2gaĈ@fgF"ME&pYHTIOXd]^gt@k%(UQH"K͜!HMx?-_=uQ$XԂ%^Dx < az@<٪2\%Ƅ3)~N MFIك&ҼZD#l0slϣM;zңe.vVⶵ0FUgiimͱR'7 j5nWڣ[,K",pg/D8P!I>MpmL!>\}vq'c&Y/^J=Wa&;Q &~JfEYKjV083FqP$*-P4E#!%M!;\@93U ŵ) No7c3H]T`IК-(1ՑiOlb@m32kZZA/MRJ>.g)t,6'hU)":ڊlXW4 `mݏgoو ضd!xj#o37<Z r Ʊ4!0y¡gn㚏!E\ lׂnٸU?r 0`҄~-AcO[2툿J$$G1>`430?O⯋>6 !443o"k}s{&D7^]ﯻ<À, V!h th=⯆8;+߰זz>xͯ9̋.kַw}GK_lڴ9<`K%! zOZMO_W=&nn_imvmo}[_}HK9>'xLJ>r-0`%FaPuOC>_Cuv#$`Zu [jճ~̍;cg믿> k_|1ܸg=իV 7RVfLnn @*g>hn>_X@N-&qc۾/XnݡzI'֭{xꩧ'OO~Sy€X,~ D0"F<=_g]tW#"ߔ~{]8_n:Ёu#^-wtQO<''S[ߚ=<ԏ]Uk_ugN?}W;u%G~;!z[6RZKg2U;ii/{ 9)Ѣf  `DV55ӄkgv/=g}o)n{ێF7K.g??_غ7G=裞GD;} 8=fׯs}?1'߇_>_~߰͛rpءkOcL}Oȣ[n-'Ϟ3nz.oܸqz'<1~!{v<wUgοB\\Y]zvi7}k#SE$S`0.&A6]|^qq _}R@4/}w[nw_|5k֜r)/>=/:}!4_;M64U.~neLζ S7nٲ?;Ջtr͞JXoHHN;~Io馛_v33w]]}MS\u3:Cn8FKKS`ݽ@㩣 JcC=Ǡ@/ P"VՖ_N_>я~+^/W;ӟwyp'7+uvPU'?w}G?%ħ>v'cʀ aG~+裾uyz;ϟf͞\{=_p!q7ta}ȦM3g=橇Ъy<Ûlko} |^u}E=Ӟ8#غwm=9|{m޲;s? Go;P=?w?}?};߽_'y}ܺuW" t}3|;mԱPD^1õFZx.B8qaT]%7\=d!⯆f! Ѕ2Le~߇Ӌ.袋.g}^Wy򕯼oꪫcw=?].uwWbvv&l;"Bǝ=A~NFO{Ʊ7o?_go//;W/'p?'O;\yUO9<8yoTkԣzk^w 7o>_{V5!ǿ Oy5kss>os?Wv>>zꪪSܻC[c>}?8c '}i||7|k}o|ͿY|L=V|bۯ[vn8y? .?oz׷x~пWJD֭{կ]}߻ ^>/we[{.Nlje6k y K%rV˽6Ͻ8͆~%\uU'pW\qWp Xk/38cTy_p[^~u'>O~;o_8=c/|s>wgE-y޳q͛7yء_~_K.^taUuW|aϜqM7ms:Z{郿U7mw{>^W]uչ>A W^ry/"E,S[G?əg[ssqE?^_::㳟{Zkԣxۿ-oύL~|Oo޴iM>; >辿oo_湙 wuu_>g|g|ozz_W5_7vM=[w~#7pUWמ6ژYp =  9X]b- R|999=Ȓ.<]2X[*O|O 6mW}B֍{r׬]kY7ؼ뮻${/m 377~soc w=/2̌F߹~|q/ߟ\;THqEk\s͵EҮu]M']vzpu.x4\smvu|3U7޴qwS~.軇Ѓ_V;u]Ke=|~S>tuz#K!@| "2S`hR%x[jU6pþ{饗 @F 7%Sqķxͫ/.W` k׬ {vUn:>{}A_m'Tp6bXru]}3lu}}8з}Q7GG:U) ׮98P^kEy /y+s;~_:Roذ!mns&%D?rn=%/// O?MSoڸ>g3^}K_W 779ЇBIEPj.DR7_7œh_!qk;W~1y1oN1yCο;mi~+_~N| o_`9vZ6K_Gv'?~_וm͞{>cp?aG|[|iO=zmosE37޸qJlƥg?=s[ "{v58nFXTѶ͜՟o{=CŘq}]t7nO8mG= pϸhE\^pw]Anw=!O}f}~ͫw߻ffV^nervuכ5vm7htc6orWZᏜz>3jn^Jԃv{-,dRMYs}.J1A;߹C_z~{ߋ{ǣzfffnn.N:x;gtɬe/~o{y_?‹.=jv@gK.O|n֝(~xYxM>3s~wqO}/)ˋg/}Î8'! ozs}~Tꫯ~{9u~ҥu IDAT;Nrq]xӏyӏ>~i?ys 7x9_|7Pj^~=/lΌO_tzq/W /ηT]~s=vu\s /C}C^w~xE.JO?.wZwd=;׾?1ׯ_S>|啿K*k}ag8!J .G''jdn+Kc7=Nxӛ>OwW>Ol׽׭#xgBg_5`J<?[|;yaSS.$Z*Mx $ %  tQ>شiNs}sO< /1/=o Xn,d?wL}@6H1%R<6m~ٳwuTn<>׭>\4`| qeX*uS`Gae~~ޑaݺ:gF7mxW} `!َVFaf+S"%!2##h!tKǪ<;w>0["nE<fxS0+N΀lXܬ&)Bz.|pOljvJ 8"ݹI%0`6%)c*D=Sn~c{πlOXZp/H0["FQ18(<` vMׅ&'` *40lP<` eӱ Agh r7_x:oiچF$q !m5ћHT`Q5!h)v{ݛ;݉ )1B$Axӵ`zt1dӁ`Yuj? I [i߆r,,)=)ܰ4Z@#/Y?wѸ1ڲěՃAUU-]f8! >oߨ#'2Bp_VF@f.uެ G O92"Ч - X1ƘQ[[Π6kBVeϸBm L^GmedU . F1G\R) ' dѥX$1GV;b1R@BzBLg@iTH ?qb -Q`Ic\%؇HC.غ- [weI;F]B7E AL3E,QJdT!~MD4c|ަ;BbϰIɕH#b3`n!##1emHg0iΨ捛*`\eI e 'Oϣ;Vl-[>k)6Z_!XJ3A_$jMwR[ B_`FUM+ x<8Nc@mb-NX*鲅i@}H՚{HXgbN]у4A[ W $bk#"#e]thІ h2h陧ɵQmV #V$ s&բ;JZkt t+"kX?0VX#Oلy5z9ac{~l@3o \'8PfNq |Hb63gOW ]P̌Ȭv=q@U/=5@y!|eIph:tF~7T\4IgEኞ"htn4g&GDƄDt /YTк٨`PPMA'Z1FGQDTnz]j+U#ԴaT} ;#UځN/[zbFڻTZ+@e`L=1ajnnND!-wDLI z=bz@@E"*Eޏ12Gǔwv݊sX#"2 d>YZdnҥǼZpb1V64 8Am]JctYjl|О_]Sj ÃXO *@܁Z`xd2^>$t8GKK*>vC3skvԙޤ""#,nuP,`bEY'޷;gK_LDgK #8dս m up %jht]!p cIe5G袳[PJ/as^. &@d|]-ڡUJ{bVD*tcmZwCc4BpD#IO1K!P ]wnSpS jiI<`>79# 0+݌Cܥ@DKs]%knȌD?bn39k-DKZ!oSwnĬ3 񁡑r:S$pYb4 h3+7H4gƭ,$z6)ٸeRaz*i. Rkg]<@lHLк+T$r1Ԭ,5jgtZ_t|>y([߫ ^xiB4@zO]ӊ&9%oMwiM]5tjX1Ռ*C0ωiٍ8hRrUwO9U$d>$ٖS1=.% 3MM.,͵H,>C4 - } ^|l=XQMO4Ϳ}1٦tHKiٝCH2 E;xS|>WW_(K\!!{Gho0ayB#E/m:KNO_O)x[* y)7OP?plK@+7)VMr=}a&ҔO245Ng/H ˅fv\~:4`.>MP#-G{1\y;>b=L6%b+Cef 6ȸ9G];@<@甒! @ӠK۵fnKJj`S"д7\(-.y_ri'v{Z) SbS:6HWƊ0ԺܞS/dsXz(a5c:`$#01TĴZېEC%}Ț\9DO_&Z8("r4MǻZmx"5%Uܑqf8C*Īytȍ-[V$Ia6t:ysZ%H,i'; Տ4T|3o疨Flr 4%)( #?*uۻ I[]Hz"Rrw"T,0R~RJy\ЋcqM XNCA}%(ï[oUULj}lͫMMiP3Ԙ-s:46p'A ySIH)]{uNO_H)D-dTv]DsD rF*`F1i[!)8Ӊ<{,ה%W6nGUud. a?r.uW}O3rx;z tB2ԋ`l4q2lе" B&Np^-VKP"(-1ۨf9E~^L)4=5 .Bܶs,S:- c{u{Y ϒ(kyR@@g(w҇apwNj[&;48NNF~hsG+X<,ѽtd +lĺcR//3Pc<1M|siϏr$ɥ:r} ^fΩາj;ث!c5 ZR9rЄU!3Μ)>/Fr 5,)//@B{fR, \I炫zɕz:X5;TN$H (E1`LjkxE!o6p{T M,srSY|/.3q4\f]AsКvW] y JɲQ.'H25 Ֆߑ8GI>J)/#j߀g<&UF)n/,ze[mkA(}Mm±< iYu^h4NDJY<$/:(V()Xe^IkZw3j.rոN\0sxH$nSn]hH$NQm}`(NB :- Jq"}>rx.H` j<.y@24 $P. cEp,lb1JIXx tJ/OJ"@5֮H`ݽ#B5x@cU՘nbd錨W~$ Ҝv} r3F˗$,7攃m+R\35UƦKu4G9=.~-@p})XQм5j4lf@EA\o[e`"˥daSȿCbovdxdyDbXeNEt6=@+ͳg莦m[܍/|wL= `K]]+( KL:B~)__8}L+zp$yO4,4+#P$D,=P9I3$Ay\) !Ž1+F^Eb"5=̴*}gGV3} j`U d{AfBض1FĚ2w&Nt>H)<Õ$ <En`i.3[_Ң2R^$)!Pa5/BMGӓ Bw瀲%)Q@h9p`(dB_tv_vW6 unj ;;lhtkV*D FY\-r:bW$Ot~_^,$Nݎ]EH!$IˆA24S @dE,&jEd^ S(%~rؾ<$)i1+*-0R^ůZj*bFv+^y4eHx|l.'$7k%Bo9'*y*?+ZV;fK!VF>e %xsXMP}$IwW֐o},S q^g D~8D4p3Mђ_M !M!MQ`km1^^A'B93~ Ђ?7jUittNрG6 IDAT4;勤y愔&EcYպ<٥xM>7_ ZJ q pyʞ**Ț 6bWH0<ڈ{/UFa.&pʫ@m ܑQU&nΨ7 \rwY&{z:9uꒉ;#UvjGŅ&$BTYCns?HQ߲۬C#.ZYȆ™FG]h󅏞/N52GI,A3\X*^ ## `^>M5KI#/R]{gVT+ڭ4]nRvWQd@, 7݈ш،L.e@3˗=}WH ؆ŭ:XiyY,э;c\WZsa.kѶʥMl)e/ց.YVB@(+aO^R˕$34)=KsI4Mr!S${ & HR#Uݒn:scP$4OjIpm(<ɓϳD if8Afeq'c@:ʜkpgL#7L['C}ۄJ  s a@(-!}wߙ<Mx,]:+]a´GJA4;;p]ˆu5yuquw Wj]JM^/^+_|7"8м)9 r{+)˗"w3Q. hih(|!-=^ 0Z>Zq˃|2l1k~$ @}U20@܃tYta#״ay918=Qn^&bH.@iAZb3Y}~Jb>Lt p1آV̴nɮޝMTe' 6WU<8cT@ټø"ia,e4 ;i̐5&S1Q2.I'#㾈&bwH$9]_K ݆j?){#ن" $@''8N4M_h)%H3f!y;3J -}@sk{DLIN)yhw7,ȧiJ;q1FY\g$Ba5ƿYXi8Dvj m&"FAvRBV=V,C`Tn֌m( Pe]mN_`ج;[(rߩ2%H'1Ng>}< ҋ8K&1ݎ Uk\ԋ$Ą$l7}B|gH7! E^%p(w]5=K bk-!*_xNZT;iq BkӔ`'`y  [ކb0/ԝ(T渁נ)tx/]95'N_O/I ;/~aJdvWW3s[誅w x1/8c@5wVX*}FLr,\IRJ_|Yr$ox;=P5D7U=Q'<乁P`}|;Ɓ&jK侬;~2Yp隚7kF{0pAs󓄔.iNۀeY6FºQe3t:Nt Vfk+ i [/dPMcP\I:A[O$7(K Ir4&tJqMx?z$I5 TO'2Ya4}.o QE^j:y>m"HEJiMM#=^ukN \(!圳"Ѿ|֞g!)-!D(ѐbDֆ%8վJ~mࢎZN7O^WI򿭎cI6w+,^84X!c=arX|n^ BriC5 )/wV^ ,A7C'DKؒvhv8n,GX|r-7AA!qpɑ,Kp0z#j78b꿊zNTnVp3Anviu$BOD]$AQgc8z_u\ZqloP /aMcIOnP ^yeQ@4(r3@a.]ߺ!1}X56=^c:<=rI0ϨQ͉D Ty3V*Z\s\"= s*ĶM> Z<"F@2jEٟKdePČ8bx$ɐ \29j:I0'nq}^k)+;n$@1p#áEt:y"M !hՙ?96. i?[W+DP;|jBb\!l+J4 #]pጀB|?>$qgi aV.>w]+:3W R5 t<&=e=j@ ⤮Qdx? +ZRAATȶJ du*+s8Hs|GvZR.?A*11-ژqOM8sHH0dBD I%i4L@RP6)C6j8`/&A&!1 2ff]>q$FqX<)ǽ< Y!R?CnGsXxKƏEKT?C 4;t @֫)/KG+8 `2F/y6dry~N?%#,ΆdV쨍^)Q.T$"#bA7eCLѹ3\㱉-XF0W,̌e!.s\[SGE/[7zOc8hQG>[NgI 2'uJk~Isފ.^NwdT8YM4evb5O:큅H;o%MJ=N$c[sb\0f9f}'7/vФ7 ^JJoQcc-e5$F[k$rDR"7DNCzaml+5'.yC%.DG:vק̓ty&$ŝ^% #[Zc- T`HRlaKY$Q2VmE¥82+Y;J%d,(±%/=$(Zc+2@ / 8wM! r`o{JES ŲHf#Ϧ_(4qLzeU$K!2\ d\0\9;KYЄ]a6$3wR~^Ⱥ@X HAxQz\ rM'KrX4Mc<ϧi;&@03izyyy B8~Vn^[{`cy$x;o@ut!r&$;M/@:_D@rʾ3Wjpo o3"<2R<>̝'»c(L;P76 wa Wkd DAC Jۡ 2V6ˣr""fgd6Mq̓__|&WE5k7-?WME2/,$p?t,IR| baW wӕ`+9/:]E:g(.Mdi'k֬{Q`M{QC}8HIo\J\щI1ͿL9,/ F(\rE sg4~oJ>3T>Tbg{,.Uhi$V?mvCI=E\*{Y>`*$=NR"բ89t9IkA=%'%i:ͺ_^/]]K!~nǓ{J A;}}:O/~i_"#6W,M ƢD\s:qry n2J`|]yߊHJyhe>pce0bH}Un+~w8hU91)PEʮ]gQǩ؛/X*P^DHE&'Ɉfft$hN}J|9%yx3L1RTp"%) {ExՅ(P\HjgAi<\Z IA'J"v/QyRrpӗGG`-l׻⢶~*ٷO۹=wE&ѕձo.^m}do_/_I˄8&Wp APib ե+5ІJZ_[ao]،-z?hXFР5hccm;oD6e7ғp8_fOvaiR|H$|\ ץᠦ3L8:`y$̽&sӬ}#{ y 262I-/]V弊߄4ZuXON Kdx_?4%.(Hp"V H /<{%ǒUsH}$$ "I99*i:)Hꦴ k?]4ϊU͚lwBg:ruC6d<~Q f<}7h')U*V*ޢ=Hgj#FլU>hdq컁Hf\0,J#G=Ţ5o~ o`o芢TP ߊ*J%P<)'$/3I>d|MB@$Y>^[G Ruk%LP{30P_*X 5hȪ>E*?ѴȐ%0(=@C{͹`mȚfYɈR;/ms# 2_Qq ?â NDBͱ^ME_J3p,YUg]oQǑVflA MCtQH-BoKv@K6Fq9}$$ P2/4?_/ XP QFژw"vM(žhUd.J +#!'4 FD)3e}hFIsZӇo]j\]{;)W3]'fTָRa+l"8,o "*N*ny'*d,I_^hǟC5e ;H`e3#똚96?6ʼ| "oɦ/_A/߾ DLhJahlF2;.MVY,Q .8"쒧D3H6M@~(̓] "HP͐ÃT/(Y_h6Qz8zBLA^N #5y$M<=HK)߿LN-0:Ⱦ͚/bĐ&$H͌yOiN$N6z.2OjYFIAF]o; (;(}f6m\*ɛ6 ۜlJ6L!h *gwQ ƨk- Xd{kMlf`Dաb$+<iZkAGMHIA@$)%\NO_ߟ/I" q)ˌ.J"xYt$0vHI˾XRj+%Fk*E1կJ$ DHDXe81M4Q0=fPhߟm-koF{^ @=$?Fp@hZH_ip j 4wYFivȋJ4_@#x߿x_r܀C WڋR/vO z pzp;,?I{F@`tI/c"~a@g+n>+!ʳMw Jأp;^]ؔnzl,[lGr> _1~WzyiRXUr{i 8VAP@y-B-T3)Zl:hQS@ʅKO $fG1T.CшW"8|ֶQ4 yu|cL<-O mXE#Z ]rX~`ϧgl~sX6<~dt3KWZyP92 bx}~i/7^1/[*Fd4޲T e3BITiKŸq[k>St:2dǞGug[7oKGK(MvD˕ޏ_Ĕ˿o0G A4ȊuD X IDATx/\"JfA,2jE'i_>l0GS-fvM8P(4Pz= S2}JȁB*$~D0/n+2jB0(Isյ Zn% y,YJT)C8pL8^r/&u۟˷C$=N})1`(B|(.ѫv*6i)Tx &f}D=/| ``+E,kG(? l{2/Z y^:(%PA+DwӾ@|,3rpM瓻{<mݨ-wX?/ [= UJ#Xpdnna; cތ e3HRO vy~-=_ @+ײtkW ]kp)$0!p_#H~?+6t>č|eml75Y}Cjc8fuF[h8f%orUuQ- ,B_Ms|ALI{G[r#l}:\UF j9_:Y%A_;!duUaO_~O˷縟,3 68#ԞFq)+JڝB#U$D2s֢ D#/ VJ";f~~a ,LQM2"wNd?/tbK(CWUxbkX]nIJ$N=y^78A^nތґ*L#*âi"9oֽETjq`CE.h mM-f5m0u#_@>6*ͭ %<m`XLZ"@B9ZWy 阩w/ {Pήc"g "'$i_~]_ONtPF'= - 8LBK宰j<9 W)'=O7 ?.x`v85y 9|j35!AQl&Q֘Xw$RLdN%=+͞8M?]f}̘H Q_tdfp^Rr>l $A~-opݨAbՌnXmAhvC^t$jOE/F%ͲB=KHSPPiwC.$ L%Nej-FS5V@ZYnA0c{]iΛ0(RhV)1 8)8)%$'*˷@qU)A&I9-2Ii 3{瓙\ ד=os?_XTwmoG VS"r&I%r[i _L` #2=/p \ra. etbҚǑP "uׯ!'X#R;f7tBM԰#uHG%Mv*h|)Ú_W WKA isz2wuCU<"T$kH'󒇶 .jA5dkA&M%0 .0!//.ʧ_Oogggh+ɧiR`q= )0xBQ19>'vʈ;bԽ8gDpXپ[x tmDWV|J\PPat:}?寿08`ROh LgloĞ*FG5|i{1oo1ٸʇb|c })E }`]pA}a ;9b>5 z VoE,fT(IhP eZtȖ"5q) e_ߗ{|{N)`5$MrH4(O- HQI=p-煺-͆ZZըVB=g+1rpM]yO,F=Y sf#c0Fn\-Gw&k>- ߯:~Q*ͽ(}|S &jCL3@^ څF^KJ %0bH[1@-5[df,}TɌəT3I. Pnpq9K<<φ.|P4%JNRF%Mp|Oh~EHBv)?|7h4[)σ7+s?;s4oB`á4`$@#xx9~;}?,i& ҅$ >PZ :' IU&/ ncS.rRM !89lYj@9 >k Y bRjb!g֔HSTEEyL`ڵ$7lʢ HW<ʨiTs FTa!׎*$@VF;6XXU5/\vv=F`nd('<-a27ٟ_Χ͗y9qyN`i{"D.olUuw:M7׷#;1trxGIr oju{؅~,T^L@Z;)me-]6CxǟOry10%O F2Ъ.-lUV1c8eW0&B,דPYFǫ}K$%%Y7s-%墯&GkTKrhЖro*<7.2B8(ɮ5j&m@S/ۚ) b&hq=߂~4Hƙ<)cmD B>AAM0qO .ߑLݾ~/ 3DLg\ tJliCʝ4Jp/V 8!όm 8z/7=HXD9j!doC˰u#]-El ٗ募>_|\dD"@M=_}v@4_pc"/rU3/dRٮQ3۔A 2bbG֤A* "켢ɸ@!'Hm!.!5=J[^rd i̊5q{1SjFZ?k#z ?~S8XUȧ{ p[J8 +Wz." \ѽO | ]idM}הx%1BlvH2|xn =sWsȋ5ls|$p@ǽʘ@Y1_nHXK8lXZ$ Lo(l3"ex0{ȗwI Dm$~\ ͒$ױ50N )֌]3Ƒ7IftDjs j:"oڝ14PL6$:@gTĶ)ߋSXaª!> z0rzd>smO|܏zĢ=\!{im E"brglaf"?%_*vEiӥ[J qG.H, VT1OnoG:I@/|/oovɶ!yd7qlwy5ĕZkbKwN%Az2$OM_٢W+m4eG(n&X;t#YAֱWo@[w=GoR[(X{6iv1Ak5oZuEK$T6pY $2fqc,1#=9\N2U)ƞl"jfIE{`mko-c/cۮ쓢2fIgn-X6PHvQEkqim?!F_OъNl *c-iuox`RgUZ rh),مx*cB<4;2̚ #CRX59tA|s}*҆D +A-C$CM*pdP @pIdlZ[BQBTu.-6QeH2FR!S*C]|DCEQ|(bvEEaˀ:M]ڮg91bGnb/^8n@afxq3 Wf:oGx`>]j6n`J;Ԣ Ez{?8Sη9,le4kT.*5*3jfvO &P ECU=^JW&4Iz;0V?Fװ;3ERqm]N !kܒfK:06-zt)lG";܏m\TLs[%M? XT*78_FЀfKZ mD)WWR**h2g4B5fJݺj͡HTZfr %Z$YGP=Z*D,Zo/z>DD{pP *դ6 6y l}F,2u-hŠόOQYVb\kx RJK5_`֦>{ltWLq{ߊP} *':j%=va#x\DqxPXƛ t(F)‡zIHkHɫE~~jDȚ{}0>ZEc8}?Rd7Bt^=c4h1no4 Od\/6ٍ]g a x69N|ĶaPMmh,7=FK :ƒ }ZN*6IngI7k!W8Ɂpy&5>m'W+ H /5QU CTY#vۨlrRf(!?%p^I _TGНeUogO :@O8Y\;~ ~,ӊx;ixe=ko-W{DC]{uq7aG8݌+ Y!efm(7E8F-=$ן"mzfVhR^h-IEJ (2eECԃ~K& $T GIFJdsjej2//Q#^j.Y&=QBy.\ްZZ z@!c[h[N!7GPS {!5VgDhc ,i ӗAR_mU%㵔]A}c? p hP_ @}h±oEnC) oBy万ٕ6~+kb60 ;㪋M)T&[?*Ϲ(lX/Խ/Q,IQzJG4R-}|>@O ^q2n3#m@.ڨoK!~W `(%d-ǪȜ88+l.WWA.ޡsܟhbvD?$]-`ml)IQYrٺӸ6>)SZVdl=imbT(H)|DJ&q.U8P}ISW W]zӢao]{עeG][ib#bQSt)k"#2u.q] G߻JIh.gȝm sQ .1߫2 IIa +#oRƢn(qZc?zGJ|f}hb%3$n$Q.{.蓴bm)cr0"x ɦRA*Bx0S ?ןԅ9 c¢?Ls!d)Oy+j.}e47nDeÙ`RJ7@nǖ:b۶lFyЏ#bT  "Rgup$ii#aF'W;] ((X>9Z ex`o<) m$NG_͈HAJdwa$i]UU'%uفf 9#rqCj4sn@>lVɸȘɎr[UƸpoVmP%nUKPyI@V/Hh+8)b IDATk w@P #Dd :_پT/<1LlFu{47c6' CFjHвE qfK~ H`>OfSjD"hL 'ȿ IKCvPm :u<|j-"`ZA2ew" wQyYtVe$$Ilхv[e5 uLS[jU(N1ӧ vPHdyMw,}=;(e|.qcAB#ViHjL.iQU*2bX yOE|uD )?3?/GUk}!cڝݠ8ACF&%l:W#۱;׺ocm8)[A\Zh+8&U9h!K@ ڃ6 R$ IՏQڄA!yO?O??-YTܚ/+ub'*u \\y1az9Eօ?`a}-DQikMcʡ+W:J,JyAw%vP;A\ sݲ8$8;Q0ngdI@.ZIdZ3e4>F鐫JPK0i\S-&IDϭkD#B𲽼2?x*ė3 (* si2l(?&Y}:-B;T$;S!s}ý s#/߽)~;񧀀({ #D~V&;v0.0tkQ@=GCRBqh 3+zq!$Wk'$ Ըfi0;Q?BiIQ:H8ki'؊ᡐ=Ʊg]$&x7'ǥC#m;.dPhP|{\ǿpC*?Ԙ*Vz_V~j8r2$L%cq 3EPbkTS̈4'M d8{t5j%FLcSHa-X2ŊVx-Uuhc-zjvJ8*,#$,V20'?~%HYs%:I> &mhT-^27FB;(G[-y/‰Ak2@HTj*IZG&̑nF$`G_)Hi)% 0:.`>j/"JܠS5I&n;"[?ېEP4F шhfxihX]n>"[ V':C%Xۅn+X6,muS K3 N*X캘k`;IM-IWʎaD b똜2rKM.F,_'F4}H` }mitjx+3 HU2'"҂X۔`imQ:2X-o8?{=eIRlר Uݽq܅~:>@/.؛5AXe٧iwhTHbdgӒ eId-zO+Id6"| $ ћ4[8&\#wRC~SwDe߰Kg/2b`^yuL0`8/|C[ݏ58: >S7Y2Eܺ4_!}P8"/|:Y[>c3ԡNX9فB۰,r'6_iB$)R\`?)c~ZV7׭}@@H!u371]䙥Z_ ^]P#td7IN[gH`##B.ʠŎ}iX,i:" ț]9=*5.pV逰%THۍE~9!!N[0J&Vϰ+ǫѬzqZxYF!`#~ ϡ =CZBY: X5l@:(6 z`U;][ARiL/=$f򲣺UA[ '5a D\$ԽwS1p #U&XgWD5EwDÜ{;.03^2d~:MC@+IrwD$0MT ۶m z%"؝w2ȸ3n]mX^?Lr|W^vЛ灲q7f:6yƒHwL1D"?(I)(t#Wf ! =uxYM-(  +uh8Q5c.h5 ǞuvN2WSLIi5nRV͑SDA-}O? V$HJo@ץ/Hv-ݷȃ"%;xY{҉a@;XVESL@o3ܢYF2I@M5M"|-}E/]U.Nj"v3m Hv0'm dfNF^V]"x^^oiLh^,ӵ]|  .NOkG!ؾv98|| DW *U_.˲+KZ{au9\ >c9L^>"Y>cp\ C$qH'/ɠ, ') -\6=k"*Ɩ^&sL:v_3R)@2eji !LOeLoڴ R= oF&,B3 E, `&\Up'&?x\ąJe2TĶqN|[ SXi]Ъk{Wd݊^߮W0wR-/k.R֛CiJ,tcw#^s F}#T&VsVLՌLBv ]9'2tzy;(44PLVH*a~0![ow941e  Pd$$<+;N&n+ds48R!C&bG_`2+H= @}`[ W30#ibD7c0]`/fWi,A;r2NOaܚCó1:l!67nYUH?}{tWK9cN7H^ٛ.2%7]һi\NX$8ڀ[4LJ% }钺((HKҫ]8&痶딲 KqBh* ~&@l\E@Έ/ح= ¯"AŇ-Dj$HJ!}h<){@ҐTX?SN WX؛^ݱs<.ص$`w)΄q832)LE 3;эX`ߠN|'>۽v{4.$pይT_)8t9m[ m2M$@{5|§3͉CT.t2F?02dؤC{aRq%/1361?6ŜMj)c6w^0$A`㍥z HYJoel"i^,*Îl~ هYLNTUo52 1XL‚%AblΚZ :OJmD۱Og1*zn@3{Lw\bzݥf@o.$a oCiV3OU ";h<;F |1yT_EHesڷ`׎F%骢,xY)XJU ͽBV~{"'K2K* ,HE @P%6{l1"_qSuָ N/(#0PQNw}l+}.Z˹CG\B1c#^eGY ~l^L mivZx.p(U=nȅκ!WYcusy]v#:3xM&6ҜvezNu>Z/s.ed-at k+X첳u5'@ u- F@D@M35Ri<<Jb6pN熴ߝ j̘$uQ@..d(۩ƤfX !?m,zPQT}~l)4}R/[4! fe+VU k@T"g()-G"V04GVh4[ B2bX%"mZC;3VN#mⷍ{8围)ʪ$m^ >\ն2ƞ!mX}gEw,@;#ڟGj!mX !RԭͬO<{U$%@Eo+@pJ2e(YLקv>oV6DrrD\2Xǐ8`ظ,skV\K)W0o15 ^0 }rU)#Mݪ>c:X>VV߮;LO&Br2 x7((H -C9&\yJo_ﶉstv [HMJ@zY9A͟K,i/^TYp/+'YB HH/B^N< OO'\ ).ߗ|X{tniVA >@KEvaVܘbԈ̲\/]!)X^ZB%1vH*$(uFf-K_ m IVMav|YplNr(,*ZJmzeI;RB{Ԣ*gJM/q)d(Hr i&ث4'D֛1KX4O/`׭ю@wLNj*^~#GT;=1IP7~iZknC6+t t3ﵟ탄;cwmmi\+Y?L.Y:)/xOPrf D RVM 9l^[/T,t1$gt3o;))j>)iq^(\nr&sD` ~V`v+- 9XqWcKu 1QwGBˉ@V$i9e#p7 A;C\jeVXu|H| >NMEhu=%eVx\>:MHI֧0d'u}O&bRm*L@jzi$q%O, 2tUG\RLA{al6n/L2V'IЅkQJv4#dD (m;_&8C1iC`kxUׂv]y >c!kzu{="G%SG06)OW_Mr#h=DfD)ffY]jfj2~zY-UZkTJY%#]3͗|qdUM "-U*v)=WSƶwC.37ޫtlLpQӫ:a]Ԕsh>դ`øρLo߀NΜӋyh:BnO ؊O;Jz ͫ!ZMDwk>%2?A .QajZfcqi) ۵jKƎ+Ҫl\=R$ !N HVD&8 wؿ\BCp4M|VD}$ z=kun%W($A͛C??7. 5H6s.yf|>\"٪R~T;IsV=v UZ{g)E2OHNJ%:l JEث?Fc4+mTSPJP9j${MB=ooZ+)Ydq="uY2/o{ + 2V %+kAR* p:L0!%lݑKٕIՒ>Jbƀۭٵ9Õx'= u0LB:U(՛9];LN'ɖ?2I!Y 2oY w9O1Jw}#pwb^͇he+F#|oc[ʨ6uف0@$6m*` G`5>7BxafQ{uVcǐ&;#xٜk*NIn]\=aJه_~82A IDAT!'CÎI1%ּjJSvU[5w?'wFdܥ(%t!S\[uz`W\e뜁z˲AlZI)`9c֩ou"qD?e` >t%K 2i2˜I2n[܆Z}`hK| &)tf 槀/]6n+CvaZL+:L { $H1K+c}̖1R#)j[p5ڊǽ/X*th |VC A2i0͠q=(][*v M<80sT< Hw>t 5t`h;> zb,3xڥ?sY|:4W`}_JN~F.fFEC-3[f" 'K-o(f< 2+wډ,DZlzAN IrE ʢsdyoo^]x򂮢p#Z?3E[|=>;ғb֣2glʄu>vኤ;ZcwrG"<huxHb찏2>4bRٲB s݁AoGgz [L([1J5:,~ю׸cڽ;[Oh::Jt`;B 2_Tg`$`gHŝfжgSY .E9(N[Z-}{ -QK{L|7HY~LpqU&EuMz1ܜ`hvP=M9 u(#v,]iӣa꾀eX-_ؿ߃ 4ޖ/Q؇ {_c?&g0:d| z%A*F8\Z.,IfdJY}w !P9ȩ:Wv"bG´+u:7wA4d&I2뇠ӘZ3V\,I>fgWgHJv?,nɮ$vfz,Om~]42=y4@A)WKgK\> rё:N`ϐ|Rk%Uqqt—Hph-2l ;j|,x:Y۲/-M\TүEG(xSӷ]G+e 2 ;=.jb@@"cUd7qN(-8|"~ wer2KT.>$@]w!tm?0*R>h͘qv [ͻ5ّQZa^u.CcR *.0C]qtDI@jN.1SPѩY#N @AR,R > V R[ C |iA.obBsDJStѷ υ)U fufxɒOR5_\gZ~ffHM޾P(1Z>H4>q_l'{' +Mg u,I9ת#Vz hMDg0%x3ùt_<|;gaC ;MǞ)ҜhoiV &]e,<ӞU]22Z`&g,-"-PnS$>vNH+* kq5ڶyu@^N1~4T" '&&H&Uwh y~JBm\_B"ܰ n (WO$&8V_ⓨǔ\${~%H$Z_@U GcT:p_X'Ech}H('xau|Xv!pf)@:1@zl<@ Fǀ,qB2!|m142 `+kWNO:c&tuNbd?|u$>.s0wZwzD>7k VamQ ""c @" t): D`'Fb{ 7> |!kXV!ٓ; yAv%>:W\ @$s@FZ`d=9#?2kjɥwL2hl~4njjT A;%!q+ah3eW̗ D|md5?ݍBE&?bfD IHo< $g۫ K! F{O3$+`>`T OKjVa8r=*cӄ" (-]SrK<fm)ҔoҖO7) w?}yzyg^U1FI~{p:oOgQB56V_<,>7:ݵ^@q̸Z[? >RY}A}NG -?f#hy2SQR ͉P@A&W0ǪAK #NBF@@^r*᤟崞m bsߥBsI*Ħ 8 IM+#oa x NDW1? ^Y4`!Y?k wbɑ5zJUd5Kڀ('N:.X(2u'hN%/)ۮb~AY[!% vVxoM8ݒ | }B! ψ1J 30 0XDᇴ!lO)g#tO@ӿ_i_&X'ˈӊ]qƒlOq}T]uqe\Br$(%ܕH+l I,$M|Ʀm7h%2 D@Ĉ(DH@Ƞx ؠH! I'((?ק3}h @ּ {^ّRu`u-2q!dwT0 1Uxvr1~-S]*ˀi`~ e[SZ6k7ơ\6)MmHӖCYzĶc2@Jư@HB2oL/(0FmMǪ "q)WI$m!n3 ggmHHgTy P6Dl.18gA[OgIf}o825$謫rdG+4eW4$;!>:\WE/l:ɚrec [Vd 3=mkܢFB "oN40brӛ0n+WׅdrBJG,/i\] &m8o{Y*r֑k@Ÿt]w SLoKr __cj#*4^,}Nf_ʶB+;;ޘ20 D&cH#l!RM5 wcatj$|M#D60JYJhk7!Hn!ǽek-S(˒S:+Bַ'yMZeF 7kʻa74OGo;]F{3ZQ.~ҫ8{}֞! *C9# ]ch G+ &n-ERAoYz^ rr()0 dq 51!1'2Fg @3" bAsWPLJJMOm#$I-b0*>q$oZ_ iC-h]H\@z!!bBċʊd8Ӷm[#bDQ OhthvvɟXN/w.}Q-'hf3ER !dcVɟ#n舵ʰ_!Hlk=0-|}/ChMWKC"/WCE]|;$nBgEF_PlQ>SwSe$EIWC 0um[͊=R8(UyQ=6E4@,$x}L}UDZ.PȊ9 7+ᘱw3j3:eҶ슰6舤pi;[ /|N {ГQ%0K d)`H)@ c<(EmўY0$TR!Εb^]-(CU6ol$9x\Z^PiS@BιWEG+qN] 2xK}oseVZSm/;S?ҾQݗKVk,724"̰Fj $sT5E'Inj^I"zc4<#&Rnu$RT /)&Ug[2$␏uFخފc? Ja>nV-xV֡+}ulg6b1**Q*KRaHؼ/x Z' I ]IIb&ނGoݔ#{1Ov*R]K ~XXmz0G4\;S\ߑ͛ZCW]Oܐc4HRB[3ᛇ)Q&vl9-P^*S-]F_mF zMRۣ tF"ұJެHtNYl}SZ|hCG|3{}rd#TjQצO>ۿN{&m˼gpZ]t+l0&ѐww_ WA$yk|; W^[ֵ0.кwfZp]jHlY 'wdQLIPq0/>RejpB|^n_7m]9 j\C gSٴ;BF=ܲޗ3@;'"O(&H\BLoDZ*0:Hz0Hrq&7t1_nS]?n5bq,,+Ԅi=_Ҝh;)b QўH欶sZe fohWzUՑ]~] -l;.:–BQ7_lоxSHZq.,m:B1v?HA\ O-V'J}'I>1jGo zuÇJ}Q6$ /$zz^ߴx⭋SLX}2gi2ɾ4q4m.@Ք%g.݇p /l? P[!𔕢L9MԑDT+]0RLN¥j,e].vxN, [Jt-zeGu+%VrzB;*oR/|rt67;5n];m洛j3"=dbR2"o,%:}JSЍ&*q'{yۣ [ñR) Km߃Q(pMɗ@R iG([:Cy2s%>vLDJey n)*L5O}˹a-R'R[P=ɦ@z1OsCYATyR#9=̯ZXa4oߩ9 ڝsu1{mcV K C$@@H$Bهٷ*}oj,?W:knA3W cn&8SMxmC>7Q[|D;g! ;b߱a[qE$(}Y >_ľp=G iu=8l6e[GODj#+;X ,NMXكtØr [VWYղu9Ix $%i{+n nK䟪 S׵ WBUt锑*7GvvNzl¨~+-ڤ{$AeL| lHPp=؉8͟VCңN]Uu"a]z:X\^V{Ѯm"W%["~ Rڲzv_1ƒ~wo+!X* 1 K9V\7wn )!IJU%&]/ZZ Iۋ#ϝt~ʮ3u IUc*Nk!H4-$KL`ӲUBNǗųH;b^ ^B!vGԌ̂t^ip>^1u?NhY8其ܝYdXocdۦ+u&S]́oo#nR*"m1yvmW6eծƘ=l9v{f`mJFP+D%' -5lFA1*IQ:T}s?\tg913#>)&|˽-J ٌ39K~ P:@1@Umn'Ϋ-]SW+T|X5K07& R6N2k]^m,ÒRJW-? SUI|洀Q.<^)I{2 l]E^:3|#tr[LI3֯EF H@QOWW'ѱiB_+] IDAT2Vo76XcU۪DžN2WiG`K6wغ7 (#Ƚ8ZVE7hg sq!ӭ0 $I_zfIm$**9VYx$)Da!*1!{K>4DnUbzÃI_TXa$ԿZs'{83kn*Ǒ!VN2C|~.;J Aр^Ҧ~3-.7Pw{? v:-aO6د-!} rpr3G#{Ub6vQ jz\;x)#H:( kp w6r=\joU +Yty-(\6syVV[,(/5IDڴ=]b +/4ǧ XǺ% 6$$+diw>ICY.#"1mc.PzuKSb $Ӻ9ДőR/-ѡk]êR'/j=55BbvWѐrFؔa9J:y[vŤЎ}n*8n x5z}o*[x{GlXՅ^b?jz*J%yciʹ7M& $! d֣${ژޭF٪L,BI-̵9Y ")+-`+)@"{s Bؗ/O"{uq+wHKCY`a$=ER{ZAEG-1n̾FS`N|FU[+J\4> /z=;UĻ/:+b, ~fLuxB 64j2 fLsw*/ hޒ-#(5.J@nǵJI̳K(P$,|C@`nkԞqڝ%Я՟AJ}٪!!$Pj&h]tDM2.qk(~Q^o`".;‚qcK MȔ*Kdnzh?R0 t2{]ς]뻐dA#/ӮW RH 0 |>+8GݼF:1;>Gx "Fyszg:9S`RjMo<+A@驡!,]F\AA)-&hIw83 ɢNE|~=qkdd%O 4J\n1w߀P3~mg hfWe lnQ۲v`7JYUxd$4YJg$NX_@UhRtQHObш]f(,c1$-a֗HVKxE`lK7Iz,-\GW _̽="g 6E tA1F,5f  dV@\=lS +2CWXŻg2UV"K N` D*?)kqHEfiNqzCΧLV U)PU|4On-Luֶm~WΘ'41M/l|[bz)>B^$T7j]&:o?ISS@_$";CDKjLl];YkIOjpU.NNOHCDt/XʍBHF"9n4x  1n$H Rd$l8?ь9$ПP >]}L[ $C)]qZv>yp<BKgɇـx7vUzc.2ƨ1Ql<\}vPvZe }3<-̏hq ]`n[楅~ltSb%6C 3`H:mb+̸l<;Xu?;~!ؚ]wG>I7+C<&.4X^aUq4St[L!sՓoԘG$iה5+% ?A Isu2&IAR Nk !`/cc-sX>BjUYYL&x jr{JS4ZIl  $$k?L&xʷTEk4g<5%v 1?uA]l \:sl4o=URhE=P I{ XOJ\b DOiJj!PZ{\v.aS*Cڢ $x!iB()R4v* 0F{xT8 BQo駴䑈DrKM3ZiIGDY}kxEO(}>*.{ffϾ\ޜs`Ûg֬왵מ={ocZ!Zd*Vv7Qڕ &dJ9&~cs.(?XlhLKbVSMٵ<ǰI3sek6UAT !J).fZ8KP]}5#:c')gf_fkEtm@d)\a /;wSmnh$tK.-ܪ01bY@?UtA@*;dv2I9n=C7'օh_#Z<^Yշ9rdHYh2|3*Rڬhlclsɺ:f1z3,؊"c ɜ q &Fl_c%e,NoY^f^0U0'Q"aYY0@ N|JMot9N܎ifvzuõz'G2c?ڛ6ExZ, 7NY9q35e%7!e)Q&u2H8bM6GHR1@Ϯ a EFL>ij~oejΫKHe7D2 s~,mJ&RdE£9#R ~3$ɏhMكb''1Ʀb7-hz݁ 0(U%3o `j tL ({Lp@̰;؜ 逘C^@@ݼe˯l،\"Kf%Lg {bV[!="$MKIer<"[ /q8*g=G=nfN8QG4ĺpސ#wC"ZCf,b;S)-dxG 3IF*Ga#PLQۏm4J͛`0n*qBI73A^p#+p0R:iXmeC6 L4J:{% ː>(4MgmL{2ŶC28R؝p\Cf/2%%1GZ8j['l:0 ( m ()!҃3rd\nQeAK_S~Ad;Y:N[G,kihWpʌ.lgpa 1ϗ{$,o& -=.2Q.CDhkJ^9 wʥj0 iFkX @5[ H6{D 0g]+茆u,d!js$fN^8 e*2(R]̀jdv*v2mb;9!tg,^ Թ2;dmɆKPA'Y=ĵaG%:-oX[Q-jԆފ@7%Sv!U-J`fv[ٸ,f%8YZ 5J3A4g-gun@]TeZLv "l.[#u.@f% uDhMF.rMPxNn;0_uΗKų9q6X>6tN+*Ǽ4*+aL`r66XŽm:XiGz;U6=ؤ ˒4qe*K^Ra*kfħ<-"m9GcI[E+뽦tkDƷlbV{ڝiL9yݴ&qrcl r`[QɂUs-4rpFkRAWc8I=WFOVLjD$JDҰH8>]zzDw7ܪ""ʍ*sâTʶˆQfh+ NJBK3&#Ũ@RIXnzЅQ2BX,3oD U@ >-HՔBL8N6Ӱh۲aCd[:XT4 7wa[Sfºic;.6 )r iADDvA+Jq4 鋦 oM7]eˁ%6 P( "} ȇ|3 NDRlDWWHJJD+oEm+ɥVRH7P4[ nN;9 a+eT+f֎- trARl!yfӺ99͐RM z7w[`4 S- aKHD230Έp2^i ic"]pw;u/։e !06D~ED) wo!FA^nc.eW6|eVU$]%Fz#}Av mOZRd F:GzX;tDDAmIK0'`i ^0QӢ*""$fd-. ()6#705(SXS\%Q͉aˆrZ, A"Ȇ$U{{!@z~+#nnZ]lڴ)ɂa,ē֝~d~ԥWfW誻mmiy_:b/}>u7_2%#\2pGNllж5;ׇŭzկ-wV!0 1p.)Ѓ f O1;Xm0 Z&"*["I"VXȰ2vHQhHjJ:==YS2Ŷ766CjIN 1Z-_R40<즋ɧY-OE‚-I[YPZkGMQ~;VՐby;T{)G=aXUU%Pq}"@#>Eoü#A Űb(13bNEmZ" P/DC=ᶎD?L+aA! ؒ0 A)f )՚_KKDA9Oon 6!2bCQK2))x!""b iIN*c-#a)ܓf-HPðBM+,$D ."f0,JRBKJy/X#^{c3 :蠃 \` ~>hĉGzjv…;v쐐ѣGoٲƎ9ReM6Y?4iҥKE=cRjx!@lOpfȜIֻ*ڮ*!P;D{O A~aMmXS;kv=j.DB!DY;Y0C(ǫ 4 sĊM\GF ifiIB)l^d4"A j[#:H\=v[ =[H [Bf66ڡ PT[SZO `DS.dt P;ђF,l[l˸ᒣܪ@AJ3O n+.EH8]j*p6" UoP]с0'wFn֭[oN:cرng}V6.,Hĉ^(:,8* 4iҝw9f̘3g.YO7n 0;餓;puםy>$o ]Ϩ+B67n9Z OIz`/~!{և7>Ҏ=kXI.cD[3f $&D ZQŵXRXcD0MgԪN HV~"jhh+kԔ(ZUtQ eKs(ԕM$9H +@43`kH(Di[iZI"r cÕW4#m~@4/SRm "[F %2;]D 5\3yp1ѣo=z}ݮ~X8>_wuG9s&_'x!CΝ `ѢE"FDSn peW^V!VŰa{CY}'6n滓~[P^7hG2dd](E8p&DL$߇:*OT&VVMbqH[zAJ+qH3 ddCDi}6 <{B Xa"JSeRS+ sUn.`wI(v-@$ 0p.F%5@X5NB8||ܷ`6e0 Xq!bTXUHIUHP˄!8$B1TTo]$eQ۳V66Rll*>wf?l~ߞ={UW]5}dއ th"ukP]\^S]Rd}3ݧ:W+szfZT0x*={͆$rhdpswY$v`ȶ=!dL HqS!K`fgKj(2o%]^aD038Vl$Z F;- \("ql=ga|8dmVJ8 X[S&: G1z$2 S]41`YL3;WOfq8nlS@-nt4""@˛9RĒK"1pAkR&K_vBah6AQM?*^Ւqryd^W@ͱ l]7*w #2+IM+ Ǻ$’6lZƺJ Y#d\N~<2GHܤT"iJ){겒`0l2,#5v88`}{Uذ~l_ Q>̉4./[F㒙(!c k3i(nIvc݌dۍ-ELlFkQfФGV2`:MhT3 x<N%hWn8x<AG2dz1L~h'&q^x<=aPL[lӞ+"J0{2\nmvIX &XlmKzYT*B g4C7zo㤓N4i^zOJ|i?lO }W\9s̵kN47o+6}{g0gΜ;v7X::f̘A] 0gΜj;eʔKUjmoN }/\Fi?9 6^ضWڭ_~P}P1yLD*dWPRl^8 B@R@[QSSsWΞ=ܹs7o>_~eoN8իWǥ4W_}e[^iӦD1hР˗Z ƍw.]9c/>}[[ny͛wX@MM̓>8ze˖pԧL`Сַws{P,ǎ;l0"zgnF?4aΜ9uuu\ro\s592=cpQG?o߾;w|KNC 8kΚ5wSA,iӦ~F7n\޽g̘q=|߬׿5}t9'x 8KGuTzw?O<ȏ>h&L8묳\>}?۷ouu_W\q!BD/~nݺG?:㪪V\yUWmܸ٫DQh㢔:˿1#͟o-}f3GRG6l|dt+ѵk ڵkӦM-J <8_K+w JBp̜9s֬Yw;u(QFڳgk >\w{1bw_P( ~ dȐ_+_ʯ &vi_|9S[[{g _]vnݺp =g1a„aÆg>3a„ӧ1bƌW_}u~܄?tr&fz}G_x_W .н{_Wg1b~I,ӦM[|gqWwyh`ƍw}{}ѧv/X3!k֬y'R . pܹ۷o߾}ڧO9rݙ+'|~+?#F0lذG}b9C)u饗zW_};5dX_____sI bŊzJGZ,|G/6l_mk)notAV\yꩧuQ;w|wZc8p tgmTl"¬s?|+0ͻk~7555߫?>c~NpRcǎ=g͚xoyݺu>#EX'!w>' C;R,tSN袋 vwյΥYW^ݯ_#8bɒ%yc)*ɽgϞk֬qn[.]uV;gb}7<@f>͛෿g9eʔ[z뭋/~'K{ gIDAT? &̙3o[Rgo& f޹k ]33PGLA _wu+VxG/$%_M;w'Nx/Y|I+&k\zq͝;W\D-{!:EeУG1cƈ Ϛ aÆ? 6X#{~?~CZ^زe jl2s?Ǐ2eKִ[/-Yĵ† <@ۻwop뉯_~~/ ٰ҅a>1ۭ[7ٸ+m!ҥK}WNi]vQBuu7x뭷/o[^)5o޼xOGU__K/KGu_jժg"ڿ?ON:u*TUuԩSu h!*m߾n p>iӒR[dG-!jhh8??sX[͛7' 6mnj3f̘ѣG7.)rL^~;_"W)<v؈# G?'|i=#jjj裏JO}SAڵkٲe쳏7#Fd.x{}Apwy"6aÆyDt '| _x$͛Lrwq1u9,\>}q-[o}+.]s9 }ݷK.o3N;n}aKAP(Xr-[={-򂉈lg{ "*0lݺ],*t\%8蠃jkkW\cǎO~GqO=zɒ%6gy_5k֭[WSSӣG+W&JX=nݺ)S;vK.{97n8qq.0 -[6y+V]XhC A3o߾ |AzC۷o>}2[6m+/+bӦMFSgq?GP_}ޟ1';Q_So1>b1rz@y;_!æK{](֬Y3|m+bÆ mF&L?sΜ9uSN2 A_.foKO3pATc823XYޑ4#FO~R]]]WW7o޼pqM`_ * `GitHub `_ * `Discord `_ * `Forum `_ * `Community wiki `_ * IRC - #LookingGlass on `libera.chat `_ Donate: * `GitHub `__ * `Ko-Fi `_ * `Patreon `_ * `PayPal `_ * BTC - 14ZFcYjsKPiVreHqcaekvHGL846u3ZuT13 * ETH - 0x6f8aEe454384122bF9ed28f025FBCe2Bce98db85 * XMR - 47xM4zG7b2tEj4mnSywHve4ydZzn3wzhf22snDRB7aSEcXrgUBpoT2Z4phTnyFMi1sMyQtHbdufMYRQ2PzMn3PGUJAE1dpc looking-glass-B6/doc/install.rst000066400000000000000000000307611434445012300170500ustar00rootroot00000000000000.. _installing: Installation ############ .. _libvirt: libvirt/QEMU configuration -------------------------- This article assumes you already have a fully functional libvirt domain with PCI passthrough working. If you use virt-manager, this guide also applies to you, since virt-manager uses libvirt as its back-end. .. _libvirt_ivshmem: IVSHMEM ^^^^^^^ Configuration ~~~~~~~~~~~~~ .. note:: If your host GPU is either AMD or Intel it is better to set this up using the KVMFR kernel module as this will allow you to make use of DMA transfers to offload some of the memory transfers to the GPU. See `VM->host` in :ref:`kernel_module`. Add the following to your libvirt machine configuration inside the 'devices' section by running ``virsh edit `` where ```` is the name of your virtual machine. .. code:: xml 32 .. note:: If you are using QEMU directly without libvirt the following arguments are required instead. Add the following to the commands to your QEMU command line, adjusting the ``bus`` parameter to suit your particular configuration: .. code:: bash -device ivshmem-plain,memdev=ivshmem,bus=pcie.0 \ -object memory-backend-file,id=ivshmem,share=on,mem-path=/dev/shm/looking-glass,size=32M The memory size (show as 32 in the example above) may need to be adjusted as per the :ref:`Determining memory ` section. .. warning:: If you change the size of this after starting your virtual machine you may need to remove the file `/dev/shm/looking-glass` to allow QEMU to re-create it with the correct size. If you do this the permissions of the file may be incorrect for your user to be able to access it and you will need to correct this. See :ref:`libvirt_shmfile_permissions` .. _libvirt_determining_memory: Determining memory ~~~~~~~~~~~~~~~~~~ You will need to adjust the memory size to be suitable for your desired maximum resolution, with the following formula: .. code:: text width x height x pixel size x 2 = frame bytes frame bytes / 1024 / 1024 = frame megabytes frame megabytes + 10 MiB = total megabytes Where `pixel size` is 4 for 32-bit RGB (SDR) or 8 for 64-bit (HDR :ref:`* `). Failure to do so will cause Looking Glass to truncate the bottom of the screen and will trigger a message popup to inform you of the size you need to increase the value to. For example, for a resolution of 1920x1080 (1080p): .. code:: text 1920 x 1080 x 4 x 2 = 16,588,800 bytes 16,588,800 / 1024 / 1024 = 15.82 MiB 15.82 MiB + 10 MiB = 25.82 MiB You must round this value up to the nearest power of two, which for the provided example is 32 MiB. .. note:: Increasing this value beyond what you need does not yield any performance improvements, it simply will block access to that RAM making it unusable by your system. .. list-table:: Common Values :widths: 50 25 25 :header-rows: 1 * - Resolution - Standard Dynamic Range - High Dynamic Range (HDR) :ref:`* ` * - 1920x1080 (1080p) - 32 - 64 * - 1920x1200 (1200p) - 32 - 64 * - 1920x1440 (1440p) - 32 - 64 * - 3840x2160 (2160p/4K) - 128 - 256 .. _libvirt_determining_memory_hdr: .. warning:: While Looking Glass can capture and display HDR, at the time of writing neither Xorg or Wayland can make use of it and it will be converted by the GPU drivers/hardware to SDR. Additionally using HDR doubles the amount of memory, bandwidth, and CPU load and should generally not be used unless you have a special reason to do so. .. _libvirt_shmfile_permissions: Permissions ~~~~~~~~~~~ The shared memory file used by IVSHMEM is found in ``/dev/shm/looking-glass``. By default, it is owned by QEMU, and does not give read/write permissions to your user, which are required for Looking Glass to run properly. You can use ``systemd-tmpfiles`` to create the file before running your VM, granting the necessary permissions which allow Looking Glass to use the file properly. Create a new file ``/etc/tmpfiles.d/10-looking-glass.conf``, and populate it with the following:: # Type Path Mode UID GID Age Argument f /dev/shm/looking-glass 0660 user kvm - Change ``UID`` to the user name you will run Looking Glass with, usually your own. .. _libvirt_spice_server: Keyboard/mouse/display/audio ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Looking Glass makes use of the SPICE protocol to provide keyboard and mouse input, audio input and output, and display fallback. .. note:: The default configuration that libvirt uses is not optimal and must be adjusted. Failure to perform these changes will cause input issues along with failure to support 5 button mice. If you would like to use SPICE to give you keyboard and mouse input along with clipboard sync support, make sure you have a ```` device, then: - Find your ``