pax_global_header00006660000000000000000000000064150677531720014527gustar00rootroot0000000000000052 comment=655e067f96fd44b3f5685e17f566b0e4d535d798 hyprwm-aquamarine-655e067/000077500000000000000000000000001506775317200154615ustar00rootroot00000000000000hyprwm-aquamarine-655e067/.clang-format000066400000000000000000000034161506775317200200400ustar00rootroot00000000000000--- Language: Cpp BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveMacros: true AlignConsecutiveAssignments: true AlignEscapedNewlines: Right AlignOperands: false AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: Never AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes BreakBeforeBraces: Attach BreakBeforeTernaryOperators: false BreakConstructorInitializers: AfterColon ColumnLimit: 180 CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false IncludeBlocks: Preserve IndentCaseLabels: true IndentWidth: 4 PointerAlignment: Left ReflowComments: false SortIncludes: false SortUsingDeclarations: false SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Auto TabWidth: 4 UseTab: Never AllowShortEnumsOnASingleLine: false BraceWrapping: AfterEnum: false AlignConsecutiveDeclarations: AcrossEmptyLines NamespaceIndentation: All hyprwm-aquamarine-655e067/.clang-tidy000066400000000000000000000072721506775317200175250ustar00rootroot00000000000000WarningsAsErrors: '*' HeaderFilterRegex: '.*\.hpp' FormatStyle: 'file' Checks: > -*, bugprone-*, -bugprone-easily-swappable-parameters, -bugprone-forward-declaration-namespace, -bugprone-forward-declaration-namespace, -bugprone-macro-parentheses, -bugprone-narrowing-conversions, -bugprone-branch-clone, -bugprone-assignment-in-if-condition, concurrency-*, -concurrency-mt-unsafe, cppcoreguidelines-*, -cppcoreguidelines-owning-memory, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-avoid-const-or-ref-data-members, -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-avoid-goto, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-avoid-do-while, -cppcoreguidelines-avoid-non-const-global-variables, -cppcoreguidelines-special-member-functions, -cppcoreguidelines-explicit-virtual-functions, -cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-narrowing-conversions, -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-member-init, -cppcoreguidelines-macro-usage, -cppcoreguidelines-macro-to-enum, -cppcoreguidelines-init-variables, -cppcoreguidelines-pro-type-cstyle-cast, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-pro-type-reinterpret-cast, google-global-names-in-headers, -google-readability-casting, google-runtime-operator, misc-*, -misc-unused-parameters, -misc-no-recursion, -misc-non-private-member-variables-in-classes, -misc-include-cleaner, -misc-use-anonymous-namespace, -misc-const-correctness, modernize-*, -modernize-return-braced-init-list, -modernize-use-trailing-return-type, -modernize-use-using, -modernize-use-override, -modernize-avoid-c-arrays, -modernize-macro-to-enum, -modernize-loop-convert, -modernize-use-nodiscard, -modernize-pass-by-value, -modernize-use-auto, performance-*, -performance-avoid-endl, -performance-unnecessary-value-param, portability-std-allocator-const, readability-*, -readability-function-cognitive-complexity, -readability-function-size, -readability-identifier-length, -readability-magic-numbers, -readability-uppercase-literal-suffix, -readability-braces-around-statements, -readability-redundant-access-specifiers, -readability-else-after-return, -readability-container-data-pointer, -readability-implicit-bool-conversion, -readability-avoid-nested-conditional-operator, -readability-redundant-member-init, -readability-redundant-string-init, -readability-avoid-const-params-in-decls, -readability-named-parameter, -readability-convert-member-functions-to-static, -readability-qualified-auto, -readability-make-member-function-const, -readability-isolate-declaration, -readability-inconsistent-declaration-parameter-name, -clang-diagnostic-error, CheckOptions: performance-for-range-copy.WarnOnAllAutoCopies: true performance-inefficient-string-concatenation.StrictMode: true readability-braces-around-statements.ShortStatementLines: 0 readability-identifier-naming.ClassCase: CamelCase readability-identifier-naming.ClassIgnoredRegexp: I.* readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!? readability-identifier-naming.EnumCase: CamelCase readability-identifier-naming.EnumPrefix: e readability-identifier-naming.EnumConstantCase: UPPER_CASE readability-identifier-naming.FunctionCase: camelBack readability-identifier-naming.NamespaceCase: CamelCase readability-identifier-naming.NamespacePrefix: N readability-identifier-naming.StructPrefix: S readability-identifier-naming.StructCase: CamelCase hyprwm-aquamarine-655e067/.github/000077500000000000000000000000001506775317200170215ustar00rootroot00000000000000hyprwm-aquamarine-655e067/.github/workflows/000077500000000000000000000000001506775317200210565ustar00rootroot00000000000000hyprwm-aquamarine-655e067/.github/workflows/nix.yml000066400000000000000000000033031506775317200223760ustar00rootroot00000000000000name: Build & Test on: [push, pull_request, workflow_dispatch] jobs: nix: strategy: matrix: package: - aquamarine # - aquamarine-with-tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Nix uses: nixbuild/nix-quick-install-action@v31 with: nix_conf: | keep-env-derivations = true keep-outputs = true - name: Restore and save Nix store uses: nix-community/cache-nix-action@v6 with: # restore and save a cache using this key primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} # if there's no cache hit, restore a cache by this prefix restore-prefixes-first-match: nix-${{ runner.os }}- # collect garbage until the Nix store size (in bytes) is at most this number # before trying to save a new cache # 1G = 1073741824 gc-max-store-size-linux: 1G # do purge caches purge: true # purge all versions of the cache purge-prefixes: nix-${{ runner.os }}- # created more than this number of seconds ago purge-created: 0 # or, last accessed more than this number of seconds ago # relative to the start of the `Post Restore and save Nix store` phase purge-last-accessed: 0 # except any version with the key that is the same as the `primary-key` purge-primary-key: never # not needed (yet) # - uses: cachix/cachix-action@v12 # with: # name: hyprland # authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' - name: Build & Test run: nix build .#${{ matrix.package }} --print-build-logs hyprwm-aquamarine-655e067/.gitignore000066400000000000000000000005071506775317200174530ustar00rootroot00000000000000# Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app build/ .vscode/ .cache/ protocols/*.cpp protocols/*.hpphyprwm-aquamarine-655e067/CMakeLists.txt000066400000000000000000000114371506775317200202270ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.19) file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW) string(STRIP ${VER_RAW} AQUAMARINE_VERSION) add_compile_definitions(AQUAMARINE_VERSION="${AQUAMARINE_VERSION}") project( aquamarine VERSION ${AQUAMARINE_VERSION} DESCRIPTION "A very light linux rendering backend library") include(CTest) include(CheckIncludeFile) include(GNUInstallDirs) set(PREFIX ${CMAKE_INSTALL_PREFIX}) set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR}) set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}) find_package(PkgConfig REQUIRED) find_package(OpenGL REQUIRED COMPONENTS "GLES3") find_package(hyprwayland-scanner 0.4.0 REQUIRED) pkg_check_modules( deps REQUIRED IMPORTED_TARGET libseat>=0.8.0 libinput>=1.26.0 wayland-client wayland-protocols hyprutils>=0.8.0 pixman-1 libdrm gbm libudev libdisplay-info hwdata) configure_file(aquamarine.pc.in aquamarine.pc @ONLY) set(CMAKE_CXX_STANDARD 23) add_compile_options( -Wall -Wextra -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wpedantic) set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) message(STATUS "Configuring aquamarine in Debug") add_compile_definitions(AQUAMARINE_DEBUG) else() add_compile_options(-O3) message(STATUS "Configuring aquamarine in Release") endif() file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp") file(GLOB_RECURSE PUBLIC_HEADERS CONFIGURE_DEPENDS "include/*.hpp") add_library(aquamarine SHARED ${SRCFILES}) target_include_directories( aquamarine PUBLIC "./include" PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}") set_target_properties(aquamarine PROPERTIES VERSION ${AQUAMARINE_VERSION} SOVERSION 8) target_link_libraries(aquamarine OpenGL::EGL OpenGL::OpenGL PkgConfig::deps) check_include_file("sys/timerfd.h" HAS_TIMERFD) pkg_check_modules(epoll IMPORTED_TARGET epoll-shim) if(NOT HAS_TIMERFD AND epoll_FOUND) target_link_libraries(aquamarine PkgConfig::epoll) endif() # Protocols pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}") pkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir) message(STATUS "Found wayland-scanner pkgdatadir at ${WAYLAND_SCANNER_PKGDATA_DIR}") function(protocolNew protoPath protoName external) if(external) set(path ${CMAKE_SOURCE_DIR}/${protoPath}) else() set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath}) endif() add_custom_command( OUTPUT ${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp ${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp COMMAND hyprwayland-scanner --client ${path}/${protoName}.xml ${CMAKE_SOURCE_DIR}/protocols/ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) target_sources(aquamarine PRIVATE protocols/${protoName}.cpp protocols/${protoName}.hpp) endfunction() function(protocolWayland) add_custom_command( OUTPUT ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp COMMAND hyprwayland-scanner --wayland-enums --client ${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) target_sources(aquamarine PRIVATE protocols/wayland.cpp protocols/wayland.hpp) endfunction() protocolwayland() protocolnew("stable/xdg-shell" "xdg-shell" false) protocolnew("stable/linux-dmabuf" "linux-dmabuf-v1" false) # Generate hwdata info pkg_get_variable(HWDATA_DIR hwdata pkgdatadir) message( STATUS "Running ${CMAKE_SOURCE_DIR}/data/hwdata.sh < ${HWDATA_DIR}/pnp.ids") execute_process( COMMAND /bin/sh -c "${CMAKE_SOURCE_DIR}/data/hwdata.sh < ${HWDATA_DIR}/pnp.ids" RESULT_VARIABLE HWDATA_PNP_RESULT OUTPUT_VARIABLE HWDATA_PNP_IDS ENCODING UTF8) if(NOT HWDATA_PNP_RESULT MATCHES 0) message(WARNING "hwdata gathering pnps failed") endif() configure_file(data/hwdata.hpp.in hwdata.hpp @ONLY) # tests add_custom_target(tests) add_executable(simpleWindow "tests/SimpleWindow.cpp") target_link_libraries(simpleWindow PRIVATE PkgConfig::deps aquamarine) add_test( NAME "simpleWindow" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND simpleWindow "simpleWindow") add_dependencies(tests simpleWindow) add_executable(attachments "tests/Attachments.cpp") target_link_libraries(attachments PRIVATE PkgConfig::deps aquamarine) add_test( NAME "attachments" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND attachments "attachments") add_dependencies(tests attachments) # Installation install(TARGETS aquamarine) install(DIRECTORY "include/aquamarine" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(FILES ${CMAKE_BINARY_DIR}/aquamarine.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) hyprwm-aquamarine-655e067/LICENSE000066400000000000000000000027371506775317200164770ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2024, Hypr Development Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. hyprwm-aquamarine-655e067/README.md000066400000000000000000000016421506775317200167430ustar00rootroot00000000000000## Aquamarine Aquamarine is a very light linux rendering backend library. It provides basic abstractions for an application to render on a Wayland session (in a window) or a native DRM session. It is agnostic of the rendering API (Vulkan/OpenGL) and designed to be lightweight, performant, and minimal. Aquamarine provides no bindings for other languages. It is C++-only. ## Stability Aquamarine depends on the ABI stability of the stdlib implementation of your compiler. Sover bumps will be done only for aquamarine ABI breaks, not stdlib. ## Building ```sh cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf _NPROCESSORS_CONF` ``` ## TODOs - [x] Wayland backend - [x] DRM backend (DRM / KMS / libinput) - [x] Virtual backend (aka. Headless) - [ ] Hardware plane support hyprwm-aquamarine-655e067/VERSION000066400000000000000000000000061506775317200165250ustar00rootroot000000000000000.9.5 hyprwm-aquamarine-655e067/aquamarine.pc.in000066400000000000000000000004001506775317200205270ustar00rootroot00000000000000prefix=@PREFIX@ includedir=@INCLUDE@ libdir=@LIBDIR@ Name: aquamarine URL: https://github.com/hyprwm/aquamarine Description: A very light linux rendering backend library Version: @AQUAMARINE_VERSION@ Cflags: -I${includedir} Libs: -L${libdir} -laquamarine hyprwm-aquamarine-655e067/data/000077500000000000000000000000001506775317200163725ustar00rootroot00000000000000hyprwm-aquamarine-655e067/data/hwdata.hpp.in000066400000000000000000000003011506775317200207520ustar00rootroot00000000000000#include #include #define __AQ_PNP_PROP(pnp, manu) {pnp, manu} inline std::unordered_map PNPIDS = { @HWDATA_PNP_IDS@ }; #undef __AQ_PNP_PROP hyprwm-aquamarine-655e067/data/hwdata.sh000077500000000000000000000002071506775317200202000ustar00rootroot00000000000000#!/bin/sh while read -r id vendor; do [ "${#id}" = 3 ] || exit 1 printf "\t__AQ_PNP_PROP(\"%s\", \"%s\"),\n" "$id" "$vendor" done hyprwm-aquamarine-655e067/docs/000077500000000000000000000000001506775317200164115ustar00rootroot00000000000000hyprwm-aquamarine-655e067/docs/env.md000066400000000000000000000010311506775317200175160ustar00rootroot00000000000000## Environment variables Unless specified otherwise, a variable is enabled if and only if it's set to `1` ### DRM `AQ_DRM_DEVICES` -> Set an explicit list of DRM devices (GPUs) to use. It's a colon-separated list of paths, with the first being the primary. E.g. `/dev/dri/card1:/dev/dri/card0` `AQ_NO_ATOMIC` -> Disables drm atomic modesetting `AQ_MGPU_NO_EXPLICIT` -> Disables explicit syncing on mgpu buffers `AQ_NO_MODIFIERS` -> Disables modifiers for DRM buffers ### Debugging `AQ_TRACE` -> Enables trace (very verbose) logging hyprwm-aquamarine-655e067/flake.lock000066400000000000000000000042631506775317200174220ustar00rootroot00000000000000{ "nodes": { "hyprutils": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1755795954, "narHash": "sha256-9QoDVkjLwjiZDR+y4cMWc/FVudRu5jCIG4rn15Afa9w=", "owner": "hyprwm", "repo": "hyprutils", "rev": "b364dcb7391709acb4492e100fe750ca722992e1", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprutils", "type": "github" } }, "hyprwayland-scanner": { "inputs": { "nixpkgs": [ "nixpkgs" ], "systems": [ "systems" ] }, "locked": { "lastModified": 1751897909, "narHash": "sha256-FnhBENxihITZldThvbO7883PdXC/2dzW4eiNvtoV5Ao=", "owner": "hyprwm", "repo": "hyprwayland-scanner", "rev": "fcca0c61f988a9d092cbb33e906775014c61579d", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprwayland-scanner", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1751792365, "narHash": "sha256-J1kI6oAj25IG4EdVlg2hQz8NZTBNYvIS0l4wpr9KcUo=", "owner": "NixOS", "repo": "nixpkgs", "rev": "1fd8bada0b6117e6c7eb54aad5813023eed37ccb", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { "hyprutils": "hyprutils", "hyprwayland-scanner": "hyprwayland-scanner", "nixpkgs": "nixpkgs", "systems": "systems" } }, "systems": { "locked": { "lastModified": 1689347949, "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", "owner": "nix-systems", "repo": "default-linux", "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", "type": "github" }, "original": { "owner": "nix-systems", "repo": "default-linux", "type": "github" } } }, "root": "root", "version": 7 } hyprwm-aquamarine-655e067/flake.nix000066400000000000000000000041701506775317200172650ustar00rootroot00000000000000{ description = "A very light linux rendering backend library"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; systems.url = "github:nix-systems/default-linux"; hyprutils = { url = "github:hyprwm/hyprutils"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; hyprwayland-scanner = { url = "github:hyprwm/hyprwayland-scanner"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; }; }; outputs = { self, nixpkgs, systems, ... } @ inputs: let inherit (nixpkgs) lib; eachSystem = lib.genAttrs (import systems); pkgsFor = eachSystem (system: import nixpkgs { localSystem.system = system; overlays = with self.overlays; [aquamarine]; }); pkgsCrossFor = eachSystem (system: crossSystem: import nixpkgs { localSystem = system; crossSystem = crossSystem; overlays = with self.overlays; [aquamarine]; }); mkDate = longDate: (lib.concatStringsSep "-" [ (builtins.substring 0 4 longDate) (builtins.substring 4 2 longDate) (builtins.substring 6 2 longDate) ]); version = lib.removeSuffix "\n" (builtins.readFile ./VERSION); in { overlays = { default = self.overlays.aquamarine; aquamarine = lib.composeManyExtensions [ inputs.hyprutils.overlays.default inputs.hyprwayland-scanner.overlays.default (final: prev: { aquamarine = final.callPackage ./nix/default.nix { stdenv = final.gcc15Stdenv; version = version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); }; aquamarine-with-tests = final.aquamarine.override {doCheck = true;}; }) ]; }; packages = eachSystem (system: { default = self.packages.${system}.aquamarine; inherit (pkgsFor.${system}) aquamarine aquamarine-with-tests; aquamarine-cross = (pkgsCrossFor.${system} "aarch64-linux").aquamarine; }); formatter = eachSystem (system: pkgsFor.${system}.alejandra); }; } hyprwm-aquamarine-655e067/include/000077500000000000000000000000001506775317200171045ustar00rootroot00000000000000hyprwm-aquamarine-655e067/include/aquamarine/000077500000000000000000000000001506775317200212275ustar00rootroot00000000000000hyprwm-aquamarine-655e067/include/aquamarine/allocator/000077500000000000000000000000001506775317200232075ustar00rootroot00000000000000hyprwm-aquamarine-655e067/include/aquamarine/allocator/Allocator.hpp000066400000000000000000000027201506775317200256410ustar00rootroot00000000000000#pragma once #include #include "../buffer/Buffer.hpp" #include namespace Aquamarine { class CBackend; class CSwapchain; struct SAllocatorBufferParams { Hyprutils::Math::Vector2D size; uint32_t format = DRM_FORMAT_INVALID; bool scanout = false, cursor = false, multigpu = false; }; enum eAllocatorType { AQ_ALLOCATOR_TYPE_GBM = 0, AQ_ALLOCATOR_TYPE_DRM_DUMB, }; class IAllocator { public: virtual ~IAllocator() = default; virtual Hyprutils::Memory::CSharedPointer acquire(const SAllocatorBufferParams& params, Hyprutils::Memory::CSharedPointer swapchain) = 0; virtual Hyprutils::Memory::CSharedPointer getBackend() = 0; virtual int drmFD() = 0; virtual eAllocatorType type() = 0; virtual void destroyBuffers(); }; }; hyprwm-aquamarine-655e067/include/aquamarine/allocator/DRMDumb.hpp000066400000000000000000000052241506775317200251550ustar00rootroot00000000000000#pragma once #include "Allocator.hpp" namespace Aquamarine { class CDRMDumbAllocator; class CBackend; class CSwapchain; class CDRMDumbBuffer : public IBuffer { public: virtual ~CDRMDumbBuffer(); virtual eBufferCapability caps(); virtual eBufferType type(); virtual void update(const Hyprutils::Math::CRegion& damage); virtual bool isSynchronous(); virtual bool good(); virtual SDMABUFAttrs dmabuf(); virtual std::tuple beginDataPtr(uint32_t flags); virtual void endDataPtr(); private: CDRMDumbBuffer(const SAllocatorBufferParams& params, Hyprutils::Memory::CWeakPointer allocator_, Hyprutils::Memory::CSharedPointer swapchain); Hyprutils::Memory::CWeakPointer allocator; // Hyprutils::Math::Vector2D pixelSize; uint32_t stride = 0, handle = 0; uint64_t bufferLen = 0; uint8_t* data = nullptr; int primeFD = -1; // SDMABUFAttrs attrs{.success = false}; friend class CDRMDumbAllocator; }; class CDRMDumbAllocator : public IAllocator { public: ~CDRMDumbAllocator(); static Hyprutils::Memory::CSharedPointer create(int drmfd_, Hyprutils::Memory::CWeakPointer backend_); virtual Hyprutils::Memory::CSharedPointer acquire(const SAllocatorBufferParams& params, Hyprutils::Memory::CSharedPointer swapchain_); virtual Hyprutils::Memory::CSharedPointer getBackend(); virtual int drmFD(); virtual eAllocatorType type(); // Hyprutils::Memory::CWeakPointer self; private: CDRMDumbAllocator(int fd_, Hyprutils::Memory::CWeakPointer backend_); // a vector purely for tracking (debugging) the buffers and nothing more std::vector> buffers; Hyprutils::Memory::CWeakPointer backend; int drmfd = -1; friend class CDRMDumbBuffer; friend class CDRMRenderer; }; }; hyprwm-aquamarine-655e067/include/aquamarine/allocator/GBM.hpp000066400000000000000000000053041506775317200243270ustar00rootroot00000000000000#pragma once #include "Allocator.hpp" struct gbm_device; struct gbm_bo; namespace Aquamarine { class CGBMAllocator; class CBackend; class CSwapchain; class CGBMBuffer : public IBuffer { public: virtual ~CGBMBuffer(); virtual eBufferCapability caps(); virtual eBufferType type(); virtual void update(const Hyprutils::Math::CRegion& damage); virtual bool isSynchronous(); virtual bool good(); virtual SDMABUFAttrs dmabuf(); virtual std::tuple beginDataPtr(uint32_t flags); virtual void endDataPtr(); private: CGBMBuffer(const SAllocatorBufferParams& params, Hyprutils::Memory::CWeakPointer allocator_, Hyprutils::Memory::CSharedPointer swapchain); Hyprutils::Memory::CWeakPointer allocator; // gbm stuff gbm_bo* bo = nullptr; void* boBuffer = nullptr; void* gboMapping = nullptr; SDMABUFAttrs attrs{.success = false}; friend class CGBMAllocator; }; class CGBMAllocator : public IAllocator { public: ~CGBMAllocator(); static Hyprutils::Memory::CSharedPointer create(int drmfd_, Hyprutils::Memory::CWeakPointer backend_); virtual Hyprutils::Memory::CSharedPointer acquire(const SAllocatorBufferParams& params, Hyprutils::Memory::CSharedPointer swapchain_); virtual Hyprutils::Memory::CSharedPointer getBackend(); virtual int drmFD(); virtual eAllocatorType type(); virtual void destroyBuffers(); // Hyprutils::Memory::CWeakPointer self; private: CGBMAllocator(int fd_, Hyprutils::Memory::CWeakPointer backend_); // a vector purely for tracking (debugging) the buffers and nothing more std::vector> buffers; int fd = -1; Hyprutils::Memory::CWeakPointer backend; // gbm stuff gbm_device* gbmDevice = nullptr; std::string gbmDeviceBackendName = ""; std::string drmName = ""; friend class CGBMBuffer; friend class CDRMRenderer; }; }; hyprwm-aquamarine-655e067/include/aquamarine/allocator/Swapchain.hpp000066400000000000000000000045431506775317200256430ustar00rootroot00000000000000#pragma once #include "Allocator.hpp" namespace Aquamarine { class IBackendImplementation; class IOutput; struct SSwapchainOptions { size_t length = 0; Hyprutils::Math::Vector2D size; uint32_t format = DRM_FORMAT_INVALID; // if you leave this on invalid, the swapchain will choose an appropriate format (and modifier) for you. bool scanout = false, cursor = false /* requires scanout = true */, multigpu = false /* if true, will force linear */; Hyprutils::Memory::CWeakPointer scanoutOutput; }; class CSwapchain { public: static Hyprutils::Memory::CSharedPointer create(Hyprutils::Memory::CSharedPointer allocator_, Hyprutils::Memory::CSharedPointer backendImpl_); bool reconfigure(const SSwapchainOptions& options_); bool contains(Hyprutils::Memory::CSharedPointer buffer); Hyprutils::Memory::CSharedPointer next(int* age); const SSwapchainOptions& currentOptions(); Hyprutils::Memory::CSharedPointer getAllocator(); // rolls the buffers back, marking the last consumed as the next valid. // useful if e.g. a commit fails and we don't wanna write to the previous buffer that is // in use. void rollback(); private: CSwapchain(Hyprutils::Memory::CSharedPointer allocator_, Hyprutils::Memory::CSharedPointer backendImpl_); bool fullReconfigure(const SSwapchainOptions& options_); bool resize(size_t newSize); // Hyprutils::Memory::CWeakPointer self; SSwapchainOptions options; Hyprutils::Memory::CSharedPointer allocator; Hyprutils::Memory::CWeakPointer backendImpl; std::vector> buffers; int lastAcquired = 0; friend class CGBMBuffer; }; }; hyprwm-aquamarine-655e067/include/aquamarine/backend/000077500000000000000000000000001506775317200226165ustar00rootroot00000000000000hyprwm-aquamarine-655e067/include/aquamarine/backend/Backend.hpp000066400000000000000000000172321506775317200246630ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include "../allocator/Allocator.hpp" #include "Misc.hpp" #include "Session.hpp" namespace Aquamarine { class IOutput; class IPointer; class IKeyboard; class ITouch; class ISwitch; class ITablet; class ITabletTool; class ITabletPad; enum eBackendType : uint32_t { AQ_BACKEND_WAYLAND = 0, AQ_BACKEND_DRM, AQ_BACKEND_HEADLESS, AQ_BACKEND_NULL, }; enum eBackendRequestMode : uint32_t { /* Require the provided backend, will error out if it's not available. */ AQ_BACKEND_REQUEST_MANDATORY = 0, /* Start the backend if it's available */ AQ_BACKEND_REQUEST_IF_AVAILABLE, /* If any IF_AVAILABLE backend fails, use this one */ AQ_BACKEND_REQUEST_FALLBACK, }; enum eBackendLogLevel : uint32_t { AQ_LOG_TRACE = 0, AQ_LOG_DEBUG, AQ_LOG_WARNING, AQ_LOG_ERROR, AQ_LOG_CRITICAL, }; struct SBackendImplementationOptions { explicit SBackendImplementationOptions(); eBackendType backendType; eBackendRequestMode backendRequestMode; }; struct SBackendOptions { explicit SBackendOptions(); std::function logFunction; }; struct SPollFD { int fd = -1; std::function onSignal; /* call this when signaled */ }; class IBackendImplementation { public: virtual ~IBackendImplementation() { ; } enum eBackendCapabilities : uint32_t { AQ_BACKEND_CAPABILITY_POINTER = (1 << 0), }; virtual eBackendType type() = 0; virtual bool start() = 0; virtual std::vector> pollFDs() = 0; virtual int drmFD() = 0; virtual bool dispatchEvents() = 0; virtual uint32_t capabilities() = 0; virtual void onReady() = 0; virtual std::vector getRenderFormats() = 0; virtual std::vector getCursorFormats() = 0; virtual bool createOutput(const std::string& name = "") = 0; // "" means auto virtual Hyprutils::Memory::CSharedPointer preferredAllocator() = 0; virtual std::vector getRenderableFormats(); // empty = use getRenderFormats virtual std::vector> getAllocators() = 0; virtual Hyprutils::Memory::CWeakPointer getPrimary() = 0; virtual int drmRenderNodeFD() = 0; }; class CBackend { public: /* Create a backend, with the provided options. May return a single or a multi-backend. */ static Hyprutils::Memory::CSharedPointer create(const std::vector& backends, const SBackendOptions& options); ~CBackend(); /* start the backend. Initializes all the stuff, and will return true on success, false on fail. */ bool start(); void log(eBackendLogLevel level, const std::string& msg); /* Gets all the FDs you have to poll. When any single one fires, call its onPoll */ std::vector> getPollFDs(); /* Checks if the backend has a session - iow if it's a DRM backend */ bool hasSession(); /* Get the primary DRM FD */ int drmFD(); /* Get the render formats the primary backend supports */ std::vector getPrimaryRenderFormats(); /* get a vector of the backend implementations available */ const std::vector>& getImplementations(); /* push an idle event to the queue */ void addIdleEvent(Hyprutils::Memory::CSharedPointer> fn); /* remove an idle event from the queue */ void removeIdleEvent(Hyprutils::Memory::CSharedPointer> pfn); // utils int reopenDRMNode(int drmFD, bool allowRenderNode = true); // called when a new DRM card is hotplugged void onNewGpu(std::string path); struct { Hyprutils::Signal::CSignalT> newOutput; Hyprutils::Signal::CSignalT> newPointer; Hyprutils::Signal::CSignalT> newKeyboard; Hyprutils::Signal::CSignalT> newTouch; Hyprutils::Signal::CSignalT> newSwitch; Hyprutils::Signal::CSignalT> newTablet; Hyprutils::Signal::CSignalT> newTabletTool; Hyprutils::Signal::CSignalT> newTabletPad; Hyprutils::Signal::CSignalT<> pollFDsChanged; } events; Hyprutils::Memory::CSharedPointer primaryAllocator; bool ready = false; Hyprutils::Memory::CSharedPointer session; /* Get the primary DRM RenderNode */ int drmRenderNodeFD(); private: CBackend(); bool terminate = false; std::vector implementationOptions; std::vector> implementations; SBackendOptions options; Hyprutils::Memory::CWeakPointer self; std::vector> sessionFDs; struct { int fd = -1; std::vector>> pending; } idle; void dispatchIdle(); void updateIdleTimer(); // struct { std::condition_variable loopSignal; std::mutex loopMutex; std::atomic shouldProcess = false; std::mutex loopRequestMutex; std::mutex eventLock; } m_sEventLoopInternals; friend class CDRMBackend; }; }; hyprwm-aquamarine-655e067/include/aquamarine/backend/DRM.hpp000066400000000000000000000474111506775317200237600ustar00rootroot00000000000000#pragma once #include "./Backend.hpp" #include "../allocator/Swapchain.hpp" #include "../output/Output.hpp" #include "../input/Input.hpp" #include #include #include #include namespace Aquamarine { class CDRMBackend; class CDRMFB; class CDRMOutput; struct SDRMConnector; class CDRMRenderer; class CDRMDumbAllocator; typedef std::function FIdleCallback; class CDRMBufferAttachment : public IAttachment { public: CDRMBufferAttachment(Hyprutils::Memory::CSharedPointer fb_); virtual ~CDRMBufferAttachment() { ; } Hyprutils::Memory::CSharedPointer fb; }; class CDRMBufferUnimportable : public IAttachment { public: CDRMBufferUnimportable() { ; } virtual ~CDRMBufferUnimportable() { ; } }; class CDRMLease { public: static Hyprutils::Memory::CSharedPointer create(std::vector> outputs); ~CDRMLease(); void terminate(); int leaseFD = -1; uint32_t lesseeID = 0; Hyprutils::Memory::CWeakPointer backend; std::vector> outputs; bool active = true; struct { Hyprutils::Signal::CSignalT<> destroy; } events; private: CDRMLease() = default; void destroy(); friend class CDRMBackend; }; class CDRMFB { public: ~CDRMFB(); static Hyprutils::Memory::CSharedPointer create(Hyprutils::Memory::CSharedPointer buffer_, Hyprutils::Memory::CWeakPointer backend_, bool* isNew = nullptr); void closeHandles(); // drops the buffer from KMS void drop(); // re-imports the buffer into KMS. Essentially drop and import. void reimport(); uint32_t id = 0; Hyprutils::Memory::CWeakPointer buffer; Hyprutils::Memory::CWeakPointer backend; std::array boHandles = {0, 0, 0, 0}; // true if the original buffer is gone and this has been released. bool dead = false; private: CDRMFB(Hyprutils::Memory::CSharedPointer buffer_, Hyprutils::Memory::CWeakPointer backend_); uint32_t submitBuffer(); void import(); bool dropped = false, handlesClosed = false; struct { Hyprutils::Signal::CHyprSignalListener destroyBuffer; } listeners; }; struct SDRMLayer { // we expect the consumers to use double-buffering, so we keep the 2 last FBs around. If any of these goes out of // scope, the DRM FB will be destroyed, but the IBuffer will stay, as long as it's ref'd somewhere. Hyprutils::Memory::CSharedPointer front /* currently displaying */, back /* submitted */, last /* keep just in case */; Hyprutils::Memory::CWeakPointer backend; }; struct SDRMPlane { bool init(drmModePlane* plane); uint64_t type = 0; uint32_t id = 0; uint32_t initialID = 0; Hyprutils::Memory::CSharedPointer front /* currently displaying */, back /* submitted */, last /* keep just in case */; Hyprutils::Memory::CWeakPointer backend; Hyprutils::Memory::CWeakPointer self; std::vector formats; union UDRMPlaneProps { struct { uint32_t type; uint32_t rotation; // Not guaranteed to exist uint32_t in_formats; // Not guaranteed to exist // atomic-modesetting only uint32_t src_x; uint32_t src_y; uint32_t src_w; uint32_t src_h; uint32_t crtc_x; uint32_t crtc_y; uint32_t crtc_w; uint32_t crtc_h; uint32_t fb_id; uint32_t crtc_id; uint32_t fb_damage_clips; uint32_t hotspot_x; uint32_t hotspot_y; uint32_t in_fence_fd; } values; uint32_t props[17] = {0}; }; UDRMPlaneProps props; }; struct SDRMCRTC { uint32_t id = 0; std::vector layers; int32_t refresh = 0; // unused struct { int gammaSize = 0; } legacy; struct { bool ownModeID = false; uint32_t modeID = 0; uint32_t gammaLut = 0; uint32_t ctm = 0; } atomic; Hyprutils::Memory::CSharedPointer primary; Hyprutils::Memory::CSharedPointer cursor; Hyprutils::Memory::CWeakPointer backend; Hyprutils::Memory::CSharedPointer pendingCursor; union UDRMCRTCProps { struct { // None of these are guaranteed to exist uint32_t vrr_enabled; uint32_t gamma_lut; uint32_t gamma_lut_size; uint32_t ctm; uint32_t degamma_lut; uint32_t degamma_lut_size; // atomic-modesetting only uint32_t active; uint32_t mode_id; uint32_t out_fence_ptr; } values; uint32_t props[9] = {0}; }; UDRMCRTCProps props; }; class CDRMOutput : public IOutput { public: virtual ~CDRMOutput(); virtual bool commit(); virtual bool test(); virtual Hyprutils::Memory::CSharedPointer getBackend(); virtual bool setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void moveCursor(const Hyprutils::Math::Vector2D& coord, bool skipSchedule = false); virtual void scheduleFrame(const scheduleFrameReason reason = AQ_SCHEDULE_UNKNOWN); virtual void setCursorVisible(bool visible); virtual Hyprutils::Math::Vector2D cursorPlaneSize(); virtual size_t getGammaSize(); virtual size_t getDeGammaSize(); virtual std::vector getRenderFormats(); int getConnectorID(); Hyprutils::Memory::CWeakPointer self; Hyprutils::Memory::CWeakPointer lease; bool cursorVisible = true; Hyprutils::Math::Vector2D cursorPos; // without hotspot Hyprutils::Math::Vector2D cursorHotspot; bool enabledState = true; // actual enabled state. Should be synced with state->state().enabled after a new frame private: CDRMOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_, Hyprutils::Memory::CSharedPointer connector_); bool commitState(bool onlyTest = false); Hyprutils::Memory::CWeakPointer backend; Hyprutils::Memory::CSharedPointer connector; Hyprutils::Memory::CSharedPointer> frameIdle; struct { Hyprutils::Memory::CSharedPointer swapchain; Hyprutils::Memory::CSharedPointer cursorSwapchain; } mgpu; bool lastCommitNoBuffer = true; friend struct SDRMConnector; friend class CDRMLease; }; struct SDRMPageFlip { Hyprutils::Memory::CWeakPointer connector; }; struct SDRMConnectorCommitData { Hyprutils::Memory::CSharedPointer mainFB, cursorFB; bool modeset = false; bool blocking = false; uint32_t flags = 0; bool test = false; drmModeModeInfo modeInfo; std::optional ctm; std::optional hdrMetadata; struct { uint32_t gammaLut = 0; uint32_t degammaLut = 0; uint32_t fbDamage = 0; uint32_t modeBlob = 0; uint32_t ctmBlob = 0; uint32_t hdrBlob = 0; bool blobbed = false; bool gammad = false; bool degammad = false; bool ctmd = false; bool hdrd = false; // true if hdr blob needs updating or clearing } atomic; void calculateMode(Hyprutils::Memory::CSharedPointer connector); }; struct SDRMConnector { ~SDRMConnector(); bool init(drmModeConnector* connector); void connect(drmModeConnector* connector); void disconnect(); Hyprutils::Memory::CSharedPointer getCurrentCRTC(const drmModeConnector* connector); drmModeModeInfo* getCurrentMode(); IOutput::SParsedEDID parseEDID(std::vector data); bool commitState(SDRMConnectorCommitData& data); void applyCommit(const SDRMConnectorCommitData& data); void rollbackCommit(const SDRMConnectorCommitData& data); void onPresent(); void recheckCRTCProps(); Hyprutils::Memory::CSharedPointer output; Hyprutils::Memory::CWeakPointer backend; Hyprutils::Memory::CWeakPointer self; std::string szName; drmModeConnection status = DRM_MODE_DISCONNECTED; uint32_t id = 0; std::array maxBpcBounds = {0, 0}; Hyprutils::Memory::CSharedPointer crtc; int32_t refresh = 0; uint32_t possibleCrtcs = 0; std::string make, serial, model; bool canDoVrr = false; bool cursorEnabled = false; Hyprutils::Math::Vector2D cursorPos, cursorSize, cursorHotspot; Hyprutils::Memory::CSharedPointer pendingCursorFB; bool isPageFlipPending = false; SDRMPageFlip pendingPageFlip; bool frameEventScheduled = false; // the current state is invalid and won't commit, don't try to modeset. bool commitTainted = false; Hyprutils::Memory::CSharedPointer fallbackMode; struct { bool vrrEnabled = false; } atomic; union UDRMConnectorProps { struct { uint32_t edid; uint32_t dpms; uint32_t link_status; // not guaranteed to exist uint32_t path; uint32_t vrr_capable; // not guaranteed to exist uint32_t subconnector; // not guaranteed to exist uint32_t non_desktop; uint32_t panel_orientation; // not guaranteed to exist uint32_t content_type; // not guaranteed to exist uint32_t max_bpc; // not guaranteed to exist uint32_t Colorspace; // not guaranteed to exist uint32_t hdr_output_metadata; // not guaranteed to exist // atomic-modesetting only uint32_t crtc_id; } values; uint32_t props[13] = {0}; }; UDRMConnectorProps props; union UDRMConnectorColorspace { struct { uint32_t Default; uint32_t BT2020_RGB; uint32_t BT2020_YCC; } values; uint32_t props[3] = {0}; }; UDRMConnectorColorspace colorspace; }; class IDRMImplementation { public: virtual ~IDRMImplementation() = default; virtual bool commit(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data) = 0; virtual bool reset() = 0; // moving a cursor IIRC is almost instant on most hardware so we don't have to wait for a commit. virtual bool moveCursor(Hyprutils::Memory::CSharedPointer connector, bool skipSchedule = false) = 0; }; class CDRMBackend : public IBackendImplementation { public: virtual ~CDRMBackend(); virtual eBackendType type(); virtual bool start(); virtual std::vector> pollFDs(); virtual int drmFD(); virtual bool dispatchEvents(); virtual uint32_t capabilities(); virtual bool setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void onReady(); virtual std::vector getRenderFormats(); virtual std::vector getCursorFormats(); virtual bool createOutput(const std::string& name = ""); virtual Hyprutils::Memory::CSharedPointer preferredAllocator(); virtual std::vector getRenderableFormats(); virtual std::vector> getAllocators(); virtual Hyprutils::Memory::CWeakPointer getPrimary(); Hyprutils::Memory::CWeakPointer self; void log(eBackendLogLevel, const std::string&); bool sessionActive(); int getNonMasterFD(); std::vector idleCallbacks; std::string gpuName; virtual int drmRenderNodeFD(); private: CDRMBackend(Hyprutils::Memory::CSharedPointer backend); static std::vector> attempt(Hyprutils::Memory::CSharedPointer backend); static Hyprutils::Memory::CSharedPointer fromGpu(std::string path, Hyprutils::Memory::CSharedPointer backend, Hyprutils::Memory::CSharedPointer primary); bool registerGPU(Hyprutils::Memory::CSharedPointer gpu_, Hyprutils::Memory::CSharedPointer primary_ = {}); bool checkFeatures(); bool initResources(); bool initMgpu(); bool grabFormats(); bool shouldBlit(); void scanConnectors(); void scanLeases(); void restoreAfterVT(); void recheckOutputs(); void recheckCRTCs(); void buildGlFormats(const std::vector& fmts); Hyprutils::Memory::CSharedPointer gpu; Hyprutils::Memory::CSharedPointer impl; Hyprutils::Memory::CWeakPointer primary; struct { Hyprutils::Memory::CSharedPointer allocator; Hyprutils::Memory::CSharedPointer renderer; // may be null if creation fails } rendererState; Hyprutils::Memory::CWeakPointer backend; std::vector> crtcs; std::vector> planes; std::vector> connectors; std::vector formats; std::vector glFormats; Hyprutils::Memory::CSharedPointer dumbAllocator; bool atomic = false; struct { Hyprutils::Math::Vector2D cursorSize; bool supportsAsyncCommit = false; bool supportsAddFb2Modifiers = false; bool supportsTimelines = false; } drmProps; struct { Hyprutils::Signal::CHyprSignalListener sessionActivate; Hyprutils::Signal::CHyprSignalListener gpuChange; Hyprutils::Signal::CHyprSignalListener gpuRemove; } listeners; friend class CBackend; friend class CDRMFB; friend class CDRMFBAttachment; friend struct SDRMConnector; friend struct SDRMCRTC; friend struct SDRMPlane; friend class CDRMOutput; friend struct SDRMPageFlip; friend class CDRMLegacyImpl; friend class CDRMAtomicImpl; friend class CDRMAtomicRequest; friend class CDRMLease; friend class CGBMBuffer; }; }; hyprwm-aquamarine-655e067/include/aquamarine/backend/Headless.hpp000066400000000000000000000075141506775317200250660ustar00rootroot00000000000000#pragma once #include "./Backend.hpp" #include "../allocator/Swapchain.hpp" #include "../output/Output.hpp" #include namespace Aquamarine { class CBackend; class CHeadlessBackend; class IAllocator; class CHeadlessOutput : public IOutput { public: virtual ~CHeadlessOutput(); virtual bool commit(); virtual bool test(); virtual Hyprutils::Memory::CSharedPointer getBackend(); virtual void scheduleFrame(const scheduleFrameReason reason = AQ_SCHEDULE_UNKNOWN); virtual bool destroy(); virtual std::vector getRenderFormats(); Hyprutils::Memory::CWeakPointer self; private: CHeadlessOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_); Hyprutils::Memory::CWeakPointer backend; Hyprutils::Memory::CSharedPointer> framecb; bool frameScheduled = false; friend class CHeadlessBackend; }; class CHeadlessBackend : public IBackendImplementation { public: virtual ~CHeadlessBackend(); virtual eBackendType type(); virtual bool start(); virtual std::vector> pollFDs(); virtual int drmFD(); virtual bool dispatchEvents(); virtual uint32_t capabilities(); virtual bool setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void onReady(); virtual std::vector getRenderFormats(); virtual std::vector getCursorFormats(); virtual bool createOutput(const std::string& name = ""); virtual Hyprutils::Memory::CSharedPointer preferredAllocator(); virtual std::vector> getAllocators(); virtual Hyprutils::Memory::CWeakPointer getPrimary(); Hyprutils::Memory::CWeakPointer self; virtual int drmRenderNodeFD(); private: CHeadlessBackend(Hyprutils::Memory::CSharedPointer backend_); Hyprutils::Memory::CWeakPointer backend; std::vector> outputs; size_t outputIDCounter = 0; class CTimer { public: std::chrono::steady_clock::time_point when; std::function what; bool expired(); }; struct { int timerfd = -1; std::vector timers; } timers; void dispatchTimers(); void updateTimerFD(); friend class CBackend; friend class CHeadlessOutput; }; }; hyprwm-aquamarine-655e067/include/aquamarine/backend/Misc.hpp000066400000000000000000000005371506775317200242270ustar00rootroot00000000000000#pragma once #include #include namespace Aquamarine { struct SGLFormat { uint32_t drmFormat = 0; uint64_t modifier = 0; bool external = false; }; struct SDRMFormat { uint32_t drmFormat = 0; /* DRM_FORMAT_INVALID */ std::vector modifiers; }; }; hyprwm-aquamarine-655e067/include/aquamarine/backend/Null.hpp000066400000000000000000000043601506775317200242440ustar00rootroot00000000000000#pragma once #include "./Backend.hpp" #include "../allocator/Swapchain.hpp" #include "../output/Output.hpp" #include namespace Aquamarine { class CBackend; class IAllocator; class CNullBackend : public IBackendImplementation { public: virtual ~CNullBackend(); virtual eBackendType type(); virtual bool start(); virtual std::vector> pollFDs(); virtual int drmFD(); virtual bool dispatchEvents(); virtual uint32_t capabilities(); virtual bool setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void onReady(); virtual std::vector getRenderFormats(); virtual std::vector getCursorFormats(); virtual bool createOutput(const std::string& name = ""); virtual Hyprutils::Memory::CSharedPointer preferredAllocator(); virtual std::vector> getAllocators(); virtual Hyprutils::Memory::CWeakPointer getPrimary(); Hyprutils::Memory::CWeakPointer self; virtual int drmRenderNodeFD(); void setFormats(const std::vector& fmts); private: CNullBackend(Hyprutils::Memory::CSharedPointer backend_); Hyprutils::Memory::CWeakPointer backend; std::vector m_formats; friend class CBackend; friend class CHeadlessOutput; }; }; hyprwm-aquamarine-655e067/include/aquamarine/backend/Session.hpp000066400000000000000000000217541506775317200247630ustar00rootroot00000000000000#pragma once #include #include #include #include "../input/Input.hpp" #include struct udev; struct udev_monitor; struct udev_device; struct libseat; struct libinput; struct libinput_event; struct libinput_device; struct libinput_tablet_tool; namespace Aquamarine { class CBackend; class CSession; class CLibinputDevice; struct SPollFD; class CSessionDevice { public: CSessionDevice(Hyprutils::Memory::CSharedPointer session_, const std::string& path_); ~CSessionDevice(); static Hyprutils::Memory::CSharedPointer openIfKMS(Hyprutils::Memory::CSharedPointer session_, const std::string& path_); bool supportsKMS(); void resolveMatchingRenderNode(udev_device* cardDevice); int fd = -1; int deviceID = -1; dev_t dev; std::string path; enum eChangeEventType : uint32_t { AQ_SESSION_EVENT_CHANGE_HOTPLUG = 0, AQ_SESSION_EVENT_CHANGE_LEASE, }; struct SChangeEvent { eChangeEventType type = AQ_SESSION_EVENT_CHANGE_HOTPLUG; struct { uint32_t connectorID = 0, propID = 0; } hotplug; }; struct { Hyprutils::Signal::CSignalT change; Hyprutils::Signal::CSignalT<> remove; } events; int renderNodeFd = -1; private: Hyprutils::Memory::CWeakPointer session; }; class CLibinputKeyboard : public IKeyboard { public: CLibinputKeyboard(Hyprutils::Memory::CSharedPointer dev); virtual ~CLibinputKeyboard() { ; } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName(); virtual void updateLEDs(uint32_t leds); private: Hyprutils::Memory::CWeakPointer device; friend class CLibinputDevice; }; class CLibinputMouse : public IPointer { public: CLibinputMouse(Hyprutils::Memory::CSharedPointer dev); virtual ~CLibinputMouse() { ; } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName(); private: Hyprutils::Memory::CWeakPointer device; friend class CLibinputDevice; }; class CLibinputTouch : public ITouch { public: CLibinputTouch(Hyprutils::Memory::CSharedPointer dev); virtual ~CLibinputTouch() { ; } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName(); private: Hyprutils::Memory::CWeakPointer device; friend class CLibinputDevice; }; class CLibinputSwitch : public ISwitch { public: CLibinputSwitch(Hyprutils::Memory::CSharedPointer dev); virtual ~CLibinputSwitch() { ; } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName(); eSwitchType type = AQ_SWITCH_TYPE_UNKNOWN; bool state = false; private: Hyprutils::Memory::CWeakPointer device; friend class CLibinputDevice; }; class CLibinputTablet : public ITablet { public: CLibinputTablet(Hyprutils::Memory::CSharedPointer dev); virtual ~CLibinputTablet() { ; } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName(); private: Hyprutils::Memory::CWeakPointer device; friend class CLibinputDevice; }; class CLibinputTabletTool : public ITabletTool { public: CLibinputTabletTool(Hyprutils::Memory::CSharedPointer dev, libinput_tablet_tool* tool); virtual ~CLibinputTabletTool(); virtual libinput_device* getLibinputHandle(); virtual const std::string& getName(); private: Hyprutils::Memory::CWeakPointer device; libinput_tablet_tool* libinputTool = nullptr; friend class CLibinputDevice; }; class CLibinputTabletPad : public ITabletPad { public: CLibinputTabletPad(Hyprutils::Memory::CSharedPointer dev); virtual ~CLibinputTabletPad(); virtual libinput_device* getLibinputHandle(); virtual const std::string& getName(); private: Hyprutils::Memory::CWeakPointer device; Hyprutils::Memory::CSharedPointer createGroupFromID(int id); friend class CLibinputDevice; }; class CLibinputDevice { public: CLibinputDevice(libinput_device* device, Hyprutils::Memory::CWeakPointer session_); ~CLibinputDevice(); void init(); libinput_device* device; Hyprutils::Memory::CWeakPointer self; Hyprutils::Memory::CWeakPointer session; std::string name; Hyprutils::Memory::CSharedPointer keyboard; Hyprutils::Memory::CSharedPointer mouse; Hyprutils::Memory::CSharedPointer touch; Hyprutils::Memory::CSharedPointer switchy; // :) Hyprutils::Memory::CSharedPointer tablet; Hyprutils::Memory::CSharedPointer tabletPad; std::vector> tabletTools; Hyprutils::Memory::CSharedPointer toolFrom(libinput_tablet_tool* tool); }; class CSession { public: ~CSession(); static Hyprutils::Memory::CSharedPointer attempt(Hyprutils::Memory::CSharedPointer backend_); bool active = true; // whether the current vt is ours uint32_t vt = 0; // 0 means unsupported std::string seatName; Hyprutils::Memory::CWeakPointer self; std::vector> sessionDevices; std::vector> libinputDevices; udev* udevHandle = nullptr; udev_monitor* udevMonitor = nullptr; libseat* libseatHandle = nullptr; libinput* libinputHandle = nullptr; std::vector> pollFDs(); void dispatchPendingEventsAsync(); bool switchVT(uint32_t vt); void onReady(); struct SAddDrmCardEvent { std::string path; }; struct { Hyprutils::Signal::CSignalT<> changeActive; Hyprutils::Signal::CSignalT addDrmCard; Hyprutils::Signal::CSignalT<> destroy; } events; private: Hyprutils::Memory::CWeakPointer backend; std::vector> polls; void dispatchUdevEvents(); void dispatchLibinputEvents(); void dispatchLibseatEvents(); void handleLibinputEvent(libinput_event* e); void handleLibinputTabletToolAxis(libinput_event* e); friend class CSessionDevice; friend class CLibinputDevice; }; }; hyprwm-aquamarine-655e067/include/aquamarine/backend/Wayland.hpp000066400000000000000000000207751506775317200247410ustar00rootroot00000000000000#pragma once #include "./Backend.hpp" #include "../allocator/Swapchain.hpp" #include "../output/Output.hpp" #include "../input/Input.hpp" #include #include #include #include #include #include namespace Aquamarine { class CBackend; class CWaylandBackend; class CWaylandOutput; class CWaylandPointer; typedef std::function FIdleCallback; class CWaylandBuffer { public: CWaylandBuffer(Hyprutils::Memory::CSharedPointer buffer_, Hyprutils::Memory::CWeakPointer backend_); ~CWaylandBuffer(); bool good(); bool pendingRelease = false; private: struct { Hyprutils::Memory::CSharedPointer buffer; } waylandState; Hyprutils::Memory::CWeakPointer buffer; Hyprutils::Memory::CWeakPointer backend; friend class CWaylandOutput; }; class CWaylandOutput : public IOutput { public: virtual ~CWaylandOutput(); virtual bool commit(); virtual bool test(); virtual Hyprutils::Memory::CSharedPointer getBackend(); virtual bool setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void moveCursor(const Hyprutils::Math::Vector2D& coord, bool skipSchedule = false); virtual void scheduleFrame(const scheduleFrameReason reason = AQ_SCHEDULE_UNKNOWN); virtual Hyprutils::Math::Vector2D cursorPlaneSize(); virtual bool destroy(); virtual std::vector getRenderFormats(); Hyprutils::Memory::CWeakPointer self; private: CWaylandOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_); Hyprutils::Memory::CWeakPointer backend; Hyprutils::Memory::CSharedPointer wlBufferFromBuffer(Hyprutils::Memory::CSharedPointer buffer); void sendFrameAndSetCallback(); void onFrameDone(); void onEnter(Hyprutils::Memory::CSharedPointer pointer, uint32_t serial); // frame loop bool frameScheduledWhileWaiting = false; bool readyForFrameCallback = false; // true after attaching a buffer bool frameScheduled = false; struct { std::vector, Hyprutils::Memory::CSharedPointer>> buffers; } backendState; struct { Hyprutils::Memory::CSharedPointer cursorBuffer; Hyprutils::Memory::CSharedPointer cursorSurface; Hyprutils::Memory::CSharedPointer cursorWlBuffer; uint32_t serial = 0; Hyprutils::Math::Vector2D hotspot; } cursorState; struct { Hyprutils::Memory::CSharedPointer surface; Hyprutils::Memory::CSharedPointer xdgSurface; Hyprutils::Memory::CSharedPointer xdgToplevel; Hyprutils::Memory::CSharedPointer frameCallback; } waylandState; friend class CWaylandBackend; friend class CWaylandPointer; }; class CWaylandKeyboard : public IKeyboard { public: CWaylandKeyboard(Hyprutils::Memory::CSharedPointer keyboard_, Hyprutils::Memory::CWeakPointer backend_); virtual ~CWaylandKeyboard(); virtual const std::string& getName(); Hyprutils::Memory::CSharedPointer keyboard; Hyprutils::Memory::CWeakPointer backend; private: const std::string name = "wl_keyboard"; }; class CWaylandPointer : public IPointer { public: CWaylandPointer(Hyprutils::Memory::CSharedPointer pointer_, Hyprutils::Memory::CWeakPointer backend_); virtual ~CWaylandPointer(); virtual const std::string& getName(); Hyprutils::Memory::CSharedPointer pointer; Hyprutils::Memory::CWeakPointer backend; private: const std::string name = "wl_pointer"; }; class CWaylandBackend : public IBackendImplementation { public: virtual ~CWaylandBackend(); virtual eBackendType type(); virtual bool start(); virtual std::vector> pollFDs(); virtual int drmFD(); virtual bool dispatchEvents(); virtual uint32_t capabilities(); virtual bool setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void onReady(); virtual std::vector getRenderFormats(); virtual std::vector getCursorFormats(); virtual bool createOutput(const std::string& name = ""); virtual Hyprutils::Memory::CSharedPointer preferredAllocator(); virtual std::vector> getAllocators(); virtual Hyprutils::Memory::CWeakPointer getPrimary(); Hyprutils::Memory::CWeakPointer self; virtual int drmRenderNodeFD(); private: CWaylandBackend(Hyprutils::Memory::CSharedPointer backend); void initSeat(); void initShell(); bool initDmabuf(); // Hyprutils::Memory::CWeakPointer backend; std::vector> outputs; std::vector> keyboards; std::vector> pointers; std::vector idleCallbacks; // pointer focus Hyprutils::Memory::CWeakPointer focusedOutput; uint32_t lastEnterSerial = 0; // state size_t lastOutputID = 0; // dmabuf formats std::vector dmabufFormats; struct { wl_display* display = nullptr; // hw-s types Hyprutils::Memory::CSharedPointer registry; Hyprutils::Memory::CSharedPointer seat; Hyprutils::Memory::CSharedPointer shm; Hyprutils::Memory::CSharedPointer xdg; Hyprutils::Memory::CSharedPointer compositor; Hyprutils::Memory::CSharedPointer dmabuf; Hyprutils::Memory::CSharedPointer dmabufFeedback; // control bool dmabufFailed = false; } waylandState; struct { int fd = -1; std::string nodeName = ""; } drmState; friend class CBackend; friend class CWaylandKeyboard; friend class CWaylandPointer; friend class CWaylandOutput; friend class CWaylandBuffer; }; }; hyprwm-aquamarine-655e067/include/aquamarine/backend/drm/000077500000000000000000000000001506775317200234005ustar00rootroot00000000000000hyprwm-aquamarine-655e067/include/aquamarine/backend/drm/Atomic.hpp000066400000000000000000000045571506775317200253400ustar00rootroot00000000000000#pragma once #include "../DRM.hpp" namespace Aquamarine { class CDRMAtomicImpl : public IDRMImplementation { public: CDRMAtomicImpl(Hyprutils::Memory::CSharedPointer backend_); virtual bool commit(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data); virtual bool reset(); virtual bool moveCursor(Hyprutils::Memory::CSharedPointer connector, bool skipSchedule = false); private: bool prepareConnector(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data); Hyprutils::Memory::CWeakPointer backend; friend class CDRMAtomicRequest; }; class CDRMAtomicRequest { public: CDRMAtomicRequest(Hyprutils::Memory::CWeakPointer backend); ~CDRMAtomicRequest(); void setConnector(Hyprutils::Memory::CSharedPointer connector); void addConnector(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data); void addConnectorModeset(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data); void addConnectorCursor(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data); bool commit(uint32_t flagssss); void add(uint32_t id, uint32_t prop, uint64_t val); void planeProps(Hyprutils::Memory::CSharedPointer plane, Hyprutils::Memory::CSharedPointer fb, uint32_t crtc, Hyprutils::Math::Vector2D pos); void planePropsPos(Hyprutils::Memory::CSharedPointer plane, Hyprutils::Math::Vector2D pos); void rollback(SDRMConnectorCommitData& data); void apply(SDRMConnectorCommitData& data); bool failed = false; private: void destroyBlob(uint32_t id); void commitBlob(uint32_t* current, uint32_t next); void rollbackBlob(uint32_t* current, uint32_t next); Hyprutils::Memory::CWeakPointer backend; drmModeAtomicReq* req = nullptr; Hyprutils::Memory::CSharedPointer conn; }; }; hyprwm-aquamarine-655e067/include/aquamarine/backend/drm/Legacy.hpp000066400000000000000000000015671506775317200253260ustar00rootroot00000000000000#pragma once #include "../DRM.hpp" namespace Aquamarine { class CDRMLegacyImpl : public IDRMImplementation { public: CDRMLegacyImpl(Hyprutils::Memory::CSharedPointer backend_); virtual bool commit(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data); virtual bool reset(); virtual bool moveCursor(Hyprutils::Memory::CSharedPointer connector, bool skipSchedule = false); private: bool commitInternal(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data); bool testInternal(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data); Hyprutils::Memory::CWeakPointer backend; }; }; hyprwm-aquamarine-655e067/include/aquamarine/buffer/000077500000000000000000000000001506775317200225005ustar00rootroot00000000000000hyprwm-aquamarine-655e067/include/aquamarine/buffer/Buffer.hpp000066400000000000000000000054711506775317200244310ustar00rootroot00000000000000#pragma once #include #include #include #include #include "../misc/Attachment.hpp" namespace Aquamarine { enum eBufferCapability : uint32_t { BUFFER_CAPABILITY_NONE = 0, BUFFER_CAPABILITY_DATAPTR = (1 << 0), }; enum eBufferType : uint32_t { BUFFER_TYPE_DMABUF = 0, BUFFER_TYPE_SHM, BUFFER_TYPE_MISC, }; struct SDMABUFAttrs { bool success = false; Hyprutils::Math::Vector2D size; uint32_t format = 0; // fourcc uint64_t modifier = 0; int planes = 1; std::array offsets = {0}; std::array strides = {0}; std::array fds = {-1, -1, -1, -1}; }; struct SSHMAttrs { bool success = false; int fd = 0; uint32_t format = 0; Hyprutils::Math::Vector2D size; int stride = 0; int64_t offset = 0; }; class IBuffer { public: virtual ~IBuffer() { attachments.clear(); }; virtual eBufferCapability caps() = 0; virtual eBufferType type() = 0; virtual void update(const Hyprutils::Math::CRegion& damage) = 0; virtual bool isSynchronous() = 0; // whether the updates to this buffer are synchronous, aka happen over cpu virtual bool good() = 0; virtual SDMABUFAttrs dmabuf(); virtual SSHMAttrs shm(); virtual std::tuple beginDataPtr(uint32_t flags); virtual void endDataPtr(); virtual void sendRelease(); virtual void lock(); virtual void unlock(); virtual bool locked(); Hyprutils::Math::Vector2D size; bool opaque = false; bool lockedByBackend = false; CAttachmentManager attachments; struct { Hyprutils::Signal::CSignalT<> destroy; Hyprutils::Signal::CSignalT<> backendRelease; } events; private: int locks = 0; }; }; hyprwm-aquamarine-655e067/include/aquamarine/input/000077500000000000000000000000001506775317200223665ustar00rootroot00000000000000hyprwm-aquamarine-655e067/include/aquamarine/input/Input.hpp000066400000000000000000000311011506775317200241720ustar00rootroot00000000000000#pragma once #include #include struct libinput_device; namespace Aquamarine { class ITabletTool; class IKeyboard { public: virtual ~IKeyboard() { events.destroy.emit(); } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName() = 0; virtual void updateLEDs(uint32_t leds); struct SKeyEvent { uint32_t timeMs = 0; uint32_t key = 0; bool pressed = false; }; struct SModifiersEvent { uint32_t depressed = 0, latched = 0, locked = 0, group = 0; }; struct { Hyprutils::Signal::CSignalT<> destroy; Hyprutils::Signal::CSignalT key; Hyprutils::Signal::CSignalT modifiers; } events; }; class IPointer { public: virtual ~IPointer() { events.destroy.emit(); } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName() = 0; enum ePointerAxis : uint32_t { AQ_POINTER_AXIS_VERTICAL = 0, AQ_POINTER_AXIS_HORIZONTAL, }; enum ePointerAxisSource : uint32_t { AQ_POINTER_AXIS_SOURCE_WHEEL = 0, AQ_POINTER_AXIS_SOURCE_FINGER, AQ_POINTER_AXIS_SOURCE_CONTINUOUS, AQ_POINTER_AXIS_SOURCE_TILT, }; enum ePointerAxisRelativeDirection : uint32_t { AQ_POINTER_AXIS_RELATIVE_IDENTICAL = 0, AQ_POINTER_AXIS_RELATIVE_INVERTED, }; struct SMoveEvent { uint32_t timeMs = 0; Hyprutils::Math::Vector2D delta, unaccel; }; struct SWarpEvent { uint32_t timeMs = 0; Hyprutils::Math::Vector2D absolute; }; struct SButtonEvent { uint32_t timeMs = 0; uint32_t button = 0; bool pressed = false; }; struct SAxisEvent { uint32_t timeMs = 0; ePointerAxis axis = AQ_POINTER_AXIS_VERTICAL; ePointerAxisSource source = AQ_POINTER_AXIS_SOURCE_WHEEL; ePointerAxisRelativeDirection direction = AQ_POINTER_AXIS_RELATIVE_IDENTICAL; double delta = 0.0, discrete = 0.0; }; struct SSwipeBeginEvent { uint32_t timeMs = 0; uint32_t fingers = 0; }; struct SSwipeUpdateEvent { uint32_t timeMs = 0; uint32_t fingers = 0; Hyprutils::Math::Vector2D delta; }; struct SSwipeEndEvent { uint32_t timeMs = 0; bool cancelled = false; }; struct SPinchBeginEvent { uint32_t timeMs = 0; uint32_t fingers = 0; }; struct SPinchUpdateEvent { uint32_t timeMs = 0; uint32_t fingers = 0; Hyprutils::Math::Vector2D delta; double scale = 1.0, rotation = 0.0; }; struct SPinchEndEvent { uint32_t timeMs = 0; bool cancelled = false; }; struct SHoldBeginEvent { uint32_t timeMs = 0; uint32_t fingers = 0; }; struct SHoldEndEvent { uint32_t timeMs = 0; bool cancelled = false; }; struct { Hyprutils::Signal::CSignalT<> destroy; Hyprutils::Signal::CSignalT move; Hyprutils::Signal::CSignalT warp; Hyprutils::Signal::CSignalT button; Hyprutils::Signal::CSignalT axis; Hyprutils::Signal::CSignalT<> frame; Hyprutils::Signal::CSignalT swipeBegin; Hyprutils::Signal::CSignalT swipeUpdate; Hyprutils::Signal::CSignalT swipeEnd; Hyprutils::Signal::CSignalT pinchBegin; Hyprutils::Signal::CSignalT pinchUpdate; Hyprutils::Signal::CSignalT pinchEnd; Hyprutils::Signal::CSignalT holdBegin; Hyprutils::Signal::CSignalT holdEnd; } events; }; class ITouch { public: virtual ~ITouch() { events.destroy.emit(); } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName() = 0; Hyprutils::Math::Vector2D physicalSize; // in mm, 0,0 if unknown struct SDownEvent { uint32_t timeMs = 0; int32_t touchID = 0; Hyprutils::Math::Vector2D pos; }; struct SUpEvent { uint32_t timeMs = 0; int32_t touchID = 0; }; struct SMotionEvent { uint32_t timeMs = 0; int32_t touchID = 0; Hyprutils::Math::Vector2D pos; }; struct SCancelEvent { uint32_t timeMs = 0; int32_t touchID = 0; }; struct { Hyprutils::Signal::CSignalT<> destroy; Hyprutils::Signal::CSignalT move; Hyprutils::Signal::CSignalT down; Hyprutils::Signal::CSignalT up; Hyprutils::Signal::CSignalT cancel; Hyprutils::Signal::CSignalT<> frame; } events; }; class ISwitch { public: virtual ~ISwitch() { events.destroy.emit(); } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName() = 0; enum eSwitchType : uint32_t { AQ_SWITCH_TYPE_UNKNOWN = 0, AQ_SWITCH_TYPE_LID, AQ_SWITCH_TYPE_TABLET_MODE, }; struct SFireEvent { uint32_t timeMs = 0; eSwitchType type = AQ_SWITCH_TYPE_UNKNOWN; bool enable = false; }; struct { Hyprutils::Signal::CSignalT<> destroy; Hyprutils::Signal::CSignalT fire; } events; }; enum eTabletToolAxes : uint32_t { AQ_TABLET_TOOL_AXIS_X = (1 << 0), AQ_TABLET_TOOL_AXIS_Y = (1 << 1), AQ_TABLET_TOOL_AXIS_DISTANCE = (1 << 2), AQ_TABLET_TOOL_AXIS_PRESSURE = (1 << 3), AQ_TABLET_TOOL_AXIS_TILT_X = (1 << 4), AQ_TABLET_TOOL_AXIS_TILT_Y = (1 << 5), AQ_TABLET_TOOL_AXIS_ROTATION = (1 << 6), AQ_TABLET_TOOL_AXIS_SLIDER = (1 << 7), AQ_TABLET_TOOL_AXIS_WHEEL = (1 << 8), }; class ITablet { public: virtual ~ITablet() { events.destroy.emit(); } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName() = 0; uint16_t usbVendorID = 0, usbProductID = 0; Hyprutils::Math::Vector2D physicalSize; // mm std::vector paths; struct SAxisEvent { Hyprutils::Memory::CSharedPointer tool; uint32_t timeMs = 0, updatedAxes = 0; Hyprutils::Math::Vector2D absolute; Hyprutils::Math::Vector2D delta; Hyprutils::Math::Vector2D tilt; double pressure = 0.0, distance = 0.0, rotation = 0.0, slider = 0.0, wheelDelta = 0.0; }; struct SProximityEvent { Hyprutils::Memory::CSharedPointer tool; uint32_t timeMs = 0; Hyprutils::Math::Vector2D absolute; bool in = false; }; struct STipEvent { Hyprutils::Memory::CSharedPointer tool; uint32_t timeMs = 0; Hyprutils::Math::Vector2D absolute; bool down = false; }; struct SButtonEvent { Hyprutils::Memory::CSharedPointer tool; uint32_t timeMs = 0, button = 0; bool down = false; }; struct { Hyprutils::Signal::CSignalT axis; Hyprutils::Signal::CSignalT proximity; Hyprutils::Signal::CSignalT tip; Hyprutils::Signal::CSignalT button; Hyprutils::Signal::CSignalT<> destroy; } events; }; class ITabletTool { public: virtual ~ITabletTool() { events.destroy.emit(); } virtual libinput_device* getLibinputHandle(); virtual const std::string& getName() = 0; enum eTabletToolType : uint32_t { AQ_TABLET_TOOL_TYPE_INVALID = 0, AQ_TABLET_TOOL_TYPE_PEN, AQ_TABLET_TOOL_TYPE_ERASER, AQ_TABLET_TOOL_TYPE_BRUSH, AQ_TABLET_TOOL_TYPE_PENCIL, AQ_TABLET_TOOL_TYPE_AIRBRUSH, AQ_TABLET_TOOL_TYPE_MOUSE, AQ_TABLET_TOOL_TYPE_LENS, AQ_TABLET_TOOL_TYPE_TOTEM, }; eTabletToolType type = AQ_TABLET_TOOL_TYPE_INVALID; uint64_t serial = 0, id = 0; enum eTabletToolCapabilities : uint32_t { AQ_TABLET_TOOL_CAPABILITY_TILT = (1 << 0), AQ_TABLET_TOOL_CAPABILITY_PRESSURE = (1 << 1), AQ_TABLET_TOOL_CAPABILITY_DISTANCE = (1 << 2), AQ_TABLET_TOOL_CAPABILITY_ROTATION = (1 << 3), AQ_TABLET_TOOL_CAPABILITY_SLIDER = (1 << 4), AQ_TABLET_TOOL_CAPABILITY_WHEEL = (1 << 5), }; uint32_t capabilities = 0; // enum eTabletToolCapabilities struct { Hyprutils::Signal::CSignalT<> destroy; } events; }; class ITabletPad { public: virtual ~ITabletPad() { events.destroy.emit(); } struct STabletPadGroup { std::vector buttons, strips, rings; uint16_t modes = 0; }; virtual libinput_device* getLibinputHandle(); virtual const std::string& getName() = 0; uint16_t buttons = 0, rings = 0, strips = 0; std::vector paths; std::vector> groups; // struct SButtonEvent { uint32_t timeMs = 0, button = 0; bool down = false; uint16_t mode = 0, group = 0; }; enum eTabletPadRingSource : uint16_t { AQ_TABLET_PAD_RING_SOURCE_UNKNOWN = 0, AQ_TABLET_PAD_RING_SOURCE_FINGER, }; enum eTabletPadStripSource : uint16_t { AQ_TABLET_PAD_STRIP_SOURCE_UNKNOWN = 0, AQ_TABLET_PAD_STRIP_SOURCE_FINGER, }; struct SRingEvent { uint32_t timeMs = 0; eTabletPadRingSource source = AQ_TABLET_PAD_RING_SOURCE_UNKNOWN; uint16_t ring = 0; double pos = 0.0; uint16_t mode = 0; }; struct SStripEvent { uint32_t timeMs = 0; eTabletPadStripSource source = AQ_TABLET_PAD_STRIP_SOURCE_UNKNOWN; uint16_t strip = 0; double pos = 0.0; uint16_t mode = 0; }; struct { Hyprutils::Signal::CSignalT<> destroy; Hyprutils::Signal::CSignalT button; Hyprutils::Signal::CSignalT ring; Hyprutils::Signal::CSignalT strip; Hyprutils::Signal::CSignalT<> attach; } events; }; } hyprwm-aquamarine-655e067/include/aquamarine/misc/000077500000000000000000000000001506775317200221625ustar00rootroot00000000000000hyprwm-aquamarine-655e067/include/aquamarine/misc/Attachment.hpp000066400000000000000000000033151506775317200247650ustar00rootroot00000000000000#pragma once #include #include #include #include namespace Aquamarine { class IAttachment { public: virtual ~IAttachment() { ; } }; template concept AttachmentConcept = std::is_base_of_v; // CAttachmentManager is a registry for arbitrary attachment types. // Any type implementing IAttachment can be added, retrieved, and removed from the registry. // However, only one attachment of a given type is permitted. class CAttachmentManager { public: template bool has() const { return attachments.contains(typeid(T)); } template Hyprutils::Memory::CSharedPointer get() const { auto it = attachments.find(typeid(T)); if (it == attachments.end()) return nullptr; // Reinterpret SP into SP. // This is safe because we looked up this attachment by typeid(T), // so it must be an SP. return Hyprutils::Memory::reinterpretPointerCast(it->second); } // Also removes the previous attachment of the same type if one exists void add(Hyprutils::Memory::CSharedPointer attachment); void remove(Hyprutils::Memory::CSharedPointer attachment); template void removeByType() { attachments.erase(typeid(T)); } void clear(); private: std::unordered_map> attachments; }; }; hyprwm-aquamarine-655e067/include/aquamarine/output/000077500000000000000000000000001506775317200225675ustar00rootroot00000000000000hyprwm-aquamarine-655e067/include/aquamarine/output/Output.hpp000066400000000000000000000245551506775317200246130ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include "../allocator/Swapchain.hpp" #include "../buffer/Buffer.hpp" #include "../backend/Misc.hpp" namespace Aquamarine { class IBackendImplementation; struct SOutputMode { Hyprutils::Math::Vector2D pixelSize; unsigned int refreshRate = 0 /* in mHz */; bool preferred = false; std::optional modeInfo; // if this is a drm mode, this will be populated. }; enum eOutputPresentationMode : uint32_t { AQ_OUTPUT_PRESENTATION_VSYNC = 0, AQ_OUTPUT_PRESENTATION_IMMEDIATE, // likely tearing }; enum eSubpixelMode : uint32_t { AQ_SUBPIXEL_UNKNOWN = 0, AQ_SUBPIXEL_NONE, AQ_SUBPIXEL_HORIZONTAL_RGB, AQ_SUBPIXEL_HORIZONTAL_BGR, AQ_SUBPIXEL_VERTICAL_RGB, AQ_SUBPIXEL_VERTICAL_BGR, }; class IOutput; class COutputState { public: enum eOutputStateProperties : uint32_t { AQ_OUTPUT_STATE_DAMAGE = (1 << 0), AQ_OUTPUT_STATE_ENABLED = (1 << 1), AQ_OUTPUT_STATE_ADAPTIVE_SYNC = (1 << 2), AQ_OUTPUT_STATE_PRESENTATION_MODE = (1 << 3), AQ_OUTPUT_STATE_GAMMA_LUT = (1 << 4), AQ_OUTPUT_STATE_MODE = (1 << 5), AQ_OUTPUT_STATE_FORMAT = (1 << 6), AQ_OUTPUT_STATE_BUFFER = (1 << 7), AQ_OUTPUT_STATE_EXPLICIT_IN_FENCE = (1 << 8), AQ_OUTPUT_STATE_EXPLICIT_OUT_FENCE = (1 << 9), AQ_OUTPUT_STATE_CTM = (1 << 10), AQ_OUTPUT_STATE_HDR = (1 << 11), AQ_OUTPUT_STATE_DEGAMMA_LUT = (1 << 12), AQ_OUTPUT_STATE_WCG = (1 << 13), AQ_OUTPUT_STATE_CURSOR_SHAPE = (1 << 14), AQ_OUTPUT_STATE_CURSOR_POS = (1 << 15), }; struct SInternalState { uint32_t committed = 0; // enum eOutputStateProperties Hyprutils::Math::CRegion damage; bool enabled = false; bool adaptiveSync = false; eOutputPresentationMode presentationMode = AQ_OUTPUT_PRESENTATION_VSYNC; std::vector gammaLut; // Gamma lut in the format [r,g,b]+ std::vector degammaLut; // Gamma lut in the format [r,g,b]+ Hyprutils::Math::Vector2D lastModeSize; Hyprutils::Memory::CWeakPointer mode; Hyprutils::Memory::CSharedPointer customMode; uint32_t drmFormat = DRM_FORMAT_INVALID; Hyprutils::Memory::CSharedPointer buffer; int32_t explicitInFence = -1, explicitOutFence = -1; Hyprutils::Math::Mat3x3 ctm; bool wideColorGamut = false; hdr_output_metadata hdrMetadata; uint16_t contentType = DRM_MODE_CONTENT_TYPE_GRAPHICS; }; const SInternalState& state(); void addDamage(const Hyprutils::Math::CRegion& region); void clearDamage(); void setEnabled(bool enabled); void setAdaptiveSync(bool enabled); void setPresentationMode(eOutputPresentationMode mode); void setGammaLut(const std::vector& lut); void setDeGammaLut(const std::vector& lut); void setMode(Hyprutils::Memory::CSharedPointer mode); void setCustomMode(Hyprutils::Memory::CSharedPointer mode); void setFormat(uint32_t drmFormat); void setBuffer(Hyprutils::Memory::CSharedPointer buffer); void setExplicitInFence(int32_t fenceFD); // -1 removes void enableExplicitOutFenceForNextCommit(); void resetExplicitFences(); void setCTM(const Hyprutils::Math::Mat3x3& ctm); void setWideColorGamut(bool wcg); void setHDRMetadata(const hdr_output_metadata& metadata); void setContentType(const uint16_t drmContentType); private: SInternalState internalState; void onCommit(); // clears a few props like damage and committed. friend class IOutput; friend class CWaylandOutput; friend class CDRMOutput; friend class CHeadlessOutput; }; class IOutput { public: virtual ~IOutput(); enum scheduleFrameReason : uint32_t { AQ_SCHEDULE_UNKNOWN = 0, AQ_SCHEDULE_NEW_CONNECTOR, AQ_SCHEDULE_CURSOR_VISIBLE, AQ_SCHEDULE_CURSOR_SHAPE, AQ_SCHEDULE_CURSOR_MOVE, AQ_SCHEDULE_CLIENT_UNKNOWN, AQ_SCHEDULE_DAMAGE, AQ_SCHEDULE_NEW_MONITOR, AQ_SCHEDULE_RENDER_MONITOR, AQ_SCHEDULE_NEEDS_FRAME, AQ_SCHEDULE_ANIMATION, AQ_SCHEDULE_ANIMATION_DAMAGE, }; struct SHDRMetadata { float desiredContentMaxLuminance = 0; float desiredMaxFrameAverageLuminance = 0; float desiredContentMinLuminance = 0; bool supportsPQ = false; }; struct xy { double x = 0; double y = 0; }; struct SChromaticityCoords { xy red; xy green; xy blue; xy white; }; struct SParsedEDID { std::string make, serial, model; std::optional hdrMetadata; std::optional chromaticityCoords; bool supportsBT2020 = false; }; virtual bool commit() = 0; virtual bool test() = 0; virtual Hyprutils::Memory::CSharedPointer getBackend() = 0; virtual std::vector getRenderFormats() = 0; virtual Hyprutils::Memory::CSharedPointer preferredMode(); virtual bool setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void moveCursor(const Hyprutils::Math::Vector2D& coord, bool skipSchedule = false); // includes the hotspot virtual void setCursorVisible(bool visible); // moving the cursor will make it visible again without this util virtual Hyprutils::Math::Vector2D cursorPlaneSize(); // -1, -1 means no set size, 0, 0 means error virtual void scheduleFrame(const scheduleFrameReason reason = AQ_SCHEDULE_UNKNOWN); virtual size_t getGammaSize(); virtual size_t getDeGammaSize(); virtual bool destroy(); // not all backends allow this!!! std::string name, description, make, model, serial; SParsedEDID parsedEDID; Hyprutils::Math::Vector2D physicalSize; bool enabled = false; bool nonDesktop = false; eSubpixelMode subpixel = AQ_SUBPIXEL_NONE; bool vrrCapable = false, vrrActive = false; bool needsFrame = false; bool supportsExplicit = false; // std::vector> modes; Hyprutils::Memory::CSharedPointer state = Hyprutils::Memory::makeShared(); Hyprutils::Memory::CSharedPointer swapchain; // enum eOutputPresentFlags : uint32_t { AQ_OUTPUT_PRESENT_VSYNC = (1 << 0), AQ_OUTPUT_PRESENT_HW_CLOCK = (1 << 1), AQ_OUTPUT_PRESENT_HW_COMPLETION = (1 << 2), AQ_OUTPUT_PRESENT_ZEROCOPY = (1 << 3), }; struct SStateEvent { Hyprutils::Math::Vector2D size; // if {0,0}, means it needs a reconfigure. }; struct SPresentEvent { bool presented = true; timespec* when = nullptr; unsigned int seq = 0; int refresh = 0; uint32_t flags = 0; }; struct { Hyprutils::Signal::CSignalT<> destroy; Hyprutils::Signal::CSignalT<> frame; Hyprutils::Signal::CSignalT<> needsFrame; Hyprutils::Signal::CSignalT present; Hyprutils::Signal::CSignalT<> commit; Hyprutils::Signal::CSignalT state; } events; }; } hyprwm-aquamarine-655e067/nix/000077500000000000000000000000001506775317200162575ustar00rootroot00000000000000hyprwm-aquamarine-655e067/nix/default.nix000066400000000000000000000030601506775317200204220ustar00rootroot00000000000000{ lib, stdenv, stdenvAdapters, cmake, hwdata, hyprutils, hyprwayland-scanner, libdisplay-info, libdrm, libffi, libGL, libinput, libgbm, pixman, pkg-config, seatd, udev, wayland, wayland-protocols, wayland-scanner, version ? "git", doCheck ? false, debug ? false, # whether to use the mold linker # disable this for older machines without SSE4_2 and AVX2 support withMold ? true, }: let inherit (builtins) foldl'; inherit (lib.lists) flatten; adapters = flatten [ (lib.optional withMold stdenvAdapters.useMoldLinker) (lib.optional debug stdenvAdapters.keepDebugInfo) ]; customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters; in customStdenv.mkDerivation { pname = "aquamarine"; inherit version doCheck; src = ../.; strictDeps = true; depsBuildBuild = [ pkg-config ]; nativeBuildInputs = [ cmake hyprwayland-scanner pkg-config ]; buildInputs = [ hwdata (hyprutils.override {inherit withMold;}) libdisplay-info libdrm libffi libGL libinput libgbm pixman seatd udev wayland wayland-protocols wayland-scanner ]; outputs = ["out" "dev"]; cmakeBuildType = if debug then "Debug" else "RelWithDebInfo"; meta = { homepage = "https://github.com/hyprwm/aquamarine"; description = "A very light linux rendering backend library"; license = lib.licenses.bsd3; platforms = lib.platforms.linux; }; } hyprwm-aquamarine-655e067/protocols/000077500000000000000000000000001506775317200175055ustar00rootroot00000000000000hyprwm-aquamarine-655e067/protocols/.gitkeep000066400000000000000000000000001506775317200211240ustar00rootroot00000000000000hyprwm-aquamarine-655e067/src/000077500000000000000000000000001506775317200162505ustar00rootroot00000000000000hyprwm-aquamarine-655e067/src/allocator/000077500000000000000000000000001506775317200202305ustar00rootroot00000000000000hyprwm-aquamarine-655e067/src/allocator/Allocator.cpp000066400000000000000000000001401506775317200226470ustar00rootroot00000000000000#include void Aquamarine::IAllocator::destroyBuffers() {} hyprwm-aquamarine-655e067/src/allocator/DRMDumb.cpp000066400000000000000000000117341506775317200221740ustar00rootroot00000000000000#include #include #include #include #include "FormatUtils.hpp" #include "Shared.hpp" #include #include #include #include #include #include #include "../backend/drm/Renderer.hpp" using namespace Aquamarine; using namespace Hyprutils::Memory; #define SP CSharedPointer #define WP CWeakPointer Aquamarine::CDRMDumbBuffer::CDRMDumbBuffer(const SAllocatorBufferParams& params, Hyprutils::Memory::CWeakPointer allocator_, Hyprutils::Memory::CSharedPointer swapchain) : allocator(allocator_) { attrs.format = params.format; if (int ret = drmModeCreateDumbBuffer(allocator->drmFD(), params.size.x, params.size.y, 32, 0, &handle, &stride, &bufferLen); ret < 0) { allocator->backend->log(AQ_LOG_ERROR, std::format("failed to create a drm_dumb buffer: {}", strerror(-ret))); return; } pixelSize = {(double)params.size.x, (double)params.size.y}; attrs.size = pixelSize; attrs.strides.at(0) = stride; attrs.planes = 1; size = pixelSize; uint64_t offset = 0; if (int ret = drmModeMapDumbBuffer(allocator->drmFD(), handle, &offset); ret < 0) { allocator->backend->log(AQ_LOG_ERROR, std::format("failed to map a drm_dumb buffer: {}", strerror(-ret))); return; } data = (uint8_t*)mmap(nullptr, bufferLen, PROT_READ | PROT_WRITE, MAP_SHARED, allocator->drmFD(), offset); if (data == MAP_FAILED) { allocator->backend->log(AQ_LOG_ERROR, "failed to mmap a drm_dumb buffer"); return; } // set the entire buffer so we dont get garbage memset(data, 0xFF, bufferLen); if (int ret = drmPrimeHandleToFD(allocator->drmFD(), handle, DRM_CLOEXEC, &primeFD); ret < 0) { allocator->backend->log(AQ_LOG_ERROR, std::format("failed to map a drm_dumb buffer: {}", strerror(-ret))); return; } attrs.fds.at(0) = primeFD; attrs.success = true; allocator->backend->log(AQ_LOG_DEBUG, std::format("DRM Dumb: Allocated a new buffer with primeFD {}, size {} and format {}", primeFD, attrs.size, fourccToName(attrs.format))); } Aquamarine::CDRMDumbBuffer::~CDRMDumbBuffer() { events.destroy.emit(); TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("DRM Dumb: dropping buffer {}", primeFD))); if (handle == 0) return; if (data) munmap(data, bufferLen); drmModeDestroyDumbBuffer(allocator->drmFD(), handle); } eBufferCapability Aquamarine::CDRMDumbBuffer::caps() { return eBufferCapability::BUFFER_CAPABILITY_DATAPTR; } eBufferType Aquamarine::CDRMDumbBuffer::type() { return eBufferType::BUFFER_TYPE_DMABUF; } void Aquamarine::CDRMDumbBuffer::update(const Hyprutils::Math::CRegion& damage) { ; // nothing to do } bool Aquamarine::CDRMDumbBuffer::isSynchronous() { return true; } bool Aquamarine::CDRMDumbBuffer::good() { return attrs.success && data; } SDMABUFAttrs Aquamarine::CDRMDumbBuffer::dmabuf() { return attrs; } std::tuple Aquamarine::CDRMDumbBuffer::beginDataPtr(uint32_t flags) { return {data, attrs.format, bufferLen}; } void Aquamarine::CDRMDumbBuffer::endDataPtr() { ; // nothing to do } Aquamarine::CDRMDumbAllocator::~CDRMDumbAllocator() { ; // nothing to do } SP Aquamarine::CDRMDumbAllocator::create(int drmfd_, Hyprutils::Memory::CWeakPointer backend_) { if (drmGetNodeTypeFromFd(drmfd_) != DRM_NODE_PRIMARY) { backend_->log(AQ_LOG_ERROR, "DRM Dumb: Cannot create allocator when drmfd is not the primary node"); return nullptr; } uint64_t hasDumb = 0; if (drmGetCap(drmfd_, DRM_CAP_DUMB_BUFFER, &hasDumb) < 0) { backend_->log(AQ_LOG_ERROR, "DRM Dumb: Failed to query hasDumb"); return nullptr; } if (!hasDumb) { backend_->log(AQ_LOG_ERROR, "DRM Dumb: hasDumb is false, gpu driver doesn't support dumb buffers!"); return nullptr; } auto a = SP(new CDRMDumbAllocator(drmfd_, backend_)); a->self = a; backend_->log(AQ_LOG_DEBUG, "DRM Dumb: created a dumb allocator"); return a; } SP Aquamarine::CDRMDumbAllocator::acquire(const SAllocatorBufferParams& params, SP swapchain_) { auto buf = SP(new CDRMDumbBuffer(params, self, swapchain_)); if (!buf->good()) return nullptr; return buf; } SP Aquamarine::CDRMDumbAllocator::getBackend() { return backend.lock(); } int Aquamarine::CDRMDumbAllocator::drmFD() { return drmfd; } eAllocatorType Aquamarine::CDRMDumbAllocator::type() { return eAllocatorType::AQ_ALLOCATOR_TYPE_DRM_DUMB; } Aquamarine::CDRMDumbAllocator::CDRMDumbAllocator(int fd_, Hyprutils::Memory::CWeakPointer backend_) : backend(backend_), drmfd(fd_) { ; // nothing to do } hyprwm-aquamarine-655e067/src/allocator/GBM.cpp000066400000000000000000000364111506775317200213460ustar00rootroot00000000000000#include #include #include #include #include #include "FormatUtils.hpp" #include "Shared.hpp" #include #include #include #include "../backend/drm/Renderer.hpp" using namespace Aquamarine; using namespace Hyprutils::Memory; #define SP CSharedPointer static SDRMFormat guessFormatFrom(std::vector formats, bool cursor, bool scanout) { if (formats.empty()) return SDRMFormat{}; if (!cursor) { /* Try to find 10bpp formats first, as they offer better color precision. For cursors, don't, as these almost never support that. */ if (!scanout) { if (auto it = std::ranges::find_if(formats, [](const auto& f) { return f.drmFormat == DRM_FORMAT_ARGB2101010 || f.drmFormat == DRM_FORMAT_ABGR2101010; }); it != formats.end()) return *it; } if (auto it = std::ranges::find_if(formats, [](const auto& f) { return f.drmFormat == DRM_FORMAT_XRGB2101010 || f.drmFormat == DRM_FORMAT_XBGR2101010; }); it != formats.end()) return *it; } if (!scanout || cursor /* don't set opaque for cursor plane */) { if (auto it = std::ranges::find_if(formats, [](const auto& f) { return f.drmFormat == DRM_FORMAT_ARGB8888 || f.drmFormat == DRM_FORMAT_ABGR8888; }); it != formats.end()) return *it; } if (auto it = std::ranges::find_if(formats, [](const auto& f) { return f.drmFormat == DRM_FORMAT_XRGB8888 || f.drmFormat == DRM_FORMAT_XBGR8888; }); it != formats.end()) return *it; for (auto const& f : formats) { auto name = fourccToName(f.drmFormat); /* 10 bpp RGB */ if (name.contains("30")) return f; } for (auto const& f : formats) { auto name = fourccToName(f.drmFormat); /* 8 bpp RGB */ if (name.contains("24")) return f; } return formats.at(0); } Aquamarine::CGBMBuffer::CGBMBuffer(const SAllocatorBufferParams& params, Hyprutils::Memory::CWeakPointer allocator_, Hyprutils::Memory::CSharedPointer swapchain) : allocator(allocator_) { if (!allocator) return; attrs.size = params.size; attrs.format = params.format; size = attrs.size; const bool CURSOR = params.cursor && params.scanout; const bool MULTIGPU = params.multigpu && params.scanout; const bool EXPLICIT_SCANOUT = params.scanout && swapchain->currentOptions().scanoutOutput && !params.multigpu; TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: Allocating a buffer: size {}, format {}, cursor: {}, multigpu: {}, scanout: {}", attrs.size, fourccToName(attrs.format), CURSOR, MULTIGPU, params.scanout))); if (EXPLICIT_SCANOUT) TRACE(allocator->backend->log( AQ_LOG_TRACE, std::format("GBM: Explicit scanout output, output has {} explicit formats", swapchain->currentOptions().scanoutOutput->getRenderFormats().size()))); const auto FORMATS = CURSOR ? swapchain->backendImpl->getCursorFormats() : (EXPLICIT_SCANOUT ? swapchain->currentOptions().scanoutOutput->getRenderFormats() : swapchain->backendImpl->getRenderFormats()); const auto RENDERABLE = swapchain->backendImpl->getRenderableFormats(); TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: Available formats: {}", FORMATS.size()))); std::vector explicitModifiers; if (attrs.format == DRM_FORMAT_INVALID) { attrs.format = guessFormatFrom(FORMATS, CURSOR, params.scanout).drmFormat; if (attrs.format != DRM_FORMAT_INVALID) allocator->backend->log(AQ_LOG_DEBUG, std::format("GBM: Automatically selected format {} for new GBM buffer", fourccToName(attrs.format))); } if (attrs.format == DRM_FORMAT_INVALID) { allocator->backend->log(AQ_LOG_ERROR, "GBM: Failed to allocate a GBM buffer: no format found"); return; } bool foundFormat = false; // check if we can use modifiers. If the requested support has any explicit modifier // supported by the primary backend, we can. for (auto const& f : FORMATS) { if (f.drmFormat != attrs.format) continue; foundFormat = true; for (auto const& m : f.modifiers) { if (m == DRM_FORMAT_MOD_INVALID) continue; if (!RENDERABLE.empty()) { TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: Renderable has {} formats, clipping", RENDERABLE.size()))); if (params.scanout && !CURSOR && !MULTIGPU) { // regular scanout plane, check if the format is renderable auto rformat = std::ranges::find_if(RENDERABLE, [f](const auto& e) { return e.drmFormat == f.drmFormat; }); if (rformat == RENDERABLE.end()) { TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: Dropping format {} as it's not renderable", fourccToName(f.drmFormat)))); break; } if (std::find(rformat->modifiers.begin(), rformat->modifiers.end(), m) == rformat->modifiers.end()) { TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: Dropping modifier 0x{:x} as it's not renderable", m))); continue; } } } explicitModifiers.push_back(m); } } if (!foundFormat) { allocator->backend->log(AQ_LOG_ERROR, std::format("GBM: Failed to allocate a GBM buffer: format {} isn't supported by primary backend", fourccToName(attrs.format))); bo = nullptr; return; } static const auto forceLinearBlit = !envExplicitlyDisabled("AQ_FORCE_LINEAR_BLIT"); auto const oldMods = explicitModifiers; // used in FORCE_LINEAR_BLIT case. if (MULTIGPU && !forceLinearBlit) { // Try to use the linear format if available for cross-GPU compatibility. // However, Nvidia doesn't support linear, so this is a best-effort basis. for (auto const& f : FORMATS) { if (f.drmFormat == DRM_FORMAT_MOD_LINEAR) { allocator->backend->log(AQ_LOG_DEBUG, "GBM: Buffer is marked as multigpu, using linear format"); explicitModifiers = {DRM_FORMAT_MOD_LINEAR}; break; } } } else if (MULTIGPU && forceLinearBlit) { // FIXME: Nvidia cannot render to linear buffers. What do? // it seems it can import them, but not create? or is it nvidia <-> nvidia thats the trouble? // without this blitting on laptops intel/amd <-> nvidia makes eglCreateImageKHR error and // fallback to slow cpu copying. allocator->backend->log(AQ_LOG_DEBUG, "GBM: Buffer is marked as multigpu, forcing linear"); explicitModifiers = {DRM_FORMAT_MOD_LINEAR}; } uint32_t flags = GBM_BO_USE_RENDERING; if (params.scanout && !MULTIGPU) flags |= GBM_BO_USE_SCANOUT; uint64_t modifier = DRM_FORMAT_MOD_INVALID; if (explicitModifiers.empty()) { allocator->backend->log(AQ_LOG_WARNING, "GBM: Using modifier-less allocation"); bo = gbm_bo_create(allocator->gbmDevice, attrs.size.x, attrs.size.y, attrs.format, flags); } else { TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: Using modifier-based allocation, modifiers: {}", explicitModifiers.size()))); for (auto const& mod : explicitModifiers) { TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: | mod 0x{:x}", mod))); } bo = gbm_bo_create_with_modifiers2(allocator->gbmDevice, attrs.size.x, attrs.size.y, attrs.format, explicitModifiers.data(), explicitModifiers.size(), flags); if (!bo && CURSOR) { // allow non-renderable cursor buffer for nvidia allocator->backend->log(AQ_LOG_ERROR, "GBM: Allocating with modifiers and flags failed, falling back to modifiers without flags"); bo = gbm_bo_create_with_modifiers(allocator->gbmDevice, attrs.size.x, attrs.size.y, attrs.format, explicitModifiers.data(), explicitModifiers.size()); } bool useLinear = explicitModifiers.size() == 1 && explicitModifiers[0] == DRM_FORMAT_MOD_LINEAR; if (bo) { modifier = gbm_bo_get_modifier(bo); if (useLinear && modifier == DRM_FORMAT_MOD_INVALID) modifier = DRM_FORMAT_MOD_LINEAR; } else { if (useLinear) { flags |= GBM_BO_USE_LINEAR; modifier = DRM_FORMAT_MOD_LINEAR; allocator->backend->log(AQ_LOG_ERROR, "GBM: Allocating with modifiers failed, falling back to modifier-less allocation"); } else allocator->backend->log(AQ_LOG_ERROR, "GBM: Allocating with modifiers failed, falling back to implicit"); bo = gbm_bo_create(allocator->gbmDevice, attrs.size.x, attrs.size.y, attrs.format, flags); } } if (MULTIGPU && forceLinearBlit) { // FIXME: most likely nvidia main gpu on multigpu if (!bo) { if (oldMods.empty()) bo = gbm_bo_create(allocator->gbmDevice, attrs.size.x, attrs.size.y, attrs.format, GBM_BO_USE_RENDERING); else bo = gbm_bo_create_with_modifiers(allocator->gbmDevice, attrs.size.x, attrs.size.y, attrs.format, oldMods.data(), oldMods.size()); if (!bo) { allocator->backend->log(AQ_LOG_ERROR, "GBM: Failed to allocate a GBM buffer: bo null"); return; } modifier = gbm_bo_get_modifier(bo); } } if (!bo) { allocator->backend->log(AQ_LOG_ERROR, "GBM: Failed to allocate a GBM buffer: bo null"); return; } attrs.planes = gbm_bo_get_plane_count(bo); attrs.modifier = modifier; for (size_t i = 0; i < (size_t)attrs.planes; ++i) { attrs.strides.at(i) = gbm_bo_get_stride_for_plane(bo, i); attrs.offsets.at(i) = gbm_bo_get_offset(bo, i); attrs.fds.at(i) = gbm_bo_get_fd_for_plane(bo, i); if (attrs.fds.at(i) < 0) { allocator->backend->log(AQ_LOG_ERROR, std::format("GBM: Failed to query fd for plane {}", i)); for (size_t j = 0; j < i; ++j) { close(attrs.fds.at(j)); } attrs.planes = 0; return; } } attrs.success = true; auto modName = drmGetFormatModifierName(attrs.modifier); allocator->backend->log(AQ_LOG_DEBUG, std::format("GBM: Allocated a new buffer with size {} and format {} with modifier {} aka {}", attrs.size, fourccToName(attrs.format), attrs.modifier, modName ? modName : "Unknown")); free(modName); if (params.scanout && !MULTIGPU && swapchain->backendImpl->type() == AQ_BACKEND_DRM) { // clear the buffer using the DRM renderer to avoid uninitialized mem auto impl = (CDRMBackend*)swapchain->backendImpl.get(); if (impl->rendererState.renderer) impl->rendererState.renderer->clearBuffer(this); } } Aquamarine::CGBMBuffer::~CGBMBuffer() { for (size_t i = 0; i < (size_t)attrs.planes; i++) { close(attrs.fds.at(i)); } events.destroy.emit(); if (bo) { if (gboMapping) gbm_bo_unmap(bo, gboMapping); // FIXME: is it needed before destroy? gbm_bo_destroy(bo); } } eBufferCapability Aquamarine::CGBMBuffer::caps() { return (Aquamarine::eBufferCapability)0; } eBufferType Aquamarine::CGBMBuffer::type() { return Aquamarine::eBufferType::BUFFER_TYPE_DMABUF; } void Aquamarine::CGBMBuffer::update(const Hyprutils::Math::CRegion& damage) { ; } bool Aquamarine::CGBMBuffer::isSynchronous() { return false; } bool Aquamarine::CGBMBuffer::good() { return bo; } SDMABUFAttrs Aquamarine::CGBMBuffer::dmabuf() { return attrs; } std::tuple Aquamarine::CGBMBuffer::beginDataPtr(uint32_t flags) { uint32_t stride = 0; if (boBuffer) allocator->backend->log(AQ_LOG_ERROR, "beginDataPtr is called a second time without calling endDataPtr first. Returning old mapping"); else boBuffer = gbm_bo_map(bo, 0, 0, attrs.size.x, attrs.size.y, flags, &stride, &gboMapping); return {(uint8_t*)boBuffer, attrs.format, stride * attrs.size.y}; } void Aquamarine::CGBMBuffer::endDataPtr() { if (gboMapping) { gbm_bo_unmap(bo, gboMapping); gboMapping = nullptr; boBuffer = nullptr; } } void CGBMAllocator::destroyBuffers() { for (auto& buf : buffers) { buf.reset(); } } CGBMAllocator::~CGBMAllocator() { if (!gbmDevice) return; int fd = gbm_device_get_fd(gbmDevice); gbm_device_destroy(gbmDevice); if (fd < 0) return; close(fd); } SP Aquamarine::CGBMAllocator::create(int drmfd_, Hyprutils::Memory::CWeakPointer backend_) { uint64_t capabilities = 0; if (drmGetCap(drmfd_, DRM_CAP_PRIME, &capabilities) || !(capabilities & DRM_PRIME_CAP_EXPORT)) { backend_->log(AQ_LOG_ERROR, "Cannot create a GBM Allocator: PRIME export is not supported by the gpu."); return nullptr; } auto allocator = SP(new CGBMAllocator(drmfd_, backend_)); if (!allocator->gbmDevice) { backend_->log(AQ_LOG_ERROR, "Cannot create a GBM Allocator: gbm failed to create a device."); return nullptr; } backend_->log(AQ_LOG_DEBUG, std::format("Created a GBM allocator with drm fd {}", drmfd_)); allocator->self = allocator; return allocator; } Aquamarine::CGBMAllocator::CGBMAllocator(int fd_, Hyprutils::Memory::CWeakPointer backend_) : fd(fd_), backend(backend_), gbmDevice(gbm_create_device(fd_)) { if (!gbmDevice) { backend->log(AQ_LOG_ERROR, std::format("Couldn't open a GBM device at fd {}", fd_)); return; } gbmDeviceBackendName = gbm_device_get_backend_name(gbmDevice); auto drmName_ = drmGetDeviceNameFromFd2(fd_); drmName = drmName_; free(drmName_); } SP Aquamarine::CGBMAllocator::acquire(const SAllocatorBufferParams& params, Hyprutils::Memory::CSharedPointer swapchain_) { if (params.size.x < 1 || params.size.y < 1) { backend->log(AQ_LOG_ERROR, std::format("Couldn't allocate a gbm buffer with invalid size {}", params.size)); return nullptr; } auto newBuffer = SP(new CGBMBuffer(params, self, swapchain_)); if (!newBuffer->good()) { backend->log(AQ_LOG_ERROR, std::format("Couldn't allocate a gbm buffer with size {} and format {}", params.size, fourccToName(params.format))); return nullptr; } buffers.emplace_back(newBuffer); std::erase_if(buffers, [](const auto& b) { return b.expired(); }); return newBuffer; } Hyprutils::Memory::CSharedPointer Aquamarine::CGBMAllocator::getBackend() { return backend.lock(); } int Aquamarine::CGBMAllocator::drmFD() { return fd; } eAllocatorType Aquamarine::CGBMAllocator::type() { return AQ_ALLOCATOR_TYPE_GBM; } hyprwm-aquamarine-655e067/src/allocator/Swapchain.cpp000066400000000000000000000104051506775317200226510ustar00rootroot00000000000000#include #include #include #include "FormatUtils.hpp" using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; #define SP CSharedPointer SP Aquamarine::CSwapchain::create(SP allocator_, SP backendImpl_) { auto p = SP(new CSwapchain(allocator_, backendImpl_)); p->self = p; return p; } Aquamarine::CSwapchain::CSwapchain(SP allocator_, SP backendImpl_) : allocator(allocator_), backendImpl(backendImpl_) { if (!allocator || !backendImpl) return; } bool Aquamarine::CSwapchain::reconfigure(const SSwapchainOptions& options_) { if (!allocator) return false; if (options_.size == Vector2D{} || options_.length == 0) { // clear the swapchain allocator->getBackend()->log(AQ_LOG_DEBUG, "Swapchain: Clearing"); buffers.clear(); options = options_; return true; } if ((options_.format == options.format || options_.format == DRM_FORMAT_INVALID) && options_.size == options.size && options_.length == options.length && buffers.size() == options.length) return true; // no need to reconfigure if ((options_.format == options.format || options_.format == DRM_FORMAT_INVALID) && options_.size == options.size) { bool ok = resize(options_.length); if (!ok) return false; options = options_; allocator->getBackend()->log(AQ_LOG_DEBUG, std::format("Swapchain: Resized a {} {} swapchain to length {}", options.size, fourccToName(options.format), options.length)); return true; } bool ok = fullReconfigure(options_); if (!ok) return false; options = options_; if (options.format == DRM_FORMAT_INVALID) options.format = buffers.at(0)->dmabuf().format; allocator->getBackend()->log(AQ_LOG_DEBUG, std::format("Swapchain: Reconfigured a swapchain to {} {} of length {}", options.size, fourccToName(options.format), options.length)); return true; } SP Aquamarine::CSwapchain::next(int* age) { if (!allocator || options.length <= 0) return nullptr; lastAcquired = (lastAcquired + 1) % options.length; if (age) *age = options.length; // we always just rotate return buffers.at(lastAcquired); } bool Aquamarine::CSwapchain::fullReconfigure(const SSwapchainOptions& options_) { std::vector> bfs; bfs.reserve(options_.length); for (size_t i = 0; i < options_.length; ++i) { auto buf = allocator->acquire( SAllocatorBufferParams{.size = options_.size, .format = options_.format, .scanout = options_.scanout, .cursor = options_.cursor, .multigpu = options_.multigpu}, self.lock()); if (!buf) { allocator->getBackend()->log(AQ_LOG_ERROR, "Swapchain: Failed acquiring a buffer"); return false; } bfs.emplace_back(buf); } buffers = std::move(bfs); return true; } bool Aquamarine::CSwapchain::resize(size_t newSize) { if (newSize == buffers.size()) return true; if (newSize < buffers.size()) { while (buffers.size() > newSize) { buffers.pop_back(); } } else { while (buffers.size() < newSize) { auto buf = allocator->acquire(SAllocatorBufferParams{.size = options.size, .format = options.format, .scanout = options.scanout, .cursor = options.cursor}, self.lock()); if (!buf) { allocator->getBackend()->log(AQ_LOG_ERROR, "Swapchain: Failed acquiring a buffer"); return false; } buffers.emplace_back(buf); } } return true; } bool Aquamarine::CSwapchain::contains(SP buffer) { return std::ranges::find(buffers, buffer) != buffers.end(); } const SSwapchainOptions& Aquamarine::CSwapchain::currentOptions() { return options; } void Aquamarine::CSwapchain::rollback() { lastAcquired--; if (lastAcquired < 0) lastAcquired = options.length - 1; } SP Aquamarine::CSwapchain::getAllocator() { return allocator; } hyprwm-aquamarine-655e067/src/backend/000077500000000000000000000000001506775317200176375ustar00rootroot00000000000000hyprwm-aquamarine-655e067/src/backend/Backend.cpp000066400000000000000000000272551506775317200217050ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Hyprutils::Memory; using namespace Aquamarine; #define SP CSharedPointer #define TIMESPEC_NSEC_PER_SEC 1000000000LL static void timespecAddNs(timespec* pTimespec, int64_t delta) { int delta_ns_low = delta % TIMESPEC_NSEC_PER_SEC; int delta_s_high = delta / TIMESPEC_NSEC_PER_SEC; pTimespec->tv_sec += delta_s_high; pTimespec->tv_nsec += (long)delta_ns_low; if (pTimespec->tv_nsec >= TIMESPEC_NSEC_PER_SEC) { pTimespec->tv_nsec -= TIMESPEC_NSEC_PER_SEC; ++pTimespec->tv_sec; } } static const char* backendTypeToName(eBackendType type) { switch (type) { case AQ_BACKEND_DRM: return "drm"; case AQ_BACKEND_HEADLESS: return "headless"; case AQ_BACKEND_WAYLAND: return "wayland"; default: break; } return "invalid"; } Aquamarine::CBackend::CBackend() { ; } Aquamarine::SBackendImplementationOptions::SBackendImplementationOptions() : backendType(AQ_BACKEND_WAYLAND), backendRequestMode(AQ_BACKEND_REQUEST_IF_AVAILABLE) { ; } Aquamarine::SBackendOptions::SBackendOptions() : logFunction(nullptr) { ; } Hyprutils::Memory::CSharedPointer Aquamarine::CBackend::create(const std::vector& backends, const SBackendOptions& options) { auto backend = SP(new CBackend()); backend->options = options; backend->implementationOptions = backends; backend->self = backend; if (backends.size() <= 0) return nullptr; backend->log(AQ_LOG_DEBUG, "Creating an Aquamarine backend!"); for (auto const& b : backends) { if (b.backendType == AQ_BACKEND_WAYLAND) { auto ref = SP(new CWaylandBackend(backend)); backend->implementations.emplace_back(ref); ref->self = ref; } else if (b.backendType == AQ_BACKEND_DRM) { auto ref = CDRMBackend::attempt(backend); if (ref.empty()) { backend->log(AQ_LOG_ERROR, "DRM Backend failed"); continue; } for (auto const& r : ref) { backend->implementations.emplace_back(r); } } else if (b.backendType == AQ_BACKEND_HEADLESS) { auto ref = SP(new CHeadlessBackend(backend)); backend->implementations.emplace_back(ref); ref->self = ref; } else if (b.backendType == AQ_BACKEND_NULL) { auto ref = SP(new CNullBackend(backend)); backend->implementations.emplace_back(ref); ref->self = ref; } else { backend->log(AQ_LOG_ERROR, std::format("Unknown backend id: {}", (int)b.backendType)); continue; } } // create a timerfd for idle events backend->idle.fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); return backend; } Aquamarine::CBackend::~CBackend() { ; } bool Aquamarine::CBackend::start() { log(AQ_LOG_DEBUG, "Starting the Aquamarine backend!"); int started = 0; auto optionsForType = [this](eBackendType type) -> SBackendImplementationOptions { for (auto const& o : implementationOptions) { if (o.backendType == type) return o; } return SBackendImplementationOptions{}; }; for (size_t i = 0; i < implementations.size(); ++i) { const bool ok = implementations.at(i)->start(); if (!ok) { log(AQ_LOG_ERROR, std::format("Requested backend ({}) could not start, enabling fallbacks", backendTypeToName(implementations.at(i)->type()))); if (optionsForType(implementations.at(i)->type()).backendRequestMode == AQ_BACKEND_REQUEST_MANDATORY) { log(AQ_LOG_CRITICAL, std::format("Requested backend ({}) could not start and it's mandatory, cannot continue!", backendTypeToName(implementations.at(i)->type()))); implementations.clear(); return false; } } else started++; } if (implementations.empty() || started <= 0) { log(AQ_LOG_CRITICAL, "No backend could be opened. Make sure there was a correct backend passed to CBackend, and that your environment supports at least one of them."); return false; } // erase failed impls std::erase_if(implementations, [this](const auto& i) { bool failed = i->pollFDs().empty() && i->type() != AQ_BACKEND_NULL; if (failed) log(AQ_LOG_ERROR, std::format("Implementation {} failed, erasing.", backendTypeToName(i->type()))); return failed; }); // TODO: obviously change this when (if) we add different allocators. for (auto const& b : implementations) { if (b->drmFD() >= 0) { auto fd = reopenDRMNode(b->drmFD()); if (fd < 0) { // this is critical, we cannot create an allocator properly log(AQ_LOG_CRITICAL, "Failed to create an allocator (reopenDRMNode failed)"); return false; } primaryAllocator = CGBMAllocator::create(fd, self); break; } } if (!primaryAllocator && (implementations.empty() || implementations.at(0)->type() != AQ_BACKEND_NULL)) { log(AQ_LOG_CRITICAL, "Cannot open backend: no allocator available"); return false; } ready = true; for (auto const& b : implementations) { b->onReady(); } if (session) session->onReady(); sessionFDs = session ? session->pollFDs() : std::vector>{}; return true; } void Aquamarine::CBackend::log(eBackendLogLevel level, const std::string& msg) { if (!options.logFunction) return; options.logFunction(level, msg); } std::vector> Aquamarine::CBackend::getPollFDs() { std::vector> result; for (auto const& i : implementations) { auto pollfds = i->pollFDs(); for (auto const& p : pollfds) { log(AQ_LOG_DEBUG, std::format("backend: poll fd {} for implementation {}", p->fd, backendTypeToName(i->type()))); result.emplace_back(p); } } for (auto const& sfd : sessionFDs) { log(AQ_LOG_DEBUG, std::format("backend: poll fd {} for session", sfd->fd)); result.emplace_back(sfd); } log(AQ_LOG_DEBUG, std::format("backend: poll fd {} for idle", idle.fd)); result.emplace_back(makeShared(idle.fd, [this]() { dispatchIdle(); })); return result; } int Aquamarine::CBackend::drmFD() { for (auto const& i : implementations) { int fd = i->drmFD(); if (fd < 0) continue; return fd; } return -1; } int Aquamarine::CBackend::drmRenderNodeFD() { for (auto const& i : implementations) { int fd = i->drmRenderNodeFD(); if (fd < 0) continue; return fd; } return -1; } bool Aquamarine::CBackend::hasSession() { return session; } std::vector Aquamarine::CBackend::getPrimaryRenderFormats() { for (auto const& b : implementations) { if (b->type() != AQ_BACKEND_DRM && b->type() != AQ_BACKEND_WAYLAND) continue; return b->getRenderFormats(); } for (auto const& b : implementations) { return b->getRenderFormats(); } return {}; } const std::vector>& Aquamarine::CBackend::getImplementations() { return implementations; } void Aquamarine::CBackend::addIdleEvent(SP> fn) { auto r = idle.pending.emplace_back(fn); updateIdleTimer(); } void Aquamarine::CBackend::updateIdleTimer() { uint64_t ADD_NS = idle.pending.empty() ? TIMESPEC_NSEC_PER_SEC * 240ULL /* 240s, 4 mins */ : 0; timespec now; clock_gettime(CLOCK_MONOTONIC, &now); timespecAddNs(&now, ADD_NS); itimerspec ts = {.it_value = now}; if (timerfd_settime(idle.fd, TFD_TIMER_ABSTIME, &ts, nullptr)) log(AQ_LOG_ERROR, std::format("backend: failed to arm timerfd: {}", strerror(errno))); } void Aquamarine::CBackend::removeIdleEvent(SP> pfn) { std::erase(idle.pending, pfn); } void Aquamarine::CBackend::dispatchIdle() { auto cpy = idle.pending; idle.pending.clear(); for (auto const& i : cpy) { if (i && *i) (*i)(); } updateIdleTimer(); } void Aquamarine::CBackend::onNewGpu(std::string path) { const auto primary = std::ranges::find_if(implementations, [](SP value) { return value->type() == Aquamarine::AQ_BACKEND_DRM; }); const auto primaryDrm = primary != implementations.end() ? ((Aquamarine::CDRMBackend*)(*primary).get())->self.lock() : nullptr; auto ref = CDRMBackend::fromGpu(path, self.lock(), primaryDrm); if (!ref) { log(AQ_LOG_ERROR, std::format("DRM Backend failed for device {}", path)); return; } if (!ref->start()) { log(AQ_LOG_ERROR, std::format("Couldn't start DRM Backend for device {}", path)); return; } implementations.emplace_back(ref); events.pollFDsChanged.emit(); ref->onReady(); // Renderer created here ref->recheckOutputs(); // Now we can recheck outputs } // Yoinked from wlroots, render/allocator/allocator.c // Ref-counting reasons, see https://gitlab.freedesktop.org/mesa/drm/-/merge_requests/110 int Aquamarine::CBackend::reopenDRMNode(int drmFD, bool allowRenderNode) { if (drmIsMaster(drmFD)) { // Only recent kernels support empty leases uint32_t lesseeID = 0; int leaseFD = drmModeCreateLease(drmFD, nullptr, 0, O_CLOEXEC, &lesseeID); if (leaseFD >= 0) { return leaseFD; } else if (leaseFD != -EINVAL && leaseFD != -EOPNOTSUPP) { log(AQ_LOG_ERROR, "reopenDRMNode: drmModeCreateLease failed"); return -1; } log(AQ_LOG_DEBUG, "reopenDRMNode: drmModeCreateLease failed, falling back to open"); } char* name = nullptr; if (allowRenderNode) name = drmGetRenderDeviceNameFromFd(drmFD); if (!name) { // primary node or no name name = drmGetDeviceNameFromFd2(drmFD); if (!name) { log(AQ_LOG_ERROR, "reopenDRMNode: drmGetDeviceNameFromFd2 failed"); return -1; } } log(AQ_LOG_DEBUG, std::format("reopenDRMNode: opening node {}", name)); int newFD = open(name, O_RDWR | O_CLOEXEC); if (newFD < 0) { log(AQ_LOG_ERROR, std::format("reopenDRMNode: failed to open node {}", name)); free(name); return -1; } free(name); // We need to authenticate if we are using a DRM primary node and are the master if (drmIsMaster(drmFD) && drmGetNodeTypeFromFd(newFD) == DRM_NODE_PRIMARY) { drm_magic_t magic; if (int ret = drmGetMagic(newFD, &magic); ret < 0) { log(AQ_LOG_ERROR, std::format("reopenDRMNode: drmGetMagic failed: {}", strerror(-ret))); close(newFD); return -1; } if (int ret = drmAuthMagic(drmFD, magic); ret < 0) { log(AQ_LOG_ERROR, std::format("reopenDRMNode: drmAuthMagic failed: {}", strerror(-ret))); close(newFD); return -1; } } return newFD; } std::vector Aquamarine::IBackendImplementation::getRenderableFormats() { return {}; } hyprwm-aquamarine-655e067/src/backend/Headless.cpp000066400000000000000000000166061506775317200221040ustar00rootroot00000000000000#include #include #include #include #include #include "Shared.hpp" using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; #define SP CSharedPointer #define TIMESPEC_NSEC_PER_SEC 1000000000LL static void timespecAddNs(timespec* pTimespec, int64_t delta) { int delta_ns_low = delta % TIMESPEC_NSEC_PER_SEC; int delta_s_high = delta / TIMESPEC_NSEC_PER_SEC; pTimespec->tv_sec += delta_s_high; pTimespec->tv_nsec += (long)delta_ns_low; if (pTimespec->tv_nsec >= TIMESPEC_NSEC_PER_SEC) { pTimespec->tv_nsec -= TIMESPEC_NSEC_PER_SEC; ++pTimespec->tv_sec; } } Aquamarine::CHeadlessOutput::CHeadlessOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_) : backend(backend_) { name = name_; framecb = makeShared>([this]() { frameScheduled = false; events.frame.emit(); }); } Aquamarine::CHeadlessOutput::~CHeadlessOutput() { backend->backend->removeIdleEvent(framecb); events.destroy.emit(); } bool Aquamarine::CHeadlessOutput::commit() { events.commit.emit(); state->onCommit(); needsFrame = false; events.present.emit(IOutput::SPresentEvent{.presented = true}); return true; } bool Aquamarine::CHeadlessOutput::test() { return true; } std::vector Aquamarine::CHeadlessOutput::getRenderFormats() { return backend->getRenderFormats(); } Hyprutils::Memory::CSharedPointer Aquamarine::CHeadlessOutput::getBackend() { return backend.lock(); } void Aquamarine::CHeadlessOutput::scheduleFrame(const scheduleFrameReason reason) { TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("CHeadlessOutput::scheduleFrame: reason {}, needsFrame {}, frameScheduled {}", (uint32_t)reason, needsFrame, frameScheduled))); // FIXME: limit fps to the committed framerate. needsFrame = true; if (frameScheduled) return; frameScheduled = true; backend->backend->addIdleEvent(framecb); } bool Aquamarine::CHeadlessOutput::destroy() { events.destroy.emit(); std::erase(backend->outputs, self.lock()); return true; } Aquamarine::CHeadlessBackend::~CHeadlessBackend() { ; } Aquamarine::CHeadlessBackend::CHeadlessBackend(SP backend_) : backend(backend_) { timers.timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); } eBackendType Aquamarine::CHeadlessBackend::type() { return eBackendType::AQ_BACKEND_HEADLESS; } bool Aquamarine::CHeadlessBackend::start() { return true; } std::vector> Aquamarine::CHeadlessBackend::pollFDs() { return {makeShared(timers.timerfd, [this]() { dispatchTimers(); })}; } int Aquamarine::CHeadlessBackend::drmFD() { return -1; } int Aquamarine::CHeadlessBackend::drmRenderNodeFD() { return -1; } bool Aquamarine::CHeadlessBackend::dispatchEvents() { return true; } uint32_t Aquamarine::CHeadlessBackend::capabilities() { return 0; } bool Aquamarine::CHeadlessBackend::setCursor(SP buffer, const Hyprutils::Math::Vector2D& hotspot) { return false; } void Aquamarine::CHeadlessBackend::onReady() { ; } std::vector Aquamarine::CHeadlessBackend::getRenderFormats() { for (const auto& impl : backend->getImplementations()) { if (impl->type() != AQ_BACKEND_DRM || impl->getRenderableFormats().empty()) continue; return impl->getRenderableFormats(); } // formats probably supported by EGL return {SDRMFormat{.drmFormat = DRM_FORMAT_XRGB8888, .modifiers = {DRM_FORMAT_INVALID}}, SDRMFormat{.drmFormat = DRM_FORMAT_XBGR8888, .modifiers = {DRM_FORMAT_INVALID}}, SDRMFormat{.drmFormat = DRM_FORMAT_RGBX8888, .modifiers = {DRM_FORMAT_INVALID}}, SDRMFormat{.drmFormat = DRM_FORMAT_BGRX8888, .modifiers = {DRM_FORMAT_INVALID}}, SDRMFormat{.drmFormat = DRM_FORMAT_ARGB8888, .modifiers = {DRM_FORMAT_INVALID}}, SDRMFormat{.drmFormat = DRM_FORMAT_ABGR8888, .modifiers = {DRM_FORMAT_INVALID}}, SDRMFormat{.drmFormat = DRM_FORMAT_RGBA8888, .modifiers = {DRM_FORMAT_INVALID}}, SDRMFormat{.drmFormat = DRM_FORMAT_BGRA8888, .modifiers = {DRM_FORMAT_INVALID}}, SDRMFormat{.drmFormat = DRM_FORMAT_XRGB2101010, .modifiers = {DRM_FORMAT_MOD_LINEAR}}, SDRMFormat{.drmFormat = DRM_FORMAT_XBGR2101010, .modifiers = {DRM_FORMAT_MOD_LINEAR}}, SDRMFormat{.drmFormat = DRM_FORMAT_RGBX1010102, .modifiers = {DRM_FORMAT_MOD_LINEAR}}, SDRMFormat{.drmFormat = DRM_FORMAT_BGRX1010102, .modifiers = {DRM_FORMAT_MOD_LINEAR}}, SDRMFormat{.drmFormat = DRM_FORMAT_ARGB2101010, .modifiers = {DRM_FORMAT_MOD_LINEAR}}, SDRMFormat{.drmFormat = DRM_FORMAT_ABGR2101010, .modifiers = {DRM_FORMAT_MOD_LINEAR}}, SDRMFormat{.drmFormat = DRM_FORMAT_RGBA1010102, .modifiers = {DRM_FORMAT_MOD_LINEAR}}, SDRMFormat{.drmFormat = DRM_FORMAT_BGRA1010102, .modifiers = {DRM_FORMAT_MOD_LINEAR}}}; } std::vector Aquamarine::CHeadlessBackend::getCursorFormats() { return {}; // No cursor support } bool Aquamarine::CHeadlessBackend::createOutput(const std::string& name) { auto output = SP(new CHeadlessOutput(name.empty() ? std::format("HEADLESS-{}", ++outputIDCounter) : name, self.lock())); outputs.emplace_back(output); output->modes.emplace_back(SP(new SOutputMode(Vector2D{1920, 1080}, 60, true))); output->swapchain = CSwapchain::create(backend->primaryAllocator, self.lock()); output->self = output; backend->events.newOutput.emit(SP(output)); return true; } void Aquamarine::CHeadlessBackend::dispatchTimers() { std::vector toFire; for (size_t i = 0; i < timers.timers.size(); ++i) { if (timers.timers.at(i).expired()) { toFire.emplace_back(timers.timers.at(i)); timers.timers.erase(timers.timers.begin() + i); i--; continue; } } for (auto const& copy : toFire) { if (copy.what) copy.what(); } updateTimerFD(); } void Aquamarine::CHeadlessBackend::updateTimerFD() { long long lowestNs = TIMESPEC_NSEC_PER_SEC * 240 /* 240s, 4 mins */; const auto clocknow = std::chrono::steady_clock::now(); for (auto const& t : timers.timers) { auto delta = std::chrono::duration_cast(t.when - clocknow).count() * 1000 /* µs -> ns */; if (delta < lowestNs) lowestNs = delta; } if (lowestNs < 0) lowestNs = 0; timespec now; clock_gettime(CLOCK_MONOTONIC, &now); timespecAddNs(&now, lowestNs); itimerspec ts = {.it_value = now}; if (timerfd_settime(timers.timerfd, TFD_TIMER_ABSTIME, &ts, nullptr)) backend->log(AQ_LOG_ERROR, std::format("headless: failed to arm timerfd: {}", strerror(errno))); } SP Aquamarine::CHeadlessBackend::preferredAllocator() { return backend->primaryAllocator; } std::vector> Aquamarine::CHeadlessBackend::getAllocators() { return {backend->primaryAllocator}; } Hyprutils::Memory::CWeakPointer Aquamarine::CHeadlessBackend::getPrimary() { return {}; } bool Aquamarine::CHeadlessBackend::CTimer::expired() { return std::chrono::steady_clock::now() > when; } hyprwm-aquamarine-655e067/src/backend/Null.cpp000066400000000000000000000037761506775317200212720ustar00rootroot00000000000000#include #include #include #include #include #include "Shared.hpp" using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; #define SP CSharedPointer Aquamarine::CNullBackend::~CNullBackend() { ; } Aquamarine::CNullBackend::CNullBackend(SP backend_) : backend(backend_) { ; } eBackendType Aquamarine::CNullBackend::type() { return eBackendType::AQ_BACKEND_NULL; } bool Aquamarine::CNullBackend::start() { return true; } std::vector> Aquamarine::CNullBackend::pollFDs() { return {}; } int Aquamarine::CNullBackend::drmFD() { return -1; } int Aquamarine::CNullBackend::drmRenderNodeFD() { return -1; } bool Aquamarine::CNullBackend::dispatchEvents() { return true; } uint32_t Aquamarine::CNullBackend::capabilities() { return 0; } bool Aquamarine::CNullBackend::setCursor(SP buffer, const Hyprutils::Math::Vector2D& hotspot) { return false; } void Aquamarine::CNullBackend::onReady() { ; } std::vector Aquamarine::CNullBackend::getRenderFormats() { for (const auto& impl : backend->getImplementations()) { if (impl->type() != AQ_BACKEND_DRM || impl->getRenderableFormats().empty()) continue; return impl->getRenderableFormats(); } return m_formats; } void Aquamarine::CNullBackend::setFormats(const std::vector& fmts) { m_formats = fmts; } std::vector Aquamarine::CNullBackend::getCursorFormats() { return {}; // No cursor support } bool Aquamarine::CNullBackend::createOutput(const std::string& name) { return false; } SP Aquamarine::CNullBackend::preferredAllocator() { return backend->primaryAllocator; } std::vector> Aquamarine::CNullBackend::getAllocators() { return {backend->primaryAllocator}; } Hyprutils::Memory::CWeakPointer Aquamarine::CNullBackend::getPrimary() { return {}; } hyprwm-aquamarine-655e067/src/backend/Session.cpp000066400000000000000000001271721506775317200220000ustar00rootroot00000000000000#include #include extern "C" { #include #include #include #include #include #include #include #include #include } using namespace Aquamarine; using namespace Hyprutils::Memory; #define SP CSharedPointer static const std::string AQ_UNKNOWN_DEVICE_NAME = "UNKNOWN"; // we can't really do better with libseat/libinput logs // because they don't allow us to pass "data" or anything... // Nobody should create multiple backends anyways really Hyprutils::Memory::CSharedPointer backendInUse; // static Aquamarine::eBackendLogLevel logLevelFromLibseat(libseat_log_level level) { switch (level) { case LIBSEAT_LOG_LEVEL_ERROR: return AQ_LOG_ERROR; case LIBSEAT_LOG_LEVEL_SILENT: return AQ_LOG_TRACE; default: break; } return AQ_LOG_DEBUG; } static Aquamarine::eBackendLogLevel logLevelFromLibinput(libinput_log_priority level) { switch (level) { case LIBINPUT_LOG_PRIORITY_ERROR: return AQ_LOG_ERROR; default: break; } return AQ_LOG_DEBUG; } static void libseatLog(libseat_log_level level, const char* fmt, va_list args) { if (!backendInUse) return; static char string[1024]; vsnprintf(string, sizeof(string), fmt, args); backendInUse->log(logLevelFromLibseat(level), std::format("[libseat] {}", string)); } static void libinputLog(libinput*, libinput_log_priority level, const char* fmt, va_list args) { if (!backendInUse) return; static char string[1024]; vsnprintf(string, sizeof(string), fmt, args); backendInUse->log(logLevelFromLibinput(level), std::format("[libinput] {}", string)); } // ------------ Libseat static void libseatEnableSeat(struct libseat* seat, void* data) { auto PSESSION = (Aquamarine::CSession*)data; PSESSION->active = true; if (PSESSION->libinputHandle) libinput_resume(PSESSION->libinputHandle); PSESSION->events.changeActive.emit(); } static void libseatDisableSeat(struct libseat* seat, void* data) { auto PSESSION = (Aquamarine::CSession*)data; PSESSION->active = false; if (PSESSION->libinputHandle) libinput_suspend(PSESSION->libinputHandle); PSESSION->events.changeActive.emit(); libseat_disable_seat(PSESSION->libseatHandle); } static const libseat_seat_listener libseatListener = { .enable_seat = ::libseatEnableSeat, .disable_seat = ::libseatDisableSeat, }; // ------------ Libinput static int libinputOpen(const char* path, int flags, void* data) { auto SESSION = (CSession*)data; auto dev = makeShared(SESSION->self.lock(), path); if (!dev->dev) return -1; SESSION->sessionDevices.emplace_back(dev); return dev->fd; } static void libinputClose(int fd, void* data) { auto SESSION = (CSession*)data; std::erase_if(SESSION->sessionDevices, [fd](const auto& dev) { auto toRemove = dev->fd == fd; if (toRemove) dev->events.remove.emit(); return toRemove; }); } static const libinput_interface libinputListener = { .open_restricted = ::libinputOpen, .close_restricted = ::libinputClose, }; // ------------ Aquamarine::CSessionDevice::CSessionDevice(Hyprutils::Memory::CSharedPointer session_, const std::string& path_) : path(path_), session(session_) { deviceID = libseat_open_device(session->libseatHandle, path.c_str(), &fd); if (deviceID < 0) { session->backend->log(AQ_LOG_ERROR, std::format("libseat: Couldn't open device at {}", path_)); return; } struct stat stat_; if (fstat(fd, &stat_) < 0) { session->backend->log(AQ_LOG_ERROR, std::format("libseat: Couldn't stat device at {}", path_)); deviceID = -1; return; } dev = stat_.st_rdev; } Aquamarine::CSessionDevice::~CSessionDevice() { if (deviceID >= 0) if (libseat_close_device(session->libseatHandle, deviceID) < 0) session->backend->log(AQ_LOG_ERROR, std::format("libseat: Couldn't close device at {}", path)); if (fd >= 0) close(fd); if (renderNodeFd) close(renderNodeFd); } bool Aquamarine::CSessionDevice::supportsKMS() { if (deviceID < 0) return false; bool kms = drmIsKMS(fd); if (kms) session->backend->log(AQ_LOG_DEBUG, std::format("libseat: Device {} supports kms", path)); else session->backend->log(AQ_LOG_DEBUG, std::format("libseat: Device {} does not support kms", path)); return kms; } void Aquamarine::CSessionDevice::resolveMatchingRenderNode(udev_device* cardDevice) { if (!cardDevice) return; auto pciParent = udev_device_get_parent_with_subsystem_devtype(cardDevice, "pci", nullptr); const auto* pciSyspath = pciParent ? udev_device_get_syspath(pciParent) : nullptr; auto* enumerate = udev_enumerate_new(session->udevHandle); if (!enumerate) return; udev_enumerate_add_match_subsystem(enumerate, "drm"); udev_enumerate_scan_devices(enumerate); auto* devices = udev_enumerate_get_list_entry(enumerate); udev_list_entry* entry = nullptr; bool matched = false; udev_list_entry_foreach(entry, devices) { const auto* path = udev_list_entry_get_name(entry); auto dev = udev_device_new_from_syspath(session->udevHandle, path); if (!dev) continue; const auto* devnode = udev_device_get_devnode(dev); const auto* devtype = udev_device_get_devtype(dev); if (!devnode || !devtype || strcmp(devtype, "drm_minor") != 0 || !strstr(devnode, "renderD")) { udev_device_unref(dev); continue; } auto devParent = udev_device_get_parent_with_subsystem_devtype(dev, "pci", nullptr); if (devParent && pciSyspath && strcmp(udev_device_get_syspath(devParent), pciSyspath) == 0) { renderNodeFd = open(devnode, O_RDWR | O_CLOEXEC); if (renderNodeFd < 0) session->backend->log(AQ_LOG_WARNING, std::format("drm: Failed to open matching render node {}", devnode)); else matched = true; udev_device_unref(dev); break; } udev_device_unref(dev); } if (!matched) { // fallback to the first render node udev_list_entry_foreach(entry, devices) { const auto* path = udev_list_entry_get_name(entry); auto dev = udev_device_new_from_syspath(session->udevHandle, path); if (!dev) continue; const auto* devnode = udev_device_get_devnode(dev); const auto* devtype = udev_device_get_devtype(dev); if (!devnode || !devtype || strcmp(devtype, "drm_minor") != 0 || !strstr(devnode, "renderD")) { udev_device_unref(dev); continue; } renderNodeFd = open(devnode, O_RDWR | O_CLOEXEC); if (renderNodeFd >= 0) { session->backend->log(AQ_LOG_WARNING, std::format("drm: No matching render node for {}, falling back to {}", path, devnode)); udev_device_unref(dev); break; } udev_device_unref(dev); } } udev_enumerate_unref(enumerate); } SP Aquamarine::CSessionDevice::openIfKMS(SP session_, const std::string& path_) { auto dev = makeShared(session_, path_); if (!dev->supportsKMS()) return nullptr; return dev; } SP Aquamarine::CSession::attempt(Hyprutils::Memory::CSharedPointer backend_) { if (!backend_) return nullptr; auto session = makeShared(); session->backend = backend_; session->self = session; backendInUse = backend_; // ------------ Libseat libseat_set_log_handler(libseatLog); libseat_set_log_level(LIBSEAT_LOG_LEVEL_INFO); session->libseatHandle = libseat_open_seat(&libseatListener, session.get()); if (!session->libseatHandle) { session->backend->log(AQ_LOG_ERROR, "libseat: failed to open a seat"); return nullptr; } auto seatName = libseat_seat_name(session->libseatHandle); if (!seatName) { session->backend->log(AQ_LOG_ERROR, "libseat: failed to get seat name"); return nullptr; } session->seatName = seatName; // dispatch any already pending events session->dispatchPendingEventsAsync(); // ----------- Udev session->udevHandle = udev_new(); if (!session->udevHandle) { session->backend->log(AQ_LOG_ERROR, "udev: failed to create a new context"); return nullptr; } session->udevMonitor = udev_monitor_new_from_netlink(session->udevHandle, "udev"); if (!session->udevMonitor) { session->backend->log(AQ_LOG_ERROR, "udev: failed to create a new udevMonitor"); return nullptr; } udev_monitor_filter_add_match_subsystem_devtype(session->udevMonitor, "drm", nullptr); udev_monitor_enable_receiving(session->udevMonitor); // ----------- Libinput session->libinputHandle = libinput_udev_create_context(&libinputListener, session.get(), session->udevHandle); if (!session->libinputHandle) { session->backend->log(AQ_LOG_ERROR, "libinput: failed to create a new context"); return nullptr; } if (libinput_udev_assign_seat(session->libinputHandle, session->seatName.c_str())) { session->backend->log(AQ_LOG_ERROR, "libinput: failed to assign a seat"); return nullptr; } libinput_log_set_handler(session->libinputHandle, ::libinputLog); libinput_log_set_priority(session->libinputHandle, LIBINPUT_LOG_PRIORITY_DEBUG); return session; } Aquamarine::CSession::~CSession() { sessionDevices.clear(); libinputDevices.clear(); if (libinputHandle) libinput_unref(libinputHandle); if (libseatHandle) libseat_close_seat(libseatHandle); if (udevMonitor) udev_monitor_unref(udevMonitor); if (udevHandle) udev_unref(udevHandle); libseatHandle = nullptr; udevMonitor = nullptr; udevHandle = nullptr; } static bool isDRMCard(const char* sysname) { const char prefix[] = DRM_PRIMARY_MINOR_NAME; if (strncmp(sysname, prefix, strlen(prefix)) != 0) return false; for (size_t i = strlen(prefix); sysname[i] != '\0'; i++) { if (sysname[i] < '0' || sysname[i] > '9') return false; } return true; } void Aquamarine::CSession::onReady() { ; } void Aquamarine::CSession::dispatchUdevEvents() { if (!udevHandle || !udevMonitor) return; auto device = udev_monitor_receive_device(udevMonitor); if (!device) return; auto sysname = udev_device_get_sysname(device); auto devnode = udev_device_get_devnode(device); auto action = udev_device_get_action(device); backend->log(AQ_LOG_DEBUG, std::format("udev: new udev {} event for {}", action ? action : "unknown", sysname ? sysname : "unknown")); if (!isDRMCard(sysname) || !action || !devnode) { udev_device_unref(device); return; } dev_t deviceNum = udev_device_get_devnum(device); SP sessionDevice; for (auto const& sDev : sessionDevices) { if (sDev->dev == deviceNum) { sessionDevice = sDev; break; } } if (!sessionDevice && action == std::string{"add"}) { backend->onNewGpu(devnode); udev_device_unref(device); return; } if (!sessionDevice) { udev_device_unref(device); return; } if (action == std::string{"add"}) events.addDrmCard.emit(SAddDrmCardEvent{.path = devnode}); else if (action == std::string{"change"}) { backend->log(AQ_LOG_DEBUG, std::format("udev: DRM device {} changed", sysname ? sysname : "unknown")); CSessionDevice::SChangeEvent event; // auto prop = udev_device_get_property_value(device, "HOTPLUG"); if (prop && prop == std::string{"1"}) { event.type = CSessionDevice::AQ_SESSION_EVENT_CHANGE_HOTPLUG; prop = udev_device_get_property_value(device, "CONNECTOR"); if (prop) event.hotplug.connectorID = std::stoull(prop); prop = udev_device_get_property_value(device, "PROPERTY"); if (prop) event.hotplug.propID = std::stoull(prop); } else if (prop = udev_device_get_property_value(device, "LEASE"); prop && prop == std::string{"1"}) { event.type = CSessionDevice::AQ_SESSION_EVENT_CHANGE_LEASE; } else { backend->log(AQ_LOG_DEBUG, std::format("udev: DRM device {} change event unrecognized", sysname ? sysname : "unknown")); } sessionDevice->events.change.emit(event); } else if (action == std::string{"remove"}) { backend->log(AQ_LOG_DEBUG, std::format("udev: DRM device {} removed", sysname ? sysname : "unknown")); sessionDevice->events.remove.emit(); std::erase_if(sessionDevices, [sessionDevice](const auto& sd) { return sd == sessionDevice; }); } udev_device_unref(device); } void Aquamarine::CSession::dispatchLibinputEvents() { if (!libinputHandle) return; if (int ret = libinput_dispatch(libinputHandle); ret) { backend->log(AQ_LOG_ERROR, std::format("Couldn't dispatch libinput events: {}", strerror(-ret))); return; } libinput_event* event = libinput_get_event(libinputHandle); while (event) { handleLibinputEvent(event); libinput_event_destroy(event); event = libinput_get_event(libinputHandle); } } void Aquamarine::CSession::dispatchLibseatEvents() { if (libseat_dispatch(libseatHandle, 0) == -1) backend->log(AQ_LOG_ERROR, "Couldn't dispatch libseat events"); } void Aquamarine::CSession::dispatchPendingEventsAsync() { dispatchLibseatEvents(); // only linux libudev allows us to asynchronously dispatch outstanding without blocking #if defined(__linux__) dispatchUdevEvents(); #endif dispatchLibinputEvents(); } std::vector> Aquamarine::CSession::pollFDs() { // clang-format off return { makeShared(libseat_get_fd(libseatHandle), [this](){ dispatchLibseatEvents(); }), makeShared(udev_monitor_get_fd(udevMonitor), [this](){ dispatchUdevEvents(); }), makeShared(libinput_get_fd(libinputHandle), [this](){ dispatchLibinputEvents(); }) }; // clang-format on } bool Aquamarine::CSession::switchVT(uint32_t vt) { return libseat_switch_session(libseatHandle, vt) == 0; } void Aquamarine::CSession::handleLibinputEvent(libinput_event* e) { auto device = libinput_event_get_device(e); auto eventType = libinput_event_get_type(e); auto data = libinput_device_get_user_data(device); backend->log(AQ_LOG_TRACE, std::format("libinput: Event {}", (int)eventType)); if (!data && eventType != LIBINPUT_EVENT_DEVICE_ADDED) { backend->log(AQ_LOG_ERROR, "libinput: No aq device in event and not added"); return; } if (!data) { auto dev = libinputDevices.emplace_back(makeShared(device, self)); dev->self = dev; dev->init(); return; } auto hlDevice = ((CLibinputDevice*)data)->self.lock(); switch (eventType) { case LIBINPUT_EVENT_DEVICE_ADDED: /* shouldn't happen */ break; case LIBINPUT_EVENT_DEVICE_REMOVED: std::erase_if(libinputDevices, [device](const auto& d) { return d->device == device; }); break; // --------- keyboard case LIBINPUT_EVENT_KEYBOARD_KEY: { auto kbe = libinput_event_get_keyboard_event(e); hlDevice->keyboard->events.key.emit(IKeyboard::SKeyEvent{ .timeMs = (uint32_t)(libinput_event_keyboard_get_time_usec(kbe) / 1000), .key = libinput_event_keyboard_get_key(kbe), .pressed = libinput_event_keyboard_get_key_state(kbe) == LIBINPUT_KEY_STATE_PRESSED, }); break; } // --------- pointer case LIBINPUT_EVENT_POINTER_MOTION: { auto pe = libinput_event_get_pointer_event(e); hlDevice->mouse->events.move.emit(IPointer::SMoveEvent{ .timeMs = (uint32_t)(libinput_event_pointer_get_time_usec(pe) / 1000), .delta = {libinput_event_pointer_get_dx(pe), libinput_event_pointer_get_dy(pe)}, .unaccel = {libinput_event_pointer_get_dx_unaccelerated(pe), libinput_event_pointer_get_dy_unaccelerated(pe)}, }); hlDevice->mouse->events.frame.emit(); break; } case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: { auto pe = libinput_event_get_pointer_event(e); hlDevice->mouse->events.warp.emit(IPointer::SWarpEvent{ .timeMs = (uint32_t)(libinput_event_pointer_get_time_usec(pe) / 1000), .absolute = {libinput_event_pointer_get_absolute_x_transformed(pe, 1), libinput_event_pointer_get_absolute_y_transformed(pe, 1)}, }); hlDevice->mouse->events.frame.emit(); break; } case LIBINPUT_EVENT_POINTER_BUTTON: { auto pe = libinput_event_get_pointer_event(e); const auto SEATCOUNT = libinput_event_pointer_get_seat_button_count(pe); const bool PRESSED = libinput_event_pointer_get_button_state(pe) == LIBINPUT_BUTTON_STATE_PRESSED; if ((PRESSED && SEATCOUNT != 1) || (!PRESSED && SEATCOUNT != 0)) break; hlDevice->mouse->events.button.emit(IPointer::SButtonEvent{ .timeMs = (uint32_t)(libinput_event_pointer_get_time_usec(pe) / 1000), .button = libinput_event_pointer_get_button(pe), .pressed = PRESSED, }); hlDevice->mouse->events.frame.emit(); break; } case LIBINPUT_EVENT_POINTER_SCROLL_WHEEL: case LIBINPUT_EVENT_POINTER_SCROLL_FINGER: case LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS: { auto pe = libinput_event_get_pointer_event(e); IPointer::SAxisEvent aqe = { .timeMs = (uint32_t)(libinput_event_pointer_get_time_usec(pe) / 1000), }; switch (eventType) { case LIBINPUT_EVENT_POINTER_SCROLL_WHEEL: aqe.source = IPointer::AQ_POINTER_AXIS_SOURCE_WHEEL; break; case LIBINPUT_EVENT_POINTER_SCROLL_FINGER: aqe.source = IPointer::AQ_POINTER_AXIS_SOURCE_FINGER; break; case LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS: aqe.source = IPointer::AQ_POINTER_AXIS_SOURCE_CONTINUOUS; break; default: break; /* unreachable */ } static const std::array LAXES = { LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, }; for (auto const& axis : LAXES) { if (!libinput_event_pointer_has_axis(pe, axis)) continue; aqe.axis = axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL ? IPointer::AQ_POINTER_AXIS_VERTICAL : IPointer::AQ_POINTER_AXIS_HORIZONTAL; aqe.delta = libinput_event_pointer_get_scroll_value(pe, axis); aqe.direction = IPointer::AQ_POINTER_AXIS_RELATIVE_IDENTICAL; if (libinput_device_config_scroll_get_natural_scroll_enabled(device)) aqe.direction = IPointer::AQ_POINTER_AXIS_RELATIVE_INVERTED; if (aqe.source == IPointer::AQ_POINTER_AXIS_SOURCE_WHEEL) aqe.discrete = libinput_event_pointer_get_scroll_value_v120(pe, axis); hlDevice->mouse->events.axis.emit(aqe); } hlDevice->mouse->events.frame.emit(); break; } case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.swipeBegin.emit(IPointer::SSwipeBeginEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .fingers = (uint32_t)libinput_event_gesture_get_finger_count(ge), }); break; } case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.swipeUpdate.emit(IPointer::SSwipeUpdateEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .fingers = (uint32_t)libinput_event_gesture_get_finger_count(ge), .delta = {libinput_event_gesture_get_dx(ge), libinput_event_gesture_get_dy(ge)}, }); break; } case LIBINPUT_EVENT_GESTURE_SWIPE_END: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.swipeEnd.emit(IPointer::SSwipeEndEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .cancelled = (bool)libinput_event_gesture_get_cancelled(ge), }); break; } case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.pinchBegin.emit(IPointer::SPinchBeginEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .fingers = (uint32_t)libinput_event_gesture_get_finger_count(ge), }); break; } case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.pinchUpdate.emit(IPointer::SPinchUpdateEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .fingers = (uint32_t)libinput_event_gesture_get_finger_count(ge), .delta = {libinput_event_gesture_get_dx(ge), libinput_event_gesture_get_dy(ge)}, .scale = libinput_event_gesture_get_scale(ge), .rotation = libinput_event_gesture_get_angle_delta(ge), }); break; } case LIBINPUT_EVENT_GESTURE_PINCH_END: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.pinchEnd.emit(IPointer::SPinchEndEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .cancelled = (bool)libinput_event_gesture_get_cancelled(ge), }); break; } case LIBINPUT_EVENT_GESTURE_HOLD_BEGIN: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.holdBegin.emit(IPointer::SHoldBeginEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .fingers = (uint32_t)libinput_event_gesture_get_finger_count(ge), }); break; } case LIBINPUT_EVENT_GESTURE_HOLD_END: { auto ge = libinput_event_get_gesture_event(e); hlDevice->mouse->events.holdEnd.emit(IPointer::SHoldEndEvent{ .timeMs = (uint32_t)(libinput_event_gesture_get_time_usec(ge) / 1000), .cancelled = (bool)libinput_event_gesture_get_cancelled(ge), }); break; } // --------- touch case LIBINPUT_EVENT_TOUCH_DOWN: { auto te = libinput_event_get_touch_event(e); hlDevice->touch->events.down.emit(ITouch::SDownEvent{ .timeMs = (uint32_t)(libinput_event_touch_get_time_usec(te) / 1000), .touchID = libinput_event_touch_get_seat_slot(te), .pos = {libinput_event_touch_get_x_transformed(te, 1), libinput_event_touch_get_y_transformed(te, 1)}, }); break; } case LIBINPUT_EVENT_TOUCH_UP: { auto te = libinput_event_get_touch_event(e); hlDevice->touch->events.up.emit(ITouch::SUpEvent{ .timeMs = (uint32_t)(libinput_event_touch_get_time_usec(te) / 1000), .touchID = libinput_event_touch_get_seat_slot(te), }); break; } case LIBINPUT_EVENT_TOUCH_MOTION: { auto te = libinput_event_get_touch_event(e); hlDevice->touch->events.move.emit(ITouch::SMotionEvent{ .timeMs = (uint32_t)(libinput_event_touch_get_time_usec(te) / 1000), .touchID = libinput_event_touch_get_seat_slot(te), .pos = {libinput_event_touch_get_x_transformed(te, 1), libinput_event_touch_get_y_transformed(te, 1)}, }); break; } case LIBINPUT_EVENT_TOUCH_CANCEL: { auto te = libinput_event_get_touch_event(e); hlDevice->touch->events.cancel.emit(ITouch::SCancelEvent{ .timeMs = (uint32_t)(libinput_event_touch_get_time_usec(te) / 1000), .touchID = libinput_event_touch_get_seat_slot(te), }); break; } case LIBINPUT_EVENT_TOUCH_FRAME: { hlDevice->touch->events.frame.emit(); break; } // --------- switch case LIBINPUT_EVENT_SWITCH_TOGGLE: { auto se = libinput_event_get_switch_event(e); const bool ENABLED = libinput_event_switch_get_switch_state(se) == LIBINPUT_SWITCH_STATE_ON; if (ENABLED == hlDevice->switchy->state) return; hlDevice->switchy->state = ENABLED; switch (libinput_event_switch_get_switch(se)) { case LIBINPUT_SWITCH_LID: hlDevice->switchy->type = ISwitch::AQ_SWITCH_TYPE_LID; break; case LIBINPUT_SWITCH_TABLET_MODE: hlDevice->switchy->type = ISwitch::AQ_SWITCH_TYPE_TABLET_MODE; break; } hlDevice->switchy->events.fire.emit(ISwitch::SFireEvent{ .timeMs = (uint32_t)(libinput_event_switch_get_time_usec(se) / 1000), .type = hlDevice->switchy->type, .enable = ENABLED, }); break; } // --------- tablet case LIBINPUT_EVENT_TABLET_PAD_BUTTON: { auto tpe = libinput_event_get_tablet_pad_event(e); hlDevice->tabletPad->events.button.emit(ITabletPad::SButtonEvent{ .timeMs = (uint32_t)(libinput_event_tablet_pad_get_time_usec(tpe) / 1000), .button = libinput_event_tablet_pad_get_button_number(tpe), .down = libinput_event_tablet_pad_get_button_state(tpe) == LIBINPUT_BUTTON_STATE_PRESSED, .mode = (uint16_t)libinput_event_tablet_pad_get_mode(tpe), .group = (uint16_t)libinput_tablet_pad_mode_group_get_index(libinput_event_tablet_pad_get_mode_group(tpe)), }); break; } case LIBINPUT_EVENT_TABLET_PAD_RING: { auto tpe = libinput_event_get_tablet_pad_event(e); hlDevice->tabletPad->events.ring.emit(ITabletPad::SRingEvent{ .timeMs = (uint32_t)(libinput_event_tablet_pad_get_time_usec(tpe) / 1000), .source = libinput_event_tablet_pad_get_ring_source(tpe) == LIBINPUT_TABLET_PAD_RING_SOURCE_UNKNOWN ? ITabletPad::AQ_TABLET_PAD_RING_SOURCE_UNKNOWN : ITabletPad::AQ_TABLET_PAD_RING_SOURCE_FINGER, .ring = (uint16_t)libinput_event_tablet_pad_get_ring_number(tpe), .pos = libinput_event_tablet_pad_get_ring_position(tpe), .mode = (uint16_t)libinput_event_tablet_pad_get_mode(tpe), }); break; } case LIBINPUT_EVENT_TABLET_PAD_STRIP: { auto tpe = libinput_event_get_tablet_pad_event(e); hlDevice->tabletPad->events.strip.emit(ITabletPad::SStripEvent{ .timeMs = (uint32_t)(libinput_event_tablet_pad_get_time_usec(tpe) / 1000), .source = libinput_event_tablet_pad_get_strip_source(tpe) == LIBINPUT_TABLET_PAD_STRIP_SOURCE_UNKNOWN ? ITabletPad::AQ_TABLET_PAD_STRIP_SOURCE_UNKNOWN : ITabletPad::AQ_TABLET_PAD_STRIP_SOURCE_FINGER, .strip = (uint16_t)libinput_event_tablet_pad_get_strip_number(tpe), .pos = libinput_event_tablet_pad_get_strip_position(tpe), .mode = (uint16_t)libinput_event_tablet_pad_get_mode(tpe), }); break; } case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: { auto tte = libinput_event_get_tablet_tool_event(e); auto tool = hlDevice->toolFrom(libinput_event_tablet_tool_get_tool(tte)); hlDevice->tablet->events.proximity.emit(ITablet::SProximityEvent{ .tool = tool, .timeMs = (uint32_t)(libinput_event_tablet_tool_get_time_usec(tte) / 1000), .absolute = {libinput_event_tablet_tool_get_x_transformed(tte, 1), libinput_event_tablet_tool_get_y_transformed(tte, 1)}, .in = libinput_event_tablet_tool_get_proximity_state(tte) == LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN, }); if (libinput_event_tablet_tool_get_proximity_state(tte) == LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN) handleLibinputTabletToolAxis(e); if (!libinput_tablet_tool_is_unique(libinput_event_tablet_tool_get_tool(tte)) && libinput_event_tablet_tool_get_proximity_state(tte) == LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT) std::erase(hlDevice->tabletTools, tool); break; } case LIBINPUT_EVENT_TABLET_TOOL_AXIS: { handleLibinputTabletToolAxis(e); break; } case LIBINPUT_EVENT_TABLET_TOOL_TIP: { auto tte = libinput_event_get_tablet_tool_event(e); auto tool = hlDevice->toolFrom(libinput_event_tablet_tool_get_tool(tte)); handleLibinputTabletToolAxis(e); hlDevice->tablet->events.tip.emit(ITablet::STipEvent{ .tool = tool, .timeMs = (uint32_t)(libinput_event_tablet_tool_get_time_usec(tte) / 1000), .absolute = {libinput_event_tablet_tool_get_x_transformed(tte, 1), libinput_event_tablet_tool_get_y_transformed(tte, 1)}, .down = libinput_event_tablet_tool_get_tip_state(tte) == LIBINPUT_TABLET_TOOL_TIP_DOWN, }); break; } case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: { auto tte = libinput_event_get_tablet_tool_event(e); auto tool = hlDevice->toolFrom(libinput_event_tablet_tool_get_tool(tte)); handleLibinputTabletToolAxis(e); hlDevice->tablet->events.button.emit(ITablet::SButtonEvent{ .tool = tool, .timeMs = (uint32_t)(libinput_event_tablet_tool_get_time_usec(tte) / 1000), .button = libinput_event_tablet_tool_get_button(tte), .down = libinput_event_tablet_tool_get_button_state(tte) == LIBINPUT_BUTTON_STATE_PRESSED, }); break; } default: break; } } void Aquamarine::CSession::handleLibinputTabletToolAxis(libinput_event* e) { auto device = libinput_event_get_device(e); auto data = libinput_device_get_user_data(device); auto hlDevice = ((CLibinputDevice*)data)->self.lock(); auto tte = libinput_event_get_tablet_tool_event(e); auto tool = hlDevice->toolFrom(libinput_event_tablet_tool_get_tool(tte)); ITablet::SAxisEvent event = { .tool = tool, .timeMs = (uint32_t)(libinput_event_tablet_tool_get_time_usec(tte) / 1000), }; if (libinput_event_tablet_tool_x_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_X; event.absolute.x = libinput_event_tablet_tool_get_x_transformed(tte, 1); event.delta.x = libinput_event_tablet_tool_get_dx(tte); } if (libinput_event_tablet_tool_y_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_Y; event.absolute.y = libinput_event_tablet_tool_get_y_transformed(tte, 1); event.delta.y = libinput_event_tablet_tool_get_dy(tte); } if (libinput_event_tablet_tool_pressure_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_PRESSURE; event.pressure = libinput_event_tablet_tool_get_pressure(tte); } if (libinput_event_tablet_tool_distance_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_DISTANCE; event.distance = libinput_event_tablet_tool_get_distance(tte); } if (libinput_event_tablet_tool_tilt_x_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_TILT_X; event.tilt.x = libinput_event_tablet_tool_get_tilt_x(tte); } if (libinput_event_tablet_tool_tilt_y_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_TILT_Y; event.tilt.y = libinput_event_tablet_tool_get_tilt_y(tte); } if (libinput_event_tablet_tool_rotation_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_ROTATION; event.rotation = libinput_event_tablet_tool_get_rotation(tte); } if (libinput_event_tablet_tool_slider_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_SLIDER; event.slider = libinput_event_tablet_tool_get_slider_position(tte); } if (libinput_event_tablet_tool_wheel_has_changed(tte)) { event.updatedAxes |= AQ_TABLET_TOOL_AXIS_WHEEL; event.wheelDelta = libinput_event_tablet_tool_get_wheel_delta(tte); } hlDevice->tablet->events.axis.emit(event); } Aquamarine::CLibinputDevice::CLibinputDevice(libinput_device* device_, Hyprutils::Memory::CWeakPointer session_) : device(device_), session(session_) { ; } void Aquamarine::CLibinputDevice::init() { const auto VENDOR = libinput_device_get_id_vendor(device); const auto PRODUCT = libinput_device_get_id_product(device); const auto NAME = libinput_device_get_name(device); session->backend->log(AQ_LOG_DEBUG, std::format("libinput: New device {}: {}-{}", NAME ? NAME : "Unknown", VENDOR, PRODUCT)); name = NAME; libinput_device_ref(device); libinput_device_set_user_data(device, this); if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_KEYBOARD)) { keyboard = makeShared(self.lock()); if (session->backend->ready) session->backend->events.newKeyboard.emit(SP(keyboard)); } if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER)) { mouse = makeShared(self.lock()); if (session->backend->ready) session->backend->events.newPointer.emit(SP(mouse)); } if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) { touch = makeShared(self.lock()); if (session->backend->ready) session->backend->events.newTouch.emit(SP(touch)); } if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_SWITCH)) { switchy = makeShared(self.lock()); if (session->backend->ready) session->backend->events.newSwitch.emit(SP(switchy)); } if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TABLET_TOOL)) { tablet = makeShared(self.lock()); if (session->backend->ready) session->backend->events.newTablet.emit(SP(tablet)); } if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TABLET_PAD)) { tabletPad = makeShared(self.lock()); if (session->backend->ready) session->backend->events.newTabletPad.emit(SP(tabletPad)); } } Aquamarine::CLibinputDevice::~CLibinputDevice() { libinput_device_set_user_data(device, nullptr); libinput_device_unref(device); } SP Aquamarine::CLibinputDevice::toolFrom(libinput_tablet_tool* tool) { for (auto const& t : tabletTools) { if (t->libinputTool == tool) return t; } auto newt = makeShared(self.lock(), tool); tabletTools.emplace_back(newt); if (session->backend->ready) session->backend->events.newTabletTool.emit(SP(newt)); return newt; } static ITabletTool::eTabletToolType aqTypeFromLibinput(libinput_tablet_tool_type value) { switch (value) { case LIBINPUT_TABLET_TOOL_TYPE_PEN: return ITabletTool::AQ_TABLET_TOOL_TYPE_PEN; case LIBINPUT_TABLET_TOOL_TYPE_ERASER: return ITabletTool::AQ_TABLET_TOOL_TYPE_ERASER; case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: return ITabletTool::AQ_TABLET_TOOL_TYPE_BRUSH; case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: return ITabletTool::AQ_TABLET_TOOL_TYPE_PENCIL; case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: return ITabletTool::AQ_TABLET_TOOL_TYPE_AIRBRUSH; case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: return ITabletTool::AQ_TABLET_TOOL_TYPE_MOUSE; case LIBINPUT_TABLET_TOOL_TYPE_LENS: return ITabletTool::AQ_TABLET_TOOL_TYPE_LENS; case LIBINPUT_TABLET_TOOL_TYPE_TOTEM: return ITabletTool::AQ_TABLET_TOOL_TYPE_TOTEM; } return ITabletTool::AQ_TABLET_TOOL_TYPE_INVALID; } Aquamarine::CLibinputKeyboard::CLibinputKeyboard(SP dev) : device(dev) { libinput_device_led_update(device->device, (libinput_led)0); } libinput_device* Aquamarine::CLibinputKeyboard::getLibinputHandle() { if (!device) return nullptr; return device->device; } const std::string& Aquamarine::CLibinputKeyboard::getName() { if (!device) return AQ_UNKNOWN_DEVICE_NAME; return device->name; } void Aquamarine::CLibinputKeyboard::updateLEDs(uint32_t leds) { libinput_device_led_update(device->device, (libinput_led)leds); } Aquamarine::CLibinputMouse::CLibinputMouse(Hyprutils::Memory::CSharedPointer dev) : device(dev) { ; } libinput_device* Aquamarine::CLibinputMouse::getLibinputHandle() { if (!device) return nullptr; return device->device; } const std::string& Aquamarine::CLibinputMouse::getName() { if (!device) return AQ_UNKNOWN_DEVICE_NAME; return device->name; } Aquamarine::CLibinputTouch::CLibinputTouch(Hyprutils::Memory::CSharedPointer dev) : device(dev) { double w = 0, h = 0; libinput_device_get_size(dev->device, &w, &h); physicalSize = {w, h}; } libinput_device* Aquamarine::CLibinputTouch::getLibinputHandle() { if (!device) return nullptr; return device->device; } const std::string& Aquamarine::CLibinputTouch::getName() { if (!device) return AQ_UNKNOWN_DEVICE_NAME; return device->name; } Aquamarine::CLibinputSwitch::CLibinputSwitch(Hyprutils::Memory::CSharedPointer dev) : device(dev) { ; } libinput_device* Aquamarine::CLibinputSwitch::getLibinputHandle() { if (!device) return nullptr; return device->device; } const std::string& Aquamarine::CLibinputSwitch::getName() { if (!device) return AQ_UNKNOWN_DEVICE_NAME; return device->name; } Aquamarine::CLibinputTablet::CLibinputTablet(Hyprutils::Memory::CSharedPointer dev) : device(dev) { if (libinput_device_get_id_bustype(device->device) == BUS_USB) { usbVendorID = libinput_device_get_id_vendor(device->device); usbProductID = libinput_device_get_id_product(device->device); } double w = 0, h = 0; libinput_device_get_size(dev->device, &w, &h); physicalSize = {w, h}; auto udevice = libinput_device_get_udev_device(device->device); paths.emplace_back(udev_device_get_syspath(udevice)); } libinput_device* Aquamarine::CLibinputTablet::getLibinputHandle() { if (!device) return nullptr; return device->device; } const std::string& Aquamarine::CLibinputTablet::getName() { if (!device) return AQ_UNKNOWN_DEVICE_NAME; return device->name; } Aquamarine::CLibinputTabletTool::CLibinputTabletTool(Hyprutils::Memory::CSharedPointer dev, libinput_tablet_tool* tool) : device(dev), libinputTool(tool) { type = aqTypeFromLibinput(libinput_tablet_tool_get_type(libinputTool)); serial = libinput_tablet_tool_get_serial(libinputTool); id = libinput_tablet_tool_get_tool_id(libinputTool); libinput_tablet_tool_ref(tool); capabilities = 0; if (libinput_tablet_tool_has_distance(tool)) capabilities |= AQ_TABLET_TOOL_CAPABILITY_DISTANCE; if (libinput_tablet_tool_has_pressure(tool)) capabilities |= AQ_TABLET_TOOL_CAPABILITY_PRESSURE; if (libinput_tablet_tool_has_tilt(tool)) capabilities |= AQ_TABLET_TOOL_CAPABILITY_TILT; if (libinput_tablet_tool_has_rotation(tool)) capabilities |= AQ_TABLET_TOOL_CAPABILITY_ROTATION; if (libinput_tablet_tool_has_slider(tool)) capabilities |= AQ_TABLET_TOOL_CAPABILITY_SLIDER; if (libinput_tablet_tool_has_wheel(tool)) capabilities |= AQ_TABLET_TOOL_CAPABILITY_WHEEL; libinput_tablet_tool_set_user_data(tool, this); } Aquamarine::CLibinputTabletTool::~CLibinputTabletTool() { libinput_tablet_tool_unref(libinputTool); } libinput_device* Aquamarine::CLibinputTabletTool::getLibinputHandle() { if (!device) return nullptr; return device->device; } const std::string& Aquamarine::CLibinputTabletTool::getName() { if (!device) return AQ_UNKNOWN_DEVICE_NAME; return device->name; } Aquamarine::CLibinputTabletPad::CLibinputTabletPad(Hyprutils::Memory::CSharedPointer dev) : device(dev) { buttons = libinput_device_tablet_pad_get_num_buttons(device->device); rings = libinput_device_tablet_pad_get_num_rings(device->device); strips = libinput_device_tablet_pad_get_num_strips(device->device); auto udevice = libinput_device_get_udev_device(device->device); paths.emplace_back(udev_device_get_syspath(udevice)); int groupsno = libinput_device_tablet_pad_get_num_mode_groups(device->device); for (int i = 0; i < groupsno; ++i) { auto g = createGroupFromID(i); if (g) groups.emplace_back(g); } } Aquamarine::CLibinputTabletPad::~CLibinputTabletPad() { int groups = libinput_device_tablet_pad_get_num_mode_groups(device->device); for (int i = 0; i < groups; ++i) { auto g = libinput_device_tablet_pad_get_mode_group(device->device, i); libinput_tablet_pad_mode_group_unref(g); } } libinput_device* Aquamarine::CLibinputTabletPad::getLibinputHandle() { if (!device) return nullptr; return device->device; } const std::string& Aquamarine::CLibinputTabletPad::getName() { if (!device) return AQ_UNKNOWN_DEVICE_NAME; return device->name; } SP Aquamarine::CLibinputTabletPad::createGroupFromID(int id) { auto libinputGroup = libinput_device_tablet_pad_get_mode_group(device->device, id); auto group = makeShared(); for (size_t i = 0; i < rings; ++i) { if (!libinput_tablet_pad_mode_group_has_ring(libinputGroup, i)) continue; group->rings.push_back(i); } for (size_t i = 0; i < strips; ++i) { if (!libinput_tablet_pad_mode_group_has_strip(libinputGroup, i)) continue; group->strips.push_back(i); } for (size_t i = 0; i < buttons; ++i) { if (!libinput_tablet_pad_mode_group_has_button(libinputGroup, i)) continue; group->buttons.push_back(i); } group->modes = libinput_tablet_pad_mode_group_get_num_modes(libinputGroup); return group; } hyprwm-aquamarine-655e067/src/backend/Wayland.cpp000066400000000000000000000736621506775317200217600ustar00rootroot00000000000000#include #include #include #include #include "Shared.hpp" #include "FormatUtils.hpp" #include #include #include #include #include #include using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; #define SP CSharedPointer static std::pair openExclusiveShm() { // Only absolute paths can be shared across different shm_open() calls srand(time(nullptr)); std::string name = std::format("/aq{:x}", rand() % RAND_MAX); for (size_t i = 0; i < 69; ++i) { int fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600); if (fd >= 0) return {fd, name}; } return {-1, ""}; } static int allocateSHMFile(size_t len) { auto [fd, name] = openExclusiveShm(); if (fd < 0) return -1; shm_unlink(name.c_str()); int ret; do { ret = ftruncate(fd, len); } while (ret < 0 && errno == EINTR); if (ret < 0) { close(fd); return -1; } return fd; } wl_shm_format shmFormatFromDRM(uint32_t drmFormat) { switch (drmFormat) { case DRM_FORMAT_XRGB8888: return WL_SHM_FORMAT_XRGB8888; case DRM_FORMAT_ARGB8888: return WL_SHM_FORMAT_ARGB8888; default: return (wl_shm_format)drmFormat; } return (wl_shm_format)drmFormat; } Aquamarine::CWaylandBackend::~CWaylandBackend() { if (drmState.fd >= 0) close(drmState.fd); } eBackendType Aquamarine::CWaylandBackend::type() { return AQ_BACKEND_WAYLAND; } Aquamarine::CWaylandBackend::CWaylandBackend(SP backend_) : backend(backend_) { ; } bool Aquamarine::CWaylandBackend::start() { backend->log(AQ_LOG_DEBUG, "Starting the Wayland backend!"); waylandState.display = wl_display_connect(nullptr); if (!waylandState.display) { backend->log(AQ_LOG_ERROR, "Wayland backend cannot start: wl_display_connect failed (is a wayland compositor running?)"); return false; } auto XDGCURRENTDESKTOP = getenv("XDG_CURRENT_DESKTOP"); backend->log(AQ_LOG_DEBUG, std::format("Connected to a wayland compositor: {}", (XDGCURRENTDESKTOP ? XDGCURRENTDESKTOP : "unknown (XDG_CURRENT_DEKSTOP unset?)"))); waylandState.registry = makeShared((wl_proxy*)wl_display_get_registry(waylandState.display)); backend->log(AQ_LOG_DEBUG, std::format("Got registry at 0x{:x}", (uintptr_t)waylandState.registry->resource())); waylandState.registry->setGlobal([this](CCWlRegistry* r, uint32_t id, const char* name, uint32_t version) { TRACE(backend->log(AQ_LOG_TRACE, std::format(" | received global: {} (version {}) with id {}", name, version, id))); const std::string NAME = name; if (NAME == "wl_seat") { TRACE(backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 9, id))); waylandState.seat = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &wl_seat_interface, 9)); initSeat(); } else if (NAME == "xdg_wm_base") { TRACE(backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 6, id))); waylandState.xdg = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &xdg_wm_base_interface, 6)); initShell(); } else if (NAME == "wl_compositor") { TRACE(backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 6, id))); waylandState.compositor = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &wl_compositor_interface, 6)); } else if (NAME == "wl_shm") { TRACE(backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 1, id))); waylandState.shm = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &wl_shm_interface, 1)); } else if (NAME == "zwp_linux_dmabuf_v1") { TRACE(backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 4, id))); waylandState.dmabuf = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &zwp_linux_dmabuf_v1_interface, 4)); if (!initDmabuf()) { backend->log(AQ_LOG_ERROR, "Wayland backend cannot start: zwp_linux_dmabuf_v1 init failed"); waylandState.dmabufFailed = true; } } }); waylandState.registry->setGlobalRemove([this](CCWlRegistry* r, uint32_t id) { backend->log(AQ_LOG_DEBUG, std::format("Global {} removed", id)); }); wl_display_roundtrip(waylandState.display); if (!waylandState.xdg || !waylandState.compositor || !waylandState.seat || !waylandState.dmabuf || waylandState.dmabufFailed || !waylandState.shm) { backend->log(AQ_LOG_ERROR, "Wayland backend cannot start: Missing protocols"); return false; } dispatchEvents(); createOutput(); return true; } int Aquamarine::CWaylandBackend::drmFD() { return drmState.fd; } int Aquamarine::CWaylandBackend::drmRenderNodeFD() { // creation already attempts to use the rendernode, so just return same fd as drmFD(). return drmState.fd; } bool Aquamarine::CWaylandBackend::createOutput(const std::string& szName) { auto o = outputs.emplace_back(SP(new CWaylandOutput(szName.empty() ? std::format("WAYLAND-{}", ++lastOutputID) : szName, self))); o->self = o; if (backend->ready) o->swapchain = CSwapchain::create(backend->primaryAllocator, self.lock()); idleCallbacks.emplace_back([this, o]() { backend->events.newOutput.emit(SP(o)); }); return true; } std::vector> Aquamarine::CWaylandBackend::pollFDs() { if (!waylandState.display) return {}; return {makeShared(wl_display_get_fd(waylandState.display), [this]() { dispatchEvents(); })}; } bool Aquamarine::CWaylandBackend::dispatchEvents() { wl_display_flush(waylandState.display); if (wl_display_prepare_read(waylandState.display) == 0) { wl_display_read_events(waylandState.display); wl_display_dispatch_pending(waylandState.display); } else wl_display_dispatch(waylandState.display); int ret = 0; do { ret = wl_display_dispatch_pending(waylandState.display); wl_display_flush(waylandState.display); } while (ret > 0); // dispatch frames if (backend->ready) { for (auto const& f : idleCallbacks) { f(); } idleCallbacks.clear(); } return true; } uint32_t Aquamarine::CWaylandBackend::capabilities() { return AQ_BACKEND_CAPABILITY_POINTER; } bool Aquamarine::CWaylandBackend::setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot) { // TODO: return true; } void Aquamarine::CWaylandBackend::onReady() { for (auto const& o : outputs) { o->swapchain = CSwapchain::create(backend->primaryAllocator, self.lock()); if (!o->swapchain) { backend->log(AQ_LOG_ERROR, std::format("Output {} failed: swapchain creation failed", o->name)); continue; } } } Aquamarine::CWaylandKeyboard::CWaylandKeyboard(SP keyboard_, Hyprutils::Memory::CWeakPointer backend_) : keyboard(keyboard_), backend(backend_) { if (!keyboard->resource()) return; backend->backend->log(AQ_LOG_DEBUG, "New wayland keyboard wl_keyboard"); keyboard->setKey([this](CCWlKeyboard* r, uint32_t serial, uint32_t timeMs, uint32_t key, wl_keyboard_key_state state) { events.key.emit(SKeyEvent{ .timeMs = timeMs, .key = key, .pressed = state == WL_KEYBOARD_KEY_STATE_PRESSED, }); }); keyboard->setModifiers([this](CCWlKeyboard* r, uint32_t serial, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) { events.modifiers.emit(SModifiersEvent{ .depressed = depressed, .latched = latched, .locked = locked, .group = group, }); }); } Aquamarine::CWaylandKeyboard::~CWaylandKeyboard() { ; } const std::string& Aquamarine::CWaylandKeyboard::getName() { return name; } Aquamarine::CWaylandPointer::CWaylandPointer(SP pointer_, Hyprutils::Memory::CWeakPointer backend_) : pointer(pointer_), backend(backend_) { if (!pointer->resource()) return; backend->backend->log(AQ_LOG_DEBUG, "New wayland pointer wl_pointer"); pointer->setMotion([this](CCWlPointer* r, uint32_t serial, wl_fixed_t x, wl_fixed_t y) { const auto STATE = backend->focusedOutput->state->state(); if (!backend->focusedOutput || (!STATE.mode && !STATE.customMode)) return; const Vector2D size = STATE.customMode ? STATE.customMode->pixelSize : STATE.mode->pixelSize; Vector2D local = {wl_fixed_to_double(x), wl_fixed_to_double(y)}; local = local / size; timespec now; clock_gettime(CLOCK_MONOTONIC, &now); events.warp.emit(SWarpEvent{ .timeMs = (uint32_t)(now.tv_sec * 1000 + now.tv_nsec / 1000000), .absolute = local, }); }); pointer->setEnter([this](CCWlPointer* r, uint32_t serial, wl_proxy* surface, wl_fixed_t x, wl_fixed_t y) { backend->lastEnterSerial = serial; for (auto const& o : backend->outputs) { if (o->waylandState.surface->resource() != surface) continue; backend->focusedOutput = o; backend->backend->log(AQ_LOG_DEBUG, std::format("[wayland] focus changed: {}", o->name)); o->onEnter(pointer, serial); break; } }); pointer->setLeave([this](CCWlPointer* r, uint32_t serial, wl_proxy* surface) { for (auto const& o : backend->outputs) { if (o->waylandState.surface->resource() != surface) continue; o->cursorState.serial = 0; } }); pointer->setButton([this](CCWlPointer* r, uint32_t serial, uint32_t timeMs, uint32_t button, wl_pointer_button_state state) { events.button.emit(SButtonEvent{ .timeMs = timeMs, .button = button, .pressed = state == WL_POINTER_BUTTON_STATE_PRESSED, }); }); pointer->setAxis([this](CCWlPointer* r, uint32_t timeMs, wl_pointer_axis axis, wl_fixed_t value) { events.axis.emit(SAxisEvent{ .timeMs = timeMs, .axis = axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL ? AQ_POINTER_AXIS_HORIZONTAL : AQ_POINTER_AXIS_VERTICAL, .delta = wl_fixed_to_double(value), }); }); pointer->setFrame([this](CCWlPointer* r) { events.frame.emit(); }); } Aquamarine::CWaylandPointer::~CWaylandPointer() { ; } const std::string& Aquamarine::CWaylandPointer::getName() { return name; } void Aquamarine::CWaylandBackend::initSeat() { waylandState.seat->setCapabilities([this](CCWlSeat* r, wl_seat_capability cap) { const bool HAS_KEYBOARD = ((uint32_t)cap) & WL_SEAT_CAPABILITY_KEYBOARD; const bool HAS_POINTER = ((uint32_t)cap) & WL_SEAT_CAPABILITY_POINTER; if (HAS_KEYBOARD && keyboards.empty()) { auto k = keyboards.emplace_back(makeShared(makeShared(waylandState.seat->sendGetKeyboard()), self)); idleCallbacks.emplace_back([this, k]() { backend->events.newKeyboard.emit(SP(k)); }); } else if (!HAS_KEYBOARD && !keyboards.empty()) keyboards.clear(); if (HAS_POINTER && pointers.empty()) { auto p = pointers.emplace_back(makeShared(makeShared(waylandState.seat->sendGetPointer()), self)); idleCallbacks.emplace_back([this, p]() { backend->events.newPointer.emit(SP(p)); }); } else if (!HAS_POINTER && !pointers.empty()) pointers.clear(); }); } void Aquamarine::CWaylandBackend::initShell() { waylandState.xdg->setPing([](CCXdgWmBase* r, uint32_t serial) { r->sendPong(serial); }); } bool Aquamarine::CWaylandBackend::initDmabuf() { waylandState.dmabufFeedback = makeShared(waylandState.dmabuf->sendGetDefaultFeedback()); if (!waylandState.dmabufFeedback) { backend->log(AQ_LOG_ERROR, "initDmabuf: failed to get default feedback"); return false; } waylandState.dmabufFeedback->setDone([this](CCZwpLinuxDmabufFeedbackV1* r) { // no-op backend->log(AQ_LOG_DEBUG, "zwp_linux_dmabuf_v1: Got done"); }); waylandState.dmabufFeedback->setMainDevice([this](CCZwpLinuxDmabufFeedbackV1* r, wl_array* deviceArr) { backend->log(AQ_LOG_DEBUG, "zwp_linux_dmabuf_v1: Got main device"); dev_t device; ASSERT(deviceArr->size == sizeof(device)); memcpy(&device, deviceArr->data, sizeof(device)); drmDevice* drmDev; if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) { backend->log(AQ_LOG_ERROR, "zwp_linux_dmabuf_v1: drmGetDeviceFromDevId failed"); return; } const char* name = nullptr; if (drmDev->available_nodes & (1 << DRM_NODE_RENDER)) name = drmDev->nodes[DRM_NODE_RENDER]; else { // Likely a split display/render setup. Pick the primary node and hope // Mesa will open the right render node under-the-hood. ASSERT(drmDev->available_nodes & (1 << DRM_NODE_PRIMARY)); name = drmDev->nodes[DRM_NODE_PRIMARY]; backend->log(AQ_LOG_WARNING, "zwp_linux_dmabuf_v1: DRM device has no render node, using primary."); } if (!name) { backend->log(AQ_LOG_ERROR, "zwp_linux_dmabuf_v1: no node name"); return; } drmState.nodeName = name; drmFreeDevice(&drmDev); backend->log(AQ_LOG_DEBUG, std::format("zwp_linux_dmabuf_v1: Got node {}", drmState.nodeName)); }); waylandState.dmabufFeedback->setFormatTable([this](CCZwpLinuxDmabufFeedbackV1* r, int32_t fd, uint32_t size) { #pragma pack(push, 1) struct wlDrmFormatMarshalled { uint32_t drmFormat; char pad[4]; uint64_t modifier; }; #pragma pack(pop) static_assert(sizeof(wlDrmFormatMarshalled) == 16); auto formatTable = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); if (formatTable == MAP_FAILED) { backend->log(AQ_LOG_ERROR, std::format("zwp_linux_dmabuf_v1: Failed to mmap the format table")); return; } const auto FORMATS = (wlDrmFormatMarshalled*)formatTable; for (size_t i = 0; i < size / 16; ++i) { auto& fmt = FORMATS[i]; auto modName = drmGetFormatModifierName(fmt.modifier); backend->log(AQ_LOG_DEBUG, std::format("zwp_linux_dmabuf_v1: Got format {} with modifier {}", fourccToName(fmt.drmFormat), modName ? modName : "UNKNOWN")); free(modName); auto it = std::ranges::find_if(dmabufFormats, [&fmt](const auto& e) { return e.drmFormat == fmt.drmFormat; }); if (it == dmabufFormats.end()) { dmabufFormats.emplace_back(SDRMFormat{.drmFormat = fmt.drmFormat, .modifiers = {fmt.modifier}}); continue; } it->modifiers.emplace_back(fmt.modifier); } munmap(formatTable, size); }); wl_display_roundtrip(waylandState.display); if (!drmState.nodeName.empty()) { drmState.fd = open(drmState.nodeName.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC); if (drmState.fd < 0) { backend->log(AQ_LOG_ERROR, std::format("zwp_linux_dmabuf_v1: Failed to open node {}", drmState.nodeName)); return false; } backend->log(AQ_LOG_DEBUG, std::format("zwp_linux_dmabuf_v1: opened node {} with fd {}", drmState.nodeName, drmState.fd)); } return true; } std::vector Aquamarine::CWaylandBackend::getRenderFormats() { return dmabufFormats; } std::vector Aquamarine::CWaylandBackend::getCursorFormats() { return dmabufFormats; } SP Aquamarine::CWaylandBackend::preferredAllocator() { return backend->primaryAllocator; } std::vector> Aquamarine::CWaylandBackend::getAllocators() { return {backend->primaryAllocator}; } Hyprutils::Memory::CWeakPointer Aquamarine::CWaylandBackend::getPrimary() { return {}; } Aquamarine::CWaylandOutput::CWaylandOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_) : backend(backend_) { name = name_; waylandState.surface = makeShared(backend->waylandState.compositor->sendCreateSurface()); if (!waylandState.surface->resource()) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {} failed: no surface given. Errno: {}", name, errno)); return; } waylandState.xdgSurface = makeShared(backend->waylandState.xdg->sendGetXdgSurface(waylandState.surface->resource())); if (!waylandState.xdgSurface->resource()) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {} failed: no xdgSurface given. Errno: {}", name, errno)); return; } waylandState.xdgSurface->setConfigure([this](CCXdgSurface* r, uint32_t serial) { backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: configure surface with {}", name, serial)); r->sendAckConfigure(serial); }); waylandState.xdgToplevel = makeShared(waylandState.xdgSurface->sendGetToplevel()); if (!waylandState.xdgToplevel->resource()) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {} failed: no xdgToplevel given. Errno: {}", name, errno)); return; } waylandState.xdgToplevel->setWmCapabilities( [this](CCXdgToplevel* r, wl_array* arr) { backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: wm_capabilities received", name)); }); waylandState.xdgToplevel->setConfigure([this](CCXdgToplevel* r, int32_t w, int32_t h, wl_array* arr) { backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: configure toplevel with {}x{}", name, w, h)); if (w == 0 || h == 0) { backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: w/h is 0, sending default hardcoded 1280x720", name)); w = 1280; h = 720; } events.state.emit(SStateEvent{.size = {w, h}}); sendFrameAndSetCallback(); }); waylandState.xdgToplevel->setClose([this](CCXdgToplevel* r) { destroy(); }); waylandState.xdgToplevel->sendSetTitle(std::format("aquamarine - {}", name).c_str()); waylandState.xdgToplevel->sendSetAppId("aquamarine"); auto inputRegion = makeShared(backend->waylandState.compositor->sendCreateRegion()); inputRegion->sendAdd(0, 0, INT32_MAX, INT32_MAX); waylandState.surface->sendSetInputRegion(inputRegion.get()); waylandState.surface->sendAttach(nullptr, 0, 0); waylandState.surface->sendCommit(); inputRegion->sendDestroy(); backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: initialized", name)); } Aquamarine::CWaylandOutput::~CWaylandOutput() { events.destroy.emit(); if (waylandState.xdgToplevel) waylandState.xdgToplevel->sendDestroy(); if (waylandState.xdgSurface) waylandState.xdgSurface->sendDestroy(); if (waylandState.surface) waylandState.surface->sendDestroy(); } std::vector Aquamarine::CWaylandOutput::getRenderFormats() { // TODO // this is technically wrong because this returns the format table formats // the actually supported formats are given by tranche formats return backend->getRenderFormats(); } bool Aquamarine::CWaylandOutput::destroy() { events.destroy.emit(); waylandState.surface->sendAttach(nullptr, 0, 0); waylandState.surface->sendCommit(); waylandState.frameCallback.reset(); std::erase(backend->outputs, self.lock()); return true; } bool Aquamarine::CWaylandOutput::test() { return true; // TODO: } bool Aquamarine::CWaylandOutput::commit() { Vector2D pixelSize = {}; if (state->internalState.customMode) pixelSize = state->internalState.customMode->pixelSize; else if (state->internalState.mode) pixelSize = state->internalState.mode->pixelSize; else { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: pending state rejected: invalid mode", name)); return false; } uint32_t format = state->internalState.drmFormat; if (format == DRM_FORMAT_INVALID) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: pending state rejected: invalid format", name)); return false; } if (!swapchain) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: no swapchain, lying because it will soon be here", name)); return true; } if (!swapchain->reconfigure(SSwapchainOptions{.length = swapchain->currentOptions().length, .size = pixelSize, .format = format})) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: pending state rejected: swapchain failed reconfiguring", name)); return false; } if (!state->internalState.buffer) { // if the consumer explicitly committed a null buffer, that's a violation. if (state->internalState.committed & COutputState::AQ_OUTPUT_STATE_BUFFER) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: pending state rejected: no buffer", name)); return false; } events.commit.emit(); state->onCommit(); return true; } auto wlBuffer = wlBufferFromBuffer(state->internalState.buffer); if (!wlBuffer) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: pending state rejected: no wlBuffer??", name)); return false; } if (wlBuffer->pendingRelease) backend->backend->log(AQ_LOG_WARNING, std::format("Output {}: pending state has a non-released buffer??", name)); wlBuffer->pendingRelease = true; waylandState.surface->sendAttach(wlBuffer->waylandState.buffer.get(), 0, 0); waylandState.surface->sendDamageBuffer(0, 0, INT32_MAX, INT32_MAX); waylandState.surface->sendCommit(); readyForFrameCallback = true; events.commit.emit(); state->onCommit(); needsFrame = false; return true; } SP Aquamarine::CWaylandOutput::getBackend() { return SP(backend.lock()); } SP Aquamarine::CWaylandOutput::wlBufferFromBuffer(SP buffer) { std::erase_if(backendState.buffers, [this](const auto& el) { return el.first.expired() || !swapchain->contains(el.first.lock()); }); for (auto const& [k, v] : backendState.buffers) { if (k != buffer) continue; return v; } // create a new one auto wlBuffer = makeShared(buffer, backend); if (!wlBuffer->good()) return nullptr; backendState.buffers.emplace_back(std::make_pair<>(buffer, wlBuffer)); return wlBuffer; } void Aquamarine::CWaylandOutput::sendFrameAndSetCallback() { events.frame.emit(); frameScheduled = false; if (waylandState.frameCallback || !readyForFrameCallback) return; waylandState.frameCallback = makeShared(waylandState.surface->sendFrame()); waylandState.frameCallback->setDone([this](CCWlCallback* r, uint32_t ms) { onFrameDone(); }); } void Aquamarine::CWaylandOutput::onFrameDone() { waylandState.frameCallback.reset(); readyForFrameCallback = false; events.present.emit(IOutput::SPresentEvent{.presented = true}); // FIXME: this is wrong, but otherwise we get bugs. // thanks @phonetic112 scheduleFrame(AQ_SCHEDULE_NEEDS_FRAME); if (frameScheduledWhileWaiting) sendFrameAndSetCallback(); else events.frame.emit(); frameScheduledWhileWaiting = false; } bool Aquamarine::CWaylandOutput::setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot) { if (!cursorState.cursorSurface) cursorState.cursorSurface = makeShared(backend->waylandState.compositor->sendCreateSurface()); if (!cursorState.cursorSurface) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to create a wl_surface for the cursor", name)); return false; } if (!buffer) { cursorState.cursorBuffer.reset(); cursorState.cursorWlBuffer.reset(); if (!backend->pointers.empty()) backend->pointers.at(0)->pointer->sendSetCursor(cursorState.serial, nullptr, cursorState.hotspot.x, cursorState.hotspot.y); return true; } cursorState.cursorBuffer = buffer; cursorState.hotspot = hotspot; if (buffer->shm().success) { auto attrs = buffer->shm(); auto [pixelData, fmt, bufLen] = buffer->beginDataPtr(0); int fd = allocateSHMFile(bufLen); if (fd < 0) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to allocate a shm file", name)); return false; } void* data = mmap(nullptr, bufLen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to mmap the cursor pixel data", name)); close(fd); return false; } memcpy(data, pixelData, bufLen); munmap(data, bufLen); auto pool = makeShared(backend->waylandState.shm->sendCreatePool(fd, bufLen)); if (!pool) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to submit a wl_shm pool", name)); close(fd); return false; } cursorState.cursorWlBuffer = makeShared(pool->sendCreateBuffer(0, attrs.size.x, attrs.size.y, attrs.stride, shmFormatFromDRM(attrs.format))); pool.reset(); close(fd); } else if (auto attrs = buffer->dmabuf(); attrs.success) { auto params = makeShared(backend->waylandState.dmabuf->sendCreateParams()); for (int i = 0; i < attrs.planes; ++i) { params->sendAdd(attrs.fds.at(i), i, attrs.offsets.at(i), attrs.strides.at(i), attrs.modifier >> 32, attrs.modifier & 0xFFFFFFFF); } cursorState.cursorWlBuffer = makeShared(params->sendCreateImmed(attrs.size.x, attrs.size.y, attrs.format, (zwpLinuxBufferParamsV1Flags)0)); } else { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to create a buffer for cursor: No known attrs (tried dmabuf / shm)", name)); return false; } if (!cursorState.cursorWlBuffer) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to create a buffer for cursor", name)); return false; } cursorState.cursorSurface->sendSetBufferScale(1); cursorState.cursorSurface->sendSetBufferTransform(WL_OUTPUT_TRANSFORM_NORMAL); cursorState.cursorSurface->sendAttach(cursorState.cursorWlBuffer.get(), 0, 0); cursorState.cursorSurface->sendDamage(0, 0, INT32_MAX, INT32_MAX); cursorState.cursorSurface->sendCommit(); // this may fail if we are not in focus if (!backend->pointers.empty() && cursorState.serial) backend->pointers.at(0)->pointer->sendSetCursor(cursorState.serial, cursorState.cursorSurface.get(), cursorState.hotspot.x, cursorState.hotspot.y); return true; } void Aquamarine::CWaylandOutput::moveCursor(const Hyprutils::Math::Vector2D& coord, bool skipSchedule) { ; } void Aquamarine::CWaylandOutput::onEnter(SP pointer, uint32_t serial) { cursorState.serial = serial; if (!cursorState.cursorSurface) return; pointer->sendSetCursor(serial, cursorState.cursorSurface.get(), cursorState.hotspot.x, cursorState.hotspot.y); } Hyprutils::Math::Vector2D Aquamarine::CWaylandOutput::cursorPlaneSize() { return {-1, -1}; // no limit } void Aquamarine::CWaylandOutput::scheduleFrame(const scheduleFrameReason reason) { TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("CWaylandOutput::scheduleFrame: reason {}, needsFrame {}, frameScheduled {}", (uint32_t)reason, needsFrame, frameScheduled))); needsFrame = true; if (frameScheduled) return; frameScheduled = true; if (waylandState.frameCallback) frameScheduledWhileWaiting = true; else { backend->idleCallbacks.emplace_back([w = self]() { if (auto o = w.lock()) o->sendFrameAndSetCallback(); }); } } Aquamarine::CWaylandBuffer::CWaylandBuffer(SP buffer_, Hyprutils::Memory::CWeakPointer backend_) : buffer(buffer_), backend(backend_) { auto params = makeShared(backend->waylandState.dmabuf->sendCreateParams()); if (!params) { backend->backend->log(AQ_LOG_ERROR, "WaylandBuffer: failed to query params"); return; } auto attrs = buffer->dmabuf(); for (int i = 0; i < attrs.planes; ++i) { params->sendAdd(attrs.fds.at(i), i, attrs.offsets.at(i), attrs.strides.at(i), attrs.modifier >> 32, attrs.modifier & 0xFFFFFFFF); } waylandState.buffer = makeShared(params->sendCreateImmed(attrs.size.x, attrs.size.y, attrs.format, (zwpLinuxBufferParamsV1Flags)0)); waylandState.buffer->setRelease([this](CCWlBuffer* r) { pendingRelease = false; }); params->sendDestroy(); } Aquamarine::CWaylandBuffer::~CWaylandBuffer() { if (waylandState.buffer && waylandState.buffer->resource()) waylandState.buffer->sendDestroy(); } bool Aquamarine::CWaylandBuffer::good() { return waylandState.buffer && waylandState.buffer->resource(); } hyprwm-aquamarine-655e067/src/backend/drm/000077500000000000000000000000001506775317200204215ustar00rootroot00000000000000hyprwm-aquamarine-655e067/src/backend/drm/DRM.cpp000066400000000000000000002441121506775317200215530ustar00rootroot00000000000000#include "aquamarine/output/Output.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include #include #include #include #include #include #include #include } #include "Props.hpp" #include "FormatUtils.hpp" #include "Shared.hpp" #include "hwdata.hpp" #include "Renderer.hpp" using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; #define SP CSharedPointer Aquamarine::CDRMBackend::CDRMBackend(SP backend_) : backend(backend_) { listeners.sessionActivate = backend->session->events.changeActive.listen([this] { if (backend->session->active) { // session got activated, we need to restore restoreAfterVT(); } }); } static udev_enumerate* enumDRMCards(udev* udev) { auto enumerate = udev_enumerate_new(udev); if (!enumerate) return nullptr; udev_enumerate_add_match_subsystem(enumerate, "drm"); #ifdef __linux__ // https://github.com/wulf7/libudev-devd/issues/11 udev_enumerate_add_match_property(enumerate, "DEVTYPE", "drm_minor"); #endif udev_enumerate_add_match_sysname(enumerate, DRM_PRIMARY_MINOR_NAME "[0-9]*"); if (udev_enumerate_scan_devices(enumerate)) { udev_enumerate_unref(enumerate); return nullptr; } return enumerate; } static int gpuNumBuiltinPanels(const SP gpu) { auto resources = drmModeGetResources(gpu->fd); if (!resources) return 0; int num = 0; for (int i = 0; i < resources->count_connectors; ++i) { auto drmConn = drmModeGetConnector(gpu->fd, resources->connectors[i]); if (!drmConn || drmConn->connection != DRM_MODE_CONNECTED) continue; if (drmConn->connector_type == DRM_MODE_CONNECTOR_LVDS || drmConn->connector_type == DRM_MODE_CONNECTOR_eDP || drmConn->connector_type == DRM_MODE_CONNECTOR_DSI) num++; drmModeFreeConnector(drmConn); } drmModeFreeResources(resources); return num; } static std::vector> scanGPUs(SP backend) { auto enumerate = enumDRMCards(backend->session->udevHandle); if (!enumerate) { backend->log(AQ_LOG_ERROR, "drm: couldn't enumerate gpus with udev"); return {}; } if (!udev_enumerate_get_list_entry(enumerate)) { // TODO: wait for them. backend->log(AQ_LOG_ERROR, "drm: No gpus in scanGPUs."); return {}; } udev_list_entry* entry = nullptr; std::deque> devices; int maxBuiltinPanels = 0; SP maxBuiltinPanelsGPU; udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(enumerate)) { auto path = udev_list_entry_get_name(entry); auto device = udev_device_new_from_syspath(backend->session->udevHandle, path); if (!device) { backend->log(AQ_LOG_WARNING, std::format("drm: Skipping device {}", path ? path : "unknown")); continue; } backend->log(AQ_LOG_DEBUG, std::format("drm: Enumerated device {}", path ? path : "unknown")); auto seat = udev_device_get_property_value(device, "ID_SEAT"); if (!seat) seat = "seat0"; if (!backend->session->seatName.empty() && backend->session->seatName != seat) { backend->log(AQ_LOG_WARNING, std::format("drm: Skipping device {} because seat {} doesn't match our {}", path ? path : "unknown", seat, backend->session->seatName)); udev_device_unref(device); continue; } auto pciDevice = udev_device_get_parent_with_subsystem_devtype(device, "pci", nullptr); bool isBootVGA = false; if (pciDevice) { auto id = udev_device_get_sysattr_value(pciDevice, "boot_vga"); isBootVGA = id && id == std::string{"1"}; } if (!udev_device_get_devnode(device)) { backend->log(AQ_LOG_ERROR, std::format("drm: Skipping device {}, no devnode", path ? path : "unknown")); udev_device_unref(device); continue; } auto sessionDevice = CSessionDevice::openIfKMS(backend->session, udev_device_get_devnode(device)); if (!sessionDevice) { backend->log(AQ_LOG_ERROR, std::format("drm: Skipping device {}, not a KMS device", path ? path : "unknown")); udev_device_unref(device); continue; } sessionDevice->resolveMatchingRenderNode(device); udev_device_unref(device); if (isBootVGA) devices.push_front(sessionDevice); else devices.push_back(sessionDevice); int numBuiltinPanels = gpuNumBuiltinPanels(sessionDevice); backend->log(AQ_LOG_TRACE, std::format("drm: Device {} has {} builtin {}", sessionDevice->path, numBuiltinPanels, numBuiltinPanels == 1 ? "panel" : "panels")); if (numBuiltinPanels > maxBuiltinPanels) { maxBuiltinPanelsGPU = sessionDevice; maxBuiltinPanels = numBuiltinPanels; } } udev_enumerate_unref(enumerate); std::vector> vecDevices; auto explicitGpus = getenv("AQ_DRM_DEVICES"); if (explicitGpus) { backend->log(AQ_LOG_DEBUG, std::format("drm: Explicit device list {}", explicitGpus)); Hyprutils::String::CVarList explicitDevices(explicitGpus, 0, ':', true); // Iterate over GPUs and canonicalize the paths for (auto& d : explicitDevices) { std::error_code ec; auto canonicalFilePath = std::filesystem::canonical(d, ec); // If there is an error, log and continue. // TODO: Verify that the path is a valid DRM device. (https://gitlab.freedesktop.org/wlroots/wlroots/-/blob/master/backend/session/session.c?ref_type=heads#L369-387) if (ec) { backend->log(AQ_LOG_ERROR, std::format("drm: Failed to canonicalize path {}", d)); continue; } d = canonicalFilePath.string(); } for (auto const& d : explicitDevices) { bool found = false; for (auto const& vd : devices) { if (vd->path == d) { vecDevices.emplace_back(vd); found = true; break; } } if (found) backend->log(AQ_LOG_DEBUG, std::format("drm: Explicit device {} found", d)); else backend->log(AQ_LOG_ERROR, std::format("drm: Explicit device {} not found", d)); } } else { if (maxBuiltinPanelsGPU && devices.front() != maxBuiltinPanelsGPU) { std::erase(devices, maxBuiltinPanelsGPU); devices.push_front(maxBuiltinPanelsGPU); } for (auto const& d : devices) { vecDevices.push_back(d); } } return vecDevices; } SP Aquamarine::CDRMBackend::fromGpu(std::string path, SP backend, SP primary) { auto gpu = CSessionDevice::openIfKMS(backend->session, path); if (!gpu) { return nullptr; } auto drmBackend = SP(new CDRMBackend(backend)); drmBackend->self = drmBackend; if (!drmBackend->registerGPU(gpu, primary)) { backend->log(AQ_LOG_ERROR, std::format("drm: Failed to register gpu {}", gpu->path)); return nullptr; } else backend->log(AQ_LOG_DEBUG, std::format("drm: Registered gpu {}", gpu->path)); if (!drmBackend->checkFeatures()) { backend->log(AQ_LOG_ERROR, "drm: Failed checking features"); return nullptr; } if (!drmBackend->initResources()) { backend->log(AQ_LOG_ERROR, "drm: Failed initializing resources"); return nullptr; } backend->log(AQ_LOG_DEBUG, std::format("drm: Basic init pass for gpu {}", gpu->path)); drmBackend->grabFormats(); drmBackend->dumbAllocator = CDRMDumbAllocator::create(gpu->fd, backend); // so that session can handle udev change/remove events for this gpu backend->session->sessionDevices.push_back(gpu); return drmBackend; } std::vector> Aquamarine::CDRMBackend::attempt(SP backend) { if (!backend->session) backend->session = CSession::attempt(backend); if (!backend->session) { backend->log(AQ_LOG_ERROR, "Failed to open a session"); return {}; } if (!backend->session->active) { backend->log(AQ_LOG_DEBUG, "Session is not active, waiting for 5s"); auto started = std::chrono::system_clock::now(); while (!backend->session->active) { std::this_thread::sleep_for(std::chrono::milliseconds(250)); backend->session->dispatchPendingEventsAsync(); if (std::chrono::duration_cast(std::chrono::system_clock::now() - started).count() >= 5000) { backend->log(AQ_LOG_DEBUG, "Session timeout reached"); break; } } if (!backend->session->active) { backend->log(AQ_LOG_DEBUG, "Session could not be activated in time"); return {}; } } auto gpus = scanGPUs(backend); if (gpus.empty()) { backend->log(AQ_LOG_ERROR, "drm: Found no gpus to use, cannot continue"); return {}; } backend->log(AQ_LOG_DEBUG, std::format("drm: Found {} GPUs", gpus.size())); std::vector> backends; SP newPrimary; for (auto const& gpu : gpus) { auto drmBackend = SP(new CDRMBackend(backend)); drmBackend->self = drmBackend; if (!drmBackend->registerGPU(gpu, newPrimary)) { backend->log(AQ_LOG_ERROR, std::format("drm: Failed to register gpu {}", gpu->path)); continue; } else backend->log(AQ_LOG_DEBUG, std::format("drm: Registered gpu {}", gpu->path)); // TODO: consider listening for new devices // But if you expect me to handle gpu hotswaps you are probably insane LOL if (!drmBackend->checkFeatures()) { backend->log(AQ_LOG_ERROR, "drm: Failed checking features"); continue; } if (!drmBackend->initResources()) { backend->log(AQ_LOG_ERROR, "drm: Failed initializing resources"); continue; } backend->log(AQ_LOG_DEBUG, std::format("drm: Basic init pass for gpu {}", gpu->path)); drmBackend->grabFormats(); drmBackend->recheckOutputs(); if (!newPrimary) { backend->log(AQ_LOG_DEBUG, std::format("drm: gpu {} becomes primary drm", gpu->path)); newPrimary = drmBackend; } drmBackend->dumbAllocator = CDRMDumbAllocator::create(gpu->fd, backend); backends.emplace_back(drmBackend); // so that session can handle udev change/remove events for this gpu backend->session->sessionDevices.push_back(gpu); } return backends; } Aquamarine::CDRMBackend::~CDRMBackend() { for (auto& conn : connectors) { conn->disconnect(); conn.reset(); } rendererState.allocator->destroyBuffers(); rendererState.renderer.reset(); rendererState.allocator.reset(); } void Aquamarine::CDRMBackend::log(eBackendLogLevel l, const std::string& s) { backend->log(l, s); } bool Aquamarine::CDRMBackend::sessionActive() { return backend->session->active; } void Aquamarine::CDRMBackend::restoreAfterVT() { backend->log(AQ_LOG_DEBUG, "drm: Restoring after VT switch"); recheckOutputs(); backend->log(AQ_LOG_DEBUG, "drm: Rescanned connectors"); if (!impl->reset()) backend->log(AQ_LOG_ERROR, "drm: failed reset"); std::vector> noMode; for (auto const& c : connectors) { if (!c->crtc || !c->output) continue; auto& STATE = c->output->state->state(); SDRMConnectorCommitData data = { .mainFB = nullptr, .modeset = true, .blocking = true, .flags = 0, .test = false, .hdrMetadata = STATE.hdrMetadata, }; auto& MODE = STATE.customMode ? STATE.customMode : STATE.mode; if (!MODE) { backend->log(AQ_LOG_WARNING, "drm: Connector {} has output but state has no mode, will send a reset state event later."); noMode.emplace_back(c); continue; } if (MODE->modeInfo.has_value()) data.modeInfo = *MODE->modeInfo; else data.calculateMode(c); if (STATE.buffer) { SP drmFB; auto buf = STATE.buffer; bool isNew = false; drmFB = CDRMFB::create(buf, self, &isNew); if (!drmFB) backend->log(AQ_LOG_ERROR, "drm: Buffer failed to import to KMS"); data.mainFB = drmFB; } if (c->crtc->pendingCursor) data.cursorFB = c->crtc->pendingCursor; if (data.cursorFB && (data.cursorFB->dead || data.cursorFB->buffer->dmabuf().modifier == DRM_FORMAT_MOD_INVALID)) data.cursorFB = nullptr; backend->log(AQ_LOG_DEBUG, std::format("drm: Restoring crtc {} with clock {} hdisplay {} vdisplay {} vrefresh {}", c->crtc->id, data.modeInfo.clock, data.modeInfo.hdisplay, data.modeInfo.vdisplay, data.modeInfo.vrefresh)); if (!impl->commit(c, data)) backend->log(AQ_LOG_ERROR, std::format("drm: crtc {} failed restore", c->crtc->id)); } for (auto const& c : noMode) { if (!c->output) continue; // tell the consumer to re-set a state because we had no mode c->output->events.state.emit(IOutput::SStateEvent{}); } } bool Aquamarine::CDRMBackend::checkFeatures() { uint64_t curW = 0, curH = 0; if (drmGetCap(gpu->fd, DRM_CAP_CURSOR_WIDTH, &curW)) curW = 64; if (drmGetCap(gpu->fd, DRM_CAP_CURSOR_HEIGHT, &curH)) curH = 64; drmProps.cursorSize = Hyprutils::Math::Vector2D{(double)curW, (double)curH}; uint64_t cap = 0; if (drmGetCap(gpu->fd, DRM_CAP_PRIME, &cap) || !(cap & DRM_PRIME_CAP_IMPORT)) { backend->log(AQ_LOG_ERROR, std::format("drm: DRM_PRIME_CAP_IMPORT unsupported")); return false; } if (drmGetCap(gpu->fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap) || !cap) { backend->log(AQ_LOG_ERROR, std::format("drm: DRM_CAP_CRTC_IN_VBLANK_EVENT unsupported")); return false; } if (drmGetCap(gpu->fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap) || !cap) { backend->log(AQ_LOG_ERROR, std::format("drm: DRM_PRIME_CAP_IMPORT unsupported")); return false; } if (drmSetClientCap(gpu->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { backend->log(AQ_LOG_ERROR, std::format("drm: DRM_CLIENT_CAP_UNIVERSAL_PLANES unsupported")); return false; } drmProps.supportsAsyncCommit = drmGetCap(gpu->fd, DRM_CAP_ASYNC_PAGE_FLIP, &cap) == 0 && cap == 1; drmProps.supportsTimelines = drmGetCap(gpu->fd, DRM_CAP_SYNCOBJ_TIMELINE, &cap) == 0 && cap == 1; if (envEnabled("AQ_NO_MODIFIERS")) { backend->log(AQ_LOG_WARNING, "drm: AQ_NO_MODIFIERS enabled, disabling modifiers for DRM buffers."); drmProps.supportsAddFb2Modifiers = false; } else drmProps.supportsAddFb2Modifiers = drmGetCap(gpu->fd, DRM_CAP_ADDFB2_MODIFIERS, &cap) == 0 && cap == 1; if (envEnabled("AQ_NO_ATOMIC")) { backend->log(AQ_LOG_WARNING, "drm: AQ_NO_ATOMIC enabled, using the legacy drm iface"); impl = makeShared(self.lock()); } else if (drmSetClientCap(gpu->fd, DRM_CLIENT_CAP_ATOMIC, 1)) { backend->log(AQ_LOG_WARNING, "drm: failed to set DRM_CLIENT_CAP_ATOMIC, falling back to legacy"); impl = makeShared(self.lock()); } else { backend->log(AQ_LOG_DEBUG, "drm: Atomic supported, using atomic for modesetting"); impl = makeShared(self.lock()); drmProps.supportsAsyncCommit = drmGetCap(gpu->fd, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, &cap) == 0 && cap == 1; atomic = true; } backend->log(AQ_LOG_DEBUG, std::format("drm: drmProps.supportsAsyncCommit: {}", drmProps.supportsAsyncCommit)); backend->log(AQ_LOG_DEBUG, std::format("drm: drmProps.supportsAddFb2Modifiers: {}", drmProps.supportsAddFb2Modifiers)); backend->log(AQ_LOG_DEBUG, std::format("drm: drmProps.supportsTimelines: {}", drmProps.supportsTimelines)); // TODO: allow no-modifiers? return true; } bool Aquamarine::CDRMBackend::initResources() { auto resources = drmModeGetResources(gpu->fd); if (!resources) { backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetResources failed")); return false; } backend->log(AQ_LOG_DEBUG, std::format("drm: found {} CRTCs", resources->count_crtcs)); for (int i = 0; i < resources->count_crtcs; ++i) { auto CRTC = makeShared(); CRTC->id = resources->crtcs[i]; CRTC->backend = self; auto drmCRTC = drmModeGetCrtc(gpu->fd, CRTC->id); if (!drmCRTC) { backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetCrtc for crtc {} failed", CRTC->id)); drmModeFreeResources(resources); crtcs.clear(); return false; } CRTC->legacy.gammaSize = drmCRTC->gamma_size; drmModeFreeCrtc(drmCRTC); if (!getDRMCRTCProps(gpu->fd, CRTC->id, &CRTC->props)) { backend->log(AQ_LOG_ERROR, std::format("drm: getDRMCRTCProps for crtc {} failed", CRTC->id)); drmModeFreeResources(resources); crtcs.clear(); return false; } crtcs.emplace_back(CRTC); } if (crtcs.size() > 32) { backend->log(AQ_LOG_CRITICAL, "drm: Cannot support more than 32 CRTCs"); return false; } // initialize planes auto planeResources = drmModeGetPlaneResources(gpu->fd); if (!planeResources) { backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetPlaneResources failed")); return false; } backend->log(AQ_LOG_DEBUG, std::format("drm: found {} planes", planeResources->count_planes)); for (uint32_t i = 0; i < planeResources->count_planes; ++i) { auto id = planeResources->planes[i]; auto plane = drmModeGetPlane(gpu->fd, id); if (!plane) { backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetPlane for plane {} failed", id)); drmModeFreeResources(resources); crtcs.clear(); planes.clear(); return false; } auto aqPlane = makeShared(); aqPlane->backend = self; aqPlane->self = aqPlane; if (!aqPlane->init((drmModePlane*)plane)) { backend->log(AQ_LOG_ERROR, std::format("drm: aqPlane->init for plane {} failed", id)); drmModeFreeResources(resources); crtcs.clear(); planes.clear(); return false; } planes.emplace_back(aqPlane); drmModeFreePlane(plane); } drmModeFreePlaneResources(planeResources); drmModeFreeResources(resources); return true; } bool Aquamarine::CDRMBackend::shouldBlit() { return primary; } bool Aquamarine::CDRMBackend::initMgpu() { SP newAllocator; if (primary || backend->primaryAllocator->type() != AQ_ALLOCATOR_TYPE_GBM) { newAllocator = CGBMAllocator::create(backend->reopenDRMNode(gpu->fd), backend); rendererState.allocator = newAllocator; } else { newAllocator = ((CGBMAllocator*)backend->primaryAllocator.get())->self.lock(); rendererState.allocator = newAllocator; } if (!rendererState.allocator) { backend->log(AQ_LOG_ERROR, "drm: initMgpu: no allocator"); return false; } rendererState.renderer = CDRMRenderer::attempt(backend.lock(), gpu->renderNodeFd >= 0 ? gpu->renderNodeFd : gpu->fd); if (!rendererState.renderer) { backend->log(AQ_LOG_ERROR, "drm: initMgpu: no renderer"); return false; } rendererState.renderer->self = rendererState.renderer; buildGlFormats(rendererState.renderer->formats); return true; } void Aquamarine::CDRMBackend::buildGlFormats(const std::vector& fmts) { std::vector result; for (auto const& fmt : fmts) { if (fmt.external && fmt.modifier != DRM_FORMAT_MOD_INVALID) continue; if (auto it = std::ranges::find_if(result, [fmt](const auto& e) { return fmt.drmFormat == e.drmFormat; }); it != result.end()) { it->modifiers.emplace_back(fmt.modifier); continue; } result.emplace_back(SDRMFormat{ .drmFormat = fmt.drmFormat, .modifiers = {fmt.modifier}, }); } glFormats = result; } void Aquamarine::CDRMBackend::recheckCRTCs() { if (connectors.empty() || crtcs.empty()) return; backend->log(AQ_LOG_DEBUG, "drm: Rechecking CRTCs"); std::vector> recheck, changed; for (auto const& c : connectors) { if (c->crtc && c->status == DRM_MODE_CONNECTED) { backend->log(AQ_LOG_DEBUG, std::format("drm: Skipping connector {}, has crtc {} and is connected", c->szName, c->crtc->id)); continue; } recheck.emplace_back(c); backend->log(AQ_LOG_DEBUG, std::format("drm: connector {}, has crtc {}, will be rechecked", c->szName, c->crtc ? (int)c->crtc->id : -1)); } for (size_t i = 0; i < crtcs.size(); ++i) { bool taken = false; for (auto const& c : connectors) { if (c->crtc != crtcs.at(i)) continue; if (c->status != DRM_MODE_CONNECTED) continue; backend->log(AQ_LOG_DEBUG, std::format("drm: slot {} crtc {} taken by {}, skipping", i, c->crtc->id, c->szName)); taken = true; break; } if (taken) continue; bool assigned = false; // try to use a connected connector for (auto const& c : recheck) { if (!(c->possibleCrtcs & (1 << i))) continue; if (c->status != DRM_MODE_CONNECTED) continue; // deactivate old output if (c->output && c->output->state && c->output->state->state().enabled) { c->output->state->setEnabled(false); c->output->commit(); } backend->log(AQ_LOG_DEBUG, std::format("drm: connected slot {} crtc {} assigned to {}{}", i, crtcs.at(i)->id, c->szName, c->crtc ? std::format(" (old {})", c->crtc->id) : "")); c->crtc = crtcs.at(i); assigned = true; changed.emplace_back(c); std::erase(recheck, c); break; } if (!assigned) backend->log(AQ_LOG_DEBUG, std::format("drm: slot {} crtc {} unassigned", i, crtcs.at(i)->id)); } for (auto const& c : connectors) { if (c->status == DRM_MODE_CONNECTED) continue; backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} is not connected{}", c->szName, c->crtc ? std::format(", removing old crtc {}", c->crtc->id) : "")); } // tell the user to re-assign a valid mode etc, if needed for (auto const& conn : changed) { if (conn->status != DRM_MODE_CONNECTED && conn->output) conn->output->events.state.emit(IOutput::SStateEvent{}); } } bool Aquamarine::CDRMBackend::grabFormats() { // FIXME: do this properly maybe? return true; } bool Aquamarine::CDRMBackend::registerGPU(SP gpu_, SP primary_) { gpu = gpu_; primary = primary_; auto drmName = drmGetDeviceNameFromFd2(gpu->fd); auto drmVer = drmGetVersion(gpu->fd); gpuName = drmName; auto drmVerName = drmVer->name ? drmVer->name : "unknown"; if (std::string_view(drmVerName) == "evdi") primary = {}; backend->log(AQ_LOG_DEBUG, std::format("drm: Starting backend for {}, with driver {}{}", drmName ? drmName : "unknown", drmVerName, (primary ? std::format(" with primary {}", primary->gpu->path) : ""))); drmFreeVersion(drmVer); listeners.gpuChange = gpu->events.change.listen([this](const CSessionDevice::SChangeEvent& E) { if (E.type == CSessionDevice::AQ_SESSION_EVENT_CHANGE_HOTPLUG) { backend->log(AQ_LOG_DEBUG, std::format("drm: Got a hotplug event for {}", gpuName)); recheckOutputs(); } else if (E.type == CSessionDevice::AQ_SESSION_EVENT_CHANGE_LEASE) { backend->log(AQ_LOG_DEBUG, std::format("drm: Got a lease event for {}", gpuName)); scanLeases(); } }); listeners.gpuRemove = gpu->events.remove.listen([this] { std::erase_if(backend->implementations, [this](const auto& impl) { return impl->drmFD() == this->drmFD(); }); backend->events.pollFDsChanged.emit(); }); return true; } eBackendType Aquamarine::CDRMBackend::type() { return eBackendType::AQ_BACKEND_DRM; } void Aquamarine::CDRMBackend::recheckOutputs() { scanConnectors(); // disconnect now to possibly free up crtcs for (const auto& conn : connectors) { if (conn->status != DRM_MODE_CONNECTED && conn->output) { backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} disconnected", conn->szName)); conn->disconnect(); } } recheckCRTCs(); // now that crtcs are assigned, connect outputs for (const auto& conn : connectors) { if (conn->status == DRM_MODE_CONNECTED && !conn->output) { backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} connected", conn->szName)); auto drmConn = drmModeGetConnector(gpu->fd, conn->id); // ??? was valid 5 sec ago... if (!drmConn) { backend->log(AQ_LOG_ERROR, std::format("drm: Connector {} couldn't be connected, drm connector id is no longer valid??", conn->szName)); continue; } conn->connect(drmConn); drmModeFreeConnector(drmConn); } } } void Aquamarine::CDRMBackend::scanConnectors() { backend->log(AQ_LOG_DEBUG, std::format("drm: Scanning connectors for {}", gpu->path)); auto resources = drmModeGetResources(gpu->fd); if (!resources) { backend->log(AQ_LOG_ERROR, std::format("drm: Scanning connectors for {} failed", gpu->path)); return; } for (int i = 0; i < resources->count_connectors; ++i) { uint32_t connectorID = resources->connectors[i]; SP conn; auto drmConn = drmModeGetConnector(gpu->fd, connectorID); backend->log(AQ_LOG_DEBUG, std::format("drm: Scanning connector id {}", connectorID)); if (!drmConn) { backend->log(AQ_LOG_ERROR, std::format("drm: Failed to get connector id {}", connectorID)); continue; } auto it = std::ranges::find_if(connectors, [connectorID](const auto& e) { return e->id == connectorID; }); if (it == connectors.end()) { backend->log(AQ_LOG_DEBUG, std::format("drm: Initializing connector id {}", connectorID)); conn = connectors.emplace_back(SP(new SDRMConnector())); conn->self = conn; conn->backend = self; conn->id = connectorID; if (!conn->init(drmConn)) { backend->log(AQ_LOG_ERROR, std::format("drm: Connector id {} failed initializing", connectorID)); connectors.pop_back(); continue; } } else { backend->log(AQ_LOG_DEBUG, std::format("drm: Connector id {} already initialized", connectorID)); conn = *it; } conn->status = drmConn->connection; if (conn->crtc) conn->recheckCRTCProps(); backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} connection state: {}", connectorID, (int)drmConn->connection)); drmModeFreeConnector(drmConn); } // cleanup hot unplugged connectors std::erase_if(connectors, [resources](const auto& conn) { for (int i = 0; i < resources->count_connectors; ++i) { if (resources->connectors[i] == conn->id) return false; } return true; }); drmModeFreeResources(resources); } void Aquamarine::CDRMBackend::scanLeases() { auto lessees = drmModeListLessees(gpu->fd); if (!lessees) { backend->log(AQ_LOG_ERROR, "drmModeListLessees failed"); return; } for (auto const& c : connectors) { if (!c->output || !c->output->lease) continue; bool has = false; for (size_t i = 0; i < lessees->count; ++i) { if (lessees->lessees[i] == c->output->lease->lesseeID) { has = true; break; } } if (has) continue; backend->log(AQ_LOG_DEBUG, std::format("lessee {} gone, removing", c->output->lease->lesseeID)); // don't terminate c->output->lease->active = false; auto l = c->output->lease; for (auto const& c2 : connectors) { if (!c2->output || c2->output->lease != c->output->lease) continue; c2->output->lease.reset(); } l->destroy(); } drmFree(lessees); } bool Aquamarine::CDRMBackend::start() { return true; } std::vector> Aquamarine::CDRMBackend::pollFDs() { return {makeShared(gpu->fd, [this]() { dispatchEvents(); })}; } int Aquamarine::CDRMBackend::drmFD() { return gpu->fd; } int Aquamarine::CDRMBackend::drmRenderNodeFD() { return gpu->renderNodeFd; } static void handlePF(int fd, unsigned seq, unsigned tv_sec, unsigned tv_usec, unsigned crtc_id, void* data) { auto pageFlip = (SDRMPageFlip*)data; if (!pageFlip || !pageFlip->connector) return; pageFlip->connector->isPageFlipPending = false; const auto& BACKEND = pageFlip->connector->backend; TRACE(BACKEND->log(AQ_LOG_TRACE, std::format("drm: pf event seq {} sec {} usec {} crtc {}", seq, tv_sec, tv_usec, crtc_id))); if (pageFlip->connector->status != DRM_MODE_CONNECTED || !pageFlip->connector->crtc) { BACKEND->log(AQ_LOG_DEBUG, "drm: Ignoring a pf event from a disabled crtc / connector"); return; } pageFlip->connector->onPresent(); uint32_t flags = IOutput::AQ_OUTPUT_PRESENT_VSYNC | IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK | IOutput::AQ_OUTPUT_PRESENT_HW_COMPLETION | IOutput::AQ_OUTPUT_PRESENT_ZEROCOPY; timespec presented = {.tv_sec = (time_t)tv_sec, .tv_nsec = (long)(tv_usec * 1000)}; pageFlip->connector->output->events.present.emit(IOutput::SPresentEvent{ .presented = BACKEND->sessionActive(), .when = &presented, .seq = seq, .refresh = (int)(pageFlip->connector->refresh ? (1000000000000LL / pageFlip->connector->refresh) : 0), .flags = flags, }); if (BACKEND->sessionActive() && !pageFlip->connector->frameEventScheduled && pageFlip->connector->output->enabledState) pageFlip->connector->output->events.frame.emit(); } bool Aquamarine::CDRMBackend::dispatchEvents() { drmEventContext event = { .version = 3, .page_flip_handler2 = ::handlePF, }; if (drmHandleEvent(gpu->fd, &event) != 0) backend->log(AQ_LOG_ERROR, std::format("drm: Failed to handle event on fd {}", gpu->fd)); return true; } uint32_t Aquamarine::CDRMBackend::capabilities() { if (getCursorFormats().empty()) return 0; return eBackendCapabilities::AQ_BACKEND_CAPABILITY_POINTER; } bool Aquamarine::CDRMBackend::setCursor(SP buffer, const Hyprutils::Math::Vector2D& hotspot) { return false; } void Aquamarine::CDRMBackend::onReady() { backend->log(AQ_LOG_DEBUG, std::format("drm: Connectors size2 {}", connectors.size())); // init a drm renderer to gather gl formats. // if we are secondary, initMgpu will have done that if (!primary) { auto a = CGBMAllocator::create(backend->reopenDRMNode(gpu->fd), backend); if (!a) backend->log(AQ_LOG_ERROR, "drm: onReady: no renderer for gl formats"); else { auto r = CDRMRenderer::attempt(backend.lock(), gpu->renderNodeFd >= 0 ? gpu->renderNodeFd : gpu->fd); if (!r) backend->log(AQ_LOG_ERROR, "drm: onReady: no renderer for gl formats"); else { TRACE(backend->log(AQ_LOG_TRACE, std::format("drm: onReady: gathered {} gl formats", r->formats.size()))); buildGlFormats(r->formats); r.reset(); a.reset(); } } } for (auto const& c : connectors) { backend->log(AQ_LOG_DEBUG, std::format("drm: onReady: connector {}", c->id)); if (!c->output) continue; backend->log(AQ_LOG_DEBUG, std::format("drm: onReady: connector {} has output name {}", c->id, c->output->name)); // swapchain has to be created here because allocator is absent in connect if not ready auto primaryBackend = primary ? primary : self; c->output->swapchain = CSwapchain::create(backend->primaryAllocator, primaryBackend.lock()); c->output->swapchain->reconfigure(SSwapchainOptions{.length = 0, .scanout = true, .multigpu = !!primary, .scanoutOutput = c->output}); // mark the swapchain for scanout c->output->needsFrame = true; backend->events.newOutput.emit(SP(c->output)); } if (!initMgpu()) { backend->log(AQ_LOG_ERROR, "drm: Failed initializing mgpu"); return; } } std::vector Aquamarine::CDRMBackend::getRenderFormats() { for (auto const& p : planes) { if (p->type != DRM_PLANE_TYPE_PRIMARY) continue; return p->formats; } return {}; } std::vector Aquamarine::CDRMBackend::getRenderableFormats() { return glFormats; } std::vector Aquamarine::CDRMBackend::getCursorFormats() { for (auto const& p : planes) { if (p->type != DRM_PLANE_TYPE_CURSOR) continue; if (primary) { TRACE(backend->log(AQ_LOG_TRACE, std::format("drm: getCursorFormats on secondary {}", gpu->path))); // this is a secondary GPU renderer. In order to receive buffers, // we'll force linear modifiers. // TODO: don't. Find a common maybe? auto fmts = p->formats; for (auto& fmt : fmts) { fmt.modifiers = {DRM_FORMAT_MOD_LINEAR}; } return fmts; } return p->formats; } return {}; } bool Aquamarine::CDRMBackend::createOutput(const std::string&) { return false; } int Aquamarine::CDRMBackend::getNonMasterFD() { int fd = open(gpuName.c_str(), O_RDWR | O_CLOEXEC); if (fd < 0) { backend->log(AQ_LOG_ERROR, "drm: couldn't dupe fd for non master"); return -1; } if (drmIsMaster(fd) && drmDropMaster(fd) < 0) { backend->log(AQ_LOG_ERROR, "drm: couldn't drop master from duped fd"); return -1; } return fd; } SP Aquamarine::CDRMBackend::preferredAllocator() { return backend->primaryAllocator; } std::vector> Aquamarine::CDRMBackend::getAllocators() { return {backend->primaryAllocator, dumbAllocator}; } Hyprutils::Memory::CWeakPointer Aquamarine::CDRMBackend::getPrimary() { return primary; } bool Aquamarine::SDRMPlane::init(drmModePlane* plane) { id = plane->plane_id; if (!getDRMPlaneProps(backend->gpu->fd, id, &props)) return false; if (!getDRMProp(backend->gpu->fd, id, props.values.type, &type)) return false; initialID = id; backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Plane {} has type {}", id, (int)type)); backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Plane {} has {} formats", id, plane->count_formats)); for (size_t i = 0; i < plane->count_formats; ++i) { if (type != DRM_PLANE_TYPE_CURSOR) formats.emplace_back(SDRMFormat{.drmFormat = plane->formats[i], .modifiers = {DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_INVALID}}); else formats.emplace_back(SDRMFormat{.drmFormat = plane->formats[i], .modifiers = {DRM_FORMAT_MOD_LINEAR}}); TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("drm: | Format {}", fourccToName(plane->formats[i])))); } if (props.values.in_formats && backend->drmProps.supportsAddFb2Modifiers) { backend->backend->log(AQ_LOG_DEBUG, "drm: Plane: checking for modifiers"); uint64_t blobID = 0; if (!getDRMProp(backend->gpu->fd, id, props.values.in_formats, &blobID)) { backend->backend->log(AQ_LOG_ERROR, "drm: Plane: No blob id"); return false; } auto blob = drmModeGetPropertyBlob(backend->gpu->fd, blobID); if (!blob) { backend->backend->log(AQ_LOG_ERROR, "drm: Plane: No property"); return false; } drmModeFormatModifierIterator iter = {0}; while (drmModeFormatModifierBlobIterNext(blob, &iter)) { auto it = std::ranges::find_if(formats, [iter](const auto& e) { return e.drmFormat == iter.fmt; }); TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("drm: | Modifier {} with format {}", iter.mod, fourccToName(iter.fmt)))); if (it == formats.end()) formats.emplace_back(SDRMFormat{.drmFormat = iter.fmt, .modifiers = {iter.mod}}); else it->modifiers.emplace_back(iter.mod); } drmModeFreePropertyBlob(blob); } for (size_t i = 0; i < backend->crtcs.size(); ++i) { uint32_t crtcBit = (1 << i); if (!(plane->possible_crtcs & crtcBit)) continue; auto CRTC = backend->crtcs.at(i); if (type == DRM_PLANE_TYPE_PRIMARY && !CRTC->primary) { CRTC->primary = self.lock(); break; } if (type == DRM_PLANE_TYPE_CURSOR && !CRTC->cursor) { CRTC->cursor = self.lock(); break; } } return true; } SP Aquamarine::SDRMConnector::getCurrentCRTC(const drmModeConnector* connector) { uint32_t crtcID = 0; if (props.values.crtc_id) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Using crtc_id for finding crtc")); uint64_t value = 0; if (!getDRMProp(backend->gpu->fd, id, props.values.crtc_id, &value)) { backend->backend->log(AQ_LOG_ERROR, "drm: Failed to get CRTC_ID"); return nullptr; } crtcID = static_cast(value); } else if (connector->encoder_id) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Using encoder_id for finding crtc")); auto encoder = drmModeGetEncoder(backend->gpu->fd, connector->encoder_id); if (!encoder) { backend->backend->log(AQ_LOG_ERROR, "drm: drmModeGetEncoder failed"); return nullptr; } crtcID = encoder->crtc_id; drmModeFreeEncoder(encoder); } else { backend->backend->log(AQ_LOG_ERROR, "drm: Connector has neither crtc_id nor encoder_id"); return nullptr; } if (crtcID == 0) { backend->backend->log(AQ_LOG_ERROR, "drm: getCurrentCRTC: No CRTC 0"); return nullptr; } auto it = std::find_if(backend->crtcs.begin(), backend->crtcs.end(), [crtcID](const auto& e) { return e->id == crtcID; }); if (it == backend->crtcs.end()) { backend->backend->log(AQ_LOG_ERROR, std::format("drm: Failed to find a CRTC with ID {}", crtcID)); return nullptr; } return *it; } bool Aquamarine::SDRMConnector::init(drmModeConnector* connector) { pendingPageFlip.connector = self.lock(); if (!getDRMConnectorProps(backend->gpu->fd, id, &props)) return false; if (props.values.Colorspace) getDRMConnectorColorspace(backend->gpu->fd, props.values.Colorspace, &colorspace); auto name = drmModeGetConnectorTypeName(connector->connector_type); if (!name) name = "ERROR"; szName = std::format("{}-{}", name, connector->connector_type_id); backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Connector gets name {}", szName)); possibleCrtcs = drmModeConnectorGetPossibleCrtcs(backend->gpu->fd, connector); if (!possibleCrtcs) backend->backend->log(AQ_LOG_ERROR, "drm: No CRTCs possible"); crtc = getCurrentCRTC(connector); return true; } Aquamarine::SDRMConnector::~SDRMConnector() { disconnect(); } static int32_t calculateRefresh(const drmModeModeInfo& mode) { int32_t refresh = (mode.clock * 1000000LL / mode.htotal + mode.vtotal / 2) / mode.vtotal; if (mode.flags & DRM_MODE_FLAG_INTERLACE) refresh *= 2; if (mode.flags & DRM_MODE_FLAG_DBLSCAN) refresh /= 2; if (mode.vscan > 1) refresh /= mode.vscan; return refresh; } drmModeModeInfo* Aquamarine::SDRMConnector::getCurrentMode() { if (!crtc) return nullptr; if (crtc->props.values.mode_id) { size_t size = 0; return (drmModeModeInfo*)getDRMPropBlob(backend->gpu->fd, crtc->id, crtc->props.values.mode_id, &size); } auto drmCrtc = drmModeGetCrtc(backend->gpu->fd, crtc->id); if (!drmCrtc) return nullptr; if (!drmCrtc->mode_valid) { drmModeFreeCrtc(drmCrtc); return nullptr; } drmModeModeInfo* modeInfo = (drmModeModeInfo*)malloc(sizeof(drmModeModeInfo)); if (!modeInfo) { drmModeFreeCrtc(drmCrtc); return nullptr; } *modeInfo = drmCrtc->mode; drmModeFreeCrtc(drmCrtc); return modeInfo; } IOutput::SParsedEDID Aquamarine::SDRMConnector::parseEDID(std::vector data) { auto info = di_info_parse_edid(data.data(), data.size()); IOutput::SParsedEDID parsed = {}; if (!info) { backend->backend->log(AQ_LOG_ERROR, "drm: failed to parse edid"); return parsed; } auto edid = di_info_get_edid(info); auto venProduct = di_edid_get_vendor_product(edid); auto pnpID = std::string{venProduct->manufacturer, 3}; if (PNPIDS.contains(pnpID)) make = PNPIDS.at(pnpID); else make = pnpID; auto mod = di_info_get_model(info); auto ser = di_info_get_serial(info); model = mod ? mod : ""; serial = ser ? ser : ""; parsed.make = make; parsed.model = model; parsed.serial = serial; const auto chromaticity = di_edid_get_chromaticity_coords(edid); if (chromaticity) { parsed.chromaticityCoords = IOutput::SChromaticityCoords{ .red = IOutput::xy{.x = chromaticity->red_x, .y = chromaticity->red_y}, .green = IOutput::xy{.x = chromaticity->green_x, .y = chromaticity->green_y}, .blue = IOutput::xy{.x = chromaticity->blue_x, .y = chromaticity->blue_y}, .white = IOutput::xy{.x = chromaticity->white_x, .y = chromaticity->white_y}, }; TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("EDID: chromaticity coords {},{} {},{} {},{} {},{}", parsed.chromaticityCoords->red.x, parsed.chromaticityCoords->red.y, parsed.chromaticityCoords->green.x, parsed.chromaticityCoords->green.y, parsed.chromaticityCoords->blue.x, parsed.chromaticityCoords->blue.y, parsed.chromaticityCoords->white.y, parsed.chromaticityCoords->white.y))); } auto exts = di_edid_get_extensions(edid); for (; *exts != nullptr; exts++) { auto tag = di_edid_ext_get_tag(*exts); TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("EDID: checking ext {}", (uint32_t)tag))); if (tag == DI_EDID_EXT_DISPLAYID) backend->backend->log(AQ_LOG_WARNING, "FIXME: support displayid blocks"); const auto cta = di_edid_ext_get_cta(*exts); if (cta) { TRACE(backend->backend->log(AQ_LOG_TRACE, "EDID: found CTA")); const di_cta_hdr_static_metadata_block* hdr_static_metadata = nullptr; const di_cta_colorimetry_block* colorimetry = nullptr; auto blocks = di_edid_cta_get_data_blocks(cta); for (; *blocks != nullptr; blocks++) { if (!hdr_static_metadata && (hdr_static_metadata = di_cta_data_block_get_hdr_static_metadata(*blocks))) { TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("EDID: found HDR {}", hdr_static_metadata->eotfs->pq))); parsed.hdrMetadata = IOutput::SHDRMetadata{ .desiredContentMaxLuminance = hdr_static_metadata->desired_content_max_luminance, .desiredMaxFrameAverageLuminance = hdr_static_metadata->desired_content_max_frame_avg_luminance, .desiredContentMinLuminance = hdr_static_metadata->desired_content_min_luminance, .supportsPQ = hdr_static_metadata->eotfs->pq, }; continue; } if (!colorimetry && (colorimetry = di_cta_data_block_get_colorimetry(*blocks))) { TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("EDID: found colorimetry {}", colorimetry->bt2020_rgb))); parsed.supportsBT2020 = colorimetry->bt2020_rgb; continue; } } break; } } di_info_destroy(info); TRACE(backend->backend->log(AQ_LOG_TRACE, "EDID: parsed")); return parsed; } void Aquamarine::SDRMConnector::recheckCRTCProps() { if (!crtc || !output) return; uint64_t prop = 0; canDoVrr = props.values.vrr_capable && crtc->props.values.vrr_enabled && getDRMProp(backend->gpu->fd, id, props.values.vrr_capable, &prop) && prop; output->vrrCapable = canDoVrr; backend->backend->log(AQ_LOG_DEBUG, std::format("drm: connector {} crtc is {} of vrr: props.vrr_capable -> {}, crtc->props.vrr_enabled -> {}", szName, (canDoVrr ? "capable" : "incapable"), props.values.vrr_capable, crtc->props.values.vrr_enabled)); output->supportsExplicit = backend->drmProps.supportsTimelines && crtc->props.values.out_fence_ptr && crtc->primary->props.values.in_fence_fd; backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Explicit sync {}", output->supportsExplicit ? "supported" : "unsupported")); backend->backend->log(AQ_LOG_DEBUG, std::format("drm: connector {} crtc {} CTM", szName, (crtc->props.values.ctm ? "supports" : "doesn't support"))); backend->backend->log( AQ_LOG_DEBUG, std::format("drm: connector {} crtc {} HDR ({})", szName, (props.values.hdr_output_metadata ? "supports" : "doesn't support"), props.values.hdr_output_metadata)); backend->backend->log(AQ_LOG_DEBUG, std::format("drm: connector {} crtc {} Colorspace ({})", szName, (props.values.Colorspace ? "supports" : "doesn't support"), props.values.Colorspace)); } void Aquamarine::SDRMConnector::connect(drmModeConnector* connector) { if (output) { backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Not connecting connector {} because it's already connected", szName)); return; } backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Connecting connector {}, {}", szName, crtc ? std::format("CRTC ID {}", crtc->id) : "no CRTC")); output = SP(new CDRMOutput(szName, backend, self.lock())); output->self = output; output->connector = self.lock(); backend->backend->log(AQ_LOG_DEBUG, "drm: Dumping detected modes:"); auto currentModeInfo = getCurrentMode(); for (int i = 0; i < connector->count_modes; ++i) { auto& drmMode = connector->modes[i]; if (drmMode.flags & DRM_MODE_FLAG_INTERLACE) { backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Skipping mode {} because it's interlaced", i)); continue; } auto aqMode = makeShared(); aqMode->pixelSize = {drmMode.hdisplay, drmMode.vdisplay}; aqMode->refreshRate = calculateRefresh(drmMode); aqMode->preferred = (drmMode.type & DRM_MODE_TYPE_PREFERRED); aqMode->modeInfo = drmMode; if (i == 1) fallbackMode = aqMode; output->modes.emplace_back(aqMode); if (currentModeInfo && std::memcmp(&drmMode, currentModeInfo, sizeof(drmModeModeInfo)) != 0) { output->state->setMode(aqMode); //uint64_t modeID = 0; // getDRMProp(backend->gpu->fd, crtc->id, crtc->props.mode_id, &modeID); } backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Mode {}: {}x{}@{:.2f}Hz {}", i, (int)aqMode->pixelSize.x, (int)aqMode->pixelSize.y, aqMode->refreshRate / 1000.0, aqMode->preferred ? " (preferred)" : "")); } if (!currentModeInfo && fallbackMode) output->state->setMode(fallbackMode); output->physicalSize = {(double)connector->mmWidth, (double)connector->mmHeight}; backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Physical size {} (mm)", output->physicalSize)); switch (connector->subpixel) { case DRM_MODE_SUBPIXEL_NONE: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_NONE; break; case DRM_MODE_SUBPIXEL_UNKNOWN: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_UNKNOWN; break; case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_HORIZONTAL_RGB; break; case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_HORIZONTAL_BGR; break; case DRM_MODE_SUBPIXEL_VERTICAL_RGB: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_VERTICAL_RGB; break; case DRM_MODE_SUBPIXEL_VERTICAL_BGR: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_VERTICAL_BGR; break; default: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_UNKNOWN; } uint64_t prop = 0; if (getDRMProp(backend->gpu->fd, id, props.values.non_desktop, &prop)) { if (prop == 1) backend->backend->log(AQ_LOG_DEBUG, "drm: Non-desktop connector"); output->nonDesktop = prop; } maxBpcBounds.fill(0); if (props.values.max_bpc && !introspectDRMPropRange(backend->gpu->fd, props.values.max_bpc, maxBpcBounds.data(), &maxBpcBounds[1])) backend->backend->log(AQ_LOG_ERROR, "drm: Failed to check max_bpc"); size_t edidLen = 0; uint8_t* edidData = (uint8_t*)getDRMPropBlob(backend->gpu->fd, id, props.values.edid, &edidLen); std::vector edid{edidData, edidData + edidLen}; auto parsedEDID = parseEDID(edid); free(edidData); edid = {}; // TODO: subconnectors output->make = parsedEDID.make; output->model = parsedEDID.model; output->serial = parsedEDID.serial; output->parsedEDID = parsedEDID; output->description = std::format("{} {} {} ({})", make, model, serial, szName); output->needsFrame = true; backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Description {}", output->description)); status = DRM_MODE_CONNECTED; recheckCRTCProps(); if (!backend->backend->ready) return; auto primaryBackend = backend->primary ? backend->primary : backend; output->swapchain = CSwapchain::create(backend->backend->primaryAllocator, primaryBackend.lock()); output->swapchain->reconfigure(SSwapchainOptions{.length = 0, .scanout = true, .multigpu = !!backend->primary, .scanoutOutput = output}); // mark the swapchain for scanout output->needsFrame = true; backend->backend->events.newOutput.emit(SP(output)); output->scheduleFrame(IOutput::AQ_SCHEDULE_NEW_CONNECTOR); } void Aquamarine::SDRMConnector::disconnect() { if (!output) { backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Not disconnecting connector {} because it's already disconnected", szName)); return; } output->events.destroy.emit(); output.reset(); status = DRM_MODE_DISCONNECTED; } bool Aquamarine::SDRMConnector::commitState(SDRMConnectorCommitData& data) { const bool ok = backend->impl->commit(self.lock(), data); if (ok && !data.test) applyCommit(data); else rollbackCommit(data); return ok; } void Aquamarine::SDRMConnector::applyCommit(const SDRMConnectorCommitData& data) { crtc->primary->back = data.mainFB; if (crtc->cursor && data.cursorFB) crtc->cursor->back = data.cursorFB; if (data.mainFB) data.mainFB->buffer->lockedByBackend = true; if (crtc->cursor && data.cursorFB) data.cursorFB->buffer->lockedByBackend = true; pendingCursorFB.reset(); if (output->state->state().committed & COutputState::AQ_OUTPUT_STATE_MODE) refresh = calculateRefresh(data.modeInfo); output->enabledState = output->state->state().enabled; } void Aquamarine::SDRMConnector::rollbackCommit(const SDRMConnectorCommitData& data) { // cursors are applied regardless, // unless this was a test if (data.test) return; if (crtc->cursor && data.cursorFB) crtc->cursor->back = data.cursorFB; crtc->pendingCursor.reset(); } void Aquamarine::SDRMConnector::onPresent() { crtc->primary->last = crtc->primary->front; crtc->primary->front = crtc->primary->back; if (crtc->primary->last && crtc->primary->last->buffer) { crtc->primary->last->buffer->lockedByBackend = false; crtc->primary->last->buffer->events.backendRelease.emit(); } if (crtc->cursor) { crtc->cursor->last = crtc->cursor->front; crtc->cursor->front = crtc->cursor->back; if (crtc->cursor->last && crtc->cursor->last->buffer) { crtc->cursor->last->buffer->lockedByBackend = false; crtc->cursor->last->buffer->events.backendRelease.emit(); } } } Aquamarine::CDRMOutput::~CDRMOutput() { backend->backend->removeIdleEvent(frameIdle); connector->isPageFlipPending = false; connector->frameEventScheduled = false; } bool Aquamarine::CDRMOutput::commit() { return commitState(); } bool Aquamarine::CDRMOutput::test() { return commitState(true); } void Aquamarine::CDRMOutput::setCursorVisible(bool visible) { cursorVisible = visible; scheduleFrame(AQ_SCHEDULE_CURSOR_VISIBLE); } bool Aquamarine::CDRMOutput::commitState(bool onlyTest) { if (!backend->backend->session->active) { backend->backend->log(AQ_LOG_ERROR, "drm: Session inactive"); return false; } if (!connector->crtc) { backend->backend->log(AQ_LOG_ERROR, "drm: No CRTC attached to output"); return false; } const auto& STATE = state->state(); const uint32_t COMMITTED = STATE.committed; if ((COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_ENABLED) && STATE.enabled) { if (!STATE.mode && !STATE.customMode) { backend->backend->log(AQ_LOG_ERROR, "drm: No mode on enable commit"); return false; } } if (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_FORMAT) { // verify the format is valid for the primary plane bool ok = false; for (auto const& f : getRenderFormats()) { if (f.drmFormat == STATE.drmFormat) { ok = true; break; } } if (!ok) { backend->backend->log(AQ_LOG_ERROR, "drm: Selected format is not supported by the primary KMS plane"); return false; } } if (STATE.enabled && STATE.drmFormat == DRM_FORMAT_INVALID) { backend->backend->log(AQ_LOG_ERROR, "drm: No format for output"); return false; } if (STATE.adaptiveSync && !connector->canDoVrr) { backend->backend->log(AQ_LOG_ERROR, "drm: No Adaptive sync support for output"); return false; } if (STATE.presentationMode == AQ_OUTPUT_PRESENTATION_IMMEDIATE && !backend->drmProps.supportsAsyncCommit) { backend->backend->log(AQ_LOG_ERROR, "drm: No Immediate presentation support in the backend"); return false; } if ((COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER) && !STATE.buffer) { backend->backend->log(AQ_LOG_ERROR, "drm: No buffer committed"); return false; } if ((COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER) && STATE.buffer->attachments.has()) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Cannot commit a KMS-unimportable buffer.")); return false; } // If we are changing the rendering format, we may need to reconfigure the output (aka modeset) // which may result in some glitches const bool NEEDS_RECONFIG = COMMITTED & (COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_ENABLED | COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_FORMAT | COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_MODE | COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_HDR | COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_WCG); const bool BLOCKING = NEEDS_RECONFIG || !(COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER); const auto MODE = STATE.mode ? STATE.mode : STATE.customMode; if (!MODE) // modeless commits are invalid return false; uint32_t flags = 0; if (!onlyTest) { if (NEEDS_RECONFIG) { if (STATE.enabled) backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Modesetting {} with {}x{}@{:.2f}Hz", name, (int)MODE->pixelSize.x, (int)MODE->pixelSize.y, MODE->refreshRate / 1000.F)); else backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Disabling output {}", name)); } if (STATE.enabled && (NEEDS_RECONFIG || (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER)) && connector->isPageFlipPending) { backend->backend->log(AQ_LOG_ERROR, "drm: Cannot commit when a page-flip is awaiting"); return false; } if (STATE.enabled && (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER)) flags |= DRM_MODE_PAGE_FLIP_EVENT; if (STATE.presentationMode == AQ_OUTPUT_PRESENTATION_IMMEDIATE && (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER)) flags |= DRM_MODE_PAGE_FLIP_ASYNC; } // we can't go further without a blit if (backend->primary && onlyTest) return true; SDRMConnectorCommitData data; if (STATE.buffer) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Committed a buffer, updating state")); SP drmFB; if (backend->shouldBlit()) { if (!backend->rendererState.renderer) { backend->backend->log(AQ_LOG_ERROR, "drm: No renderer attached to backend when required for blitting"); return false; } TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Backend requires blit, blitting")); if (!mgpu.swapchain) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: No swapchain for blit, creating")); mgpu.swapchain = CSwapchain::create(backend->rendererState.allocator, backend.lock()); } auto OPTIONS = swapchain->currentOptions(); auto bufDma = STATE.buffer->dmabuf(); OPTIONS.size = STATE.buffer->size; if (OPTIONS.format == DRM_FORMAT_INVALID) OPTIONS.format = bufDma.format; OPTIONS.multigpu = false; // this is not a shared swapchain, and additionally, don't make it linear, nvidia would be mad OPTIONS.cursor = false; OPTIONS.scanout = true; if (!mgpu.swapchain->reconfigure(OPTIONS)) { backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but the mgpu swapchain failed reconfiguring"); return false; } auto NEWAQBUF = mgpu.swapchain->next(nullptr); SP primaryRenderer; if (backend->primary) primaryRenderer = backend->primary->rendererState.renderer; auto blitResult = backend->rendererState.renderer->blit( STATE.buffer, NEWAQBUF, primaryRenderer, (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_EXPLICIT_IN_FENCE) ? STATE.explicitInFence : -1); if (!blitResult.success) { backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but blit failed"); return false; } // replace the explicit in fence if the blitting backend returned one, otherwise discard old. Passed fence from the client is wrong. // if the commit doesn't have an explicit fence, don't use the one we created, just fallback to implicit static auto NO_EXPLICIT = envEnabled("AQ_MGPU_NO_EXPLICIT"); if (blitResult.syncFD.has_value() && !NO_EXPLICIT && (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_EXPLICIT_IN_FENCE)) state->setExplicitInFence(blitResult.syncFD.value()); else state->setExplicitInFence(-1); drmFB = CDRMFB::create(NEWAQBUF, backend, nullptr); // will return attachment if present } else drmFB = CDRMFB::create(STATE.buffer, backend, nullptr); // will return attachment if present if (!drmFB) { backend->backend->log(AQ_LOG_ERROR, "drm: Buffer failed to import to KMS"); return false; } if (drmFB->dead) { backend->backend->log(AQ_LOG_ERROR, "drm: KMS buffer is dead?!"); return false; } data.mainFB = drmFB; } // sometimes, our consumer could f up the swapchain format and change it without the state changing bool formatMismatch = false; if (data.mainFB) { if (const auto params = data.mainFB->buffer->dmabuf(); params.success && params.format != STATE.drmFormat) { // formats mismatch. Update the state format and roll with it backend->backend->log(AQ_LOG_WARNING, std::format("drm: Formats mismatch in commit, buffer is {} but output is set to {}. Modesetting to {}", fourccToName(params.format), fourccToName(STATE.drmFormat), fourccToName(params.format))); state->setFormat(params.format); formatMismatch = true; // TODO: reject if tearing? We will miss a frame event! flags &= ~DRM_MODE_PAGE_FLIP_ASYNC; // we cannot modeset with async pf } } if (connector->crtc->pendingCursor) data.cursorFB = connector->crtc->pendingCursor; else if (connector->crtc->cursor) data.cursorFB = connector->crtc->cursor->front; if (data.cursorFB) { // verify cursor format. This might be wrong on NVIDIA where linear buffers // fail to be created from gbm // TODO: add an API to detect this and request drm_dumb linear buffers. Or do something, // idk if (data.cursorFB->dead || data.cursorFB->buffer->dmabuf().modifier == DRM_FORMAT_MOD_INVALID) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Dropping invalid buffer for cursor plane")); data.cursorFB = nullptr; } } if (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_CTM) data.ctm = STATE.ctm; if (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_HDR) data.hdrMetadata = STATE.hdrMetadata; data.blocking = BLOCKING || formatMismatch; data.modeset = NEEDS_RECONFIG || lastCommitNoBuffer || formatMismatch; data.flags = flags; data.test = onlyTest; if (MODE->modeInfo.has_value()) data.modeInfo = *MODE->modeInfo; else data.calculateMode(connector); bool ok = connector->commitState(data); if (!ok && !data.modeset && !connector->commitTainted) { // attempt to re-modeset, however, flip a tainted flag if the modesetting fails // to avoid doing this over and over. data.modeset = true; data.blocking = true; data.flags = onlyTest ? 0 : DRM_MODE_PAGE_FLIP_EVENT; ok = connector->commitState(data); if (!ok) connector->commitTainted = true; } if (onlyTest || !ok) return ok; events.commit.emit(); state->onCommit(); lastCommitNoBuffer = !data.mainFB; needsFrame = false; if (ok) connector->commitTainted = false; if (data.flags & DRM_MODE_PAGE_FLIP_ASYNC) { // for tearing commits, we will send presentation feedback instantly, and rotate // drm framebuffers to properly send backendRelease events. // the last FB should already be gone from KMS because it's been immediately replaced // no completion and no vsync, because tearing uint32_t flags = IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK | IOutput::AQ_OUTPUT_PRESENT_ZEROCOPY; timespec presented; clock_gettime(CLOCK_MONOTONIC, &presented); connector->output->events.present.emit(IOutput::SPresentEvent{ .presented = backend->sessionActive(), .when = &presented, .seq = 0, /* unknown sequence for tearing */ .refresh = (int)(connector->refresh ? (1000000000000LL / connector->refresh) : 0), .flags = flags, }); connector->onPresent(); } return ok; } SP Aquamarine::CDRMOutput::getBackend() { return backend.lock(); } bool Aquamarine::CDRMOutput::setCursor(SP buffer, const Vector2D& hotspot) { if (!connector->crtc) return false; state->internalState.committed |= COutputState::AQ_OUTPUT_STATE_CURSOR_SHAPE; if (!buffer) setCursorVisible(false); else { auto bufferType = buffer->type(); if (!buffer->good()) { backend->backend->log(AQ_LOG_ERROR, "drm: bad buffer passed to setCursor"); return false; } if ((bufferType == eBufferType::BUFFER_TYPE_SHM && !buffer->shm().success) || (bufferType == eBufferType::BUFFER_TYPE_DMABUF && !buffer->dmabuf().success)) { backend->backend->log(AQ_LOG_ERROR, "drm: Invalid buffer passed to setCursor"); return false; } SP fb; if (backend->primary) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Backend requires cursor blit, blitting")); // TODO: will this not implode on drm_dumb?! if (!mgpu.cursorSwapchain) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: No cursorSwapchain for blit, creating")); mgpu.cursorSwapchain = CSwapchain::create(backend->rendererState.allocator, backend.lock()); } const auto FORMAT = bufferType == eBufferType::BUFFER_TYPE_SHM ? buffer->shm().format : buffer->dmabuf().format; const auto SIZE = bufferType == eBufferType::BUFFER_TYPE_SHM ? buffer->shm().size : buffer->dmabuf().size; auto OPTIONS = mgpu.cursorSwapchain->currentOptions(); OPTIONS.multigpu = false; OPTIONS.scanout = true; OPTIONS.cursor = true; OPTIONS.format = FORMAT; OPTIONS.size = SIZE; OPTIONS.length = 2; if (!mgpu.cursorSwapchain->reconfigure(OPTIONS)) { backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but the mgpu cursorSwapchain failed reconfiguring"); return false; } auto NEWAQBUF = mgpu.cursorSwapchain->next(nullptr); SP primaryRenderer; if (backend->primary) primaryRenderer = backend->primary->rendererState.renderer; if (!backend->rendererState.renderer->blit(buffer, NEWAQBUF, primaryRenderer).success) { backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but cursor blit failed"); return false; } fb = CDRMFB::create(NEWAQBUF, backend, nullptr); // will return attachment if present } else fb = CDRMFB::create(buffer, backend, nullptr); if (!fb) { backend->backend->log(AQ_LOG_ERROR, "drm: Cursor buffer failed to import to KMS"); return false; } cursorHotspot = hotspot; backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Cursor buffer imported into KMS with id {}", fb->id)); connector->crtc->pendingCursor = fb; cursorVisible = true; } scheduleFrame(AQ_SCHEDULE_CURSOR_SHAPE); return true; } void Aquamarine::CDRMOutput::moveCursor(const Vector2D& coord, bool skipSchedule) { cursorPos = coord; // cursorVisible = true; // if (!skipSchedule) state->internalState.committed |= COutputState::AQ_OUTPUT_STATE_CURSOR_POS; backend->impl->moveCursor(connector, skipSchedule); } void Aquamarine::CDRMOutput::scheduleFrame(const scheduleFrameReason reason) { TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("CDRMOutput::scheduleFrame: reason {}, needsFrame {}, isPageFlipPending {}, frameEventScheduled {}", (uint32_t)reason, needsFrame, connector->isPageFlipPending, connector->frameEventScheduled))); needsFrame = true; if (connector->isPageFlipPending || connector->frameEventScheduled || !enabledState) return; connector->frameEventScheduled = true; backend->backend->addIdleEvent(frameIdle); } Vector2D Aquamarine::CDRMOutput::cursorPlaneSize() { return backend->drmProps.cursorSize; } size_t Aquamarine::CDRMOutput::getGammaSize() { if (!backend->atomic) { backend->log(AQ_LOG_ERROR, "No support for gamma on the legacy iface"); return 0; } uint64_t size = 0; if (!getDRMProp(backend->gpu->fd, connector->crtc->id, connector->crtc->props.values.gamma_lut_size, &size)) { backend->log(AQ_LOG_ERROR, "Couldn't get the gamma_size prop"); return 0; } return size; } size_t Aquamarine::CDRMOutput::getDeGammaSize() { if (!backend->atomic) { backend->log(AQ_LOG_ERROR, "No support for gamma on the legacy iface"); return 0; } uint64_t size = 0; if (!getDRMProp(backend->gpu->fd, connector->crtc->id, connector->crtc->props.values.degamma_lut_size, &size)) { backend->log(AQ_LOG_ERROR, "Couldn't get the degamma_size prop"); return 0; } return size; } std::vector Aquamarine::CDRMOutput::getRenderFormats() { if (!connector->crtc || !connector->crtc->primary || connector->crtc->primary->formats.empty()) { backend->log(AQ_LOG_ERROR, "Can't get formats: no crtc"); return {}; } return connector->crtc->primary->formats; } int Aquamarine::CDRMOutput::getConnectorID() { return connector->id; } Aquamarine::CDRMOutput::CDRMOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_, SP connector_) : backend(backend_), connector(connector_) { name = name_; frameIdle = makeShared>([this]() { connector->frameEventScheduled = false; if (connector->isPageFlipPending) return; events.frame.emit(); }); } SP Aquamarine::CDRMFB::create(SP buffer_, Hyprutils::Memory::CWeakPointer backend_, bool* isNew) { SP fb; if (isNew) *isNew = true; if (auto at = buffer_->attachments.get()) { fb = at->fb; TRACE(backend_->log(AQ_LOG_TRACE, std::format("drm: CDRMFB: buffer has drmfb attachment with fb {:x}", (uintptr_t)fb.get()))); } if (fb) { if (isNew) *isNew = false; return fb; } fb = SP(new CDRMFB(buffer_, backend_)); if (!fb->id) return nullptr; buffer_->attachments.add(makeShared(fb)); return fb; } Aquamarine::CDRMFB::CDRMFB(SP buffer_, Hyprutils::Memory::CWeakPointer backend_) : buffer(buffer_), backend(backend_) { import(); } void Aquamarine::CDRMFB::import() { auto attrs = buffer->dmabuf(); if (!attrs.success) { backend->backend->log(AQ_LOG_ERROR, "drm: Buffer submitted has no dmabuf or a drm handle"); return; } if (buffer->attachments.has()) { backend->backend->log(AQ_LOG_ERROR, "drm: Buffer submitted is unimportable"); return; } // TODO: check format for (int i = 0; i < attrs.planes; ++i) { int ret = drmPrimeFDToHandle(backend->gpu->fd, attrs.fds.at(i), &boHandles[i]); if (ret) { backend->backend->log(AQ_LOG_ERROR, "drm: drmPrimeFDToHandle failed"); drop(); return; } TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("drm: CDRMFB: plane {} has fd {}, got handle {}", i, attrs.fds.at(i), boHandles.at(i)))); } id = submitBuffer(); if (!id) { backend->backend->log(AQ_LOG_ERROR, "drm: Failed to submit a buffer to KMS"); buffer->attachments.add(makeShared()); drop(); return; } TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("drm: new buffer {}", id))); closeHandles(); listeners.destroyBuffer = buffer->events.destroy.listen([this] { drop(); dead = true; id = 0; boHandles = {0, 0, 0, 0}; }); } void Aquamarine::CDRMFB::reimport() { drop(); dropped = false; handlesClosed = false; boHandles = {0, 0, 0, 0}; import(); } Aquamarine::CDRMFB::~CDRMFB() { drop(); } void Aquamarine::CDRMFB::closeHandles() { if (handlesClosed) return; handlesClosed = true; std::vector closed; for (size_t i = 0; i < 4; ++i) { if (boHandles.at(i) == 0) continue; bool exists = false; for (size_t j = 0; j < i; ++j) { if (boHandles.at(i) == boHandles.at(j)) { exists = true; break; } } if (exists) continue; if (drmCloseBufferHandle(backend->gpu->fd, boHandles.at(i))) backend->backend->log(AQ_LOG_ERROR, "drm: drmCloseBufferHandle failed"); } boHandles = {0, 0, 0, 0}; } void Aquamarine::CDRMFB::drop() { if (dropped) return; dropped = true; if (!id) return; closeHandles(); TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("drm: dropping buffer {}", id))); int ret = drmModeCloseFB(backend->gpu->fd, id); if (ret == -EINVAL) ret = drmModeRmFB(backend->gpu->fd, id); if (ret) backend->backend->log(AQ_LOG_ERROR, std::format("drm: Failed to close a buffer: {}", strerror(-ret))); } uint32_t Aquamarine::CDRMFB::submitBuffer() { uint32_t newID = 0; if (!buffer->dmabuf().success) return 0; auto attrs = buffer->dmabuf(); std::array mods = {0, 0, 0, 0}; for (int i = 0; i < attrs.planes; ++i) { mods[i] = attrs.modifier; } if (backend->drmProps.supportsAddFb2Modifiers && attrs.modifier != DRM_FORMAT_MOD_INVALID) { TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("drm: Using drmModeAddFB2WithModifiers to import buffer into KMS: Size {} with format {} and mod {}", attrs.size, fourccToName(attrs.format), attrs.modifier))); if (drmModeAddFB2WithModifiers(backend->gpu->fd, attrs.size.x, attrs.size.y, attrs.format, boHandles.data(), attrs.strides.data(), attrs.offsets.data(), mods.data(), &newID, DRM_MODE_FB_MODIFIERS)) { backend->backend->log(AQ_LOG_ERROR, "drm: Failed to submit a buffer with drmModeAddFB2WithModifiers"); return 0; } } else { if (attrs.modifier != DRM_FORMAT_MOD_INVALID && attrs.modifier != DRM_FORMAT_MOD_LINEAR) { backend->backend->log(AQ_LOG_ERROR, "drm: drmModeAddFB2WithModifiers unsupported and buffer has explicit modifiers"); return 0; } TRACE(backend->backend->log( AQ_LOG_TRACE, std::format("drm: Using drmModeAddFB2 to import buffer into KMS: Size {} with format {} and mod {}", attrs.size, fourccToName(attrs.format), attrs.modifier))); if (drmModeAddFB2(backend->gpu->fd, attrs.size.x, attrs.size.y, attrs.format, boHandles.data(), attrs.strides.data(), attrs.offsets.data(), &newID, 0)) { backend->backend->log(AQ_LOG_ERROR, "drm: Failed to submit a buffer with drmModeAddFB2"); return 0; } } return newID; } void Aquamarine::SDRMConnectorCommitData::calculateMode(Hyprutils::Memory::CSharedPointer connector) { if (!connector || !connector->output || !connector->output->state) return; const auto& STATE = connector->output->state->state(); const auto MODE = STATE.mode ? STATE.mode : STATE.customMode; if (!MODE) { connector->backend->log(AQ_LOG_ERROR, "drm: no mode in calculateMode??"); return; } di_cvt_options options = { .red_blank_ver = DI_CVT_REDUCED_BLANKING_NONE, .h_pixels = (int)MODE->pixelSize.x, .v_lines = (int)MODE->pixelSize.y, .ip_freq_rqd = MODE->refreshRate ? MODE->refreshRate / 1000.0 : 60.0, }; di_cvt_timing timing; di_cvt_compute(&timing, &options); uint16_t hsync_start = (int)MODE->pixelSize.x + timing.h_front_porch; uint16_t vsync_start = timing.v_lines_rnd + timing.v_front_porch; uint16_t hsync_end = hsync_start + timing.h_sync; uint16_t vsync_end = vsync_start + timing.v_sync; modeInfo = drmModeModeInfo{ .clock = (uint32_t)std::round(timing.act_pixel_freq * 1000), .hdisplay = (uint16_t)MODE->pixelSize.x, .hsync_start = hsync_start, .hsync_end = hsync_end, .htotal = (uint16_t)(hsync_end + timing.h_back_porch), .vdisplay = (uint16_t)timing.v_lines_rnd, .vsync_start = vsync_start, .vsync_end = vsync_end, .vtotal = (uint16_t)(vsync_end + timing.v_back_porch), .vrefresh = (uint32_t)std::round(timing.act_frame_rate), .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC, }; snprintf(modeInfo.name, sizeof(modeInfo.name), "%dx%d", (int)MODE->pixelSize.x, (int)MODE->pixelSize.y); TRACE(connector->backend->log(AQ_LOG_TRACE, std::format("drm: calculateMode: modeline dump: {} {} {} {} {} {} {} {} {} {} {}", modeInfo.clock, modeInfo.hdisplay, modeInfo.hsync_start, modeInfo.hsync_end, modeInfo.htotal, modeInfo.vdisplay, modeInfo.vsync_start, modeInfo.vsync_end, modeInfo.vtotal, modeInfo.vrefresh, modeInfo.flags))); } Aquamarine::CDRMBufferAttachment::CDRMBufferAttachment(SP fb_) : fb(fb_) { ; } SP Aquamarine::CDRMLease::create(std::vector> outputs) { if (outputs.empty()) return nullptr; if (outputs.at(0)->getBackend()->type() != AQ_BACKEND_DRM) return nullptr; auto backend = ((CDRMBackend*)outputs.at(0)->getBackend().get())->self.lock(); for (auto const& o : outputs) { if (o->getBackend() != backend) { backend->log(AQ_LOG_ERROR, "drm lease: Mismatched backends"); return nullptr; } } std::vector objects; auto lease = SP(new CDRMLease); for (auto const& o : outputs) { auto drmo = ((CDRMOutput*)o.get())->self.lock(); backend->log(AQ_LOG_DEBUG, std::format("drm lease: output {}, connector {}", drmo->name, drmo->connector->id)); // FIXME: do we have to alloc a crtc here? if (!drmo->connector->crtc) { backend->log(AQ_LOG_ERROR, std::format("drm lease: output {} has no crtc", drmo->name)); return nullptr; } backend->log(AQ_LOG_DEBUG, std::format("drm lease: crtc {}, primary {}", drmo->connector->crtc->id, drmo->connector->crtc->primary->id)); objects.push_back(drmo->connector->id); objects.push_back(drmo->connector->crtc->id); objects.push_back(drmo->connector->crtc->primary->id); if (drmo->connector->crtc->cursor) objects.push_back(drmo->connector->crtc->cursor->id); lease->outputs.emplace_back(drmo); } backend->log(AQ_LOG_DEBUG, "drm lease: issuing a lease"); int leaseFD = drmModeCreateLease(backend->gpu->fd, objects.data(), objects.size(), O_CLOEXEC, &lease->lesseeID); if (leaseFD < 0) { backend->log(AQ_LOG_ERROR, "drm lease: drm rejected a lease"); return nullptr; } for (auto const& o : lease->outputs) { o->lease = lease; } lease->leaseFD = leaseFD; lease->backend = backend; backend->log(AQ_LOG_DEBUG, std::format("drm lease: lease granted with lessee id {}", lease->lesseeID)); return lease; } Aquamarine::CDRMLease::~CDRMLease() { if (active) terminate(); else destroy(); } void Aquamarine::CDRMLease::terminate() { active = false; if (drmModeRevokeLease(backend->gpu->fd, lesseeID) < 0) backend->log(AQ_LOG_ERROR, "drm lease: Failed to revoke lease"); destroy(); } void Aquamarine::CDRMLease::destroy() { events.destroy.emit(); } hyprwm-aquamarine-655e067/src/backend/drm/Math.cpp000066400000000000000000000111011506775317200220100ustar00rootroot00000000000000#include "Math.hpp" #include #include #include void matrixIdentity(float mat[9]) { static const float identity[9] = { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; memcpy(mat, identity, sizeof(identity)); } void matrixMultiply(float mat[9], const float a[9], const float b[9]) { float product[9]; product[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6]; product[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7]; product[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8]; product[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6]; product[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7]; product[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8]; product[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6]; product[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7]; product[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8]; memcpy(mat, product, sizeof(product)); } void matrixTranspose(float mat[9], const float a[9]) { float transposition[9] = { a[0], a[3], a[6], a[1], a[4], a[7], a[2], a[5], a[8], }; memcpy(mat, transposition, sizeof(transposition)); } void matrixTranslate(float mat[9], float x, float y) { float translate[9] = { 1.0f, 0.0f, x, 0.0f, 1.0f, y, 0.0f, 0.0f, 1.0f, }; matrixMultiply(mat, mat, translate); } void matrixScale(float mat[9], float x, float y) { float scale[9] = { x, 0.0f, 0.0f, 0.0f, y, 0.0f, 0.0f, 0.0f, 1.0f, }; matrixMultiply(mat, mat, scale); } void matrixRotate(float mat[9], float rad) { float rotate[9] = { (float)cos(rad), (float)-sin(rad), 0.0f, (float)sin(rad), (float)cos(rad), 0.0f, 0.0f, 0.0f, 1.0f, }; matrixMultiply(mat, mat, rotate); } std::unordered_map> transforms = { {HYPRUTILS_TRANSFORM_NORMAL, { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, {HYPRUTILS_TRANSFORM_90, { 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, {HYPRUTILS_TRANSFORM_180, { -1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, {HYPRUTILS_TRANSFORM_270, { 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, {HYPRUTILS_TRANSFORM_FLIPPED, { -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, {HYPRUTILS_TRANSFORM_FLIPPED_90, { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, {HYPRUTILS_TRANSFORM_FLIPPED_180, { 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, {HYPRUTILS_TRANSFORM_FLIPPED_270, { 0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }}, }; void matrixTransform(float mat[9], eTransform transform) { matrixMultiply(mat, mat, transforms.at(transform).data()); } void matrixProjection(float mat[9], int width, int height, eTransform transform) { memset(mat, 0, sizeof(*mat) * 9); const float* t = transforms.at(transform).data(); float x = 2.0f / width; float y = 2.0f / height; // Rotation + reflection mat[0] = x * t[0]; mat[1] = x * t[1]; mat[3] = y * -t[3]; mat[4] = y * -t[4]; // Translation mat[2] = -copysign(1.0f, mat[0] + mat[1]); mat[5] = -copysign(1.0f, mat[3] + mat[4]); // Identity mat[8] = 1.0f; } void projectBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9]) { double x = box.x; double y = box.y; double width = box.width; double height = box.height; matrixIdentity(mat); matrixTranslate(mat, x, y); if (rotation != 0) { matrixTranslate(mat, width / 2, height / 2); matrixRotate(mat, rotation); matrixTranslate(mat, -width / 2, -height / 2); } matrixScale(mat, width, height); if (transform != HYPRUTILS_TRANSFORM_NORMAL) { matrixTranslate(mat, 0.5, 0.5); matrixTransform(mat, transform); matrixTranslate(mat, -0.5, -0.5); } matrixMultiply(mat, projection, mat); } hyprwm-aquamarine-655e067/src/backend/drm/Math.hpp000066400000000000000000000013051506775317200220220ustar00rootroot00000000000000#pragma once // FIXME: migrate this to utils // includes box and vector as well #include using namespace Hyprutils::Math; void projectBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9]); void matrixProjection(float mat[9], int width, int height, eTransform transform); void matrixTransform(float mat[9], eTransform transform); void matrixRotate(float mat[9], float rad); void matrixScale(float mat[9], float x, float y); void matrixTranslate(float mat[9], float x, float y); void matrixTranspose(float mat[9], const float a[9]); void matrixMultiply(float mat[9], const float a[9], const float b[9]); void matrixIdentity(float mat[9]); hyprwm-aquamarine-655e067/src/backend/drm/Props.cpp000066400000000000000000000175321506775317200222400ustar00rootroot00000000000000#include extern "C" { #include #include #include #include } #include using namespace Aquamarine; using namespace Hyprutils::Memory; #define SP CSharedPointer struct prop_info { const char* name; size_t index; }; static const struct prop_info connector_info[] = { #define INDEX(name) (offsetof(SDRMConnector::UDRMConnectorProps, values.name) / sizeof(uint32_t)) {.name = "CRTC_ID", .index = INDEX(crtc_id)}, {.name = "Colorspace", .index = INDEX(Colorspace)}, {.name = "DPMS", .index = INDEX(dpms)}, {.name = "EDID", .index = INDEX(edid)}, {.name = "HDR_OUTPUT_METADATA", .index = INDEX(hdr_output_metadata)}, {.name = "PATH", .index = INDEX(path)}, {.name = "content type", .index = INDEX(content_type)}, {.name = "link-status", .index = INDEX(link_status)}, {.name = "max bpc", .index = INDEX(max_bpc)}, {.name = "non-desktop", .index = INDEX(non_desktop)}, {.name = "panel orientation", .index = INDEX(panel_orientation)}, {.name = "subconnector", .index = INDEX(subconnector)}, {.name = "vrr_capable", .index = INDEX(vrr_capable)}, #undef INDEX }; static const struct prop_info colorspace_info[] = { #define INDEX(name) (offsetof(SDRMConnector::UDRMConnectorColorspace, values.name) / sizeof(uint32_t)) {.name = "BT2020_RGB", .index = INDEX(BT2020_RGB)}, {.name = "BT2020_YCC", .index = INDEX(BT2020_YCC)}, {.name = "Default", .index = INDEX(Default)}, #undef INDEX }; static const struct prop_info crtc_info[] = { #define INDEX(name) (offsetof(SDRMCRTC::UDRMCRTCProps, values.name) / sizeof(uint32_t)) {.name = "ACTIVE", .index = INDEX(active)}, {.name = "CTM", .index = INDEX(ctm)}, {.name = "DEGAMMA_LUT", .index = INDEX(degamma_lut)}, {.name = "DEGAMMA_LUT_SIZE", .index = INDEX(degamma_lut_size)}, {.name = "GAMMA_LUT", .index = INDEX(gamma_lut)}, {.name = "GAMMA_LUT_SIZE", .index = INDEX(gamma_lut_size)}, {.name = "MODE_ID", .index = INDEX(mode_id)}, {.name = "OUT_FENCE_PTR", .index = INDEX(out_fence_ptr)}, {.name = "VRR_ENABLED", .index = INDEX(vrr_enabled)}, #undef INDEX }; static const struct prop_info plane_info[] = { #define INDEX(name) (offsetof(SDRMPlane::UDRMPlaneProps, values.name) / sizeof(uint32_t)) {.name = "CRTC_H", .index = INDEX(crtc_h)}, {.name = "CRTC_ID", .index = INDEX(crtc_id)}, {.name = "CRTC_W", .index = INDEX(crtc_w)}, {.name = "CRTC_X", .index = INDEX(crtc_x)}, {.name = "CRTC_Y", .index = INDEX(crtc_y)}, {.name = "FB_DAMAGE_CLIPS", .index = INDEX(fb_damage_clips)}, {.name = "FB_ID", .index = INDEX(fb_id)}, {.name = "HOTSPOT_X", .index = INDEX(hotspot_x)}, {.name = "HOTSPOT_Y", .index = INDEX(hotspot_y)}, {.name = "IN_FENCE_FD", .index = INDEX(in_fence_fd)}, {.name = "IN_FORMATS", .index = INDEX(in_formats)}, {.name = "SRC_H", .index = INDEX(src_h)}, {.name = "SRC_W", .index = INDEX(src_w)}, {.name = "SRC_X", .index = INDEX(src_x)}, {.name = "SRC_Y", .index = INDEX(src_y)}, {.name = "rotation", .index = INDEX(rotation)}, {.name = "type", .index = INDEX(type)}, #undef INDEX }; namespace Aquamarine { static int comparePropInfo(const void* arg1, const void* arg2) { const char* key = (const char*)arg1; const prop_info* elem = (prop_info*)arg2; return strcmp(key, elem->name); } static bool scanProperties(int fd, uint32_t id, uint32_t type, uint32_t* result, const prop_info* info, size_t info_len) { drmModeObjectProperties* props = drmModeObjectGetProperties(fd, id, type); if (!props) return false; for (uint32_t i = 0; i < props->count_props; ++i) { drmModePropertyRes* prop = drmModeGetProperty(fd, props->props[i]); if (!prop) continue; const prop_info* p = (prop_info*)bsearch(prop->name, info, info_len, sizeof(info[0]), comparePropInfo); if (p) result[p->index] = prop->prop_id; drmModeFreeProperty(prop); } drmModeFreeObjectProperties(props); return true; } static bool scanPropertyEnum(int fd, uint32_t propertyId, uint32_t* result, const prop_info* info, size_t info_len) { drmModePropertyRes* prop = drmModeGetProperty(fd, propertyId); if (!prop) return false; for (int i = 0; i < prop->count_enums; ++i) { const prop_info* p = (prop_info*)bsearch(prop->enums[i].name, info, info_len, sizeof(info[0]), comparePropInfo); if (p) result[p->index] = prop->enums[i].value; } drmModeFreeProperty(prop); return true; } bool getDRMConnectorProps(int fd, uint32_t id, SDRMConnector::UDRMConnectorProps* out) { return scanProperties(fd, id, DRM_MODE_OBJECT_CONNECTOR, out->props, connector_info, sizeof(connector_info) / sizeof(connector_info[0])); } bool getDRMConnectorColorspace(int fd, uint32_t id, SDRMConnector::UDRMConnectorColorspace* out) { return scanPropertyEnum(fd, id, out->props, colorspace_info, sizeof(colorspace_info) / sizeof(colorspace_info[0])); } bool getDRMCRTCProps(int fd, uint32_t id, SDRMCRTC::UDRMCRTCProps* out) { return scanProperties(fd, id, DRM_MODE_OBJECT_CRTC, out->props, crtc_info, sizeof(crtc_info) / sizeof(crtc_info[0])); } bool getDRMPlaneProps(int fd, uint32_t id, SDRMPlane::UDRMPlaneProps* out) { return scanProperties(fd, id, DRM_MODE_OBJECT_PLANE, out->props, plane_info, sizeof(plane_info) / sizeof(plane_info[0])); } bool getDRMProp(int fd, uint32_t obj, uint32_t prop, uint64_t* ret) { drmModeObjectProperties* props = drmModeObjectGetProperties(fd, obj, DRM_MODE_OBJECT_ANY); if (!props) return false; bool found = false; for (uint32_t i = 0; i < props->count_props; ++i) { if (props->props[i] == prop) { *ret = props->prop_values[i]; found = true; break; } } drmModeFreeObjectProperties(props); return found; } void* getDRMPropBlob(int fd, uint32_t obj, uint32_t prop, size_t* ret_len) { uint64_t blob_id; if (!getDRMProp(fd, obj, prop, &blob_id)) return nullptr; drmModePropertyBlobRes* blob = drmModeGetPropertyBlob(fd, blob_id); if (!blob) return nullptr; void* ptr = malloc(blob->length); if (!ptr) { drmModeFreePropertyBlob(blob); return nullptr; } memcpy(ptr, blob->data, blob->length); *ret_len = blob->length; drmModeFreePropertyBlob(blob); return ptr; } char* getDRMPropEnum(int fd, uint32_t obj, uint32_t prop_id) { uint64_t value; if (!getDRMProp(fd, obj, prop_id, &value)) return nullptr; drmModePropertyRes* prop = drmModeGetProperty(fd, prop_id); if (!prop) return nullptr; char* str = nullptr; for (int i = 0; i < prop->count_enums; i++) { if (prop->enums[i].value == value) { str = strdup(prop->enums[i].name); break; } } drmModeFreeProperty(prop); return str; } bool introspectDRMPropRange(int fd, uint32_t prop_id, uint64_t* min, uint64_t* max) { drmModePropertyRes* prop = drmModeGetProperty(fd, prop_id); if (!prop) return false; if (drmModeGetPropertyType(prop) != DRM_MODE_PROP_RANGE) { drmModeFreeProperty(prop); return false; } if (prop->count_values != 2) abort(); if (min != nullptr) *min = prop->values[0]; if (max != nullptr) *max = prop->values[1]; drmModeFreeProperty(prop); return true; } }; hyprwm-aquamarine-655e067/src/backend/drm/Props.hpp000066400000000000000000000013501506775317200222340ustar00rootroot00000000000000#pragma once #include namespace Aquamarine { bool getDRMConnectorProps(int fd, uint32_t id, SDRMConnector::UDRMConnectorProps* out); bool getDRMConnectorColorspace(int fd, uint32_t id, SDRMConnector::UDRMConnectorColorspace* out); bool getDRMCRTCProps(int fd, uint32_t id, SDRMCRTC::UDRMCRTCProps* out); bool getDRMPlaneProps(int fd, uint32_t id, SDRMPlane::UDRMPlaneProps* out); bool getDRMProp(int fd, uint32_t obj, uint32_t prop, uint64_t* ret); void* getDRMPropBlob(int fd, uint32_t obj, uint32_t prop, size_t* ret_len); char* getDRMPropEnum(int fd, uint32_t obj, uint32_t prop_id); bool introspectDRMPropRange(int fd, uint32_t prop_id, uint64_t* min, uint64_t* max); }; hyprwm-aquamarine-655e067/src/backend/drm/Renderer.cpp000066400000000000000000001302311506775317200226730ustar00rootroot00000000000000#include "Renderer.hpp" #include #include #include #include #include #include #include "Math.hpp" #include "Shared.hpp" #include "FormatUtils.hpp" #include #include using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; using namespace Hyprutils::OS; #define SP CSharedPointer #define WP CWeakPointer // macros #define GLCALL(__CALL__) \ { \ __CALL__; \ if (Aquamarine::isTrace()) { \ auto err = glGetError(); \ if (err != GL_NO_ERROR) { \ backend->log(AQ_LOG_ERROR, \ std::format("[GLES] Error in call at {}@{}: 0x{:x}", __LINE__, \ ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })(), err)); \ } \ } \ } // static funcs static WP gBackend; // ------------------- shader utils static GLuint compileShader(const GLuint& type, std::string src) { auto shader = glCreateShader(type); auto shaderSource = src.c_str(); glShaderSource(shader, 1, (const GLchar**)&shaderSource, nullptr); glCompileShader(shader); GLint ok; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); if (ok == GL_FALSE) return 0; return shader; } static GLuint createProgram(const std::string& vert, const std::string& frag) { auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert); if (vertCompiled == 0) return 0; auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag); if (fragCompiled == 0) return 0; auto prog = glCreateProgram(); glAttachShader(prog, vertCompiled); glAttachShader(prog, fragCompiled); glLinkProgram(prog); glDetachShader(prog, vertCompiled); glDetachShader(prog, fragCompiled); glDeleteShader(vertCompiled); glDeleteShader(fragCompiled); GLint ok; glGetProgramiv(prog, GL_LINK_STATUS, &ok); if (ok == GL_FALSE) return 0; return prog; } inline const std::string VERT_SRC = R"#( #version 300 es precision highp float; uniform mat3 proj; in vec2 pos; in vec2 texcoord; out vec2 v_texcoord; void main() { gl_Position = vec4(proj * vec3(pos, 1.0), 1.0); v_texcoord = texcoord; })#"; inline const std::string FRAG_SRC = R"#( #version 300 es precision highp float; in vec2 v_texcoord; out vec4 fragColor; uniform sampler2D tex; void main() { fragColor = texture(tex, v_texcoord); })#"; inline const std::string FRAG_SRC_EXT = R"#( #version 300 es #extension GL_OES_EGL_image_external_essl3 : require precision highp float; in vec2 v_texcoord; out vec4 fragColor; uniform samplerExternalOES texture0; void main() { fragColor = texture(texture0, v_texcoord); })#"; // ------------------- egl stuff static inline void loadGLProc(void* pProc, const char* name) { void* proc = (void*)eglGetProcAddress(name); if (!proc) { gBackend->log(AQ_LOG_ERROR, std::format("eglGetProcAddress({}) failed, the display driver doesn't support it", name)); abort(); } *(void**)pProc = proc; } static enum eBackendLogLevel eglLogToLevel(EGLint type) { switch (type) { case EGL_DEBUG_MSG_CRITICAL_KHR: return AQ_LOG_CRITICAL; case EGL_DEBUG_MSG_ERROR_KHR: return AQ_LOG_ERROR; case EGL_DEBUG_MSG_WARN_KHR: return AQ_LOG_WARNING; case EGL_DEBUG_MSG_INFO_KHR: return AQ_LOG_DEBUG; default: return AQ_LOG_DEBUG; } } static const char* eglErrorToString(EGLint error) { switch (error) { 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_DEVICE_EXT: return "EGL_BAD_DEVICE_EXT"; 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"; } } static void eglLog(EGLenum error, const char* command, EGLint type, EGLLabelKHR thread, EGLLabelKHR obj, const char* msg) { gBackend->log(eglLogToLevel(type), std::format("[EGL] Command {} errored out with {} (0x{}): {}", command, eglErrorToString(error), error, msg)); } static bool drmDeviceHasName(const drmDevice* device, const std::string& name) { for (size_t i = 0; i < DRM_NODE_MAX; i++) { if (!(device->available_nodes & (1 << i))) continue; if (device->nodes[i] == name) return true; } return false; } // ------------------- CDRMRenderer::SShader::~SShader() { if (program == 0) return; if (shaderVao) glDeleteVertexArrays(1, &shaderVao); if (shaderVboPos) glDeleteBuffers(1, &shaderVboPos); if (shaderVboUv) glDeleteBuffers(1, &shaderVboUv); glDeleteProgram(program); program = 0; } void CDRMRenderer::SShader::createVao() { const float fullVerts[] = { 1, 0, // top right 0, 0, // top left 1, 1, // bottom right 0, 1, // bottom left }; glGenVertexArrays(1, &shaderVao); glBindVertexArray(shaderVao); if (posAttrib != -1) { glGenBuffers(1, &shaderVboPos); glBindBuffer(GL_ARRAY_BUFFER, shaderVboPos); glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), fullVerts, GL_STATIC_DRAW); glEnableVertexAttribArray(posAttrib); glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 0, nullptr); } if (texAttrib != -1) { glGenBuffers(1, &shaderVboUv); glBindBuffer(GL_ARRAY_BUFFER, shaderVboUv); glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), fullVerts, GL_STATIC_DRAW); glEnableVertexAttribArray(texAttrib); glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 0, nullptr); } glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); } EGLDeviceEXT CDRMRenderer::eglDeviceFromDRMFD(int drmFD) { EGLint nDevices = 0; if (!proc.eglQueryDevicesEXT(0, nullptr, &nDevices)) { backend->log(AQ_LOG_ERROR, "CDRMRenderer(drm): eglQueryDevicesEXT failed"); return EGL_NO_DEVICE_EXT; } if (nDevices <= 0) { backend->log(AQ_LOG_ERROR, "CDRMRenderer(drm): no devices"); return EGL_NO_DEVICE_EXT; } std::vector devices; devices.resize(nDevices); if (!proc.eglQueryDevicesEXT(nDevices, devices.data(), &nDevices)) { backend->log(AQ_LOG_ERROR, "CDRMRenderer(drm): eglQueryDevicesEXT failed (2)"); return EGL_NO_DEVICE_EXT; } drmDevice* drmDev = nullptr; if (int ret = drmGetDevice(drmFD, &drmDev); ret < 0) { backend->log(AQ_LOG_ERROR, "CDRMRenderer(drm): drmGetDevice failed"); drmFreeDevice(&drmDev); return EGL_NO_DEVICE_EXT; } for (auto const& d : devices) { auto devName = proc.eglQueryDeviceStringEXT(d, EGL_DRM_DEVICE_FILE_EXT); if (!devName) continue; if (drmDeviceHasName(drmDev, devName)) { backend->log(AQ_LOG_DEBUG, std::format("CDRMRenderer(drm): Using device {}", devName)); drmFreeDevice(&drmDev); return d; } } drmFreeDevice(&drmDev); return EGL_NO_DEVICE_EXT; } std::optional>> CDRMRenderer::getModsForFormat(EGLint format) { // TODO: return std::expected when clang supports it EGLint len = 0; if (!proc.eglQueryDmaBufModifiersEXT(egl.display, format, 0, nullptr, nullptr, &len)) { backend->log(AQ_LOG_ERROR, std::format("EGL: eglQueryDmaBufModifiersEXT failed for format {}", fourccToName(format))); return std::nullopt; } if (len <= 0) return std::vector>{}; std::vector mods; std::vector external; mods.resize(len); external.resize(len); proc.eglQueryDmaBufModifiersEXT(egl.display, format, len, mods.data(), external.data(), &len); std::vector> result; result.reserve(mods.size()); bool linearIsExternal = false; for (size_t i = 0; i < mods.size(); ++i) { if (external.at(i) && mods.at(i) == DRM_FORMAT_MOD_LINEAR) linearIsExternal = true; result.emplace_back(mods.at(i), external.at(i)); } // if the driver doesn't mark linear as external, add it. It's allowed unless the driver says otherwise. (e.g. nvidia) if (!linearIsExternal && std::ranges::find(mods, DRM_FORMAT_MOD_LINEAR) == mods.end() && mods.empty()) result.emplace_back(DRM_FORMAT_MOD_LINEAR, true); return result; } void CDRMRenderer::useProgram(GLuint prog) { if (m_currentProgram == prog) return; GLCALL(glUseProgram(prog)); } bool CDRMRenderer::initDRMFormats() { std::vector formats; EGLint len = 0; proc.eglQueryDmaBufFormatsEXT(egl.display, 0, nullptr, &len); formats.resize(len); proc.eglQueryDmaBufFormatsEXT(egl.display, len, formats.data(), &len); if (formats.empty()) { backend->log(AQ_LOG_ERROR, "EGL: Failed to get formats"); return false; } TRACE(backend->log(AQ_LOG_TRACE, "EGL: Supported formats:")); std::vector dmaFormats; dmaFormats.reserve(formats.size()); for (auto const& fmt : formats) { std::vector> mods; if (exts.EXT_image_dma_buf_import_modifiers) { auto ret = getModsForFormat(fmt); if (!ret.has_value()) continue; mods = *ret; } hasModifiers = hasModifiers || !mods.empty(); // EGL can always do implicit modifiers. mods.emplace_back(DRM_FORMAT_MOD_INVALID, true); for (auto const& [mod, external] : mods) { dmaFormats.push_back(SGLFormat{ .drmFormat = (uint32_t)fmt, .modifier = mod, .external = external, }); } TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL: GPU Supports Format {} (0x{:x})", fourccToName((uint32_t)fmt), fmt))); for (auto const& [mod, external] : mods) { auto modName = drmGetFormatModifierName(mod); TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL: | {}with modifier 0x{:x}: {}", (external ? "external only " : ""), mod, modName ? modName : "?unknown?"))); free(modName); } } TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL: Found {} formats", dmaFormats.size()))); if (dmaFormats.empty()) { backend->log(AQ_LOG_ERROR, "EGL: No formats"); return false; } this->formats = dmaFormats; return true; } Aquamarine::CDRMRenderer::~CDRMRenderer() { if (egl.display) eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (egl.display && egl.context != EGL_NO_CONTEXT && egl.context != nullptr) eglDestroyContext(egl.display, egl.context); if (egl.display) eglTerminate(egl.display); eglReleaseThread(); } void CDRMRenderer::loadEGLAPI() { const std::string EGLEXTENSIONS = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); backend->log(AQ_LOG_DEBUG, std::format("Supported EGL client extensions: ({}) {}", std::count(EGLEXTENSIONS.begin(), EGLEXTENSIONS.end(), ' '), EGLEXTENSIONS)); exts.KHR_display_reference = EGLEXTENSIONS.contains("KHR_display_reference"); exts.EXT_platform_device = EGLEXTENSIONS.contains("EXT_platform_device"); exts.KHR_platform_gbm = EGLEXTENSIONS.contains("KHR_platform_gbm"); loadGLProc(&proc.eglGetPlatformDisplayEXT, "eglGetPlatformDisplayEXT"); loadGLProc(&proc.eglCreateImageKHR, "eglCreateImageKHR"); loadGLProc(&proc.eglDestroyImageKHR, "eglDestroyImageKHR"); loadGLProc(&proc.eglQueryDmaBufFormatsEXT, "eglQueryDmaBufFormatsEXT"); loadGLProc(&proc.eglQueryDmaBufModifiersEXT, "eglQueryDmaBufModifiersEXT"); loadGLProc(&proc.glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES"); loadGLProc(&proc.glEGLImageTargetRenderbufferStorageOES, "glEGLImageTargetRenderbufferStorageOES"); loadGLProc(&proc.eglDestroySyncKHR, "eglDestroySyncKHR"); loadGLProc(&proc.eglWaitSyncKHR, "eglWaitSyncKHR"); loadGLProc(&proc.eglCreateSyncKHR, "eglCreateSyncKHR"); loadGLProc(&proc.eglDupNativeFenceFDANDROID, "eglDupNativeFenceFDANDROID"); loadGLProc(&proc.glReadnPixelsEXT, "glReadnPixelsEXT"); if (EGLEXTENSIONS.contains("EGL_EXT_device_base") || EGLEXTENSIONS.contains("EGL_EXT_device_enumeration")) loadGLProc(&proc.eglQueryDevicesEXT, "eglQueryDevicesEXT"); if (EGLEXTENSIONS.contains("EGL_EXT_device_base") || EGLEXTENSIONS.contains("EGL_EXT_device_query")) loadGLProc(&proc.eglQueryDeviceStringEXT, "eglQueryDeviceStringEXT"); if (EGLEXTENSIONS.contains("EGL_KHR_debug")) { loadGLProc(&proc.eglDebugMessageControlKHR, "eglDebugMessageControlKHR"); static const EGLAttrib debugAttrs[] = { EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, EGL_NONE, }; proc.eglDebugMessageControlKHR(::eglLog, debugAttrs); } if (EGLEXTENSIONS.contains("EXT_platform_device")) { loadGLProc(&proc.eglQueryDevicesEXT, "eglQueryDevicesEXT"); loadGLProc(&proc.eglQueryDeviceStringEXT, "eglQueryDeviceStringEXT"); } RASSERT(eglBindAPI(EGL_OPENGL_ES_API) != EGL_FALSE, "Couldn't bind to EGL's opengl ES API. This means your gpu driver f'd up. This is not a Hyprland or Aquamarine issue."); } void CDRMRenderer::initContext() { RASSERT(egl.display != nullptr && egl.display != EGL_NO_DISPLAY, "CDRMRenderer: Can't create EGL context without display"); EGLint major, minor; if (eglInitialize(egl.display, &major, &minor) == EGL_FALSE) { backend->log(AQ_LOG_ERROR, "CDRMRenderer: fail, eglInitialize failed"); return; } std::string EGLEXTENSIONS = eglQueryString(egl.display, EGL_EXTENSIONS); exts.IMG_context_priority = EGLEXTENSIONS.contains("IMG_context_priority"); exts.EXT_create_context_robustness = EGLEXTENSIONS.contains("EXT_create_context_robustness"); exts.EXT_image_dma_buf_import = EGLEXTENSIONS.contains("EXT_image_dma_buf_import"); exts.EXT_image_dma_buf_import_modifiers = EGLEXTENSIONS.contains("EXT_image_dma_buf_import_modifiers"); std::vector attrs; if (exts.IMG_context_priority) { backend->log(AQ_LOG_DEBUG, "CDRMRenderer: IMG_context_priority supported, requesting high"); attrs.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG); attrs.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG); } if (exts.EXT_create_context_robustness) { backend->log(AQ_LOG_DEBUG, "CDRMRenderer: EXT_create_context_robustness supported, requesting lose on reset"); attrs.push_back(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT); attrs.push_back(EGL_LOSE_CONTEXT_ON_RESET_EXT); } attrs.push_back(EGL_CONTEXT_OPENGL_DEBUG); attrs.push_back(Aquamarine::isTrace() ? EGL_TRUE : EGL_FALSE); auto attrsNoVer = attrs; attrs.push_back(EGL_CONTEXT_MAJOR_VERSION); attrs.push_back(3); attrs.push_back(EGL_CONTEXT_MINOR_VERSION); attrs.push_back(2); attrs.push_back(EGL_NONE); egl.context = eglCreateContext(egl.display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attrs.data()); if (egl.context == EGL_NO_CONTEXT) { backend->log(AQ_LOG_ERROR, "CDRMRenderer: eglCreateContext failed with GLES 3.2, retrying GLES 3.0"); attrs = attrsNoVer; attrs.push_back(EGL_CONTEXT_MAJOR_VERSION); attrs.push_back(3); attrs.push_back(EGL_CONTEXT_MINOR_VERSION); attrs.push_back(0); attrs.push_back(EGL_NONE); egl.context = eglCreateContext(egl.display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attrs.data()); if (egl.context == EGL_NO_CONTEXT) { backend->log(AQ_LOG_ERROR, "CDRMRenderer: Can't create renderer, eglCreateContext failed with both GLES 3.2 and GLES 3.0"); return; } } if (exts.IMG_context_priority) { EGLint priority = EGL_CONTEXT_PRIORITY_MEDIUM_IMG; eglQueryContext(egl.display, egl.context, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &priority); if (priority != EGL_CONTEXT_PRIORITY_HIGH_IMG) backend->log(AQ_LOG_DEBUG, "CDRMRenderer: Failed to get a high priority context"); else backend->log(AQ_LOG_DEBUG, "CDRMRenderer: Got a high priority context"); } CEglContextGuard eglContext(*this); EGLEXTENSIONS = (const char*)glGetString(GL_EXTENSIONS); std::string gpuName = "unknown"; char* drmName = drmGetDeviceNameFromFd2(drmFD); if (drmName != nullptr) { gpuName = std::string{drmName}; free(drmName); } backend->log(AQ_LOG_DEBUG, std::format("Creating CDRMRenderer on gpu {}", gpuName)); backend->log(AQ_LOG_DEBUG, std::format("Using: {}", (char*)glGetString(GL_VERSION))); backend->log(AQ_LOG_DEBUG, std::format("Vendor: {}", (char*)glGetString(GL_VENDOR))); backend->log(AQ_LOG_DEBUG, std::format("Renderer: {}", (char*)glGetString(GL_RENDERER))); backend->log(AQ_LOG_DEBUG, std::format("Supported context extensions: ({}) {}", std::count(EGLEXTENSIONS.begin(), EGLEXTENSIONS.end(), ' '), EGLEXTENSIONS)); exts.EXT_read_format_bgra = EGLEXTENSIONS.contains("GL_EXT_read_format_bgra"); exts.EXT_texture_format_BGRA8888 = EGLEXTENSIONS.contains("GL_EXT_texture_format_BGRA8888"); } void CDRMRenderer::initResources() { CEglContextGuard eglContext(*this); if (!exts.EXT_image_dma_buf_import || !initDRMFormats()) backend->log(AQ_LOG_ERROR, "CDRMRenderer: initDRMFormats failed, dma-buf won't work"); shader.program = createProgram(VERT_SRC, FRAG_SRC); if (shader.program == 0) backend->log(AQ_LOG_ERROR, "CDRMRenderer: texture shader failed"); shader.proj = glGetUniformLocation(shader.program, "proj"); shader.posAttrib = glGetAttribLocation(shader.program, "pos"); shader.texAttrib = glGetAttribLocation(shader.program, "texcoord"); shader.tex = glGetUniformLocation(shader.program, "tex"); shader.createVao(); shaderExt.program = createProgram(VERT_SRC, FRAG_SRC_EXT); if (shaderExt.program == 0) backend->log(AQ_LOG_ERROR, "CDRMRenderer: external texture shader failed"); shaderExt.proj = glGetUniformLocation(shaderExt.program, "proj"); shaderExt.posAttrib = glGetAttribLocation(shaderExt.program, "pos"); shaderExt.texAttrib = glGetAttribLocation(shaderExt.program, "texcoord"); shaderExt.tex = glGetUniformLocation(shaderExt.program, "tex"); shaderExt.createVao(); } SP CDRMRenderer::attempt(SP backend_, int drmFD) { SP renderer = SP(new CDRMRenderer()); renderer->drmFD = drmFD; renderer->backend = backend_; gBackend = backend_; renderer->loadEGLAPI(); if (!renderer->exts.EXT_platform_device) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer(drm): Can't create renderer, EGL doesn't support EXT_platform_device"); return nullptr; } EGLDeviceEXT device = renderer->eglDeviceFromDRMFD(drmFD); if (device == EGL_NO_DEVICE_EXT) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer(drm): Can't create renderer, no matching devices found"); return nullptr; } std::vector attrs; if (renderer->exts.KHR_display_reference) { attrs.push_back(EGL_TRACK_REFERENCES_KHR); attrs.push_back(EGL_TRUE); } attrs.push_back(EGL_NONE); renderer->egl.display = renderer->proc.eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, device, attrs.data()); if (renderer->egl.display == EGL_NO_DISPLAY) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, eglGetPlatformDisplayEXT failed"); return nullptr; } renderer->initContext(); if (renderer->egl.context == nullptr || renderer->egl.context == EGL_NO_CONTEXT) return nullptr; renderer->initResources(); return renderer; } SP CDRMRenderer::attempt(SP backend_, Hyprutils::Memory::CSharedPointer allocator_) { SP renderer = SP(new CDRMRenderer()); renderer->drmFD = allocator_->drmFD(); renderer->backend = backend_; gBackend = backend_; renderer->loadEGLAPI(); if (!renderer->exts.KHR_platform_gbm) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer(gbm): Can't create renderer, EGL doesn't support KHR_platform_gbm"); return nullptr; } std::vector attrs; if (renderer->exts.KHR_display_reference) { attrs.push_back(EGL_TRACK_REFERENCES_KHR); attrs.push_back(EGL_TRUE); } attrs.push_back(EGL_NONE); renderer->egl.display = renderer->proc.eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, allocator_->gbmDevice, attrs.data()); if (renderer->egl.display == EGL_NO_DISPLAY) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, eglGetPlatformDisplayEXT failed"); return nullptr; } renderer->initContext(); if (renderer->egl.context == nullptr || renderer->egl.context == EGL_NO_CONTEXT) return nullptr; renderer->initResources(); return renderer; } CEglContextGuard::CEglContextGuard(const CDRMRenderer& renderer_) : renderer(renderer_) { savedEGLState.display = eglGetCurrentDisplay(); savedEGLState.context = eglGetCurrentContext(); savedEGLState.draw = eglGetCurrentSurface(EGL_DRAW); savedEGLState.read = eglGetCurrentSurface(EGL_READ); if (!eglMakeCurrent(renderer.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, renderer.egl.context)) renderer.backend->log(AQ_LOG_WARNING, "CDRMRenderer: setEGL eglMakeCurrent failed"); } CEglContextGuard::~CEglContextGuard() { EGLDisplay dpy = savedEGLState.display ? savedEGLState.display : renderer.egl.display; // egl can't handle this if (dpy == EGL_NO_DISPLAY) return; if (!eglMakeCurrent(dpy, savedEGLState.draw, savedEGLState.read, savedEGLState.context)) renderer.backend->log(AQ_LOG_WARNING, "CDRMRenderer: restoreEGL eglMakeCurrent failed"); } EGLImageKHR CDRMRenderer::createEGLImage(const SDMABUFAttrs& attrs) { std::vector attribs; attribs.push_back(EGL_WIDTH); attribs.push_back(attrs.size.x); attribs.push_back(EGL_HEIGHT); attribs.push_back(attrs.size.y); attribs.push_back(EGL_LINUX_DRM_FOURCC_EXT); attribs.push_back(attrs.format); TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL: createEGLImage: size {} with format {} and modifier 0x{:x}", attrs.size, fourccToName(attrs.format), attrs.modifier))); struct { EGLint fd; EGLint offset; EGLint pitch; EGLint modlo; EGLint modhi; } attrNames[4] = {{.fd = EGL_DMA_BUF_PLANE0_FD_EXT, .offset = EGL_DMA_BUF_PLANE0_OFFSET_EXT, .pitch = EGL_DMA_BUF_PLANE0_PITCH_EXT, .modlo = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, .modhi = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT}, {.fd = EGL_DMA_BUF_PLANE1_FD_EXT, .offset = EGL_DMA_BUF_PLANE1_OFFSET_EXT, .pitch = EGL_DMA_BUF_PLANE1_PITCH_EXT, .modlo = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, .modhi = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT}, {.fd = EGL_DMA_BUF_PLANE2_FD_EXT, .offset = EGL_DMA_BUF_PLANE2_OFFSET_EXT, .pitch = EGL_DMA_BUF_PLANE2_PITCH_EXT, .modlo = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, .modhi = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT}, {.fd = EGL_DMA_BUF_PLANE3_FD_EXT, .offset = EGL_DMA_BUF_PLANE3_OFFSET_EXT, .pitch = EGL_DMA_BUF_PLANE3_PITCH_EXT, .modlo = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, .modhi = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT}}; for (int i = 0; i < attrs.planes; i++) { attribs.push_back(attrNames[i].fd); attribs.push_back(attrs.fds[i]); attribs.push_back(attrNames[i].offset); attribs.push_back(attrs.offsets[i]); attribs.push_back(attrNames[i].pitch); attribs.push_back(attrs.strides[i]); if (hasModifiers && attrs.modifier != DRM_FORMAT_MOD_INVALID) { attribs.push_back(attrNames[i].modlo); attribs.push_back(attrs.modifier & 0xFFFFFFFF); attribs.push_back(attrNames[i].modhi); attribs.push_back(attrs.modifier >> 32); } } attribs.push_back(EGL_IMAGE_PRESERVED_KHR); attribs.push_back(EGL_TRUE); attribs.push_back(EGL_NONE); EGLImageKHR image = proc.eglCreateImageKHR(egl.display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, (int*)attribs.data()); if (image == EGL_NO_IMAGE_KHR) { backend->log(AQ_LOG_ERROR, std::format("EGL: EGLCreateImageKHR failed: {}", eglGetError())); return EGL_NO_IMAGE_KHR; } return image; } CGLTex CDRMRenderer::glTex(Hyprutils::Memory::CSharedPointer buffa) { CGLTex tex; const auto& dma = buffa->dmabuf(); tex.image = createEGLImage(dma); if (tex.image == EGL_NO_IMAGE_KHR) { backend->log(AQ_LOG_ERROR, std::format("EGL (glTex): createEGLImage failed: {}", eglGetError())); return tex; } bool external = false; for (auto const& fmt : formats) { if (fmt.drmFormat != dma.format || fmt.modifier != dma.modifier) continue; backend->log(AQ_LOG_DEBUG, std::format("CDRMRenderer::glTex: found format+mod, external = {}", fmt.external)); external = fmt.external; break; } tex.target = external ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; GLCALL(glGenTextures(1, &tex.texid)); GLCALL(tex.bind()); GLCALL(tex.setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GLCALL(tex.setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); GLCALL(proc.glEGLImageTargetTexture2DOES(tex.target, tex.image)); GLCALL(tex.unbind()); return tex; } constexpr GLenum PIXEL_BUFFER_FORMAT = GL_RGBA; void CDRMRenderer::readBuffer(Hyprutils::Memory::CSharedPointer buf, std::span out) { CEglContextGuard eglContext(*this); auto att = buf->attachments.get(); if (!att) { att = makeShared(self, buf, nullptr, 0, 0, CGLTex{}, std::vector()); buf->attachments.add(att); } const auto& dma = buf->dmabuf(); if (!att->eglImage) { att->eglImage = createEGLImage(dma); if (att->eglImage == EGL_NO_IMAGE_KHR) { backend->log(AQ_LOG_ERROR, std::format("EGL (readBuffer): createEGLImage failed: {}", eglGetError())); return; } GLCALL(glGenRenderbuffers(1, &att->rbo)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, att->rbo)); GLCALL(proc.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)att->eglImage)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); GLCALL(glGenFramebuffers(1, &att->fbo)); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, att->fbo)); GLCALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, att->rbo)); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { backend->log(AQ_LOG_ERROR, std::format("EGL (readBuffer): glCheckFramebufferStatus failed: {}", glGetError())); return; } } GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, att->fbo)); GLCALL(proc.glReadnPixelsEXT(0, 0, dma.size.x, dma.size.y, GL_RGBA, GL_UNSIGNED_BYTE, out.size(), out.data())); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); } void CDRMRenderer::waitOnSync(int fd) { TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (waitOnSync): attempting to wait on fd {}", fd))); std::array attribs; int dupFd = fcntl(fd, F_DUPFD_CLOEXEC, 0); if (dupFd < 0) { backend->log(AQ_LOG_TRACE, "EGL (waitOnSync): failed to dup fd for wait"); return; } attribs[0] = EGL_SYNC_NATIVE_FENCE_FD_ANDROID; attribs[1] = dupFd; attribs[2] = EGL_NONE; EGLSyncKHR sync = proc.eglCreateSyncKHR(egl.display, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs.data()); if (sync == EGL_NO_SYNC_KHR) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (waitOnSync): failed to create an egl sync for explicit")); if (dupFd >= 0) close(dupFd); return; } // we got a sync, now we just tell egl to wait before sampling if (proc.eglWaitSyncKHR(egl.display, sync, 0) != EGL_TRUE) { if (proc.eglDestroySyncKHR(egl.display, sync) != EGL_TRUE) TRACE(backend->log(AQ_LOG_TRACE, "EGL (waitOnSync): failed to destroy sync")); TRACE(backend->log(AQ_LOG_TRACE, "EGL (waitOnSync): failed to wait on the sync object")); return; } if (proc.eglDestroySyncKHR(egl.display, sync) != EGL_TRUE) TRACE(backend->log(AQ_LOG_TRACE, "EGL (waitOnSync): failed to destroy sync")); } int CDRMRenderer::recreateBlitSync() { TRACE(backend->log(AQ_LOG_TRACE, "EGL (recreateBlitSync): recreating blit sync")); if (egl.lastBlitSync) { TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (recreateBlitSync): cleaning up old sync (fd {})", egl.lastBlitSyncFD))); // cleanup last sync if (proc.eglDestroySyncKHR(egl.display, egl.lastBlitSync) != EGL_TRUE) TRACE(backend->log(AQ_LOG_TRACE, "EGL (recreateBlitSync): failed to destroy old sync")); if (egl.lastBlitSyncFD >= 0) close(egl.lastBlitSyncFD); egl.lastBlitSyncFD = -1; egl.lastBlitSync = nullptr; } EGLSyncKHR sync = proc.eglCreateSyncKHR(egl.display, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr); if (sync == EGL_NO_SYNC_KHR) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (recreateBlitSync): failed to create an egl sync for explicit")); return -1; } // we need to flush otherwise we might not get a valid fd glFlush(); int fd = proc.eglDupNativeFenceFDANDROID(egl.display, sync); if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (recreateBlitSync): failed to dup egl fence fd")); if (proc.eglDestroySyncKHR(egl.display, sync) != EGL_TRUE) TRACE(backend->log(AQ_LOG_TRACE, "EGL (recreateBlitSync): failed to destroy new sync")); return -1; } egl.lastBlitSync = sync; egl.lastBlitSyncFD = fd; TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (recreateBlitSync): success, new fence exported with fd {}", fd))); return fd; } void CDRMRenderer::clearBuffer(IBuffer* buf) { CEglContextGuard eglContext(*this); const auto& dmabuf = buf->dmabuf(); GLuint rboID = 0, fboID = 0; if (!dmabuf.success) { backend->log(AQ_LOG_ERROR, "EGL (clear): cannot clear a non-dmabuf"); return; } auto rboImage = createEGLImage(dmabuf); if (rboImage == EGL_NO_IMAGE_KHR) { backend->log(AQ_LOG_ERROR, std::format("EGL (clear): createEGLImage failed: {}", eglGetError())); return; } GLCALL(glGenRenderbuffers(1, &rboID)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, rboID)); GLCALL(proc.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)rboImage)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); GLCALL(glGenFramebuffers(1, &fboID)); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, fboID)); GLCALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboID)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, rboID)); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, fboID)); TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (clear): fbo {} rbo {}", fboID, rboID))); glClearColor(0.F, 0.F, 0.F, 1.F); glClear(GL_COLOR_BUFFER_BIT); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); glDeleteFramebuffers(1, &fboID); glDeleteRenderbuffers(1, &rboID); proc.eglDestroyImageKHR(egl.display, rboImage); } CDRMRenderer::SBlitResult CDRMRenderer::blit(SP from, SP to, SP primaryRenderer, int waitFD) { CEglContextGuard eglContext(*this); if (from->dmabuf().size != to->dmabuf().size) { backend->log(AQ_LOG_ERROR, "EGL (blit): buffer sizes mismatched"); return {}; } if (waitFD >= 0 && !CFileDescriptor::isReadable(waitFD)) { // wait on a provided explicit fence waitOnSync(waitFD); } // firstly, get a texture from the from buffer // if it has an attachment, use that // both from and to have the same AQ_ATTACHMENT_DRM_RENDERER_DATA. // Those buffers always come from different swapchains, so it's OK. WP fromTex; const auto& fromDma = from->dmabuf(); std::span intermediateBuf; { auto attachment = from->attachments.get(); if (attachment) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (blit): From attachment found")); fromTex = attachment->tex; intermediateBuf = attachment->intermediateBuf; } if ((!fromTex || !fromTex->image) && intermediateBuf.empty()) { backend->log(AQ_LOG_DEBUG, "EGL (blit): No attachment in from, creating a new image"); attachment = makeShared(self, from, nullptr, 0, 0, glTex(from), std::vector()); from->attachments.add(attachment); if (!attachment->tex->image && primaryRenderer) { backend->log(AQ_LOG_DEBUG, "EGL (blit): Failed to create image from source buffer directly, allocating intermediate buffer"); static_assert(PIXEL_BUFFER_FORMAT == GL_RGBA); // If the pixel buffer format changes, the below size calculation probably needs to as well. attachment->intermediateBuf.resize(fromDma.size.x * fromDma.size.y * 4); intermediateBuf = attachment->intermediateBuf; attachment->tex->target = GL_TEXTURE_2D; GLCALL(glGenTextures(1, &attachment->tex->texid)); } fromTex = attachment->tex; } if (!intermediateBuf.empty() && primaryRenderer) { // Note: this might modify from's attachments primaryRenderer->readBuffer(from, intermediateBuf); } } TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (blit): fromTex id {}, image 0x{:x}, target {}", fromTex->texid, (uintptr_t)fromTex->image, fromTex->target == GL_TEXTURE_2D ? "GL_TEXTURE_2D" : "GL_TEXTURE_EXTERNAL_OES"))); // then, get a rbo from our to buffer // if it has an attachment, use that EGLImageKHR rboImage = nullptr; GLuint rboID = 0, fboID = 0; const auto& toDma = to->dmabuf(); if (!verifyDestinationDMABUF(toDma)) { backend->log(AQ_LOG_ERROR, "EGL (blit): failed to blit: destination dmabuf unsupported"); return {}; } { auto attachment = to->attachments.get(); if (attachment) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (blit): To attachment found")); rboImage = attachment->eglImage; fboID = attachment->fbo; rboID = attachment->rbo; } if (!rboImage) { backend->log(AQ_LOG_DEBUG, "EGL (blit): No attachment in to, creating a new image"); rboImage = createEGLImage(toDma); if (rboImage == EGL_NO_IMAGE_KHR) { backend->log(AQ_LOG_ERROR, std::format("EGL (blit): createEGLImage failed: {}", eglGetError())); return {}; } GLCALL(glGenRenderbuffers(1, &rboID)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, rboID)); GLCALL(proc.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)rboImage)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); GLCALL(glGenFramebuffers(1, &fboID)); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, fboID)); GLCALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboID)); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { backend->log(AQ_LOG_ERROR, std::format("EGL (blit): glCheckFramebufferStatus failed: {}", glGetError())); return {}; } to->attachments.add(makeShared(self, to, rboImage, fboID, rboID, CGLTex{}, std::vector())); } } TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (blit): rboImage 0x{:x}", (uintptr_t)rboImage))); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, rboID)); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, fboID)); TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (blit): fbo {} rbo {}", fboID, rboID))); glClearColor(0.77F, 0.F, 0.74F, 1.F); glClear(GL_COLOR_BUFFER_BIT); // done, let's render the texture to the rbo CBox renderBox = {{}, toDma.size}; TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (blit): box size {}", renderBox.size()))); float mtx[9]; float base[9]; float monitorProj[9]; matrixIdentity(base); auto& SHADER = fromTex->target == GL_TEXTURE_2D ? shader : shaderExt; // KMS uses flipped y, we have to do FLIPPED_180 matrixTranslate(base, toDma.size.x / 2.0, toDma.size.y / 2.0); matrixTransform(base, HYPRUTILS_TRANSFORM_FLIPPED_180); matrixTranslate(base, -toDma.size.x / 2.0, -toDma.size.y / 2.0); projectBox(mtx, renderBox, HYPRUTILS_TRANSFORM_FLIPPED_180, 0, base); matrixProjection(monitorProj, toDma.size.x, toDma.size.y, HYPRUTILS_TRANSFORM_FLIPPED_180); float glMtx[9]; matrixMultiply(glMtx, monitorProj, mtx); static Vector2D lastViewportSize = {-1, -1}; if (lastViewportSize != toDma.size) { GLCALL(glViewport(0, 0, toDma.size.x, toDma.size.y)); lastViewportSize = toDma.size; } GLCALL(glActiveTexture(GL_TEXTURE0)); GLCALL(fromTex->bind()); GLCALL(fromTex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST)); GLCALL(fromTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST)); if (!intermediateBuf.empty()) GLCALL(glTexImage2D(fromTex->target, 0, PIXEL_BUFFER_FORMAT, fromDma.size.x, fromDma.size.y, 0, PIXEL_BUFFER_FORMAT, GL_UNSIGNED_BYTE, intermediateBuf.data())); useProgram(SHADER.program); GLCALL(glDisable(GL_BLEND)); GLCALL(glDisable(GL_SCISSOR_TEST)); matrixTranspose(glMtx, glMtx); GLCALL(glUniformMatrix3fv(SHADER.proj, 1, GL_FALSE, glMtx)); GLCALL(glUniform1i(SHADER.tex, 0)); GLCALL(glBindVertexArray(SHADER.shaderVao)); GLCALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); GLCALL(glBindVertexArray(0)); GLCALL(fromTex->unbind()); // get an explicit sync fd for the secondary gpu. // when we pass buffers between gpus we should always use explicit sync, // as implicit is not guaranteed at all int explicitFD = recreateBlitSync(); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); return {.success = true, .syncFD = explicitFD == -1 ? std::nullopt : std::optional{explicitFD}}; } void CDRMRenderer::onBufferAttachmentDrop(CDRMRendererBufferAttachment* attachment) { CEglContextGuard eglContext(*this); TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (onBufferAttachmentDrop): dropping fbo {} rbo {} image 0x{:x}", attachment->fbo, attachment->rbo, (uintptr_t)attachment->eglImage))); if (attachment->tex && attachment->tex->texid) GLCALL(glDeleteTextures(1, &attachment->tex->texid)); if (attachment->rbo) GLCALL(glDeleteRenderbuffers(1, &attachment->rbo)); if (attachment->fbo) GLCALL(glDeleteFramebuffers(1, &attachment->fbo)); if (attachment->eglImage) proc.eglDestroyImageKHR(egl.display, attachment->eglImage); if (attachment->tex && attachment->tex->image) proc.eglDestroyImageKHR(egl.display, attachment->tex->image); } bool CDRMRenderer::verifyDestinationDMABUF(const SDMABUFAttrs& attrs) { for (auto const& fmt : formats) { if (fmt.drmFormat != attrs.format) continue; if (fmt.modifier != attrs.modifier) continue; if (fmt.modifier != DRM_FORMAT_INVALID && fmt.external) { backend->log(AQ_LOG_ERROR, "EGL (verifyDestinationDMABUF): FAIL, format is external-only"); return false; } return true; } backend->log(AQ_LOG_ERROR, "EGL (verifyDestinationDMABUF): FAIL, format is unsupported by EGL"); return false; } constexpr std::optional CGLTex::getCacheStateIndex(GLenum pname) { switch (pname) { case GL_TEXTURE_WRAP_S: return TEXTURE_PAR_WRAP_S; case GL_TEXTURE_WRAP_T: return TEXTURE_PAR_WRAP_T; case GL_TEXTURE_MAG_FILTER: return TEXTURE_PAR_MAG_FILTER; case GL_TEXTURE_MIN_FILTER: return TEXTURE_PAR_MIN_FILTER; default: return std::nullopt; } } void CGLTex::bind() { glBindTexture(target, texid); } void CGLTex::unbind() { glBindTexture(target, 0); } void CGLTex::setTexParameter(GLenum pname, GLint param) { const auto cacheIndex = getCacheStateIndex(pname); if (!cacheIndex) { glTexParameteri(target, pname, param); return; } const auto idx = cacheIndex.value(); if (m_cachedStates[idx] == param) return; m_cachedStates[idx] = param; glTexParameteri(target, pname, param); } CDRMRendererBufferAttachment::CDRMRendererBufferAttachment(Hyprutils::Memory::CWeakPointer renderer_, Hyprutils::Memory::CSharedPointer buffer, EGLImageKHR image, GLuint fbo_, GLuint rbo_, CGLTex&& tex_, std::vector intermediateBuf_) : eglImage(image), fbo(fbo_), rbo(rbo_), tex(makeUnique(std::move(tex_))), intermediateBuf(intermediateBuf_), renderer(renderer_) { bufferDestroy = buffer->events.destroy.listen([this] { renderer->onBufferAttachmentDrop(this); }); } hyprwm-aquamarine-655e067/src/backend/drm/Renderer.hpp000066400000000000000000000202321506775317200226770ustar00rootroot00000000000000#pragma once #include #include "FormatUtils.hpp" #include #include #include #include #define __gl2_h_ // define guard for gl2ext.h #include #include #include #include #include #include namespace Aquamarine { class CGBMAllocator; class CGLTex { public: CGLTex() = default; void bind(); void unbind(); void setTexParameter(GLenum pname, GLint param); EGLImage image = nullptr; GLuint texid = 0; GLuint target = GL_TEXTURE_2D; private: enum eTextureParam : uint8_t { TEXTURE_PAR_WRAP_S = 0, TEXTURE_PAR_WRAP_T, TEXTURE_PAR_MAG_FILTER, TEXTURE_PAR_MIN_FILTER, TEXTURE_PAR_LAST, }; inline constexpr std::optional getCacheStateIndex(GLenum pname); std::array, TEXTURE_PAR_LAST> m_cachedStates; }; class CDRMRendererBufferAttachment : public IAttachment { public: CDRMRendererBufferAttachment(Hyprutils::Memory::CWeakPointer renderer_, Hyprutils::Memory::CSharedPointer buffer, EGLImageKHR image, GLuint fbo_, GLuint rbo_, CGLTex&& tex, std::vector intermediateBuf_); virtual ~CDRMRendererBufferAttachment() { ; } EGLImageKHR eglImage = nullptr; GLuint fbo = 0, rbo = 0; Hyprutils::Memory::CUniquePointer tex; Hyprutils::Signal::CHyprSignalListener bufferDestroy; std::vector intermediateBuf; Hyprutils::Memory::CWeakPointer renderer; }; // CEglContextGuard is a RAII abstraction for the EGL context. // On initialization, it sets the EGL context to the renderer's display, // and on destruction, it restores the previous EGL context. class CEglContextGuard { public: CEglContextGuard(const CDRMRenderer& renderer_); ~CEglContextGuard(); // No copy or move constructors CEglContextGuard(const CEglContextGuard&) = delete; CEglContextGuard& operator=(const CEglContextGuard&) = delete; CEglContextGuard(CEglContextGuard&&) = delete; CEglContextGuard& operator=(CEglContextGuard&&) = delete; private: const CDRMRenderer& renderer; struct { EGLDisplay display = nullptr; EGLContext context = nullptr; EGLSurface draw = nullptr, read = nullptr; } savedEGLState; }; class CDRMRenderer { public: ~CDRMRenderer(); static Hyprutils::Memory::CSharedPointer attempt(Hyprutils::Memory::CSharedPointer backend_, int drmFD); static Hyprutils::Memory::CSharedPointer attempt(Hyprutils::Memory::CSharedPointer backend_, Hyprutils::Memory::CSharedPointer allocator_); int drmFD = -1; struct SBlitResult { bool success = false; std::optional syncFD; }; SBlitResult blit(Hyprutils::Memory::CSharedPointer from, Hyprutils::Memory::CSharedPointer to, Hyprutils::Memory::CSharedPointer primaryRenderer, int waitFD = -1); // can't be a SP<> because we call it from buf's ctor... void clearBuffer(IBuffer* buf); void onBufferAttachmentDrop(CDRMRendererBufferAttachment* attachment); struct SShader { ~SShader(); void createVao(); GLuint program = 0; GLint proj = -1, tex = -1, posAttrib = -1, texAttrib = -1; GLuint shaderVao = 0, shaderVboPos = 0, shaderVboUv = 0; } shader, shaderExt; struct { PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = nullptr; PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = nullptr; PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = nullptr; PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr; PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES = nullptr; PFNEGLQUERYDMABUFFORMATSEXTPROC eglQueryDmaBufFormatsEXT = nullptr; PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr; PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR = nullptr; PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR = nullptr; PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR = nullptr; PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID = nullptr; PFNEGLDEBUGMESSAGECONTROLKHRPROC eglDebugMessageControlKHR = nullptr; PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = nullptr; PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT = nullptr; PFNGLREADNPIXELSEXTPROC glReadnPixelsEXT = nullptr; } proc; struct { bool EXT_read_format_bgra = false; bool EXT_texture_format_BGRA8888 = false; bool EXT_platform_device = false; bool KHR_platform_gbm = false; bool EXT_image_dma_buf_import = false; bool EXT_image_dma_buf_import_modifiers = false; bool KHR_display_reference = false; bool IMG_context_priority = false; bool EXT_create_context_robustness = false; } exts; struct { EGLDisplay display = nullptr; EGLContext context = nullptr; EGLSync lastBlitSync = nullptr; int lastBlitSyncFD = -1; } egl; CGLTex glTex(Hyprutils::Memory::CSharedPointer buf); void readBuffer(Hyprutils::Memory::CSharedPointer buf, std::span out); Hyprutils::Memory::CWeakPointer self; std::vector formats; private: CDRMRenderer() = default; EGLImageKHR createEGLImage(const SDMABUFAttrs& attrs); bool verifyDestinationDMABUF(const SDMABUFAttrs& attrs); void waitOnSync(int fd); int recreateBlitSync(); void loadEGLAPI(); EGLDeviceEXT eglDeviceFromDRMFD(int drmFD); void initContext(); void initResources(); bool initDRMFormats(); std::optional>> getModsForFormat(EGLint format); bool hasModifiers = false; void useProgram(GLuint prog); GLuint m_currentProgram = 0; Hyprutils::Memory::CWeakPointer backend; friend class CEglContextGuard; }; }; hyprwm-aquamarine-655e067/src/backend/drm/impl/000077500000000000000000000000001506775317200213625ustar00rootroot00000000000000hyprwm-aquamarine-655e067/src/backend/drm/impl/Atomic.cpp000066400000000000000000000531421506775317200233070ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "Shared.hpp" #include "aquamarine/output/Output.hpp" using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; #define SP CSharedPointer // HW capabilites aren't checked. Should be handled by the drivers (and highly unlikely to get a format outside of bpc range) // https://drmdb.emersion.fr/properties/3233857728/max%20bpc static uint8_t getMaxBPC(uint32_t drmFormat) { switch (drmFormat) { case DRM_FORMAT_XRGB8888: case DRM_FORMAT_XBGR8888: case DRM_FORMAT_RGBX8888: case DRM_FORMAT_BGRX8888: case DRM_FORMAT_ARGB8888: case DRM_FORMAT_ABGR8888: case DRM_FORMAT_RGBA8888: case DRM_FORMAT_BGRA8888: return 8; case DRM_FORMAT_XRGB2101010: case DRM_FORMAT_XBGR2101010: case DRM_FORMAT_RGBX1010102: case DRM_FORMAT_BGRX1010102: case DRM_FORMAT_ARGB2101010: case DRM_FORMAT_ABGR2101010: case DRM_FORMAT_RGBA1010102: case DRM_FORMAT_BGRA1010102: return 10; case DRM_FORMAT_XRGB16161616: case DRM_FORMAT_XBGR16161616: case DRM_FORMAT_ARGB16161616: case DRM_FORMAT_ABGR16161616: return 16; // FIXME? handle non-rgb formats and some weird stuff like DRM_FORMAT_AXBXGXRX106106106106 default: return 8; } } Aquamarine::CDRMAtomicRequest::CDRMAtomicRequest(Hyprutils::Memory::CWeakPointer backend_) : backend(backend_), req(drmModeAtomicAlloc()) { if (!req) failed = true; } Aquamarine::CDRMAtomicRequest::~CDRMAtomicRequest() { if (req) drmModeAtomicFree(req); } void Aquamarine::CDRMAtomicRequest::add(uint32_t id, uint32_t prop, uint64_t val) { if (failed) return; TRACE(backend->log(AQ_LOG_TRACE, std::format("atomic drm request: adding id {} prop {} with value {}", id, prop, val))); if (id == 0 || prop == 0) { backend->log(AQ_LOG_ERROR, "atomic drm request: failed to add prop: id / prop == 0"); return; } if (drmModeAtomicAddProperty(req, id, prop, val) < 0) { backend->log(AQ_LOG_ERROR, "atomic drm request: failed to add prop"); failed = true; } } void Aquamarine::CDRMAtomicRequest::planeProps(Hyprutils::Memory::CSharedPointer plane, Hyprutils::Memory::CSharedPointer fb, uint32_t crtc, Hyprutils::Math::Vector2D pos) { if (failed) return; if (!fb || !crtc) { // Disable the plane TRACE(backend->log(AQ_LOG_TRACE, std::format("atomic planeProps: disabling plane {}", plane->id))); add(plane->id, plane->props.values.fb_id, 0); add(plane->id, plane->props.values.crtc_id, 0); add(plane->id, plane->props.values.crtc_x, (uint64_t)pos.x); add(plane->id, plane->props.values.crtc_y, (uint64_t)pos.y); return; } TRACE(backend->log(AQ_LOG_TRACE, std::format("atomic planeProps: prop blobs: src_x {}, src_y {}, src_w {}, src_h {}, crtc_w {}, crtc_h {}, fb_id {}, crtc_id {}", plane->props.values.src_x, plane->props.values.src_y, plane->props.values.src_w, plane->props.values.src_h, plane->props.values.crtc_w, plane->props.values.crtc_h, plane->props.values.fb_id, plane->props.values.crtc_id))); // src_ are 16.16 fixed point (lol) add(plane->id, plane->props.values.src_x, 0); add(plane->id, plane->props.values.src_y, 0); add(plane->id, plane->props.values.src_w, ((uint64_t)fb->buffer->size.x) << 16); add(plane->id, plane->props.values.src_h, ((uint64_t)fb->buffer->size.y) << 16); add(plane->id, plane->props.values.crtc_w, (uint32_t)fb->buffer->size.x); add(plane->id, plane->props.values.crtc_h, (uint32_t)fb->buffer->size.y); add(plane->id, plane->props.values.fb_id, fb->id); add(plane->id, plane->props.values.crtc_id, crtc); planePropsPos(plane, pos); } void Aquamarine::CDRMAtomicRequest::planePropsPos(Hyprutils::Memory::CSharedPointer plane, Hyprutils::Math::Vector2D pos) { if (failed) return; TRACE(backend->log(AQ_LOG_TRACE, std::format("atomic planeProps: pos blobs: crtc_x {}, crtc_y {}", plane->props.values.crtc_x, plane->props.values.crtc_y))); add(plane->id, plane->props.values.crtc_x, (uint64_t)pos.x); add(plane->id, plane->props.values.crtc_y, (uint64_t)pos.y); } void Aquamarine::CDRMAtomicRequest::setConnector(Hyprutils::Memory::CSharedPointer connector) { conn = connector; } void Aquamarine::CDRMAtomicRequest::addConnector(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data) { const auto& STATE = connector->output->state->state(); const bool enable = STATE.enabled && data.mainFB; TRACE(backend->log(AQ_LOG_TRACE, std::format("atomic addConnector blobs: mode_id {}, active {}, crtc_id {}, link_status {}, content_type {}", connector->crtc->props.values.mode_id, connector->crtc->props.values.active, connector->props.values.crtc_id, connector->props.values.link_status, connector->props.values.content_type))); TRACE(backend->log(AQ_LOG_TRACE, std::format("atomic addConnector values: CRTC {}, mode {}", enable ? connector->crtc->id : 0, data.atomic.modeBlob))); conn = connector; if (enable) { drmModeModeInfo* currentMode = connector->getCurrentMode(); bool modeDiffers = true; if (currentMode) { modeDiffers = memcmp(currentMode, &data.modeInfo, sizeof(drmModeModeInfo)) != 0; free(currentMode); } if (modeDiffers) addConnectorModeset(connector, data); // Setup HDR if (connector->props.values.max_bpc && connector->maxBpcBounds.at(1)) add(connector->id, connector->props.values.max_bpc, getMaxBPC(data.mainFB->buffer->dmabuf().format)); if (connector->props.values.Colorspace && connector->colorspace.values.BT2020_RGB) add(connector->id, connector->props.values.Colorspace, STATE.wideColorGamut ? connector->colorspace.values.BT2020_RGB : connector->colorspace.values.Default); if (connector->props.values.hdr_output_metadata && data.atomic.hdrd) add(connector->id, connector->props.values.hdr_output_metadata, data.atomic.hdrBlob); } else addConnectorModeset(connector, data); addConnectorCursor(connector, data); add(connector->id, connector->props.values.crtc_id, enable ? connector->crtc->id : 0); if (enable && connector->props.values.content_type) add(connector->id, connector->props.values.content_type, STATE.contentType); add(connector->crtc->id, connector->crtc->props.values.active, enable); if (enable) { if (connector->output->supportsExplicit && STATE.committed & COutputState::AQ_OUTPUT_STATE_EXPLICIT_OUT_FENCE) add(connector->crtc->id, connector->crtc->props.values.out_fence_ptr, (uintptr_t)&STATE.explicitOutFence); if (connector->crtc->props.values.gamma_lut && data.atomic.gammad) add(connector->crtc->id, connector->crtc->props.values.gamma_lut, data.atomic.gammaLut); if (connector->crtc->props.values.degamma_lut && data.atomic.degammad) add(connector->crtc->id, connector->crtc->props.values.degamma_lut, data.atomic.degammaLut); if (connector->crtc->props.values.ctm && data.atomic.ctmd) add(connector->crtc->id, connector->crtc->props.values.ctm, data.atomic.ctmBlob); if (connector->crtc->props.values.vrr_enabled) add(connector->crtc->id, connector->crtc->props.values.vrr_enabled, (uint64_t)STATE.adaptiveSync); planeProps(connector->crtc->primary, data.mainFB, connector->crtc->id, {}); if (connector->output->supportsExplicit && STATE.explicitInFence >= 0) add(connector->crtc->primary->id, connector->crtc->primary->props.values.in_fence_fd, STATE.explicitInFence); if (connector->crtc->primary->props.values.fb_damage_clips) add(connector->crtc->primary->id, connector->crtc->primary->props.values.fb_damage_clips, data.atomic.fbDamage); } else { planeProps(connector->crtc->primary, nullptr, 0, {}); } } void Aquamarine::CDRMAtomicRequest::addConnectorModeset(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data) { if (!data.modeset) return; const auto& STATE = connector->output->state->state(); const bool enable = STATE.enabled && data.mainFB; data.atomic.blobbed = true; if (enable) { add(connector->crtc->id, connector->crtc->props.values.mode_id, data.atomic.modeBlob); if (connector->props.values.link_status) add(connector->id, connector->props.values.link_status, DRM_MODE_LINK_STATUS_GOOD); } else add(connector->crtc->id, connector->crtc->props.values.mode_id, data.atomic.modeBlob); } void Aquamarine::CDRMAtomicRequest::addConnectorCursor(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data) { if (!connector->crtc->cursor) return; const auto& STATE = connector->output->state->state(); const bool enable = STATE.enabled && data.mainFB; if (enable) { if (STATE.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_SHAPE || STATE.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_POS) { TRACE(backend->log(AQ_LOG_TRACE, STATE.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_SHAPE ? "atomic addConnector cursor shape" : "atomic addConnector cursor pos")); if (STATE.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_SHAPE) { if (!connector->output->cursorVisible) planeProps(connector->crtc->cursor, nullptr, 0, {}); else planeProps(connector->crtc->cursor, data.cursorFB, connector->crtc->id, connector->output->cursorPos - connector->output->cursorHotspot); } else if (connector->output->cursorVisible) planePropsPos(connector->crtc->cursor, connector->output->cursorPos - connector->output->cursorHotspot); } } else planeProps(connector->crtc->cursor, nullptr, 0, {}); } bool Aquamarine::CDRMAtomicRequest::commit(uint32_t flagssss) { static auto flagsToStr = [](uint32_t flags) { std::ostringstream result; if (flags & DRM_MODE_ATOMIC_ALLOW_MODESET) result << "ATOMIC_ALLOW_MODESET "; if (flags & DRM_MODE_ATOMIC_NONBLOCK) result << "ATOMIC_NONBLOCK "; if (flags & DRM_MODE_ATOMIC_TEST_ONLY) result << "ATOMIC_TEST_ONLY "; if (flags & DRM_MODE_PAGE_FLIP_EVENT) result << "PAGE_FLIP_EVENT "; if (flags & DRM_MODE_PAGE_FLIP_ASYNC) result << "PAGE_FLIP_ASYNC "; if (flags & (~DRM_MODE_ATOMIC_FLAGS)) result << " + invalid..."; return result.str(); }; if (failed) { backend->log((flagssss & DRM_MODE_ATOMIC_TEST_ONLY) ? AQ_LOG_DEBUG : AQ_LOG_ERROR, std::format("atomic drm request: failed to commit, failed flag set to true")); return false; } if (auto ret = drmModeAtomicCommit(backend->gpu->fd, req, flagssss, conn ? &conn->pendingPageFlip : nullptr); ret) { backend->log((flagssss & DRM_MODE_ATOMIC_TEST_ONLY) ? AQ_LOG_DEBUG : AQ_LOG_ERROR, std::format("atomic drm request: failed to commit: {}, flags: {}", strerror(ret == -1 ? errno : -ret), flagsToStr(flagssss))); return false; } return true; } void Aquamarine::CDRMAtomicRequest::destroyBlob(uint32_t id) { if (!id) return; if (drmModeDestroyPropertyBlob(backend->gpu->fd, id)) backend->log(AQ_LOG_ERROR, "atomic drm request: failed to destroy a blob"); } void Aquamarine::CDRMAtomicRequest::commitBlob(uint32_t* current, uint32_t next) { if (*current == next) return; destroyBlob(*current); *current = next; } void Aquamarine::CDRMAtomicRequest::rollbackBlob(uint32_t* current, uint32_t next) { if (*current == next) return; destroyBlob(next); } void Aquamarine::CDRMAtomicRequest::rollback(SDRMConnectorCommitData& data) { if (!conn) return; conn->crtc->atomic.ownModeID = true; if (data.atomic.blobbed) rollbackBlob(&conn->crtc->atomic.modeID, data.atomic.modeBlob); rollbackBlob(&conn->crtc->atomic.gammaLut, data.atomic.gammaLut); rollbackBlob(&conn->crtc->atomic.ctm, data.atomic.ctmBlob); destroyBlob(data.atomic.fbDamage); } void Aquamarine::CDRMAtomicRequest::apply(SDRMConnectorCommitData& data) { if (!conn) return; if (!conn->crtc->atomic.ownModeID) conn->crtc->atomic.modeID = 0; conn->crtc->atomic.ownModeID = true; if (data.atomic.blobbed) commitBlob(&conn->crtc->atomic.modeID, data.atomic.modeBlob); commitBlob(&conn->crtc->atomic.gammaLut, data.atomic.gammaLut); commitBlob(&conn->crtc->atomic.ctm, data.atomic.ctmBlob); destroyBlob(data.atomic.fbDamage); } Aquamarine::CDRMAtomicImpl::CDRMAtomicImpl(Hyprutils::Memory::CSharedPointer backend_) : backend(backend_) { ; } bool Aquamarine::CDRMAtomicImpl::prepareConnector(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data) { const auto& STATE = connector->output->state->state(); const bool enable = STATE.enabled; const auto& MODE = STATE.mode ? STATE.mode : STATE.customMode; if (data.modeset) { if (!enable) data.atomic.modeBlob = 0; else { if (drmModeCreatePropertyBlob(connector->backend->gpu->fd, &data.modeInfo, sizeof(drmModeModeInfo), &data.atomic.modeBlob)) { connector->backend->backend->log(AQ_LOG_ERROR, "atomic drm: failed to create a modeset blob"); return false; } TRACE(connector->backend->log(AQ_LOG_TRACE, std::format("Connector blob id {}: clock {}, {}x{}, vrefresh {}, name: {}", data.atomic.modeBlob, data.modeInfo.clock, data.modeInfo.hdisplay, data.modeInfo.vdisplay, data.modeInfo.vrefresh, data.modeInfo.name))); } } auto prepareGammaBlob = [connector](uint32_t prop, const std::vector& gammaLut, uint32_t* blobId) -> bool { if (!prop) // TODO: allow this with legacy gamma, perhaps. connector->backend->backend->log(AQ_LOG_ERROR, "atomic drm: failed to commit gamma: no gamma_lut prop"); else if (gammaLut.empty()) { blobId = nullptr; return true; } else { std::vector lut; lut.resize(gammaLut.size() / 3); // [r,g,b]+ for (size_t i = 0; i < lut.size(); ++i) { lut.at(i).red = gammaLut.at(i * 3 + 0); lut.at(i).green = gammaLut.at(i * 3 + 1); lut.at(i).blue = gammaLut.at(i * 3 + 2); lut.at(i).reserved = 0; } if (drmModeCreatePropertyBlob(connector->backend->gpu->fd, lut.data(), lut.size() * sizeof(drm_color_lut), blobId)) { connector->backend->backend->log(AQ_LOG_ERROR, "atomic drm: failed to create a gamma blob"); *blobId = 0; } else return true; } return false; }; if (STATE.committed & COutputState::AQ_OUTPUT_STATE_GAMMA_LUT) data.atomic.gammad = prepareGammaBlob(connector->crtc->props.values.gamma_lut, STATE.gammaLut, &data.atomic.gammaLut); if (STATE.committed & COutputState::AQ_OUTPUT_STATE_DEGAMMA_LUT) data.atomic.degammad = prepareGammaBlob(connector->crtc->props.values.degamma_lut, STATE.degammaLut, &data.atomic.degammaLut); if ((STATE.committed & COutputState::AQ_OUTPUT_STATE_CTM) && data.ctm.has_value()) { if (!connector->crtc->props.values.ctm) connector->backend->backend->log(AQ_LOG_ERROR, "atomic drm: failed to commit ctm: no ctm prop support"); else { static auto doubleToS3132Fixed = [](const double val) -> uint64_t { const uint64_t result = std::abs(val) * (1ULL << 32); if (val < 0) return result | 1ULL << 63; return result; }; drm_color_ctm ctm = {0}; for (size_t i = 0; i < 9; ++i) { ctm.matrix[i] = doubleToS3132Fixed(data.ctm->getMatrix()[i]); } if (drmModeCreatePropertyBlob(connector->backend->gpu->fd, &ctm, sizeof(drm_color_ctm), &data.atomic.ctmBlob)) { connector->backend->backend->log(AQ_LOG_ERROR, "atomic drm: failed to create a ctm blob"); data.atomic.ctmBlob = 0; } else data.atomic.ctmd = true; } } if ((STATE.committed & COutputState::AQ_OUTPUT_STATE_HDR) && data.hdrMetadata.has_value()) { if (!connector->props.values.hdr_output_metadata) connector->backend->backend->log(AQ_LOG_ERROR, "atomic drm: failed to commit hdr metadata: no HDR_OUTPUT_METADATA prop support"); else { if (!data.hdrMetadata->hdmi_metadata_type1.eotf) { data.atomic.hdrBlob = 0; data.atomic.hdrd = true; } else if (drmModeCreatePropertyBlob(connector->backend->gpu->fd, &data.hdrMetadata.value(), sizeof(hdr_output_metadata), &data.atomic.hdrBlob)) { connector->backend->backend->log(AQ_LOG_ERROR, "atomic drm: failed to create a hdr metadata blob"); data.atomic.hdrBlob = 0; data.atomic.hdrd = false; } else { data.atomic.hdrd = true; TRACE(connector->backend->backend->log( AQ_LOG_TRACE, std::format("atomic drm: setting hdr min {}, max {}, avg {}, content {}, primaries {},{} {},{} {},{} {},{}", data.hdrMetadata->hdmi_metadata_type1.min_display_mastering_luminance, data.hdrMetadata->hdmi_metadata_type1.max_display_mastering_luminance, data.hdrMetadata->hdmi_metadata_type1.max_fall, data.hdrMetadata->hdmi_metadata_type1.max_cll, data.hdrMetadata->hdmi_metadata_type1.display_primaries[0].x, data.hdrMetadata->hdmi_metadata_type1.display_primaries[0].y, data.hdrMetadata->hdmi_metadata_type1.display_primaries[1].x, data.hdrMetadata->hdmi_metadata_type1.display_primaries[1].y, data.hdrMetadata->hdmi_metadata_type1.display_primaries[2].x, data.hdrMetadata->hdmi_metadata_type1.display_primaries[2].y, data.hdrMetadata->hdmi_metadata_type1.display_primaries[0].x, data.hdrMetadata->hdmi_metadata_type1.white_point.x, data.hdrMetadata->hdmi_metadata_type1.white_point.y))); } } } if ((STATE.committed & COutputState::AQ_OUTPUT_STATE_DAMAGE) && connector->crtc->primary->props.values.fb_damage_clips && MODE) { if (STATE.damage.empty()) data.atomic.fbDamage = 0; else { TRACE(connector->backend->backend->log(AQ_LOG_TRACE, std::format("atomic drm: clipping damage to pixel size {}", MODE->pixelSize))); std::vector rects = STATE.damage.copy().intersect(CBox{{}, MODE->pixelSize}).getRects(); if (drmModeCreatePropertyBlob(connector->backend->gpu->fd, rects.data(), sizeof(pixman_box32_t) * rects.size(), &data.atomic.fbDamage)) { connector->backend->backend->log(AQ_LOG_ERROR, "atomic drm: failed to create a damage blob"); return false; } } } return true; } bool Aquamarine::CDRMAtomicImpl::commit(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data) { if (!prepareConnector(connector, data)) return false; CDRMAtomicRequest request(backend); request.addConnector(connector, data); uint32_t flags = data.flags; if (data.test) flags |= DRM_MODE_ATOMIC_TEST_ONLY; if (data.modeset) flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; if (!data.blocking && !data.test) flags |= DRM_MODE_ATOMIC_NONBLOCK; const bool ok = request.commit(flags); if (ok) { request.apply(data); if (!data.test && data.mainFB && connector->output->state->state().enabled && (flags & DRM_MODE_PAGE_FLIP_EVENT)) connector->isPageFlipPending = true; } else request.rollback(data); return ok; } bool Aquamarine::CDRMAtomicImpl::reset() { CDRMAtomicRequest request(backend); for (auto const& crtc : backend->crtcs) { request.add(crtc->id, crtc->props.values.mode_id, 0); request.add(crtc->id, crtc->props.values.active, 0); } for (auto const& conn : backend->connectors) { request.add(conn->id, conn->props.values.crtc_id, 0); } for (auto const& plane : backend->planes) { request.planeProps(plane, nullptr, 0, {}); } return request.commit(DRM_MODE_ATOMIC_ALLOW_MODESET); } bool Aquamarine::CDRMAtomicImpl::moveCursor(SP connector, bool skipSchedule) { if (!connector->output->cursorVisible || !connector->output->state->state().enabled || !connector->crtc || !connector->crtc->cursor) return true; if (!skipSchedule) { TRACE(connector->backend->log(AQ_LOG_TRACE, "atomic moveCursor")); connector->output->scheduleFrame(IOutput::AQ_SCHEDULE_CURSOR_MOVE); } return true; } hyprwm-aquamarine-655e067/src/backend/drm/impl/Legacy.cpp000066400000000000000000000163621506775317200233020ustar00rootroot00000000000000#include "aquamarine/output/Output.hpp" #include #include #include #include #include using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; #define SP CSharedPointer Aquamarine::CDRMLegacyImpl::CDRMLegacyImpl(Hyprutils::Memory::CSharedPointer backend_) : backend(backend_) { ; } bool Aquamarine::CDRMLegacyImpl::moveCursor(Hyprutils::Memory::CSharedPointer connector, bool skipSchedule) { if (!connector->output->cursorVisible || !connector->output->state->state().enabled || !connector->crtc || !connector->crtc->cursor) return true; if (!skipSchedule) connector->output->scheduleFrame(IOutput::AQ_SCHEDULE_CURSOR_MOVE); return true; } bool Aquamarine::CDRMLegacyImpl::commitInternal(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data) { const auto& STATE = connector->output->state->state(); SP mainFB; bool enable = STATE.enabled; if (enable) { if (!data.mainFB) connector->backend->backend->log(AQ_LOG_WARNING, "legacy drm: No buffer, will fall back to only modeset (if present)"); else mainFB = data.mainFB; } if (data.modeset) { connector->backend->backend->log(AQ_LOG_DEBUG, std::format("legacy drm: Modesetting CRTC {}", connector->crtc->id)); uint32_t dpms = enable ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF; if (drmModeConnectorSetProperty(connector->backend->gpu->fd, connector->id, connector->props.values.dpms, dpms)) { connector->backend->backend->log(AQ_LOG_ERROR, "legacy drm: Failed to set dpms"); return false; } std::vector connectors; drmModeModeInfo* mode = nullptr; if (enable) { connectors.push_back(connector->id); mode = &data.modeInfo; } if (mode) { connector->backend->backend->log( AQ_LOG_DEBUG, std::format("legacy drm: Modesetting CRTC, mode: clock {} hdisplay {} vdisplay {} vrefresh {}", mode->clock, mode->hdisplay, mode->vdisplay, mode->vrefresh)); } else connector->backend->backend->log(AQ_LOG_DEBUG, "legacy drm: Modesetting CRTC, mode null"); if (auto ret = drmModeSetCrtc(connector->backend->gpu->fd, connector->crtc->id, mainFB ? mainFB->id : -1, 0, 0, connectors.data(), connectors.size(), mode); ret) { connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: drmModeSetCrtc failed: {}", strerror(-ret))); return false; } } if (STATE.committed & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_ADAPTIVE_SYNC) { if (STATE.adaptiveSync && !connector->canDoVrr) { connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: connector {} can't do vrr", connector->id)); return false; } if (connector->crtc->props.values.vrr_enabled) { if (auto ret = drmModeObjectSetProperty(backend->gpu->fd, connector->crtc->id, DRM_MODE_OBJECT_CRTC, connector->crtc->props.values.vrr_enabled, (uint64_t)STATE.adaptiveSync); ret) { connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: drmModeObjectSetProperty: vrr -> {} failed: {}", STATE.adaptiveSync, strerror(-ret))); return false; } } connector->output->vrrActive = STATE.adaptiveSync; connector->backend->backend->log(AQ_LOG_DEBUG, std::format("legacy drm: connector {} vrr -> {}", connector->id, STATE.adaptiveSync)); } // TODO: gamma if (data.cursorFB && connector->crtc->cursor && connector->output->cursorVisible && enable && (STATE.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_SHAPE || STATE.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_POS)) { uint32_t boHandle = 0; auto attrs = data.cursorFB->buffer->dmabuf(); if (int ret = drmPrimeFDToHandle(connector->backend->gpu->fd, attrs.fds.at(0), &boHandle); ret) { connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: drmPrimeFDToHandle failed: {}", strerror(-ret))); return false; } connector->backend->backend->log(AQ_LOG_DEBUG, std::format("legacy drm: cursor fb: {} with bo handle {} from fd {}, size {}", connector->backend->gpu->fd, boHandle, data.cursorFB->buffer->dmabuf().fds.at(0), data.cursorFB->buffer->size)); Vector2D cursorPos = connector->output->cursorPos; struct drm_mode_cursor2 request = { .flags = DRM_MODE_CURSOR_BO | DRM_MODE_CURSOR_MOVE, .crtc_id = connector->crtc->id, .x = (int32_t)cursorPos.x, .y = (int32_t)cursorPos.y, .width = (uint32_t)data.cursorFB->buffer->size.x, .height = (uint32_t)data.cursorFB->buffer->size.y, .handle = boHandle, .hot_x = (int32_t)connector->output->cursorHotspot.x, .hot_y = (int32_t)connector->output->cursorHotspot.y, }; int ret = drmIoctl(connector->backend->gpu->fd, DRM_IOCTL_MODE_CURSOR2, &request); if (boHandle && drmCloseBufferHandle(connector->backend->gpu->fd, boHandle)) connector->backend->backend->log(AQ_LOG_ERROR, "legacy drm: drmCloseBufferHandle in cursor failed"); if (ret) { connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: cursor drmIoctl failed: {}", strerror(errno))); return false; } } else if (drmModeSetCursor(connector->backend->gpu->fd, connector->crtc->id, 0, 0, 0)) connector->backend->backend->log(AQ_LOG_ERROR, "legacy drm: cursor null failed"); if (!enable) return true; if (!(data.flags & DRM_MODE_PAGE_FLIP_EVENT)) return true; if (int ret = drmModePageFlip(connector->backend->gpu->fd, connector->crtc->id, mainFB ? mainFB->id : -1, data.flags, &connector->pendingPageFlip); ret) { connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: drmModePageFlip failed: {}", strerror(-ret))); return false; } connector->isPageFlipPending = true; return true; } bool Aquamarine::CDRMLegacyImpl::testInternal(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data) { return true; // TODO: lol } bool Aquamarine::CDRMLegacyImpl::commit(Hyprutils::Memory::CSharedPointer connector, SDRMConnectorCommitData& data) { if (!testInternal(connector, data)) return false; return commitInternal(connector, data); } bool Aquamarine::CDRMLegacyImpl::reset() { bool ok = true; for (auto const& connector : backend->connectors) { if (!connector->crtc) continue; if (int ret = drmModeSetCrtc(backend->gpu->fd, connector->crtc->id, 0, 0, 0, nullptr, 0, nullptr); ret) { connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: reset failed: {}", strerror(-ret))); ok = false; } } return ok; } hyprwm-aquamarine-655e067/src/buffer/000077500000000000000000000000001506775317200175215ustar00rootroot00000000000000hyprwm-aquamarine-655e067/src/buffer/Buffer.cpp000066400000000000000000000012671506775317200214440ustar00rootroot00000000000000#include #include "Shared.hpp" using namespace Aquamarine; SDMABUFAttrs Aquamarine::IBuffer::dmabuf() { return SDMABUFAttrs{}; } SSHMAttrs Aquamarine::IBuffer::shm() { return SSHMAttrs{}; } std::tuple Aquamarine::IBuffer::beginDataPtr(uint32_t flags) { return {nullptr, 0, 0}; } void Aquamarine::IBuffer::endDataPtr() { ; // empty } void Aquamarine::IBuffer::sendRelease() { ; } void Aquamarine::IBuffer::lock() { locks++; } void Aquamarine::IBuffer::unlock() { locks--; ASSERT(locks >= 0); if (locks <= 0) sendRelease(); } bool Aquamarine::IBuffer::locked() { return locks; } hyprwm-aquamarine-655e067/src/include/000077500000000000000000000000001506775317200176735ustar00rootroot00000000000000hyprwm-aquamarine-655e067/src/include/FormatUtils.hpp000066400000000000000000000001411506775317200226510ustar00rootroot00000000000000#pragma once #include #include std::string fourccToName(uint32_t drmFormat);hyprwm-aquamarine-655e067/src/include/Shared.hpp000066400000000000000000000046171506775317200216220ustar00rootroot00000000000000#pragma once #include #include #include namespace Aquamarine { bool envEnabled(const std::string& env); bool envExplicitlyDisabled(const std::string& env); bool isTrace(); }; #define RASSERT(expr, reason, ...) \ if (!(expr)) { \ std::cout << std::format("\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \ std::format(reason, ##__VA_ARGS__), __LINE__, \ ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })()); \ std::cout << "[Aquamarine] Assertion failed!"; \ raise(SIGABRT); \ } #define ASSERT(expr) RASSERT(expr, "?") #define TRACE(expr) \ { \ if (Aquamarine::isTrace()) { \ expr; \ } \ } hyprwm-aquamarine-655e067/src/input/000077500000000000000000000000001506775317200174075ustar00rootroot00000000000000hyprwm-aquamarine-655e067/src/input/Input.cpp000066400000000000000000000012651506775317200212160ustar00rootroot00000000000000#include libinput_device* Aquamarine::IPointer::getLibinputHandle() { return nullptr; } libinput_device* Aquamarine::IKeyboard::getLibinputHandle() { return nullptr; } libinput_device* Aquamarine::ITouch::getLibinputHandle() { return nullptr; } libinput_device* Aquamarine::ISwitch::getLibinputHandle() { return nullptr; } libinput_device* Aquamarine::ITabletTool::getLibinputHandle() { return nullptr; } libinput_device* Aquamarine::ITablet::getLibinputHandle() { return nullptr; } libinput_device* Aquamarine::ITabletPad::getLibinputHandle() { return nullptr; } void Aquamarine::IKeyboard::updateLEDs(uint32_t leds) { ; } hyprwm-aquamarine-655e067/src/misc/000077500000000000000000000000001506775317200172035ustar00rootroot00000000000000hyprwm-aquamarine-655e067/src/misc/Attachment.cpp000066400000000000000000000011771506775317200220050ustar00rootroot00000000000000#include using namespace Aquamarine; using namespace Hyprutils::Memory; #define SP CSharedPointer void Aquamarine::CAttachmentManager::add(SP attachment) { const IAttachment& att = *attachment; attachments[typeid(att)] = attachment; } void Aquamarine::CAttachmentManager::remove(SP attachment) { const IAttachment& att = *attachment; auto it = attachments.find(typeid(att)); if (it != attachments.end() && it->second == attachment) attachments.erase(it); } void Aquamarine::CAttachmentManager::clear() { attachments.clear(); } hyprwm-aquamarine-655e067/src/output/000077500000000000000000000000001506775317200176105ustar00rootroot00000000000000hyprwm-aquamarine-655e067/src/output/Output.cpp000066400000000000000000000104701506775317200216160ustar00rootroot00000000000000#include using namespace Aquamarine; Aquamarine::IOutput::~IOutput() { events.destroy.emit(); } Hyprutils::Memory::CSharedPointer Aquamarine::IOutput::preferredMode() { for (auto const& m : modes) { if (m->preferred) return m; } return nullptr; } void Aquamarine::IOutput::moveCursor(const Hyprutils::Math::Vector2D& coord, bool skipSchedule) { ; } bool Aquamarine::IOutput::setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot) { return false; } void Aquamarine::IOutput::setCursorVisible(bool visible) { ; } void Aquamarine::IOutput::scheduleFrame(const scheduleFrameReason reason) { ; } Hyprutils::Math::Vector2D Aquamarine::IOutput::cursorPlaneSize() { return {}; // error } size_t Aquamarine::IOutput::getGammaSize() { return 0; } size_t Aquamarine::IOutput::getDeGammaSize() { return 0; } bool Aquamarine::IOutput::destroy() { return false; } const Aquamarine::COutputState::SInternalState& Aquamarine::COutputState::state() { return internalState; } void Aquamarine::COutputState::addDamage(const Hyprutils::Math::CRegion& region) { internalState.damage.add(region); internalState.committed |= AQ_OUTPUT_STATE_DAMAGE; } void Aquamarine::COutputState::clearDamage() { internalState.damage.clear(); internalState.committed |= AQ_OUTPUT_STATE_DAMAGE; } void Aquamarine::COutputState::setEnabled(bool enabled) { internalState.enabled = enabled; internalState.committed |= AQ_OUTPUT_STATE_ENABLED; } void Aquamarine::COutputState::setAdaptiveSync(bool enabled) { internalState.adaptiveSync = enabled; internalState.committed |= AQ_OUTPUT_STATE_ADAPTIVE_SYNC; } void Aquamarine::COutputState::setPresentationMode(eOutputPresentationMode mode) { internalState.presentationMode = mode; internalState.committed |= AQ_OUTPUT_STATE_PRESENTATION_MODE; } void Aquamarine::COutputState::setGammaLut(const std::vector& lut) { internalState.gammaLut = lut; internalState.committed |= AQ_OUTPUT_STATE_GAMMA_LUT; } void Aquamarine::COutputState::setDeGammaLut(const std::vector& lut) { internalState.gammaLut = lut; internalState.committed |= AQ_OUTPUT_STATE_DEGAMMA_LUT; } void Aquamarine::COutputState::setMode(Hyprutils::Memory::CSharedPointer mode) { internalState.mode = mode; internalState.customMode = nullptr; internalState.committed |= AQ_OUTPUT_STATE_MODE; } void Aquamarine::COutputState::setCustomMode(Hyprutils::Memory::CSharedPointer mode) { internalState.mode.reset(); internalState.customMode = mode; internalState.committed |= AQ_OUTPUT_STATE_MODE; } void Aquamarine::COutputState::setFormat(uint32_t drmFormat) { internalState.drmFormat = drmFormat; internalState.committed |= AQ_OUTPUT_STATE_FORMAT; } void Aquamarine::COutputState::setBuffer(Hyprutils::Memory::CSharedPointer buffer) { internalState.buffer = buffer; internalState.committed |= AQ_OUTPUT_STATE_BUFFER; } void Aquamarine::COutputState::setExplicitInFence(int32_t fenceFD) { internalState.explicitInFence = fenceFD; internalState.committed |= AQ_OUTPUT_STATE_EXPLICIT_IN_FENCE; } void Aquamarine::COutputState::enableExplicitOutFenceForNextCommit() { internalState.committed |= AQ_OUTPUT_STATE_EXPLICIT_OUT_FENCE; } void Aquamarine::COutputState::resetExplicitFences() { // fences are now used, let's reset them to not confuse ourselves later. internalState.explicitInFence = -1; internalState.explicitOutFence = -1; } void Aquamarine::COutputState::setCTM(const Hyprutils::Math::Mat3x3& ctm) { internalState.ctm = ctm; internalState.committed |= AQ_OUTPUT_STATE_CTM; } void Aquamarine::COutputState::setWideColorGamut(bool wcg) { internalState.wideColorGamut = wcg; internalState.committed |= AQ_OUTPUT_STATE_WCG; } void Aquamarine::COutputState::setHDRMetadata(const hdr_output_metadata& metadata) { internalState.hdrMetadata = metadata; internalState.committed |= AQ_OUTPUT_STATE_HDR; } void Aquamarine::COutputState::setContentType(const uint16_t drmContentType) { internalState.contentType = drmContentType; } void Aquamarine::COutputState::onCommit() { internalState.committed = 0; internalState.damage.clear(); } hyprwm-aquamarine-655e067/src/utils/000077500000000000000000000000001506775317200174105ustar00rootroot00000000000000hyprwm-aquamarine-655e067/src/utils/FormatUtils.cpp000066400000000000000000000004171506775317200223670ustar00rootroot00000000000000#include "FormatUtils.hpp" #include #include #include std::string fourccToName(uint32_t drmFormat) { auto fmt = drmGetFormatName(drmFormat); std::string name = fmt ? fmt : "unknown"; free(fmt); return name; } hyprwm-aquamarine-655e067/src/utils/Shared.cpp000066400000000000000000000007051506775317200213240ustar00rootroot00000000000000#include "Shared.hpp" #include bool Aquamarine::envEnabled(const std::string& env) { auto e = getenv(env.c_str()); return e && e == std::string{"1"}; } bool Aquamarine::envExplicitlyDisabled(const std::string& env) { auto e = getenv(env.c_str()); return e && e == std::string{"0"}; } static bool trace = []() -> bool { return Aquamarine::envEnabled("AQ_TRACE"); }(); bool Aquamarine::isTrace() { return trace; } hyprwm-aquamarine-655e067/tests/000077500000000000000000000000001506775317200166235ustar00rootroot00000000000000hyprwm-aquamarine-655e067/tests/Attachments.cpp000066400000000000000000000043331506775317200216050ustar00rootroot00000000000000#include #include #include #include "shared.hpp" class CFooAttachment : public Aquamarine::IAttachment { public: int counter = 0; }; class CBarAttachment : public Aquamarine::IAttachment { public: int counter = 0; }; int main() { Aquamarine::CAttachmentManager attachments; int ret = 0; EXPECT(attachments.has(), false); EXPECT(attachments.get(), nullptr); EXPECT(attachments.has(), false); EXPECT(attachments.get(), nullptr); auto foo = Hyprutils::Memory::makeShared(); attachments.add(foo); EXPECT(attachments.has(), true); EXPECT(attachments.has(), false); foo->counter++; EXPECT(attachments.get(), foo); EXPECT(attachments.get()->counter, 1); attachments.add(Hyprutils::Memory::makeShared()); EXPECT(attachments.get()->counter, 1); EXPECT(attachments.get()->counter, 0); Hyprutils::Memory::CWeakPointer bar = attachments.get(); EXPECT(bar.valid(), true); bar->counter = 5; // test overriding an attachment attachments.add(Hyprutils::Memory::makeShared()); Hyprutils::Memory::CWeakPointer newBar = attachments.get(); EXPECT(bar == newBar, false); EXPECT(attachments.get()->counter, 0); // should be a noop as this is a different attachment attachments.remove(Hyprutils::Memory::makeShared()); EXPECT(attachments.has(), true); EXPECT(attachments.has(), true); attachments.remove(foo); EXPECT(attachments.has(), false); EXPECT(attachments.has(), true); attachments.removeByType(); EXPECT(attachments.has(), false); EXPECT(attachments.has(), false); EXPECT(foo.strongRef(), 1); EXPECT(bar.valid(), false); EXPECT(newBar.valid(), false); return ret; } hyprwm-aquamarine-655e067/tests/SimpleWindow.cpp000066400000000000000000000070261506775317200217550ustar00rootroot00000000000000#include #include #include #include #include using namespace Hyprutils::Signal; using namespace Hyprutils::Memory; #define SP CSharedPointer static const char* aqLevelToString(Aquamarine::eBackendLogLevel level) { switch (level) { case Aquamarine::eBackendLogLevel::AQ_LOG_TRACE: return "TRACE"; case Aquamarine::eBackendLogLevel::AQ_LOG_DEBUG: return "DEBUG"; case Aquamarine::eBackendLogLevel::AQ_LOG_ERROR: return "ERROR"; case Aquamarine::eBackendLogLevel::AQ_LOG_WARNING: return "WARNING"; case Aquamarine::eBackendLogLevel::AQ_LOG_CRITICAL: return "CRITICAL"; default: break; } return "UNKNOWN"; } void aqLog(Aquamarine::eBackendLogLevel level, std::string msg) { std::cout << "[AQ] [" << aqLevelToString(level) << "] " << msg << "\n"; } CHyprSignalListener newOutputListener, outputFrameListener, outputStateListener, mouseMotionListener, keyboardKeyListener, newMouseListener, newKeyboardListener; SP output; // void onFrame() { std::cout << "[Client] onFrame\n"; auto buf = output->swapchain->next(nullptr); output->state->setBuffer(buf); output->commit(); } void onState(const Aquamarine::IOutput::SStateEvent& event) { std::cout << "[Client] onState with size " << std::format("{}", event.size) << "\n"; output->state->setEnabled(true); output->state->setCustomMode(makeShared(Aquamarine::SOutputMode{.pixelSize = event.size})); output->state->setFormat(DRM_FORMAT_XRGB8888); output->commit(); } int main(int argc, char** argv, char** envp) { Aquamarine::SBackendOptions options; options.logFunction = aqLog; std::vector implementations; Aquamarine::SBackendImplementationOptions waylandOptions; waylandOptions.backendType = Aquamarine::eBackendType::AQ_BACKEND_WAYLAND; waylandOptions.backendRequestMode = Aquamarine::eBackendRequestMode::AQ_BACKEND_REQUEST_IF_AVAILABLE; implementations.emplace_back(waylandOptions); auto aqBackend = Aquamarine::CBackend::create(implementations, options); newOutputListener = aqBackend->events.newOutput.listen([](const SP newOutput) { output = newOutput; std::cout << "[Client] Got a new output named " << output->name << "\n"; outputFrameListener = output->events.frame.listen([] { onFrame(); }); outputStateListener = output->events.state.listen([](const Aquamarine::IOutput::SStateEvent& event) { onState(event); }); }); newMouseListener = aqBackend->events.newPointer.listen([](const SP& pointer) { mouseMotionListener = pointer->events.warp.listen( [](const Aquamarine::IPointer::SWarpEvent& event) { std::cout << "[Client] Mouse warped to " << std::format("{}", event.absolute) << "\n"; }); }); newKeyboardListener = aqBackend->events.newKeyboard.listen([](const SP& keyboard) { keyboardKeyListener = keyboard->events.key.listen( [](const Aquamarine::IKeyboard::SKeyEvent& event) { std::cout << "[Client] Key " << std::format("{}", event.key) << " state: " << event.pressed << " \n"; }); }); if (!aqBackend || !aqBackend->start()) { std::cout << "Failed to start the aq backend\n"; return 1; } // FIXME: write an event loop. // aqBackend->enterLoop(); return 0; } hyprwm-aquamarine-655e067/tests/shared.hpp000066400000000000000000000027141506775317200206060ustar00rootroot00000000000000#pragma once #include namespace Colors { constexpr const char* RED = "\x1b[31m"; constexpr const char* GREEN = "\x1b[32m"; constexpr const char* YELLOW = "\x1b[33m"; constexpr const char* BLUE = "\x1b[34m"; constexpr const char* MAGENTA = "\x1b[35m"; constexpr const char* CYAN = "\x1b[36m"; constexpr const char* RESET = "\x1b[0m"; }; #define EXPECT(expr, val) \ if (const auto RESULT = expr; RESULT != (val)) { \ std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected " << val << " but got " << RESULT << "\n"; \ ret = 1; \ } else { \ std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got " << val << "\n"; \ }