diff options
29 files changed, 780 insertions, 268 deletions
diff --git a/.travis-build-docker.sh b/.travis-build-docker.sh new file mode 100644 index 000000000..ca6fae42b --- /dev/null +++ b/.travis-build-docker.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +set -e +set -x + +cd /citra + +apt-get update +apt-get install -y build-essential libsdl2-dev qtbase5-dev libqt5opengl5-dev libcurl4-openssl-dev libssl-dev wget git + +# Get a recent version of CMake +wget https://cmake.org/files/v3.9/cmake-3.9.0-Linux-x86_64.sh +echo y | sh cmake-3.9.0-Linux-x86_64.sh --prefix=cmake +export PATH=/citra/cmake/cmake-3.9.0-Linux-x86_64/bin:$PATH + +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +make -j4 + +ctest -VV -C Release diff --git a/.travis-build.sh b/.travis-build.sh index df6e236b6..64f5aed94 100755 --- a/.travis-build.sh +++ b/.travis-build.sh @@ -44,15 +44,7 @@ fi  #if OS is linux or is not set  if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then -    export CC=gcc-6 -    export CXX=g++-6 -    export PKG_CONFIG_PATH=$HOME/.local/lib/pkgconfig:$PKG_CONFIG_PATH - -    mkdir build && cd build -    cmake .. -    make -j4 - -    ctest -VV -C Release +    docker run -v $(pwd):/citra ubuntu:16.04 /bin/bash /citra/.travis-build-docker.sh  elif [ "$TRAVIS_OS_NAME" = "osx" ]; then      set -o pipefail diff --git a/.travis-deps.sh b/.travis-deps.sh index 25a287c7f..0cee68041 100755 --- a/.travis-deps.sh +++ b/.travis-deps.sh @@ -5,35 +5,7 @@ set -x  #if OS is linux or is not set  if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then -    export CC=gcc-6 -    export CXX=g++-6 -    mkdir -p $HOME/.local - -    if [ ! -e $HOME/.local/bin/cmake ]; then -        echo "CMake not found in the cache, get and extract it..." -        curl -L http://www.cmake.org/files/v3.6/cmake-3.6.3-Linux-x86_64.tar.gz \ -            | tar -xz -C $HOME/.local --strip-components=1 -    else -        echo "Using cached CMake" -    fi - -    if [ ! -e $HOME/.local/lib/libSDL2.la ]; then -        echo "SDL2 not found in cache, get and build it..." -        wget http://libsdl.org/release/SDL2-2.0.5.tar.gz -O - | tar xz -        cd SDL2-2.0.5 -        ./configure --prefix=$HOME/.local -        make -j4 && make install -    else -        echo "Using cached SDL2" -    fi - -    export DEBIAN_FRONTEND=noninteractive -    # Amazing placebo security -    curl http://apt.llvm.org/llvm-snapshot.gpg.key | sudo -E apt-key add - -    sudo -E add-apt-repository "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.9 main" -    sudo -E apt-get -yq update -    sudo -E apt-get -yq install clang-format-3.9 - +    docker pull ubuntu:16.04  elif [ "$TRAVIS_OS_NAME" = "osx" ]; then      brew update      brew install qt5 sdl2 dylibbundler p7zip diff --git a/.travis-upload.sh b/.travis-upload.sh index 8cfab31cb..8c1fa21c5 100755 --- a/.travis-upload.sh +++ b/.travis-upload.sh @@ -1,134 +1,139 @@ -if [ "$TRAVIS_EVENT_TYPE" = "push" ]&&[ "$TRAVIS_BRANCH" = "master" ]; then -    GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`" -    GITREV="`git show -s --format='%h'`" -    mkdir -p artifacts - -    if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then -        REV_NAME="citra-linux-${GITDATE}-${GITREV}" -        ARCHIVE_NAME="${REV_NAME}.tar.xz" -        COMPRESSION_FLAGS="-cJvf" -        mkdir "$REV_NAME" - -        cp build/src/citra/citra "$REV_NAME" -        cp build/src/citra_qt/citra-qt "$REV_NAME" -    elif [ "$TRAVIS_OS_NAME" = "osx" ]; then -        REV_NAME="citra-osx-${GITDATE}-${GITREV}" -        ARCHIVE_NAME="${REV_NAME}.tar.gz" -        COMPRESSION_FLAGS="-czvf" -        mkdir "$REV_NAME" - -        cp build/src/citra/Release/citra "$REV_NAME" -        cp -r build/src/citra_qt/Release/citra-qt.app "$REV_NAME" - -        # move qt libs into app bundle for deployment -        $(brew --prefix)/opt/qt5/bin/macdeployqt "${REV_NAME}/citra-qt.app" - -        # move SDL2 libs into folder for deployment -        dylibbundler -b -x "${REV_NAME}/citra" -cd -d "${REV_NAME}/libs" -p "@executable_path/libs/" - -        # Make the changes to make the citra-qt app standalone (i.e. not dependent on the current brew installation). -        # To do this, the absolute references to each and every QT framework must be re-written to point to the local frameworks -        # (in the Contents/Frameworks folder). -        # The "install_name_tool" is used to do so. - -        # Coreutils is a hack to coerce Homebrew to point to the absolute Cellar path (symlink dereferenced). i.e: -        # ls -l /usr/local/opt/qt5:: /usr/local/opt/qt5 -> ../Cellar/qt5/5.6.1-1 -        # grealpath ../Cellar/qt5/5.6.1-1:: /usr/local/Cellar/qt5/5.6.1-1 -        brew install coreutils - -        REV_NAME_ALT=$REV_NAME/ -        # grealpath is located in coreutils, there is no "realpath" for OS X :( -        QT_BREWS_PATH=$(grealpath "$(brew --prefix qt5)") -        BREW_PATH=$(brew --prefix) -        QT_VERSION_NUM=5 - -        $BREW_PATH/opt/qt5/bin/macdeployqt "${REV_NAME_ALT}citra-qt.app" \ -            -executable="${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt" - -        # These are the files that macdeployqt packed into Contents/Frameworks/ - we don't want those, so we replace them. -        declare -a macos_libs=("QtCore" "QtWidgets" "QtGui" "QtOpenGL" "QtPrintSupport") - -        for macos_lib in "${macos_libs[@]}" +GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`" +GITREV="`git show -s --format='%h'`" +mkdir -p artifacts + +if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then +    REV_NAME="citra-linux-${GITDATE}-${GITREV}" +    ARCHIVE_NAME="${REV_NAME}.tar.xz" +    COMPRESSION_FLAGS="-cJvf" +    mkdir "$REV_NAME" + +    cp build/src/citra/citra "$REV_NAME" +    cp build/src/citra_qt/citra-qt "$REV_NAME" +elif [ "$TRAVIS_OS_NAME" = "osx" ]; then +    REV_NAME="citra-osx-${GITDATE}-${GITREV}" +    ARCHIVE_NAME="${REV_NAME}.tar.gz" +    COMPRESSION_FLAGS="-czvf" +    mkdir "$REV_NAME" + +    cp build/src/citra/Release/citra "$REV_NAME" +    cp -r build/src/citra_qt/Release/citra-qt.app "$REV_NAME" + +    # move qt libs into app bundle for deployment +    $(brew --prefix)/opt/qt5/bin/macdeployqt "${REV_NAME}/citra-qt.app" + +    # move SDL2 libs into folder for deployment +    dylibbundler -b -x "${REV_NAME}/citra" -cd -d "${REV_NAME}/libs" -p "@executable_path/libs/" + +    # Make the changes to make the citra-qt app standalone (i.e. not dependent on the current brew installation). +    # To do this, the absolute references to each and every QT framework must be re-written to point to the local frameworks +    # (in the Contents/Frameworks folder). +    # The "install_name_tool" is used to do so. + +    # Coreutils is a hack to coerce Homebrew to point to the absolute Cellar path (symlink dereferenced). i.e: +    # ls -l /usr/local/opt/qt5:: /usr/local/opt/qt5 -> ../Cellar/qt5/5.6.1-1 +    # grealpath ../Cellar/qt5/5.6.1-1:: /usr/local/Cellar/qt5/5.6.1-1 +    brew install coreutils + +    REV_NAME_ALT=$REV_NAME/ +    # grealpath is located in coreutils, there is no "realpath" for OS X :( +    QT_BREWS_PATH=$(grealpath "$(brew --prefix qt5)") +    BREW_PATH=$(brew --prefix) +    QT_VERSION_NUM=5 + +    $BREW_PATH/opt/qt5/bin/macdeployqt "${REV_NAME_ALT}citra-qt.app" \ +        -executable="${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt" + +    # These are the files that macdeployqt packed into Contents/Frameworks/ - we don't want those, so we replace them. +    declare -a macos_libs=("QtCore" "QtWidgets" "QtGui" "QtOpenGL" "QtPrintSupport") + +    for macos_lib in "${macos_libs[@]}" +    do +        SC_FRAMEWORK_PART=$macos_lib.framework/Versions/$QT_VERSION_NUM/$macos_lib +        # Replace macdeployqt versions of the Frameworks with our own (from /usr/local/opt/qt5/lib/) +        cp "$BREW_PATH/opt/qt5/lib/$SC_FRAMEWORK_PART" "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART" + +        # Replace references within the embedded Framework files with "internal" versions. +        for macos_lib2 in "${macos_libs[@]}"          do -            SC_FRAMEWORK_PART=$macos_lib.framework/Versions/$QT_VERSION_NUM/$macos_lib -            # Replace macdeployqt versions of the Frameworks with our own (from /usr/local/opt/qt5/lib/) -            cp "$BREW_PATH/opt/qt5/lib/$SC_FRAMEWORK_PART" "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART" - -            # Replace references within the embedded Framework files with "internal" versions. -            for macos_lib2 in "${macos_libs[@]}" -            do -                # Since brew references both the non-symlinked and symlink paths of QT5, it needs to be duplicated. -                # /usr/local/Cellar/qt5/5.6.1-1/lib and /usr/local/opt/qt5/lib both resolve to the same files. -                # So the two lines below are effectively duplicates when resolved as a path, but as strings, they aren't. -                RM_FRAMEWORK_PART=$macos_lib2.framework/Versions/$QT_VERSION_NUM/$macos_lib2 -                install_name_tool -change \ -                    $QT_BREWS_PATH/lib/$RM_FRAMEWORK_PART \ -                    @executable_path/../Frameworks/$RM_FRAMEWORK_PART \ -                    "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART" -                install_name_tool -change \ -                    "$BREW_PATH/opt/qt5/lib/$RM_FRAMEWORK_PART" \ -                    @executable_path/../Frameworks/$RM_FRAMEWORK_PART \ -                    "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART" -            done +            # Since brew references both the non-symlinked and symlink paths of QT5, it needs to be duplicated. +            # /usr/local/Cellar/qt5/5.6.1-1/lib and /usr/local/opt/qt5/lib both resolve to the same files. +            # So the two lines below are effectively duplicates when resolved as a path, but as strings, they aren't. +            RM_FRAMEWORK_PART=$macos_lib2.framework/Versions/$QT_VERSION_NUM/$macos_lib2 +            install_name_tool -change \ +                $QT_BREWS_PATH/lib/$RM_FRAMEWORK_PART \ +                @executable_path/../Frameworks/$RM_FRAMEWORK_PART \ +                "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART" +            install_name_tool -change \ +                "$BREW_PATH/opt/qt5/lib/$RM_FRAMEWORK_PART" \ +                @executable_path/../Frameworks/$RM_FRAMEWORK_PART \ +                "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART"          done - -        # Handles `This application failed to start because it could not find or load the Qt platform plugin "cocoa"` -        # Which manifests itself as: -        # "Exception Type: EXC_CRASH (SIGABRT) | Exception Codes: 0x0000000000000000, 0x0000000000000000 | Exception Note: EXC_CORPSE_NOTIFY" -        # There may be more dylibs needed to be fixed... -        declare -a macos_plugins=("Plugins/platforms/libqcocoa.dylib") - -        for macos_lib in "${macos_plugins[@]}" +    done + +    # Handles `This application failed to start because it could not find or load the Qt platform plugin "cocoa"` +    # Which manifests itself as: +    # "Exception Type: EXC_CRASH (SIGABRT) | Exception Codes: 0x0000000000000000, 0x0000000000000000 | Exception Note: EXC_CORPSE_NOTIFY" +    # There may be more dylibs needed to be fixed... +    declare -a macos_plugins=("Plugins/platforms/libqcocoa.dylib") + +    for macos_lib in "${macos_plugins[@]}" +    do +        install_name_tool -id @executable_path/../$macos_lib "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib" +        for macos_lib2 in "${macos_libs[@]}"          do -            install_name_tool -id @executable_path/../$macos_lib "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib" -            for macos_lib2 in "${macos_libs[@]}" -            do -                RM_FRAMEWORK_PART=$macos_lib2.framework/Versions/$QT_VERSION_NUM/$macos_lib2 -                install_name_tool -change \ -                    $QT_BREWS_PATH/lib/$RM_FRAMEWORK_PART \ -                    @executable_path/../Frameworks/$RM_FRAMEWORK_PART \ -                    "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib" -                install_name_tool -change \ -                    "$BREW_PATH/opt/qt5/lib/$RM_FRAMEWORK_PART" \ -                    @executable_path/../Frameworks/$RM_FRAMEWORK_PART \ -                    "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib" -            done +            RM_FRAMEWORK_PART=$macos_lib2.framework/Versions/$QT_VERSION_NUM/$macos_lib2 +            install_name_tool -change \ +                $QT_BREWS_PATH/lib/$RM_FRAMEWORK_PART \ +                @executable_path/../Frameworks/$RM_FRAMEWORK_PART \ +                "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib" +            install_name_tool -change \ +                "$BREW_PATH/opt/qt5/lib/$RM_FRAMEWORK_PART" \ +                @executable_path/../Frameworks/$RM_FRAMEWORK_PART \ +                "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib"          done +    done -        for macos_lib in "${macos_libs[@]}" -        do -            # Debugging info for Travis-CI -            otool -L "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$macos_lib.framework/Versions/$QT_VERSION_NUM/$macos_lib" -        done +    for macos_lib in "${macos_libs[@]}" +    do +        # Debugging info for Travis-CI +        otool -L "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$macos_lib.framework/Versions/$QT_VERSION_NUM/$macos_lib" +    done -        # Make the citra-qt.app application launch a debugging terminal. -        # Store away the actual binary -        mv ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt-bin +    # Make the citra-qt.app application launch a debugging terminal. +    # Store away the actual binary +    mv ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt-bin -        cat > ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt <<EOL +    cat > ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt <<EOL  #!/usr/bin/env bash  cd "\`dirname "\$0"\`"  chmod +x citra-qt-bin  open citra-qt-bin --args "\$@"  EOL -        # Content that will serve as the launching script for citra (within the .app folder) +    # Content that will serve as the launching script for citra (within the .app folder) -        # Make the launching script executable -        chmod +x ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt +    # Make the launching script executable +    chmod +x ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt -    fi +fi + +# Copy documentation +cp license.txt "$REV_NAME" +cp README.md "$REV_NAME" -    # Copy documentation -    cp license.txt "$REV_NAME" -    cp README.md "$REV_NAME" +tar $COMPRESSION_FLAGS "$ARCHIVE_NAME" "$REV_NAME" -    tar $COMPRESSION_FLAGS "$ARCHIVE_NAME" "$REV_NAME" +# Find out what release we are building +if [ -z $TRAVIS_TAG ]; then +    RELEASE_NAME=head +else +    RELEASE_NAME=$(echo $TRAVIS_TAG | cut -d- -f1) +fi -    mv "$REV_NAME" nightly +mv "$REV_NAME" $RELEASE_NAME -    7z a "$REV_NAME.7z" nightly +7z a "$REV_NAME.7z" $RELEASE_NAME -    # move the compiled archive into the artifacts directory to be uploaded by travis releases -    mv "$ARCHIVE_NAME" artifacts/ -    mv "$REV_NAME.7z" artifacts/ -fi +# move the compiled archive into the artifacts directory to be uploaded by travis releases +mv "$ARCHIVE_NAME" artifacts/ +mv "$REV_NAME.7z" artifacts/ diff --git a/.travis.yml b/.travis.yml index 846758881..b92d7f236 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,23 +8,15 @@ matrix:        sudo: false        osx_image: xcode7.3 +services: +  - docker +  addons:    apt: -    sources: -      - ubuntu-toolchain-r-test      packages: -      - gcc-6 -      - g++-6 -      - qt5-default -      - libqt5opengl5-dev -      - xorg-dev -      - lib32stdc++6 # For CMake +      - clang-format-3.9        - p7zip-full -cache: -  directories: -    - "$HOME/.local" -  install: "./.travis-deps.sh"  script: "./.travis-build.sh"  after_success: "./.travis-upload.sh" @@ -37,4 +29,4 @@ deploy:    file: "artifacts/*"    skip_cleanup: true    on: -    repo: citra-emu/citra-nightly +    tags: true diff --git a/appveyor.yml b/appveyor.yml index d062a1f3e..94e9969f5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,9 +1,6 @@  # shallow clone  clone_depth: 10 -# don't build on tag -skip_tags: true -  cache:    - C:\ProgramData\chocolatey\bin -> appveyor.yml    - C:\ProgramData\chocolatey\lib -> appveyor.yml @@ -49,13 +46,21 @@ after_build:          7z a -tzip $MSVC_BUILD_PDB .\build\bin\release\*.pdb          rm .\build\bin\release\*.pdb -        mkdir nightly -        Copy-Item .\build\bin\release\* -Destination nightly -Recurse -        Copy-Item .\license.txt -Destination nightly -        Copy-Item .\README.md -Destination nightly +        # Find out which kind of release we are producing by tag name +        if ($env:APPVEYOR_REPO_TAG_NAME) { +            $RELEASE_DIST, $RELEASE_VERSION = $env:APPVEYOR_REPO_TAG_NAME.split('-') +        } else { +            # There is no repo tag - make assumptions +            $RELEASE_DIST = "head" +        } + +        mkdir $RELEASE_DIST +        Copy-Item .\build\bin\release\* -Destination $RELEASE_DIST -Recurse +        Copy-Item .\license.txt -Destination $RELEASE_DIST +        Copy-Item .\README.md -Destination $RELEASE_DIST -        7z a -tzip $MSVC_BUILD_NAME nightly\* -        7z a $MSVC_SEVENZIP nightly +        7z a -tzip $MSVC_BUILD_NAME $RELEASE_DIST\* +        7z a $MSVC_SEVENZIP $RELEASE_DIST  test_script:    - cd build && ctest -VV -C Release && cd .. @@ -72,16 +77,11 @@ artifacts:  deploy:    provider: GitHub -  release: nightly-$(appveyor_build_number) -  description: | -    Citra nightly releases. Please choose the correct download for your operating system from the list below. - -    Short Commit Hash $(GITREV) +  release: $(appveyor_repo_tag_name)    auth_token:      secure: "dbpsMC/MgPKWFNJCXpQl4cR8FYhepkPLjgNp/pRMktZ8oLKTqPYErfreaIxb/4P1"    artifact: msvcupdate,msvcbuild    draft: false    prerelease: false    on: -    branch: master -    appveyor_repo_name: citra-emu/citra-nightly +    appveyor_repo_tag: true diff --git a/externals/cryptopp/cryptopp b/externals/cryptopp/cryptopp -Subproject 841c37e34765487a2968357369ab74db8b10a62 +Subproject 24bc2b85674254fb294e717eb5b47d9f53e786b diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index a12498e0f..b0a0ebd3b 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -12,7 +12,7 @@ const char* sdl2_config_file = R"(  # It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."  # Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values -# for button input, the following devices are avaible: +# for button input, the following devices are available:  #  - "keyboard" (default) for keyboard input. Required parameters:  #      - "code": the code of the key to bind  #  - "sdl" for joystick input using SDL. Required parameters: @@ -21,7 +21,7 @@ const char* sdl2_config_file = R"(  #      - "hat"(optional): the index of the hat to bind as direction buttons  #      - "axis"(optional): the index of the axis to bind  #      - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right" -#      - "threshould"(only used for axis): a float value in (-1.0, 1.0) which the button is +#      - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is  #          triggered if the axis value crosses  #      - "direction"(only used for axis): "+" means the button is triggered when the axis value  #          is greater than the threshold; "-" means the button is triggered when the axis value @@ -42,7 +42,7 @@ button_zl=  button_zr=  button_home= -# for analog input, the following devices are avaible: +# for analog input, the following devices are available:  #  - "analog_from_button" (default) for emulating analog input from direction buttons.  Required parameters:  #      - "up", "down", "left", "right": sub-devices for each direction.  #          Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00" diff --git a/src/common/quaternion.h b/src/common/quaternion.h index 84ac82ed3..77f626bcb 100644 --- a/src/common/quaternion.h +++ b/src/common/quaternion.h @@ -30,6 +30,11 @@ public:          return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz),                  w * other.w - Dot(xyz, other.xyz)};      } + +    Quaternion<T> Normalized() const { +        T length = std::sqrt(xyz.Length2() + w * w); +        return {xyz / length, w / length}; +    }  };  template <typename T> diff --git a/src/common/vector_math.h b/src/common/vector_math.h index c7a461a1e..6e2a5ad60 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h @@ -31,7 +31,6 @@  #pragma once  #include <cmath> -#include <type_traits>  namespace Math { @@ -90,7 +89,7 @@ public:          x -= other.x;          y -= other.y;      } -    template <typename Q = T, class = typename std::enable_if<std::is_signed<Q>::value>::type> +      Vec2<decltype(-T{})> operator-() const {          return MakeVec(-x, -y);      } @@ -247,7 +246,7 @@ public:          y -= other.y;          z -= other.z;      } -    template <typename Q = T, class = typename std::enable_if<std::is_signed<Q>::value>::type> +      Vec3<decltype(-T{})> operator-() const {          return MakeVec(-x, -y, -z);      } @@ -462,7 +461,7 @@ public:          z -= other.z;          w -= other.w;      } -    template <typename Q = T, class = typename std::enable_if<std::is_signed<Q>::value>::type> +      Vec4<decltype(-T{})> operator-() const {          return MakeVec(-x, -y, -z, -w);      } diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 0109fa2b2..58d94768c 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -34,8 +34,6 @@ static bool shared_font_loaded = false;  static bool shared_font_relocated = false;  static Kernel::SharedPtr<Kernel::Mutex> lock; -static Kernel::SharedPtr<Kernel::Event> notification_event; ///< APT notification event -static Kernel::SharedPtr<Kernel::Event> parameter_event;    ///< APT parameter event  static u32 cpu_percent; ///< CPU time available to the running application @@ -44,32 +42,160 @@ static u8 unknown_ns_state_field;  static ScreencapPostPermission screen_capture_post_permission; -/// Parameter data to be returned in the next call to Glance/ReceiveParameter +/// Parameter data to be returned in the next call to Glance/ReceiveParameter. +/// TODO(Subv): Use std::optional once we migrate to C++17.  static boost::optional<MessageParameter> next_parameter; +enum class AppletPos { Application = 0, Library = 1, System = 2, SysLibrary = 3, Resident = 4 }; + +static constexpr size_t NumAppletSlot = 4; + +enum class AppletSlot : u8 { +    Application, +    SystemApplet, +    HomeMenu, +    LibraryApplet, + +    // An invalid tag +    Error, +}; + +union AppletAttributes { +    u32 raw; + +    BitField<0, 3, u32> applet_pos; + +    AppletAttributes() : raw(0) {} +    AppletAttributes(u32 attributes) : raw(attributes) {} +}; + +struct AppletSlotData { +    AppletId applet_id; +    AppletSlot slot; +    bool registered; +    AppletAttributes attributes; +    Kernel::SharedPtr<Kernel::Event> notification_event; +    Kernel::SharedPtr<Kernel::Event> parameter_event; +}; + +// Holds data about the concurrently running applets in the system. +static std::array<AppletSlotData, NumAppletSlot> applet_slots = {}; + +// This overload returns nullptr if no applet with the specified id has been started. +static AppletSlotData* GetAppletSlotData(AppletId id) { +    auto GetSlot = [](AppletSlot slot) -> AppletSlotData* { +        return &applet_slots[static_cast<size_t>(slot)]; +    }; + +    if (id == AppletId::Application) { +        auto* slot = GetSlot(AppletSlot::Application); +        if (slot->applet_id != AppletId::None) +            return slot; + +        return nullptr; +    } + +    if (id == AppletId::AnySystemApplet) { +        auto* system_slot = GetSlot(AppletSlot::SystemApplet); +        if (system_slot->applet_id != AppletId::None) +            return system_slot; + +        // The Home Menu is also a system applet, but it lives in its own slot to be able to run +        // concurrently with other system applets. +        auto* home_slot = GetSlot(AppletSlot::HomeMenu); +        if (home_slot->applet_id != AppletId::None) +            return home_slot; + +        return nullptr; +    } + +    if (id == AppletId::AnyLibraryApplet || id == AppletId::AnySysLibraryApplet) { +        auto* slot = GetSlot(AppletSlot::LibraryApplet); +        if (slot->applet_id == AppletId::None) +            return nullptr; + +        u32 applet_pos = slot->attributes.applet_pos; + +        if (id == AppletId::AnyLibraryApplet && applet_pos == static_cast<u32>(AppletPos::Library)) +            return slot; + +        if (id == AppletId::AnySysLibraryApplet && +            applet_pos == static_cast<u32>(AppletPos::SysLibrary)) +            return slot; + +        return nullptr; +    } + +    if (id == AppletId::HomeMenu || id == AppletId::AlternateMenu) { +        auto* slot = GetSlot(AppletSlot::HomeMenu); +        if (slot->applet_id != AppletId::None) +            return slot; + +        return nullptr; +    } + +    for (auto& slot : applet_slots) { +        if (slot.applet_id == id) +            return &slot; +    } + +    return nullptr; +} + +static AppletSlotData* GetAppletSlotData(AppletAttributes attributes) { +    // Mapping from AppletPos to AppletSlot +    static constexpr std::array<AppletSlot, 6> applet_position_slots = { +        AppletSlot::Application,   AppletSlot::LibraryApplet, AppletSlot::SystemApplet, +        AppletSlot::LibraryApplet, AppletSlot::Error,         AppletSlot::LibraryApplet}; + +    u32 applet_pos = attributes.applet_pos; +    if (applet_pos >= applet_position_slots.size()) +        return nullptr; + +    AppletSlot slot = applet_position_slots[applet_pos]; + +    if (slot == AppletSlot::Error) +        return nullptr; + +    return &applet_slots[static_cast<size_t>(slot)]; +} +  void SendParameter(const MessageParameter& parameter) {      next_parameter = parameter; -    // Signal the event to let the application know that a new parameter is ready to be read -    parameter_event->Signal(); +    // Signal the event to let the receiver know that a new parameter is ready to be read +    auto* const slot_data = GetAppletSlotData(static_cast<AppletId>(parameter.destination_id)); +    ASSERT(slot_data); + +    slot_data->parameter_event->Signal();  }  void Initialize(Service::Interface* self) {      IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2, 2, 0); // 0x20080      u32 app_id = rp.Pop<u32>(); -    u32 flags = rp.Pop<u32>(); -    IPC::RequestBuilder rb = rp.MakeBuilder(1, 3); -    rb.Push(RESULT_SUCCESS); -    rb.PushCopyHandles(Kernel::g_handle_table.Create(notification_event).Unwrap(), -                       Kernel::g_handle_table.Create(parameter_event).Unwrap()); +    u32 attributes = rp.Pop<u32>(); -    // TODO(bunnei): Check if these events are cleared every time Initialize is called. -    notification_event->Clear(); -    parameter_event->Clear(); +    LOG_DEBUG(Service_APT, "called app_id=0x%08X, attributes=0x%08X", app_id, attributes); + +    auto* const slot_data = GetAppletSlotData(attributes); + +    // Note: The real NS service does not check if the attributes value is valid before accessing +    // the data in the array +    ASSERT_MSG(slot_data, "Invalid application attributes"); + +    if (slot_data->registered) { +        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); +        rb.Push(ResultCode(ErrorDescription::AlreadyExists, ErrorModule::Applet, +                           ErrorSummary::InvalidState, ErrorLevel::Status)); +        return; +    } -    ASSERT_MSG((nullptr != lock), "Cannot initialize without lock"); -    lock->Release(); +    slot_data->applet_id = static_cast<AppletId>(app_id); +    slot_data->attributes.raw = attributes; -    LOG_DEBUG(Service_APT, "called app_id=0x%08X, flags=0x%08X", app_id, flags); +    IPC::RequestBuilder rb = rp.MakeBuilder(1, 3); +    rb.Push(RESULT_SUCCESS); +    rb.PushCopyHandles(Kernel::g_handle_table.Create(slot_data->notification_event).Unwrap(), +                       Kernel::g_handle_table.Create(slot_data->parameter_event).Unwrap());  }  void GetSharedFont(Service::Interface* self) { @@ -120,7 +246,12 @@ void GetLockHandle(Service::Interface* self) {      // this will cause the app to wait until parameter_event is signaled.      u32 applet_attributes = rp.Pop<u32>();      IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); -    rb.Push(RESULT_SUCCESS);    // No error +    rb.Push(RESULT_SUCCESS); // No error + +    // TODO(Subv): The output attributes should have an AppletPos of either Library or System | +    // Library (depending on the type of the last launched applet) if the input attributes' +    // AppletPos has the Library bit set. +      rb.Push(applet_attributes); // Applet Attributes, this value is passed to Enable.      rb.Push<u32>(0);            // Least significant bit = power button state      Kernel::Handle handle_copy = Kernel::g_handle_table.Create(lock).Unwrap(); @@ -133,10 +264,22 @@ void GetLockHandle(Service::Interface* self) {  void Enable(Service::Interface* self) {      IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x3, 1, 0); // 0x30040      u32 attributes = rp.Pop<u32>(); + +    LOG_DEBUG(Service_APT, "called attributes=0x%08X", attributes); +      IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); -    rb.Push(RESULT_SUCCESS);   // No error -    parameter_event->Signal(); // Let the application know that it has been started -    LOG_WARNING(Service_APT, "(STUBBED) called attributes=0x%08X", attributes); + +    auto* const slot_data = GetAppletSlotData(attributes); + +    if (!slot_data) { +        rb.Push(ResultCode(ErrCodes::InvalidAppletSlot, ErrorModule::Applet, +                           ErrorSummary::InvalidState, ErrorLevel::Status)); +        return; +    } + +    slot_data->registered = true; + +    rb.Push(RESULT_SUCCESS);  }  void GetAppletManInfo(Service::Interface* self) { @@ -154,22 +297,27 @@ void GetAppletManInfo(Service::Interface* self) {  void IsRegistered(Service::Interface* self) {      IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x9, 1, 0); // 0x90040 -    u32 app_id = rp.Pop<u32>(); +    AppletId app_id = static_cast<AppletId>(rp.Pop<u32>());      IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);      rb.Push(RESULT_SUCCESS); // No error -    // TODO(Subv): An application is considered "registered" if it has already called APT::Enable -    // handle this properly once we implement multiprocess support. -    bool is_registered = false; // Set to not registered by default +    auto* const slot_data = GetAppletSlotData(app_id); + +    // Check if an LLE applet was registered first, then fallback to HLE applets +    bool is_registered = slot_data && slot_data->registered; -    if (app_id == static_cast<u32>(AppletId::AnyLibraryApplet)) { -        is_registered = HLE::Applets::IsLibraryAppletRunning(); -    } else if (auto applet = HLE::Applets::Applet::Get(static_cast<AppletId>(app_id))) { -        is_registered = true; // Set to registered +    if (!is_registered) { +        if (app_id == AppletId::AnyLibraryApplet) { +            is_registered = HLE::Applets::IsLibraryAppletRunning(); +        } else if (auto applet = HLE::Applets::Applet::Get(app_id)) { +            // The applet exists, set it as registered. +            is_registered = true; +        }      } +      rb.Push(is_registered); -    LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); +    LOG_DEBUG(Service_APT, "called app_id=0x%08X", static_cast<u32>(app_id));  }  void InquireNotification(Service::Interface* self) { @@ -864,14 +1012,23 @@ void Init() {      screen_capture_post_permission =          ScreencapPostPermission::CleanThePermission; // TODO(JamePeng): verify the initial value -    // TODO(bunnei): Check if these are created in Initialize or on APT process startup. -    notification_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Notification"); -    parameter_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Start"); +    for (size_t slot = 0; slot < applet_slots.size(); ++slot) { +        auto& slot_data = applet_slots[slot]; +        slot_data.slot = static_cast<AppletSlot>(slot); +        slot_data.applet_id = AppletId::None; +        slot_data.attributes.raw = 0; +        slot_data.registered = false; +        slot_data.notification_event = +            Kernel::Event::Create(Kernel::ResetType::OneShot, "APT:Notification"); +        slot_data.parameter_event = +            Kernel::Event::Create(Kernel::ResetType::OneShot, "APT:Parameter"); +    }      // Initialize the parameter to wake up the application.      next_parameter.emplace();      next_parameter->signal = static_cast<u32>(SignalType::Wakeup);      next_parameter->destination_id = static_cast<u32>(AppletId::Application); +    applet_slots[static_cast<size_t>(AppletSlot::Application)].parameter_event->Signal();  }  void Shutdown() { @@ -879,8 +1036,12 @@ void Shutdown() {      shared_font_loaded = false;      shared_font_relocated = false;      lock = nullptr; -    notification_event = nullptr; -    parameter_event = nullptr; + +    for (auto& slot : applet_slots) { +        slot.registered = false; +        slot.notification_event = nullptr; +        slot.parameter_event = nullptr; +    }      next_parameter = boost::none; diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index 106754853..96b28b438 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -72,6 +72,8 @@ enum class SignalType : u32 {  /// App Id's used by APT functions  enum class AppletId : u32 { +    None = 0, +    AnySystemApplet = 0x100,      HomeMenu = 0x101,      AlternateMenu = 0x103,      Camera = 0x110, @@ -83,6 +85,7 @@ enum class AppletId : u32 {      Miiverse = 0x117,      MiiversePost = 0x118,      AmiiboSettings = 0x119, +    AnySysLibraryApplet = 0x200,      SoftwareKeyboard1 = 0x201,      Ed1 = 0x202,      PnoteApp = 0x204, @@ -119,8 +122,9 @@ enum class ScreencapPostPermission : u32 {  namespace ErrCodes {  enum {      ParameterPresent = 2, +    InvalidAppletSlot = 4,  }; -} +} // namespace ErrCodes  /// Send a parameter to the currently-running application, which will read it via ReceiveParameter  void SendParameter(const MessageParameter& parameter); diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 6624f1711..3dbeb27cc 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -681,7 +681,7 @@ void GenerateConsoleUniqueId(u32& random_number, u64& console_id) {      CryptoPP::AutoSeededRandomPool rng;      random_number = rng.GenerateWord32(0, 0xFFFF);      u64_le local_friend_code_seed; -    rng.GenerateBlock(reinterpret_cast<byte*>(&local_friend_code_seed), +    rng.GenerateBlock(reinterpret_cast<CryptoPP::byte*>(&local_friend_code_seed),                        sizeof(local_friend_code_seed));      console_id = (local_friend_code_seed & 0x3FFFFFFFF) | (static_cast<u64>(random_number) << 48);  } diff --git a/src/core/hle/service/dlp/dlp_clnt.cpp b/src/core/hle/service/dlp/dlp_clnt.cpp index 56f934b3f..6f2bf2061 100644 --- a/src/core/hle/service/dlp/dlp_clnt.cpp +++ b/src/core/hle/service/dlp/dlp_clnt.cpp @@ -8,7 +8,26 @@ namespace Service {  namespace DLP {  const Interface::FunctionInfo FunctionTable[] = { -    {0x000100C3, nullptr, "Initialize"}, {0x00110000, nullptr, "GetWirelessRebootPassphrase"}, +    {0x000100C3, nullptr, "Initialize"}, +    {0x00020000, nullptr, "Finalize"}, +    {0x00030000, nullptr, "GetEventDesc"}, +    {0x00040000, nullptr, "GetChannel"}, +    {0x00050180, nullptr, "StartScan"}, +    {0x00060000, nullptr, "StopScan"}, +    {0x00070080, nullptr, "GetServerInfo"}, +    {0x00080100, nullptr, "GetTitleInfo"}, +    {0x00090040, nullptr, "GetTitleInfoInOrder"}, +    {0x000A0080, nullptr, "DeleteScanInfo"}, +    {0x000B0100, nullptr, "PrepareForSystemDownload"}, +    {0x000C0000, nullptr, "StartSystemDownload"}, +    {0x000D0100, nullptr, "StartTitleDownload"}, +    {0x000E0000, nullptr, "GetMyStatus"}, +    {0x000F0040, nullptr, "GetConnectingNodes"}, +    {0x00100040, nullptr, "GetNodeInfo"}, +    {0x00110000, nullptr, "GetWirelessRebootPassphrase"}, +    {0x00120000, nullptr, "StopSession"}, +    {0x00130100, nullptr, "GetCupVersion"}, +    {0x00140100, nullptr, "GetDupAvailability"},  };  DLP_CLNT_Interface::DLP_CLNT_Interface() { diff --git a/src/core/hle/service/dlp/dlp_fkcl.cpp b/src/core/hle/service/dlp/dlp_fkcl.cpp index 29b9d52e0..fe6be7d32 100644 --- a/src/core/hle/service/dlp/dlp_fkcl.cpp +++ b/src/core/hle/service/dlp/dlp_fkcl.cpp @@ -8,7 +8,23 @@ namespace Service {  namespace DLP {  const Interface::FunctionInfo FunctionTable[] = { -    {0x00010083, nullptr, "Initialize"}, {0x000F0000, nullptr, "GetWirelessRebootPassphrase"}, +    {0x00010083, nullptr, "Initialize"}, +    {0x00020000, nullptr, "Finalize"}, +    {0x00030000, nullptr, "GetEventDesc"}, +    {0x00040000, nullptr, "GetChannels"}, +    {0x00050180, nullptr, "StartScan"}, +    {0x00060000, nullptr, "StopScan"}, +    {0x00070080, nullptr, "GetServerInfo"}, +    {0x00080100, nullptr, "GetTitleInfo"}, +    {0x00090040, nullptr, "GetTitleInfoInOrder"}, +    {0x000A0080, nullptr, "DeleteScanInfo"}, +    {0x000B0100, nullptr, "StartFakeSession"}, +    {0x000C0000, nullptr, "GetMyStatus"}, +    {0x000D0040, nullptr, "GetConnectingNodes"}, +    {0x000E0040, nullptr, "GetNodeInfo"}, +    {0x000F0000, nullptr, "GetWirelessRebootPassphrase"}, +    {0x00100000, nullptr, "StopSession"}, +    {0x00110203, nullptr, "Initialize2"},  };  DLP_FKCL_Interface::DLP_FKCL_Interface() { diff --git a/src/core/hle/service/dlp/dlp_srvr.cpp b/src/core/hle/service/dlp/dlp_srvr.cpp index 32cfa2c44..1bcea43d3 100644 --- a/src/core/hle/service/dlp/dlp_srvr.cpp +++ b/src/core/hle/service/dlp/dlp_srvr.cpp @@ -11,7 +11,7 @@  namespace Service {  namespace DLP { -static void unk_0x000E0040(Interface* self) { +static void IsChild(Interface* self) {      u32* cmd_buff = Kernel::GetCommandBuffer();      cmd_buff[1] = RESULT_SUCCESS.raw; @@ -24,14 +24,19 @@ const Interface::FunctionInfo FunctionTable[] = {      {0x00010183, nullptr, "Initialize"},      {0x00020000, nullptr, "Finalize"},      {0x00030000, nullptr, "GetServerState"}, +    {0x00040000, nullptr, "GetEventDescription"},      {0x00050080, nullptr, "StartAccepting"}, +    {0x00060000, nullptr, "EndAccepting"},      {0x00070000, nullptr, "StartDistribution"},      {0x000800C0, nullptr, "SendWirelessRebootPassphrase"},      {0x00090040, nullptr, "AcceptClient"}, +    {0x000A0040, nullptr, "DisconnectClient"},      {0x000B0042, nullptr, "GetConnectingClients"},      {0x000C0040, nullptr, "GetClientInfo"},      {0x000D0040, nullptr, "GetClientState"}, -    {0x000E0040, unk_0x000E0040, "unk_0x000E0040"}, +    {0x000E0040, IsChild, "IsChild"}, +    {0x000F0303, nullptr, "InitializeWithName"}, +    {0x00100000, nullptr, "GetDupNoticeNeed"},  };  DLP_SRVR_Interface::DLP_SRVR_Interface() { diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 1ef972e70..ef25926b5 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -24,7 +24,7 @@ namespace HID {   */  struct PadState {      union { -        u32 hex; +        u32 hex{};          BitField<0, 1, u32> a;          BitField<1, 1, u32> b; diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp index 837413f93..0912d5756 100644 --- a/src/core/hle/service/ir/ir_rst.cpp +++ b/src/core/hle/service/ir/ir_rst.cpp @@ -18,7 +18,7 @@ namespace Service {  namespace IR {  union PadState { -    u32_le hex; +    u32_le hex{};      BitField<14, 1, u32_le> zl;      BitField<15, 1, u32_le> zr; diff --git a/src/input_common/main.h b/src/input_common/main.h index 140bbd014..e7fb3890e 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -11,7 +11,7 @@ namespace InputCommon {  /// Initializes and registers all built-in input device factories.  void Init(); -/// Unresisters all build-in input device factories and shut them down. +/// Deregisters all built-in input device factories and shuts them down.  void Shutdown();  class Keyboard; diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp index 756ee58b7..d404afa89 100644 --- a/src/input_common/sdl/sdl.cpp +++ b/src/input_common/sdl/sdl.cpp @@ -159,7 +159,7 @@ public:       *     - "axis"(optional): the index of the axis to bind       *     - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",       *         "down", "left" or "right" -     *     - "threshould"(only used for axis): a float value in (-1.0, 1.0) which the button is +     *     - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is       *         triggered if the axis value crosses       *     - "direction"(only used for axis): "+" means the button is triggered when the axis value       *         is greater than the threshold; "-" means the button is triggered when the axis value diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 0961a3251..cffa4c952 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -15,6 +15,7 @@ set(SRCS              shader/shader_interpreter.cpp              swrasterizer/clipper.cpp              swrasterizer/framebuffer.cpp +            swrasterizer/lighting.cpp              swrasterizer/proctex.cpp              swrasterizer/rasterizer.cpp              swrasterizer/swrasterizer.cpp @@ -55,6 +56,7 @@ set(HEADERS              shader/shader_interpreter.h              swrasterizer/clipper.h              swrasterizer/framebuffer.h +            swrasterizer/lighting.h              swrasterizer/proctex.h              swrasterizer/rasterizer.h              swrasterizer/swrasterizer.h diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 4633a1df1..f98ca3302 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -119,27 +119,6 @@ static void WriteUniformFloatReg(ShaderRegs& config, Shader::ShaderSetup& setup,      }  } -static void WriteProgramCode(ShaderRegs& config, Shader::ShaderSetup& setup, -                             unsigned max_program_code_length, u32 value) { -    if (config.program.offset >= max_program_code_length) { -        LOG_ERROR(HW_GPU, "Invalid %s program offset %d", GetShaderSetupTypeName(setup), -                  (int)config.program.offset); -    } else { -        setup.program_code[config.program.offset] = value; -        config.program.offset++; -    } -} - -static void WriteSwizzlePatterns(ShaderRegs& config, Shader::ShaderSetup& setup, u32 value) { -    if (config.swizzle_patterns.offset >= setup.swizzle_data.size()) { -        LOG_ERROR(HW_GPU, "Invalid %s swizzle pattern offset %d", GetShaderSetupTypeName(setup), -                  (int)config.swizzle_patterns.offset); -    } else { -        setup.swizzle_data[config.swizzle_patterns.offset] = value; -        config.swizzle_patterns.offset++; -    } -} -  static void WritePicaReg(u32 id, u32 value, u32 mask) {      auto& regs = g_state.regs; @@ -458,7 +437,13 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {      case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[5], 0x2a1):      case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[6], 0x2a2):      case PICA_REG_INDEX_WORKAROUND(gs.program.set_word[7], 0x2a3): { -        WriteProgramCode(g_state.regs.gs, g_state.gs, 4096, value); +        u32& offset = g_state.regs.gs.program.offset; +        if (offset >= 4096) { +            LOG_ERROR(HW_GPU, "Invalid GS program offset %u", offset); +        } else { +            g_state.gs.program_code[offset] = value; +            offset++; +        }          break;      } @@ -470,11 +455,18 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {      case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[5], 0x2ab):      case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[6], 0x2ac):      case PICA_REG_INDEX_WORKAROUND(gs.swizzle_patterns.set_word[7], 0x2ad): { -        WriteSwizzlePatterns(g_state.regs.gs, g_state.gs, value); +        u32& offset = g_state.regs.gs.swizzle_patterns.offset; +        if (offset >= g_state.gs.swizzle_data.size()) { +            LOG_ERROR(HW_GPU, "Invalid GS swizzle pattern offset %u", offset); +        } else { +            g_state.gs.swizzle_data[offset] = value; +            offset++; +        }          break;      }      case PICA_REG_INDEX(vs.bool_uniforms): +        // TODO (wwylele): does regs.pipeline.gs_unit_exclusive_configuration affect this?          WriteUniformBoolReg(g_state.vs, g_state.regs.vs.bool_uniforms.Value());          break; @@ -482,6 +474,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {      case PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[1], 0x2b2):      case PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[2], 0x2b3):      case PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[3], 0x2b4): { +        // TODO (wwylele): does regs.pipeline.gs_unit_exclusive_configuration affect this?          unsigned index = (id - PICA_REG_INDEX_WORKAROUND(vs.int_uniforms[0], 0x2b1));          auto values = regs.vs.int_uniforms[index];          WriteUniformIntReg(g_state.vs, index, @@ -497,6 +490,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {      case PICA_REG_INDEX_WORKAROUND(vs.uniform_setup.set_value[5], 0x2c6):      case PICA_REG_INDEX_WORKAROUND(vs.uniform_setup.set_value[6], 0x2c7):      case PICA_REG_INDEX_WORKAROUND(vs.uniform_setup.set_value[7], 0x2c8): { +        // TODO (wwylele): does regs.pipeline.gs_unit_exclusive_configuration affect this?          WriteUniformFloatReg(g_state.regs.vs, g_state.vs, vs_float_regs_counter,                               vs_uniform_write_buffer, value);          break; @@ -510,7 +504,16 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {      case PICA_REG_INDEX_WORKAROUND(vs.program.set_word[5], 0x2d1):      case PICA_REG_INDEX_WORKAROUND(vs.program.set_word[6], 0x2d2):      case PICA_REG_INDEX_WORKAROUND(vs.program.set_word[7], 0x2d3): { -        WriteProgramCode(g_state.regs.vs, g_state.vs, 512, value); +        u32& offset = g_state.regs.vs.program.offset; +        if (offset >= 512) { +            LOG_ERROR(HW_GPU, "Invalid VS program offset %u", offset); +        } else { +            g_state.vs.program_code[offset] = value; +            if (!g_state.regs.pipeline.gs_unit_exclusive_configuration) { +                g_state.gs.program_code[offset] = value; +            } +            offset++; +        }          break;      } @@ -522,7 +525,16 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {      case PICA_REG_INDEX_WORKAROUND(vs.swizzle_patterns.set_word[5], 0x2db):      case PICA_REG_INDEX_WORKAROUND(vs.swizzle_patterns.set_word[6], 0x2dc):      case PICA_REG_INDEX_WORKAROUND(vs.swizzle_patterns.set_word[7], 0x2dd): { -        WriteSwizzlePatterns(g_state.regs.vs, g_state.vs, value); +        u32& offset = g_state.regs.vs.swizzle_patterns.offset; +        if (offset >= g_state.vs.swizzle_data.size()) { +            LOG_ERROR(HW_GPU, "Invalid VS swizzle pattern offset %u", offset); +        } else { +            g_state.vs.swizzle_data[offset] = value; +            if (!g_state.regs.pipeline.gs_unit_exclusive_configuration) { +                g_state.gs.swizzle_data[offset] = value; +            } +            offset++; +        }          break;      } diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h index 2d23d34e6..864a2c9e6 100644 --- a/src/video_core/pica_state.h +++ b/src/video_core/pica_state.h @@ -79,7 +79,7 @@ struct State {          std::array<ColorDifferenceEntry, 256> color_diff_table;      } proctex; -    struct { +    struct Lighting {          union LutEntry {              // Used for raw access              u32 raw; diff --git a/src/video_core/regs_pipeline.h b/src/video_core/regs_pipeline.h index 31c747d77..8b6369297 100644 --- a/src/video_core/regs_pipeline.h +++ b/src/video_core/regs_pipeline.h @@ -202,7 +202,14 @@ struct PipelineRegs {      /// Number of input attributes to the vertex shader minus 1      BitField<0, 4, u32> max_input_attrib_index; -    INSERT_PADDING_WORDS(2); +    INSERT_PADDING_WORDS(1); + +    // The shader unit 3, which can be used for both vertex and geometry shader, gets its +    // configuration depending on this register. If this is not set, unit 3 will share some +    // configuration with other units. It is known that program code and swizzle pattern uploaded +    // via regs.vs will be also uploaded to unit 3 if this is not set. Although very likely, it is +    // still unclear whether uniforms and other configuration can be also shared. +    BitField<0, 1, u32> gs_unit_exclusive_configuration;      enum class GPUMode : u32 {          Drawing = 0, diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index bb192affd..ae67aab05 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -525,11 +525,12 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {             "float geo_factor = 1.0;\n";      // Compute fragment normals and tangents -    const std::string pertubation = -        "2.0 * (" + SampleTexture(config, lighting.bump_selector) + ").rgb - 1.0"; +    auto Perturbation = [&]() { +        return "2.0 * (" + SampleTexture(config, lighting.bump_selector) + ").rgb - 1.0"; +    };      if (lighting.bump_mode == LightingRegs::LightingBumpMode::NormalMap) {          // Bump mapping is enabled using a normal map -        out += "vec3 surface_normal = " + pertubation + ";\n"; +        out += "vec3 surface_normal = " + Perturbation() + ";\n";          // Recompute Z-component of perturbation if 'renorm' is enabled, this provides a higher          // precision result @@ -543,7 +544,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {          out += "vec3 surface_tangent = vec3(1.0, 0.0, 0.0);\n";      } else if (lighting.bump_mode == LightingRegs::LightingBumpMode::TangentMap) {          // Bump mapping is enabled using a tangent map -        out += "vec3 surface_tangent = " + pertubation + ";\n"; +        out += "vec3 surface_tangent = " + Perturbation() + ";\n";          // Mathematically, recomputing Z-component of the tangent vector won't affect the relevant          // computation below, which is also confirmed on 3DS. So we don't bother recomputing here          // even if 'renorm' is enabled. diff --git a/src/video_core/swrasterizer/clipper.cpp b/src/video_core/swrasterizer/clipper.cpp index 6fb923756..7537689b7 100644 --- a/src/video_core/swrasterizer/clipper.cpp +++ b/src/video_core/swrasterizer/clipper.cpp @@ -95,6 +95,17 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu      static const size_t MAX_VERTICES = 9;      static_vector<Vertex, MAX_VERTICES> buffer_a = {v0, v1, v2};      static_vector<Vertex, MAX_VERTICES> buffer_b; + +    auto FlipQuaternionIfOpposite = [](auto& a, const auto& b) { +        if (Math::Dot(a, b) < float24::Zero()) +            a = -a; +    }; + +    // Flip the quaternions if they are opposite to prevent interpolating them over the wrong +    // direction. +    FlipQuaternionIfOpposite(buffer_a[1].quat, buffer_a[0].quat); +    FlipQuaternionIfOpposite(buffer_a[2].quat, buffer_a[0].quat); +      auto* output_list = &buffer_a;      auto* input_list = &buffer_b; diff --git a/src/video_core/swrasterizer/lighting.cpp b/src/video_core/swrasterizer/lighting.cpp new file mode 100644 index 000000000..d61e6d572 --- /dev/null +++ b/src/video_core/swrasterizer/lighting.cpp @@ -0,0 +1,250 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/math_util.h" +#include "video_core/swrasterizer/lighting.h" + +namespace Pica { + +static float LookupLightingLut(const Pica::State::Lighting& lighting, size_t lut_index, u8 index, +                               float delta) { +    ASSERT_MSG(lut_index < lighting.luts.size(), "Out of range lut"); +    ASSERT_MSG(index < lighting.luts[lut_index].size(), "Out of range index"); + +    const auto& lut = lighting.luts[lut_index][index]; + +    float lut_value = lut.ToFloat(); +    float lut_diff = lut.DiffToFloat(); + +    return lut_value + lut_diff * delta; +} + +std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors( +    const Pica::LightingRegs& lighting, const Pica::State::Lighting& lighting_state, +    const Math::Quaternion<float>& normquat, const Math::Vec3<float>& view) { + +    // TODO(Subv): Bump mapping +    Math::Vec3<float> surface_normal = {0.0f, 0.0f, 1.0f}; + +    if (lighting.config0.bump_mode != LightingRegs::LightingBumpMode::None) { +        LOG_CRITICAL(HW_GPU, "unimplemented bump mapping"); +        UNIMPLEMENTED(); +    } + +    // Use the normalized the quaternion when performing the rotation +    auto normal = Math::QuaternionRotate(normquat, surface_normal); + +    Math::Vec4<float> diffuse_sum = {0.0f, 0.0f, 0.0f, 1.0f}; +    Math::Vec4<float> specular_sum = {0.0f, 0.0f, 0.0f, 1.0f}; + +    for (unsigned light_index = 0; light_index <= lighting.max_light_index; ++light_index) { +        unsigned num = lighting.light_enable.GetNum(light_index); +        const auto& light_config = lighting.light[num]; + +        Math::Vec3<float> refl_value = {}; +        Math::Vec3<float> position = {float16::FromRaw(light_config.x).ToFloat32(), +                                      float16::FromRaw(light_config.y).ToFloat32(), +                                      float16::FromRaw(light_config.z).ToFloat32()}; +        Math::Vec3<float> light_vector; + +        if (light_config.config.directional) +            light_vector = position; +        else +            light_vector = position + view; + +        light_vector.Normalize(); + +        float dist_atten = 1.0f; +        if (!lighting.IsDistAttenDisabled(num)) { +            auto distance = (-view - position).Length(); +            float scale = Pica::float20::FromRaw(light_config.dist_atten_scale).ToFloat32(); +            float bias = Pica::float20::FromRaw(light_config.dist_atten_bias).ToFloat32(); +            size_t lut = +                static_cast<size_t>(LightingRegs::LightingSampler::DistanceAttenuation) + num; + +            float sample_loc = MathUtil::Clamp(scale * distance + bias, 0.0f, 1.0f); + +            u8 lutindex = +                static_cast<u8>(MathUtil::Clamp(std::floor(sample_loc * 256.0f), 0.0f, 255.0f)); +            float delta = sample_loc * 256 - lutindex; +            dist_atten = LookupLightingLut(lighting_state, lut, lutindex, delta); +        } + +        auto GetLutValue = [&](LightingRegs::LightingLutInput input, bool abs, +                               LightingRegs::LightingScale scale_enum, +                               LightingRegs::LightingSampler sampler) { +            Math::Vec3<float> norm_view = view.Normalized(); +            Math::Vec3<float> half_angle = (norm_view + light_vector).Normalized(); +            float result = 0.0f; + +            switch (input) { +            case LightingRegs::LightingLutInput::NH: +                result = Math::Dot(normal, half_angle); +                break; + +            case LightingRegs::LightingLutInput::VH: +                result = Math::Dot(norm_view, half_angle); +                break; + +            case LightingRegs::LightingLutInput::NV: +                result = Math::Dot(normal, norm_view); +                break; + +            case LightingRegs::LightingLutInput::LN: +                result = Math::Dot(light_vector, normal); +                break; + +            default: +                LOG_CRITICAL(HW_GPU, "Unknown lighting LUT input %u\n", static_cast<u32>(input)); +                UNIMPLEMENTED(); +                result = 0.0f; +            } + +            u8 index; +            float delta; + +            if (abs) { +                if (light_config.config.two_sided_diffuse) +                    result = std::abs(result); +                else +                    result = std::max(result, 0.0f); + +                float flr = std::floor(result * 256.0f); +                index = static_cast<u8>(MathUtil::Clamp(flr, 0.0f, 255.0f)); +                delta = result * 256 - index; +            } else { +                float flr = std::floor(result * 128.0f); +                s8 signed_index = static_cast<s8>(MathUtil::Clamp(flr, -128.0f, 127.0f)); +                delta = result * 128.0f - signed_index; +                index = static_cast<u8>(signed_index); +            } + +            float scale = lighting.lut_scale.GetScale(scale_enum); +            return scale * +                   LookupLightingLut(lighting_state, static_cast<size_t>(sampler), index, delta); +        }; + +        // Specular 0 component +        float d0_lut_value = 1.0f; +        if (lighting.config1.disable_lut_d0 == 0 && +            LightingRegs::IsLightingSamplerSupported( +                lighting.config0.config, LightingRegs::LightingSampler::Distribution0)) { +            d0_lut_value = +                GetLutValue(lighting.lut_input.d0, lighting.abs_lut_input.disable_d0 == 0, +                            lighting.lut_scale.d0, LightingRegs::LightingSampler::Distribution0); +        } + +        Math::Vec3<float> specular_0 = d0_lut_value * light_config.specular_0.ToVec3f(); + +        // If enabled, lookup ReflectRed value, otherwise, 1.0 is used +        if (lighting.config1.disable_lut_rr == 0 && +            LightingRegs::IsLightingSamplerSupported(lighting.config0.config, +                                                     LightingRegs::LightingSampler::ReflectRed)) { +            refl_value.x = +                GetLutValue(lighting.lut_input.rr, lighting.abs_lut_input.disable_rr == 0, +                            lighting.lut_scale.rr, LightingRegs::LightingSampler::ReflectRed); +        } else { +            refl_value.x = 1.0f; +        } + +        // If enabled, lookup ReflectGreen value, otherwise, ReflectRed value is used +        if (lighting.config1.disable_lut_rg == 0 && +            LightingRegs::IsLightingSamplerSupported(lighting.config0.config, +                                                     LightingRegs::LightingSampler::ReflectGreen)) { +            refl_value.y = +                GetLutValue(lighting.lut_input.rg, lighting.abs_lut_input.disable_rg == 0, +                            lighting.lut_scale.rg, LightingRegs::LightingSampler::ReflectGreen); +        } else { +            refl_value.y = refl_value.x; +        } + +        // If enabled, lookup ReflectBlue value, otherwise, ReflectRed value is used +        if (lighting.config1.disable_lut_rb == 0 && +            LightingRegs::IsLightingSamplerSupported(lighting.config0.config, +                                                     LightingRegs::LightingSampler::ReflectBlue)) { +            refl_value.z = +                GetLutValue(lighting.lut_input.rb, lighting.abs_lut_input.disable_rb == 0, +                            lighting.lut_scale.rb, LightingRegs::LightingSampler::ReflectBlue); +        } else { +            refl_value.z = refl_value.x; +        } + +        // Specular 1 component +        float d1_lut_value = 1.0f; +        if (lighting.config1.disable_lut_d1 == 0 && +            LightingRegs::IsLightingSamplerSupported( +                lighting.config0.config, LightingRegs::LightingSampler::Distribution1)) { +            d1_lut_value = +                GetLutValue(lighting.lut_input.d1, lighting.abs_lut_input.disable_d1 == 0, +                            lighting.lut_scale.d1, LightingRegs::LightingSampler::Distribution1); +        } + +        Math::Vec3<float> specular_1 = +            d1_lut_value * refl_value * light_config.specular_1.ToVec3f(); + +        // Fresnel +        if (lighting.config1.disable_lut_fr == 0 && +            LightingRegs::IsLightingSamplerSupported(lighting.config0.config, +                                                     LightingRegs::LightingSampler::Fresnel)) { + +            float lut_value = +                GetLutValue(lighting.lut_input.fr, lighting.abs_lut_input.disable_fr == 0, +                            lighting.lut_scale.fr, LightingRegs::LightingSampler::Fresnel); + +            // Enabled for diffuse lighting alpha component +            if (lighting.config0.fresnel_selector == +                    LightingRegs::LightingFresnelSelector::PrimaryAlpha || +                lighting.config0.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { +                diffuse_sum.a() *= lut_value; +            } + +            // Enabled for the specular lighting alpha component +            if (lighting.config0.fresnel_selector == +                    LightingRegs::LightingFresnelSelector::SecondaryAlpha || +                lighting.config0.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { +                specular_sum.a() *= lut_value; +            } +        } + +        auto dot_product = Math::Dot(light_vector, normal); + +        // Calculate clamp highlights before applying the two-sided diffuse configuration to the dot +        // product. +        float clamp_highlights = 1.0f; +        if (lighting.config0.clamp_highlights) { +            if (dot_product <= 0.0f) +                clamp_highlights = 0.0f; +            else +                clamp_highlights = 1.0f; +        } + +        if (light_config.config.two_sided_diffuse) +            dot_product = std::abs(dot_product); +        else +            dot_product = std::max(dot_product, 0.0f); + +        auto diffuse = +            light_config.diffuse.ToVec3f() * dot_product + light_config.ambient.ToVec3f(); +        diffuse_sum += Math::MakeVec(diffuse * dist_atten, 0.0f); + +        specular_sum += +            Math::MakeVec((specular_0 + specular_1) * clamp_highlights * dist_atten, 0.0f); +    } + +    diffuse_sum += Math::MakeVec(lighting.global_ambient.ToVec3f(), 0.0f); + +    auto diffuse = Math::MakeVec<float>(MathUtil::Clamp(diffuse_sum.x, 0.0f, 1.0f) * 255, +                                        MathUtil::Clamp(diffuse_sum.y, 0.0f, 1.0f) * 255, +                                        MathUtil::Clamp(diffuse_sum.z, 0.0f, 1.0f) * 255, +                                        MathUtil::Clamp(diffuse_sum.w, 0.0f, 1.0f) * 255) +                       .Cast<u8>(); +    auto specular = Math::MakeVec<float>(MathUtil::Clamp(specular_sum.x, 0.0f, 1.0f) * 255, +                                         MathUtil::Clamp(specular_sum.y, 0.0f, 1.0f) * 255, +                                         MathUtil::Clamp(specular_sum.z, 0.0f, 1.0f) * 255, +                                         MathUtil::Clamp(specular_sum.w, 0.0f, 1.0f) * 255) +                        .Cast<u8>(); +    return std::make_tuple(diffuse, specular); +} + +} // namespace Pica diff --git a/src/video_core/swrasterizer/lighting.h b/src/video_core/swrasterizer/lighting.h new file mode 100644 index 000000000..438dca926 --- /dev/null +++ b/src/video_core/swrasterizer/lighting.h @@ -0,0 +1,18 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <tuple> +#include "common/quaternion.h" +#include "common/vector_math.h" +#include "video_core/pica_state.h" + +namespace Pica { + +std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors( +    const Pica::LightingRegs& lighting, const Pica::State::Lighting& lighting_state, +    const Math::Quaternion<float>& normquat, const Math::Vec3<float>& view); + +} // namespace Pica diff --git a/src/video_core/swrasterizer/rasterizer.cpp b/src/video_core/swrasterizer/rasterizer.cpp index 512e81c08..fdc1df199 100644 --- a/src/video_core/swrasterizer/rasterizer.cpp +++ b/src/video_core/swrasterizer/rasterizer.cpp @@ -13,6 +13,7 @@  #include "common/logging/log.h"  #include "common/math_util.h"  #include "common/microprofile.h" +#include "common/quaternion.h"  #include "common/vector_math.h"  #include "core/hw/gpu.h"  #include "core/memory.h" @@ -24,6 +25,7 @@  #include "video_core/regs_texturing.h"  #include "video_core/shader/shader.h"  #include "video_core/swrasterizer/framebuffer.h" +#include "video_core/swrasterizer/lighting.h"  #include "video_core/swrasterizer/proctex.h"  #include "video_core/swrasterizer/rasterizer.h"  #include "video_core/swrasterizer/texturing.h" @@ -419,6 +421,26 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve                  regs.texturing.tev_combiner_buffer_color.a,              }; +            Math::Vec4<u8> primary_fragment_color = {0, 0, 0, 0}; +            Math::Vec4<u8> secondary_fragment_color = {0, 0, 0, 0}; + +            if (!g_state.regs.lighting.disable) { +                Math::Quaternion<float> normquat = Math::Quaternion<float>{ +                    {GetInterpolatedAttribute(v0.quat.x, v1.quat.x, v2.quat.x).ToFloat32(), +                     GetInterpolatedAttribute(v0.quat.y, v1.quat.y, v2.quat.y).ToFloat32(), +                     GetInterpolatedAttribute(v0.quat.z, v1.quat.z, v2.quat.z).ToFloat32()}, +                    GetInterpolatedAttribute(v0.quat.w, v1.quat.w, v2.quat.w).ToFloat32(), +                }.Normalized(); + +                Math::Vec3<float> view{ +                    GetInterpolatedAttribute(v0.view.x, v1.view.x, v2.view.x).ToFloat32(), +                    GetInterpolatedAttribute(v0.view.y, v1.view.y, v2.view.y).ToFloat32(), +                    GetInterpolatedAttribute(v0.view.z, v1.view.z, v2.view.z).ToFloat32(), +                }; +                std::tie(primary_fragment_color, secondary_fragment_color) = +                    ComputeFragmentsColors(g_state.regs.lighting, g_state.lighting, normquat, view); +            } +              for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size();                   ++tev_stage_index) {                  const auto& tev_stage = tev_stages[tev_stage_index]; @@ -427,14 +449,13 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve                  auto GetSource = [&](Source source) -> Math::Vec4<u8> {                      switch (source) {                      case Source::PrimaryColor: +                        return primary_color; -                    // HACK: Until we implement fragment lighting, use primary_color                      case Source::PrimaryFragmentColor: -                        return primary_color; +                        return primary_fragment_color; -                    // HACK: Until we implement fragment lighting, use zero                      case Source::SecondaryFragmentColor: -                        return {0, 0, 0, 0}; +                        return secondary_fragment_color;                      case Source::Texture0:                          return texture_color[0];  | 
