From dc186c3cba0c2bcfaf9195fb49da1bd6c7fa37c7 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 14 Dec 2021 23:03:31 +0300 Subject: [PATCH 001/548] github: build android with NDK compiler --- .github/workflows/c-cpp.yml | 1 - scripts/gha/build_android.sh | 5 ----- scripts/gha/deps_android.sh | 20 +------------------- 3 files changed, 1 insertion(+), 25 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index bbc740ac..09daee39 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -37,7 +37,6 @@ jobs: GH_CPU_ARCH: ${{ matrix.targetarch }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ANDROID_SDK_TOOLS_VER: 4333796 - ANDROID_NDK_VER: r10e UPLOADTOOL_ISPRERELEASE: true steps: - name: Checkout diff --git a/scripts/gha/build_android.sh b/scripts/gha/build_android.sh index 03b85d7b..f451b956 100755 --- a/scripts/gha/build_android.sh +++ b/scripts/gha/build_android.sh @@ -12,11 +12,6 @@ elif [[ "$GH_CPU_ARCH" == "32&64" ]]; then export ARCHS="armeabi armeabi-v7a x86 aarch64 x86_64" fi -export API=21 -export TOOLCHAIN=host -export CC=clang-12 -export CXX=clang++-12 -export STRIP=llvm-strip-12 sh compile.sh release if [[ "$GH_CPU_ARCH" == "64" ]]; then diff --git a/scripts/gha/deps_android.sh b/scripts/gha/deps_android.sh index e1809ec0..790f8c2d 100755 --- a/scripts/gha/deps_android.sh +++ b/scripts/gha/deps_android.sh @@ -1,14 +1,5 @@ #!/bin/bash -echo "Install packages" -echo "deb https://apt.llvm.org/xenial/ llvm-toolchain-xenial-12 main" | sudo tee -a /etc/apt/sources.list -wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - -sudo apt update -sudo apt install clang-12 lld-12 llvm-12 llvm-12-tools p7zip-full -sudo rm /usr/bin/lld /usr/bin/ld.lld -sudo ln -s /usr/bin/lld-12 /usr/bin/lld -sudo ln -s /usr/bin/ld.lld-12 /usr/bin/ld.lld - echo "Download HLSDK" cd $GITHUB_WORKSPACE @@ -23,16 +14,7 @@ popd echo "Download all needed tools and NDK" yes | sdk/tools/bin/sdkmanager --licenses > /dev/null 2>/dev/null # who even reads licenses? :) -NDK_BUNDLE="ndk-bundle" -if [ "$ANDROID_NDK_VER" = "r10e" ]; then - NDK_BUNDLE="" -fi -sdk/tools/bin/sdkmanager --install build-tools\;29.0.1 platform-tools platforms\;android-29 $NDK_BUNDLE > /dev/null 2>/dev/null -if [ "$ANDROID_NDK_VER" = "r10e" ]; then - wget https://dl.google.com/android/repository/android-ndk-r10e-linux-x86_64.zip -qO ndk.zip > /dev/null || exit 1 - unzip -q ndk.zip || exit 1 - mv android-ndk-r10e sdk/ndk-bundle -fi +sdk/tools/bin/sdkmanager --install build-tools\;29.0.1 platform-tools platforms\;android-29 ndk-bundle > /dev/null 2>/dev/null echo "Download Xash3D FWGS Android source" git clone --depth 1 https://github.com/FWGS/xash3d-android-project -b waf android || exit 1 From 2604ea39c5a3418276f77ccfbe25e17696029f3f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 14 Dec 2021 23:17:04 +0300 Subject: [PATCH 002/548] github: try to upload artifacts --- .github/workflows/c-cpp.yml | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 09daee39..fbd1290f 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -11,27 +11,34 @@ jobs: - os: ubuntu-18.04 targetos: linux targetarch: amd64 + artifacts: xash3d-fwgs-amd64.AppImage xashds-linux-amd64 - os: ubuntu-18.04 targetos: linux targetarch: i386 + artifacts: xash3d-fwgs-i386.AppImage xashds-linux-i386 - os: ubuntu-18.04 targetos: android targetarch: 32 + artifacts: xashdroid-32.apk - os: ubuntu-18.04 targetos: android targetarch: 64 + artifacts: xashdroid-64.apk - os: ubuntu-18.04 targetos: motomagx targetarch: armv6 + artifacts: xash3d-fwgs-magx.7z - os: windows-latest targetos: win32 targetarch: amd64 + artifacts: xash3d-fwgs-win32-amd64.7z - os: windows-latest targetos: win32 targetarch: i386 + artifacts: xash3d-fwgs-win32-i386.7z env: SDL_VERSION: 2.0.14 GH_CPU_ARCH: ${{ matrix.targetarch }} @@ -55,15 +62,10 @@ jobs: - name: Build engine run: bash scripts/gha/build_${{ matrix.targetos }}.sh - - name: Upload engine (android) - if: matrix.targetos == 'android' - run: bash scripts/continious_upload.sh xashdroid-${{ matrix.targetarch }}.apk - - name: Upload engine (motomagx) - if: matrix.targetos == 'motomagx' - run: bash scripts/continious_upload.sh xash3d-fwgs-magx.7z - - name: Upload engine (linux) - if: matrix.targetos == 'linux' - run: bash scripts/continious_upload.sh xash3d-fwgs-${{ matrix.targetarch }}.AppImage xashds-linux-${{ matrix.targetarch }} - - name: Upload engine (windows) - if: matrix.targetos == 'win32' - run: bash scripts/continious_upload.sh xash3d-fwgs-win32-${{ matrix.targetarch }}.7z + - name: Upload engine (prereleases) + run: bash scripts/continious_upload.sh ${{ matrix.artifacts }} + - name: Upload engine (artifacts) + uses: actions/upload-artifact@v2 + with: + name: artifact-${{ matrix.targetos }}-${{ matrix.targetarch }} + path: ${{ matrix.artifacts }} From 960a8959d54d297651fd659753591491d955ce92 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 14 Dec 2021 23:23:52 +0300 Subject: [PATCH 003/548] github: download ndk directly instead of sdkmanager because sdkmanager sucks and ships ndk 22 --- scripts/gha/deps_android.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/gha/deps_android.sh b/scripts/gha/deps_android.sh index 790f8c2d..75e70979 100755 --- a/scripts/gha/deps_android.sh +++ b/scripts/gha/deps_android.sh @@ -14,7 +14,10 @@ popd echo "Download all needed tools and NDK" yes | sdk/tools/bin/sdkmanager --licenses > /dev/null 2>/dev/null # who even reads licenses? :) -sdk/tools/bin/sdkmanager --install build-tools\;29.0.1 platform-tools platforms\;android-29 ndk-bundle > /dev/null 2>/dev/null +sdk/tools/bin/sdkmanager --install build-tools\;29.0.1 platform-tools platforms\;android-29 > /dev/null 2>/dev/null +wget https://dl.google.com/android/repository/android-ndk-r23b-linux.zip -qO ndk.zip > /dev/null || exit 1 +unzip -q ndk.zip || exit 1 +mv android-ndk-r23b sdk/ndk-bundle || exit 1 echo "Download Xash3D FWGS Android source" git clone --depth 1 https://github.com/FWGS/xash3d-android-project -b waf android || exit 1 From 000c594596d4d3097f31644120dc19d6ed974418 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 15 Dec 2021 17:37:31 +0300 Subject: [PATCH 004/548] scripts: waifulib: disable -no-canonical-prefixes for new NDKs --- scripts/waifulib/xcompile.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/waifulib/xcompile.py b/scripts/waifulib/xcompile.py index 5d7082b3..f1853fc8 100644 --- a/scripts/waifulib/xcompile.py +++ b/scripts/waifulib/xcompile.py @@ -322,8 +322,9 @@ class Android: if self.is_clang() or self.is_host(): linkflags += ['-fuse-ld=lld'] + else: linkflags += ['-no-canonical-prefixes'] - linkflags += ['-Wl,--hash-style=sysv', '-Wl,--no-undefined', '-no-canonical-prefixes'] + linkflags += ['-Wl,--hash-style=sysv', '-Wl,--no-undefined'] return linkflags def ldflags(self): @@ -332,10 +333,9 @@ class Android: if self.ndk_rev < 23: ldflags += ['-lgcc'] - ldflags += ['-no-canonical-prefixes'] - if self.is_clang() or self.is_host(): ldflags += ['-stdlib=libstdc++'] + else: ldflags += ['-no-canonical-prefixes'] if self.is_arm(): if self.arch == 'armeabi-v7a': From d3248aad6622658d4c449faad5532c5a2ddc3fca Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 15 Dec 2021 17:40:24 +0300 Subject: [PATCH 005/548] scripts: waifulib: clean legacy Android definitions --- scripts/waifulib/xcompile.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/waifulib/xcompile.py b/scripts/waifulib/xcompile.py index f1853fc8..729f6cf1 100644 --- a/scripts/waifulib/xcompile.py +++ b/scripts/waifulib/xcompile.py @@ -266,7 +266,9 @@ class Android: '-isystem', '%s/usr/include/' % (self.sysroot()) ] - cflags += ['-I%s' % (self.system_stl()), '-DANDROID', '-D__ANDROID__'] + cflags += ['-I%s' % (self.system_stl())] + if not self.is_clang(): + cflags += ['-DANDROID', '-D__ANDROID__'] if cxx and not self.is_clang() and self.toolchain not in ['4.8','4.9']: cflags += ['-fno-sized-deallocation'] @@ -287,7 +289,7 @@ class Android: if self.is_arm(): if self.arch == 'armeabi-v7a': # ARMv7 support - cflags += ['-mthumb', '-mfpu=neon', '-mcpu=cortex-a9', '-DHAVE_EFFICIENT_UNALIGNED_ACCESS', '-DVECTORIZE_SINCOS'] + cflags += ['-mthumb', '-mfpu=neon', '-mcpu=cortex-a9'] if not self.is_clang() and not self.is_host(): cflags += [ '-mvectorize-with-neon-quad' ] @@ -306,7 +308,7 @@ class Android: # ARMv5 support cflags += ['-march=armv5te', '-msoft-float'] elif self.is_x86(): - cflags += ['-mtune=atom', '-march=atom', '-mssse3', '-mfpmath=sse', '-DVECTORIZE_SINCOS', '-DHAVE_EFFICIENT_UNALIGNED_ACCESS'] + cflags += ['-mtune=atom', '-march=atom', '-mssse3', '-mfpmath=sse'] return cflags # they go before object list From a4be009ce7d349ee3cb28b59b53ea13419db323b Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 17 Dec 2021 03:15:38 +0300 Subject: [PATCH 006/548] scripts: waifulib: disable stpcpy builtin for API level <21 --- scripts/waifulib/xcompile.py | 38 ++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/scripts/waifulib/xcompile.py b/scripts/waifulib/xcompile.py index 729f6cf1..0abf53f1 100644 --- a/scripts/waifulib/xcompile.py +++ b/scripts/waifulib/xcompile.py @@ -26,6 +26,8 @@ ANDROID_NDK_GCC_MAX = 17 # latest NDK that ships with GCC ANDROID_NDK_UNIFIED_SYSROOT_MIN = 15 ANDROID_NDK_SYSROOT_FLAG_MAX = 19 # latest NDK that need --sysroot flag ANDROID_NDK_API_MIN = { 10: 3, 19: 16, 20: 16, 23: 16 } # minimal API level ndk revision supports + +ANDROID_STPCPY_API_MIN = 21 # stpcpy() introduced in SDK 21 ANDROID_64BIT_API_MIN = 21 # minimal API level that supports 64-bit targets # This class does support ONLY r10e and r19c/r20 NDK @@ -273,38 +275,32 @@ class Android: if cxx and not self.is_clang() and self.toolchain not in ['4.8','4.9']: cflags += ['-fno-sized-deallocation'] - def fixup_host_clang_with_old_ndk(): - cflags = [] - # Clang builtin redefine w/ different calling convention bug - # NOTE: I did not added complex.h functions here, despite - # that NDK devs forgot to put __NDK_FPABI_MATH__ for complex - # math functions - # I personally don't need complex numbers support, but if you want it - # just run sed to patch header - for f in ['strtod', 'strtof', 'strtold']: - cflags += ['-fno-builtin-%s' % f] - return cflags - + if self.is_clang(): + # stpcpy() isn't available in early Android versions + # disable it here so Clang won't use it + if self.api < ANDROID_STPCPY_API_MIN: + cflags += ['-fno-builtin-stpcpy'] if self.is_arm(): if self.arch == 'armeabi-v7a': # ARMv7 support cflags += ['-mthumb', '-mfpu=neon', '-mcpu=cortex-a9'] - if not self.is_clang() and not self.is_host(): - cflags += [ '-mvectorize-with-neon-quad' ] - - if self.is_host() and self.ndk_rev <= ANDROID_NDK_HARDFP_MAX: - cflags += fixup_host_clang_with_old_ndk() - if self.is_hardfp(): cflags += ['-D_NDK_MATH_NO_SOFTFP=1', '-mfloat-abi=hard', '-DLOAD_HARDFP', '-DSOFTFP_LINK'] + + if self.is_host(): + # Clang builtin redefine w/ different calling convention bug + # NOTE: I did not added complex.h functions here, despite + # that NDK devs forgot to put __NDK_FPABI_MATH__ for complex + # math functions + # I personally don't need complex numbers support, but if you want it + # just run sed to patch header + for f in ['strtod', 'strtof', 'strtold']: + cflags += ['-fno-builtin-%s' % f] else: cflags += ['-mfloat-abi=softfp'] else: - if self.is_host() and self.ndk_rev <= ANDROID_NDK_HARDFP_MAX: - cflags += fixup_host_clang_with_old_ndk() - # ARMv5 support cflags += ['-march=armv5te', '-msoft-float'] elif self.is_x86(): From f7bf081650258eb7270ee37eb952fe3cc8bcf6d8 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 17 Dec 2021 03:50:18 +0300 Subject: [PATCH 007/548] github: try to fix artifacts uploading (YAML is worst markup language) --- .github/workflows/c-cpp.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index fbd1290f..920949f0 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -11,11 +11,15 @@ jobs: - os: ubuntu-18.04 targetos: linux targetarch: amd64 - artifacts: xash3d-fwgs-amd64.AppImage xashds-linux-amd64 + artifacts: | + xash3d-fwgs-amd64.AppImage + xashds-linux-amd64 - os: ubuntu-18.04 targetos: linux targetarch: i386 - artifacts: xash3d-fwgs-i386.AppImage xashds-linux-i386 + artifacts: | + xash3d-fwgs-i386.AppImage + xashds-linux-i386 - os: ubuntu-18.04 targetos: android @@ -63,7 +67,7 @@ jobs: run: bash scripts/gha/build_${{ matrix.targetos }}.sh - name: Upload engine (prereleases) - run: bash scripts/continious_upload.sh ${{ matrix.artifacts }} + run: bash scripts/continious_upload.sh ${{ join( matrix.artifacts, ' ' ) }} - name: Upload engine (artifacts) uses: actions/upload-artifact@v2 with: From 7a0a355bafa6e7c3377cee0b389b808bc3b60089 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 17 Dec 2021 04:09:54 +0300 Subject: [PATCH 008/548] github: grab artifacts by wildcard rather than a list --- .github/workflows/c-cpp.yml | 15 ++------------- scripts/gha/build_android.sh | 8 ++++++-- scripts/gha/build_linux.sh | 5 ++++- scripts/gha/build_motomagx.sh | 8 +++++--- scripts/gha/build_win32.sh | 3 ++- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 920949f0..5f0928ac 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -11,38 +11,27 @@ jobs: - os: ubuntu-18.04 targetos: linux targetarch: amd64 - artifacts: | - xash3d-fwgs-amd64.AppImage - xashds-linux-amd64 - os: ubuntu-18.04 targetos: linux targetarch: i386 - artifacts: | - xash3d-fwgs-i386.AppImage - xashds-linux-i386 - os: ubuntu-18.04 targetos: android targetarch: 32 - artifacts: xashdroid-32.apk - os: ubuntu-18.04 targetos: android targetarch: 64 - artifacts: xashdroid-64.apk - os: ubuntu-18.04 targetos: motomagx targetarch: armv6 - artifacts: xash3d-fwgs-magx.7z - os: windows-latest targetos: win32 targetarch: amd64 - artifacts: xash3d-fwgs-win32-amd64.7z - os: windows-latest targetos: win32 targetarch: i386 - artifacts: xash3d-fwgs-win32-i386.7z env: SDL_VERSION: 2.0.14 GH_CPU_ARCH: ${{ matrix.targetarch }} @@ -67,9 +56,9 @@ jobs: run: bash scripts/gha/build_${{ matrix.targetos }}.sh - name: Upload engine (prereleases) - run: bash scripts/continious_upload.sh ${{ join( matrix.artifacts, ' ' ) }} + run: bash scripts/continious_upload.sh artifacts/* - name: Upload engine (artifacts) uses: actions/upload-artifact@v2 with: name: artifact-${{ matrix.targetos }}-${{ matrix.targetarch }} - path: ${{ matrix.artifacts }} + path: artifacts/* diff --git a/scripts/gha/build_android.sh b/scripts/gha/build_android.sh index f451b956..a5f4df5c 100755 --- a/scripts/gha/build_android.sh +++ b/scripts/gha/build_android.sh @@ -14,8 +14,12 @@ fi sh compile.sh release +popd + +mkdir -p artifacts/ + if [[ "$GH_CPU_ARCH" == "64" ]]; then - mv xashdroid.apk ../xashdroid-64.apk + mv android/xashdroid.apk artifacts/xashdroid-64.apk else - mv xashdroid.apk ../xashdroid-32.apk + mv android/xashdroid.apk artifacts/xashdroid-32.apk fi diff --git a/scripts/gha/build_linux.sh b/scripts/gha/build_linux.sh index 77910f98..99e76d43 100755 --- a/scripts/gha/build_linux.sh +++ b/scripts/gha/build_linux.sh @@ -104,11 +104,14 @@ EOF ./appimagetool.AppImage "$APPDIR" "$APPIMAGE" } +mkdir -p artifacts/ + rm -rf build # clean-up build directory build_engine dedicated -mv build/engine/xash xashds-linux-$ARCH +mv build/engine/xash artifacts/xashds-linux-$ARCH rm -rf build build_sdl2 build_engine full build_appimage +mv $APPIMAGE artifacts/ diff --git a/scripts/gha/build_motomagx.sh b/scripts/gha/build_motomagx.sh index 06c06e7d..8ab397a1 100755 --- a/scripts/gha/build_motomagx.sh +++ b/scripts/gha/build_motomagx.sh @@ -8,9 +8,9 @@ cd $GITHUB_WORKSPACE mkdir -p Xash/valve/cl_dlls mkdir -p Xash/valve/dlls -cd hlsdk +pushd hlsdk ./waf configure -T fast --enable-magx --enable-simple-mod-hacks build install --destdir=../Xash || die -cd ../ +popd ./waf configure -T fast --enable-magx build install --destdir=Xash/ || die @@ -30,4 +30,6 @@ exec $mypath/xash -dev $@ EOF -7z a -t7z $GITHUB_WORKSPACE/xash3d-fwgs-magx.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -r Xash/ +mkdir -p artifacts/ + +7z a -t7z artifacts/xash3d-fwgs-magx.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -r Xash/ diff --git a/scripts/gha/build_win32.sh b/scripts/gha/build_win32.sh index b99da3ea..ad11f6bc 100755 --- a/scripts/gha/build_win32.sh +++ b/scripts/gha/build_win32.sh @@ -24,4 +24,5 @@ else die fi -7z a -t7z $BUILDDIR/xash3d-fwgs-win32-$ARCH.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on *.dll *.exe *.pdb +mkdir -p artifacts/ +7z a -t7z artifacts/xash3d-fwgs-win32-$ARCH.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on *.dll *.exe *.pdb From ca3544d55949f9c7e73cc24ed29e7afe5748ffbe Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 18 Dec 2021 03:32:40 +0300 Subject: [PATCH 009/548] engine: server: don't assert for NULL player in FatPAS/PVS if we don't need it --- engine/server/sv_game.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index 494f1262..0f9239f0 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -4150,13 +4150,13 @@ byte *pfnSetFatPVS( const float *org ) if( !sv.worldmodel->visdata || sv_novis->value || !org || CL_DisableVisibility( )) fullvis = true; - ASSERT( pfnGetCurrentPlayer() != -1 ); - // portals can't change viewpoint! if( !FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY )) { vec3_t viewPos, offset; + ASSERT( pfnGetCurrentPlayer() != -1 ); + // see code from client.cpp for understanding: // org = pView->v.origin + pView->v.view_ofs; // if ( pView->v.flags & FL_DUCKING ) @@ -4200,13 +4200,13 @@ byte *pfnSetFatPAS( const float *org ) if( !sv.worldmodel->visdata || sv_novis->value || !org || CL_DisableVisibility( )) fullvis = true; - ASSERT( pfnGetCurrentPlayer() != -1 ); - // portals can't change viewpoint! if( !FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY )) { vec3_t viewPos, offset; + ASSERT( pfnGetCurrentPlayer() != -1 ); + // see code from client.cpp for understanding: // org = pView->v.origin + pView->v.view_ofs; // if ( pView->v.flags & FL_DUCKING ) From 23e2f4d0ccba4b73865bb222fda4a9c398459764 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 18 Dec 2021 03:46:21 +0300 Subject: [PATCH 010/548] wscript: fix install on Android --- wscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wscript b/wscript index c283d47a..b0d12c0f 100644 --- a/wscript +++ b/wscript @@ -301,7 +301,7 @@ def configure(conf): conf.env.LIBDIR = conf.env.BINDIR = '${PREFIX}/lib/xash3d' conf.env.SHAREDIR = '${PREFIX}/share/xash3d' else: - if sys.platform != 'win32': + if sys.platform != 'win32' and not conf.env.DEST_OS == 'android': conf.env.PREFIX = '/' conf.env.SHAREDIR = conf.env.LIBDIR = conf.env.BINDIR = conf.env.PREFIX From 0963e05716daba1b7d9ae9ef32341358c31c4719 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 20 Dec 2021 18:12:31 +0300 Subject: [PATCH 011/548] engine: restore lumps after swapping them for blue shift maps Mod_TestBmodelLumps may modify srclumps data, which is essential for brush model loader. BlueShift maps have swapped lumps for some reason Restoring them to default is fix for now --- engine/common/mod_bmodel.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/engine/common/mod_bmodel.c b/engine/common/mod_bmodel.c index 8d8dcae8..97c2e059 100644 --- a/engine/common/mod_bmodel.c +++ b/engine/common/mod_bmodel.c @@ -2908,13 +2908,28 @@ qboolean Mod_TestBmodelLumps( const char *name, const byte *mod_base, qboolean s break; } - if( header->version == HLBSP_VERSION && - header->lumps[LUMP_ENTITIES].fileofs <= 1024 && - (header->lumps[LUMP_ENTITIES].filelen % sizeof( dplane_t )) == 0 ) + if( header->version == HLBSP_VERSION ) { - // blue-shift swapped lumps - srclumps[0].lumpnumber = LUMP_PLANES; - srclumps[1].lumpnumber = LUMP_ENTITIES; + // only relevant for half-life maps + if( header->lumps[LUMP_ENTITIES].fileofs <= 1024 && + (header->lumps[LUMP_ENTITIES].filelen % sizeof( dplane_t )) == 0 ) + { + // blue-shift swapped lumps + srclumps[0].lumpnumber = LUMP_PLANES; + srclumps[1].lumpnumber = LUMP_ENTITIES; + } + else + { + // everything else + srclumps[0].lumpnumber = LUMP_ENTITIES; + srclumps[1].lumpnumber = LUMP_PLANES; + } + } + else + { + // everything else + srclumps[0].lumpnumber = LUMP_ENTITIES; + srclumps[1].lumpnumber = LUMP_PLANES; } // loading base lumps From 7e574ea42e6dda4298e2f22e1b026b9dd493f38d Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 18 Dec 2021 05:56:38 +0300 Subject: [PATCH 012/548] engine: filesystem: do not create folders for files in rodir mode --- engine/common/filesystem.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index e15c6f1a..8f928d5d 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -2114,9 +2114,15 @@ void FS_Init( void ) for( i = 0; i < dirs.numstrings; i++ ) { - // no need to check folders here, FS_CreatePath will not fail if path exists - // and listdirectory returns only really existing directories - FS_CreatePath( va( "%s" PATH_SPLITTER "%s/", host.rootdir, dirs.strings[i] )); + const char *roPath = va( "%s" PATH_SPLITTER "%s", host.rodir, dirs.strings[i] ); + const char *rwPath = va( "%s" PATH_SPLITTER "%s", host.rootdir, dirs.strings[i] ); + + // check if it's a directory + if( !FS_SysFolderExists( roPath )) + continue; + + // no need to check folders here, FS_CreatePath will not fail + FS_CreatePath( rwPath ); } stringlistfreecontents( &dirs ); From 08e72bad356e7cefe441b8901b2575bf88e734f0 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 22 Dec 2021 05:02:34 +0300 Subject: [PATCH 013/548] engine: crashhandler: clean up code --- engine/common/crashhandler.c | 113 +++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 51 deletions(-) diff --git a/engine/common/crashhandler.c b/engine/common/crashhandler.c index 525237c3..f8cbc542 100644 --- a/engine/common/crashhandler.c +++ b/engine/common/crashhandler.c @@ -24,9 +24,11 @@ Crash handler, called from system ================ */ #if XASH_CRASHHANDLER == CRASHHANDLER_DBGHELP || XASH_CRASHHANDLER == CRASHHANDLER_WIN32 + #if XASH_CRASHHANDLER == CRASHHANDLER_DBGHELP + #pragma comment( lib, "dbghelp" ) -#pragma comment( lib, "psapi" ) + #include #include #include @@ -35,7 +37,7 @@ Crash handler, called from system typedef ULONG_PTR DWORD_PTR, *PDWORD_PTR; #endif -int ModuleName( HANDLE process, char *name, void *address, int len ) +static int Sys_ModuleName( HANDLE process, char *name, void *address, int len ) { DWORD_PTR baseAddress = 0; static HMODULE *moduleArray; @@ -44,27 +46,24 @@ int ModuleName( HANDLE process, char *name, void *address, int len ) DWORD bytesRequired; int i; - if(len < 3) + if( len < 3 ) return 0; - if ( !moduleArray && EnumProcessModules( process, NULL, 0, &bytesRequired ) ) + if( !moduleArray && EnumProcessModules( process, NULL, 0, &bytesRequired ) ) { if ( bytesRequired ) { moduleArrayBytes = (LPBYTE)LocalAlloc( LPTR, bytesRequired ); - if ( moduleArrayBytes ) + if( moduleArrayBytes && EnumProcessModules( process, (HMODULE *)moduleArrayBytes, bytesRequired, &bytesRequired ) ) { - if( EnumProcessModules( process, (HMODULE *)moduleArrayBytes, bytesRequired, &bytesRequired ) ) - { - moduleCount = bytesRequired / sizeof( HMODULE ); - moduleArray = (HMODULE *)moduleArrayBytes; - } + moduleCount = bytesRequired / sizeof( HMODULE ); + moduleArray = (HMODULE *)moduleArrayBytes; } } } - for( i = 0; iContextRecord, sizeof(CONTEXT) ); + memcpy( &context, pInfo->ContextRecord, sizeof( CONTEXT )); + options = SymGetOptions(); options |= SYMOPT_DEBUG; options |= SYMOPT_LOAD_LINES; @@ -97,9 +98,7 @@ static void stack_trace( PEXCEPTION_POINTERS pInfo ) SymInitialize( process, NULL, TRUE ); - - - ZeroMemory( &stackframe, sizeof(STACKFRAME64) ); + ZeroMemory( &stackframe, sizeof( STACKFRAME64 )); #ifdef _M_IX86 image = IMAGE_FILE_MACHINE_I386; @@ -127,19 +126,25 @@ static void stack_trace( PEXCEPTION_POINTERS pInfo ) stackframe.AddrBStore.Mode = AddrModeFlat; stackframe.AddrStack.Offset = context.IntSp; stackframe.AddrStack.Mode = AddrModeFlat; +#elif +#error #endif - len += Q_snprintf( message + len, 1024 - len, "Sys_Crash: address %p, code %p\n", pInfo->ExceptionRecord->ExceptionAddress, (void*)pInfo->ExceptionRecord->ExceptionCode ); + len += Q_snprintf( message + len, 1024 - len, "Sys_Crash: address %p, code %p\n", + pInfo->ExceptionRecord->ExceptionAddress, (void*)pInfo->ExceptionRecord->ExceptionCode ); if( SymGetLineFromAddr64( process, (DWORD64)pInfo->ExceptionRecord->ExceptionAddress, &dline, &line ) ) { - len += Q_snprintf(message + len, 1024 - len,"Exception: %s:%d:%d\n", (char*)line.FileName, (int)line.LineNumber, (int)dline); + len += Q_snprintf(message + len, 1024 - len, "Exception: %s:%d:%d\n", + (char*)line.FileName, (int)line.LineNumber, (int)dline); } if( SymGetLineFromAddr64( process, stackframe.AddrPC.Offset, &dline, &line ) ) { - len += Q_snprintf(message + len, 1024 - len,"PC: %s:%d:%d\n", (char*)line.FileName, (int)line.LineNumber, (int)dline); + len += Q_snprintf(message + len, 1024 - len,"PC: %s:%d:%d\n", + (char*)line.FileName, (int)line.LineNumber, (int)dline); } if( SymGetLineFromAddr64( process, stackframe.AddrFrame.Offset, &dline, &line ) ) { - len += Q_snprintf(message + len, 1024 - len,"Frame: %s:%d:%d\n", (char*)line.FileName, (int)line.LineNumber, (int)dline); + len += Q_snprintf(message + len, 1024 - len,"Frame: %s:%d:%d\n", + (char*)line.FileName, (int)line.LineNumber, (int)dline); } for( i = 0; i < 25; i++ ) { @@ -149,39 +154,43 @@ static void stack_trace( PEXCEPTION_POINTERS pInfo ) image, process, thread, &stackframe, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL); - DWORD64 displacement = 0; + if( !result ) break; - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); symbol->MaxNameLen = MAX_SYM_NAME; - len += Q_snprintf( message + len, 1024 - len, "% 2d %p", i, (void*)stackframe.AddrPC.Offset ); + len += Q_snprintf( message + len, 1024 - len, "% 2d %p", + i, (void*)stackframe.AddrPC.Offset ); if( SymFromAddr( process, stackframe.AddrPC.Offset, &displacement, symbol ) ) { len += Q_snprintf( message + len, 1024 - len, " %s ", symbol->Name ); } if( SymGetLineFromAddr64( process, stackframe.AddrPC.Offset, &dline, &line ) ) { - len += Q_snprintf(message + len, 1024 - len,"(%s:%d:%d) ", (char*)line.FileName, (int)line.LineNumber, (int)dline); + len += Q_snprintf(message + len, 1024 - len,"(%s:%d:%d) ", + (char*)line.FileName, (int)line.LineNumber, (int)dline); } len += Q_snprintf( message + len, 1024 - len, "("); - len += ModuleName( process, message + len, (void*)stackframe.AddrPC.Offset, 1024 - len ); + len += Sys_ModuleName( process, message + len, (void*)stackframe.AddrPC.Offset, 1024 - len ); len += Q_snprintf( message + len, 1024 - len, ")\n"); } -#ifdef XASH_SDL - if( host.type != HOST_DEDICATED ) // let system to restart server automaticly - SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR,"Sys_Crash", message, host.hWnd ); -#endif - Sys_PrintLog(message); - SymCleanup(process); +#if XASH_SDL >= 2 + if( host.type != HOST_DEDICATED ) // let system to restart server automaticly + SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, "Sys_Crash", message, host.hWnd ); +#endif + + Sys_PrintLog( message ); + + SymCleanup( process ); } -#endif //DBGHELP +#endif /* XASH_CRASHHANDLER == CRASHHANDLER_DBGHELP */ + LPTOP_LEVEL_EXCEPTION_FILTER oldFilter; -long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo ) +static long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo ) { // save config if( host.status != HOST_CRASHED ) @@ -190,7 +199,7 @@ long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo ) host.crashed = true; #if XASH_CRASHHANDLER == CRASHHANDLER_DBGHELP - stack_trace( pInfo ); + Sys_StackTrace( pInfo ); #else Sys_Warn( "Sys_Crash: call %p at address %p", pInfo->ExceptionRecord->ExceptionAddress, pInfo->ExceptionRecord->ExceptionCode ); #endif @@ -233,13 +242,14 @@ void Sys_RestoreCrashHandler( void ) #include "library.h" -#if XASH_FREEBSD || XASH_NETBSD || XASH_ANDROID || XASH_LINUX +#if XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD || XASH_ANDROID || XASH_LINUX #define HAVE_UCONTEXT_H 1 #endif #ifdef HAVE_UCONTEXT_H #include #endif + #include #include @@ -261,7 +271,7 @@ static int d_dladdr( void *sym, Dl_info *info ) #define dladdr d_dladdr #endif -int printframe( char *buf, int len, int i, void *addr ) +static int Sys_PrintFrame( char *buf, int len, int i, void *addr ) { Dl_info dlinfo; if( len <= 0 ) @@ -271,7 +281,7 @@ int printframe( char *buf, int len, int i, void *addr ) { if( dlinfo.dli_sname ) return Q_snprintf( buf, len, "%2d: %p <%s+%lu> (%s)\n", i, addr, dlinfo.dli_sname, - (unsigned long)addr - (unsigned long)dlinfo.dli_saddr, dlinfo.dli_fname ); // print symbol, module and address + (unsigned long)addr - (unsigned long)dlinfo.dli_saddr, dlinfo.dli_fname ); // print symbol, module and address else return Q_snprintf( buf, len, "%2d: %p (%s)\n", i, addr, dlinfo.dli_fname ); // print module and address } @@ -281,10 +291,11 @@ int printframe( char *buf, int len, int i, void *addr ) struct sigaction oldFilter; -#define STACK_BACKTRACE_STR_LEN 17 -#define STACK_BACKTRACE_STR "Stack backtrace:\n" -#define STACK_DUMP_STR_LEN 12 -#define STACK_DUMP_STR "Stack dump:\n" +#define STACK_BACKTRACE_STR "Stack backtrace:\n" +#define STACK_DUMP_STR "Stack dump:\n" + +#define STACK_BACKTRACE_STR_LEN (sizeof( STACK_BACKTRACE_STR ) - 1) +#define STACK_DUMP_STR_LEN (sizeof( STACK_DUMP_STR ) - 1) #define ALIGN( x, y ) (((uintptr_t) (x) + ((y)-1)) & ~((y)-1)) static void Sys_Crash( int signal, siginfo_t *si, void *context) @@ -356,7 +367,7 @@ static void Sys_Crash( int signal, siginfo_t *si, void *context) len += Q_snprintf( message + len, sizeof( message ) - len, "Crash: signal %d errno %d with code %d at %p\n", signal, si->si_errno, si->si_code, si->si_addr ); #endif - write( 2, message, len ); + write( STDERR_FILENO, message, len ); // flush buffers before writing directly to descriptors fflush( stdout ); @@ -370,9 +381,9 @@ static void Sys_Crash( int signal, siginfo_t *si, void *context) { size_t pagesize = sysconf( _SC_PAGESIZE ); // try to print backtrace - write( 2, STACK_BACKTRACE_STR, STACK_BACKTRACE_STR_LEN ); + write( STDERR_FILENO, STACK_BACKTRACE_STR, STACK_BACKTRACE_STR_LEN ); write( logfd, STACK_BACKTRACE_STR, STACK_BACKTRACE_STR_LEN ); - strncpy( message + len, STACK_BACKTRACE_STR, sizeof( message ) - len ); + Q_strncpy( message + len, STACK_BACKTRACE_STR, sizeof( message ) - len ); len += STACK_BACKTRACE_STR_LEN; // false on success, true on failure @@ -384,8 +395,8 @@ static void Sys_Crash( int signal, siginfo_t *si, void *context) do { - int line = printframe( message + len, sizeof( message ) - len, ++i, pc); - write( 2, message + len, line ); + int line = Sys_PrintFrame( message + len, sizeof( message ) - len, ++i, pc); + write( STDERR_FILENO, message + len, line ); write( logfd, message + len, line ); len += line; //if( !dladdr(bp,0) ) break; // only when bp is in module @@ -399,17 +410,17 @@ static void Sys_Crash( int signal, siginfo_t *si, void *context) while( bp && i < 128 ); // try to print stack - write( 2, STACK_DUMP_STR, STACK_DUMP_STR_LEN ); + write( STDERR_FILENO, STACK_DUMP_STR, STACK_DUMP_STR_LEN ); write( logfd, STACK_DUMP_STR, STACK_DUMP_STR_LEN ); - strncpy( message + len, STACK_DUMP_STR, sizeof( message ) - len ); + Q_strncpy( message + len, STACK_DUMP_STR, sizeof( message ) - len ); len += STACK_DUMP_STR_LEN; if( !try_allow_read( sp, pagesize ) ) { for( i = 0; i < 32; i++ ) { - int line = printframe( message + len, sizeof( message ) - len, i, sp[i] ); - write( 2, message + len, line ); + int line = Sys_PrintFrame( message + len, sizeof( message ) - len, i, sp[i] ); + write( STDERR_FILENO, message + len, line ); write( logfd, message + len, line ); len += line; } From d8b3ab4dcc1acd2a5cfb54a5c95d98ebe5c742e1 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 22 Dec 2021 05:03:06 +0300 Subject: [PATCH 014/548] engine: filesystem: fix unused result warnings --- engine/common/filesystem.c | 106 +++++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 27 deletions(-) diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index 8f928d5d..51e03f04 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -406,7 +406,8 @@ static const char *FS_FixFileCase( const char *path ) out[i] = 0; return out; #elif !XASH_WIN32 && !XASH_IOS // assume case insensitive - DIR *dir; struct dirent *entry; + DIR *dir; + struct dirent *entry; char path2[PATH_MAX], *fname; if( !fs_caseinsensitive ) @@ -600,11 +601,12 @@ of the list so they override previous pack files. */ pack_t *FS_LoadPackPAK( const char *packfile, int *error ) { - dpackheader_t header; - int packhandle; - int i, numpackfiles; - pack_t *pack; - dpackfile_t *info; + dpackheader_t header; + int packhandle; + int i, numpackfiles; + pack_t *pack; + dpackfile_t *info; + ssize_t c; packhandle = open( packfile, O_RDONLY|O_BINARY ); @@ -612,21 +614,21 @@ pack_t *FS_LoadPackPAK( const char *packfile, int *error ) if( packhandle < 0 ) { const char *fpackfile = FS_FixFileCase( packfile ); - if( fpackfile!= packfile ) + if( fpackfile != packfile ) packhandle = open( fpackfile, O_RDONLY|O_BINARY ); } #endif if( packhandle < 0 ) { - Con_Reportf( "%s couldn't open\n", packfile ); + Con_Reportf( "%s couldn't open: %s\n", packfile, strerror( errno )); if( error ) *error = PAK_LOAD_COULDNT_OPEN; return NULL; } - read( packhandle, (void *)&header, sizeof( header )); + c = read( packhandle, (void *)&header, sizeof( header )); - if( header.ident != IDPACKV1HEADER ) + if( c != sizeof( header ) || header.ident != IDPACKV1HEADER ) { Con_Reportf( "%s is not a packfile. Ignored.\n", packfile ); if( error ) *error = PAK_LOAD_BAD_HEADER; @@ -685,7 +687,7 @@ pack_t *FS_LoadPackPAK( const char *packfile, int *error ) #ifdef XASH_REDUCE_FD // will reopen when needed - close(pack->handle); + close( pack->handle ); pack->handle = -1; #endif @@ -719,7 +721,8 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) fs_offset_t filepos = 0, length; zipfile_t *info = NULL; char filename_buffer[MAX_SYSPATH]; - zip_t *zip = (zip_t *)Mem_Calloc( fs_mempool, sizeof( zip_t ) ); + zip_t *zip = (zip_t *)Mem_Calloc( fs_mempool, sizeof( *zip )); + ssize_t c; zip->handle = open( zipfile, O_RDONLY|O_BINARY ); @@ -758,9 +761,9 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) lseek( zip->handle, 0, SEEK_SET ); - read( zip->handle, &signature, sizeof( signature ) ); + c = read( zip->handle, &signature, sizeof( signature ) ); - if( signature == ZIP_HEADER_EOCD ) + if( c != sizeof( signature ) || signature == ZIP_HEADER_EOCD ) { Con_Reportf( S_WARN "%s has no files. Ignored.\n", zipfile ); @@ -789,9 +792,9 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) while ( filepos > 0 ) { lseek( zip->handle, filepos, SEEK_SET ); - read( zip->handle, &signature, sizeof( signature ) ); + c = read( zip->handle, &signature, sizeof( signature ) ); - if( signature == ZIP_HEADER_EOCD ) + if( c != sizeof( signature ) || signature == ZIP_HEADER_EOCD ) break; filepos -= sizeof( char ); // step back one byte @@ -808,19 +811,30 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) return NULL; } - read( zip->handle, &header_eocd, sizeof( zip_header_eocd_t ) ); + c = read( zip->handle, &header_eocd, sizeof( header_eocd ) ); + + if( c != sizeof( header_eocd )) + { + Con_Reportf( S_ERROR "invalid EOCD header in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + Zip_Close( zip ); + return NULL; + } // Move to CDF start lseek( zip->handle, header_eocd.central_directory_offset, SEEK_SET ); // Calc count of files in archive - info = (zipfile_t *)Mem_Calloc( fs_mempool, sizeof( zipfile_t ) * header_eocd.total_central_directory_record ); + info = (zipfile_t *)Mem_Calloc( fs_mempool, sizeof( *info ) * header_eocd.total_central_directory_record ); for( i = 0; i < header_eocd.total_central_directory_record; i++ ) { - read( zip->handle, &header_cdf, sizeof( header_cdf ) ); + c = read( zip->handle, &header_cdf, sizeof( header_cdf ) ); - if( header_cdf.signature != ZIP_HEADER_CDF ) + if( c != sizeof( header_cdf ) || header_cdf.signature != ZIP_HEADER_CDF ) { Con_Reportf( S_ERROR "CDF signature mismatch in %s. Zip file corrupted.\n", zipfile ); @@ -835,7 +849,20 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) if( header_cdf.uncompressed_size && header_cdf.filename_len && ( header_cdf.filename_len < MAX_SYSPATH ) ) { memset( &filename_buffer, '\0', MAX_SYSPATH ); - read( zip->handle, &filename_buffer, header_cdf.filename_len ); + c = read( zip->handle, &filename_buffer, header_cdf.filename_len ); + + if( c != header_cdf.filename_len ) + { + Con_Reportf( S_ERROR "filename length mismatch in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_CORRUPTED; + + Mem_Free( info ); + Zip_Close( zip ); + return NULL; + } + Q_strncpy( info[numpackfiles].name, filename_buffer, MAX_SYSPATH ); info[numpackfiles].size = header_cdf.uncompressed_size; @@ -859,7 +886,20 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) zip_header_t header; lseek( zip->handle, info[i].offset, SEEK_SET ); - read( zip->handle, &header, sizeof( header ) ); + c = read( zip->handle, &header, sizeof( header ) ); + + if( c != sizeof( header )) + { + Con_Reportf( S_ERROR "header length mismatch in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_CORRUPTED; + + Mem_Free( info ); + Zip_Close( zip ); + return NULL; + } + info[i].flags = header.compression_flags; info[i].offset = info[i].offset + header.filename_len + header.extrafield_len + sizeof( header ); } @@ -869,7 +909,7 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) zip->numfiles = numpackfiles; zip->files = info; - qsort( zip->files, zip->numfiles, sizeof( zipfile_t ), FS_SortZip ); + qsort( zip->files, zip->numfiles, sizeof( *zip->files ), FS_SortZip ); #ifdef XASH_REDUCE_FD // will reopen when needed @@ -907,6 +947,7 @@ static byte *Zip_LoadFile( const char *path, fs_offset_t *sizeptr, qboolean game int zlib_result = 0; dword test_crc, final_crc; z_stream decompress_stream; + size_t c; if( sizeptr ) *sizeptr = 0; @@ -936,7 +977,13 @@ static byte *Zip_LoadFile( const char *path, fs_offset_t *sizeptr, qboolean game decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); decompressed_buffer[file->size] = '\0'; - read( search->zip->handle, decompressed_buffer, file->size ); + c = read( search->zip->handle, decompressed_buffer, file->size ); + if( c != file->size ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s size doesn't match\n", file->name ); + return NULL; + } + #if 0 CRC32_Init( &test_crc ); CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); @@ -961,7 +1008,12 @@ static byte *Zip_LoadFile( const char *path, fs_offset_t *sizeptr, qboolean game decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); decompressed_buffer[file->size] = '\0'; - read( search->zip->handle, compressed_buffer, file->compressed_size ); + c = read( search->zip->handle, compressed_buffer, file->compressed_size ); + if( c != file->compressed_size ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s compressed size doesn't match\n", file->name ); + return NULL; + } memset( &decompress_stream, 0, sizeof( decompress_stream ) ); @@ -2114,8 +2166,8 @@ void FS_Init( void ) for( i = 0; i < dirs.numstrings; i++ ) { - const char *roPath = va( "%s" PATH_SPLITTER "%s", host.rodir, dirs.strings[i] ); - const char *rwPath = va( "%s" PATH_SPLITTER "%s", host.rootdir, dirs.strings[i] ); + char *roPath = va( "%s" PATH_SPLITTER "%s", host.rodir, dirs.strings[i] ); + char *rwPath = va( "%s" PATH_SPLITTER "%s", host.rootdir, dirs.strings[i] ); // check if it's a directory if( !FS_SysFolderExists( roPath )) From 764ef939843b6bf6d3b5c09d5bfcf18b7484c80e Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 23 Dec 2021 01:21:33 +0300 Subject: [PATCH 015/548] common: add fs_size_t typedef, for read()/write() return value --- common/port.h | 1 + common/xash3d_types.h | 5 +++++ engine/common/filesystem.c | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/common/port.h b/common/port.h index 183f1fe7..9b6317ce 100644 --- a/common/port.h +++ b/common/port.h @@ -137,6 +137,7 @@ GNU General Public License for more details. #define HAVE_DUP #endif //WIN32 + #ifndef XASH_LOW_MEMORY #define XASH_LOW_MEMORY 0 #endif diff --git a/common/xash3d_types.h b/common/xash3d_types.h index e1d1a0ae..809da0ad 100644 --- a/common/xash3d_types.h +++ b/common/xash3d_types.h @@ -117,6 +117,11 @@ typedef struct file_s file_t; // normal file typedef struct wfile_s wfile_t; // wad file typedef struct stream_s stream_t; // sound stream for background music playing typedef off_t fs_offset_t; +#if XASH_WIN32 +typedef int fs_size_t; // return type of _read, _write funcs +#else /* !XASH_WIN32 */ +typedef ssize_t fs_size_t; +#endif /* !XASH_WIN32 */ typedef struct dllfunc_s { diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index 51e03f04..ff1139c3 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -606,7 +606,7 @@ pack_t *FS_LoadPackPAK( const char *packfile, int *error ) int i, numpackfiles; pack_t *pack; dpackfile_t *info; - ssize_t c; + fs_size_t c; packhandle = open( packfile, O_RDONLY|O_BINARY ); @@ -722,7 +722,7 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) zipfile_t *info = NULL; char filename_buffer[MAX_SYSPATH]; zip_t *zip = (zip_t *)Mem_Calloc( fs_mempool, sizeof( *zip )); - ssize_t c; + fs_size_t c; zip->handle = open( zipfile, O_RDONLY|O_BINARY ); From 2df27e14a835be445a86fcbd7e2cf5982afbbbdd Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 23 Dec 2021 01:25:03 +0300 Subject: [PATCH 016/548] engine: client: make connect command available through stufftext, for future use --- engine/client/cl_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index b3f8f14d..0cc2790a 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -2921,7 +2921,7 @@ void CL_InitLocal( void ) Cmd_AddCommand ("levelshot", CL_LevelShot_f, "same as \"screenshot\", used for create plaque images" ); Cmd_AddCommand ("saveshot", CL_SaveShot_f, "used for create save previews with LoadGame menu" ); - Cmd_AddRestrictedCommand ("connect", CL_Connect_f, "connect to a server by hostname" ); + Cmd_AddCommand ("connect", CL_Connect_f, "connect to a server by hostname" ); Cmd_AddCommand ("reconnect", CL_Reconnect_f, "reconnect to current level" ); Cmd_AddCommand ("rcon", CL_Rcon_f, "sends a command to the server console (rcon_password and rcon_address required)" ); From f0feb1dfbf9a9d5f62f8f19ccb744c0d5acac8ed Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 23 Dec 2021 18:34:46 +0300 Subject: [PATCH 017/548] ref: remove predictable random tiling textures --- ref_gl/gl_rsurf.c | 3 --- ref_soft/r_surf.c | 3 --- 2 files changed, 6 deletions(-) diff --git a/ref_gl/gl_rsurf.c b/ref_gl/gl_rsurf.c index c720feff..297296d1 100644 --- a/ref_gl/gl_rsurf.c +++ b/ref_gl/gl_rsurf.c @@ -3630,9 +3630,6 @@ void GL_InitRandomTable( void ) { int tu, tv; - // make random predictable - gEngfuncs.COM_SetRandomSeed( 255 ); - for( tu = 0; tu < MOD_FRAMES; tu++ ) { for( tv = 0; tv < MOD_FRAMES; tv++ ) diff --git a/ref_soft/r_surf.c b/ref_soft/r_surf.c index d42db305..e1d44681 100644 --- a/ref_soft/r_surf.c +++ b/ref_soft/r_surf.c @@ -357,9 +357,6 @@ void R_InitRandomTable( void ) { int tu, tv; - // make random predictable - gEngfuncs.COM_SetRandomSeed( 255 ); - for( tu = 0; tu < MOD_FRAMES; tu++ ) { for( tv = 0; tv < MOD_FRAMES; tv++ ) From fb8791529efccf82a54065921bf1dce1f27ac247 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 23 Dec 2021 18:46:40 +0300 Subject: [PATCH 018/548] common: include build.h in xash3d_types.h --- common/xash3d_types.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/xash3d_types.h b/common/xash3d_types.h index 809da0ad..7ed4ab91 100644 --- a/common/xash3d_types.h +++ b/common/xash3d_types.h @@ -2,7 +2,9 @@ #ifndef XASH_TYPES_H #define XASH_TYPES_H -#ifdef _WIN32 +#include "build.h" + +#if XASH_WIN32 #include // off_t #endif // _WIN32 From 4f89288ccf5a3f84ebf30de2cb57267b90c1f3b2 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 23 Dec 2021 19:17:11 +0300 Subject: [PATCH 019/548] common: cleanup port.h --- common/port.h | 83 ++++++--------------------- engine/client/cl_main.c | 6 +- engine/client/cl_parse.c | 2 +- engine/client/cl_render.c | 2 +- engine/client/cl_tent.c | 4 +- engine/client/console.c | 2 +- engine/client/s_dsp.c | 2 +- engine/client/s_mix.c | 2 +- engine/common/filesystem.c | 4 +- engine/common/imagelib/img_utils.c | 4 +- engine/common/lib_common.c | 6 +- engine/common/system.c | 2 +- engine/platform/android/lib_android.c | 2 +- engine/platform/posix/lib_posix.c | 3 +- engine/platform/win32/con_win.c | 2 +- engine/server/sv_filter.c | 2 +- public/crtlib.c | 3 +- public/xash3d_mathlib.h | 3 +- ref_gl/gl_backend.c | 6 +- ref_gl/gl_decals.c | 4 +- ref_gl/gl_image.c | 2 +- ref_gl/gl_rmain.c | 2 +- ref_gl/gl_rsurf.c | 4 +- ref_soft/r_decals.c | 4 +- ref_soft/r_main.c | 2 +- 25 files changed, 55 insertions(+), 103 deletions(-) diff --git a/common/port.h b/common/port.h index 9b6317ce..435de115 100644 --- a/common/port.h +++ b/common/port.h @@ -20,8 +20,6 @@ GNU General Public License for more details. #include "build.h" #if !XASH_WIN32 - - #if XASH_APPLE #include #define OS_LIB_EXT "dylib" @@ -30,67 +28,38 @@ GNU General Public License for more details. #define OS_LIB_EXT "so" #define OPEN_COMMAND "xdg-open" #endif - #define OS_LIB_PREFIX "lib" - - #if XASH_ANDROID - //#if defined(LOAD_HARDFP) - // #define POSTFIX "_hardfp" - //#else - #define POSTFIX - //#endif - #else - #endif - #define VGUI_SUPPORT_DLL "libvgui_support." OS_LIB_EXT // Windows-specific #define __cdecl #define __stdcall - #define _inline static inline #define FORCEINLINE inline __attribute__((always_inline)) -#if XASH_POSIX - #define PATH_SPLITTER "/" - #include - #include - #define O_BINARY 0 // O_BINARY is Windows extension - #define O_TEXT 0 // O_TEXT is Windows extension - // Windows functions to posix equivalent - #define _mkdir( x ) mkdir( x, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) - #define LoadLibrary( x ) dlopen( x, RTLD_NOW ) - #define GetProcAddress( x, y ) dlsym( x, y ) - #define FreeLibrary( x ) dlclose( x ) - #define tell( a ) lseek(a, 0, SEEK_CUR) - #define HAVE_DUP -#endif + #if XASH_POSIX + #include + #include -#if XASH_DOS4GW - #define PATH_SPLITTER "\\" - #define LoadLibrary( x ) (0) - #define GetProcAddress( x, y ) (0) - #define FreeLibrary( x ) (0) -#endif - //#define MAKEWORD( a, b ) ((short int)(((unsigned char)(a))|(((short int)((unsigned char)(b)))<<8))) - #define max( a, b ) (((a) > (b)) ? (a) : (b)) - #define min( a, b ) (((a) < (b)) ? (a) : (b)) + #define PATH_SPLITTER "/" + #define HAVE_DUP - /// TODO: check if we may clean this defines, it should not appear in non-platform code! - typedef unsigned char BYTE; - typedef short int WORD; - typedef unsigned int DWORD; - typedef int LONG; - typedef unsigned int ULONG; - typedef int WPARAM; - typedef unsigned int LPARAM; + #define O_BINARY 0 + #define O_TEXT 0 + #define _mkdir( x ) mkdir( x, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) + #define LoadLibrary( x ) dlopen( x, RTLD_NOW ) + #define GetProcAddress( x, y ) dlsym( x, y ) + #define FreeLibrary( x ) dlclose( x ) + #elif XASH_DOS4GW + #define PATH_SPLITTER "\\" + #define LoadLibrary( x ) (0) + #define GetProcAddress( x, y ) (0) + #define FreeLibrary( x ) (0) + #endif typedef void* HANDLE; - typedef void* HMODULE; typedef void* HINSTANCE; - typedef char* LPSTR; - typedef struct tagPOINT { int x, y; @@ -108,19 +77,6 @@ GNU General Public License for more details. #define read _read #define alloca _alloca - // shut-up compiler warnings - #pragma warning(disable : 4244) // MIPS - #pragma warning(disable : 4018) // signed/unsigned mismatch - #pragma warning(disable : 4305) // truncation from const double to float - #pragma warning(disable : 4115) // named type definition in parentheses - #pragma warning(disable : 4100) // unreferenced formal parameter - #pragma warning(disable : 4127) // conditional expression is constant - #pragma warning(disable : 4057) // differs in indirection to slightly different base types - #pragma warning(disable : 4201) // nonstandard extension used - #pragma warning(disable : 4706) // assignment within conditional expression - #pragma warning(disable : 4054) // type cast' : from function pointer - #pragma warning(disable : 4310) // cast truncates constant value - #define HSPRITE WINAPI_HSPRITE #define WIN32_LEAN_AND_MEAN #include @@ -130,12 +86,7 @@ GNU General Public License for more details. #define OS_LIB_PREFIX "" #define OS_LIB_EXT "dll" #define VGUI_SUPPORT_DLL "../vgui_support." OS_LIB_EXT - #ifdef XASH_64BIT - // windows NameForFunction not implemented yet - #define XASH_ALLOW_SAVERESTORE_OFFSETS - #endif #define HAVE_DUP - #endif //WIN32 #ifndef XASH_LOW_MEMORY diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 0cc2790a..c4a3b9f9 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -243,7 +243,7 @@ void CL_SignonReply( void ) float CL_LerpInterval( void ) { - return max( cl_interp->value, 1.f / cl_updaterate->value ); + return Q_max( cl_interp->value, 1.f / cl_updaterate->value ); } /* @@ -296,7 +296,7 @@ static float CL_LerpPoint( void ) if( cl_interp->value > 0.001f ) { // manual lerp value (goldsrc mode) - float td = max( 0.f, cl.time - cl.mtime[0] ); + float td = Q_max( 0.f, cl.time - cl.mtime[0] ); frac = td / CL_LerpInterval(); } else if( server_frametime > 0.001f ) @@ -528,7 +528,7 @@ qboolean CL_ProcessShowTexturesCmds( usercmd_t *cmd ) if( released & ( IN_RIGHT|IN_MOVERIGHT )) Cvar_SetValue( "r_showtextures", gl_showtextures->value + 1 ); if( released & ( IN_LEFT|IN_MOVELEFT )) - Cvar_SetValue( "r_showtextures", max( 1, gl_showtextures->value - 1 )); + Cvar_SetValue( "r_showtextures", Q_max( 1, gl_showtextures->value - 1 )); oldbuttons = cmd->buttons; return true; diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 997edebc..24dc19d7 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -1819,7 +1819,7 @@ void CL_ParseScreenShake( sizebuf_t *msg ) clgame.shake.amplitude = (float)(word)MSG_ReadShort( msg ) * (1.0f / (float)(1<<12)); clgame.shake.duration = (float)(word)MSG_ReadShort( msg ) * (1.0f / (float)(1<<12)); clgame.shake.frequency = (float)(word)MSG_ReadShort( msg ) * (1.0f / (float)(1<<8)); - clgame.shake.time = cl.time + max( clgame.shake.duration, 0.01f ); + clgame.shake.time = cl.time + Q_max( clgame.shake.duration, 0.01f ); clgame.shake.next_shake = 0.0f; // apply immediately } diff --git a/engine/client/cl_render.c b/engine/client/cl_render.c index 88a25f69..9af5aec7 100644 --- a/engine/client/cl_render.c +++ b/engine/client/cl_render.c @@ -117,7 +117,7 @@ static void R_EnvShot( const float *vieworg, const char *name, qboolean skyshot, else cls.scrshot_action = scrshot_envshot; // catch negative values - cls.envshot_viewsize = max( 0, shotsize ); + cls.envshot_viewsize = Q_max( 0, shotsize ); } /* diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c index 6c9aeb47..89d74aa5 100644 --- a/engine/client/cl_tent.c +++ b/engine/client/cl_tent.c @@ -361,8 +361,8 @@ void CL_TempEntPlaySound( TEMPENTITY *pTemp, float damp ) sound_t handle; if( isshellcasing ) - fvol *= min ( 1.0f, ((float)zvel) / 350.0f ); - else fvol *= min ( 1.0f, ((float)zvel) / 450.0f ); + fvol *= Q_min( 1.0f, ((float)zvel) / 350.0f ); + else fvol *= Q_min( 1.0f, ((float)zvel) / 450.0f ); if( !COM_RandomLong( 0, 3 ) && !isshellcasing ) pitch = COM_RandomLong( 95, 105 ); diff --git a/engine/client/console.c b/engine/client/console.c index c908972e..34fbce4f 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -2172,7 +2172,7 @@ void Con_DrawSolidConsole( int lines ) Con_DrawInput( lines ); y = lines - ( con.curFont->charHeight * 1.2f ); - SCR_DrawFPS( max( y, 4 )); // to avoid to hide fps counter + SCR_DrawFPS( Q_max( y, 4 )); // to avoid to hide fps counter ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); } diff --git a/engine/client/s_dsp.c b/engine/client/s_dsp.c index 39a54d4d..4d57675c 100644 --- a/engine/client/s_dsp.c +++ b/engine/client/s_dsp.c @@ -482,7 +482,7 @@ void DLY_CheckNewDelayVal( void ) } else { - delay = min( delay, MAX_MONO_DELAY ); + delay = Q_min( delay, MAX_MONO_DELAY ); dly->delaysamples = (int)(delay * idsp_dma_speed) << sxhires; // init dly diff --git a/engine/client/s_mix.c b/engine/client/s_mix.c index f79f7aee..8cceac98 100644 --- a/engine/client/s_mix.c +++ b/engine/client/s_mix.c @@ -809,7 +809,7 @@ void S_MixBufferUpsample2x( int count, portable_samplepair_t *pbuffer, portable_ // zero out all paintbuffers void MIX_ClearAllPaintBuffers( int SampleCount, qboolean clearFilters ) { - int count = min( SampleCount, PAINTBUFFER_SIZE ); + int count = Q_min( SampleCount, PAINTBUFFER_SIZE ); int i; // zero out all paintbuffer data (ignore sampleCount) diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index ff1139c3..e3c3e402 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -3649,8 +3649,8 @@ search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) slash = Q_strrchr( pattern, '/' ); backslash = Q_strrchr( pattern, '\\' ); colon = Q_strrchr( pattern, ':' ); - separator = max( slash, backslash ); - separator = max( separator, colon ); + separator = Q_max( slash, backslash ); + separator = Q_max( separator, colon ); basepathlength = separator ? (separator + 1 - pattern) : 0; basepath = Mem_Calloc( fs_mempool, basepathlength + 1 ); if( basepathlength ) memcpy( basepath, pattern, basepathlength ); diff --git a/engine/common/imagelib/img_utils.c b/engine/common/imagelib/img_utils.c index 49976277..5ff568cf 100644 --- a/engine/common/imagelib/img_utils.c +++ b/engine/common/imagelib/img_utils.c @@ -456,8 +456,8 @@ void Image_PaletteHueReplace( byte *palSrc, int newHue, int start, int end, int g = palSrc[i*pal_size+1]; b = palSrc[i*pal_size+2]; - maxcol = max( max( r, g ), b ) / 255.0f; - mincol = min( min( r, g ), b ) / 255.0f; + maxcol = Q_max( Q_max( r, g ), b ) / 255.0f; + mincol = Q_min( Q_min( r, g ), b ) / 255.0f; if( maxcol == 0 ) continue; diff --git a/engine/common/lib_common.c b/engine/common/lib_common.c index 9fd9e31e..e18161db 100644 --- a/engine/common/lib_common.c +++ b/engine/common/lib_common.c @@ -291,12 +291,12 @@ static char *COM_GetItaniumName( const char * const in_name ) len = len * 10 + ( *f - '0' ); // sane value - len = min( remaining, len ); + len = Q_min( remaining, len ); if( len == 0 ) goto invalid_format; - Q_strncpy( symbols[i], f, min( len + 1, sizeof( out_name ))); + Q_strncpy( symbols[i], f, Q_min( len + 1, sizeof( out_name ))); f += len; remaining -= len; @@ -361,7 +361,7 @@ char **COM_ConvertToLocalPlatform( EFunctionMangleType to, const char *from, siz if( at ) len = (uint)( at - prev ); else len = (uint)Q_strlen( prev ); - Q_strncpy( symbols[i], prev, min( len + 1, sizeof( symbols[i] ))); + Q_strncpy( symbols[i], prev, Q_min( len + 1, sizeof( symbols[i] ))); prev = at + 1; if( !at ) diff --git a/engine/common/system.c b/engine/common/system.c index ff62096a..b2a0a659 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -107,7 +107,7 @@ void Sys_Sleep( int msec ) if( !msec ) return; - msec = min( msec, 1000 ); + msec = Q_min( msec, 1000 ); Platform_Sleep( msec ); } diff --git a/engine/platform/android/lib_android.c b/engine/platform/android/lib_android.c index 143552d7..506c4e70 100644 --- a/engine/platform/android/lib_android.c +++ b/engine/platform/android/lib_android.c @@ -34,7 +34,7 @@ void *ANDROID_LoadLibrary( const char *dllname ) if( !libdir[i] ) continue; - Q_snprintf( path, MAX_SYSPATH, "%s/lib%s"POSTFIX"."OS_LIB_EXT, libdir[i], dllname ); + Q_snprintf( path, MAX_SYSPATH, "%s/lib%s."OS_LIB_EXT, libdir[i], dllname ); pHandle = dlopen( path, RTLD_LAZY ); if( pHandle ) return pHandle; diff --git a/engine/platform/posix/lib_posix.c b/engine/platform/posix/lib_posix.c index 9e6af461..c4026706 100644 --- a/engine/platform/posix/lib_posix.c +++ b/engine/platform/posix/lib_posix.c @@ -15,8 +15,7 @@ GNU General Public License for more details. #define _GNU_SOURCE #include "platform/platform.h" #if XASH_LIB == LIB_POSIX - - +#include #include "common.h" #include "library.h" #include "filesystem.h" diff --git a/engine/platform/win32/con_win.c b/engine/platform/win32/con_win.c index 1605c9f8..74d5a0a5 100644 --- a/engine/platform/win32/con_win.c +++ b/engine/platform/win32/con_win.c @@ -349,7 +349,7 @@ void Wcon_CreateConsole( void ) // create the scrollbuffer GetClientRect( s_wcd.hWnd, &rect ); - s_wcd.hwndBuffer = CreateWindowEx( WS_EX_DLGMODALFRAME|WS_EX_CLIENTEDGE, "edit", NULL, CONSTYLE, 0, 0, rect.right - rect.left, min(365, rect.bottom), s_wcd.hWnd, (HMENU)EDIT_ID, host.hInst, NULL ); + s_wcd.hwndBuffer = CreateWindowEx( WS_EX_DLGMODALFRAME|WS_EX_CLIENTEDGE, "edit", NULL, CONSTYLE, 0, 0, rect.right - rect.left, Q_min(365, rect.bottom), s_wcd.hWnd, (HMENU)EDIT_ID, host.hInst, NULL ); SendMessage( s_wcd.hwndBuffer, WM_SETFONT, (WPARAM)s_wcd.hfBufferFont, 0 ); if( host.type == HOST_DEDICATED ) diff --git a/engine/server/sv_filter.c b/engine/server/sv_filter.c index b36908b2..fa7164a0 100644 --- a/engine/server/sv_filter.c +++ b/engine/server/sv_filter.c @@ -101,7 +101,7 @@ qboolean SV_CheckID( const char *id ) for( filter = cidfilter; filter; filter = filter->next ) { int len1 = Q_strlen( id ), len2 = Q_strlen( filter->id ); - int len = min( len1, len2 ); + int len = Q_min( len1, len2 ); while( filter->endTime && host.realtime > filter->endTime ) { diff --git a/public/crtlib.c b/public/crtlib.c index 9aca2cf6..e92e2d82 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -21,6 +21,7 @@ GNU General Public License for more details. #include #include "stdio.h" #include "crtlib.h" +#include "xash3d_mathlib.h" void Q_strnupr( const char *in, char *out, size_t size_out ) { @@ -651,7 +652,7 @@ char *Q_pretifymem( float value, int digitsafterdecimal ) else Q_sprintf( suffix, " bytes" ); // clamp to >= 0 - digitsafterdecimal = max( digitsafterdecimal, 0 ); + digitsafterdecimal = Q_max( digitsafterdecimal, 0 ); // if it's basically integral, don't do any decimals if( fabs( value - (int)value ) < 0.00001f ) diff --git a/public/xash3d_mathlib.h b/public/xash3d_mathlib.h index 4e37788b..b2ae966f 100644 --- a/public/xash3d_mathlib.h +++ b/public/xash3d_mathlib.h @@ -22,6 +22,7 @@ GNU General Public License for more details. #endif #include "build.h" +#include "com_model.h" #ifdef XASH_MSVC #pragma warning(disable : 4201) // nonstandard extension used @@ -104,7 +105,7 @@ GNU General Public License for more details. #define VectorScale(in, scale, out) ((out)[0] = (in)[0] * (scale),(out)[1] = (in)[1] * (scale),(out)[2] = (in)[2] * (scale)) #define VectorCompare(v1,v2) ((v1)[0]==(v2)[0] && (v1)[1]==(v2)[1] && (v1)[2]==(v2)[2]) #define VectorDivide( in, d, out ) VectorScale( in, (1.0f / (d)), out ) -#define VectorMax(a) ( max((a)[0], max((a)[1], (a)[2])) ) +#define VectorMax(a) ( Q_max((a)[0], Q_max((a)[1], (a)[2])) ) #define VectorAvg(a) ( ((a)[0] + (a)[1] + (a)[2]) / 3 ) #define VectorLength(a) ( sqrt( DotProduct( a, a ))) #define VectorLength2(a) (DotProduct( a, a )) diff --git a/ref_gl/gl_backend.c b/ref_gl/gl_backend.c index ffc56ab0..88881f9f 100644 --- a/ref_gl/gl_backend.c +++ b/ref_gl/gl_backend.c @@ -300,7 +300,7 @@ GL_TexGen */ void GL_TexGen( GLenum coord, GLenum mode ) { - int tmu = min( glConfig.max_texture_coords, glState.activeTMU ); + int tmu = Q_min( glConfig.max_texture_coords, glState.activeTMU ); int bit, gen; switch( coord ) @@ -350,7 +350,7 @@ GL_SetTexCoordArrayMode */ void GL_SetTexCoordArrayMode( GLenum mode ) { - int tmu = min( glConfig.max_texture_coords, glState.activeTMU ); + int tmu = Q_min( glConfig.max_texture_coords, glState.activeTMU ); int bit, cmode = glState.texCoordArrayMode[tmu]; if( mode == GL_TEXTURE_COORD_ARRAY ) @@ -656,7 +656,7 @@ rebuild_page: if( i == MAX_TEXTURES && gl_showtextures->value != 1 ) { // bad case, rewind to one and try again - gEngfuncs.Cvar_SetValue( "r_showtextures", max( 1, gl_showtextures->value - 1 )); + gEngfuncs.Cvar_SetValue( "r_showtextures", Q_max( 1, gl_showtextures->value - 1 )); if( ++numTries < 2 ) goto rebuild_page; // to prevent infinite loop } diff --git a/ref_gl/gl_decals.c b/ref_gl/gl_decals.c index 2266df1e..c9e70983 100644 --- a/ref_gl/gl_decals.c +++ b/ref_gl/gl_decals.c @@ -476,8 +476,8 @@ static decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int // Now figure out the part of the projection that intersects pDecal's // clip box [0,0,1,1]. - Vector2Set( vUnionMin, max( vDecalMin[0], 0 ), max( vDecalMin[1], 0 )); - Vector2Set( vUnionMax, min( vDecalMax[0], 1 ), min( vDecalMax[1], 1 )); + Vector2Set( vUnionMin, Q_max( vDecalMin[0], 0 ), Q_max( vDecalMin[1], 0 )); + Vector2Set( vUnionMax, Q_min( vDecalMax[0], 1 ), Q_min( vDecalMax[1], 1 )); if( vUnionMin[0] < 1 && vUnionMin[1] < 1 && vUnionMax[0] > 0 && vUnionMax[1] > 0 ) { diff --git a/ref_gl/gl_image.c b/ref_gl/gl_image.c index 643325d9..ee006a5a 100644 --- a/ref_gl/gl_image.c +++ b/ref_gl/gl_image.c @@ -1620,7 +1620,7 @@ int GL_LoadTextureArray( const char **names, int flags ) mipsize = srcsize = dstsize = 0; - for( j = 0; j < max( 1, pic->numMips ); j++ ) + for( j = 0; j < Q_max( 1, pic->numMips ); j++ ) { int width = Q_max( 1, ( pic->width >> j )); int height = Q_max( 1, ( pic->height >> j )); diff --git a/ref_gl/gl_rmain.c b/ref_gl/gl_rmain.c index 664bbcc0..21eabbdc 100644 --- a/ref_gl/gl_rmain.c +++ b/ref_gl/gl_rmain.c @@ -379,7 +379,7 @@ static void R_SetupProjectionMatrix( matrix4x4 m ) RI.farClip = R_GetFarClip(); zNear = 4.0f; - zFar = max( 256.0f, RI.farClip ); + zFar = Q_max( 256.0f, RI.farClip ); yMax = zNear * tan( RI.fov_y * M_PI_F / 360.0f ); yMin = -yMax; diff --git a/ref_gl/gl_rsurf.c b/ref_gl/gl_rsurf.c index 297296d1..d9f3d4ae 100644 --- a/ref_gl/gl_rsurf.c +++ b/ref_gl/gl_rsurf.c @@ -2934,8 +2934,8 @@ static qboolean R_CheckLightMap( msurface_t *fa ) } else { - smax = min( smax, 132 ); - tmax = min( tmax, 132 ); + smax = Q_min( smax, 132 ); + tmax = Q_min( tmax, 132 ); //Host_MapDesignError( "R_RenderBrushPoly: bad surface extents: %d %d", fa->extents[0], fa->extents[1] ); memset( temp, 255, sizeof( temp ) ); } diff --git a/ref_soft/r_decals.c b/ref_soft/r_decals.c index 04370cd8..49fa6111 100644 --- a/ref_soft/r_decals.c +++ b/ref_soft/r_decals.c @@ -480,8 +480,8 @@ static decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int // Now figure out the part of the projection that intersects pDecal's // clip box [0,0,1,1]. - Vector2Set( vUnionMin, max( vDecalMin[0], 0 ), max( vDecalMin[1], 0 )); - Vector2Set( vUnionMax, min( vDecalMax[0], 1 ), min( vDecalMax[1], 1 )); + Vector2Set( vUnionMin, Q_max( vDecalMin[0], 0 ), Q_max( vDecalMin[1], 0 )); + Vector2Set( vUnionMax, Q_min( vDecalMax[0], 1 ), Q_min( vDecalMax[1], 1 )); if( vUnionMin[0] < 1 && vUnionMin[1] < 1 && vUnionMax[0] > 0 && vUnionMax[1] > 0 ) { diff --git a/ref_soft/r_main.c b/ref_soft/r_main.c index 829304ef..96a1d160 100644 --- a/ref_soft/r_main.c +++ b/ref_soft/r_main.c @@ -496,7 +496,7 @@ static void R_SetupProjectionMatrix( matrix4x4 m ) RI.farClip = R_GetFarClip(); zNear = 4.0f; - zFar = max( 256.0f, RI.farClip ); + zFar = Q_max( 256.0f, RI.farClip ); yMax = zNear * tan( RI.fov_y * M_PI_F / 360.0f ); yMin = -yMax; From f3dfbc92195cc8352285437c26401583ef2b1c1f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 24 Dec 2021 18:12:30 +0300 Subject: [PATCH 020/548] mainui: update --- mainui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainui b/mainui index 330b16fb..e1b85a2d 160000 --- a/mainui +++ b/mainui @@ -1 +1 @@ -Subproject commit 330b16fb29cfe0915047db9e2a374777e6b513ec +Subproject commit e1b85a2d205a18954276884259ea46a8010f7734 From 32fa3ab7deaaf50f23ec1b73cb7d7b37815756c4 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Fri, 24 Dec 2021 12:50:38 -0800 Subject: [PATCH 021/548] rtx: better surface light triangle selection - Enumerate all triangles and pick random one based on solid angle/expected contribution - Sample N(4) of surface lights per pixel/frame. This is needed for performance reasons. Some surface lights have way too many triangles and enumerating them takes ages. - Write emissive color directly on primary ray hit (still looks too bright ;_;) Known issues: - changes above make it really apparent that light clusters are subtly broken (see test_brush3, etc) - triangle contribution sometimes breaks at axis aligned plane intersecting one of the vertices (supposedly the solid angle is broken?) - performance is bad --- ref_vk/shaders/ray.rchit | 7 +- ref_vk/shaders/ray.rgen | 136 ++++++++++++++++++++++++++------------- 2 files changed, 95 insertions(+), 48 deletions(-) diff --git a/ref_vk/shaders/ray.rchit b/ref_vk/shaders/ray.rchit index 1f39a6c9..4f3104ad 100644 --- a/ref_vk/shaders/ray.rchit +++ b/ref_vk/shaders/ray.rchit @@ -164,10 +164,11 @@ void main() { payload.emissive = vec3(0.); if (any(greaterThan(kusok.emissive, vec3(0.)))) { - const vec3 emissive_color = base_color; + //const vec3 emissive_color = base_color; //const vec3 emissive_color = pow(base_color, vec3(2.2)); //const float max_color = max(max(emissive_color.r, emissive_color.g), emissive_color.b); - payload.emissive = normalize(kusok.emissive) * emissive_color;// * mix(vec3(1.), kusok.emissive, smoothstep(.3, .6, max_color)); + //payload.emissive = normalize(kusok.emissive) * emissive_color;// * mix(vec3(1.), kusok.emissive, smoothstep(.3, .6, max_color)); + payload.emissive = kusok.emissive * base_color; } payload.kusok_index = kusok_index; @@ -179,5 +180,5 @@ void main() { T = baryMix(vertices[vi1].tangent, vertices[vi2].tangent, vertices[vi3].tangent, bary); T = normalize(normalTransformMat * T); - payload.debug = vec4(T, 0.); + payload.debug = vec4(bary, 0., 0.); } diff --git a/ref_vk/shaders/ray.rgen b/ref_vk/shaders/ray.rgen index 5af6d8d3..c173c330 100644 --- a/ref_vk/shaders/ray.rgen +++ b/ref_vk/shaders/ray.rgen @@ -111,6 +111,7 @@ float triangleSolidAngle(vec3 p, vec3 a, vec3 b, vec3 c) { b = normalize(b - p); c = normalize(c - p); + // TODO horizon culling const float tanHalfOmega = dot(a, cross(b,c)) / (1. + dot(b,c) + dot(c,a) + dot(a,b)); return atan(tanHalfOmega) * 2.; @@ -124,9 +125,23 @@ vec2 baryMix(vec2 v1, vec2 v2, vec2 v3, vec2 bary) { return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; } +float computeTriangleContrib(uint triangle_index, uint index_offset, uint vertex_offset, mat4x3 xform, vec3 pos) { + const uint first_index_offset = index_offset + triangle_index * 3; + + const uint vi1 = uint(indices[first_index_offset+0]) + vertex_offset; + const uint vi2 = uint(indices[first_index_offset+1]) + vertex_offset; + const uint vi3 = uint(indices[first_index_offset+2]) + vertex_offset; + + const vec3 v1 = (xform * vec4(vertices[vi1].pos, 1.)).xyz; + const vec3 v2 = (xform * vec4(vertices[vi2].pos, 1.)).xyz; + const vec3 v3 = (xform * vec4(vertices[vi3].pos, 1.)).xyz; + + return triangleSolidAngle(pos, v1, v2, v3) / TWO_PI; +} + void sampleSurfaceTriangle( vec3 color, vec3 view_dir, MaterialProperties material /* TODO BrdfData instead is supposedly more efficient */, - mat4x3 emissive_transform, mat3 emissive_transform_normal, + mat4x3 emissive_transform, uint triangle_index, uint index_offset, uint vertex_offset, uint kusok_index, out vec3 diffuse, out vec3 specular) @@ -158,23 +173,14 @@ void sampleSurfaceTriangle( return; #endif - // Consider area light sources as planes, take the first normal - const vec3 normal = normalize(emissive_transform_normal * vertices[vi1].normal); - const float light_dot = -dot(light_dir, normal); - if (light_dot <= 0.) -#ifdef DEBUG_LIGHT_CULLING - return vec3(1., 0., 0.) * color_factor; -#else - return; -#endif - - // TODO emissive normals and areas can be precomputed - const float area = 1.;//.5 * length(cross(v1 - v2, v1 - v3)); const float light_dist2 = dot(light_dir, light_dir); - //float pdf = /*light_dist2 */ 1./ (area * light_dot); float pdf = TWO_PI / triangleSolidAngle(payload_opaque.hit_pos_t.xyz, v1, v2, v3); + // Cull back facing + if (pdf <= 0.) + return; + if (pdf > pdf_culling_threshold) #ifdef DEBUG_LIGHT_CULLING return vec3(0., 1., 0.) * color_factor; @@ -182,7 +188,7 @@ void sampleSurfaceTriangle( return; #endif -#if 0 +#if 1 { const uint tex_index = kusochki[kusok_index].tex_base_color; if ((KUSOK_MATERIAL_FLAG_SKYBOX & tex_index) == 0) { @@ -224,6 +230,62 @@ void sampleSurfaceTriangle( if (shadowed(payload_opaque.hit_pos_t.xyz, light_dir, sqrt(light_dist2))) { diffuse = specular = vec3(0.); } + + if (pdf < 0.) { //any(lessThan(diffuse, vec3(0.)))) { + diffuse = vec3(1., 0., 0.); + } +} + +void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties material, uint ekusok_index, out vec3 out_diffuse, out vec3 out_specular) { + const EmissiveKusok ek = lights.kusochki[ekusok_index]; + const uint emissive_kusok_index = lights.kusochki[ekusok_index].kusok_index; + const Kusok kusok = kusochki[emissive_kusok_index]; + + // TODO streamline matrices layouts + const mat4x3 emissive_transform = mat4x3( + vec3(ek.tx_row_x.x, ek.tx_row_y.x, ek.tx_row_z.x), + vec3(ek.tx_row_x.y, ek.tx_row_y.y, ek.tx_row_z.y), + vec3(ek.tx_row_x.z, ek.tx_row_y.z, ek.tx_row_z.z), + vec3(ek.tx_row_x.w, ek.tx_row_y.w, ek.tx_row_z.w) + ); + + if (emissive_kusok_index == uint(payload_opaque.kusok_index)) + return; + + // Taken from Ray Tracing Gems II, ch.47, p.776, listing 47-2 + int selected = -1; + float selected_contrib = 0.; + float total_contrib = 0.; + float eps1 = rand01(); + + for (uint i = 0; i < kusok.triangles; ++i) { + const float tri_contrib = computeTriangleContrib(i, kusok.index_offset, kusok.vertex_offset, emissive_transform, payload_opaque.hit_pos_t.xyz); + + if (tri_contrib <= 0.) + continue; + + const float tau = total_contrib / (total_contrib + tri_contrib); + total_contrib += tri_contrib; + + if (eps1 < tau) { + eps1 /= tau; + } else { + selected = int(i); + selected_contrib = tri_contrib; + eps1 = (eps1 - tau) / (1. - tau); + } + +#define MAX_BELOW_ONE .99999 // FIXME what's the correct way to do this + eps1 = clamp(eps1, 0., MAX_BELOW_ONE); // Numerical stability (?) + } + + if (selected >= 0) { + sampleSurfaceTriangle(throughput * ek.emissive, view_dir, material, emissive_transform, selected, kusok.index_offset, kusok.vertex_offset, emissive_kusok_index, out_diffuse, out_specular); + + const float tri_factor = total_contrib / selected_contrib; + out_diffuse *= tri_factor; + out_specular *= tri_factor; + } } void computePointLights(uint cluster_index, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { @@ -332,7 +394,7 @@ void computeLighting(vec3 throughput, vec3 view_dir, MaterialProperties material const uint num_emissive_kusochki = uint(light_grid.clusters[cluster_index].num_emissive_surfaces); float sampling_light_scale = 1.; -#if 0 +#if 1 const uint max_lights_per_frame = 4; uint begin_i = 0, end_i = num_emissive_kusochki; if (end_i > max_lights_per_frame) { @@ -352,31 +414,14 @@ void computeLighting(vec3 throughput, vec3 view_dir, MaterialProperties material continue; } - const EmissiveKusok ek = lights.kusochki[index_into_emissive_kusochki]; - const uint emissive_kusok_index = lights.kusochki[index_into_emissive_kusochki].kusok_index; - const Kusok ekusok = kusochki[emissive_kusok_index]; - - // TODO streamline matrices layouts - const mat4x3 emissive_transform = mat4x3( - vec3(ek.tx_row_x.x, ek.tx_row_y.x, ek.tx_row_z.x), - vec3(ek.tx_row_x.y, ek.tx_row_y.y, ek.tx_row_z.y), - vec3(ek.tx_row_x.z, ek.tx_row_y.z, ek.tx_row_z.z), - vec3(ek.tx_row_x.w, ek.tx_row_y.w, ek.tx_row_z.w) - ); - - const mat3 emissive_transform_normal = transpose(inverse(mat3(emissive_transform))); - - if (emissive_kusok_index == uint(payload_opaque.kusok_index)) - continue; - - const uint triangle_index = rand_range(ekusok.triangles); vec3 ldiffuse, lspecular; - sampleSurfaceTriangle(throughput * ek.emissive, view_dir, material, emissive_transform, emissive_transform_normal, triangle_index, ekusok.index_offset, ekusok.vertex_offset, emissive_kusok_index, ldiffuse, lspecular); + sampleEmissiveSurface(throughput, view_dir, material, index_into_emissive_kusochki, ldiffuse, lspecular); + diffuse += ldiffuse * sampling_light_scale; specular += lspecular * sampling_light_scale; } // for all emissive kusochki - vec3 ldiffuse, lspecular; + vec3 ldiffuse = vec3(0.), lspecular = vec3(0.); computePointLights(cluster_index, throughput, view_dir, material, ldiffuse, lspecular); diffuse += ldiffuse; specular += lspecular; @@ -436,6 +481,16 @@ void main() { origin, 0., direction, L, PAYLOAD_LOCATION_OPAQUE); +#if 0 + imageStore(out_image_base_color, ivec2(gl_LaunchIDEXT.xy), vec4(fract(payload_opaque.debug.xy), 0., 0.)); + //imageStore(out_image_base_color, ivec2(gl_LaunchIDEXT.xy), vec4(payload_opaque.kusok_index)); + //imageStore(out_image_base_color, ivec2(gl_LaunchIDEXT.xy), vec4(payload_opaque.roughness)); + imageStore(out_image_diffuse_gi, ivec2(gl_LaunchIDEXT.xy), vec4(0)); + imageStore(out_image_specular, ivec2(gl_LaunchIDEXT.xy), vec4(0.)); + imageStore(out_image_additive, ivec2(gl_LaunchIDEXT.xy), vec4(clamp(payload_opaque.normal, vec3(0.), vec3(1.)), 0.)); + return; +#endif + vec3 additive = traceAdditive(origin, direction, payload_opaque.hit_pos_t.w <= 0. ? L : payload_opaque.hit_pos_t.w); // Sky/envmap/emissive @@ -501,15 +556,6 @@ void main() { payload_opaque.base_color = vec3(1.); //out_material_index = float(kusochki[payload_opaque.kusok_index].tex_roughness); -#if 0 - //imageStore(out_image_base_color, ivec2(gl_LaunchIDEXT.xy), vec4(fract(payload_opaque.debug.xy), 0., 0.)); - //imageStore(out_image_base_color, ivec2(gl_LaunchIDEXT.xy), vec4(payload_opaque.kusok_index)); - imageStore(out_image_base_color, ivec2(gl_LaunchIDEXT.xy), vec4(payload_opaque.roughness)); - imageStore(out_image_diffuse_gi, ivec2(gl_LaunchIDEXT.xy), vec4(0)); - imageStore(out_image_specular, ivec2(gl_LaunchIDEXT.xy), vec4(0.)); - imageStore(out_image_additive, ivec2(gl_LaunchIDEXT.xy), vec4(clamp(payload_opaque.normal, vec3(0.), vec3(1.)), 0.)); - return; -#endif } // TODO should we do this after reflect/transmit decision? From 7869aac2e5cbee6787603327c9e444964f0ac429 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 27 Dec 2021 03:18:23 +0300 Subject: [PATCH 022/548] wscript: add public to includes --- vgui_support/wscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vgui_support/wscript b/vgui_support/wscript index cafab08d..47a7317a 100644 --- a/vgui_support/wscript +++ b/vgui_support/wscript @@ -111,7 +111,7 @@ def build(bld): source = bld.path.ant_glob(['*.cpp']) - includes = [ '.', 'miniutl/', '../common', '../engine' ] + includes = [ '.', '../common', '../engine', '../public' ] bld.shlib( source = source, From ebfa8e6ffc6314a023166dd80c6d95f314e3b75c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 27 Dec 2021 03:19:50 +0300 Subject: [PATCH 023/548] engine: platform: android: fix compile --- engine/platform/android/lib_android.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/platform/android/lib_android.c b/engine/platform/android/lib_android.c index 506c4e70..948d6324 100644 --- a/engine/platform/android/lib_android.c +++ b/engine/platform/android/lib_android.c @@ -51,7 +51,7 @@ void *ANDROID_LoadLibrary( const char *dllname ) } else { - Q_snprintf( path, MAX_SYSPATH, "lib%s"POSTFIX"."OS_LIB_EXT, dllname ); + Q_snprintf( path, MAX_SYSPATH, "lib%s."OS_LIB_EXT, dllname ); pHandle = dlopen( path, RTLD_LAZY ); if( pHandle ) return pHandle; From 7cb06956c23a439c262eed89d5611333240266f8 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 28 Dec 2021 00:16:06 +0300 Subject: [PATCH 024/548] engine: platform: win32: fix compile --- engine/platform/win32/con_win.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/engine/platform/win32/con_win.c b/engine/platform/win32/con_win.c index 74d5a0a5..68a5d896 100644 --- a/engine/platform/win32/con_win.c +++ b/engine/platform/win32/con_win.c @@ -13,8 +13,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#ifdef _WIN32 #include "common.h" +#include "xash3d_mathlib.h" /* =============================================================================== @@ -468,4 +468,3 @@ void Wcon_RegisterHotkeys( void ) // user can hit escape for quit RegisterHotKey( s_wcd.hWnd, QUIT_ON_ESCAPE_ID, 0, VK_ESCAPE ); } -#endif // _WIN32 From 74e9401e5608ffd519dd3cf5b8173886235c47ec Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Tue, 28 Dec 2021 19:41:19 -0800 Subject: [PATCH 025/548] rtx: start experimenting with peters2021 poly sampling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add bits of shader code from https://github.com/MomentsInGraphics/vulkan_renderer/blob/main/src/shaders/ More info and the paper itself is here: https://momentsingraphics.de/Siggraph2021.html ``` BRDF Importance Sampling for Polygonal Lights Christoph Peters. 2021–07 in ACM Transactions on Graphics (Proc. SIGGRAPH) 40, 4. ``` --- ref_vk/shaders/light_common.glsl | 51 + ref_vk/shaders/light_polygon.glsl | 259 +++++ .../peters2021-sampling/math_constants.glsl | 28 + .../peters2021-sampling/polygon_clipping.glsl | 225 +++++ .../peters2021-sampling/polygon_sampling.glsl | 883 ++++++++++++++++++ ref_vk/shaders/ray.rgen | 268 +----- 6 files changed, 1452 insertions(+), 262 deletions(-) create mode 100644 ref_vk/shaders/light_common.glsl create mode 100644 ref_vk/shaders/light_polygon.glsl create mode 100644 ref_vk/shaders/peters2021-sampling/math_constants.glsl create mode 100644 ref_vk/shaders/peters2021-sampling/polygon_clipping.glsl create mode 100644 ref_vk/shaders/peters2021-sampling/polygon_sampling.glsl diff --git a/ref_vk/shaders/light_common.glsl b/ref_vk/shaders/light_common.glsl new file mode 100644 index 00000000..04c8fc49 --- /dev/null +++ b/ref_vk/shaders/light_common.glsl @@ -0,0 +1,51 @@ +bool shadowed(vec3 pos, vec3 dir, float dist) { + payload_shadow.hit_type = SHADOW_HIT; + const uint flags = 0 + //| gl_RayFlagsCullFrontFacingTrianglesEXT + //| gl_RayFlagsOpaqueEXT + | gl_RayFlagsTerminateOnFirstHitEXT + | gl_RayFlagsSkipClosestHitShaderEXT + ; + traceRayEXT(tlas, + flags, + GEOMETRY_BIT_OPAQUE, + SHADER_OFFSET_HIT_SHADOW_BASE, SBT_RECORD_SIZE, SHADER_OFFSET_MISS_SHADOW, + pos, 0., dir, dist - shadow_offset_fudge, PAYLOAD_LOCATION_SHADOW); + return payload_shadow.hit_type == SHADOW_HIT; +} + +// TODO join with just shadowed() +bool shadowedSky(vec3 pos, vec3 dir, float dist) { + payload_shadow.hit_type = SHADOW_HIT; + const uint flags = 0 + //| gl_RayFlagsCullFrontFacingTrianglesEXT + //| gl_RayFlagsOpaqueEXT + //| gl_RayFlagsTerminateOnFirstHitEXT + //| gl_RayFlagsSkipClosestHitShaderEXT + ; + traceRayEXT(tlas, + flags, + GEOMETRY_BIT_OPAQUE, + SHADER_OFFSET_HIT_SHADOW_BASE, SBT_RECORD_SIZE, SHADER_OFFSET_MISS_SHADOW, + pos, 0., dir, dist - shadow_offset_fudge, PAYLOAD_LOCATION_SHADOW); + return payload_shadow.hit_type != SHADOW_SKY; +} + +// This is an entry point for evaluation of all other BRDFs based on selected configuration (for direct light) +void evalSplitBRDF(vec3 N, vec3 L, vec3 V, MaterialProperties material, out vec3 diffuse, out vec3 specular) { + // Prepare data needed for BRDF evaluation - unpack material properties and evaluate commonly used terms (e.g. Fresnel, NdotL, ...) + const BrdfData data = prepareBRDFData(N, L, V, material); + + // Ignore V and L rays "below" the hemisphere + //if (data.Vbackfacing || data.Lbackfacing) return vec3(0.0f, 0.0f, 0.0f); + + // Eval specular and diffuse BRDFs + specular = evalSpecular(data); + diffuse = evalDiffuse(data); + + // Combine specular and diffuse layers +#if COMBINE_BRDFS_WITH_FRESNEL + // Specular is already multiplied by F, just attenuate diffuse + diffuse *= vec3(1.) - data.F; +#endif +} diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl new file mode 100644 index 00000000..4f1426d8 --- /dev/null +++ b/ref_vk/shaders/light_polygon.glsl @@ -0,0 +1,259 @@ +#define MAX_POLYGON_VERTEX_COUNT 4 +#define MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING 3 +#include "peters2021-sampling/polygon_clipping.glsl" +#include "peters2021-sampling/polygon_sampling.glsl" + +struct SampleContext { + mat4x3 world_to_shading; +}; + +SampleContext buildSampleContext(vec3 position, vec3 normal, vec3 view_dir) { + SampleContext ctx; + const float normal_dot_outgoing = dot(normal, -view_dir); + const vec3 x_axis = normalize(fma(vec3(-normal_dot_outgoing), normal, -view_dir)); + const vec3 y_axis = cross(normal, x_axis); + const mat3 rotation = transpose(mat3(x_axis, y_axis, normal)); + ctx.world_to_shading = mat4x3(rotation[0], rotation[1], rotation[2], -rotation * position); + return ctx; +} + +float triangleSolidAngle(vec3 p, vec3 a, vec3 b, vec3 c) { + a = normalize(a - p); + b = normalize(b - p); + c = normalize(c - p); + + // TODO horizon culling + const float tanHalfOmega = dot(a, cross(b,c)) / (1. + dot(b,c) + dot(c,a) + dot(a,b)); + + return atan(tanHalfOmega) * 2.; +} + +vec3 baryMix(vec3 v1, vec3 v2, vec3 v3, vec2 bary) { + return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; +} + +vec2 baryMix(vec2 v1, vec2 v2, vec2 v3, vec2 bary) { + return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; +} + +float computeTriangleContrib(uint triangle_index, uint index_offset, uint vertex_offset, mat4x3 xform, vec3 pos) { + const uint first_index_offset = index_offset + triangle_index * 3; + + const uint vi1 = uint(indices[first_index_offset+0]) + vertex_offset; + const uint vi2 = uint(indices[first_index_offset+1]) + vertex_offset; + const uint vi3 = uint(indices[first_index_offset+2]) + vertex_offset; + + const vec3 v1 = (xform * vec4(vertices[vi1].pos, 1.)).xyz; + const vec3 v2 = (xform * vec4(vertices[vi2].pos, 1.)).xyz; + const vec3 v3 = (xform * vec4(vertices[vi3].pos, 1.)).xyz; + + return triangleSolidAngle(pos, v1, v2, v3) / TWO_PI; +} + +void sampleSurfaceTriangle( + vec3 color, vec3 view_dir, MaterialProperties material /* TODO BrdfData instead is supposedly more efficient */, + mat4x3 emissive_transform, + uint triangle_index, uint index_offset, uint vertex_offset, + uint kusok_index, + out vec3 diffuse, out vec3 specular) +{ + diffuse = specular = vec3(0.); + const uint first_index_offset = index_offset + triangle_index * 3; + + // TODO this is not entirely correct -- need to mix between all normals, or have this normal precomputed + const uint vi1 = uint(indices[first_index_offset+0]) + vertex_offset; + const uint vi2 = uint(indices[first_index_offset+1]) + vertex_offset; + const uint vi3 = uint(indices[first_index_offset+2]) + vertex_offset; + + const vec3 v1 = (emissive_transform * vec4(vertices[vi1].pos, 1.)).xyz; + const vec3 v2 = (emissive_transform * vec4(vertices[vi2].pos, 1.)).xyz; + const vec3 v3 = (emissive_transform * vec4(vertices[vi3].pos, 1.)).xyz; + + // TODO projected uniform sampling + vec2 bary = vec2(sqrt(rand01()), rand01()); + bary.y *= bary.x; + bary.x = 1. - bary.x; + const vec3 sample_pos = baryMix(v1, v2, v3, bary); + + vec3 light_dir = sample_pos - payload_opaque.hit_pos_t.xyz; + const float light_dir_normal_dot = dot(light_dir, payload_opaque.normal); + if (light_dir_normal_dot <= 0.) +#ifdef DEBUG_LIGHT_CULLING + return vec3(1., 0., 1.) * color_factor; +#else + return; +#endif + + + const float light_dist2 = dot(light_dir, light_dir); + float pdf = TWO_PI / triangleSolidAngle(payload_opaque.hit_pos_t.xyz, v1, v2, v3); + + // Cull back facing + if (pdf <= 0.) + return; + + if (pdf > pdf_culling_threshold) +#ifdef DEBUG_LIGHT_CULLING + return vec3(0., 1., 0.) * color_factor; +#else + return; +#endif + +#if 1 + { + const uint tex_index = kusochki[kusok_index].tex_base_color; + if ((KUSOK_MATERIAL_FLAG_SKYBOX & tex_index) == 0) { + const vec2 uv1 = vertices[vi1].gl_tc; + const vec2 uv2 = vertices[vi2].gl_tc; + const vec2 uv3 = vertices[vi3].gl_tc; + const vec2 uv = baryMix(uv1, uv2, uv3, bary); + + color *= texture(textures[nonuniformEXT(tex_index)], uv).rgb; + } + } +#endif + + color /= pdf; + + if (dot(color,color) < color_culling_threshold) +#ifdef DEBUG_LIGHT_CULLING + return vec3(0., 1., 0.) * color_factor; +#else + return; +#endif + + light_dir = normalize(light_dir); + + // TODO sample emissive texture + evalSplitBRDF(payload_opaque.normal, light_dir, view_dir, material, diffuse, specular); + diffuse *= color; + specular *= color; + + vec3 combined = diffuse + specular; + + if (dot(combined,combined) < color_culling_threshold) +#ifdef DEBUG_LIGHT_CULLING + return vec3(1., 1., 0.) * color_factor; +#else + return; +#endif + + if (shadowed(payload_opaque.hit_pos_t.xyz, light_dir, sqrt(light_dist2))) { + diffuse = specular = vec3(0.); + } + + if (pdf < 0.) { //any(lessThan(diffuse, vec3(0.)))) { + diffuse = vec3(1., 0., 0.); + } +} + +void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties material, SampleContext ctx, uint ekusok_index, out vec3 out_diffuse, out vec3 out_specular) { + const EmissiveKusok ek = lights.kusochki[ekusok_index]; + const uint emissive_kusok_index = lights.kusochki[ekusok_index].kusok_index; + const Kusok kusok = kusochki[emissive_kusok_index]; + + // TODO streamline matrices layouts + const mat4x3 to_shading = ctx.world_to_shading * mat4( + vec4(ek.tx_row_x.x, ek.tx_row_y.x, ek.tx_row_z.x, 0), + vec4(ek.tx_row_x.y, ek.tx_row_y.y, ek.tx_row_z.y, 0), + vec4(ek.tx_row_x.z, ek.tx_row_y.z, ek.tx_row_z.z, 0), + vec4(ek.tx_row_x.w, ek.tx_row_y.w, ek.tx_row_z.w, 1) + ); + + const mat4x3 emissive_transform = mat4x3( + vec3(ek.tx_row_x.x, ek.tx_row_y.x, ek.tx_row_z.x), + vec3(ek.tx_row_x.y, ek.tx_row_y.y, ek.tx_row_z.y), + vec3(ek.tx_row_x.z, ek.tx_row_y.z, ek.tx_row_z.z), + vec3(ek.tx_row_x.w, ek.tx_row_y.w, ek.tx_row_z.w) + ); + + + if (emissive_kusok_index == uint(payload_opaque.kusok_index)) + return; + + // Taken from Ray Tracing Gems II, ch.47, p.776, listing 47-2 + int selected = -1; + float selected_contrib = 0.; + float total_contrib = 0.; + float eps1 = rand01(); + + solid_angle_polygon_t poly_angle; + for (uint i = 0; i < kusok.triangles; ++i) { + const uint first_index_offset = kusok.index_offset + i * 3; + const uint vi1 = uint(indices[first_index_offset+0]) + kusok.vertex_offset; + const uint vi2 = uint(indices[first_index_offset+1]) + kusok.vertex_offset; + const uint vi3 = uint(indices[first_index_offset+2]) + kusok.vertex_offset; + + // Transform to shading space + vec3 v[MAX_POLYGON_VERTEX_COUNT]; + v[0] = to_shading * vec4(vertices[vi1].pos, 1.); + v[1] = to_shading * vec4(vertices[vi2].pos, 1.); + v[2] = to_shading * vec4(vertices[vi3].pos, 1.); + + // Clip + const uint vertex_count = clip_polygon(3, v); + + // poly_angle + poly_angle = prepare_solid_angle_polygon_sampling(vertex_count, v, payload_opaque.hit_pos_t.xyz); + const float tri_contrib = poly_angle.solid_angle; + + if (tri_contrib <= 0.) + continue; + + const float tau = total_contrib / (total_contrib + tri_contrib); + total_contrib += tri_contrib; + + if (eps1 < tau) { + eps1 /= tau; + } else { + selected = int(i); + selected_contrib = tri_contrib; + eps1 = (eps1 - tau) / (1. - tau); + } + +#define MAX_BELOW_ONE .99999 // FIXME what's the correct way to do this + eps1 = clamp(eps1, 0., MAX_BELOW_ONE); // Numerical stability (?) + } + + if (selected >= 0) { + sampleSurfaceTriangle(throughput * ek.emissive, view_dir, material, emissive_transform, selected, kusok.index_offset, kusok.vertex_offset, emissive_kusok_index, out_diffuse, out_specular); + + const float tri_factor = total_contrib / selected_contrib; + out_diffuse *= tri_factor; + out_specular *= tri_factor; + } +} + +void sampleEmissiveSurfaces(vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { + + const SampleContext ctx = buildSampleContext(payload_opaque.hit_pos_t.xyz, payload_opaque.normal, view_dir); + + const uint num_emissive_kusochki = uint(light_grid.clusters[cluster_index].num_emissive_surfaces); + float sampling_light_scale = 1.; +#if 1 + const uint max_lights_per_frame = 4; + uint begin_i = 0, end_i = num_emissive_kusochki; + if (end_i > max_lights_per_frame) { + begin_i = rand() % (num_emissive_kusochki - max_lights_per_frame); + end_i = begin_i + max_lights_per_frame; + sampling_light_scale = float(num_emissive_kusochki) / float(max_lights_per_frame); + } + for (uint i = begin_i; i < end_i; ++i) { +#else + + for (uint i = 0; i < num_emissive_kusochki; ++i) { +#endif + const uint index_into_emissive_kusochki = uint(light_grid.clusters[cluster_index].emissive_surfaces[i]); + + if (push_constants.debug_light_index_begin < push_constants.debug_light_index_end) { + if (index_into_emissive_kusochki < push_constants.debug_light_index_begin || index_into_emissive_kusochki >= push_constants.debug_light_index_end) + continue; + } + + vec3 ldiffuse, lspecular; + sampleEmissiveSurface(throughput, view_dir, material, ctx, index_into_emissive_kusochki, ldiffuse, lspecular); + + diffuse += ldiffuse * sampling_light_scale; + specular += lspecular * sampling_light_scale; + } // for all emissive kusochki +} diff --git a/ref_vk/shaders/peters2021-sampling/math_constants.glsl b/ref_vk/shaders/peters2021-sampling/math_constants.glsl new file mode 100644 index 00000000..fe5ec395 --- /dev/null +++ b/ref_vk/shaders/peters2021-sampling/math_constants.glsl @@ -0,0 +1,28 @@ +// Copyright (C) 2021, Christoph Peters, Karlsruhe Institute of Technology +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#ifndef M_PI + #define M_PI 3.1415926535897932384626433832795f +#endif +#ifndef M_INV_PI + #define M_INV_PI 0.31830988618379067153776752674503f +#endif +#ifndef M_HALF_PI + #define M_HALF_PI 1.5707963267948966192313216916398f +#endif +#ifndef M_INFINITY + #define M_INFINITY (1.0f / 0.0f) +#endif diff --git a/ref_vk/shaders/peters2021-sampling/polygon_clipping.glsl b/ref_vk/shaders/peters2021-sampling/polygon_clipping.glsl new file mode 100644 index 00000000..1b2aa934 --- /dev/null +++ b/ref_vk/shaders/peters2021-sampling/polygon_clipping.glsl @@ -0,0 +1,225 @@ +// Copyright (C) 2021, Christoph Peters, Karlsruhe Institute of Technology +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +/*! Returns the intersection of the line connecting the given two points with + the plane z == 0.0f.*/ +vec3 iz0(vec3 lhs, vec3 rhs) { + float lerp_factor = lhs.z / (lhs.z - rhs.z); + // Equivalent to the following but I have trust issues regarding the + // stability of mix() + // return vec3(mix(lhs.xy, rhs.xy, lerp_factor), 0.0f); + return vec3(fma(vec2(lerp_factor), rhs.xy, fma(-vec2(lerp_factor), lhs.xy, lhs.xy)), 0.0f); +} + + +/*! This function clips the given convex polygon with vertices in v to the + upper hemisphere (i.e. the half-space with non-negative z-coordinate). + The vertex count after clipping is returned. It is either zero or between + three and vertex_count + 1. If it is less than MAX_POLYGON_VERTEX_COUNT, + the first entry of v is repeated at the returned vertex count for the + output. vertex_count must be at least + MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING.*/ +uint clip_polygon(uint vertex_count, inout vec3 v[MAX_POLYGON_VERTEX_COUNT]) { + // The vertex count after clipping + uint vc; + // Encode the whole configuration into a single integer + uint bit_mask = vertex_count; + [[unroll]] + for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 1; ++i) + bit_mask |= (v[i].z > 0.0f && (i < MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING || i < vertex_count)) ? (1 << (i + 3)) : 0; + // This code has been generated automatically to handle all possible cases + // with a single conditional jump and no unnecessary instructions + switch (bit_mask) { + // AUTOGENERATED PART BEGIN +#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 3 && MAX_POLYGON_VERTEX_COUNT >= 4 + case 3: vc = 0; break; + case 59: vc = 3; v[3] = v[0]; break; + case 11: vc = 3; v[1] = iz0(v[0], v[1]); v[2] = iz0(v[2], v[0]); v[3] = v[0]; break; + case 19: vc = 3; v[0] = iz0(v[0], v[1]); v[2] = iz0(v[1], v[2]); v[3] = v[0]; break; + case 35: vc = 3; v[0] = iz0(v[2], v[0]); v[1] = iz0(v[1], v[2]); v[3] = v[0]; break; +#endif +#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 3 && MAX_POLYGON_VERTEX_COUNT == 4 + case 27: vc = 4; v[3] = iz0(v[2], v[0]); v[2] = iz0(v[1], v[2]); break; + case 51: vc = 4; v[3] = iz0(v[2], v[0]); v[0] = iz0(v[0], v[1]); break; + case 43: vc = 4; v[3] = v[2]; v[2] = iz0(v[1], v[2]); v[1] = iz0(v[0], v[1]); break; +#elif MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 3 && MAX_POLYGON_VERTEX_COUNT > 4 + case 27: vc = 4; v[3] = iz0(v[2], v[0]); v[2] = iz0(v[1], v[2]); v[4] = v[0]; break; + case 51: vc = 4; v[3] = iz0(v[2], v[0]); v[0] = iz0(v[0], v[1]); v[4] = v[0]; break; + case 43: vc = 4; v[3] = v[2]; v[2] = iz0(v[1], v[2]); v[1] = iz0(v[0], v[1]); v[4] = v[0]; break; +#endif +#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 4 && MAX_POLYGON_VERTEX_COUNT >= 5 + case 4: vc = 0; break; + case 124: vc = 4; v[4] = v[0]; break; + case 12: vc = 3; v[1] = iz0(v[0], v[1]); v[2] = iz0(v[3], v[0]); v[3] = v[0]; break; + case 20: vc = 3; v[0] = iz0(v[0], v[1]); v[2] = iz0(v[1], v[2]); v[3] = v[0]; break; + case 36: vc = 3; v[0] = iz0(v[2], v[3]); v[1] = iz0(v[1], v[2]); v[3] = v[0]; break; + case 68: vc = 3; v[1] = iz0(v[3], v[0]); v[0] = v[3]; v[2] = iz0(v[2], v[3]); break; + case 28: vc = 4; v[2] = iz0(v[1], v[2]); v[3] = iz0(v[3], v[0]); v[4] = v[0]; break; + case 52: vc = 4; v[0] = iz0(v[0], v[1]); v[3] = iz0(v[2], v[3]); v[4] = v[0]; break; + case 100: vc = 4; v[0] = iz0(v[3], v[0]); v[1] = iz0(v[1], v[2]); v[4] = v[0]; break; + case 76: vc = 4; v[1] = iz0(v[0], v[1]); v[2] = iz0(v[2], v[3]); v[4] = v[0]; break; +#endif +#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 4 && MAX_POLYGON_VERTEX_COUNT == 5 + case 60: vc = 5; v[4] = iz0(v[3], v[0]); v[3] = iz0(v[2], v[3]); break; + case 116: vc = 5; v[4] = iz0(v[3], v[0]); v[0] = iz0(v[0], v[1]); break; + case 108: vc = 5; v[4] = v[0]; v[0] = iz0(v[0], v[1]); v[1] = iz0(v[1], v[2]); break; + case 92: vc = 5; v[4] = v[3]; v[3] = iz0(v[2], v[3]); v[2] = iz0(v[1], v[2]); break; +#elif MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 4 && MAX_POLYGON_VERTEX_COUNT > 5 + case 60: vc = 5; v[4] = iz0(v[3], v[0]); v[3] = iz0(v[2], v[3]); v[5] = v[0]; break; + case 116: vc = 5; v[4] = iz0(v[3], v[0]); v[0] = iz0(v[0], v[1]); v[5] = v[0]; break; + case 108: vc = 5; v[4] = v[0]; v[0] = iz0(v[0], v[1]); v[1] = iz0(v[1], v[2]); v[5] = v[0]; break; + case 92: vc = 5; v[4] = v[3]; v[3] = iz0(v[2], v[3]); v[2] = iz0(v[1], v[2]); v[5] = v[0]; break; +#endif +#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 5 && MAX_POLYGON_VERTEX_COUNT >= 6 + case 5: vc = 0; break; + case 253: vc = 5; v[5] = v[0]; break; + case 13: vc = 3; v[1] = iz0(v[0], v[1]); v[2] = iz0(v[4], v[0]); v[3] = v[0]; break; + case 21: vc = 3; v[0] = iz0(v[0], v[1]); v[2] = iz0(v[1], v[2]); v[3] = v[0]; break; + case 37: vc = 3; v[0] = iz0(v[2], v[3]); v[1] = iz0(v[1], v[2]); v[3] = v[0]; break; + case 69: vc = 3; v[0] = v[3]; v[1] = iz0(v[3], v[4]); v[2] = iz0(v[2], v[3]); break; + case 133: vc = 3; v[1] = v[4]; v[2] = iz0(v[4], v[0]); v[0] = iz0(v[3], v[4]); v[3] = v[0]; break; + case 29: vc = 4; v[2] = iz0(v[1], v[2]); v[3] = iz0(v[4], v[0]); v[4] = v[0]; break; + case 53: vc = 4; v[0] = iz0(v[0], v[1]); v[3] = iz0(v[2], v[3]); v[4] = v[0]; break; + case 101: vc = 4; v[0] = iz0(v[3], v[4]); v[1] = iz0(v[1], v[2]); v[4] = v[0]; break; + case 197: vc = 4; v[1] = iz0(v[4], v[0]); v[0] = v[4]; v[2] = iz0(v[2], v[3]); break; + case 141: vc = 4; v[1] = iz0(v[0], v[1]); v[2] = iz0(v[3], v[4]); v[3] = v[4]; v[4] = v[0]; break; + case 61: vc = 5; v[3] = iz0(v[2], v[3]); v[4] = iz0(v[4], v[0]); v[5] = v[0]; break; + case 117: vc = 5; v[0] = iz0(v[0], v[1]); v[4] = iz0(v[3], v[4]); v[5] = v[0]; break; + case 229: vc = 5; v[0] = iz0(v[4], v[0]); v[1] = iz0(v[1], v[2]); v[5] = v[0]; break; + case 205: vc = 5; v[1] = iz0(v[0], v[1]); v[2] = iz0(v[2], v[3]); v[5] = v[0]; break; + case 157: vc = 5; v[2] = iz0(v[1], v[2]); v[3] = iz0(v[3], v[4]); v[5] = v[0]; break; +#endif +#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 5 && MAX_POLYGON_VERTEX_COUNT == 6 + case 125: vc = 6; v[5] = iz0(v[4], v[0]); v[4] = iz0(v[3], v[4]); break; + case 245: vc = 6; v[5] = iz0(v[4], v[0]); v[0] = iz0(v[0], v[1]); break; + case 237: vc = 6; v[5] = v[0]; v[0] = iz0(v[0], v[1]); v[1] = iz0(v[1], v[2]); break; + case 221: vc = 6; v[5] = v[4]; v[4] = v[3]; v[3] = iz0(v[2], v[3]); v[2] = iz0(v[1], v[2]); break; + case 189: vc = 6; v[5] = v[4]; v[4] = iz0(v[3], v[4]); v[3] = iz0(v[2], v[3]); break; +#elif MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 5 && MAX_POLYGON_VERTEX_COUNT > 6 + case 125: vc = 6; v[5] = iz0(v[4], v[0]); v[4] = iz0(v[3], v[4]); v[6] = v[0]; break; + case 245: vc = 6; v[5] = iz0(v[4], v[0]); v[0] = iz0(v[0], v[1]); v[6] = v[0]; break; + case 237: vc = 6; v[5] = v[0]; v[0] = iz0(v[0], v[1]); v[1] = iz0(v[1], v[2]); v[6] = v[0]; break; + case 221: vc = 6; v[5] = v[4]; v[4] = v[3]; v[3] = iz0(v[2], v[3]); v[2] = iz0(v[1], v[2]); v[6] = v[0]; break; + case 189: vc = 6; v[5] = v[4]; v[4] = iz0(v[3], v[4]); v[3] = iz0(v[2], v[3]); v[6] = v[0]; break; +#endif +#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 6 && MAX_POLYGON_VERTEX_COUNT >= 7 + case 6: vc = 0; break; + case 510: vc = 6; v[6] = v[0]; break; + case 14: vc = 3; v[1] = iz0(v[0], v[1]); v[2] = iz0(v[5], v[0]); v[3] = v[0]; break; + case 22: vc = 3; v[0] = iz0(v[0], v[1]); v[2] = iz0(v[1], v[2]); v[3] = v[0]; break; + case 38: vc = 3; v[0] = iz0(v[2], v[3]); v[1] = iz0(v[1], v[2]); v[3] = v[0]; break; + case 70: vc = 3; v[0] = v[3]; v[1] = iz0(v[3], v[4]); v[2] = iz0(v[2], v[3]); break; + case 134: vc = 3; v[0] = iz0(v[3], v[4]); v[1] = v[4]; v[2] = iz0(v[4], v[5]); v[3] = v[0]; break; + case 262: vc = 3; v[1] = v[5]; v[2] = iz0(v[5], v[0]); v[0] = iz0(v[4], v[5]); v[3] = v[0]; break; + case 30: vc = 4; v[2] = iz0(v[1], v[2]); v[3] = iz0(v[5], v[0]); v[4] = v[0]; break; + case 54: vc = 4; v[0] = iz0(v[0], v[1]); v[3] = iz0(v[2], v[3]); v[4] = v[0]; break; + case 102: vc = 4; v[0] = iz0(v[3], v[4]); v[1] = iz0(v[1], v[2]); v[4] = v[0]; break; + case 198: vc = 4; v[0] = v[4]; v[1] = iz0(v[4], v[5]); v[2] = iz0(v[2], v[3]); break; + case 390: vc = 4; v[1] = v[5]; v[2] = iz0(v[5], v[0]); v[0] = v[4]; v[3] = iz0(v[3], v[4]); break; + case 270: vc = 4; v[1] = iz0(v[0], v[1]); v[2] = iz0(v[4], v[5]); v[3] = v[5]; v[4] = v[0]; break; + case 62: vc = 5; v[3] = iz0(v[2], v[3]); v[4] = iz0(v[5], v[0]); v[5] = v[0]; break; + case 118: vc = 5; v[0] = iz0(v[0], v[1]); v[4] = iz0(v[3], v[4]); v[5] = v[0]; break; + case 230: vc = 5; v[0] = iz0(v[4], v[5]); v[1] = iz0(v[1], v[2]); v[5] = v[0]; break; + case 454: vc = 5; v[1] = iz0(v[5], v[0]); v[0] = v[5]; v[2] = iz0(v[2], v[3]); break; + case 398: vc = 5; v[2] = iz0(v[0], v[1]); v[1] = v[0]; v[0] = v[5]; v[3] = iz0(v[3], v[4]); break; + case 286: vc = 5; v[2] = iz0(v[1], v[2]); v[3] = iz0(v[4], v[5]); v[4] = v[5]; v[5] = v[0]; break; + case 126: vc = 6; v[4] = iz0(v[3], v[4]); v[5] = iz0(v[5], v[0]); v[6] = v[0]; break; + case 246: vc = 6; v[0] = iz0(v[0], v[1]); v[5] = iz0(v[4], v[5]); v[6] = v[0]; break; + case 486: vc = 6; v[0] = iz0(v[5], v[0]); v[1] = iz0(v[1], v[2]); v[6] = v[0]; break; + case 462: vc = 6; v[1] = iz0(v[0], v[1]); v[2] = iz0(v[2], v[3]); v[6] = v[0]; break; + case 414: vc = 6; v[2] = iz0(v[1], v[2]); v[3] = iz0(v[3], v[4]); v[6] = v[0]; break; + case 318: vc = 6; v[3] = iz0(v[2], v[3]); v[4] = iz0(v[4], v[5]); v[6] = v[0]; break; +#endif +#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 6 && MAX_POLYGON_VERTEX_COUNT == 7 + case 254: vc = 7; v[6] = iz0(v[5], v[0]); v[5] = iz0(v[4], v[5]); break; + case 502: vc = 7; v[6] = iz0(v[5], v[0]); v[0] = iz0(v[0], v[1]); break; + case 494: vc = 7; v[6] = v[0]; v[0] = iz0(v[0], v[1]); v[1] = iz0(v[1], v[2]); break; + case 478: vc = 7; v[6] = v[0]; v[0] = v[1]; v[1] = iz0(v[1], v[2]); v[2] = iz0(v[2], v[3]); break; + case 446: vc = 7; v[6] = v[5]; v[5] = v[4]; v[4] = iz0(v[3], v[4]); v[3] = iz0(v[2], v[3]); break; + case 382: vc = 7; v[6] = v[5]; v[5] = iz0(v[4], v[5]); v[4] = iz0(v[3], v[4]); break; +#elif MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 6 && MAX_POLYGON_VERTEX_COUNT > 7 + case 254: vc = 7; v[6] = iz0(v[5], v[0]); v[5] = iz0(v[4], v[5]); v[7] = v[0]; break; + case 502: vc = 7; v[6] = iz0(v[5], v[0]); v[0] = iz0(v[0], v[1]); v[7] = v[0]; break; + case 494: vc = 7; v[6] = v[0]; v[0] = iz0(v[0], v[1]); v[1] = iz0(v[1], v[2]); v[7] = v[0]; break; + case 478: vc = 7; v[6] = v[0]; v[0] = v[1]; v[1] = iz0(v[1], v[2]); v[2] = iz0(v[2], v[3]); v[7] = v[0]; break; + case 446: vc = 7; v[6] = v[5]; v[5] = v[4]; v[4] = iz0(v[3], v[4]); v[3] = iz0(v[2], v[3]); v[7] = v[0]; break; + case 382: vc = 7; v[6] = v[5]; v[5] = iz0(v[4], v[5]); v[4] = iz0(v[3], v[4]); v[7] = v[0]; break; +#endif +#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 7 && MAX_POLYGON_VERTEX_COUNT >= 8 + case 7: vc = 0; break; + case 1023: vc = 7; v[7] = v[0]; break; + case 15: vc = 3; v[1] = iz0(v[0], v[1]); v[2] = iz0(v[6], v[0]); v[3] = v[0]; break; + case 23: vc = 3; v[0] = iz0(v[0], v[1]); v[2] = iz0(v[1], v[2]); v[3] = v[0]; break; + case 39: vc = 3; v[0] = iz0(v[2], v[3]); v[1] = iz0(v[1], v[2]); v[3] = v[0]; break; + case 71: vc = 3; v[0] = v[3]; v[1] = iz0(v[3], v[4]); v[2] = iz0(v[2], v[3]); break; + case 135: vc = 3; v[0] = iz0(v[3], v[4]); v[1] = v[4]; v[2] = iz0(v[4], v[5]); v[3] = v[0]; break; + case 263: vc = 3; v[0] = iz0(v[4], v[5]); v[1] = v[5]; v[2] = iz0(v[5], v[6]); v[3] = v[0]; break; + case 519: vc = 3; v[1] = v[6]; v[2] = iz0(v[6], v[0]); v[0] = iz0(v[5], v[6]); v[3] = v[0]; break; + case 31: vc = 4; v[2] = iz0(v[1], v[2]); v[3] = iz0(v[6], v[0]); v[4] = v[0]; break; + case 55: vc = 4; v[0] = iz0(v[0], v[1]); v[3] = iz0(v[2], v[3]); v[4] = v[0]; break; + case 103: vc = 4; v[0] = iz0(v[3], v[4]); v[1] = iz0(v[1], v[2]); v[4] = v[0]; break; + case 199: vc = 4; v[0] = v[4]; v[1] = iz0(v[4], v[5]); v[2] = iz0(v[2], v[3]); break; + case 391: vc = 4; v[0] = v[4]; v[1] = v[5]; v[2] = iz0(v[5], v[6]); v[3] = iz0(v[3], v[4]); break; + case 775: vc = 4; v[1] = v[5]; v[2] = v[6]; v[3] = iz0(v[6], v[0]); v[0] = iz0(v[4], v[5]); v[4] = v[0]; break; + case 527: vc = 4; v[1] = iz0(v[0], v[1]); v[2] = iz0(v[5], v[6]); v[3] = v[6]; v[4] = v[0]; break; + case 63: vc = 5; v[3] = iz0(v[2], v[3]); v[4] = iz0(v[6], v[0]); v[5] = v[0]; break; + case 119: vc = 5; v[0] = iz0(v[0], v[1]); v[4] = iz0(v[3], v[4]); v[5] = v[0]; break; + case 231: vc = 5; v[0] = iz0(v[4], v[5]); v[1] = iz0(v[1], v[2]); v[5] = v[0]; break; + case 455: vc = 5; v[0] = v[5]; v[1] = iz0(v[5], v[6]); v[2] = iz0(v[2], v[3]); break; + case 903: vc = 5; v[1] = v[6]; v[2] = iz0(v[6], v[0]); v[0] = v[5]; v[3] = iz0(v[3], v[4]); break; + case 783: vc = 5; v[1] = iz0(v[0], v[1]); v[2] = iz0(v[4], v[5]); v[3] = v[5]; v[4] = v[6]; v[5] = v[0]; break; + case 543: vc = 5; v[2] = iz0(v[1], v[2]); v[3] = iz0(v[5], v[6]); v[4] = v[6]; v[5] = v[0]; break; + case 127: vc = 6; v[4] = iz0(v[3], v[4]); v[5] = iz0(v[6], v[0]); v[6] = v[0]; break; + case 247: vc = 6; v[0] = iz0(v[0], v[1]); v[5] = iz0(v[4], v[5]); v[6] = v[0]; break; + case 487: vc = 6; v[0] = iz0(v[5], v[6]); v[1] = iz0(v[1], v[2]); v[6] = v[0]; break; + case 967: vc = 6; v[1] = iz0(v[6], v[0]); v[0] = v[6]; v[2] = iz0(v[2], v[3]); break; + case 911: vc = 6; v[2] = iz0(v[0], v[1]); v[1] = v[0]; v[0] = v[6]; v[3] = iz0(v[3], v[4]); break; + case 799: vc = 6; v[2] = iz0(v[1], v[2]); v[3] = iz0(v[4], v[5]); v[4] = v[5]; v[5] = v[6]; v[6] = v[0]; break; + case 575: vc = 6; v[3] = iz0(v[2], v[3]); v[4] = iz0(v[5], v[6]); v[5] = v[6]; v[6] = v[0]; break; + case 255: vc = 7; v[5] = iz0(v[4], v[5]); v[6] = iz0(v[6], v[0]); v[7] = v[0]; break; + case 503: vc = 7; v[0] = iz0(v[0], v[1]); v[6] = iz0(v[5], v[6]); v[7] = v[0]; break; + case 999: vc = 7; v[0] = iz0(v[6], v[0]); v[1] = iz0(v[1], v[2]); v[7] = v[0]; break; + case 975: vc = 7; v[1] = iz0(v[0], v[1]); v[2] = iz0(v[2], v[3]); v[7] = v[0]; break; + case 927: vc = 7; v[2] = iz0(v[1], v[2]); v[3] = iz0(v[3], v[4]); v[7] = v[0]; break; + case 831: vc = 7; v[3] = iz0(v[2], v[3]); v[4] = iz0(v[4], v[5]); v[7] = v[0]; break; + case 639: vc = 7; v[4] = iz0(v[3], v[4]); v[5] = iz0(v[5], v[6]); v[7] = v[0]; break; +#endif +#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 7 && MAX_POLYGON_VERTEX_COUNT == 8 + case 511: vc = 8; v[7] = iz0(v[6], v[0]); v[6] = iz0(v[5], v[6]); break; + case 1015: vc = 8; v[7] = iz0(v[6], v[0]); v[0] = iz0(v[0], v[1]); break; + case 1007: vc = 8; v[7] = v[0]; v[0] = iz0(v[0], v[1]); v[1] = iz0(v[1], v[2]); break; + case 991: vc = 8; v[7] = v[0]; v[0] = v[1]; v[1] = iz0(v[1], v[2]); v[2] = iz0(v[2], v[3]); break; + case 959: vc = 8; v[7] = v[6]; v[6] = v[5]; v[5] = v[4]; v[4] = iz0(v[3], v[4]); v[3] = iz0(v[2], v[3]); break; + case 895: vc = 8; v[7] = v[6]; v[6] = v[5]; v[5] = iz0(v[4], v[5]); v[4] = iz0(v[3], v[4]); break; + case 767: vc = 8; v[7] = v[6]; v[6] = iz0(v[5], v[6]); v[5] = iz0(v[4], v[5]); break; +#elif MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 7 && MAX_POLYGON_VERTEX_COUNT > 8 + case 511: vc = 8; v[7] = iz0(v[6], v[0]); v[6] = iz0(v[5], v[6]); v[8] = v[0]; break; + case 1015: vc = 8; v[7] = iz0(v[6], v[0]); v[0] = iz0(v[0], v[1]); v[8] = v[0]; break; + case 1007: vc = 8; v[7] = v[0]; v[0] = iz0(v[0], v[1]); v[1] = iz0(v[1], v[2]); v[8] = v[0]; break; + case 991: vc = 8; v[7] = v[0]; v[0] = v[1]; v[1] = iz0(v[1], v[2]); v[2] = iz0(v[2], v[3]); v[8] = v[0]; break; + case 959: vc = 8; v[7] = v[6]; v[6] = v[5]; v[5] = v[4]; v[4] = iz0(v[3], v[4]); v[3] = iz0(v[2], v[3]); v[8] = v[0]; break; + case 895: vc = 8; v[7] = v[6]; v[6] = v[5]; v[5] = iz0(v[4], v[5]); v[4] = iz0(v[3], v[4]); v[8] = v[0]; break; + case 767: vc = 8; v[7] = v[6]; v[6] = iz0(v[5], v[6]); v[5] = iz0(v[4], v[5]); v[8] = v[0]; break; +#endif + // AUTOGENERATED PART END + default: + // This should never happen. Just pretend the polygon is below the + // horizon. + vc = 0; + break; + }; + return vc; +} diff --git a/ref_vk/shaders/peters2021-sampling/polygon_sampling.glsl b/ref_vk/shaders/peters2021-sampling/polygon_sampling.glsl new file mode 100644 index 00000000..44290119 --- /dev/null +++ b/ref_vk/shaders/peters2021-sampling/polygon_sampling.glsl @@ -0,0 +1,883 @@ +// Copyright (C) 2021, Christoph Peters, Karlsruhe Institute of Technology +// +// This source code file is licensed under both the three-clause BSD license +// and the GPLv3. You may select, at your option, one of the two licenses. The +// corresponding license headers follow: +// +// +// Three-clause BSD license: +// +// 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. +// +// +// GPLv3: +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#include "math_constants.glsl" + + +/*! This structure carries intermediate results that only need to be computed + once per polygon and shading point to take samples proportional to solid + angle. Sampling is performed by subdividing the convex polygon into + triangles as triangle fan around vertex 0 and then using our variant of + Arvo's method.*/ +struct solid_angle_polygon_t { + //! The number of vertices that form the polygon + uint vertex_count; + //! Normalized direction vectors from the shading point to each vertex + vec3 vertex_dirs[MAX_POLYGON_VERTEX_COUNT]; + /*! A few intermediate quantities about the triangle consisting of vertices + i + 1, 0, and i + 2. If the three vertices are v0, v1, v2, the entries + are determinant(mat3(v0, v1, v2)), dot(v0 + v1, v2) and + 1.0f + dot(v0, v1).*/ + vec3 triangle_parameters[MAX_POLYGON_VERTEX_COUNT - 2]; + //! At index i, this array holds the solid angle of the triangle fan formed + //! by vertices 0 to i + 2. + float fan_solid_angles[MAX_POLYGON_VERTEX_COUNT - 2]; + //! The total solid angle of the polygon + float solid_angle; +}; + + +/*! A piecewise polynomial approximation to positive_atan(y). The maximal + absolute error is 1.16e-05f. At least on Turing GPUs, it is faster but also + significantly less accurate. The proper atan has at most 2 ulps of error + there.*/ +float fast_positive_atan(float y) { + float rx; + float ry; + float rz; + rx = (abs(y) > 1.0f) ? (1.0f / abs(y)) : abs(y); + ry = rx * rx; + rz = fma(ry, 0.02083509974181652f, -0.08513300120830536); + rz = fma(ry, rz, 0.18014100193977356f); + rz = fma(ry, rz, -0.3302994966506958f); + ry = fma(ry, rz, 0.9998660087585449f); + rz = fma(-2.0f * ry, rx, M_HALF_PI); + rz = (abs(y) > 1.0f) ? rz : 0.0f; + rx = fma(rx, ry, rz); + return (y < 0.0f) ? (M_PI - rx) : rx; +} + + +/*! Returns an angle between 0 and M_PI such that tan(angle) == tangent. In + other words, it is a version of atan() that is offset to be non-negative. + Note that it may be switched to an approximate mode by the + USE_BIASED_PROJECTED_SOLID_ANGLE_SAMPLING flag.*/ +float positive_atan(float tangent) { +#ifdef USE_BIASED_PROJECTED_SOLID_ANGLE_SAMPLING + return fast_positive_atan(tangent); +#else + float offset = (tangent < 0.0f) ? M_PI : 0.0f; + return atan(tangent) + offset; +#endif +} + + +/*! Prepares all intermediate values to sample a triangle fan around vertex 0 + (e.g. a convex polygon) proportional to solid angle using our method. + \param vertex_count Number of vertices forming the polygon. + \param vertices List of vertex locations. + \param shading_position The location of the shading point. + \return Input for sample_solid_angle_polygon().*/ +solid_angle_polygon_t prepare_solid_angle_polygon_sampling(uint vertex_count, vec3 vertices[MAX_POLYGON_VERTEX_COUNT], vec3 shading_position) { + solid_angle_polygon_t polygon; + polygon.vertex_count = vertex_count; + // Normalize vertex directions + [[unroll]] + for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT; ++i) { + polygon.vertex_dirs[i] = normalize(vertices[i] - shading_position); + } + // Prepare a Householder transform that maps vertex 0 onto (+/-1, 0, 0). We + // only store the yz-components of that Householder vector and a factor of + // 2.0f / sqrt(abs(polygon.vertex_dirs[0].x) + 1.0f) is pulled in there to + // save on multiplications later. This approach is necessary to avoid + // numerical instabilities in determinant computation below. + float householder_sign = (polygon.vertex_dirs[0].x > 0.0f) ? -1.0f : 1.0f; + vec2 householder_yz = polygon.vertex_dirs[0].yz * (1.0f / (abs(polygon.vertex_dirs[0].x) + 1.0f)); + // Compute solid angles and prepare sampling + polygon.solid_angle = 0.0f; + float previous_dot_1_2 = dot(polygon.vertex_dirs[0], polygon.vertex_dirs[1]); + [[unroll]] + for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 2; ++i) { + if (i >= 1 && i + 2 >= vertex_count) break; + // We look at one triangle of the triangle fan at a time + vec3 vertices[3] = { + polygon.vertex_dirs[i + 1], + polygon.vertex_dirs[0], + polygon.vertex_dirs[i + 2]}; + float dot_0_1 = previous_dot_1_2; + float dot_0_2 = dot(vertices[0], vertices[2]); + float dot_1_2 = dot(vertices[1], vertices[2]); + previous_dot_1_2 = dot_1_2; + // Compute the bottom right minor of vertices after application of the + // Householder transform + float dot_householder_0 = fma(-householder_sign, vertices[0].x, dot_0_1); + float dot_householder_2 = fma(-householder_sign, vertices[2].x, dot_1_2); + mat2 bottom_right_minor = mat2( + fma(vec2(-dot_householder_0), householder_yz, vertices[0].yz), + fma(vec2(-dot_householder_2), householder_yz, vertices[2].yz)); + // The absolute value of the determinant of vertices equals the 2x2 + // determinant because the Householder transform turns the first column + // into (+/-1, 0, 0) + float simplex_volume = abs(determinant(bottom_right_minor)); + // Compute the solid angle of the triangle using a formula proposed by: + // A. Van Oosterom and J. Strackee, 1983, The Solid Angle of a + // Plane Triangle, IEEE Transactions on Biomedical Engineering 30:2 + // https://doi.org/10.1109/TBME.1983.325207 + float dot_0_2_plus_1_2 = dot_0_2 + dot_1_2; + float one_plus_dot_0_1 = 1.0f + dot_0_1; + float tangent = simplex_volume / (one_plus_dot_0_1 + dot_0_2_plus_1_2); + float triangle_solid_angle = 2.0f * positive_atan(tangent); + polygon.solid_angle += triangle_solid_angle; + polygon.fan_solid_angles[i] = polygon.solid_angle; + // Some intermediate results from above help us with sampling + polygon.triangle_parameters[i] = vec3(simplex_volume, dot_0_2_plus_1_2, one_plus_dot_0_1); + } + return polygon; +} + + +/*! An implementation of mix() using two fused-multiply add instructions. Used + because the native mix() implementation had stability issues in a few + spots. Credit to Fabian Giessen's blog, see: + https://fgiesen.wordpress.com/2012/08/15/linear-interpolation-past-present-and-future/ + */ +float mix_fma(float x, float y, float a) { + return fma(a, y, fma(-a, x, x)); +} + + +/*! Given the output of prepare_solid_angle_polygon_sampling(), this function + maps the given random numbers in the range from 0 to 1 to a normalized + direction vector providing a sample of the solid angle of the polygon in + the original space (used for arguments of + prepare_solid_angle_polygon_sampling()). Samples are distributed in + proportion to solid angle assuming uniform inputs.*/ +vec3 sample_solid_angle_polygon(solid_angle_polygon_t polygon, vec2 random_numbers) { + // Decide which triangle needs to be sampled + float target_solid_angle = polygon.solid_angle * random_numbers[0]; + float subtriangle_solid_angle = target_solid_angle; + vec3 parameters = polygon.triangle_parameters[0]; + vec3 vertices[3] = { + polygon.vertex_dirs[1], polygon.vertex_dirs[0], polygon.vertex_dirs[2] + }; + [[unroll]] + for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 3; ++i) { + if (i + 3 >= polygon.vertex_count || polygon.fan_solid_angles[i] >= target_solid_angle) break; + subtriangle_solid_angle = target_solid_angle - polygon.fan_solid_angles[i]; + vertices[0] = polygon.vertex_dirs[i + 2]; + vertices[2] = polygon.vertex_dirs[i + 3]; + parameters = polygon.triangle_parameters[i + 1]; + } + // Construct a new vertex 2 on the arc between vertices 0 and 2 such that + // the resulting triangle has solid angle subtriangle_solid_angle + vec2 cos_sin = vec2(cos(0.5f * subtriangle_solid_angle), sin(0.5f * subtriangle_solid_angle)); + vec3 offset = vertices[0] * (parameters[0] * cos_sin.x - parameters[1] * cos_sin.y) + vertices[2] * (parameters[2] * cos_sin.y); + vec3 new_vertex_2 = fma(2.0f * vec3(dot(vertices[0], offset) / dot(offset, offset)), offset, -vertices[0]); + // Now sample the line between vertex 1 and the newly created vertex 2 + float s2 = dot(vertices[1], new_vertex_2); + float s = mix_fma(1.0f, s2, random_numbers[1]); + float denominator = fma(-s2, s2, 1.0f); + float t_normed = sqrt(fma(-s, s, 1.0f) / denominator); + // s2 may exceed one due to rounding error. random_numbers[1] is the + // limit of t_normed for s2 -> 1. + t_normed = (denominator > 0.0f) ? t_normed : random_numbers[1]; + return fma(-t_normed, s2, s) * vertices[1] + t_normed * new_vertex_2; +} + + +/*! This structure carries intermediate results that only need to be computed + once per polygon and shading point to take samples proportional to + projected solid angle.*/ +struct projected_solid_angle_polygon_t { + //! The number of vertices that form the polygon + uint vertex_count; + /*! The x- and y-coordinates of each polygon vertex in a coordinate system + where the normal is the z-axis. The vertices are sorted + counterclockwise.*/ + vec2 vertices[MAX_POLYGON_VERTEX_COUNT]; + /*! For each vertex in vertices, this vector describes the ellipse for the + next edge in counterclockwise direction. The last entry is meaningless, + except in the central case. For vertex 0, it holds the outer ellipse. + \see ellipse_from_edge() */ + vec2 ellipses[MAX_POLYGON_VERTEX_COUNT]; + //! The inner ellipse adjacent to vertex 0. If the x-component is positive, + //! the central case is present. + vec2 inner_ellipse_0; + /*! At index i, this array holds the projected solid angle of the polygon + in the sector between (sorted) vertices i and (i + 1) % vertex_count + In the central case, entry vertex_count - 1 is meaningful, otherwise + not.*/ + float sector_projected_solid_angles[MAX_POLYGON_VERTEX_COUNT]; + //! The total projected solid angle of the polygon + float projected_solid_angle; +}; + + +//! Computes a * b - c * d with at most 1.5 ulps of error in the result. See +//! https://pharr.org/matt/blog/2019/11/03/difference-of-floats.html or +//! Claude-Pierre Jeannerod, Nicolas Louvet and Jean-Michel Muller, 2013, +//! Further analysis of Kahan's algorithm for the accurate computation of 2x2 +//! determinants, AMS Mathematics of Computation 82:284, +//! https://doi.org/10.1090/S0025-5718-2013-02679-8 +float kahan(float a, float b, float c, float d) { + // Uncomment the line below to improve efficiency but reduce accuracy + // return a * b - c * d; + float cd = c * d; + float error = fma(c, d, -cd); + float result = fma(a, b, -cd); + return result - error; +} + + +//! Implements a cross product using Kahan's algorithm for every single entry, +//! i.e. the error in each output entry is at most 1.5 ulps +vec3 cross_stable(vec3 lhs, vec3 rhs) { + return vec3( + kahan(lhs.y, rhs.z, lhs.z, rhs.y), + kahan(lhs.z, rhs.x, lhs.x, rhs.z), + kahan(lhs.x, rhs.y, lhs.y, rhs.x) + ); +} + + +//! \return The given vector, rotated 90 degrees counterclockwise around the +//! origin +vec2 rotate_90(vec2 input_vector) { + return vec2(-input_vector.y, input_vector.x); +} + + +//! \return true iff the given ellipse is marked as inner ellipse, i.e. iff the +//! spherical polygon that is bounded by it is further away from the zenith +//! than this ellipse. +bool is_inner_ellipse(vec2 ellipse) { + // If the implementation below causes you trouble, e.g. because you want to + // port to a different language, you may replace it by the commented line + // below but it will lead to seldom artifacts when ellipse.x == 0.0f. + // return ellipse.x < 0.0f; + // Extract the sign bit from ellipse.x (to be able to tell apart +0 and -0) + return (floatBitsToUint(ellipse.x) & 0x80000000) != 0; +} + + +//! \return true iff the given polygon contains the zenith (also known as +//! normal vector). +bool is_central_case(projected_solid_angle_polygon_t polygon) { + return polygon.inner_ellipse_0.x > 0.0f; +} + + +/*! Takes the great circle for the plane through the origin and the given two + points and constructs an ellipse for its projection to the xy-plane. + \return A vector ellipse such that a point is on the ellipse if and only if + dot(ellipse, point) * dot(ellipse, point) + dot(point, point) == 1.0f. + In other words, it is a normal vector of the great circle in half- + vector space. The sign bit of x encodes whether the edge runs clockwise + from vertex_0 to vertex_1 (inner ellipse) or not. + \see is_inner_ellipse() */ +vec2 ellipse_from_edge(vec3 vertex_0, vec3 vertex_1) { + vec3 normal = cross_stable(vertex_0, vertex_1); + float scaling = 1.0f / normal.z; + scaling = is_inner_ellipse(normal.xy) ? -scaling : scaling; + vec2 ellipse = normal.xy * scaling; + // By convention, degenerate ellipses are outer ellipses, i.e. the first + // component is infinite + ellipse.x = (normal.z != 0.0f) ? ellipse.x : M_INFINITY; + return ellipse; +} + + +//! Transforms the given point using the matrix that characterizes the given +//! ellipse (as produced by ellipse_from_edge()). To be precise, this matrix is +//! identity + outerProduct(ellipse, ellipse). +vec2 ellipse_transform(vec2 ellipse, vec2 point) { + return fma(vec2(dot(ellipse, point)), ellipse, point); +} + + +//! Given an ellipse in the format produced by ellipse_from_edge(), this +//! function returns the determinant of the matrix characterizing this +//! ellipse. +float get_ellipse_det(vec2 ellipse) { + return fma(ellipse.x, ellipse.x, fma(ellipse.y, ellipse.y, 1.0f)); +} + +//! Returns the reciprocal square root of the ellipse determinant produced by +//! get_ellipse_det(). +float get_ellipse_rsqrt_det(vec2 ellipse) { + return inversesqrt(get_ellipse_det(ellipse)); +} + +//! \return Reciprocal square of get_ellipse_direction_factor(ellipse, dir) +float get_ellipse_direction_factor_rsq(vec2 ellipse, vec2 dir) { + float ellipse_dot_dir = dot(ellipse, dir); + float dir_dot_dir = dot(dir, dir); + return fma(ellipse_dot_dir, ellipse_dot_dir, dir_dot_dir); +} + +/*! Computes a factor by which a direction vector has to be multiplied to + obtain a point on the given ellipse. + \param ellipse An ellipse as produced by ellipse_from_edge(). + \param dir The direction vector to be scaled onto the ellipse. + \return get_ellipse_direction_factor(ellipse, dir) * dir is a point on + the ellipse.*/ +float get_ellipse_direction_factor(vec2 ellipse, vec2 dir) { + return inversesqrt(get_ellipse_direction_factor_rsq(ellipse, dir)); +} + +//! Like get_ellipse_direction_factor() but assumes that the given direction is +//! normalized. Faster. +float get_ellipse_normalized_direction_factor(vec2 ellipse, vec2 normalized_dir) { + float ellipse_dot_dir = dot(ellipse, normalized_dir); + return inversesqrt(fma(ellipse_dot_dir, ellipse_dot_dir, 1.0f)); +} + + +//! Helper for get_area_between_ellipses_in_sector() and +//! sample_sector_between_ellipses() +float get_area_between_ellipses_in_sector_from_tangents(float inner_rsqrt_det, float inner_tangent, float outer_rsqrt_det, float outer_tangent) { + float inner_area = inner_rsqrt_det * positive_atan(inner_tangent); + float result = fma(outer_rsqrt_det, positive_atan(outer_tangent), -inner_area); + // Sort out NaNs and negative results + return (result > 0.0f) ? (0.5f * result) : 0.0f; +} + + +/*! Returns the signed area between the given outer and inner ellipses within + the sector enclosed by dir_0 and dir_1. Besides ellipses as produced by + ellipse_from_edge(), you also have to pass output of + get_ellipse_rsqrt_det(). Faster than calling get_ellipse_area_in_sector() + twice.*/ +float get_area_between_ellipses_in_sector(vec2 inner_ellipse, float inner_rsqrt_det, vec2 outer_ellipse, float outer_rsqrt_det, vec2 dir_0, vec2 dir_1) { + float det_dirs = max(+0.0f, dot(dir_1, rotate_90(dir_0))); + float inner_dot = inner_rsqrt_det * dot(dir_0, ellipse_transform(inner_ellipse, dir_1)); + float outer_dot = outer_rsqrt_det * dot(dir_0, ellipse_transform(outer_ellipse, dir_1)); + return get_area_between_ellipses_in_sector_from_tangents( + inner_rsqrt_det, det_dirs / inner_dot, + outer_rsqrt_det, det_dirs / outer_dot); +} + + +/*! Computes the area for the intersection of the given ellipse and the sector + between the given two directions (going counterclockwise from dir_0 to + dir_1 for at most 180 degrees). The scaling of the directions is + irrelevant. + \see ellipse_from_edge() */ +float get_ellipse_area_in_sector(vec2 ellipse, vec2 dir_0, vec2 dir_1) { + float ellipse_rsqrt_det = get_ellipse_rsqrt_det(ellipse); + float det_dirs = max(+0.0f, dot(dir_1, rotate_90(dir_0))); + float ellipse_dot = ellipse_rsqrt_det * dot(dir_0, ellipse_transform(ellipse, dir_1)); + float area = 0.5f * ellipse_rsqrt_det * positive_atan(det_dirs / ellipse_dot); + // For degenerate ellipses, the result may be NaN but must be 0.0f + return (ellipse_rsqrt_det > 0.0f) ? area : 0.0f; +} + + +/*! Swaps vertices lhs and rhs (along with corresponding ellipses) of the given + polygon if the shorter path from lhs to rhs is clockwise. If the vertices + have identical directions in the xy-plane, vertices with degenerate + ellipses come first. + \note To avoid costly register spilling, lhs and rhs must be compile time + constants.*/ +void compare_and_swap(inout projected_solid_angle_polygon_t polygon, uint lhs, uint rhs) { + vec2 lhs_copy = polygon.vertices[lhs]; + // This line is designed to agree with the implementation of cross_stable + // for the z-coordinate, which determines if ellipses are inner or outer + float normal_z = kahan(lhs_copy.x, -polygon.vertices[rhs].y, lhs_copy.y, -polygon.vertices[rhs].x); + // Tie breaker: If both vertices are at the same angle (i.e. on a common + // great circle through the zenith), the one with the degenerate ellipse + // comes first + bool swap = (normal_z == 0.0f) ? isinf(polygon.ellipses[rhs].x) : (normal_z > 0.0f); + polygon.vertices[lhs] = swap ? polygon.vertices[rhs] : lhs_copy; + polygon.vertices[rhs] = swap ? lhs_copy : polygon.vertices[rhs]; + lhs_copy = polygon.ellipses[lhs]; + polygon.ellipses[lhs] = swap ? polygon.ellipses[rhs] : lhs_copy; + polygon.ellipses[rhs] = swap ? lhs_copy : polygon.ellipses[rhs]; +} + + +//! Sorts the vertices of the given convex polygon counterclockwise using a +//! special sorting network. For non-convex polygons, the method may fail. +void sort_convex_polygon_vertices(inout projected_solid_angle_polygon_t polygon) { + if (polygon.vertex_count == 3) { + compare_and_swap(polygon, 1, 2); + } +#if MAX_POLYGON_VERTEX_COUNT >= 4 + else if (polygon.vertex_count == 4) { + compare_and_swap(polygon, 1, 3); + } +#endif +#if MAX_POLYGON_VERTEX_COUNT >= 5 + else if (polygon.vertex_count == 5) { + compare_and_swap(polygon, 2, 4); + compare_and_swap(polygon, 1, 3); + compare_and_swap(polygon, 1, 2); + compare_and_swap(polygon, 0, 3); + compare_and_swap(polygon, 3, 4); + } +#endif +#if MAX_POLYGON_VERTEX_COUNT >= 6 + else if (polygon.vertex_count == 6) { + compare_and_swap(polygon, 3, 5); + compare_and_swap(polygon, 2, 4); + compare_and_swap(polygon, 1, 5); + compare_and_swap(polygon, 0, 4); + compare_and_swap(polygon, 4, 5); + compare_and_swap(polygon, 1, 3); + } +#endif +#if MAX_POLYGON_VERTEX_COUNT >= 7 + else if (polygon.vertex_count == 7) { + compare_and_swap(polygon, 2, 5); + compare_and_swap(polygon, 1, 6); + compare_and_swap(polygon, 5, 6); + compare_and_swap(polygon, 3, 4); + compare_and_swap(polygon, 0, 4); + compare_and_swap(polygon, 4, 6); + compare_and_swap(polygon, 1, 3); + compare_and_swap(polygon, 3, 5); + compare_and_swap(polygon, 4, 5); + } +#endif +#if MAX_POLYGON_VERTEX_COUNT >= 8 + else if (polygon.vertex_count == 8) { + compare_and_swap(polygon, 2, 6); + compare_and_swap(polygon, 3, 7); + compare_and_swap(polygon, 1, 5); + compare_and_swap(polygon, 0, 4); + compare_and_swap(polygon, 4, 6); + compare_and_swap(polygon, 5, 7); + compare_and_swap(polygon, 6, 7); + compare_and_swap(polygon, 4, 5); + compare_and_swap(polygon, 1, 3); + } +#endif + // This comparison is shared by all sorting networks + compare_and_swap(polygon, 0, 2); +#if MAX_POLYGON_VERTEX_COUNT >= 4 + if (polygon.vertex_count >= 4) { + // This comparison is shared by all sorting networks except the one for + // triangles + compare_and_swap(polygon, 2, 3); + } +#endif + // This comparison is shared by all sorting networks + compare_and_swap(polygon, 0, 1); +} + + +/*! Prepares all intermediate values to sample a convex polygon proportional to + projected solid angle. + \param vertex_count Number of vertices forming the polygon (at least 3). + \param vertices List of vertex locations in a coordinate system where the + shading position is the origin and the normal is the z-axis. The + polygon should be already clipped against the plane z=0. If + vertex_count < MAX_POLYGON_VERTEX_COUNT, the first vertex has to be + repeated at vertex_count. They need not be normalized but if you + encounter issues with under- or overflow (e.g. NaN or INF outputs), + normalization may help. The polygon must be convex, and the winding of + the vertices as seen from the origin must be clockwise. No three + vertices should be collinear. + \return Intermediate values for sampling.*/ +projected_solid_angle_polygon_t prepare_projected_solid_angle_polygon_sampling(uint vertex_count, vec3 vertices[MAX_POLYGON_VERTEX_COUNT]) { + projected_solid_angle_polygon_t polygon; + // Copy vertices and assign ellipses + polygon.vertex_count = vertex_count; + polygon.inner_ellipse_0 = vec2(1.0f, 0.0f); + polygon.vertices[0] = vertices[0].xy; + polygon.ellipses[0] = ellipse_from_edge(vertices[0], vertices[1]); + vec2 previous_ellipse = polygon.ellipses[0]; + [[unroll]] + for (uint i = 1; i != MAX_POLYGON_VERTEX_COUNT; ++i) { + polygon.vertices[i] = vertices[i].xy; + if (i > 2 && i == polygon.vertex_count) break; + vec2 ellipse = ellipse_from_edge(vertices[i], vertices[(i + 1) % MAX_POLYGON_VERTEX_COUNT]); + bool ellipse_inner = is_inner_ellipse(ellipse); + // If the edge is an inner edge, the order is going to flip + polygon.ellipses[i] = ellipse_inner ? previous_ellipse : ellipse; + // In doing so, we drop one ellipse, unless we store it explicitly + polygon.inner_ellipse_0 = (is_inner_ellipse(previous_ellipse) && !ellipse_inner) ? previous_ellipse : polygon.inner_ellipse_0; + previous_ellipse = ellipse; + } + // Same thing for the first vertex (i.e. here we close the loop) + vec2 ellipse = polygon.ellipses[0]; + bool ellipse_inner = is_inner_ellipse(ellipse); + polygon.ellipses[0] = ellipse_inner ? previous_ellipse : ellipse; + polygon.inner_ellipse_0 = (is_inner_ellipse(previous_ellipse) && !ellipse_inner) ? previous_ellipse : polygon.inner_ellipse_0; + // Compute projected solid angles per sector and in total + polygon.projected_solid_angle = 0.0f; + if (is_central_case(polygon)) { + // In the central case, we have polygon.vertex_count sectors, each + // bounded by a single ellipse + [[unroll]] + for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT; ++i) { + if (i > 2 && i == polygon.vertex_count) break; + polygon.sector_projected_solid_angles[i] = get_ellipse_area_in_sector(polygon.ellipses[i], polygon.vertices[i], polygon.vertices[(i + 1) % MAX_POLYGON_VERTEX_COUNT]); + polygon.projected_solid_angle += polygon.sector_projected_solid_angles[i]; + } + } + else { + // Sort vertices counter clockwise + sort_convex_polygon_vertices(polygon); + // There are polygon.vertex_count - 1 sectors, each bounded by an inner + // and an outer ellipse + vec2 inner_ellipse = polygon.inner_ellipse_0; + float inner_rsqrt_det = get_ellipse_rsqrt_det(inner_ellipse); + vec2 outer_ellipse; + float outer_rsqrt_det; + [[unroll]] + for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 1; ++i) { + if (i > 1 && i + 1 == polygon.vertex_count) break; + vec2 vertex_ellipse = polygon.ellipses[i]; + bool vertex_inner = is_inner_ellipse(vertex_ellipse); + float vertex_rsqrt_det = get_ellipse_rsqrt_det(vertex_ellipse); + if (i == 0) { + outer_ellipse = vertex_ellipse; + outer_rsqrt_det = vertex_rsqrt_det; + } + else { + inner_ellipse = vertex_inner ? vertex_ellipse : inner_ellipse; + inner_rsqrt_det = vertex_inner ? vertex_rsqrt_det : inner_rsqrt_det; + outer_ellipse = vertex_inner ? outer_ellipse : vertex_ellipse; + outer_rsqrt_det = vertex_inner ? outer_rsqrt_det : vertex_rsqrt_det; + } + polygon.sector_projected_solid_angles[i] = get_area_between_ellipses_in_sector( + inner_ellipse, inner_rsqrt_det, outer_ellipse, outer_rsqrt_det, polygon.vertices[i], polygon.vertices[i + 1]); + polygon.projected_solid_angle += polygon.sector_projected_solid_angles[i]; + } + } + return polygon; +} + + +/*! \return A scalar multiple of rhs that is not too far from being normalized. + For the result, length() returns something between sqrt(2.0f) and 8.0f. + The sign gets flipped such that the dot product of semi_circle and the + result is non-negative. + \note Introduces less latency than normalize() and does not use special + functions. Useful to avoid under- and overflow when working with + homogeneous coordinates. The result is undefined if rhs is zero.*/ +vec2 normalize_approx_and_flip(vec2 rhs, vec2 semi_circle) { + float scaling = abs(rhs.x) + abs(rhs.y); + // By flipping each bit on the exponent E, we turn it into 1 - E, which is + // close enough to a reciprocal. + scaling = uintBitsToFloat(floatBitsToUint(scaling) ^ 0x7F800000u); + // If the line above causes you any sort of trouble (e.g. because you want + // to port the code to another language or you are doing differentiable + // rendering), just use this one instead: + // scaling = 1.0f / scaling; + // Flip the sign as needed + scaling = (dot(rhs, semi_circle) >= 0.0f) ? scaling : -scaling; + return scaling * rhs; +} + + +/*! Returns a solution to the given homogeneous quadratic equation, i.e. a + non-zero vector root such that dot(root, quadratic * root) == 0.0f. The + returned root depends continuously on quadratic. Pass -quadratic if you + want the other root. + \note The implementation is as proposed by Blinn, except that we do not + have a special case for quadratic[0][1] + quadratic[1][0] == 0.0f. Unlike + the standard quadratic formula, it allows us to postpone a division and is + stable in all cases. + James F. Blinn 2006, How to Solve a Quadratic Equation, Part 2, IEEE + Computer Graphics and Applications 26:2 https://doi.org/10.1109/MCG.2006.35 +*/ +vec2 solve_homogeneous_quadratic(mat2 quadratic) { + float coeff_xy = 0.5f * (quadratic[0][1] + quadratic[1][0]); + float sqrt_discriminant = sqrt(max(0.0f, coeff_xy * coeff_xy - quadratic[0][0] * quadratic[1][1])); + float scaled_root = abs(coeff_xy) + sqrt_discriminant; + return (coeff_xy >= 0.0f) ? vec2(scaled_root, -quadratic[0][0]) : vec2(quadratic[1][1], scaled_root); +} + + +/*! Generates a sample between two ellipses and in a specified sector. The + sample is distributed uniformly with respect to the area measure. + \param random_numbers A pair of independent uniform random numbers on [0,1] + \param target_area random_numbers[0] multiplied by the projected solid + angle of the area to be sampled. + \param inner_ellipse, outer_ellipse The inner and outer ellipse, as + produced by ellipse_from_edge(). + \param dir_0, dir_1 Two direction vectors bounding the sector. They need + not be normalized. + \param iteration_count The number of iterations to perform. Lower values + trade speed for bias. Two iterations give practically no bias. + \return The sample in Cartesian coordinates.*/ +vec2 sample_sector_between_ellipses(vec2 random_numbers, float target_area, vec2 inner_ellipse, vec2 outer_ellipse, vec2 dir_0, vec2 dir_1, uint iteration_count) { + // For the initialization, split the sector in half + vec2 quad_dirs[3]; + quad_dirs[0] = normalize(dir_0); + quad_dirs[2] = normalize(dir_1); + quad_dirs[1] = quad_dirs[0] + quad_dirs[2]; + // Compute where these lines intersect the ellipses. The six intersection + // points define two adjacent quads. + float normalization_factor[2][3] = { + { + get_ellipse_normalized_direction_factor(inner_ellipse, quad_dirs[0]), + get_ellipse_direction_factor(inner_ellipse, quad_dirs[1]), + get_ellipse_normalized_direction_factor(inner_ellipse, quad_dirs[2]) + }, + { + get_ellipse_normalized_direction_factor(outer_ellipse, quad_dirs[0]), + get_ellipse_direction_factor(outer_ellipse, quad_dirs[1]), + get_ellipse_normalized_direction_factor(outer_ellipse, quad_dirs[2]) + } + }; + // Compute the relative size of the areas inside these quads + float sector_areas[2] = { + normalization_factor[1][0] * normalization_factor[1][1] - normalization_factor[0][0] * normalization_factor[0][1], + normalization_factor[1][1] * normalization_factor[1][2] - normalization_factor[0][1] * normalization_factor[0][2] + }; + // Now pick which of the two quads should be sampled for the + // initialization. If it is not the second, we move data such that the + // relevant array indices are 1 and 2 anyway. + float target_quad_area = mix_fma(-sector_areas[0], sector_areas[1], random_numbers[0]); + quad_dirs[2] = (target_quad_area <= 0.0f) ? quad_dirs[0] : quad_dirs[2]; + normalization_factor[0][2] = (target_quad_area <= 0.0f) ? normalization_factor[0][0] : normalization_factor[0][2]; + normalization_factor[1][2] = (target_quad_area <= 0.0f) ? normalization_factor[1][0] : normalization_factor[1][2]; + target_quad_area += (target_quad_area <= 0.0f) ? sector_areas[0] : -sector_areas[1]; + // We have been a bit lazy about area computation before but now we need + // all the factors (except for a factor of 0.5 that cancels with a 2 later) + target_quad_area *= abs(determinant(mat2(quad_dirs[1], quad_dirs[2]))); + // Construct normal vectors for the inner and outer edge of the selected + // quad. We construct the normal like a half vector (i.e. by addition) + // because it is less prone to cancellation than an approach using the edge + // direction (i.e. subtraction of sometimes nearly identical vectors) + vec2 quad_normals[2] = { + quad_dirs[1] * normalization_factor[0][1] + quad_dirs[2] * normalization_factor[0][2], + quad_dirs[1] * normalization_factor[1][1] + quad_dirs[2] * normalization_factor[1][2] + }; + quad_normals[0] = ellipse_transform(inner_ellipse, quad_normals[0]); + quad_normals[1] = ellipse_transform(outer_ellipse, quad_normals[1]); + // Construct complete line equations + float quad_offsets[2] = { + dot(quad_normals[0], quad_dirs[1]) * normalization_factor[0][1], + dot(quad_normals[1], quad_dirs[1]) * normalization_factor[1][1] + }; + // Now sample the direction within the selected quad by constructing a + // quadratic equation. This is the initialization for the iteration. + mat2 quadratic = outerProduct((quad_offsets[1] * normalization_factor[1][2]) * rotate_90(quad_dirs[2]), quad_normals[0]); + quadratic -= outerProduct((quad_offsets[0] * normalization_factor[0][2]) * rotate_90(quad_dirs[2]) + target_quad_area * quad_normals[0], quad_normals[1]); + vec2 current_dir = solve_homogeneous_quadratic(quadratic); + +#ifndef USE_BIASED_PROJECTED_SOLID_ANGLE_SAMPLING + // For boundary values, the initialization is perfect but the iteration may + // be unstable, so we disable it + float acceptable_error = 1.0e-5f; + iteration_count = (abs(random_numbers.x - 0.5f) <= 0.5f - acceptable_error) ? iteration_count : 0; + + // Now refine this initialization iteratively + float inner_rsqrt_det = get_ellipse_rsqrt_det(inner_ellipse); + float outer_rsqrt_det = get_ellipse_rsqrt_det(outer_ellipse); + [[dont_unroll]] + for (uint i = 0; i != iteration_count; ++i) { + // Avoid under- or overflow and flip the sign so that the clamping to + // zero below makes sense + current_dir = normalize_approx_and_flip(current_dir, quad_dirs[1]); + // Transform current_dir using both ellipses + vec2 inner_dir = ellipse_transform(inner_ellipse, current_dir); + vec2 outer_dir = ellipse_transform(outer_ellipse, current_dir); + // Evaluate the objective function (reusing inner_dir and outer_dir) + float det_dirs = max(+0.0f, dot(current_dir, rotate_90(quad_dirs[0]))); + float error = target_area - get_area_between_ellipses_in_sector_from_tangents( + inner_rsqrt_det, det_dirs / (inner_rsqrt_det * dot(quad_dirs[0], inner_dir)), + outer_rsqrt_det, det_dirs / (outer_rsqrt_det * dot(quad_dirs[0], outer_dir))); + // Construct a homogeneous quadratic whose solutions include the next + // step of the iteration + quadratic = outerProduct(inner_dir - outer_dir, rotate_90(current_dir)) - outerProduct((2.0f * error) * inner_dir, outer_dir); + current_dir = solve_homogeneous_quadratic(quadratic); + } +#endif + + // The halved sector is at most 90 degrees large, so the dot product with + // the half vector has to be positive + current_dir = (dot(current_dir, quad_dirs[1]) >= 0.0f) ? current_dir : -current_dir; + // Sample a squared radius uniformly between the two ellipses + float inner_factor = 1.0f / get_ellipse_direction_factor_rsq(inner_ellipse, current_dir); + float outer_factor = 1.0f / get_ellipse_direction_factor_rsq(outer_ellipse, current_dir); + current_dir *= sqrt(mix_fma(inner_factor, outer_factor, random_numbers[1])); + return current_dir; +} + + +/*! Produces a sample in the solid angle of the given polygon. If the random + numbers are uniform in [0,1]^2, the sample is uniform in the projected + solid angle of the polygon. + \param polygon Output of prepare_projected_solid_angle_polygon_sampling(). + \param random_numbers A uniform point in [0,1]^2. + \return A sample on the upper hemisphere (i.e. z>=0) in Cartesian + coordinates.*/ +vec3 sample_projected_solid_angle_polygon(projected_solid_angle_polygon_t polygon, vec2 random_numbers) { + float target_projected_solid_angle = random_numbers[0] * polygon.projected_solid_angle; + // Distinguish between the central case + vec3 sampled_dir; + vec2 outer_ellipse; + vec2 dir_0; + if (is_central_case(polygon)) { + // Select a sector and copy the relevant attributes + [[unroll]] + for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT; ++i) { + if (i > 0) { + target_projected_solid_angle -= polygon.sector_projected_solid_angles[i - 1]; + } + outer_ellipse = polygon.ellipses[i]; + dir_0 = polygon.vertices[i]; + if ((i >= 2 && i + 1 == polygon.vertex_count) || target_projected_solid_angle < polygon.sector_projected_solid_angles[i]) + break; + } + // Sample a direction within the sector + float sqrt_det = sqrt(get_ellipse_det(outer_ellipse)); + float angle = 2.0f * target_projected_solid_angle * sqrt_det; + sampled_dir.xy = (cos(angle) * sqrt_det) * dir_0 + sin(angle) * rotate_90(ellipse_transform(outer_ellipse, dir_0)); + // Sample a squared radius uniformly within the ellipse + sampled_dir.xy *= sqrt(random_numbers[1] / get_ellipse_direction_factor_rsq(outer_ellipse, sampled_dir.xy)); + } + // And the decentral case + else { + // Select a sector and copy the relevant attributes + float sector_projected_solid_angle; + vec2 inner_ellipse = polygon.inner_ellipse_0; + vec2 dir_1; + [[unroll]] + for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 1; ++i) { + vec2 vertex_ellipse = polygon.ellipses[i]; + if (i == 0) { + outer_ellipse = vertex_ellipse; + } + else { + target_projected_solid_angle -= polygon.sector_projected_solid_angles[i - 1]; + bool vertex_inner = is_inner_ellipse(vertex_ellipse); + inner_ellipse = vertex_inner ? vertex_ellipse : inner_ellipse; + outer_ellipse = vertex_inner ? outer_ellipse : vertex_ellipse; + } + dir_0 = polygon.vertices[i]; + dir_1 = polygon.vertices[i + 1]; + sector_projected_solid_angle = polygon.sector_projected_solid_angles[i]; + if ((i >= 1 && i + 2 == polygon.vertex_count) || target_projected_solid_angle < sector_projected_solid_angle) + break; + } + // Sample it + random_numbers[0] = target_projected_solid_angle / sector_projected_solid_angle; + sampled_dir.xy = sample_sector_between_ellipses(random_numbers, target_projected_solid_angle, inner_ellipse, outer_ellipse, dir_0, dir_1, 2); + } + // Construct the sample + sampled_dir.z = sqrt(max(0.0f, fma(-sampled_dir.x, sampled_dir.x, fma(-sampled_dir.y, sampled_dir.y, 1.0f)))); + return sampled_dir; +} + + +/*! Determines the error of a sample from the projected solid angle of a + polygon due to the iterative procedure. + \param polygon Output of prepare_projected_solid_angle_polygon_sampling(). + \param random_numbers The value passed for random_numbers in + sample_projected_solid_angle_polygon(). + \param sampled_dir The direction returned by + sample_projected_solid_angle_polygon(). + \return The first component is the signed backward error: The difference + between random_number_0 and the random number that would yield + sampled_dir with perfect computation. The second component is the + backward error, multiplied by the projected solid angle of the polygon. + The third component is the signed forward error (at least a first-order + estimate of it): The difference between the exact result of sampling + and the actual result in radians. Note that all of these are subject + to rounding error themselves. In the central case, zero is returned.*/ +vec3 compute_projected_solid_angle_polygon_sampling_error(projected_solid_angle_polygon_t polygon, vec2 random_numbers, vec3 sampled_dir) { + float target_projected_solid_angle = random_numbers[0] * polygon.projected_solid_angle; + // In the central case, the sampling procedure is exact except for rounding + // error + if (is_central_case(polygon)) { + return vec3(0.0f); + } + // In the other case, we repeat some computations to find the error + else { + // Select a sector and copy the relevant attributes + float sector_projected_solid_angle; + vec2 outer_ellipse; + vec2 inner_ellipse = polygon.inner_ellipse_0; + vec2 dir_0; + [[unroll]] + for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 1; ++i) { + if ((i > 1 && i + 1 == polygon.vertex_count) || (i > 0 && target_projected_solid_angle < 0.0f)) + break; + sector_projected_solid_angle = polygon.sector_projected_solid_angles[i]; + target_projected_solid_angle -= sector_projected_solid_angle; + vec2 vertex_ellipse = polygon.ellipses[i]; + bool vertex_inner = is_inner_ellipse(vertex_ellipse); + if (i == 0) { + outer_ellipse = vertex_ellipse; + } + else { + inner_ellipse = vertex_inner ? vertex_ellipse : inner_ellipse; + outer_ellipse = vertex_inner ? outer_ellipse : vertex_ellipse; + } + dir_0 = polygon.vertices[i]; + } + target_projected_solid_angle += sector_projected_solid_angle; + // Compute the area in the sector from sampled_dir and dir_0 between + // the two ellipses + float sampled_projected_solid_angle = get_area_between_ellipses_in_sector( + inner_ellipse, get_ellipse_rsqrt_det(inner_ellipse), + outer_ellipse, get_ellipse_rsqrt_det(outer_ellipse), + dir_0, sampled_dir.xy); + // Compute error in the projected solid angle + float scaled_backward_error = target_projected_solid_angle - sampled_projected_solid_angle; + float backward_error = scaled_backward_error / polygon.projected_solid_angle; + // Evaluate the derivative of the sampled direction with respect to the + // projected solid angle + mat2 constraint_matrix; + vec2 inner_dir = ellipse_transform(inner_ellipse, sampled_dir.xy); + vec2 outer_dir = ellipse_transform(outer_ellipse, sampled_dir.xy); + float inner_factor = 1.0f / dot(sampled_dir.xy, inner_dir); + float outer_factor = 1.0f / dot(sampled_dir.xy, outer_dir); + constraint_matrix[0] = 0.5f * (inner_factor - outer_factor) * rotate_90(sampled_dir.xy); + constraint_matrix[1] = ((1.0f - random_numbers[1]) / (inner_factor * inner_factor)) * inner_dir; + constraint_matrix[1] += (random_numbers[1] / (outer_factor * outer_factor)) * outer_dir; + constraint_matrix = transpose(constraint_matrix); + vec3 sample_derivative; + sample_derivative.xy = (1.0f / determinant(constraint_matrix)) * vec2(constraint_matrix[1][1], -constraint_matrix[0][1]); + sample_derivative.z = -dot(sampled_dir.xy, sample_derivative.xy) / sampled_dir.z; + // Evaluate the forward error in radians + float forward_error = length(sample_derivative) * scaled_backward_error; + // Return both errors + return vec3(backward_error, scaled_backward_error, forward_error); + } +} diff --git a/ref_vk/shaders/ray.rgen b/ref_vk/shaders/ray.rgen index c173c330..ecaf73f1 100644 --- a/ref_vk/shaders/ray.rgen +++ b/ref_vk/shaders/ray.rgen @@ -1,6 +1,8 @@ #version 460 core #extension GL_EXT_nonuniform_qualifier : enable #extension GL_GOOGLE_include_directive : require +#extension GL_EXT_control_flow_attributes : require + #include "ray_common.glsl" #include "ray_kusochki.glsl" #include "noise.glsl" @@ -12,7 +14,7 @@ const float shadow_offset_fudge = .1; const float pdf_culling_threshold = 1e6;//100.; const float color_factor = 1.; -const float color_culling_threshold = 1e-6;//600./color_factor; +const float color_culling_threshold = 0;//600./color_factor; const float throughput_threshold = 1e-3; layout (constant_id = 4) const float LIGHT_GRID_CELL_SIZE = 256.; @@ -54,239 +56,8 @@ layout(location = PAYLOAD_LOCATION_OPAQUE) rayPayloadEXT RayPayloadOpaque payloa layout(location = PAYLOAD_LOCATION_SHADOW) rayPayloadEXT RayPayloadShadow payload_shadow; layout(location = PAYLOAD_LOCATION_ADDITIVE) rayPayloadEXT RayPayloadAdditive payload_additive; -bool shadowed(vec3 pos, vec3 dir, float dist) { - payload_shadow.hit_type = SHADOW_HIT; - const uint flags = 0 - //| gl_RayFlagsCullFrontFacingTrianglesEXT - //| gl_RayFlagsOpaqueEXT - | gl_RayFlagsTerminateOnFirstHitEXT - | gl_RayFlagsSkipClosestHitShaderEXT - ; - traceRayEXT(tlas, - flags, - GEOMETRY_BIT_OPAQUE, - SHADER_OFFSET_HIT_SHADOW_BASE, SBT_RECORD_SIZE, SHADER_OFFSET_MISS_SHADOW, - pos, 0., dir, dist - shadow_offset_fudge, PAYLOAD_LOCATION_SHADOW); - return payload_shadow.hit_type == SHADOW_HIT; -} - -// TODO join with just shadowed() -bool shadowedSky(vec3 pos, vec3 dir, float dist) { - payload_shadow.hit_type = SHADOW_HIT; - const uint flags = 0 - //| gl_RayFlagsCullFrontFacingTrianglesEXT - //| gl_RayFlagsOpaqueEXT - //| gl_RayFlagsTerminateOnFirstHitEXT - //| gl_RayFlagsSkipClosestHitShaderEXT - ; - traceRayEXT(tlas, - flags, - GEOMETRY_BIT_OPAQUE, - SHADER_OFFSET_HIT_SHADOW_BASE, SBT_RECORD_SIZE, SHADER_OFFSET_MISS_SHADOW, - pos, 0., dir, dist - shadow_offset_fudge, PAYLOAD_LOCATION_SHADOW); - return payload_shadow.hit_type != SHADOW_SKY; -} - -// This is an entry point for evaluation of all other BRDFs based on selected configuration (for direct light) -void evalSplitBRDF(vec3 N, vec3 L, vec3 V, MaterialProperties material, out vec3 diffuse, out vec3 specular) { - // Prepare data needed for BRDF evaluation - unpack material properties and evaluate commonly used terms (e.g. Fresnel, NdotL, ...) - const BrdfData data = prepareBRDFData(N, L, V, material); - - // Ignore V and L rays "below" the hemisphere - //if (data.Vbackfacing || data.Lbackfacing) return vec3(0.0f, 0.0f, 0.0f); - - // Eval specular and diffuse BRDFs - specular = evalSpecular(data); - diffuse = evalDiffuse(data); - - // Combine specular and diffuse layers -#if COMBINE_BRDFS_WITH_FRESNEL - // Specular is already multiplied by F, just attenuate diffuse - diffuse *= vec3(1.) - data.F; -#endif -} - -float triangleSolidAngle(vec3 p, vec3 a, vec3 b, vec3 c) { - a = normalize(a - p); - b = normalize(b - p); - c = normalize(c - p); - - // TODO horizon culling - const float tanHalfOmega = dot(a, cross(b,c)) / (1. + dot(b,c) + dot(c,a) + dot(a,b)); - - return atan(tanHalfOmega) * 2.; -} - -vec3 baryMix(vec3 v1, vec3 v2, vec3 v3, vec2 bary) { - return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; -} - -vec2 baryMix(vec2 v1, vec2 v2, vec2 v3, vec2 bary) { - return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; -} - -float computeTriangleContrib(uint triangle_index, uint index_offset, uint vertex_offset, mat4x3 xform, vec3 pos) { - const uint first_index_offset = index_offset + triangle_index * 3; - - const uint vi1 = uint(indices[first_index_offset+0]) + vertex_offset; - const uint vi2 = uint(indices[first_index_offset+1]) + vertex_offset; - const uint vi3 = uint(indices[first_index_offset+2]) + vertex_offset; - - const vec3 v1 = (xform * vec4(vertices[vi1].pos, 1.)).xyz; - const vec3 v2 = (xform * vec4(vertices[vi2].pos, 1.)).xyz; - const vec3 v3 = (xform * vec4(vertices[vi3].pos, 1.)).xyz; - - return triangleSolidAngle(pos, v1, v2, v3) / TWO_PI; -} - -void sampleSurfaceTriangle( - vec3 color, vec3 view_dir, MaterialProperties material /* TODO BrdfData instead is supposedly more efficient */, - mat4x3 emissive_transform, - uint triangle_index, uint index_offset, uint vertex_offset, - uint kusok_index, - out vec3 diffuse, out vec3 specular) -{ - diffuse = specular = vec3(0.); - const uint first_index_offset = index_offset + triangle_index * 3; - - // TODO this is not entirely correct -- need to mix between all normals, or have this normal precomputed - const uint vi1 = uint(indices[first_index_offset+0]) + vertex_offset; - const uint vi2 = uint(indices[first_index_offset+1]) + vertex_offset; - const uint vi3 = uint(indices[first_index_offset+2]) + vertex_offset; - - const vec3 v1 = (emissive_transform * vec4(vertices[vi1].pos, 1.)).xyz; - const vec3 v2 = (emissive_transform * vec4(vertices[vi2].pos, 1.)).xyz; - const vec3 v3 = (emissive_transform * vec4(vertices[vi3].pos, 1.)).xyz; - - // TODO projected uniform sampling - vec2 bary = vec2(sqrt(rand01()), rand01()); - bary.y *= bary.x; - bary.x = 1. - bary.x; - const vec3 sample_pos = baryMix(v1, v2, v3, bary); - - vec3 light_dir = sample_pos - payload_opaque.hit_pos_t.xyz; - const float light_dir_normal_dot = dot(light_dir, payload_opaque.normal); - if (light_dir_normal_dot <= 0.) -#ifdef DEBUG_LIGHT_CULLING - return vec3(1., 0., 1.) * color_factor; -#else - return; -#endif - - - const float light_dist2 = dot(light_dir, light_dir); - float pdf = TWO_PI / triangleSolidAngle(payload_opaque.hit_pos_t.xyz, v1, v2, v3); - - // Cull back facing - if (pdf <= 0.) - return; - - if (pdf > pdf_culling_threshold) -#ifdef DEBUG_LIGHT_CULLING - return vec3(0., 1., 0.) * color_factor; -#else - return; -#endif - -#if 1 - { - const uint tex_index = kusochki[kusok_index].tex_base_color; - if ((KUSOK_MATERIAL_FLAG_SKYBOX & tex_index) == 0) { - const vec2 uv1 = vertices[vi1].gl_tc; - const vec2 uv2 = vertices[vi2].gl_tc; - const vec2 uv3 = vertices[vi3].gl_tc; - const vec2 uv = baryMix(uv1, uv2, uv3, bary); - - color *= texture(textures[nonuniformEXT(tex_index)], uv).rgb; - } - } -#endif - - color /= pdf; - - if (dot(color,color) < color_culling_threshold) -#ifdef DEBUG_LIGHT_CULLING - return vec3(0., 1., 0.) * color_factor; -#else - return; -#endif - - light_dir = normalize(light_dir); - - // TODO sample emissive texture - evalSplitBRDF(payload_opaque.normal, light_dir, view_dir, material, diffuse, specular); - diffuse *= color; - specular *= color; - - vec3 combined = diffuse + specular; - - if (dot(combined,combined) < color_culling_threshold) -#ifdef DEBUG_LIGHT_CULLING - return vec3(1., 1., 0.) * color_factor; -#else - return; -#endif - - if (shadowed(payload_opaque.hit_pos_t.xyz, light_dir, sqrt(light_dist2))) { - diffuse = specular = vec3(0.); - } - - if (pdf < 0.) { //any(lessThan(diffuse, vec3(0.)))) { - diffuse = vec3(1., 0., 0.); - } -} - -void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties material, uint ekusok_index, out vec3 out_diffuse, out vec3 out_specular) { - const EmissiveKusok ek = lights.kusochki[ekusok_index]; - const uint emissive_kusok_index = lights.kusochki[ekusok_index].kusok_index; - const Kusok kusok = kusochki[emissive_kusok_index]; - - // TODO streamline matrices layouts - const mat4x3 emissive_transform = mat4x3( - vec3(ek.tx_row_x.x, ek.tx_row_y.x, ek.tx_row_z.x), - vec3(ek.tx_row_x.y, ek.tx_row_y.y, ek.tx_row_z.y), - vec3(ek.tx_row_x.z, ek.tx_row_y.z, ek.tx_row_z.z), - vec3(ek.tx_row_x.w, ek.tx_row_y.w, ek.tx_row_z.w) - ); - - if (emissive_kusok_index == uint(payload_opaque.kusok_index)) - return; - - // Taken from Ray Tracing Gems II, ch.47, p.776, listing 47-2 - int selected = -1; - float selected_contrib = 0.; - float total_contrib = 0.; - float eps1 = rand01(); - - for (uint i = 0; i < kusok.triangles; ++i) { - const float tri_contrib = computeTriangleContrib(i, kusok.index_offset, kusok.vertex_offset, emissive_transform, payload_opaque.hit_pos_t.xyz); - - if (tri_contrib <= 0.) - continue; - - const float tau = total_contrib / (total_contrib + tri_contrib); - total_contrib += tri_contrib; - - if (eps1 < tau) { - eps1 /= tau; - } else { - selected = int(i); - selected_contrib = tri_contrib; - eps1 = (eps1 - tau) / (1. - tau); - } - -#define MAX_BELOW_ONE .99999 // FIXME what's the correct way to do this - eps1 = clamp(eps1, 0., MAX_BELOW_ONE); // Numerical stability (?) - } - - if (selected >= 0) { - sampleSurfaceTriangle(throughput * ek.emissive, view_dir, material, emissive_transform, selected, kusok.index_offset, kusok.vertex_offset, emissive_kusok_index, out_diffuse, out_specular); - - const float tri_factor = total_contrib / selected_contrib; - out_diffuse *= tri_factor; - out_specular *= tri_factor; - } -} +#include "light_common.glsl" +#include "light_polygon.glsl" void computePointLights(uint cluster_index, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { diffuse = specular = vec3(0.); @@ -392,34 +163,7 @@ void computeLighting(vec3 throughput, vec3 view_dir, MaterialProperties material //C = vec3(float(int(light_grid.clusters[cluster_index].num_emissive_surfaces))); //C += .3 * fract(vec3(light_cell) / 4.); - const uint num_emissive_kusochki = uint(light_grid.clusters[cluster_index].num_emissive_surfaces); - float sampling_light_scale = 1.; -#if 1 - const uint max_lights_per_frame = 4; - uint begin_i = 0, end_i = num_emissive_kusochki; - if (end_i > max_lights_per_frame) { - begin_i = rand() % (num_emissive_kusochki - max_lights_per_frame); - end_i = begin_i + max_lights_per_frame; - sampling_light_scale = float(num_emissive_kusochki) / float(max_lights_per_frame); - } - for (uint i = begin_i; i < end_i; ++i) { -#else - - for (uint i = 0; i < num_emissive_kusochki; ++i) { -#endif - const uint index_into_emissive_kusochki = uint(light_grid.clusters[cluster_index].emissive_surfaces[i]); - - if (push_constants.debug_light_index_begin < push_constants.debug_light_index_end) { - if (index_into_emissive_kusochki < push_constants.debug_light_index_begin || index_into_emissive_kusochki >= push_constants.debug_light_index_end) - continue; - } - - vec3 ldiffuse, lspecular; - sampleEmissiveSurface(throughput, view_dir, material, index_into_emissive_kusochki, ldiffuse, lspecular); - - diffuse += ldiffuse * sampling_light_scale; - specular += lspecular * sampling_light_scale; - } // for all emissive kusochki + sampleEmissiveSurfaces(throughput, view_dir, material, cluster_index, diffuse, specular); vec3 ldiffuse = vec3(0.), lspecular = vec3(0.); computePointLights(cluster_index, throughput, view_dir, material, ldiffuse, lspecular); From 14176f147f0dfb26fbd87ecd9b8c8488ad763c75 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Tue, 28 Dec 2021 22:54:17 -0800 Subject: [PATCH 026/548] rtx: implement more of that solid angle sampling also tweak tonemapping and exposure a bit --- ref_vk/shaders/denoiser.comp | 31 +++++++++- ref_vk/shaders/light_polygon.glsl | 98 ++++++++++++++++++++++--------- 2 files changed, 99 insertions(+), 30 deletions(-) diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index c8b11b2f..147a0b0a 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -56,11 +56,20 @@ void main() { return; } + /* if (pix.y < res.y / 3) { */ + /* imageStore(dest, pix, vec4(pow(float(pix.x) / res.x, 2.2))); return; */ + /* } else if (pix.y < res.y * 2 / 3) { */ + /* imageStore(dest, pix, vec4(float(pix.x) / res.x)); return; */ + /* } else { */ + /* imageStore(dest, pix, vec4(sqrt(float(pix.x) / res.x))); return; */ + /* } */ + const vec4 base_color = imageLoad(src_base_color, pix); const float material_index = imageLoad(src_diffuse_gi, pix).a; //imageStore(dest, pix, vec4(aces_tonemap(base_color.rgb), 0.)); return; //imageStore(dest, pix, vec4((base_color.rgb), 0.)); return; + //imageStore(dest, pix, vec4((imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_specular, pix).rgb), 0.)); return; @@ -128,10 +137,26 @@ void main() { // HACK: exposure // TODO: should be dynamic based on previous frames brightness - colour *= 4.; +#if 0 + if (pix.x >= res.x / 2) { + colour *= 8.; + } +#else + colour *= .25; +#endif - //colour = aces_tonemap(colour); - colour = reinhard02(colour, vec3(400.)); + colour = aces_tonemap(colour); + //colour = reinhard02(colour, vec3(400.)); + //colour = reinhard02(colour, vec3(1.)); + +#if 0 + if (pix.x < res.x / 2) { +#endif + //colour *= .25; + colour = pow(colour, vec3(1. / 2.2)); +#if 0 + } +#endif imageStore(dest, pix, vec4(colour, 0.)); //imageStore(dest, pix, imageLoad(src_diffuse_gi, pix)); diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 4f1426d8..822fcd7b 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -150,9 +150,21 @@ void sampleSurfaceTriangle( void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties material, SampleContext ctx, uint ekusok_index, out vec3 out_diffuse, out vec3 out_specular) { const EmissiveKusok ek = lights.kusochki[ekusok_index]; const uint emissive_kusok_index = lights.kusochki[ekusok_index].kusok_index; + if (emissive_kusok_index == uint(payload_opaque.kusok_index)) + return; + + out_diffuse = out_specular = vec3(0.); + const Kusok kusok = kusochki[emissive_kusok_index]; // TODO streamline matrices layouts + const mat4x3 to_world = mat4x3( + vec3(ek.tx_row_x.x, ek.tx_row_y.x, ek.tx_row_z.x), + vec3(ek.tx_row_x.y, ek.tx_row_y.y, ek.tx_row_z.y), + vec3(ek.tx_row_x.z, ek.tx_row_y.z, ek.tx_row_z.z), + vec3(ek.tx_row_x.w, ek.tx_row_y.w, ek.tx_row_z.w) + ); + const mat4x3 to_shading = ctx.world_to_shading * mat4( vec4(ek.tx_row_x.x, ek.tx_row_y.x, ek.tx_row_z.x, 0), vec4(ek.tx_row_x.y, ek.tx_row_y.y, ek.tx_row_z.y, 0), @@ -160,24 +172,13 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma vec4(ek.tx_row_x.w, ek.tx_row_y.w, ek.tx_row_z.w, 1) ); - const mat4x3 emissive_transform = mat4x3( - vec3(ek.tx_row_x.x, ek.tx_row_y.x, ek.tx_row_z.x), - vec3(ek.tx_row_x.y, ek.tx_row_y.y, ek.tx_row_z.y), - vec3(ek.tx_row_x.z, ek.tx_row_y.z, ek.tx_row_z.z), - vec3(ek.tx_row_x.w, ek.tx_row_y.w, ek.tx_row_z.w) - ); - - - if (emissive_kusok_index == uint(payload_opaque.kusok_index)) - return; - - // Taken from Ray Tracing Gems II, ch.47, p.776, listing 47-2 + // Picking a triangle is taken from Ray Tracing Gems II, ch.47, p.776, listing 47-2 int selected = -1; - float selected_contrib = 0.; float total_contrib = 0.; float eps1 = rand01(); - solid_angle_polygon_t poly_angle; + solid_angle_polygon_t selected_angle; + vec4 selected_plane; for (uint i = 0; i < kusok.triangles; ++i) { const uint first_index_offset = kusok.index_offset + i * 3; const uint vi1 = uint(indices[first_index_offset+0]) + kusok.vertex_offset; @@ -186,16 +187,20 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma // Transform to shading space vec3 v[MAX_POLYGON_VERTEX_COUNT]; - v[0] = to_shading * vec4(vertices[vi1].pos, 1.); - v[1] = to_shading * vec4(vertices[vi2].pos, 1.); - v[2] = to_shading * vec4(vertices[vi3].pos, 1.); + v[0] = to_world * vec4(vertices[vi1].pos, 1.); + v[1] = to_world * vec4(vertices[vi2].pos, 1.); + v[2] = to_world * vec4(vertices[vi3].pos, 1.); + + // TODO cull by normal // Clip - const uint vertex_count = clip_polygon(3, v); + /* const uint vertex_count = clip_polygon(3, v); */ + /* if (vertex_count == 0) */ + /* continue; */ // poly_angle - poly_angle = prepare_solid_angle_polygon_sampling(vertex_count, v, payload_opaque.hit_pos_t.xyz); - const float tri_contrib = poly_angle.solid_angle; + const solid_angle_polygon_t sap = prepare_solid_angle_polygon_sampling(3, v, payload_opaque.hit_pos_t.xyz); + const float tri_contrib = sap.solid_angle; if (tri_contrib <= 0.) continue; @@ -203,25 +208,64 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma const float tau = total_contrib / (total_contrib + tri_contrib); total_contrib += tri_contrib; +#if 0 + if (false) { +#else if (eps1 < tau) { +#endif eps1 /= tau; } else { selected = int(i); - selected_contrib = tri_contrib; eps1 = (eps1 - tau) / (1. - tau); + selected_angle = sap; + + selected_plane.xyz = cross(v[1] - v[0], v[2] - v[0]); + selected_plane.w = -dot(v[0], selected_plane.xyz); } #define MAX_BELOW_ONE .99999 // FIXME what's the correct way to do this eps1 = clamp(eps1, 0., MAX_BELOW_ONE); // Numerical stability (?) } - if (selected >= 0) { - sampleSurfaceTriangle(throughput * ek.emissive, view_dir, material, emissive_transform, selected, kusok.index_offset, kusok.vertex_offset, emissive_kusok_index, out_diffuse, out_specular); + if (selected < 0 || selected_angle.solid_angle <= 0.) + return; - const float tri_factor = total_contrib / selected_contrib; - out_diffuse *= tri_factor; - out_specular *= tri_factor; + //sampleSurfaceTriangle(throughput * ek.emissive, view_dir, material, emissive_transform, selected, kusok.index_offset, kusok.vertex_offset, emissive_kusok_index, out_diffuse, out_specular); + + vec2 rnd = vec2(rand01(), rand01()); + //const vec3 light_dir = normalize(inverse(mat3(ctx.world_to_shading)) * sample_solid_angle_polygon(poly_angle, rnd)); + const vec3 light_dir = sample_solid_angle_polygon(selected_angle, rnd); + + vec3 tri_diffuse = vec3(0.), tri_specular = vec3(0.); +#if 1 + evalSplitBRDF(payload_opaque.normal, light_dir, view_dir, material, tri_diffuse, tri_specular); + tri_diffuse *= throughput * ek.emissive; + tri_specular *= throughput * ek.emissive; +#else + tri_diffuse = vec3(selected_angle.solid_angle); +#endif + +#if 1 + vec3 combined = tri_diffuse + tri_specular; + if (dot(combined,combined) > color_culling_threshold) { + const float dist = -dot(vec4(payload_opaque.hit_pos_t.xyz, 1.f), selected_plane) / dot(light_dir, selected_plane.xyz); + if (!shadowed(payload_opaque.hit_pos_t.xyz, light_dir, dist)) { + const float tri_factor = total_contrib; // selected_angle.solid_angle; + out_diffuse += tri_diffuse * tri_factor; + out_specular += tri_specular * tri_factor; + } + } else { +#ifdef DEBUG_LIGHT_CULLING + return vec3(1., 1., 0.) * color_factor; +#else + return; +#endif } +#else + //const float tri_factor = total_contrib / selected_angle.solid_angle; + out_diffuse += tri_diffuse * tri_factor; + out_specular += tri_specular * tri_factor; +#endif } void sampleEmissiveSurfaces(vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { @@ -230,7 +274,7 @@ void sampleEmissiveSurfaces(vec3 throughput, vec3 view_dir, MaterialProperties m const uint num_emissive_kusochki = uint(light_grid.clusters[cluster_index].num_emissive_surfaces); float sampling_light_scale = 1.; -#if 1 +#if 0 const uint max_lights_per_frame = 4; uint begin_i = 0, end_i = num_emissive_kusochki; if (end_i > max_lights_per_frame) { From 639a09520e6d5bb3fbff9a934b404729eae187d4 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Wed, 29 Dec 2021 12:30:00 -0800 Subject: [PATCH 027/548] rtx: remove old tri angle sampling --- ref_vk/shaders/light_polygon.glsl | 130 ------------------------------ 1 file changed, 130 deletions(-) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 822fcd7b..ed3308e0 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -17,136 +17,6 @@ SampleContext buildSampleContext(vec3 position, vec3 normal, vec3 view_dir) { return ctx; } -float triangleSolidAngle(vec3 p, vec3 a, vec3 b, vec3 c) { - a = normalize(a - p); - b = normalize(b - p); - c = normalize(c - p); - - // TODO horizon culling - const float tanHalfOmega = dot(a, cross(b,c)) / (1. + dot(b,c) + dot(c,a) + dot(a,b)); - - return atan(tanHalfOmega) * 2.; -} - -vec3 baryMix(vec3 v1, vec3 v2, vec3 v3, vec2 bary) { - return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; -} - -vec2 baryMix(vec2 v1, vec2 v2, vec2 v3, vec2 bary) { - return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; -} - -float computeTriangleContrib(uint triangle_index, uint index_offset, uint vertex_offset, mat4x3 xform, vec3 pos) { - const uint first_index_offset = index_offset + triangle_index * 3; - - const uint vi1 = uint(indices[first_index_offset+0]) + vertex_offset; - const uint vi2 = uint(indices[first_index_offset+1]) + vertex_offset; - const uint vi3 = uint(indices[first_index_offset+2]) + vertex_offset; - - const vec3 v1 = (xform * vec4(vertices[vi1].pos, 1.)).xyz; - const vec3 v2 = (xform * vec4(vertices[vi2].pos, 1.)).xyz; - const vec3 v3 = (xform * vec4(vertices[vi3].pos, 1.)).xyz; - - return triangleSolidAngle(pos, v1, v2, v3) / TWO_PI; -} - -void sampleSurfaceTriangle( - vec3 color, vec3 view_dir, MaterialProperties material /* TODO BrdfData instead is supposedly more efficient */, - mat4x3 emissive_transform, - uint triangle_index, uint index_offset, uint vertex_offset, - uint kusok_index, - out vec3 diffuse, out vec3 specular) -{ - diffuse = specular = vec3(0.); - const uint first_index_offset = index_offset + triangle_index * 3; - - // TODO this is not entirely correct -- need to mix between all normals, or have this normal precomputed - const uint vi1 = uint(indices[first_index_offset+0]) + vertex_offset; - const uint vi2 = uint(indices[first_index_offset+1]) + vertex_offset; - const uint vi3 = uint(indices[first_index_offset+2]) + vertex_offset; - - const vec3 v1 = (emissive_transform * vec4(vertices[vi1].pos, 1.)).xyz; - const vec3 v2 = (emissive_transform * vec4(vertices[vi2].pos, 1.)).xyz; - const vec3 v3 = (emissive_transform * vec4(vertices[vi3].pos, 1.)).xyz; - - // TODO projected uniform sampling - vec2 bary = vec2(sqrt(rand01()), rand01()); - bary.y *= bary.x; - bary.x = 1. - bary.x; - const vec3 sample_pos = baryMix(v1, v2, v3, bary); - - vec3 light_dir = sample_pos - payload_opaque.hit_pos_t.xyz; - const float light_dir_normal_dot = dot(light_dir, payload_opaque.normal); - if (light_dir_normal_dot <= 0.) -#ifdef DEBUG_LIGHT_CULLING - return vec3(1., 0., 1.) * color_factor; -#else - return; -#endif - - - const float light_dist2 = dot(light_dir, light_dir); - float pdf = TWO_PI / triangleSolidAngle(payload_opaque.hit_pos_t.xyz, v1, v2, v3); - - // Cull back facing - if (pdf <= 0.) - return; - - if (pdf > pdf_culling_threshold) -#ifdef DEBUG_LIGHT_CULLING - return vec3(0., 1., 0.) * color_factor; -#else - return; -#endif - -#if 1 - { - const uint tex_index = kusochki[kusok_index].tex_base_color; - if ((KUSOK_MATERIAL_FLAG_SKYBOX & tex_index) == 0) { - const vec2 uv1 = vertices[vi1].gl_tc; - const vec2 uv2 = vertices[vi2].gl_tc; - const vec2 uv3 = vertices[vi3].gl_tc; - const vec2 uv = baryMix(uv1, uv2, uv3, bary); - - color *= texture(textures[nonuniformEXT(tex_index)], uv).rgb; - } - } -#endif - - color /= pdf; - - if (dot(color,color) < color_culling_threshold) -#ifdef DEBUG_LIGHT_CULLING - return vec3(0., 1., 0.) * color_factor; -#else - return; -#endif - - light_dir = normalize(light_dir); - - // TODO sample emissive texture - evalSplitBRDF(payload_opaque.normal, light_dir, view_dir, material, diffuse, specular); - diffuse *= color; - specular *= color; - - vec3 combined = diffuse + specular; - - if (dot(combined,combined) < color_culling_threshold) -#ifdef DEBUG_LIGHT_CULLING - return vec3(1., 1., 0.) * color_factor; -#else - return; -#endif - - if (shadowed(payload_opaque.hit_pos_t.xyz, light_dir, sqrt(light_dist2))) { - diffuse = specular = vec3(0.); - } - - if (pdf < 0.) { //any(lessThan(diffuse, vec3(0.)))) { - diffuse = vec3(1., 0., 0.); - } -} - void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties material, SampleContext ctx, uint ekusok_index, out vec3 out_diffuse, out vec3 out_specular) { const EmissiveKusok ek = lights.kusochki[ekusok_index]; const uint emissive_kusok_index = lights.kusochki[ekusok_index].kusok_index; From dcf787274c7044e1ff924c889cd6490f36085e1f Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Wed, 29 Dec 2021 12:30:50 -0800 Subject: [PATCH 028/548] rtx: shading sample space sampling --- ref_vk/shaders/light_polygon.glsl | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index ed3308e0..2faf1ebc 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -18,13 +18,13 @@ SampleContext buildSampleContext(vec3 position, vec3 normal, vec3 view_dir) { } void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties material, SampleContext ctx, uint ekusok_index, out vec3 out_diffuse, out vec3 out_specular) { + out_diffuse = out_specular = vec3(0.); + const EmissiveKusok ek = lights.kusochki[ekusok_index]; const uint emissive_kusok_index = lights.kusochki[ekusok_index].kusok_index; if (emissive_kusok_index == uint(payload_opaque.kusok_index)) return; - out_diffuse = out_specular = vec3(0.); - const Kusok kusok = kusochki[emissive_kusok_index]; // TODO streamline matrices layouts @@ -57,9 +57,9 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma // Transform to shading space vec3 v[MAX_POLYGON_VERTEX_COUNT]; - v[0] = to_world * vec4(vertices[vi1].pos, 1.); - v[1] = to_world * vec4(vertices[vi2].pos, 1.); - v[2] = to_world * vec4(vertices[vi3].pos, 1.); + v[0] = to_shading * vec4(vertices[vi1].pos, 1.); + v[1] = to_shading * vec4(vertices[vi2].pos, 1.); + v[2] = to_shading * vec4(vertices[vi3].pos, 1.); // TODO cull by normal @@ -69,7 +69,7 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma /* continue; */ // poly_angle - const solid_angle_polygon_t sap = prepare_solid_angle_polygon_sampling(3, v, payload_opaque.hit_pos_t.xyz); + const solid_angle_polygon_t sap = prepare_solid_angle_polygon_sampling(3, v, vec3(0.f)); const float tri_contrib = sap.solid_angle; if (tri_contrib <= 0.) @@ -89,8 +89,13 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma eps1 = (eps1 - tau) / (1. - tau); selected_angle = sap; - selected_plane.xyz = cross(v[1] - v[0], v[2] - v[0]); - selected_plane.w = -dot(v[0], selected_plane.xyz); + vec3 vw[MAX_POLYGON_VERTEX_COUNT]; + vw[0] = to_world * vec4(vertices[vi1].pos, 1.); + vw[1] = to_world * vec4(vertices[vi2].pos, 1.); + vw[2] = to_world * vec4(vertices[vi3].pos, 1.); + + selected_plane.xyz = cross(vw[1] - vw[0], vw[2] - vw[0]); + selected_plane.w = -dot(vw[0], selected_plane.xyz); } #define MAX_BELOW_ONE .99999 // FIXME what's the correct way to do this @@ -103,8 +108,8 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma //sampleSurfaceTriangle(throughput * ek.emissive, view_dir, material, emissive_transform, selected, kusok.index_offset, kusok.vertex_offset, emissive_kusok_index, out_diffuse, out_specular); vec2 rnd = vec2(rand01(), rand01()); - //const vec3 light_dir = normalize(inverse(mat3(ctx.world_to_shading)) * sample_solid_angle_polygon(poly_angle, rnd)); - const vec3 light_dir = sample_solid_angle_polygon(selected_angle, rnd); + const vec3 light_dir = (transpose(ctx.world_to_shading) * sample_solid_angle_polygon(selected_angle, rnd)).xyz; + //const vec3 light_dir = sample_solid_angle_polygon(selected_angle, rnd); vec3 tri_diffuse = vec3(0.), tri_specular = vec3(0.); #if 1 @@ -120,7 +125,7 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma if (dot(combined,combined) > color_culling_threshold) { const float dist = -dot(vec4(payload_opaque.hit_pos_t.xyz, 1.f), selected_plane) / dot(light_dir, selected_plane.xyz); if (!shadowed(payload_opaque.hit_pos_t.xyz, light_dir, dist)) { - const float tri_factor = total_contrib; // selected_angle.solid_angle; + const float tri_factor = total_contrib; // / selected_angle.solid_angle; out_diffuse += tri_diffuse * tri_factor; out_specular += tri_specular * tri_factor; } @@ -132,7 +137,7 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma #endif } #else - //const float tri_factor = total_contrib / selected_angle.solid_angle; + const float tri_factor = total_contrib; out_diffuse += tri_diffuse * tri_factor; out_specular += tri_specular * tri_factor; #endif From e98d7d0d7f0cd9ac1d49bf5430c1038bb83b2e47 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Wed, 29 Dec 2021 12:37:24 -0800 Subject: [PATCH 029/548] rtx: cull triangles by their orientation --- ref_vk/shaders/light_polygon.glsl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 2faf1ebc..8f831854 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -61,7 +61,10 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma v[1] = to_shading * vec4(vertices[vi2].pos, 1.); v[2] = to_shading * vec4(vertices[vi3].pos, 1.); - // TODO cull by normal + // cull by triangle orientation + const vec3 tri_normal_dir = cross(v[1] - v[0], v[2] - v[0]); + if (dot(tri_normal_dir, v[0]) <= 0.) + continue; // Clip /* const uint vertex_count = clip_polygon(3, v); */ From 1352f68462c4d60ea9354c29830fe9a0526d9f20 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Wed, 29 Dec 2021 12:40:37 -0800 Subject: [PATCH 030/548] rtx: clip sampled triangles by half-dome --- ref_vk/shaders/light_polygon.glsl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 8f831854..cb57049b 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -67,12 +67,12 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma continue; // Clip - /* const uint vertex_count = clip_polygon(3, v); */ - /* if (vertex_count == 0) */ - /* continue; */ + const uint vertex_count = clip_polygon(3, v); + if (vertex_count == 0) + continue; // poly_angle - const solid_angle_polygon_t sap = prepare_solid_angle_polygon_sampling(3, v, vec3(0.f)); + const solid_angle_polygon_t sap = prepare_solid_angle_polygon_sampling(vertex_count, v, vec3(0.f)); const float tri_contrib = sap.solid_angle; if (tri_contrib <= 0.) From 44652da57ad3f4de2e02ef29af0312880ba8b152 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Wed, 29 Dec 2021 12:56:58 -0800 Subject: [PATCH 031/548] rtx: switch sampling to projected solid angle --- ref_vk/shaders/light_polygon.glsl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index cb57049b..6c19c873 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -47,7 +47,7 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma float total_contrib = 0.; float eps1 = rand01(); - solid_angle_polygon_t selected_angle; + projected_solid_angle_polygon_t selected_angle; vec4 selected_plane; for (uint i = 0; i < kusok.triangles; ++i) { const uint first_index_offset = kusok.index_offset + i * 3; @@ -72,8 +72,8 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma continue; // poly_angle - const solid_angle_polygon_t sap = prepare_solid_angle_polygon_sampling(vertex_count, v, vec3(0.f)); - const float tri_contrib = sap.solid_angle; + const projected_solid_angle_polygon_t sap = prepare_projected_solid_angle_polygon_sampling(vertex_count, v); + const float tri_contrib = sap.projected_solid_angle; if (tri_contrib <= 0.) continue; @@ -105,13 +105,13 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma eps1 = clamp(eps1, 0., MAX_BELOW_ONE); // Numerical stability (?) } - if (selected < 0 || selected_angle.solid_angle <= 0.) + if (selected < 0 || selected_angle.projected_solid_angle <= 0.) return; //sampleSurfaceTriangle(throughput * ek.emissive, view_dir, material, emissive_transform, selected, kusok.index_offset, kusok.vertex_offset, emissive_kusok_index, out_diffuse, out_specular); vec2 rnd = vec2(rand01(), rand01()); - const vec3 light_dir = (transpose(ctx.world_to_shading) * sample_solid_angle_polygon(selected_angle, rnd)).xyz; + const vec3 light_dir = (transpose(ctx.world_to_shading) * sample_projected_solid_angle_polygon(selected_angle, rnd)).xyz; //const vec3 light_dir = sample_solid_angle_polygon(selected_angle, rnd); vec3 tri_diffuse = vec3(0.), tri_specular = vec3(0.); From f0073748669b54e17056dfc4a5926610d29a775a Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 31 Dec 2021 03:49:11 +0300 Subject: [PATCH 032/548] engine: client: rewrite console history Fix duplicate and empty lines saved into history Fix backup copied too early Add tests --- engine/client/cl_main.c | 1 - engine/client/client.h | 1 - engine/client/console.c | 276 ++++++++++++++++++++++++++++------------ engine/common/host.c | 3 + engine/common/tests.h | 13 +- 5 files changed, 212 insertions(+), 82 deletions(-) diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index c4a3b9f9..a0b79b95 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -3078,7 +3078,6 @@ void CL_Init( void ) MSG_Init( &cls.datagram, "cls.datagram", cls.datagram_buf, sizeof( cls.datagram_buf )); // IN_TouchInit(); - Con_LoadHistory(); COM_GetCommonLibraryPath( LIBRARY_CLIENT, libpath, sizeof( libpath )); diff --git a/engine/client/client.h b/engine/client/client.h index dc50afcc..0a15384a 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -1048,7 +1048,6 @@ void Con_Bottom( void ); void Con_Top( void ); void Con_PageDown( int lines ); void Con_PageUp( int lines ); -void Con_LoadHistory( void ); // // s_main.c diff --git a/engine/client/console.c b/engine/client/console.c index 34fbce4f..a450b685 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -81,6 +81,14 @@ typedef struct con_lineinfo_s double addtime; // notify stuff } con_lineinfo_t; +typedef struct con_history_s +{ + field_t lines[CON_HISTORY]; + field_t backup; + int line; // the line being displayed from history buffer will be <= nextHistoryLine + int next; // the last line in the history buffer, not masked +} con_history_t; + typedef struct { qboolean initialized; @@ -118,10 +126,8 @@ typedef struct string chat_cmd; // can be overrieded by user // console history - field_t historyLines[CON_HISTORY]; - int historyLine; // the line being displayed from history buffer will be <= nextHistoryLine - int nextHistoryLine; // the last line in the history buffer, not masked - field_t backup; + con_history_t history; + qboolean historyLoaded; notify_t notify[MAX_DBG_NOTIFY]; // for Con_NXPrintf qboolean draw_notify; // true if we have NXPrint message @@ -135,6 +141,9 @@ static console_t con; void Con_ClearField( field_t *edit ); void Field_CharEvent( field_t *edit, int ch ); +static void Con_LoadHistory( con_history_t *self ); +static void Con_SaveHistory( con_history_t *self ); + /* ================ Con_Clear_f @@ -486,7 +495,7 @@ void Con_CheckResize( void ) con.input.widthInChars = con.linewidth; for( i = 0; i < CON_HISTORY; i++ ) - con.historyLines[i].widthInChars = con.linewidth; + con.history.lines[i].widthInChars = con.linewidth; } /* @@ -1113,61 +1122,6 @@ int Con_DrawString( int x, int y, const char *string, rgba_t setColor ) return Con_DrawGenericString( x, y, string, setColor, false, -1 ); } -void Con_LoadHistory( void ) -{ - const byte *aFile = FS_LoadFile( "console_history.txt", NULL, true ); - const char *pLine = (char *)aFile, *pFile = (char *)aFile; - int i; - - if( !aFile ) - return; - - while( true ) - { - if( !*pFile ) - break; - if( *pFile == '\n') - { - int len = pFile - pLine + 1; - field_t *f; - if( len > 255 ) len = 255; - Con_ClearField( &con.historyLines[con.nextHistoryLine] ); - f = &con.historyLines[con.nextHistoryLine % CON_HISTORY]; - f->widthInChars = con.linewidth; - f->cursor = len - 1; - Q_strncpy( f->buffer, pLine, len); - con.nextHistoryLine++; - pLine = pFile + 1; - } - pFile++; - } - - for( i = con.nextHistoryLine; i < CON_HISTORY; i++ ) - { - Con_ClearField( &con.historyLines[i] ); - con.historyLines[i].widthInChars = con.linewidth; - } - - con.historyLine = con.nextHistoryLine; - -} - -void Con_SaveHistory( void ) -{ - int historyStart = con.nextHistoryLine - CON_HISTORY; - int i; - file_t *f; - - if( historyStart < 0 ) - historyStart = 0; - - f = FS_Open("console_history.txt", "w", true ); - - for( i = historyStart; i < con.nextHistoryLine; i++ ) - FS_Printf( f, "%s\n", con.historyLines[i % CON_HISTORY].buffer ); - - FS_Close(f); -} /* ================ @@ -1233,7 +1187,7 @@ void Con_Shutdown( void ) con.buffer = NULL; con.lines = NULL; - Con_SaveHistory(); + Con_SaveHistory( &con.history ); } /* @@ -1468,11 +1422,22 @@ Con_ClearField */ void Con_ClearField( field_t *edit ) { - memset(edit->buffer, 0, MAX_STRING); + memset( edit->buffer, 0, MAX_STRING ); edit->cursor = 0; edit->scroll = 0; } +/* +================ +Field_Set +================ +*/ +static void Field_Set( field_t *f, const char *string ) +{ + f->scroll = 0; + f->cursor = Q_strncpy( f->buffer, string, MAX_STRING ); +} + /* ================ Field_Paste @@ -1708,12 +1673,125 @@ void Field_DrawInputLine( int x, int y, field_t *edit ) } } +/* +============================================================================= + +CONSOLE HISTORY HANDLING + +============================================================================= +*/ +/* +=================== +Con_HistoryUp + +=================== +*/ +static void Con_HistoryUp( con_history_t *self, field_t *in ) +{ + if( self->line == self->next ) + self->backup = *in; + + if(( self->next - self->line ) < CON_HISTORY ) + self->line = Q_max( 0, self->line - 1 ); + + *in = self->lines[self->line % CON_HISTORY]; +} + +/* +=================== +Con_HistoryDown + +=================== +*/ +static void Con_HistoryDown( con_history_t *self, field_t *in ) +{ + self->line = Q_min( self->next, self->line + 1 ); + if( self->line == self->next ) + *in = self->backup; + else *in = self->lines[self->line % CON_HISTORY]; +} + +/* +=================== +Con_HistoryAppend +=================== +*/ +static void Con_HistoryAppend( con_history_t *self, field_t *from ) +{ + int prevLine = Q_max( 0, self->line - 1 ); + + // only if non-empty + if( !from->buffer[0] ) + return; + + // if not copy + if( !Q_strcmp( from->buffer, self->lines[prevLine % CON_HISTORY].buffer )) + return; + + self->lines[self->next % CON_HISTORY] = *from; + self->line = ++self->next; +} + +static void Con_LoadHistory( con_history_t *self ) +{ + const byte *aFile = FS_LoadFile( "console_history.txt", NULL, true ); + const char *pLine, *pFile; + int i, len; + field_t *f; + + if( !aFile ) + return; + + for( pFile = pLine = (char *)aFile; *pFile; pFile++ ) + { + if( *pFile != '\n' ) + continue; + + Con_ClearField( &self->lines[self->next] ); + + len = Q_min( pFile - pLine + 1, sizeof( f->buffer )); + f = &self->lines[self->next % CON_HISTORY]; + f->widthInChars = con.linewidth; + f->cursor = len - 1; + Q_strncpy( f->buffer, pLine, len); + + self->next++; + + pLine = pFile + 1; + } + + for( i = self->next; i < CON_HISTORY; i++ ) + { + Con_ClearField( &self->lines[i] ); + self->lines[i].widthInChars = con.linewidth; + } + + self->line = self->next; +} + +static void Con_SaveHistory( con_history_t *self ) +{ + int historyStart = self->next - CON_HISTORY, i; + file_t *f; + + if( historyStart < 0 ) + historyStart = 0; + + f = FS_Open( "console_history.txt", "w", true ); + + for( i = historyStart; i < self->next; i++ ) + FS_Printf( f, "%s\n", self->lines[i % CON_HISTORY].buffer ); + + FS_Close( f ); +} + + /* ============================================================================= CONSOLE LINE EDITING -============================================================================== +============================================================================= */ /* ==================== @@ -1754,9 +1832,7 @@ void Key_Console( int key ) Con_Printf( ">%s\n", con.input.buffer ); // copy line to history buffer - con.historyLines[con.nextHistoryLine % CON_HISTORY] = con.input; - con.nextHistoryLine++; - con.historyLine = con.nextHistoryLine; + Con_HistoryAppend( &con.history, &con.input ); Con_ClearField( &con.input ); con.input.widthInChars = con.linewidth; @@ -1781,23 +1857,13 @@ void Key_Console( int key ) // command history (ctrl-p ctrl-n for unix style) if(( key == K_MWHEELUP && Key_IsDown( K_SHIFT )) || ( key == K_UPARROW ) || (( Q_tolower(key) == 'p' ) && Key_IsDown( K_CTRL ))) { - if( con.historyLine == con.nextHistoryLine ) - con.backup = con.input; - if( con.nextHistoryLine - con.historyLine < CON_HISTORY && con.historyLine > 0 ) - con.historyLine--; - con.input = con.historyLines[con.historyLine % CON_HISTORY]; + Con_HistoryUp( &con.history, &con.input ); return; } if(( key == K_MWHEELDOWN && Key_IsDown( K_SHIFT )) || ( key == K_DOWNARROW ) || (( Q_tolower(key) == 'n' ) && Key_IsDown( K_CTRL ))) { - if( con.historyLine >= con.nextHistoryLine - 1 ) - con.input = con.backup; - else - { - con.historyLine++; - con.input = con.historyLines[con.historyLine % CON_HISTORY]; - } + Con_HistoryDown( &con.history, &con.input ); return; } @@ -2388,6 +2454,12 @@ INTERNAL RESOURCE */ void Con_VidInit( void ) { + if( !con.historyLoaded ) + { + Con_LoadHistory( &con.history ); + con.historyLoaded = true; + } + Con_LoadConchars(); Con_CheckResize(); #if XASH_LOW_MEMORY @@ -2423,7 +2495,6 @@ void Con_VidInit( void ) } } - if( !con.background ) // last chance - quake conback image { qboolean draw_to_console = false; @@ -2511,3 +2582,50 @@ void GAME_EXPORT Con_DefaultColor( int r, int g, int b ) b = bound( 0, b, 255 ); MakeRGBA( g_color_table[7], r, g, b, 255 ); } + +#if XASH_ENGINE_TESTS +#include "tests.h" + +static void Test_RunConHistory( void ) +{ + con_history_t hist = { 0 }; + field_t input = { 0 }; + const char *strs1[] = { "map t0a0", "quit", "wtf", "wtf", "", "nyan" }; + const char *strs2[] = { "nyan", "wtf", "quit", "map t0a0" }; + const char *testbackup = "unfinished_edit"; + int i; + + for( i = 0; i < ARRAYSIZE( strs1 ); i++ ) + { + Field_Set( &input, strs1[i] ); + Con_HistoryAppend( &hist, &input ); + } + + Field_Set( &input, testbackup ); + + for( i = 0; i < ARRAYSIZE( strs2 ); i++ ) + { + Con_HistoryUp( &hist, &input ); + TASSERT_STR( input.buffer, strs2[i] ); + } + + // check for overrun + Con_HistoryUp( &hist, &input ); + + for( i = ARRAYSIZE( strs2 ) - 1; i >= 0; i-- ) + { + TASSERT_STR( input.buffer, strs2[i] ); + Con_HistoryDown( &hist, &input ); + } + + TASSERT_STR( input.buffer, testbackup ); + + TASSERT_STR( "floof", "meow" ); +} + +void Test_RunCon( void ) +{ + TRUN( Test_RunConHistory() ); +} + +#endif /* XASH_ENGINE_TESTS */ diff --git a/engine/common/host.c b/engine/common/host.c index 47d5a565..91e082c8 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -820,6 +820,9 @@ static void Host_RunTests( int stage ) Test_RunCommon(); Test_RunCmd(); Test_RunCvar(); +#if !XASH_DEDICATED + Test_RunCon(); +#endif /* XASH_DEDICATED */ break; case 1: // after FS load Test_RunImagelib(); diff --git a/engine/common/tests.h b/engine/common/tests.h index a142ba47..d4166974 100644 --- a/engine/common/tests.h +++ b/engine/common/tests.h @@ -11,7 +11,9 @@ struct tests_stats_s extern struct tests_stats_s tests_stats; -#define TRUN( x ) Msg( "Running " #x "\n" ); x +#define TRUN( x ) Msg( "Starting " #x "\n" ); \ + x; \ + Msg( "Finished " #x "\n" ) #define TASSERT( exp ) \ if(!( exp )) \ @@ -21,11 +23,20 @@ extern struct tests_stats_s tests_stats; } \ else tests_stats.passed++; +#define TASSERT_STR( str1, str2 ) \ + if( Q_strcmp(( str1 ), ( str2 ))) \ + { \ + tests_stats.failed++; \ + Msg( S_ERROR "assert failed at %s:%i, \"%s\" != \"%s\"\n", __FILE__, __LINE__, ( str1 ), ( str2 )); \ + } \ + else tests_stats.passed++; + void Test_RunImagelib( void ); void Test_RunLibCommon( void ); void Test_RunCommon( void ); void Test_RunCmd( void ); void Test_RunCvar( void ); +void Test_RunCon( void ); #endif From 947b94d29f0c87a4fd20c6ff3998ed6aea9c721a Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 31 Dec 2021 04:10:06 +0300 Subject: [PATCH 033/548] engine: client: delete unused test data :) --- engine/client/console.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/engine/client/console.c b/engine/client/console.c index a450b685..e30e27eb 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -2619,8 +2619,6 @@ static void Test_RunConHistory( void ) } TASSERT_STR( input.buffer, testbackup ); - - TASSERT_STR( "floof", "meow" ); } void Test_RunCon( void ) From f1dabf13a16353342ec050bf5c430c15bc02edae Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 31 Dec 2021 05:21:35 +0300 Subject: [PATCH 034/548] github: try to add aarch64 runner --- .github/workflows/c-cpp.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 5f0928ac..a78215ff 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -14,6 +14,9 @@ jobs: - os: ubuntu-18.04 targetos: linux targetarch: i386 + - os: ubuntu-aarch64-20.04 + targetos: linux + targetarch: aarch64 - os: ubuntu-18.04 targetos: android From b7a32f68439d12b41fd4d72bc2c7512f1d4991d4 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 31 Dec 2021 05:29:11 +0300 Subject: [PATCH 035/548] scripts: gha: install deps on aarch64 --- scripts/gha/deps_linux.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/gha/deps_linux.sh b/scripts/gha/deps_linux.sh index b7e8320c..7d351a9a 100755 --- a/scripts/gha/deps_linux.sh +++ b/scripts/gha/deps_linux.sh @@ -13,6 +13,11 @@ elif [ "$GH_CPU_ARCH" == "amd64" ]; then sudo apt install libx11-dev libxext-dev x11-utils libgl1-mesa-dev libasound-dev libstdc++6 libfuse2 zlib1g wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" -O appimagetool.AppImage +elif [ "$GH_CPU_ARCH" == "aarch64" ]; then + sudo apt update + sudo apt install libx11-dev libxext-dev x11-utils libgl1-mesa-dev libasound-dev libstdc++6 libfuse2 zlib1g + + wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-aarch64.AppImage" -O appimagetool.AppImage else exit 1 fi From d4d79573f62b034f3d109d006006f0b3f9cb7036 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 3 Jan 2022 21:13:02 +0300 Subject: [PATCH 036/548] github: try to cleanup work directory before running, useful for self-hosted runners --- .github/workflows/c-cpp.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index a78215ff..1ec1542d 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -42,6 +42,8 @@ jobs: ANDROID_SDK_TOOLS_VER: 4333796 UPLOADTOOL_ISPRERELEASE: true steps: + - name: Cleanup + run: rm -rf .* - name: Checkout uses: actions/checkout@v2 with: From 210137c325c660776daff40fdc3d61c9106e6247 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 3 Jan 2022 23:07:33 +0300 Subject: [PATCH 037/548] engine: increase model limit to the possible maximum without breaking the protocol --- engine/common/protocol.h | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/engine/common/protocol.h b/engine/common/protocol.h index bfaf29bb..8d1f83c5 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -110,13 +110,7 @@ GNU General Public License for more details. #define MAX_EVENTS (1< Date: Mon, 3 Jan 2022 23:21:31 +0300 Subject: [PATCH 038/548] readme: update discord link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e988100..4b1e55ab 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Xash3D FWGS Engine -[![GitHub Actions Status](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [![FreeBSD Build Status](https://img.shields.io/cirrus/github/FWGS/xash3d-fwgs?label=freebsd%20build)](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) [![Discord Server](https://img.shields.io/discord/355697768582610945.svg)](http://discord.fwgs.ru/) \ +[![GitHub Actions Status](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [![FreeBSD Build Status](https://img.shields.io/cirrus/github/FWGS/xash3d-fwgs?label=freebsd%20build)](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) [![Discord Server](https://img.shields.io/discord/355697768582610945.svg)](http://fwgsdiscord.mentality.rip/) \ [![Download Stable](https://img.shields.io/badge/download-stable-yellow)](https://github.com/FWGS/xash3d-fwgs/releases/latest) [![Download Testing](https://img.shields.io/badge/downloads-testing-orange)](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous) Xash3D FWGS is a fork of Xash3D Engine by Unkle Mike with extended features and crossplatform. From f1a4d66d27e356c0773225efa7dfeeaba20a48fc Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 4 Jan 2022 01:58:45 +0300 Subject: [PATCH 039/548] scripts: waifulib: remove nooptimize build type, use debug instead. Disable optimization for sanitize builds --- scripts/waifulib/compiler_optimizations.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/scripts/waifulib/compiler_optimizations.py b/scripts/waifulib/compiler_optimizations.py index e964c812..c708850b 100644 --- a/scripts/waifulib/compiler_optimizations.py +++ b/scripts/waifulib/compiler_optimizations.py @@ -30,7 +30,7 @@ compiler_optimizations.CFLAGS['gottagofast'] = { } ''' -VALID_BUILD_TYPES = ['fastnative', 'fast', 'release', 'debug', 'nooptimize', 'sanitize', 'none'] +VALID_BUILD_TYPES = ['fastnative', 'fast', 'release', 'debug', 'sanitize', 'none'] LINKFLAGS = { 'common': { @@ -82,14 +82,10 @@ CFLAGS = { }, 'sanitize': { 'msvc': ['/Od', '/RTC1', '/Zi'], - 'gcc': ['-Og', '-fsanitize=undefined', '-fsanitize=address', '-pthread'], + 'gcc': ['-O0', '-fsanitize=undefined', '-fsanitize=address', '-pthread'], 'clang': ['-O0', '-fsanitize=undefined', '-fsanitize=address', '-pthread'], 'default': ['-O0'] }, - 'nooptimize': { - 'msvc': ['/Od', '/Zi'], - 'default': ['-O0'] - } } LTO_CFLAGS = { From 46fd27eb14cc3d0db6bc260515531f50348a40a0 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 4 Jan 2022 02:14:34 +0300 Subject: [PATCH 040/548] console: remove prepending backslashes This is a leftover from Quake, where the console and chat were in fact same entity. Because Xash splits it, there is no need in prepending backslashes to separate commands from chat messages --- engine/client/console.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/engine/client/console.c b/engine/client/console.c index e30e27eb..0de8bcaf 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -1812,16 +1812,6 @@ void Key_Console( int key ) // enter finishes the line if( key == K_ENTER || key == K_KP_ENTER ) { - // if not in the game explicitly prepent a slash if needed - if( cls.state != ca_active && con.input.buffer[0] != '\\' && con.input.buffer[0] != '/' ) - { - char temp[MAX_SYSPATH]; - - Q_strncpy( temp, con.input.buffer, sizeof( temp )); - Q_sprintf( con.input.buffer, "\\%s", temp ); - con.input.cursor++; - } - // backslash text are commands, else chat if( con.input.buffer[0] == '\\' || con.input.buffer[0] == '/' ) Cbuf_AddText( con.input.buffer + 1 ); // skip backslash From 7fbbe9f015aa39ee4bb2ecf96f000f72062a6d4e Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 4 Jan 2022 02:18:41 +0300 Subject: [PATCH 041/548] github: ignore cleanup failure, run only on self-hosted --- .github/workflows/c-cpp.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 1ec1542d..60dbc618 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -1,6 +1,11 @@ name: Build & Deploy Engine on: [push, pull_request] jobs: + cleanup: + runs-on: self-hosted + steps: + - name: Cleanup + run: rm -rf .* || true build: runs-on: ${{ matrix.os }} continue-on-error: true @@ -42,8 +47,6 @@ jobs: ANDROID_SDK_TOOLS_VER: 4333796 UPLOADTOOL_ISPRERELEASE: true steps: - - name: Cleanup - run: rm -rf .* - name: Checkout uses: actions/checkout@v2 with: From ccf90beb7d85928630ea9273545ce734edbac175 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 4 Jan 2022 02:22:10 +0300 Subject: [PATCH 042/548] public: crtlib: add Q_isspace function --- common/const.h | 1 - public/crtlib.c | 10 ++++++++++ public/crtlib.h | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/common/const.h b/common/const.h index 2533ec09..a7e3d08b 100644 --- a/common/const.h +++ b/common/const.h @@ -725,7 +725,6 @@ typedef int string_t; typedef unsigned short word; #include "xash3d_types.h" -#define Q_isspace( ch ) (ch < 32 || ch > 255) typedef struct { diff --git a/public/crtlib.c b/public/crtlib.c index e92e2d82..73bafdf4 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -61,6 +61,16 @@ qboolean Q_isdigit( const char *str ) return false; } +qboolean Q_isspace( const char *str ) +{ + if( str && *str ) + { + while( isspace( *str ) ) str++; + if( !*str ) return true; + } + return false; +} + size_t Q_colorstr( const char *string ) { size_t len; diff --git a/public/crtlib.h b/public/crtlib.h index 338598db..99752535 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -61,6 +61,7 @@ size_t Q_strncat( char *dst, const char *src, size_t siz ); size_t Q_strncpy( char *dst, const char *src, size_t siz ); uint Q_hashkey( const char *string, uint hashSize, qboolean caseinsensitive ); qboolean Q_isdigit( const char *str ); +qboolean Q_isspace( const char *str ); int Q_atoi( const char *str ); float Q_atof( const char *str ); void Q_atov( float *vec, const char *str, size_t siz ); From 3351ecd75489ac9037385813406338fac1586f15 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 4 Jan 2022 02:31:07 +0300 Subject: [PATCH 043/548] console: ignore whitespace commands on history, ignore backslash --- engine/client/console.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/engine/client/console.c b/engine/client/console.c index 0de8bcaf..f52eaf81 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -207,8 +207,6 @@ Con_ClearTyping */ void Con_ClearTyping( void ) { - int i; - Con_ClearField( &con.input ); con.input.widthInChars = con.linewidth; @@ -1719,12 +1717,21 @@ Con_HistoryAppend static void Con_HistoryAppend( con_history_t *self, field_t *from ) { int prevLine = Q_max( 0, self->line - 1 ); + const char *buf = from->buffer; + + // skip backslashes + if( from->buffer[0] == '\\' || from->buffer[1] == '/' ) + buf++; // only if non-empty if( !from->buffer[0] ) return; - // if not copy + // skip empty commands + if( Q_isspace( buf )) + return; + + // if not copy (don't ignore backslashes) if( !Q_strcmp( from->buffer, self->lines[prevLine % CON_HISTORY].buffer )) return; From 295adcf22215d2c46e92ecf6fb5c16dc7834b565 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 4 Jan 2022 02:31:41 +0300 Subject: [PATCH 044/548] engine: con_utils: don't also prepend backslash in autocompletion~ --- engine/common/con_utils.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/common/con_utils.c b/engine/common/con_utils.c index c6b2cd3c..b07374e4 100644 --- a/engine/common/con_utils.c +++ b/engine/common/con_utils.c @@ -1216,7 +1216,7 @@ void Con_CompleteCommand( field_t *field ) if( con.matchCount == 1 ) { - Q_sprintf( con.completionField->buffer, "\\%s", con.cmds[0] ); + Q_strncpy( con.completionField->buffer, con.cmds[0], sizeof( con.completionField->buffer )); if( Cmd_Argc() == 1 ) Q_strncat( con.completionField->buffer, " ", sizeof( con.completionField->buffer ) ); else Con_ConcatRemaining( temp.buffer, con.completionString ); con.completionField->cursor = Q_strlen( con.completionField->buffer ); @@ -1244,7 +1244,7 @@ void Con_CompleteCommand( field_t *field ) con.shortestMatch[len] = 0; // multiple matches, complete to shortest - Q_sprintf( con.completionField->buffer, "\\%s", con.shortestMatch ); + Q_strncpy( con.completionField->buffer, con.shortestMatch, sizeof( con.completionField->buffer )); con.completionField->cursor = Q_strlen( con.completionField->buffer ); Con_ConcatRemaining( temp.buffer, con.completionString ); From 4ae608913b4bfa046fdbcaea69086ebf90c7ab33 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 4 Jan 2022 03:01:59 +0300 Subject: [PATCH 045/548] console: fix Home and End buttons or equivalent hotkeys to always set start or end of input buffer --- engine/client/console.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/engine/client/console.c b/engine/client/console.c index f52eaf81..26982b70 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -1455,6 +1455,18 @@ void Field_Paste( field_t *edit ) Field_CharEvent( edit, cbd[i] ); } +/* +================= +Field_GoTo + +================= +*/ +static void Field_GoTo( field_t *edit, int pos ) +{ + edit->cursor = pos; + edit->scroll = Q_max( 0, edit->cursor - edit->widthInChars ); +} + /* ================= Field_KeyDownEvent @@ -1507,20 +1519,20 @@ void Field_KeyDownEvent( field_t *edit, int key ) if( key == K_LEFTARROW ) { - if( edit->cursor > 0 ) edit->cursor= Con_UtfMoveLeft( edit->buffer, edit->cursor ); + if( edit->cursor > 0 ) edit->cursor = Con_UtfMoveLeft( edit->buffer, edit->cursor ); if( edit->cursor < edit->scroll ) edit->scroll--; return; } if( key == K_HOME || ( Q_tolower(key) == 'a' && Key_IsDown( K_CTRL ))) { - edit->cursor = 0; + Field_GoTo( edit, 0 ); return; } if( key == K_END || ( Q_tolower(key) == 'e' && Key_IsDown( K_CTRL ))) { - edit->cursor = len; + Field_GoTo( edit, len ); return; } @@ -1559,16 +1571,14 @@ void Field_CharEvent( field_t *edit, int ch ) if( ch == 'a' - 'a' + 1 ) { // ctrl-a is home - edit->cursor = 0; - edit->scroll = 0; + Field_GoTo( edit, 0 ); return; } if( ch == 'e' - 'a' + 1 ) { // ctrl-e is end - edit->cursor = len; - edit->scroll = edit->cursor - edit->widthInChars; + Field_GoTo( edit, len ); return; } From 0714ab27b58672a1ac538f35fc893653c516917d Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 4 Jan 2022 04:04:56 +0300 Subject: [PATCH 046/548] mainui: update --- mainui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainui b/mainui index e1b85a2d..e8f8c9a0 160000 --- a/mainui +++ b/mainui @@ -1 +1 @@ -Subproject commit e1b85a2d205a18954276884259ea46a8010f7734 +Subproject commit e8f8c9a0721913e75357728e05c2f5ab0b506e47 From 01b2266b7ea3b9c92b9ab4db734153ebd9f6731c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 4 Jan 2022 04:30:47 +0300 Subject: [PATCH 047/548] engine: net_encode: minor code style fix --- engine/common/net_encode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/common/net_encode.c b/engine/common/net_encode.c index 3c4c8c37..05c4a21d 100644 --- a/engine/common/net_encode.c +++ b/engine/common/net_encode.c @@ -1909,7 +1909,7 @@ qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, entity_state_t *from, entity_state from = &cl.instanced_baseline[baseline_offset]; } } - } + } // g-cont. probably is redundant *to = *from; From 284eeea3c3debad2949208c1dbe74652a57e8421 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 2 Jun 2021 17:42:24 +0300 Subject: [PATCH 048/548] engine: client: rework mouse input, use IN_MouseEvent for clientdll, don't emit mouse events when mouse is visible or touch emulate is used --- engine/client/input.c | 134 ++++++++++++++++------------------- engine/client/input.h | 2 +- engine/platform/sdl/events.c | 33 ++++----- 3 files changed, 74 insertions(+), 95 deletions(-) diff --git a/engine/client/input.c b/engine/client/input.c index 69143274..ffe09bed 100644 --- a/engine/client/input.c +++ b/engine/client/input.c @@ -18,7 +18,7 @@ GNU General Public License for more details. #include "client.h" #include "vgui_draw.h" -#ifdef XASH_SDL +#if XASH_SDL #include #endif @@ -30,6 +30,8 @@ qboolean in_mouseinitialized; qboolean in_mouse_suspended; POINT in_lastvalidpos; qboolean in_mouse_savedpos; +static uint in_mouse_oldbuttonstate; +static int in_mouse_buttons = 5; // SDL maximum static struct inputstate_s { float lastpitch, lastyaw; @@ -131,7 +133,7 @@ static void IN_ActivateCursor( void ) { if( cls.key_dest == key_menu ) { -#ifdef XASH_SDL +#if XASH_SDL SDL_SetCursor( in_mousecursor ); #endif } @@ -196,24 +198,24 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) else if( newstate == key_game ) { // reset mouse pos, so cancel effect in game -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) +#if XASH_SDL >= 2 if( CVAR_TO_BOOL( touch_enable ) ) { SDL_SetRelativeMouseMode( SDL_FALSE ); SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); } else -#endif +#endif // XASH_SDL >= 2 { Platform_SetMousePos( host.window_center_x, host.window_center_y ); #if XASH_SDL SDL_SetWindowGrab( host.hWnd, SDL_TRUE ); -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) +#if XASH_SDL >= 2 if ( clgame.dllFuncs.pfnLookEvent || ( clgame.client_dll_uses_sdl && CVAR_TO_BOOL( m_rawinput ) ) ) { SDL_SetRelativeMouseMode( SDL_TRUE ); } -#endif +#endif // XASH_SDL >= 2 #endif // XASH_SDL } if( cls.initialized ) @@ -222,14 +224,14 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) if( ( newstate == key_menu || newstate == key_console || newstate == key_message ) && ( !CL_IsBackgroundMap() || CL_IsBackgroundDemo( ))) { -#ifdef XASH_SDL +#if XASH_SDL SDL_SetWindowGrab(host.hWnd, SDL_FALSE); -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) +#if XASH_SDL >= 2 if ( clgame.dllFuncs.pfnLookEvent || ( clgame.client_dll_uses_sdl && CVAR_TO_BOOL( m_rawinput ) ) ) { SDL_SetRelativeMouseMode( SDL_FALSE ); } -#endif +#endif // XASH_SDL >= 2 #endif // XASH_SDL #if XASH_ANDROID Android_ShowMouse( true ); @@ -258,8 +260,7 @@ Called when the window gains focus or changes in some way */ void IN_ActivateMouse( qboolean force ) { - int width, height; - static int oldstate; + static qboolean oldstate; if( !in_mouseinitialized ) return; @@ -277,7 +278,7 @@ void IN_ActivateMouse( qboolean force ) { if( in_mouse_suspended ) { -#ifdef XASH_SDL +#if XASH_SDL /// TODO: Platform_ShowCursor if( !touch_emulate ) SDL_ShowCursor( SDL_FALSE ); @@ -303,10 +304,11 @@ void IN_ActivateMouse( qboolean force ) if( cls.key_dest == key_game ) { - clgame.dllFuncs.IN_ActivateMouse(); -#ifdef XASH_SDL - SDL_GetRelativeMouseState( 0, 0 ); // Reset mouse position +#if XASH_SDL + SDL_SetRelativeMouseMode( SDL_TRUE ); + SDL_GetRelativeMouseState( 0, 0 ); #endif + clgame.dllFuncs.IN_ActivateMouse(); } } @@ -322,15 +324,17 @@ void IN_DeactivateMouse( void ) if( !in_mouseinitialized || !in_mouseactive ) return; +#if XASH_SDL + SDL_SetRelativeMouseMode( SDL_FALSE ); + SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); +#endif // XASH_SDL + if( cls.key_dest == key_game ) { clgame.dllFuncs.IN_DeactivateMouse(); } in_mouseactive = false; -#ifdef XASH_SDL - SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); -#endif // XASH_SDL } /* @@ -342,9 +346,14 @@ void IN_MouseMove( void ) { POINT current_pos; - if( !in_mouseinitialized || !in_mouseactive || !UI_IsVisible( )) + if( !in_mouseinitialized || !in_mouseactive ) return; +#if XASH_SDL >= 2 + if( cls.key_dest == key_game && !host.mouse_visible && touch_emulate->value == 0.0f ) + SDL_SetRelativeMouseMode( SDL_TRUE ); +#endif // XASH_SDL >= 2 + // find mouse movement Platform_GetMousePos( ¤t_pos.x, ¤t_pos.y ); @@ -352,7 +361,7 @@ void IN_MouseMove( void ) // HACKHACK: show cursor in UI, as mainui doesn't call // platform-dependent SetCursor anymore -#ifdef XASH_SDL +#if XASH_SDL if( UI_IsVisible() ) SDL_ShowCursor( SDL_TRUE ); #endif @@ -368,74 +377,49 @@ void IN_MouseMove( void ) IN_MouseEvent =========== */ -void IN_MouseEvent( void ) +void IN_MouseEvent( uint mstate ) { int i; - // touch emu: handle motion - if( CVAR_TO_BOOL( touch_emulate )) - { - if( Key_IsDown( K_SHIFT ) ) - Touch_KeyEvent( K_MOUSE2, 2 ); - else - Touch_KeyEvent( K_MOUSE1, 2 ); - } - if( !in_mouseinitialized || !in_mouseactive ) return; - - if( m_ignore->value ) - return; - if( cls.key_dest == key_game ) { -#if defined( XASH_SDL ) - static qboolean ignore; // igonre mouse warp event - int x, y; - Platform_GetMousePos(&x, &y); - if( host.mouse_visible ) - SDL_ShowCursor( SDL_TRUE ); - else if( !CVAR_TO_BOOL( touch_emulate ) ) - SDL_ShowCursor( SDL_FALSE ); - - if( x < host.window_center_x / 2 || - y < host.window_center_y / 2 || - x > host.window_center_x + host.window_center_x / 2 || - y > host.window_center_y + host.window_center_y / 2 ) + // perform button actions + for( i = 0; i < in_mouse_buttons; i++ ) { - Platform_SetMousePos( host.window_center_x, host.window_center_y ); - ignore = 1; // next mouse event will be mouse warp - return; - } - - if ( !ignore ) - { - if( !m_enginemouse->value ) + if( FBitSet( mstate, BIT( i )) && !FBitSet( in_mouse_oldbuttonstate, BIT( i ))) { - // a1ba: mouse keys are now separated - // so pass 0 here - clgame.dllFuncs.IN_MouseEvent( 0 ); + VGui_KeyEvent( K_MOUSE1 + i, true ); + } + + if( !FBitSet( mstate, BIT( i )) && FBitSet( in_mouse_oldbuttonstate, BIT( i ))) + { + VGui_KeyEvent( K_MOUSE1 + i, false ); } } - else - { - SDL_GetRelativeMouseState( 0, 0 ); // reset relative state - ignore = 0; - } -#endif + + clgame.dllFuncs.IN_MouseEvent( mstate ); + + in_mouse_oldbuttonstate = mstate; return; } - else + + // perform button actions + for( i = 0; i < in_mouse_buttons; i++ ) { -#if XASH_SDL && !XASH_WIN32 -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - SDL_SetRelativeMouseMode( SDL_FALSE ); -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) - SDL_ShowCursor( SDL_TRUE ); -#endif // XASH_SDL && !XASH_WIN32 - IN_MouseMove(); + if( FBitSet( mstate, BIT( i )) && !FBitSet( in_mouse_oldbuttonstate, BIT( i ))) + { + Key_Event( K_MOUSE1 + i, true ); + } + + if( !FBitSet( mstate, BIT( i )) && FBitSet( in_mouse_oldbuttonstate, BIT( i ))) + { + Key_Event( K_MOUSE1 + i, false ); + } } + in_mouse_oldbuttonstate = mstate; } /* @@ -626,7 +610,9 @@ void IN_EngineAppendMove( float frametime, void *cmd1, qboolean active ) { float sensitivity = 1;//( (float)cl.local.scr_fov / (float)90.0f ); - IN_CollectInput( &forward, &side, &pitch, &yaw, in_mouseinitialized && !CVAR_TO_BOOL( m_ignore ), m_enginemouse->value ); + IN_CollectInput( &forward, &side, &pitch, &yaw, + !host.mouse_visible && in_mouseinitialized && !CVAR_TO_BOOL( m_ignore ), + m_enginemouse->value ); IN_JoyAppendMove( cmd, forward, side ); @@ -635,7 +621,7 @@ void IN_EngineAppendMove( float frametime, void *cmd1, qboolean active ) cmd->viewangles[YAW] += yaw * sensitivity; cmd->viewangles[PITCH] += pitch * sensitivity; cmd->viewangles[PITCH] = bound( -90, cmd->viewangles[PITCH], 90 ); - VectorCopy(cmd->viewangles, cl.viewangles); + VectorCopy( cmd->viewangles, cl.viewangles ); } } } diff --git a/engine/client/input.h b/engine/client/input.h index 184220df..5392dda3 100644 --- a/engine/client/input.h +++ b/engine/client/input.h @@ -33,7 +33,7 @@ extern qboolean in_mouseinitialized; void IN_Init( void ); void Host_InputFrame( void ); void IN_Shutdown( void ); -void IN_MouseEvent( void ); +void IN_MouseEvent( uint mstate ); void IN_ActivateMouse( qboolean force ); void IN_DeactivateMouse( void ); void IN_MouseSavePos( void ); diff --git a/engine/platform/sdl/events.c b/engine/platform/sdl/events.c index 4ee53cf2..f5da7f20 100644 --- a/engine/platform/sdl/events.c +++ b/engine/platform/sdl/events.c @@ -271,30 +271,27 @@ SDLash_MouseEvent static void SDLash_MouseEvent( SDL_MouseButtonEvent button ) { int down = button.state != SDL_RELEASED; - qboolean istouch; + uint mstate = 0; -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - istouch = button.which == SDL_TOUCH_MOUSEID; -#else // SDL_VERSION_ATLEAST( 2, 0, 0 ) - istouch = false; -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) + if( button.which == SDL_TOUCH_MOUSEID ) + return; switch( button.button ) { case SDL_BUTTON_LEFT: - SDLash_MouseKey( K_MOUSE1, down, istouch ); - break; - case SDL_BUTTON_RIGHT: - SDLash_MouseKey( K_MOUSE2, down, istouch ); + if( down ) SetBits( mstate, BIT( 0 )); break; case SDL_BUTTON_MIDDLE: - SDLash_MouseKey( K_MOUSE3, down, istouch ); + if( down ) SetBits( mstate, BIT( 1 )); + break; + case SDL_BUTTON_RIGHT: + if( down ) SetBits( mstate, BIT( 2 )); break; case SDL_BUTTON_X1: - SDLash_MouseKey( K_MOUSE4, down, istouch ); + if( down ) SetBits( mstate, BIT( 3 )); break; case SDL_BUTTON_X2: - SDLash_MouseKey( K_MOUSE5, down, istouch ); + if( down ) SetBits( mstate, BIT( 4 )); break; #if ! SDL_VERSION_ATLEAST( 2, 0, 0 ) case SDL_BUTTON_WHEELUP: @@ -307,6 +304,8 @@ static void SDLash_MouseEvent( SDL_MouseButtonEvent button ) default: Con_Printf( "Unknown mouse button ID: %d\n", button.button ); } + + IN_MouseEvent( mstate ); } /* @@ -426,13 +425,7 @@ static void SDLash_EventFilter( SDL_Event *event ) { /* Mouse events */ case SDL_MOUSEMOTION: - if( !host.mouse_visible -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - && event->motion.which != SDL_TOUCH_MOUSEID ) -#else - ) -#endif - IN_MouseEvent(); + /* ignored */ break; case SDL_MOUSEBUTTONUP: From 7e687b45bbc9876619e00f58a827f23b2194bd00 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 4 Jan 2022 08:10:28 +0300 Subject: [PATCH 049/548] engine: client always uses SDL (virtually) on non-Win32 platforms --- engine/client/cl_game.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index c5e260bf..5cff0ac6 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -3967,7 +3967,7 @@ qboolean CL_LoadProgs( const char *name ) #else // this doesn't mean other platforms uses SDL2 in any case // it just helps input code to stay platform-independent - clgame.client_dll_uses_sdl = false; + clgame.client_dll_uses_sdl = true; #endif clgame.hInstance = COM_LoadLibrary( name, false, false ); From f63d2d747f4e23969e8cd8de6e646967d283cd0e Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 4 Jan 2022 08:13:57 +0300 Subject: [PATCH 050/548] vgui: remove unneeded SDL calls, remove unused VGUI API function, fix mouse cursor visibility --- engine/client/vgui/vgui_draw.c | 36 ++++++++-------------------------- engine/vgui_api.h | 7 +------ vgui_support/vgui_surf.cpp | 12 +++++------- 3 files changed, 14 insertions(+), 41 deletions(-) diff --git a/engine/client/vgui/vgui_draw.c b/engine/client/vgui/vgui_draw.c index b61c1d85..fa775a2d 100644 --- a/engine/client/vgui/vgui_draw.c +++ b/engine/client/vgui/vgui_draw.c @@ -83,14 +83,13 @@ void VGUI_InitCursors( void ) s_pDefaultCursor[dc_sizeall] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); s_pDefaultCursor[dc_no] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); s_pDefaultCursor[dc_hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); - //host.mouse_visible = true; - SDL_SetCursor( s_pDefaultCursor[dc_arrow] ); #endif } -void GAME_EXPORT VGUI_CursorSelect(enum VGUI_DefaultCursor cursor ) +void GAME_EXPORT VGUI_CursorSelect( enum VGUI_DefaultCursor cursor ) { qboolean visible; + if( cls.key_dest != key_game || cl.paused ) return; @@ -101,35 +100,30 @@ void GAME_EXPORT VGUI_CursorSelect(enum VGUI_DefaultCursor cursor ) visible = false; break; default: - visible = true; - break; + visible = true; + break; } + host.mouse_visible = visible; + #if SDL_VERSION_ATLEAST( 2, 0, 0 ) /// TODO: platform cursors if( CVAR_TO_BOOL( touch_emulate ) ) return; + if( host.mouse_visible ) { - SDL_SetRelativeMouseMode( SDL_FALSE ); SDL_SetCursor( s_pDefaultCursor[cursor] ); SDL_ShowCursor( true ); } else { SDL_ShowCursor( false ); - if( host.mouse_visible ) - SDL_GetRelativeMouseState( NULL, NULL ); Key_EnableTextInput( false, true ); } - //SDL_SetRelativeMouseMode(false); #endif - if( s_currentCursor == cursor ) - return; - s_currentCursor = cursor; - host.mouse_visible = visible; } byte GAME_EXPORT VGUI_GetColor( int i, int j) @@ -138,18 +132,6 @@ byte GAME_EXPORT VGUI_GetColor( int i, int j) } // Define and initialize vgui API - -void GAME_EXPORT VGUI_SetVisible( qboolean state ) -{ - host.mouse_visible=state; -#ifdef XASH_SDL - SDL_ShowCursor( state ); - if( !state ) - SDL_GetRelativeMouseState( NULL, NULL ); -#endif - Key_EnableTextInput( false, true ); -} - int GAME_EXPORT VGUI_UtfProcessChar( int in ) { if( CVAR_TO_BOOL( vgui_utf8 ) ) @@ -175,12 +157,10 @@ vguiapi_t vgui = NULL, // VGUI_GetTextureSizes, NULL, // VGUI_GenerateTexture, VGUI_EngineMalloc, -/* VGUI_ShowCursor, - VGUI_HideCursor,*/ VGUI_CursorSelect, VGUI_GetColor, VGUI_IsInGame, - VGUI_SetVisible, + NULL, VGUI_GetMousePos, VGUI_UtfProcessChar, NULL, diff --git a/engine/vgui_api.h b/engine/vgui_api.h index 7f35d5d9..a54c4d8d 100644 --- a/engine/vgui_api.h +++ b/engine/vgui_api.h @@ -176,11 +176,6 @@ enum VGUI_DefaultCursor dc_last }; - - - - - typedef struct vguiapi_s { qboolean initialized; @@ -201,7 +196,7 @@ typedef struct vguiapi_s void (*CursorSelect)( enum VGUI_DefaultCursor cursor ); byte (*GetColor)( int i, int j ); qboolean (*IsInGame)( void ); - void (*SetVisible)( qboolean state ); + void (*Unused)( void ); void (*GetCursorPos)( int *x, int *y ); int (*ProcessUtfChar)( int ch ); void (*Startup)( int width, int height ); diff --git a/vgui_support/vgui_surf.cpp b/vgui_support/vgui_surf.cpp index e994021c..21802dab 100644 --- a/vgui_support/vgui_surf.cpp +++ b/vgui_support/vgui_surf.cpp @@ -92,7 +92,11 @@ bool CEngineSurface :: hasFocus( void ) void CEngineSurface :: setCursor( Cursor *cursor ) { _currentCursor = cursor; - g_api->CursorSelect( (VGUI_DefaultCursor)cursor->getDefaultCursor() ); + + if( cursor ) + { + g_api->CursorSelect( (VGUI_DefaultCursor)cursor->getDefaultCursor() ); + } } void CEngineSurface :: SetupPaintState( const PaintStack *paintState ) @@ -428,15 +432,9 @@ void CEngineSurface :: popMakeCurrent( Panel *panel ) bool CEngineSurface :: setFullscreenMode( int wide, int tall, int bpp ) { - // NOTE: Xash3D always working in 32-bit mode - // Skip it now. VGUI cannot change video modes return false; } void CEngineSurface :: setWindowedMode( void ) { - // Skip it now. VGUI cannot change video modes - /* - Cvar_SetFloat( "fullscreen", 0.0f ); - */ } From d0a39ef492f1bb55697348113d772fc24b1528f3 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 4 Jan 2022 08:19:57 +0300 Subject: [PATCH 051/548] engine: client: rework mouse input again, do what GoldSrc does, minimize SDL mouse calls --- engine/client/in_touch.c | 8 +- engine/client/input.c | 238 +++++++++++++--------------------- engine/client/input.h | 2 +- engine/platform/sdl/events.c | 7 +- engine/platform/sdl/vid_sdl.c | 2 - 5 files changed, 100 insertions(+), 157 deletions(-) diff --git a/engine/client/in_touch.c b/engine/client/in_touch.c index 8a72bc04..343b41a2 100644 --- a/engine/client/in_touch.c +++ b/engine/client/in_touch.c @@ -503,20 +503,14 @@ void Touch_SetClientOnly( byte state ) touch.move_finger = touch.look_finger = -1; touch.forward = touch.side = 0; - /// TODO: touch sdl platform -#if 0 if( state ) { - SDL_SetRelativeMouseMode( SDL_FALSE ); - SDL_ShowCursor( true ); IN_DeactivateMouse(); } else { - SDL_ShowCursor( false ); - SDL_GetRelativeMouseState( 0, 0 ); + IN_ActivateMouse(); } -#endif } static void Touch_SetClientOnly_f( void ) diff --git a/engine/client/input.c b/engine/client/input.c index ffe09bed..b5756ec0 100644 --- a/engine/client/input.c +++ b/engine/client/input.c @@ -50,6 +50,8 @@ convar_t *cl_backspeed; convar_t *look_filter; convar_t *m_rawinput; +static qboolean s_bRawInput, s_bMouseGrab; + /* ================ IN_CollectInputDevices @@ -129,21 +131,9 @@ void IN_StartupMouse( void ) in_mouseinitialized = true; } -static void IN_ActivateCursor( void ) -{ - if( cls.key_dest == key_menu ) - { -#if XASH_SDL - SDL_SetCursor( in_mousecursor ); -#endif - } -} - void GAME_EXPORT IN_SetCursor( void *hCursor ) { - in_mousecursor = hCursor; - - IN_ActivateCursor(); + // stub } /* @@ -192,47 +182,15 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) if( oldstate == key_game ) { - if( cls.initialized ) - clgame.dllFuncs.IN_DeactivateMouse(); + IN_DeactivateMouse(); } else if( newstate == key_game ) { - // reset mouse pos, so cancel effect in game -#if XASH_SDL >= 2 - if( CVAR_TO_BOOL( touch_enable ) ) - { - SDL_SetRelativeMouseMode( SDL_FALSE ); - SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); - } - else -#endif // XASH_SDL >= 2 - { - Platform_SetMousePos( host.window_center_x, host.window_center_y ); -#if XASH_SDL - SDL_SetWindowGrab( host.hWnd, SDL_TRUE ); -#if XASH_SDL >= 2 - if ( clgame.dllFuncs.pfnLookEvent || ( clgame.client_dll_uses_sdl && CVAR_TO_BOOL( m_rawinput ) ) ) - { - SDL_SetRelativeMouseMode( SDL_TRUE ); - } -#endif // XASH_SDL >= 2 -#endif // XASH_SDL - } - if( cls.initialized ) - clgame.dllFuncs.IN_ActivateMouse(); + IN_ActivateMouse(); } if( ( newstate == key_menu || newstate == key_console || newstate == key_message ) && ( !CL_IsBackgroundMap() || CL_IsBackgroundDemo( ))) { -#if XASH_SDL - SDL_SetWindowGrab(host.hWnd, SDL_FALSE); -#if XASH_SDL >= 2 - if ( clgame.dllFuncs.pfnLookEvent || ( clgame.client_dll_uses_sdl && CVAR_TO_BOOL( m_rawinput ) ) ) - { - SDL_SetRelativeMouseMode( SDL_FALSE ); - } -#endif // XASH_SDL >= 2 -#endif // XASH_SDL #if XASH_ANDROID Android_ShowMouse( true ); #endif @@ -251,6 +209,65 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) } } +void IN_CheckMouseState( qboolean active ) +{ +#if XASH_WIN32 + qboolean useRawInput = CVAR_TO_BOOL( m_rawinput ) && clgame.client_dll_uses_sdl; +#else + qboolean useRawInput = true; // always use SDL code +#endif + + if( active && useRawInput && !host.mouse_visible && cls.state == ca_active ) + { + if( !s_bRawInput ) + { +#if XASH_SDL >= 2 + SDL_GetRelativeMouseState( NULL, NULL ); + SDL_SetRelativeMouseMode( SDL_TRUE ); +#endif + + // Con_Printf( "Enable relative mode\n" ); + s_bRawInput = true; + } + } + else + { + if( s_bRawInput ) + { +#if XASH_SDL >= 2 + SDL_GetRelativeMouseState( NULL, NULL ); + SDL_SetRelativeMouseMode( SDL_FALSE ); +#endif + // Con_Printf( "Disable relative mode\n" ); + s_bRawInput = false; + } + } + + if( active && !host.mouse_visible && cls.state == ca_active ) + { + if( !s_bMouseGrab ) + { +#if XASH_SDL + SDL_SetWindowGrab( host.hWnd, SDL_TRUE ); +#endif + // Con_Printf( "Enable grab\n" ); + s_bMouseGrab = true; + } + } + else + { + if( s_bMouseGrab ) + { +#if XASH_SDL + SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); +#endif + + // Con_Printf( "Disable grab\n" ); + s_bMouseGrab = false; + } + } +} + /* =========== IN_ActivateMouse @@ -258,58 +275,14 @@ IN_ActivateMouse Called when the window gains focus or changes in some way =========== */ -void IN_ActivateMouse( qboolean force ) +void IN_ActivateMouse( void ) { - static qboolean oldstate; - if( !in_mouseinitialized ) return; - if( CL_Active() && host.mouse_visible && !force ) - return; // VGUI controls - - if( cls.key_dest == key_menu && !Cvar_VariableInteger( "fullscreen" )) - { - // check for mouse leave-entering - if( !in_mouse_suspended && !UI_MouseInRect( )) - in_mouse_suspended = true; - - if( oldstate != in_mouse_suspended ) - { - if( in_mouse_suspended ) - { -#if XASH_SDL - /// TODO: Platform_ShowCursor - if( !touch_emulate ) - SDL_ShowCursor( SDL_FALSE ); -#endif - UI_ShowCursor( false ); - } - } - - oldstate = in_mouse_suspended; - - if( in_mouse_suspended ) - { - in_mouse_suspended = false; - in_mouseactive = false; // re-initialize mouse - UI_ShowCursor( true ); - } - } - - if( in_mouseactive ) return; + IN_CheckMouseState( true ); + clgame.dllFuncs.IN_ActivateMouse(); in_mouseactive = true; - - if( UI_IsVisible( )) return; - - if( cls.key_dest == key_game ) - { -#if XASH_SDL - SDL_SetRelativeMouseMode( SDL_TRUE ); - SDL_GetRelativeMouseState( 0, 0 ); -#endif - clgame.dllFuncs.IN_ActivateMouse(); - } } /* @@ -321,22 +294,16 @@ Called when the window loses focus */ void IN_DeactivateMouse( void ) { - if( !in_mouseinitialized || !in_mouseactive ) + if( !in_mouseinitialized ) return; -#if XASH_SDL - SDL_SetRelativeMouseMode( SDL_FALSE ); - SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); -#endif // XASH_SDL - - if( cls.key_dest == key_game ) - { - clgame.dllFuncs.IN_DeactivateMouse(); - } - + IN_CheckMouseState( false ); + clgame.dllFuncs.IN_DeactivateMouse(); in_mouseactive = false; } + + /* ================ IN_MouseMove @@ -346,14 +313,9 @@ void IN_MouseMove( void ) { POINT current_pos; - if( !in_mouseinitialized || !in_mouseactive ) + if( !in_mouseinitialized ) return; -#if XASH_SDL >= 2 - if( cls.key_dest == key_game && !host.mouse_visible && touch_emulate->value == 0.0f ) - SDL_SetRelativeMouseMode( SDL_TRUE ); -#endif // XASH_SDL >= 2 - // find mouse movement Platform_GetMousePos( ¤t_pos.x, ¤t_pos.y ); @@ -368,8 +330,6 @@ void IN_MouseMove( void ) // if the menu is visible, move the menu cursor UI_MouseMove( current_pos.x, current_pos.y ); - - IN_ActivateCursor(); } /* @@ -381,7 +341,7 @@ void IN_MouseEvent( uint mstate ) { int i; - if( !in_mouseinitialized || !in_mouseactive ) + if( !in_mouseinitialized ) return; if( cls.key_dest == key_game ) @@ -400,7 +360,8 @@ void IN_MouseEvent( uint mstate ) } } - clgame.dllFuncs.IN_MouseEvent( mstate ); + if( in_mouseactive ) + clgame.dllFuncs.IN_MouseEvent( mstate ); in_mouse_oldbuttonstate = mstate; return; @@ -610,9 +571,7 @@ void IN_EngineAppendMove( float frametime, void *cmd1, qboolean active ) { float sensitivity = 1;//( (float)cl.local.scr_fov / (float)90.0f ); - IN_CollectInput( &forward, &side, &pitch, &yaw, - !host.mouse_visible && in_mouseinitialized && !CVAR_TO_BOOL( m_ignore ), - m_enginemouse->value ); + IN_CollectInput( &forward, &side, &pitch, &yaw, false, false ); IN_JoyAppendMove( cmd, forward, side ); @@ -626,26 +585,16 @@ void IN_EngineAppendMove( float frametime, void *cmd1, qboolean active ) } } -/* -================== -Host_InputFrame - -Called every frame, even if not generating commands -================== -*/ -void Host_InputFrame( void ) +void IN_Commands( void ) { - qboolean shutdownMouse = false; - float forward = 0, side = 0, pitch = 0, yaw = 0; - - Sys_SendKeyEvents (); - #ifdef XASH_USE_EVDEV IN_EvdevFrame(); #endif if( clgame.dllFuncs.pfnLookEvent ) { + float forward = 0, side = 0, pitch = 0, yaw = 0; + IN_CollectInput( &forward, &side, &pitch, &yaw, in_mouseinitialized && !CVAR_TO_BOOL( m_ignore ), true ); if( cls.key_dest == key_game ) @@ -658,22 +607,21 @@ void Host_InputFrame( void ) if( !in_mouseinitialized ) return; - if( host.status != HOST_FRAME ) - { - IN_DeactivateMouse(); - return; - } + IN_CheckMouseState( in_mouseactive ); +} - // release mouse during pause or console typeing - if( cl.paused && cls.key_dest == key_game ) - shutdownMouse = true; +/* +================== +Host_InputFrame - if( shutdownMouse && !Cvar_VariableInteger( "fullscreen" )) - { - IN_DeactivateMouse(); - return; - } +Called every frame, even if not generating commands +================== +*/ +void Host_InputFrame( void ) +{ + Sys_SendKeyEvents (); + + IN_Commands(); - IN_ActivateMouse( false ); IN_MouseMove(); } diff --git a/engine/client/input.h b/engine/client/input.h index 5392dda3..4c647dfe 100644 --- a/engine/client/input.h +++ b/engine/client/input.h @@ -34,7 +34,7 @@ void IN_Init( void ); void Host_InputFrame( void ); void IN_Shutdown( void ); void IN_MouseEvent( uint mstate ); -void IN_ActivateMouse( qboolean force ); +void IN_ActivateMouse( void ); void IN_DeactivateMouse( void ); void IN_MouseSavePos( void ); void IN_MouseRestorePos( void ); diff --git a/engine/platform/sdl/events.c b/engine/platform/sdl/events.c index f5da7f20..6e84ae26 100644 --- a/engine/platform/sdl/events.c +++ b/engine/platform/sdl/events.c @@ -340,7 +340,7 @@ static void SDLash_ActiveEvent( int gain ) if( gain ) { host.status = HOST_FRAME; - IN_ActivateMouse( true ); + IN_ActivateMouse( ); if( dma.initialized && snd_mute_losefocus.value ) { SNDDMA_Activate( true ); @@ -425,7 +425,10 @@ static void SDLash_EventFilter( SDL_Event *event ) { /* Mouse events */ case SDL_MOUSEMOTION: - /* ignored */ + if( host.mouse_visible ) + { + SDL_GetRelativeMouseState( NULL, NULL ); + } break; case SDL_MOUSEBUTTONUP: diff --git a/engine/platform/sdl/vid_sdl.c b/engine/platform/sdl/vid_sdl.c index 12b99732..4496c772 100644 --- a/engine/platform/sdl/vid_sdl.c +++ b/engine/platform/sdl/vid_sdl.c @@ -574,7 +574,6 @@ static qboolean VID_SetScreenResolution( int width, int height ) SDL_SetWindowBordered( host.hWnd, SDL_FALSE ); //SDL_SetWindowPosition( host.hWnd, 0, 0 ); - SDL_SetWindowGrab( host.hWnd, SDL_TRUE ); SDL_SetWindowSize( host.hWnd, got.w, got.h ); VID_SaveWindowSize( got.w, got.h ); @@ -590,7 +589,6 @@ void VID_RestoreScreenResolution( void ) if( !Cvar_VariableInteger("fullscreen") ) { SDL_SetWindowBordered( host.hWnd, SDL_TRUE ); - SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); } else { From a2f741fe8afd6a05956d9d009c42e199cd7a9590 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 4 Jan 2022 08:26:41 +0300 Subject: [PATCH 052/548] engine: platform: sdl: try to fix 1.2 build --- engine/client/input.c | 4 ++-- engine/common/crashhandler.c | 2 +- engine/platform/sdl/events.c | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/engine/client/input.c b/engine/client/input.c index b5756ec0..e90a5848 100644 --- a/engine/client/input.c +++ b/engine/client/input.c @@ -221,7 +221,7 @@ void IN_CheckMouseState( qboolean active ) { if( !s_bRawInput ) { -#if XASH_SDL >= 2 +#if XASH_SDL == 2 SDL_GetRelativeMouseState( NULL, NULL ); SDL_SetRelativeMouseMode( SDL_TRUE ); #endif @@ -234,7 +234,7 @@ void IN_CheckMouseState( qboolean active ) { if( s_bRawInput ) { -#if XASH_SDL >= 2 +#if XASH_SDL == 2 SDL_GetRelativeMouseState( NULL, NULL ); SDL_SetRelativeMouseMode( SDL_FALSE ); #endif diff --git a/engine/common/crashhandler.c b/engine/common/crashhandler.c index f8cbc542..fac7474a 100644 --- a/engine/common/crashhandler.c +++ b/engine/common/crashhandler.c @@ -178,7 +178,7 @@ static void Sys_StackTrace( PEXCEPTION_POINTERS pInfo ) len += Q_snprintf( message + len, 1024 - len, ")\n"); } -#if XASH_SDL >= 2 +#if XASH_SDL == 2 if( host.type != HOST_DEDICATED ) // let system to restart server automaticly SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, "Sys_Crash", message, host.hWnd ); #endif diff --git a/engine/platform/sdl/events.c b/engine/platform/sdl/events.c index 6e84ae26..54e36e87 100644 --- a/engine/platform/sdl/events.c +++ b/engine/platform/sdl/events.c @@ -273,8 +273,10 @@ static void SDLash_MouseEvent( SDL_MouseButtonEvent button ) int down = button.state != SDL_RELEASED; uint mstate = 0; +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( button.which == SDL_TOUCH_MOUSEID ) return; +#endif switch( button.button ) { From 1ffe0502051b5a2f6555601f1095b1dbef456a67 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 4 Jan 2022 08:58:58 +0300 Subject: [PATCH 053/548] engine: print only command name in Unknown command message, rather than a full line --- engine/common/cmd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/common/cmd.c b/engine/common/cmd.c index 335596dd..eb684a01 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -1093,7 +1093,7 @@ static void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPr if( Cvar_CommandWithPrivilegeCheck( cvar, isPrivileged )) return; if( host.apply_game_config ) - return; // don't send nothing to server: we is a server! + return; // don't send nothing to server: we are a server! // forward the command line to the server, so the entity DLL can parse it if( host.type == HOST_NORMAL ) @@ -1107,7 +1107,7 @@ static void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPr #endif // XASH_DEDICATED if( Cvar_VariableInteger( "host_gameloaded" )) { - Con_Printf( S_WARN "Unknown command \"%s\"\n", text ); + Con_Printf( S_WARN "Unknown command \"%s\"\n", Cmd_Argv( 0 ) ); } } } From f9d0fba05fcb3667c3bc8a4fea727e0b91ae7e10 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 4 Jan 2022 08:59:46 +0300 Subject: [PATCH 054/548] wscript: remove duplicated branches diagnostic for good --- engine/common/net_encode.c | 21 --------------------- wscript | 1 - 2 files changed, 22 deletions(-) diff --git a/engine/common/net_encode.c b/engine/common/net_encode.c index 05c4a21d..d6f854d3 100644 --- a/engine/common/net_encode.c +++ b/engine/common/net_encode.c @@ -980,10 +980,6 @@ qboolean Delta_CompareField( delta_t *pField, void *from, void *to, double timeb } else if( pField->flags & DT_INTEGER ) { -#if defined __GNUC__ && __GNUC_MAJOR < 9 && !defined __clang__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wduplicated-branches" -#endif if( pField->flags & DT_SIGNED ) { fromF = *(int *)((byte *)from + pField->offset ); @@ -994,9 +990,6 @@ qboolean Delta_CompareField( delta_t *pField, void *from, void *to, double timeb fromF = *(uint *)((byte *)from + pField->offset ); toF = *(uint *)((byte *)to + pField->offset ); } -#if defined __GNUC__ && __GNUC_MAJOR < 9 && !defined __clang__ -#pragma GCC diagnostic pop -#endif fromF = Delta_ClampIntegerField( pField, fromF, bSigned, pField->bits ); toF = Delta_ClampIntegerField( pField, toF, bSigned, pField->bits ); if( !Q_equal( pField->multiplier, 1.0 ) ) @@ -1161,17 +1154,10 @@ qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, void *to } else if( pField->flags & DT_INTEGER ) { -#if defined __GNUC__ && __GNUC_MAJOR < 9 && !defined __clang__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wduplicated-branches" -#endif if( bSigned ) iValue = *(int32_t *)((int8_t *)to + pField->offset ); else iValue = *(uint32_t *)((int8_t *)to + pField->offset ); -#if defined __GNUC__ && __GNUC_MAJOR < 9 && !defined __clang__ -#pragma GCC diagnostic pop -#endif iValue = Delta_ClampIntegerField( pField, iValue, bSigned, pField->bits ); if( !Q_equal( pField->multiplier, 1.0 ) ) @@ -1281,10 +1267,6 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, } else if( pField->flags & DT_INTEGER ) { -#if defined __GNUC__ && __GNUC_MAJOR < 9 && !defined __clang__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wduplicated-branches" -#endif if( bChanged ) { iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); @@ -1302,9 +1284,6 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, *(int32_t *)((uint8_t *)to + pField->offset ) = iValue; else *(uint32_t *)((uint8_t *)to + pField->offset ) = iValue; -#if defined __GNUC__ && __GNUC_MAJOR < 9 && !defined __clang__ -#pragma GCC diagnostic pop -#endif } else if( pField->flags & DT_FLOAT ) { diff --git a/wscript b/wscript index b0d12c0f..e9ba2d90 100644 --- a/wscript +++ b/wscript @@ -184,7 +184,6 @@ def configure(conf): '-Werror=vla', '-Werror=tautological-compare', '-Werror=duplicated-cond', - '-Werror=duplicated-branches', # BEWARE: buggy '-Werror=bool-compare', '-Werror=bool-operation', '-Wcast-align', From 9adf9e1779f4da1ce7e8b5eed28157f4919eca30 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 6 Jan 2022 19:56:15 -0800 Subject: [PATCH 055/548] rtx: decrease payload_opaque dependencies --- ref_vk/shaders/light_polygon.glsl | 14 +++++++------- ref_vk/shaders/ray.rchit | 3 --- ref_vk/shaders/ray.rgen | 22 +++++++++++----------- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 6c19c873..12ec36d2 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -17,7 +17,7 @@ SampleContext buildSampleContext(vec3 position, vec3 normal, vec3 view_dir) { return ctx; } -void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties material, SampleContext ctx, uint ekusok_index, out vec3 out_diffuse, out vec3 out_specular) { +void sampleEmissiveSurface(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, SampleContext ctx, uint ekusok_index, out vec3 out_diffuse, out vec3 out_specular) { out_diffuse = out_specular = vec3(0.); const EmissiveKusok ek = lights.kusochki[ekusok_index]; @@ -116,7 +116,7 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma vec3 tri_diffuse = vec3(0.), tri_specular = vec3(0.); #if 1 - evalSplitBRDF(payload_opaque.normal, light_dir, view_dir, material, tri_diffuse, tri_specular); + evalSplitBRDF(N, light_dir, view_dir, material, tri_diffuse, tri_specular); tri_diffuse *= throughput * ek.emissive; tri_specular *= throughput * ek.emissive; #else @@ -126,8 +126,8 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma #if 1 vec3 combined = tri_diffuse + tri_specular; if (dot(combined,combined) > color_culling_threshold) { - const float dist = -dot(vec4(payload_opaque.hit_pos_t.xyz, 1.f), selected_plane) / dot(light_dir, selected_plane.xyz); - if (!shadowed(payload_opaque.hit_pos_t.xyz, light_dir, dist)) { + const float dist = -dot(vec4(P, 1.f), selected_plane) / dot(light_dir, selected_plane.xyz); + if (!shadowed(P, light_dir, dist)) { const float tri_factor = total_contrib; // / selected_angle.solid_angle; out_diffuse += tri_diffuse * tri_factor; out_specular += tri_specular * tri_factor; @@ -146,9 +146,9 @@ void sampleEmissiveSurface(vec3 throughput, vec3 view_dir, MaterialProperties ma #endif } -void sampleEmissiveSurfaces(vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { +void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { - const SampleContext ctx = buildSampleContext(payload_opaque.hit_pos_t.xyz, payload_opaque.normal, view_dir); + const SampleContext ctx = buildSampleContext(P, N, view_dir); const uint num_emissive_kusochki = uint(light_grid.clusters[cluster_index].num_emissive_surfaces); float sampling_light_scale = 1.; @@ -173,7 +173,7 @@ void sampleEmissiveSurfaces(vec3 throughput, vec3 view_dir, MaterialProperties m } vec3 ldiffuse, lspecular; - sampleEmissiveSurface(throughput, view_dir, material, ctx, index_into_emissive_kusochki, ldiffuse, lspecular); + sampleEmissiveSurface(P, N, throughput, view_dir, material, ctx, index_into_emissive_kusochki, ldiffuse, lspecular); diffuse += ldiffuse * sampling_light_scale; specular += lspecular * sampling_light_scale; diff --git a/ref_vk/shaders/ray.rchit b/ref_vk/shaders/ray.rchit index 4f3104ad..f36a1915 100644 --- a/ref_vk/shaders/ray.rchit +++ b/ref_vk/shaders/ray.rchit @@ -17,9 +17,6 @@ layout (push_constant) uniform PC_ { hitAttributeEXT vec2 bary; -float hash(float f) { return fract(sin(f)*53478.4327); } -vec3 hashUintToVec3(uint i) { return vec3(hash(float(i)), hash(float(i)+15.43), hash(float(i)+34.)); } - // FIXME implement more robust self-intersection avoidance (see chap 6 of "Ray Tracing Gems") const float normal_offset_fudge = .001; diff --git a/ref_vk/shaders/ray.rgen b/ref_vk/shaders/ray.rgen index ecaf73f1..b87fb4f2 100644 --- a/ref_vk/shaders/ray.rgen +++ b/ref_vk/shaders/ray.rgen @@ -59,7 +59,7 @@ layout(location = PAYLOAD_LOCATION_ADDITIVE) rayPayloadEXT RayPayloadAdditive pa #include "light_common.glsl" #include "light_polygon.glsl" -void computePointLights(uint cluster_index, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { +void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { diffuse = specular = vec3(0.); const uint num_point_lights = uint(light_grid.clusters[cluster_index].num_point_lights); for (uint j = 0; j < num_point_lights; ++j) { @@ -75,11 +75,11 @@ void computePointLights(uint cluster_index, vec3 throughput, vec3 view_dir, Mate const float stopdot2 = lights.point_lights[i].dir_stopdot2.a; const bool not_environment = (lights.point_lights[i].environment == 0); - const vec3 light_dir = not_environment ? (origin_r.xyz - payload_opaque.hit_pos_t.xyz) : -dir; // TODO need to randomize sampling direction for environment soft shadow + const vec3 light_dir = not_environment ? (origin_r.xyz - P) : -dir; // TODO need to randomize sampling direction for environment soft shadow const float radius = origin_r.w; const vec3 light_dir_norm = normalize(light_dir); - const float light_dot = dot(light_dir_norm, payload_opaque.normal); + const float light_dot = dot(light_dir_norm, N); if (light_dot < 1e-5) continue; @@ -123,7 +123,7 @@ void computePointLights(uint cluster_index, vec3 throughput, vec3 view_dir, Mate // continue; vec3 ldiffuse, lspecular; - evalSplitBRDF(payload_opaque.normal, light_dir_norm, view_dir, material, ldiffuse, lspecular); + evalSplitBRDF(N, light_dir_norm, view_dir, material, ldiffuse, lspecular); ldiffuse *= color; lspecular *= color; @@ -133,11 +133,11 @@ void computePointLights(uint cluster_index, vec3 throughput, vec3 view_dir, Mate continue; if (not_environment) { - if (shadowed(payload_opaque.hit_pos_t.xyz, light_dir_norm, light_dist + shadow_offset_fudge)) + if (shadowed(P, light_dir_norm, light_dist + shadow_offset_fudge)) continue; } else { // for environment light check that we've hit SURF_SKY - if (shadowedSky(payload_opaque.hit_pos_t.xyz, light_dir_norm, light_dist + shadow_offset_fudge)) + if (shadowedSky(P, light_dir_norm, light_dist + shadow_offset_fudge)) continue; } @@ -146,9 +146,9 @@ void computePointLights(uint cluster_index, vec3 throughput, vec3 view_dir, Mate } // for all lights } -void computeLighting(vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { +void computeLighting(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { diffuse = specular = vec3(0.); - const ivec3 light_cell = ivec3(floor(payload_opaque.hit_pos_t.xyz / LIGHT_GRID_CELL_SIZE)) - light_grid.grid_min; + const ivec3 light_cell = ivec3(floor(P / LIGHT_GRID_CELL_SIZE)) - light_grid.grid_min; const uint cluster_index = uint(dot(light_cell, ivec3(1, light_grid.grid_size.x, light_grid.grid_size.x * light_grid.grid_size.y))); if (any(greaterThanEqual(light_cell, light_grid.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) @@ -163,10 +163,10 @@ void computeLighting(vec3 throughput, vec3 view_dir, MaterialProperties material //C = vec3(float(int(light_grid.clusters[cluster_index].num_emissive_surfaces))); //C += .3 * fract(vec3(light_cell) / 4.); - sampleEmissiveSurfaces(throughput, view_dir, material, cluster_index, diffuse, specular); + sampleEmissiveSurfaces(P, N, throughput, view_dir, material, cluster_index, diffuse, specular); vec3 ldiffuse = vec3(0.), lspecular = vec3(0.); - computePointLights(cluster_index, throughput, view_dir, material, ldiffuse, lspecular); + computePointLights(P, N, cluster_index, throughput, view_dir, material, ldiffuse, lspecular); diffuse += ldiffuse; specular += lspecular; } @@ -338,7 +338,7 @@ void main() { #ifdef SKIP_TRASMITTED_LIGHT vec3 diffuse, specular; - computeLighting(prev_throughput, -direction, material, diffuse, specular); + computeLighting(payload_opaque.hit_pos_t.xyz, payload_opaque.normal, prev_throughput, -direction, material, diffuse, specular); if (bounce == 0) { out_diffuse_gi += diffuse; From cd47861d9bee207ede8c82e72293765da27e0d19 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 7 Jan 2022 18:51:59 -0800 Subject: [PATCH 056/548] rt: start splitting tracer into passes; add primary pass stub renders only uv gradient as a test empty rgen, 6900xt: 12.3us, 6/16 vgpr, 20/128 sgpr, 16/16 occupancy --- ref_vk/shaders/denoiser.comp | 2 +- ref_vk/shaders/ray_primary.rchit | 4 + ref_vk/shaders/ray_primary.rgen | 12 ++ ref_vk/shaders/ray_primary.rmiss | 4 + ref_vk/vk_buffer.c | 5 + ref_vk/vk_buffer.h | 2 + ref_vk/vk_core.c | 5 + ref_vk/vk_core.h | 1 + ref_vk/vk_ray_primary.c | 223 ++++++++++++++++++++++++ ref_vk/vk_ray_primary.h | 21 +++ ref_vk/vk_rtx.c | 283 +++++++++++++++++-------------- 11 files changed, 432 insertions(+), 130 deletions(-) create mode 100644 ref_vk/shaders/ray_primary.rchit create mode 100644 ref_vk/shaders/ray_primary.rgen create mode 100644 ref_vk/shaders/ray_primary.rmiss create mode 100644 ref_vk/vk_ray_primary.c create mode 100644 ref_vk/vk_ray_primary.h diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 147a0b0a..62268f52 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -68,7 +68,7 @@ void main() { const float material_index = imageLoad(src_diffuse_gi, pix).a; //imageStore(dest, pix, vec4(aces_tonemap(base_color.rgb), 0.)); return; - //imageStore(dest, pix, vec4((base_color.rgb), 0.)); return; + imageStore(dest, pix, vec4((base_color.rgb), 0.)); return; //imageStore(dest, pix, vec4((imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_specular, pix).rgb), 0.)); return; diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit new file mode 100644 index 00000000..4b072ab7 --- /dev/null +++ b/ref_vk/shaders/ray_primary.rchit @@ -0,0 +1,4 @@ +#version 460 core +#extension GL_EXT_ray_tracing: require + +void main() {} diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen new file mode 100644 index 00000000..463d6788 --- /dev/null +++ b/ref_vk/shaders/ray_primary.rgen @@ -0,0 +1,12 @@ +#version 460 core +#extension GL_EXT_ray_tracing: require + +layout(set = 0, binding = 0, rgba8) uniform image2D out_image_base_color_r; + +void main() { + vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.; + + vec4 out_base_color_r = vec4(uv, 0., 0.); + + imageStore(out_image_base_color_r, ivec2(gl_LaunchIDEXT.xy), out_base_color_r); +} diff --git a/ref_vk/shaders/ray_primary.rmiss b/ref_vk/shaders/ray_primary.rmiss new file mode 100644 index 00000000..4b072ab7 --- /dev/null +++ b/ref_vk/shaders/ray_primary.rmiss @@ -0,0 +1,4 @@ +#version 460 core +#extension GL_EXT_ray_tracing: require + +void main() {} diff --git a/ref_vk/vk_buffer.c b/ref_vk/vk_buffer.c index 70ed310a..a9da6731 100644 --- a/ref_vk/vk_buffer.c +++ b/ref_vk/vk_buffer.c @@ -92,3 +92,8 @@ void VK_RingBuffer_ClearFrame(vk_ring_buffer_t* buf) { buf->offset_free = buf->permanent_size; buf->free = buf->size - buf->permanent_size; } + +VkDeviceAddress XVK_BufferGetDeviceAddress(VkBuffer buffer) { + const VkBufferDeviceAddressInfo bdai = {.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, .buffer = buffer}; + return vkGetBufferDeviceAddress(vk_core.device, &bdai); +} diff --git a/ref_vk/vk_buffer.h b/ref_vk/vk_buffer.h index 7262a628..85a91360 100644 --- a/ref_vk/vk_buffer.h +++ b/ref_vk/vk_buffer.h @@ -4,6 +4,8 @@ qboolean createBuffer(const char *debug_name, vk_buffer_t *buf, uint32_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags flags); void destroyBuffer(vk_buffer_t *buf); +VkDeviceAddress XVK_BufferGetDeviceAddress(VkBuffer buffer); + // v -- begin of ring buffer|permanent_size // |XXXMAPLIFETME|<......|FRAME1|FRAME2|FRAMEN|......................>| diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index cb0b25ab..cea2f569 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -598,6 +598,11 @@ static qboolean createDevice( void ) { vk_core.physical_device.properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; vkGetPhysicalDeviceProperties2(vk_core.physical_device.device, &vk_core.physical_device.properties2); + if (vk_core.rtx) { + //g_rtx.sbt_record_size = ALIGN_UP(vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupHandleSize, vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupHandleAlignment); + vk_core.physical_device.sbt_record_size = ALIGN_UP(vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupHandleSize, vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupBaseAlignment); + } + vkGetDeviceQueue(vk_core.device, 0, 0, &vk_core.queue); return true; } diff --git a/ref_vk/vk_core.h b/ref_vk/vk_core.h index e3944ddd..b37a9bd6 100644 --- a/ref_vk/vk_core.h +++ b/ref_vk/vk_core.h @@ -45,6 +45,7 @@ typedef struct physical_device_s { VkPhysicalDeviceAccelerationStructurePropertiesKHR properties_accel; VkPhysicalDeviceRayTracingPipelinePropertiesKHR properties_ray_tracing_pipeline; qboolean anisotropy_enabled; + uint32_t sbt_record_size; } physical_device_t; typedef struct vulkan_core_s { diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c new file mode 100644 index 00000000..ca3a4421 --- /dev/null +++ b/ref_vk/vk_ray_primary.c @@ -0,0 +1,223 @@ +#include "vk_ray_primary.h" + +#include "vk_descriptor.h" +#include "vk_pipeline.h" +#include "vk_buffer.h" + +#include "eiface.h" // ARRAYSIZE + +enum { + RtPrim_SBT_RayGen, + RtPrim_SBT_RayMiss, + RtPrim_SBT_RayHit, + RtPrim_SBT_COUNT, +}; + +enum { + RtPrim_Desc_Out_BaseColorR, + RtPrim_Desc_COUNT +}; + +static struct { + struct { + vk_descriptors_t riptors; + VkDescriptorSetLayoutBinding bindings[RtPrim_Desc_COUNT]; + vk_descriptor_value_t values[RtPrim_Desc_COUNT]; + VkDescriptorSet sets[1]; + } desc; + + vk_buffer_t sbt_buffer; + + VkPipeline pipeline; +} g_ray_primary; + +static void initDescriptors( void ) { + g_ray_primary.desc.riptors = (vk_descriptors_t) { + .bindings = g_ray_primary.desc.bindings, + .num_bindings = ARRAYSIZE(g_ray_primary.desc.bindings), + .values = g_ray_primary.desc.values, + .num_sets = ARRAYSIZE(g_ray_primary.desc.sets), + .desc_sets = g_ray_primary.desc.sets, + /* .push_constants = (VkPushConstantRange){ */ + /* .offset = 0, */ + /* .size = sizeof(vk_rtx_push_constants_t), */ + /* .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, */ + /* }, */ + }; + +#define INIT_BINDING(index, type, count, stages) \ + g_ray_primary.desc.bindings[index] = (VkDescriptorSetLayoutBinding){ \ + .binding = index, \ + .descriptorType = type, \ + .descriptorCount = count, \ + .stageFlags = stages, \ + } + + INIT_BINDING(RtPrim_Desc_Out_BaseColorR, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); + + VK_DescriptorsCreate(&g_ray_primary.desc.riptors); +} + +static void initPipeline( void ) { + enum { + ShaderStageIndex_RayGen, + ShaderStageIndex_RayMiss, + ShaderStageIndex_RayClosestHit, + ShaderStageIndex_COUNT, + }; + + VkPipelineShaderStageCreateInfo shaders[ShaderStageIndex_COUNT]; + VkRayTracingShaderGroupCreateInfoKHR shader_groups[RtPrim_SBT_COUNT]; + + const VkRayTracingPipelineCreateInfoKHR rtpci = { + .sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR, + //TODO .flags = VK_PIPELINE_CREATE_RAY_TRACING_NO_NULL_ANY_HIT_SHADERS_BIT_KHR .... + .stageCount = ARRAYSIZE(shaders), + .pStages = shaders, + .groupCount = ARRAYSIZE(shader_groups), + .pGroups = shader_groups, + .maxPipelineRayRecursionDepth = 1, + .layout = g_ray_primary.desc.riptors.pipeline_layout, + }; + +#define DEFINE_SHADER(filename, bit, sbt_index) \ + shaders[sbt_index] = (VkPipelineShaderStageCreateInfo){ \ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, \ + .stage = VK_SHADER_STAGE_##bit##_BIT_KHR, \ + .module = loadShader(filename ".spv"), \ + .pName = "main", \ + .pSpecializationInfo = NULL, \ + } + + DEFINE_SHADER("ray_primary.rgen", RAYGEN, ShaderStageIndex_RayGen); + DEFINE_SHADER("ray_primary.rchit", CLOSEST_HIT, ShaderStageIndex_RayClosestHit); + DEFINE_SHADER("ray_primary.rmiss", MISS, ShaderStageIndex_RayMiss); + + // TODO static assert +/* #define ASSERT_SHADER_OFFSET(sbt_kind, sbt_index, offset) \ */ +/* ASSERT((offset) == (sbt_index - sbt_kind)) */ +/* */ +/* ASSERT_SHADER_OFFSET(ShaderBindingTable_RayGen, ShaderBindingTable_RayGen, 0); */ +/* ASSERT_SHADER_OFFSET(ShaderBindingTable_Miss, ShaderBindingTable_Miss, SHADER_OFFSET_MISS_REGULAR); */ +/* ASSERT_SHADER_OFFSET(ShaderBindingTable_Miss, ShaderBindingTable_Miss_Shadow, SHADER_OFFSET_MISS_SHADOW); */ +/* */ +/* ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_Base, SHADER_OFFSET_HIT_REGULAR_BASE + SHADER_OFFSET_HIT_REGULAR); */ +/* ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_WithAlphaTest, SHADER_OFFSET_HIT_REGULAR_BASE + SHADER_OFFSET_HIT_ALPHA_TEST); */ +/* ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_Additive, SHADER_OFFSET_HIT_REGULAR_BASE + SHADER_OFFSET_HIT_ADDITIVE); */ +/* */ +/* ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_Shadow_Base, SHADER_OFFSET_HIT_SHADOW_BASE + 0); */ +/* ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_Shadow_AlphaTest, SHADER_OFFSET_HIT_SHADOW_BASE + SHADER_OFFSET_HIT_ALPHA_TEST); */ + + shader_groups[RtPrim_SBT_RayGen] = (VkRayTracingShaderGroupCreateInfoKHR) { + .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, + .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR, + .anyHitShader = VK_SHADER_UNUSED_KHR, + .closestHitShader = VK_SHADER_UNUSED_KHR, + .generalShader = ShaderStageIndex_RayGen, + .intersectionShader = VK_SHADER_UNUSED_KHR, + }; + + shader_groups[RtPrim_SBT_RayHit] = (VkRayTracingShaderGroupCreateInfoKHR) { + .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, + .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR, + .anyHitShader = VK_SHADER_UNUSED_KHR, + .closestHitShader = ShaderStageIndex_RayClosestHit, + .generalShader = VK_SHADER_UNUSED_KHR, + .intersectionShader = VK_SHADER_UNUSED_KHR, + }; + + shader_groups[RtPrim_SBT_RayMiss] = (VkRayTracingShaderGroupCreateInfoKHR) { + .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, + .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR, + .anyHitShader = VK_SHADER_UNUSED_KHR, + .closestHitShader = VK_SHADER_UNUSED_KHR, + .generalShader = ShaderStageIndex_RayMiss, + .intersectionShader = VK_SHADER_UNUSED_KHR, + }; + + XVK_CHECK(vkCreateRayTracingPipelinesKHR(vk_core.device, VK_NULL_HANDLE, g_pipeline_cache, 1, &rtpci, NULL, &g_ray_primary.pipeline)); + ASSERT(g_ray_primary.pipeline != VK_NULL_HANDLE); + + { + const uint32_t sbt_handle_size = vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupHandleSize; + const uint32_t sbt_handles_buffer_size = ARRAYSIZE(shader_groups) * sbt_handle_size; + uint8_t *sbt_handles = Mem_Malloc(vk_core.pool, sbt_handles_buffer_size); + XVK_CHECK(vkGetRayTracingShaderGroupHandlesKHR(vk_core.device, g_ray_primary.pipeline, 0, ARRAYSIZE(shader_groups), sbt_handles_buffer_size, sbt_handles)); + for (int i = 0; i < ARRAYSIZE(shader_groups); ++i) + { + uint8_t *sbt_dst = g_ray_primary.sbt_buffer.mapped; + memcpy(sbt_dst + vk_core.physical_device.sbt_record_size * i, sbt_handles + sbt_handle_size * i, sbt_handle_size); + } + Mem_Free(sbt_handles); + } + + for (int i = 0; i < ARRAYSIZE(shaders); ++i) + vkDestroyShaderModule(vk_core.device, shaders[i].module, NULL); +} + +qboolean XVK_RayTracePrimaryInit( void ) { + if (!createBuffer("primary ray sbt_buffer", &g_ray_primary.sbt_buffer, RtPrim_SBT_COUNT * vk_core.physical_device.sbt_record_size, + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) + { + return false; + } + + initDescriptors(); + initPipeline(); + + return true; +} + +void XVK_RayTracePrimaryDestroy( void ) { + vkDestroyPipeline(vk_core.device, g_ray_primary.pipeline, NULL); + VK_DescriptorsDestroy(&g_ray_primary.desc.riptors); + + destroyBuffer(&g_ray_primary.sbt_buffer); +} + +static void updateDescriptors( const xvk_ray_trace_primary_t* args ) { + g_ray_primary.desc.values[RtPrim_Desc_Out_BaseColorR].image = (VkDescriptorImageInfo){ + .sampler = VK_NULL_HANDLE, + .imageView = args->out.base_color_r, + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + + VK_DescriptorsWrite(&g_ray_primary.desc.riptors); +} + +void XVK_RayTracePrimary( VkCommandBuffer cmdbuf, const xvk_ray_trace_primary_t *args ) { + updateDescriptors( args ); + + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_primary.pipeline); + /* { */ + /* vk_rtx_push_constants_t push_constants = { */ + /* .time = gpGlobals->time, */ + /* .random_seed = (uint32_t)gEngine.COM_RandomLong(0, INT32_MAX), */ + /* .bounces = vk_rtx_bounces->value, */ + /* .pixel_cone_spread_angle = atanf((2.0f*tanf(DEG2RAD(fov_angle_y) * 0.5f)) / (float)FRAME_HEIGHT), */ + /* .debug_light_index_begin = (uint32_t)(vk_rtx_light_begin->value), */ + /* .debug_light_index_end = (uint32_t)(vk_rtx_light_end->value), */ + /* .flags = r_lightmap->value ? PUSH_FLAG_LIGHTMAP_ONLY : 0, */ + /* }; */ + /* vkCmdPushConstants(cmdbuf, g_ray_primary.descriptors.pipeline_layout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, 0, sizeof(push_constants), &push_constants); */ + /* } */ + + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_primary.desc.riptors.pipeline_layout, 0, 1, g_ray_primary.desc.riptors.desc_sets + 0, 0, NULL); + + { + const uint32_t sbt_record_size = vk_core.physical_device.sbt_record_size; +#define SBT_INDEX(index, count) { \ +.deviceAddress = XVK_BufferGetDeviceAddress(g_ray_primary.sbt_buffer.buffer) + sbt_record_size * index, \ +.size = sbt_record_size * (count), \ +.stride = sbt_record_size, \ +} + const VkStridedDeviceAddressRegionKHR sbt_raygen = SBT_INDEX(RtPrim_SBT_RayGen, 1); + const VkStridedDeviceAddressRegionKHR sbt_miss = SBT_INDEX(RtPrim_SBT_RayMiss, 1); //ShaderBindingTable_Miss_Empty - ShaderBindingTable_Miss); + const VkStridedDeviceAddressRegionKHR sbt_hit = SBT_INDEX(RtPrim_SBT_RayHit, 1); //ShaderBindingTable_Hit__END - ShaderBindingTable_Hit_Base); + const VkStridedDeviceAddressRegionKHR sbt_callable = { 0 }; + + vkCmdTraceRaysKHR(cmdbuf, &sbt_raygen, &sbt_miss, &sbt_hit, &sbt_callable, args->width, args->height, 1 ); + } +} + diff --git a/ref_vk/vk_ray_primary.h b/ref_vk/vk_ray_primary.h new file mode 100644 index 00000000..00378259 --- /dev/null +++ b/ref_vk/vk_ray_primary.h @@ -0,0 +1,21 @@ +#pragma once + +#include "vk_core.h" + +qboolean XVK_RayTracePrimaryInit( void ); +void XVK_RayTracePrimaryDestroy( void ); + +typedef struct { + uint32_t width, height; + + struct { + VkAccelerationStructureKHR tlas; + } in; + + struct { + VkImageView base_color_r; + //VkImageView normals; + } out; +} xvk_ray_trace_primary_t; + +void XVK_RayTracePrimary( VkCommandBuffer cmdbuf, const xvk_ray_trace_primary_t *args ); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index d96491c7..7a38ff6e 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -1,5 +1,7 @@ #include "vk_rtx.h" +#include "vk_ray_primary.h" + #include "vk_core.h" #include "vk_common.h" #include "vk_buffer.h" @@ -610,7 +612,7 @@ static void prepareTlas( VkCommandBuffer cmdbuf ) { createTlas(cmdbuf); } -static void updateDescriptors( VkCommandBuffer cmdbuf, const vk_ray_frame_render_args_t *args, const xvk_ray_frame_images_t *frame_dst ) { +static void updateDescriptors( const vk_ray_frame_render_args_t *args, const xvk_ray_frame_images_t *frame_dst ) { // 3. Update descriptor sets (bind dest image, tlas, projection matrix) VkDescriptorImageInfo dii_all_textures[MAX_TEXTURES]; @@ -708,46 +710,6 @@ static void updateDescriptors( VkCommandBuffer cmdbuf, const vk_ray_frame_render } static qboolean rayTrace( VkCommandBuffer cmdbuf, const xvk_ray_frame_images_t *current_frame, float fov_angle_y ) { -#define LIST_GBUFFER_IMAGES(X) \ - X(base_color) \ - X(diffuse_gi) \ - X(specular) \ - X(additive) \ - X(normals) \ - - // 4. Barrier for TLAS build and dest image layout transfer - { - VkBufferMemoryBarrier bmb[] = { { - .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, - .buffer = g_rtx.accels_buffer.buffer, - .offset = 0, - .size = VK_WHOLE_SIZE, - } }; - VkImageMemoryBarrier image_barrier[] = { -#define GBUFFER_WRITE_BARRIER(img) { \ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, \ - .image = current_frame->img.image, \ - .srcAccessMask = 0, \ - .dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT, \ - .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, \ - .newLayout = VK_IMAGE_LAYOUT_GENERAL, \ - .subresourceRange = (VkImageSubresourceRange) { \ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, \ - .baseMipLevel = 0, \ - .levelCount = 1, \ - .baseArrayLayer = 0, \ - .layerCount = 1, \ - }, \ - }, -LIST_GBUFFER_IMAGES(GBUFFER_WRITE_BARRIER) - }; - vkCmdPipelineBarrier(cmdbuf, - VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, - VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, - 0, 0, NULL, ARRAYSIZE(bmb), bmb, ARRAYSIZE(image_barrier), image_barrier); - } // 4. dispatch ray tracing vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_rtx.pipeline); @@ -955,6 +917,151 @@ static void blitImage( const xvk_blit_args *blit_args ) { } } +static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_args_t* args, const xvk_ray_frame_images_t *current_frame, float fov_angle_y) { + uploadLights(); + + prepareTlas(cmdbuf); + + updateDescriptors(args, current_frame); + +#define LIST_GBUFFER_IMAGES(X) \ + X(base_color) \ + X(diffuse_gi) \ + X(specular) \ + X(additive) \ + X(normals) \ + + // 4. Barrier for TLAS build and dest image layout transfer + { + VkBufferMemoryBarrier bmb[] = { { + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .buffer = g_rtx.accels_buffer.buffer, + .offset = 0, + .size = VK_WHOLE_SIZE, + } }; + VkImageMemoryBarrier image_barrier[] = { +#define GBUFFER_WRITE_BARRIER(img) { \ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, \ + .image = current_frame->img.image, \ + .srcAccessMask = 0, \ + .dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT, \ + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, \ + .newLayout = VK_IMAGE_LAYOUT_GENERAL, \ + .subresourceRange = (VkImageSubresourceRange) { \ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, \ + .baseMipLevel = 0, \ + .levelCount = 1, \ + .baseArrayLayer = 0, \ + .layerCount = 1, \ + }, \ + }, +LIST_GBUFFER_IMAGES(GBUFFER_WRITE_BARRIER) + }; + vkCmdPipelineBarrier(cmdbuf, + VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, + VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, + 0, 0, NULL, ARRAYSIZE(bmb), bmb, ARRAYSIZE(image_barrier), image_barrier); + } + + { + const xvk_ray_trace_primary_t primary_args = { + .width = FRAME_WIDTH, + .height = FRAME_HEIGHT, + .in = { + .tlas = g_rtx.tlas, + }, + .out = { + .base_color_r = current_frame->base_color.view, + }, + }; + XVK_RayTracePrimary( cmdbuf, &primary_args ); + } + //rayTrace(cmdbuf, current_frame, fov_angle_y); + + { + const VkImageMemoryBarrier image_barriers[] = { +#define GBUFFER_READ_BARRIER(img) { \ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, \ + .image = current_frame->img.image, \ + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, \ + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, \ + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, \ + .newLayout = VK_IMAGE_LAYOUT_GENERAL, \ + .subresourceRange = (VkImageSubresourceRange) { \ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, \ + .baseMipLevel = 0, \ + .levelCount = 1, \ + .baseArrayLayer = 0, \ + .layerCount = 1, \ + }, \ + }, +LIST_GBUFFER_IMAGES(GBUFFER_READ_BARRIER) + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .image = current_frame->denoised.image, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .subresourceRange = + (VkImageSubresourceRange){ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + } }; + vkCmdPipelineBarrier(args->cmdbuf, + VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + 0, 0, NULL, 0, NULL, ARRAYSIZE(image_barriers), image_barriers); + } + + { + const xvk_denoiser_args_t denoiser_args = { + .cmdbuf = cmdbuf, + .width = FRAME_WIDTH, + .height = FRAME_HEIGHT, + .src = { + .base_color_view = current_frame->base_color.view, + .diffuse_gi_view = current_frame->diffuse_gi.view, + .specular_view = current_frame->specular.view, + .additive_view = current_frame->additive.view, + .normals_view = current_frame->normals.view, + }, + .dst_view = current_frame->denoised.view, + }; + + XVK_DenoiserDenoise( &denoiser_args ); + } + + { + const xvk_blit_args blit_args = { + .cmdbuf = args->cmdbuf, + .in_stage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + .src = { + .image = current_frame->denoised.image, + .width = FRAME_WIDTH, + .height = FRAME_HEIGHT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + }, + .dst = { + .image = args->dst.image, + .width = args->dst.width, + .height = args->dst.height, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .srcAccessMask = 0, + }, + }; + + blitImage( &blit_args ); + } +} + qboolean initVk2d(void); void deinitVk2d(void); @@ -986,8 +1093,6 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) g_rtx.reload_pipeline = false; } - uploadLights(); - if (g_ray_model_state.frame.num_models == 0) { const xvk_blit_args blit_args = { .cmdbuf = args->cmdbuf, @@ -1011,90 +1116,7 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) clearVkImage( cmdbuf, current_frame->denoised.image ); blitImage( &blit_args ); } else { - prepareTlas(cmdbuf); - updateDescriptors(cmdbuf, args, current_frame); - rayTrace(cmdbuf, current_frame, args->fov_angle_y); - - { - const VkImageMemoryBarrier image_barriers[] = { -#define GBUFFER_READ_BARRIER(img) { \ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, \ - .image = current_frame->img.image, \ - .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, \ - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, \ - .oldLayout = VK_IMAGE_LAYOUT_GENERAL, \ - .newLayout = VK_IMAGE_LAYOUT_GENERAL, \ - .subresourceRange = (VkImageSubresourceRange) { \ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, \ - .baseMipLevel = 0, \ - .levelCount = 1, \ - .baseArrayLayer = 0, \ - .layerCount = 1, \ - }, \ - }, -LIST_GBUFFER_IMAGES(GBUFFER_READ_BARRIER) - { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .image = current_frame->denoised.image, - .srcAccessMask = 0, - .dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT, - .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .subresourceRange = - (VkImageSubresourceRange){ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - } }; - vkCmdPipelineBarrier(args->cmdbuf, - VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - 0, 0, NULL, 0, NULL, ARRAYSIZE(image_barriers), image_barriers); - } - - { - const xvk_denoiser_args_t denoiser_args = { - .cmdbuf = cmdbuf, - .width = FRAME_WIDTH, - .height = FRAME_HEIGHT, - .src = { - .base_color_view = current_frame->base_color.view, - .diffuse_gi_view = current_frame->diffuse_gi.view, - .specular_view = current_frame->specular.view, - .additive_view = current_frame->additive.view, - .normals_view = current_frame->normals.view, - }, - .dst_view = current_frame->denoised.view, - }; - - XVK_DenoiserDenoise( &denoiser_args ); - } - - { - const xvk_blit_args blit_args = { - .cmdbuf = args->cmdbuf, - .in_stage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - .src = { - .image = current_frame->denoised.image, - .width = FRAME_WIDTH, - .height = FRAME_HEIGHT, - .oldLayout = VK_IMAGE_LAYOUT_GENERAL, - .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, - }, - .dst = { - .image = args->dst.image, - .width = args->dst.width, - .height = args->dst.height, - .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .srcAccessMask = 0, - }, - }; - - blitImage( &blit_args ); - } + performTracing( cmdbuf, args, current_frame, args->fov_angle_y ); } } @@ -1236,8 +1258,9 @@ qboolean VK_RayInit( void ) ASSERT(vk_core.rtx); // TODO complain and cleanup on failure - //g_rtx.sbt_record_size = ALIGN_UP(vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupHandleSize, vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupHandleAlignment); - g_rtx.sbt_record_size = ALIGN_UP(vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupHandleSize, vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupBaseAlignment); + ASSERT(XVK_RayTracePrimaryInit()); + + g_rtx.sbt_record_size = vk_core.physical_device.sbt_record_size; if (!createBuffer("ray sbt_buffer", &g_rtx.sbt_buffer, ShaderBindingTable_COUNT * g_rtx.sbt_record_size, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, @@ -1337,6 +1360,8 @@ qboolean VK_RayInit( void ) void VK_RayShutdown( void ) { ASSERT(vk_core.rtx); + XVK_RayTracePrimaryDestroy(); + for (int i = 0; i < ARRAYSIZE(g_rtx.frames); ++i) { XVK_ImageDestroy(&g_rtx.frames[i].denoised); XVK_ImageDestroy(&g_rtx.frames[i].base_color); From 246c42f0439384df279a8c7fcb0fa786536b57dd Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 7 Jan 2022 19:22:40 -0800 Subject: [PATCH 057/548] rt: trace actual rays in primary pass uh oh, test_brush2: 74.3us, 48/48 vgpr, 44/128 sgpr, 16/16 occupancy --- ref_vk/shaders/ray_primary.rchit | 11 +++++++++-- ref_vk/shaders/ray_primary.rgen | 26 ++++++++++++++++++++++++-- ref_vk/shaders/ray_primary_common.glsl | 11 +++++++++++ ref_vk/vk_ray_primary.c | 18 ++++++++++++++++++ ref_vk/vk_ray_primary.h | 3 +++ ref_vk/vk_rtx.c | 1 + 6 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 ref_vk/shaders/ray_primary_common.glsl diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index 4b072ab7..ad1e7973 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -1,4 +1,11 @@ #version 460 core -#extension GL_EXT_ray_tracing: require +#extension GL_GOOGLE_include_directive : require -void main() {} +#include "ray_primary_common.glsl" + +layout(location = PAYLOAD_LOCATION_PRIMARY) rayPayloadInEXT RayPayloadPrimary payload; + +void main() { + payload.hit_t.w = gl_HitTEXT; + payload.hit_t.xyz = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT; +} diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index 463d6788..bd1d8fee 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -1,12 +1,34 @@ #version 460 core -#extension GL_EXT_ray_tracing: require +#extension GL_GOOGLE_include_directive : require + +#include "ray_primary_common.glsl" layout(set = 0, binding = 0, rgba8) uniform image2D out_image_base_color_r; +layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; +layout(set = 0, binding = 2) uniform UBO { + mat4 inv_proj, inv_view; +} ubo; + +layout(location = PAYLOAD_LOCATION_PRIMARY) rayPayloadEXT RayPayloadPrimary payload; void main() { vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.; - vec4 out_base_color_r = vec4(uv, 0., 0.); + // FIXME start on a near plane + vec3 origin = (ubo.inv_view * vec4(0, 0, 0, 1)).xyz; + vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); + vec3 direction = (ubo.inv_view * vec4(normalize(target.xyz), 0)).xyz; + + const uint flags = gl_RayFlagsCullFrontFacingTrianglesEXT; + const uint sbt_offset = 0; + const uint sbt_stride = 0; + const float L = 10000.; // TODO Why 10k? + traceRayEXT(tlas, flags, GEOMETRY_BIT_OPAQUE, // | GEOMETRY_BIT_REFRACTIVE, + sbt_offset, sbt_stride, SHADER_OFFSET_MISS_REGULAR, + origin, 0., direction, L, + PAYLOAD_LOCATION_PRIMARY); + + vec4 out_base_color_r = vec4(fract(payload.hit_t.xyz), 0.); imageStore(out_image_base_color_r, ivec2(gl_LaunchIDEXT.xy), out_base_color_r); } diff --git a/ref_vk/shaders/ray_primary_common.glsl b/ref_vk/shaders/ray_primary_common.glsl new file mode 100644 index 00000000..93b0a7a6 --- /dev/null +++ b/ref_vk/shaders/ray_primary_common.glsl @@ -0,0 +1,11 @@ +#extension GL_EXT_ray_tracing: require + +#define GLSL +#include "ray_interop.h" +#undef GLSL + +struct RayPayloadPrimary { + vec4 hit_t; +}; + +#define PAYLOAD_LOCATION_PRIMARY 0 diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index ca3a4421..2ded7936 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -15,6 +15,8 @@ enum { enum { RtPrim_Desc_Out_BaseColorR, + RtPrim_Desc_TLAS, + RtPrim_Desc_UBO, RtPrim_Desc_COUNT }; @@ -23,6 +25,8 @@ static struct { vk_descriptors_t riptors; VkDescriptorSetLayoutBinding bindings[RtPrim_Desc_COUNT]; vk_descriptor_value_t values[RtPrim_Desc_COUNT]; + + // TODO: split into two sets, one common to all rt passes (tlas, kusochki, etc), another one this pass only VkDescriptorSet sets[1]; } desc; @@ -54,6 +58,8 @@ static void initDescriptors( void ) { } INIT_BINDING(RtPrim_Desc_Out_BaseColorR, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); + INIT_BINDING(RtPrim_Desc_UBO, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); + INIT_BINDING(RtPrim_Desc_TLAS, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); VK_DescriptorsCreate(&g_ray_primary.desc.riptors); } @@ -183,6 +189,18 @@ static void updateDescriptors( const xvk_ray_trace_primary_t* args ) { .imageLayout = VK_IMAGE_LAYOUT_GENERAL, }; + g_ray_primary.desc.values[RtPrim_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, + .accelerationStructureCount = 1, + .pAccelerationStructures = &args->in.tlas, + }; + + g_ray_primary.desc.values[RtPrim_Desc_UBO].buffer = (VkDescriptorBufferInfo){ + .buffer = args->in.ubo.buffer, + .offset = args->in.ubo.offset, + .range = args->in.ubo.size, + }; + VK_DescriptorsWrite(&g_ray_primary.desc.riptors); } diff --git a/ref_vk/vk_ray_primary.h b/ref_vk/vk_ray_primary.h index 00378259..a64b7260 100644 --- a/ref_vk/vk_ray_primary.h +++ b/ref_vk/vk_ray_primary.h @@ -2,6 +2,8 @@ #include "vk_core.h" +#include "vk_rtx.h" + qboolean XVK_RayTracePrimaryInit( void ); void XVK_RayTracePrimaryDestroy( void ); @@ -10,6 +12,7 @@ typedef struct { struct { VkAccelerationStructureKHR tlas; + vk_buffer_region_t ubo; } in; struct { diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 7a38ff6e..9b52fb3b 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -971,6 +971,7 @@ LIST_GBUFFER_IMAGES(GBUFFER_WRITE_BARRIER) .height = FRAME_HEIGHT, .in = { .tlas = g_rtx.tlas, + .ubo = args->ubo, }, .out = { .base_color_r = current_frame->base_color.view, From da91ff551e75b324b0569c758e9b4bad24dfefdb Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 7 Jan 2022 22:14:50 -0800 Subject: [PATCH 058/548] rt: pass geometry data to primary ray pass also teach it to reload --- ref_vk/shaders/ray_interop.h | 4 ++ ref_vk/shaders/ray_primary.rchit | 35 +++++++++++- ref_vk/shaders/ray_primary_common.glsl | 1 + ref_vk/vk_ray_primary.c | 79 +++++++++++++++++++++----- ref_vk/vk_ray_primary.h | 3 + ref_vk/vk_rtx.c | 16 ++++++ ref_vk/vk_rtx.h | 2 +- 7 files changed, 123 insertions(+), 17 deletions(-) diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index 98841150..af8684fa 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -1,4 +1,6 @@ // Common definitions for both shaders and native code +#ifndef RAY_INTEROP_H_INCLUDED +#define RAY_INTEROP_H_INCLUDED #ifndef GLSL #include "xash3d_types.h" @@ -116,3 +118,5 @@ struct PushConstants { #undef TOKENPASTE #undef TOKENPASTE2 #endif + +#endif // RAY_INTEROP_H_INCLUDED diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index ad1e7973..40d91655 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -3,9 +3,42 @@ #include "ray_primary_common.glsl" +#include "ray_kusochki.glsl" + layout(location = PAYLOAD_LOCATION_PRIMARY) rayPayloadInEXT RayPayloadPrimary payload; +hitAttributeEXT vec2 bary; + +vec3 baryMix(vec3 v1, vec3 v2, vec3 v3, vec2 bary) { + return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; +} + +struct Geometry { + vec3 pos; + //vec2 uv; +}; + +Geometry readHitGeometry() { + Geometry geom; + + const int instance_kusochki_offset = gl_InstanceCustomIndexEXT; + const int kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT; + const Kusok kusok = kusochki[kusok_index]; + + const uint first_index_offset = kusok.index_offset + gl_PrimitiveID * 3; + const uint vi1 = uint(indices[first_index_offset+0]) + kusok.vertex_offset; + const uint vi2 = uint(indices[first_index_offset+1]) + kusok.vertex_offset; + const uint vi3 = uint(indices[first_index_offset+2]) + kusok.vertex_offset; + + geom.pos = (gl_ObjectToWorldEXT * vec4(baryMix( + vertices[vi1].pos, + vertices[vi2].pos, + vertices[vi3].pos, bary), 1.f)).xyz; + + return geom; +} void main() { payload.hit_t.w = gl_HitTEXT; - payload.hit_t.xyz = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT; + //payload.hit_t.xyz = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT; + payload.hit_t.xyz = readHitGeometry().pos; } diff --git a/ref_vk/shaders/ray_primary_common.glsl b/ref_vk/shaders/ray_primary_common.glsl index 93b0a7a6..65edf7a9 100644 --- a/ref_vk/shaders/ray_primary_common.glsl +++ b/ref_vk/shaders/ray_primary_common.glsl @@ -6,6 +6,7 @@ struct RayPayloadPrimary { vec4 hit_t; + vec3 normal_geometry, normal_shading; }; #define PAYLOAD_LOCATION_PRIMARY 0 diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index 2ded7936..07b9f199 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -14,9 +14,17 @@ enum { }; enum { - RtPrim_Desc_Out_BaseColorR, - RtPrim_Desc_TLAS, - RtPrim_Desc_UBO, + // TODO set 1 + RtPrim_Desc_Out_BaseColorR = 0, + + // TODO set 0 + RtPrim_Desc_TLAS = 1, + RtPrim_Desc_UBO = 2, + RtPrim_Desc_Kusochki = 3, + RtPrim_Desc_Indices = 4, + RtPrim_Desc_Vertices = 5, + //RtPrim_Desc_Textures = 6, + RtPrim_Desc_COUNT }; @@ -60,11 +68,27 @@ static void initDescriptors( void ) { INIT_BINDING(RtPrim_Desc_Out_BaseColorR, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); INIT_BINDING(RtPrim_Desc_UBO, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); INIT_BINDING(RtPrim_Desc_TLAS, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); + INIT_BINDING(RtPrim_Desc_Kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); + INIT_BINDING(RtPrim_Desc_Indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); + INIT_BINDING(RtPrim_Desc_Vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); + + /* g_ray_primary.desc.bindings[RtPrim_Desc_Textures] = (VkDescriptorSetLayoutBinding){ */ + /* .binding = RtPrim_Desc_Textures, */ + /* .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, */ + /* .descriptorCount = MAX_TEXTURES, */ + /* .stageFlags = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, */ + /* // FIXME on AMD using immutable samplers leads to nearest filtering ???! */ + /* .pImmutableSamplers = NULL, //samplers, */ + /* }; */ + +#undef INIT_BINDING VK_DescriptorsCreate(&g_ray_primary.desc.riptors); } -static void initPipeline( void ) { +static VkPipeline createPipeline( void ) { + VkPipeline pipeline; + enum { ShaderStageIndex_RayGen, ShaderStageIndex_RayMiss, @@ -141,14 +165,19 @@ static void initPipeline( void ) { .intersectionShader = VK_SHADER_UNUSED_KHR, }; - XVK_CHECK(vkCreateRayTracingPipelinesKHR(vk_core.device, VK_NULL_HANDLE, g_pipeline_cache, 1, &rtpci, NULL, &g_ray_primary.pipeline)); - ASSERT(g_ray_primary.pipeline != VK_NULL_HANDLE); + XVK_CHECK(vkCreateRayTracingPipelinesKHR(vk_core.device, VK_NULL_HANDLE, g_pipeline_cache, 1, &rtpci, NULL, &pipeline)); + + for (int i = 0; i < ARRAYSIZE(shaders); ++i) + vkDestroyShaderModule(vk_core.device, shaders[i].module, NULL); + + if (pipeline == VK_NULL_HANDLE) + return VK_NULL_HANDLE; { const uint32_t sbt_handle_size = vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupHandleSize; const uint32_t sbt_handles_buffer_size = ARRAYSIZE(shader_groups) * sbt_handle_size; uint8_t *sbt_handles = Mem_Malloc(vk_core.pool, sbt_handles_buffer_size); - XVK_CHECK(vkGetRayTracingShaderGroupHandlesKHR(vk_core.device, g_ray_primary.pipeline, 0, ARRAYSIZE(shader_groups), sbt_handles_buffer_size, sbt_handles)); + XVK_CHECK(vkGetRayTracingShaderGroupHandlesKHR(vk_core.device, pipeline, 0, ARRAYSIZE(shader_groups), sbt_handles_buffer_size, sbt_handles)); for (int i = 0; i < ARRAYSIZE(shader_groups); ++i) { uint8_t *sbt_dst = g_ray_primary.sbt_buffer.mapped; @@ -157,8 +186,7 @@ static void initPipeline( void ) { Mem_Free(sbt_handles); } - for (int i = 0; i < ARRAYSIZE(shaders); ++i) - vkDestroyShaderModule(vk_core.device, shaders[i].module, NULL); + return pipeline; } qboolean XVK_RayTracePrimaryInit( void ) { @@ -170,7 +198,9 @@ qboolean XVK_RayTracePrimaryInit( void ) { } initDescriptors(); - initPipeline(); + + g_ray_primary.pipeline = createPipeline(); + ASSERT(g_ray_primary.pipeline != VK_NULL_HANDLE); return true; } @@ -182,6 +212,15 @@ void XVK_RayTracePrimaryDestroy( void ) { destroyBuffer(&g_ray_primary.sbt_buffer); } +void XVK_RayTracePrimaryReloadPipeline( void ) { + VkPipeline pipeline = createPipeline(); + if (pipeline == VK_NULL_HANDLE) + return; + + vkDestroyPipeline(vk_core.device, g_ray_primary.pipeline, NULL); + g_ray_primary.pipeline = pipeline; +} + static void updateDescriptors( const xvk_ray_trace_primary_t* args ) { g_ray_primary.desc.values[RtPrim_Desc_Out_BaseColorR].image = (VkDescriptorImageInfo){ .sampler = VK_NULL_HANDLE, @@ -195,11 +234,21 @@ static void updateDescriptors( const xvk_ray_trace_primary_t* args ) { .pAccelerationStructures = &args->in.tlas, }; - g_ray_primary.desc.values[RtPrim_Desc_UBO].buffer = (VkDescriptorBufferInfo){ - .buffer = args->in.ubo.buffer, - .offset = args->in.ubo.offset, - .range = args->in.ubo.size, - }; +#define DESC_SET_BUFFER(index, buffer_) \ + g_ray_primary.desc.values[index].buffer = (VkDescriptorBufferInfo){ \ + .buffer = args->in.buffer_.buffer, \ + .offset = args->in.buffer_.offset, \ + .range = args->in.buffer_.size, \ + } + + DESC_SET_BUFFER(RtPrim_Desc_UBO, ubo); + DESC_SET_BUFFER(RtPrim_Desc_Kusochki, kusochki); + DESC_SET_BUFFER(RtPrim_Desc_Indices, indices); + DESC_SET_BUFFER(RtPrim_Desc_Vertices, vertices); + +#undef DESC_SET_BUFFER + + //g_ray_primary.desc.values[RtPrim_Desc_Textures].image_array = args->in.all_textures; VK_DescriptorsWrite(&g_ray_primary.desc.riptors); } diff --git a/ref_vk/vk_ray_primary.h b/ref_vk/vk_ray_primary.h index a64b7260..42eacf3e 100644 --- a/ref_vk/vk_ray_primary.h +++ b/ref_vk/vk_ray_primary.h @@ -6,6 +6,7 @@ qboolean XVK_RayTracePrimaryInit( void ); void XVK_RayTracePrimaryDestroy( void ); +void XVK_RayTracePrimaryReloadPipeline( void ); typedef struct { uint32_t width, height; @@ -13,6 +14,8 @@ typedef struct { struct { VkAccelerationStructureKHR tlas; vk_buffer_region_t ubo; + vk_buffer_region_t kusochki, indices, vertices; + //VkDescriptorImageInfo *all_textures; // MAX_TEXTURES } in; struct { diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 9b52fb3b..79383c15 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -972,6 +972,21 @@ LIST_GBUFFER_IMAGES(GBUFFER_WRITE_BARRIER) .in = { .tlas = g_rtx.tlas, .ubo = args->ubo, + .kusochki = { + .buffer = g_ray_model_state.kusochki_buffer.buffer, + .offset = 0, + .size = g_ray_model_state.kusochki_buffer.size, + }, + .indices = { + .buffer = args->geometry_data.buffer, + .offset = 0, + .size = args->geometry_data.size, + }, + .vertices = { + .buffer = args->geometry_data.buffer, + .offset = 0, + .size = args->geometry_data.size, + }, }, .out = { .base_color_r = current_frame->base_color.view, @@ -1090,6 +1105,7 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) vkDestroyPipeline(vk_core.device, g_rtx.pipeline, NULL); createPipeline(); + XVK_RayTracePrimaryReloadPipeline(); XVK_DenoiserReloadPipeline(); g_rtx.reload_pipeline = false; } diff --git a/ref_vk/vk_rtx.h b/ref_vk/vk_rtx.h index f1377dfe..19ba718e 100644 --- a/ref_vk/vk_rtx.h +++ b/ref_vk/vk_rtx.h @@ -22,7 +22,7 @@ void VK_RayFrameAddModel( struct vk_ray_model_s *model, const struct vk_render_m typedef struct { VkBuffer buffer; uint32_t offset; - uint32_t size; + uint64_t size; } vk_buffer_region_t; typedef struct { From f85b1b9dc17e894042342ad9cb1ab97a190c2432 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 7 Jan 2022 22:24:57 -0800 Subject: [PATCH 059/548] rt: pass uvs to screen same: 111us, 53(64)v, 48(128)s, 16/16o --- ref_vk/shaders/ray_primary.rchit | 16 ++++++++++++---- ref_vk/shaders/ray_primary.rgen | 3 ++- ref_vk/shaders/ray_primary_common.glsl | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index 40d91655..f3ff8625 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -12,9 +12,13 @@ vec3 baryMix(vec3 v1, vec3 v2, vec3 v3, vec2 bary) { return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; } +vec2 baryMix(vec2 v1, vec2 v2, vec2 v3, vec2 bary) { + return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; +} + struct Geometry { vec3 pos; - //vec2 uv; + vec2 uv; }; Geometry readHitGeometry() { @@ -34,11 +38,15 @@ Geometry readHitGeometry() { vertices[vi2].pos, vertices[vi3].pos, bary), 1.f)).xyz; + geom.uv = baryMix(vertices[vi1].gl_tc, vertices[vi2].gl_tc, vertices[vi3].gl_tc, bary); + //TODO or not TODO? const vec2 texture_uv = texture_uv_stationary + push_constants.time * kusok.uv_speed; + return geom; } void main() { - payload.hit_t.w = gl_HitTEXT; - //payload.hit_t.xyz = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT; - payload.hit_t.xyz = readHitGeometry().pos; + const Geometry geom = readHitGeometry(); + + payload.hit_t = vec4(geom.pos, gl_HitTEXT); + payload.uv = geom.uv; } diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index bd1d8fee..a3140904 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -28,7 +28,8 @@ void main() { origin, 0., direction, L, PAYLOAD_LOCATION_PRIMARY); - vec4 out_base_color_r = vec4(fract(payload.hit_t.xyz), 0.); + //vec4 out_base_color_r = vec4(fract(payload.hit_t.xyz), 0.); + vec4 out_base_color_r = vec4(fract(payload.uv), 0., 0.); imageStore(out_image_base_color_r, ivec2(gl_LaunchIDEXT.xy), out_base_color_r); } diff --git a/ref_vk/shaders/ray_primary_common.glsl b/ref_vk/shaders/ray_primary_common.glsl index 65edf7a9..327fafa0 100644 --- a/ref_vk/shaders/ray_primary_common.glsl +++ b/ref_vk/shaders/ray_primary_common.glsl @@ -6,7 +6,7 @@ struct RayPayloadPrimary { vec4 hit_t; - vec3 normal_geometry, normal_shading; + vec2 uv; }; #define PAYLOAD_LOCATION_PRIMARY 0 From 4b540d28eb8d68ed583c3fa5ec8b39055c5287cf Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 7 Jan 2022 22:43:27 -0800 Subject: [PATCH 060/548] rt: pass both positions and "color" to g-buffer vgpr is up to 57 :( --- ref_vk/shaders/denoiser.comp | 4 +++- ref_vk/shaders/ray_primary.rgen | 6 ++++-- ref_vk/vk_denoiser.c | 15 +++++++++++++++ ref_vk/vk_denoiser.h | 1 + ref_vk/vk_ray_primary.c | 11 +++++++++++ ref_vk/vk_ray_primary.h | 2 +- ref_vk/vk_rtx.c | 10 ++++++++++ 7 files changed, 45 insertions(+), 4 deletions(-) diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 62268f52..b0ac0422 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -11,6 +11,7 @@ layout(set = 0, binding = 2, rgba16f) uniform readonly image2D src_diffuse_gi; layout(set = 0, binding = 3, rgba16f) uniform readonly image2D src_specular; layout(set = 0, binding = 4, rgba16f) uniform readonly image2D src_additive; layout(set = 0, binding = 5, rgba16f) uniform readonly image2D src_normals; +layout(set = 0, binding = 6, rgba32f) uniform readonly image2D src_position_t; // Blatantly copypasted from https://www.shadertoy.com/view/XsGfWV vec3 aces_tonemap(vec3 color){ @@ -68,7 +69,8 @@ void main() { const float material_index = imageLoad(src_diffuse_gi, pix).a; //imageStore(dest, pix, vec4(aces_tonemap(base_color.rgb), 0.)); return; - imageStore(dest, pix, vec4((base_color.rgb), 0.)); return; + //imageStore(dest, pix, vec4((base_color.rgb), 0.)); return; + imageStore(dest, pix, vec4(fract(imageLoad(src_position_t, pix).rgb / 10.), 0.)); return; //imageStore(dest, pix, vec4((imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_specular, pix).rgb), 0.)); return; diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index a3140904..68f420b6 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -4,6 +4,8 @@ #include "ray_primary_common.glsl" layout(set = 0, binding = 0, rgba8) uniform image2D out_image_base_color_r; +layout(set = 0, binding = 6, rgba32f) uniform image2D out_image_position_t; + layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; layout(set = 0, binding = 2) uniform UBO { mat4 inv_proj, inv_view; @@ -28,8 +30,8 @@ void main() { origin, 0., direction, L, PAYLOAD_LOCATION_PRIMARY); - //vec4 out_base_color_r = vec4(fract(payload.hit_t.xyz), 0.); - vec4 out_base_color_r = vec4(fract(payload.uv), 0., 0.); + const vec4 out_base_color_r = vec4(fract(payload.uv), 0., 0.); + imageStore(out_image_position_t, ivec2(gl_LaunchIDEXT.xy), payload.hit_t); imageStore(out_image_base_color_r, ivec2(gl_LaunchIDEXT.xy), out_base_color_r); } diff --git a/ref_vk/vk_denoiser.c b/ref_vk/vk_denoiser.c index b8b7df12..a7434744 100644 --- a/ref_vk/vk_denoiser.c +++ b/ref_vk/vk_denoiser.c @@ -14,6 +14,8 @@ enum { DenoiserBinding_Source_Additive = 4, DenoiserBinding_Source_Normals = 5, + DenoiserBinding_Source_PositionT = 6, + DenoiserBinding_COUNT }; @@ -81,6 +83,13 @@ static void createLayouts( void ) { .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, }; + g_denoiser.desc_bindings[DenoiserBinding_Source_PositionT] = (VkDescriptorSetLayoutBinding){ + .binding = DenoiserBinding_Source_PositionT, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + }; + VK_DescriptorsCreate(&g_denoiser.descriptors); } @@ -159,6 +168,12 @@ void XVK_DenoiserDenoise( const xvk_denoiser_args_t* args ) { .imageLayout = VK_IMAGE_LAYOUT_GENERAL, }; + g_denoiser.desc_values[DenoiserBinding_Source_PositionT].image = (VkDescriptorImageInfo){ + .sampler = VK_NULL_HANDLE, + .imageView = args->src.position_t_view, + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + VK_DescriptorsWrite(&g_denoiser.descriptors); vkCmdBindPipeline(args->cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_denoiser.pipeline); diff --git a/ref_vk/vk_denoiser.h b/ref_vk/vk_denoiser.h index 10d1c48a..dc58590f 100644 --- a/ref_vk/vk_denoiser.h +++ b/ref_vk/vk_denoiser.h @@ -17,6 +17,7 @@ typedef struct { VkImageView specular_view; VkImageView additive_view; VkImageView normals_view; + VkImageView position_t_view; } src; VkImageView dst_view; diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index 07b9f199..4c34754a 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -25,6 +25,9 @@ enum { RtPrim_Desc_Vertices = 5, //RtPrim_Desc_Textures = 6, + // TODO set 1 + RtPrim_Desc_Out_PositionT = 6, + RtPrim_Desc_COUNT }; @@ -66,6 +69,8 @@ static void initDescriptors( void ) { } INIT_BINDING(RtPrim_Desc_Out_BaseColorR, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); + INIT_BINDING(RtPrim_Desc_Out_PositionT, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); + INIT_BINDING(RtPrim_Desc_UBO, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); INIT_BINDING(RtPrim_Desc_TLAS, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); INIT_BINDING(RtPrim_Desc_Kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); @@ -228,6 +233,12 @@ static void updateDescriptors( const xvk_ray_trace_primary_t* args ) { .imageLayout = VK_IMAGE_LAYOUT_GENERAL, }; + g_ray_primary.desc.values[RtPrim_Desc_Out_PositionT].image = (VkDescriptorImageInfo){ + .sampler = VK_NULL_HANDLE, + .imageView = args->out.position_t, + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + g_ray_primary.desc.values[RtPrim_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, .accelerationStructureCount = 1, diff --git a/ref_vk/vk_ray_primary.h b/ref_vk/vk_ray_primary.h index 42eacf3e..7e8c2d81 100644 --- a/ref_vk/vk_ray_primary.h +++ b/ref_vk/vk_ray_primary.h @@ -19,8 +19,8 @@ typedef struct { } in; struct { + VkImageView position_t; VkImageView base_color_r; - //VkImageView normals; } out; } xvk_ray_trace_primary_t; diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 79383c15..8a9db3d7 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -89,7 +89,11 @@ enum { typedef struct { xvk_image_t denoised; + + xvk_image_t position_t; xvk_image_t base_color; + //xvk_image_t rough_metal_trans; + xvk_image_t diffuse_gi; xvk_image_t specular; xvk_image_t additive; @@ -925,6 +929,7 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar updateDescriptors(args, current_frame); #define LIST_GBUFFER_IMAGES(X) \ + X(position_t) \ X(base_color) \ X(diffuse_gi) \ X(specular) \ @@ -989,6 +994,7 @@ LIST_GBUFFER_IMAGES(GBUFFER_WRITE_BARRIER) }, }, .out = { + .position_t = current_frame->position_t.view, .base_color_r = current_frame->base_color.view, }, }; @@ -1042,6 +1048,7 @@ LIST_GBUFFER_IMAGES(GBUFFER_READ_BARRIER) .width = FRAME_WIDTH, .height = FRAME_HEIGHT, .src = { + .position_t_view = current_frame->position_t.view, .base_color_view = current_frame->base_color.view, .diffuse_gi_view = current_frame->diffuse_gi.view, .specular_view = current_frame->specular.view, @@ -1358,6 +1365,8 @@ qboolean VK_RayInit( void ) } while(0) CREATE_GBUFFER_IMAGE(denoised, VK_FORMAT_R16G16B16A16_SFLOAT, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT); + + CREATE_GBUFFER_IMAGE(position_t, VK_FORMAT_R32G32B32A32_SFLOAT, 0); CREATE_GBUFFER_IMAGE(base_color, VK_FORMAT_R8G8B8A8_UNORM, 0); CREATE_GBUFFER_IMAGE(diffuse_gi, VK_FORMAT_R16G16B16A16_SFLOAT, 0); CREATE_GBUFFER_IMAGE(specular, VK_FORMAT_R16G16B16A16_SFLOAT, 0); @@ -1381,6 +1390,7 @@ void VK_RayShutdown( void ) { for (int i = 0; i < ARRAYSIZE(g_rtx.frames); ++i) { XVK_ImageDestroy(&g_rtx.frames[i].denoised); + XVK_ImageDestroy(&g_rtx.frames[i].position_t); XVK_ImageDestroy(&g_rtx.frames[i].base_color); XVK_ImageDestroy(&g_rtx.frames[i].diffuse_gi); XVK_ImageDestroy(&g_rtx.frames[i].specular); From 93b0766142a00a7f3d2cc924cc27ed7f9fb27016 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 7 Jan 2022 23:22:19 -0800 Subject: [PATCH 061/548] rt: read textures and pass it to gbuffer 102us, 56(64)v, 48(128)s, 16/16o --- ref_vk/shaders/denoiser.comp | 4 +-- ref_vk/shaders/ray_primary.rchit | 22 +++++++++++++--- ref_vk/shaders/ray_primary.rgen | 6 ++--- ref_vk/shaders/ray_primary_common.glsl | 2 +- ref_vk/vk_ray_primary.c | 22 ++++++++-------- ref_vk/vk_ray_primary.h | 2 +- ref_vk/vk_rtx.c | 1 + ref_vk/vk_textures.c | 36 ++++++++++++++++++++++++++ ref_vk/vk_textures.h | 3 +++ 9 files changed, 76 insertions(+), 22 deletions(-) diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index b0ac0422..7b7ca17a 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -69,8 +69,8 @@ void main() { const float material_index = imageLoad(src_diffuse_gi, pix).a; //imageStore(dest, pix, vec4(aces_tonemap(base_color.rgb), 0.)); return; - //imageStore(dest, pix, vec4((base_color.rgb), 0.)); return; - imageStore(dest, pix, vec4(fract(imageLoad(src_position_t, pix).rgb / 10.), 0.)); return; + imageStore(dest, pix, vec4((base_color.rgb), 0.)); return; + //imageStore(dest, pix, vec4(fract(imageLoad(src_position_t, pix).rgb / 10.), 0.)); return; //imageStore(dest, pix, vec4((imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_specular, pix).rgb), 0.)); return; diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index f3ff8625..f51efaf0 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -1,10 +1,14 @@ #version 460 core #extension GL_GOOGLE_include_directive : require +#extension GL_EXT_nonuniform_qualifier : enable #include "ray_primary_common.glsl" #include "ray_kusochki.glsl" +layout (constant_id = 6) const uint MAX_TEXTURES = 4096; +layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; + layout(location = PAYLOAD_LOCATION_PRIMARY) rayPayloadInEXT RayPayloadPrimary payload; hitAttributeEXT vec2 bary; @@ -19,14 +23,16 @@ vec2 baryMix(vec2 v1, vec2 v2, vec2 v3, vec2 bary) { struct Geometry { vec3 pos; vec2 uv; + + int kusok_index; }; Geometry readHitGeometry() { Geometry geom; const int instance_kusochki_offset = gl_InstanceCustomIndexEXT; - const int kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT; - const Kusok kusok = kusochki[kusok_index]; + geom.kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT; + const Kusok kusok = kusochki[geom.kusok_index]; const uint first_index_offset = kusok.index_offset + gl_PrimitiveID * 3; const uint vi1 = uint(indices[first_index_offset+0]) + kusok.vertex_offset; @@ -48,5 +54,15 @@ void main() { const Geometry geom = readHitGeometry(); payload.hit_t = vec4(geom.pos, gl_HitTEXT); - payload.uv = geom.uv; + + const Kusok kusok = kusochki[geom.kusok_index]; + const uint tex_base_color = kusok.tex_base_color; + + if ((tex_base_color & KUSOK_MATERIAL_FLAG_SKYBOX) != 0) { + // FIXME read skybox + payload.base_color_a = vec4(1.,0.,1.,1.); + } else { + // FIXME mips + payload.base_color_a = texture(textures[nonuniformEXT(tex_base_color)], geom.uv) * kusok.color; + } } diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index 68f420b6..eeba54fe 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -4,7 +4,7 @@ #include "ray_primary_common.glsl" layout(set = 0, binding = 0, rgba8) uniform image2D out_image_base_color_r; -layout(set = 0, binding = 6, rgba32f) uniform image2D out_image_position_t; +layout(set = 0, binding = 7, rgba32f) uniform image2D out_image_position_t; layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; layout(set = 0, binding = 2) uniform UBO { @@ -30,8 +30,6 @@ void main() { origin, 0., direction, L, PAYLOAD_LOCATION_PRIMARY); - const vec4 out_base_color_r = vec4(fract(payload.uv), 0., 0.); - imageStore(out_image_position_t, ivec2(gl_LaunchIDEXT.xy), payload.hit_t); - imageStore(out_image_base_color_r, ivec2(gl_LaunchIDEXT.xy), out_base_color_r); + imageStore(out_image_base_color_r, ivec2(gl_LaunchIDEXT.xy), payload.base_color_a); } diff --git a/ref_vk/shaders/ray_primary_common.glsl b/ref_vk/shaders/ray_primary_common.glsl index 327fafa0..2a0b6dba 100644 --- a/ref_vk/shaders/ray_primary_common.glsl +++ b/ref_vk/shaders/ray_primary_common.glsl @@ -6,7 +6,7 @@ struct RayPayloadPrimary { vec4 hit_t; - vec2 uv; + vec4 base_color_a; }; #define PAYLOAD_LOCATION_PRIMARY 0 diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index 4c34754a..2bd31607 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -23,10 +23,10 @@ enum { RtPrim_Desc_Kusochki = 3, RtPrim_Desc_Indices = 4, RtPrim_Desc_Vertices = 5, - //RtPrim_Desc_Textures = 6, + RtPrim_Desc_Textures = 6, // TODO set 1 - RtPrim_Desc_Out_PositionT = 6, + RtPrim_Desc_Out_PositionT = 7, RtPrim_Desc_COUNT }; @@ -77,14 +77,14 @@ static void initDescriptors( void ) { INIT_BINDING(RtPrim_Desc_Indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); INIT_BINDING(RtPrim_Desc_Vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - /* g_ray_primary.desc.bindings[RtPrim_Desc_Textures] = (VkDescriptorSetLayoutBinding){ */ - /* .binding = RtPrim_Desc_Textures, */ - /* .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, */ - /* .descriptorCount = MAX_TEXTURES, */ - /* .stageFlags = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, */ - /* // FIXME on AMD using immutable samplers leads to nearest filtering ???! */ - /* .pImmutableSamplers = NULL, //samplers, */ - /* }; */ + g_ray_primary.desc.bindings[RtPrim_Desc_Textures] = (VkDescriptorSetLayoutBinding){ + .binding = RtPrim_Desc_Textures, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = MAX_TEXTURES, + .stageFlags = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, + // FIXME on AMD using immutable samplers leads to nearest filtering ???! + .pImmutableSamplers = NULL, //samplers, + }; #undef INIT_BINDING @@ -259,7 +259,7 @@ static void updateDescriptors( const xvk_ray_trace_primary_t* args ) { #undef DESC_SET_BUFFER - //g_ray_primary.desc.values[RtPrim_Desc_Textures].image_array = args->in.all_textures; + g_ray_primary.desc.values[RtPrim_Desc_Textures].image_array = args->in.all_textures; VK_DescriptorsWrite(&g_ray_primary.desc.riptors); } diff --git a/ref_vk/vk_ray_primary.h b/ref_vk/vk_ray_primary.h index 7e8c2d81..29135f49 100644 --- a/ref_vk/vk_ray_primary.h +++ b/ref_vk/vk_ray_primary.h @@ -15,7 +15,7 @@ typedef struct { VkAccelerationStructureKHR tlas; vk_buffer_region_t ubo; vk_buffer_region_t kusochki, indices, vertices; - //VkDescriptorImageInfo *all_textures; // MAX_TEXTURES + VkDescriptorImageInfo *all_textures; // [MAX_TEXTURES] } in; struct { diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 8a9db3d7..5e5ca39d 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -992,6 +992,7 @@ LIST_GBUFFER_IMAGES(GBUFFER_WRITE_BARRIER) .offset = 0, .size = args->geometry_data.size, }, + .all_textures = tglob.dii_all_textures, }, .out = { .position_t = current_frame->position_t.view, diff --git a/ref_vk/vk_textures.c b/ref_vk/vk_textures.c index 575c409c..c5b28314 100644 --- a/ref_vk/vk_textures.c +++ b/ref_vk/vk_textures.c @@ -47,6 +47,18 @@ void initTextures( void ) /* FIXME gEngine.Cmd_AddCommand( "texturelist", R_TextureList_f, "display loaded textures list" ); */ + + { + const vk_texture_t *const default_texture = vk_textures + tglob.defaultTexture; + for (int i = 0; i < MAX_TEXTURES; ++i) { + const vk_texture_t *const tex = findTexture(i);; + tglob.dii_all_textures[i] = (VkDescriptorImageInfo){ + .imageView = tex->vk.image.view != VK_NULL_HANDLE ? tex->vk.image.view : default_texture->vk.image.view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .sampler = vk_core.default_sampler, + }; + } + } } static void unloadSkybox( void ); @@ -726,6 +738,15 @@ int VK_LoadTexture( const char *name, const byte *buf, size_t size, int flags ) return 0; } + { + const int index = tex - vk_textures; + tglob.dii_all_textures[index] = (VkDescriptorImageInfo){ + .imageView = tex->vk.image.view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .sampler = vk_core.default_sampler, + }; + } + /* FIXME VK_ApplyTextureParams( tex ); // update texture filter, wrap etc */ @@ -814,6 +835,12 @@ void VK_FreeTexture( unsigned int texnum ) { XVK_ImageDestroy(&tex->vk.image); memset(tex, 0, sizeof(*tex)); + + tglob.dii_all_textures[texnum] = (VkDescriptorImageInfo){ + .imageView = vk_textures[tglob.defaultTexture].vk.image.view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .sampler = vk_core.default_sampler, + }; } int VK_LoadTextureFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update ) @@ -850,6 +877,15 @@ int VK_LoadTextureFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags return 0; } + { + const int index = tex - vk_textures; + tglob.dii_all_textures[index] = (VkDescriptorImageInfo){ + .imageView = tex->vk.image.view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .sampler = vk_core.default_sampler, + }; + } + /* FIXME VK_ApplyTextureParams( tex ); // update texture filter, wrap etc */ diff --git a/ref_vk/vk_textures.h b/ref_vk/vk_textures.h index 5de3d7cd..09f3fa1c 100644 --- a/ref_vk/vk_textures.h +++ b/ref_vk/vk_textures.h @@ -1,6 +1,7 @@ #pragma once #include "vk_core.h" #include "vk_image.h" +#include "vk_const.h" #include "xash3d_types.h" #include "const.h" @@ -45,6 +46,8 @@ typedef struct vk_textures_global_s vk_texture_t skybox_cube; vk_texture_t cubemap_placeholder; + + VkDescriptorImageInfo dii_all_textures[MAX_TEXTURES]; } vk_textures_global_t; // TODO rename this consistently From 3453a2f563a4bdfd13af4c2e51dd62f354c1ae0f Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 8 Jan 2022 13:44:02 -0800 Subject: [PATCH 062/548] rt: add anisomips to primary ray also move uniform buffer to rtx from render 110us, 57(64)v, 52(128)s, 16/16 --- ref_vk/shaders/ray_interop.h | 7 +++ ref_vk/shaders/ray_primary.rchit | 82 +++++++++++++++++++++++++++++--- ref_vk/shaders/ray_primary.rgen | 4 +- ref_vk/vk_ray_primary.c | 2 +- ref_vk/vk_render.c | 26 ++-------- ref_vk/vk_rtx.c | 55 ++++++++++++++++----- ref_vk/vk_rtx.h | 3 +- 7 files changed, 132 insertions(+), 47 deletions(-) diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index af8684fa..d77d7226 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -9,6 +9,7 @@ #define vec2 vec2_t #define vec3 vec3_t #define vec4 vec4_t +#define mat4 matrix4x4 #define TOKENPASTE(x, y) x ## y #define TOKENPASTE2(x, y) TOKENPASTE(x, y) #define PAD(x) float TOKENPASTE2(pad_, __LINE__)[x]; @@ -108,6 +109,12 @@ struct PushConstants { uint flags; }; +struct UniformBuffer { + mat4 inv_proj, inv_view; + float ray_cone_width; + PAD(3) +}; + #undef PAD #undef STRUCT diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index f51efaf0..113cc9ac 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -8,6 +8,7 @@ layout (constant_id = 6) const uint MAX_TEXTURES = 4096; layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; +layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; layout(location = PAYLOAD_LOCATION_PRIMARY) rayPayloadInEXT RayPayloadPrimary payload; hitAttributeEXT vec2 bary; @@ -20,9 +21,56 @@ vec2 baryMix(vec2 v1, vec2 v2, vec2 v3, vec2 bary) { return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; } +vec4 sampleTexture(uint tex_index, vec2 uv, vec4 uv_lods) { + return textureGrad(textures[nonuniformEXT(tex_index)], uv, uv_lods.xy, uv_lods.zw); +} + +// Taken from Journal of Computer Graphics Techniques, Vol. 10, No. 1, 2021. +// Improved Shader and Texture Level of Detail Using Ray Cones, +// by T. Akenine-Moller, C. Crassin, J. Boksansky, L. Belcour, A. Panteleev, and O. Wright +// https://jcgt.org/published/0010/01/01/ +// P is the intersection point +// f is the triangle normal +// d is the ray cone direction +vec4 computeAnisotropicEllipseAxes(in vec3 P, in vec3 f, + in vec3 d, in float rayConeRadiusAtIntersection, + in vec3 positions[3], in vec2 txcoords[3], + in vec2 interpolatedTexCoordsAtIntersection) +{ + vec4 texGradient; + // Compute ellipse axes. + vec3 a1 = d - dot(f, d) * f; + vec3 p1 = a1 - dot(d, a1) * d; + a1 *= rayConeRadiusAtIntersection / max(0.0001, length(p1)); + vec3 a2 = cross(f, a1); + vec3 p2 = a2 - dot(d, a2) * d; + a2 *= rayConeRadiusAtIntersection / max(0.0001, length(p2)); + // Compute texture coordinate gradients. + vec3 eP, delta = P - positions[0]; + vec3 e1 = positions[1] - positions[0]; + vec3 e2 = positions[2] - positions[0]; + float oneOverAreaTriangle = 1.0 / dot(f, cross(e1, e2)); + eP = delta + a1; + float u1 = dot(f, cross(eP, e2)) * oneOverAreaTriangle; + float v1 = dot(f, cross(e1, eP)) * oneOverAreaTriangle; + texGradient.xy = (1.0-u1-v1) * txcoords[0] + u1 * txcoords[1] + + v1 * txcoords[2] - interpolatedTexCoordsAtIntersection; + eP = delta + a2; + float u2 = dot(f, cross(eP, e2)) * oneOverAreaTriangle; + float v2 = dot(f, cross(e1, eP)) * oneOverAreaTriangle; + texGradient.zw = (1.0-u2-v2) * txcoords[0] + u2 * txcoords[1] + + v2 * txcoords[2] - interpolatedTexCoordsAtIntersection; + return texGradient; +} + struct Geometry { vec3 pos; + vec2 uv; + vec4 uv_lods; + + vec3 normal_geometry; + vec3 normal_shading; int kusok_index; }; @@ -39,14 +87,35 @@ Geometry readHitGeometry() { const uint vi2 = uint(indices[first_index_offset+1]) + kusok.vertex_offset; const uint vi3 = uint(indices[first_index_offset+2]) + kusok.vertex_offset; - geom.pos = (gl_ObjectToWorldEXT * vec4(baryMix( - vertices[vi1].pos, - vertices[vi2].pos, - vertices[vi3].pos, bary), 1.f)).xyz; + const vec3 pos[3] = { + gl_ObjectToWorldEXT * vec4(vertices[vi1].pos, 1.f), + gl_ObjectToWorldEXT * vec4(vertices[vi2].pos, 1.f), + gl_ObjectToWorldEXT * vec4(vertices[vi3].pos, 1.f), + }; - geom.uv = baryMix(vertices[vi1].gl_tc, vertices[vi2].gl_tc, vertices[vi3].gl_tc, bary); + const vec2 uvs[3] = { + vertices[vi1].gl_tc, + vertices[vi2].gl_tc, + vertices[vi3].gl_tc, + }; + + geom.pos = baryMix(pos[0], pos[1], pos[2], bary); + geom.uv = baryMix(uvs[0], uvs[1], uvs[2], bary); //TODO or not TODO? const vec2 texture_uv = texture_uv_stationary + push_constants.time * kusok.uv_speed; + // NOTE: need to flip if back-facing + geom.normal_geometry = normalize(cross(pos[2]-pos[0], pos[1]-pos[0])); + + // NOTE: only support rotations, for arbitrary transform would need to do transpose(inverse(mat3(gl_ObjectToWorldEXT))) + const mat3 normalTransform = mat3(gl_ObjectToWorldEXT); + geom.normal_shading = normalTransform * baryMix( + vertices[vi1].normal, + vertices[vi2].normal, + vertices[vi3].normal, + bary); + + geom.uv_lods = computeAnisotropicEllipseAxes(geom.pos, geom.normal_geometry, gl_WorldRayDirectionEXT, ubo.ray_cone_width * gl_HitTEXT, pos, uvs, geom.uv); + return geom; } @@ -62,7 +131,6 @@ void main() { // FIXME read skybox payload.base_color_a = vec4(1.,0.,1.,1.); } else { - // FIXME mips - payload.base_color_a = texture(textures[nonuniformEXT(tex_base_color)], geom.uv) * kusok.color; + payload.base_color_a = sampleTexture(tex_base_color, geom.uv, geom.uv_lods) * kusok.color; } } diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index eeba54fe..8eb05841 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -7,9 +7,7 @@ layout(set = 0, binding = 0, rgba8) uniform image2D out_image_base_color_r; layout(set = 0, binding = 7, rgba32f) uniform image2D out_image_position_t; layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; -layout(set = 0, binding = 2) uniform UBO { - mat4 inv_proj, inv_view; -} ubo; +layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; layout(location = PAYLOAD_LOCATION_PRIMARY) rayPayloadEXT RayPayloadPrimary payload; diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index 2bd31607..7f5b7305 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -71,7 +71,7 @@ static void initDescriptors( void ) { INIT_BINDING(RtPrim_Desc_Out_BaseColorR, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); INIT_BINDING(RtPrim_Desc_Out_PositionT, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); - INIT_BINDING(RtPrim_Desc_UBO, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); + INIT_BINDING(RtPrim_Desc_UBO, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); INIT_BINDING(RtPrim_Desc_TLAS, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); INIT_BINDING(RtPrim_Desc_Kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); INIT_BINDING(RtPrim_Desc_Indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); diff --git a/ref_vk/vk_render.c b/ref_vk/vk_render.c index da741d69..32be817e 100644 --- a/ref_vk/vk_render.c +++ b/ref_vk/vk_render.c @@ -664,12 +664,9 @@ void VK_RenderEndRTX( VkCommandBuffer cmdbuf, VkImageView img_dst_view, VkImage .width = w, .height = h, }, - // FIXME this should really be in vk_rtx, calling vk_render(or what?) to alloc slot for it - .ubo = { - .buffer = g_render.uniform_buffer.buffer, - .offset = allocUniform(sizeof(matrix4x4) * 2, sizeof(matrix4x4)), - .size = sizeof(matrix4x4) * 2, - }, + + .projection = &g_render_state.projection, + .view = &g_render_state.view, .geometry_data = { .buffer = g_render.buffer.buffer, @@ -679,23 +676,6 @@ void VK_RenderEndRTX( VkCommandBuffer cmdbuf, VkImageView img_dst_view, VkImage .fov_angle_y = g_render.fov_angle_y, }; - if (args.ubo.offset == UINT32_MAX) { - gEngine.Con_Printf(S_ERROR "Cannot allocate UBO for RTX\n"); - return; - } - - { - matrix4x4 *ubo_matrices = (matrix4x4*)((byte*)g_render.uniform_buffer.mapped + args.ubo.offset); - matrix4x4 proj_inv, view_inv; - Matrix4x4_Invert_Full(proj_inv, g_render_state.projection); - Matrix4x4_ToArrayFloatGL(proj_inv, (float*)ubo_matrices[0]); - - // TODO there's a more efficient way to construct an inverse view matrix - // from vforward/right/up vectors and origin in g_camera - Matrix4x4_Invert_Full(view_inv, g_render_state.view); - Matrix4x4_ToArrayFloatGL(view_inv, (float*)ubo_matrices[1]); - } - VK_RayFrameEnd(&args); } } diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 5e5ca39d..5723406c 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -12,6 +12,7 @@ #include "vk_descriptor.h" #include "vk_ray_internal.h" #include "vk_denoiser.h" +#include "vk_math.h" #include "eiface.h" #include "xash3d_mathlib.h" @@ -21,7 +22,8 @@ #define MAX_SCRATCH_BUFFER (32*1024*1024) #define MAX_ACCELS_BUFFER (64*1024*1024) -#define MAX_LIGHT_LEAVES 8192 +// TODO actually use it +#define MAX_FRAMES_IN_FLIGHT 2 enum { ShaderBindingTable_RayGen, @@ -108,6 +110,9 @@ static struct { VkPipeline pipeline; + // Holds UniformBuffer data + vk_buffer_t uniform_buffer; + // Shader binding table buffer vk_buffer_t sbt_buffer; uint32_t sbt_record_size; @@ -153,7 +158,7 @@ static struct { } frame; unsigned frame_number; - xvk_ray_frame_images_t frames[2]; + xvk_ray_frame_images_t frames[MAX_FRAMES_IN_FLIGHT]; qboolean reload_pipeline; qboolean reload_lighting; @@ -616,7 +621,7 @@ static void prepareTlas( VkCommandBuffer cmdbuf ) { createTlas(cmdbuf); } -static void updateDescriptors( const vk_ray_frame_render_args_t *args, const xvk_ray_frame_images_t *frame_dst ) { +static void updateDescriptors( const vk_ray_frame_render_args_t *args, int frame_index, const xvk_ray_frame_images_t *frame_dst ) { // 3. Update descriptor sets (bind dest image, tlas, projection matrix) VkDescriptorImageInfo dii_all_textures[MAX_TEXTURES]; @@ -633,9 +638,9 @@ static void updateDescriptors( const vk_ray_frame_render_args_t *args, const xvk }; g_rtx.desc_values[RayDescBinding_UBOMatrices].buffer = (VkDescriptorBufferInfo){ - .buffer = args->ubo.buffer, - .offset = args->ubo.offset, - .range = args->ubo.size, + .buffer = g_rtx.uniform_buffer.buffer, + .offset = frame_index * sizeof(struct UniformBuffer), + .range = sizeof(struct UniformBuffer), }; g_rtx.desc_values[RayDescBinding_Kusochki].buffer = (VkDescriptorBufferInfo){ @@ -921,12 +926,29 @@ static void blitImage( const xvk_blit_args *blit_args ) { } } -static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_args_t* args, const xvk_ray_frame_images_t *current_frame, float fov_angle_y) { +static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int frame_index, float fov_angle_y ) { + struct UniformBuffer *ubo = (struct UniformBuffer*)g_rtx.uniform_buffer.mapped + frame_index; + + matrix4x4 proj_inv, view_inv; + Matrix4x4_Invert_Full(proj_inv, *args->projection); + Matrix4x4_ToArrayFloatGL(proj_inv, (float*)ubo->inv_proj); + + // TODO there's a more efficient way to construct an inverse view matrix + // from vforward/right/up vectors and origin in g_camera + Matrix4x4_Invert_Full(view_inv, *args->view); + Matrix4x4_ToArrayFloatGL(view_inv, (float*)ubo->inv_view); + + ubo->ray_cone_width = atanf((2.0f*tanf(DEG2RAD(fov_angle_y) * 0.5f)) / (float)FRAME_HEIGHT); +} + +static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_args_t* args, int frame_index, const xvk_ray_frame_images_t *current_frame, float fov_angle_y) { uploadLights(); prepareTlas(cmdbuf); - updateDescriptors(args, current_frame); + prepareUniformBuffer(args, frame_index, fov_angle_y); + + updateDescriptors(args, frame_index, current_frame); #define LIST_GBUFFER_IMAGES(X) \ X(position_t) \ @@ -976,7 +998,11 @@ LIST_GBUFFER_IMAGES(GBUFFER_WRITE_BARRIER) .height = FRAME_HEIGHT, .in = { .tlas = g_rtx.tlas, - .ubo = args->ubo, + .ubo = { + .buffer = g_rtx.uniform_buffer.buffer, + .offset = frame_index * sizeof(struct UniformBuffer), + .size = sizeof(struct UniformBuffer), + }, .kusochki = { .buffer = g_ray_model_state.kusochki_buffer.buffer, .offset = 0, @@ -1097,7 +1123,6 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) ASSERT(vk_core.rtx); // ubo should contain two matrices // FIXME pass these matrices explicitly to let RTX module handle ubo itself - ASSERT(args->ubo.size == sizeof(float) * 16 * 2); g_rtx.frame_number++; @@ -1141,7 +1166,7 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) clearVkImage( cmdbuf, current_frame->denoised.image ); blitImage( &blit_args ); } else { - performTracing( cmdbuf, args, current_frame, args->fov_angle_y ); + performTracing( cmdbuf, args, (g_rtx.frame_number % 2), current_frame, args->fov_angle_y ); } } @@ -1287,6 +1312,13 @@ qboolean VK_RayInit( void ) g_rtx.sbt_record_size = vk_core.physical_device.sbt_record_size; + if (!createBuffer("ray uniform_buffer", &g_rtx.uniform_buffer, sizeof(struct UniformBuffer) * MAX_FRAMES_IN_FLIGHT, + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) + { + return false; + } + if (!createBuffer("ray sbt_buffer", &g_rtx.sbt_buffer, ShaderBindingTable_COUNT * g_rtx.sbt_record_size, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) @@ -1419,4 +1451,5 @@ void VK_RayShutdown( void ) { destroyBuffer(&g_ray_model_state.lights_buffer); destroyBuffer(&g_rtx.light_grid_buffer); destroyBuffer(&g_rtx.sbt_buffer); + destroyBuffer(&g_rtx.uniform_buffer); } diff --git a/ref_vk/vk_rtx.h b/ref_vk/vk_rtx.h index 19ba718e..04960198 100644 --- a/ref_vk/vk_rtx.h +++ b/ref_vk/vk_rtx.h @@ -34,8 +34,7 @@ typedef struct { uint32_t width, height; } dst; - // TODO inv_view/proj matrices instead of UBO - vk_buffer_region_t ubo; + const matrix4x4 *projection, *view; // Buffer holding vertex and index data struct { From 090fb3bfbbbab755f54e55a6d9dc968ddfdbb037 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 8 Jan 2022 17:21:04 -0800 Subject: [PATCH 063/548] rt: add alpha test to primary rays 105us, 57(64)v, 60(128)s, 2048l, 16/16 --- ref_vk/shaders/ray_primary.rchit | 104 +-------------------- ref_vk/shaders/ray_primary.rgen | 5 +- ref_vk/shaders/ray_primary_alphatest.rahit | 26 ++++++ ref_vk/shaders/rt_geometry.glsl | 101 ++++++++++++++++++++ ref_vk/vk_ray_primary.c | 42 ++++++--- 5 files changed, 160 insertions(+), 118 deletions(-) create mode 100644 ref_vk/shaders/ray_primary_alphatest.rahit create mode 100644 ref_vk/shaders/rt_geometry.glsl diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index 113cc9ac..54453bda 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -6,119 +6,19 @@ #include "ray_kusochki.glsl" -layout (constant_id = 6) const uint MAX_TEXTURES = 4096; +layout(constant_id = 6) const uint MAX_TEXTURES = 4096; layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; layout(location = PAYLOAD_LOCATION_PRIMARY) rayPayloadInEXT RayPayloadPrimary payload; hitAttributeEXT vec2 bary; -vec3 baryMix(vec3 v1, vec3 v2, vec3 v3, vec2 bary) { - return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; -} - -vec2 baryMix(vec2 v1, vec2 v2, vec2 v3, vec2 bary) { - return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; -} +#include "rt_geometry.glsl" vec4 sampleTexture(uint tex_index, vec2 uv, vec4 uv_lods) { return textureGrad(textures[nonuniformEXT(tex_index)], uv, uv_lods.xy, uv_lods.zw); } -// Taken from Journal of Computer Graphics Techniques, Vol. 10, No. 1, 2021. -// Improved Shader and Texture Level of Detail Using Ray Cones, -// by T. Akenine-Moller, C. Crassin, J. Boksansky, L. Belcour, A. Panteleev, and O. Wright -// https://jcgt.org/published/0010/01/01/ -// P is the intersection point -// f is the triangle normal -// d is the ray cone direction -vec4 computeAnisotropicEllipseAxes(in vec3 P, in vec3 f, - in vec3 d, in float rayConeRadiusAtIntersection, - in vec3 positions[3], in vec2 txcoords[3], - in vec2 interpolatedTexCoordsAtIntersection) -{ - vec4 texGradient; - // Compute ellipse axes. - vec3 a1 = d - dot(f, d) * f; - vec3 p1 = a1 - dot(d, a1) * d; - a1 *= rayConeRadiusAtIntersection / max(0.0001, length(p1)); - vec3 a2 = cross(f, a1); - vec3 p2 = a2 - dot(d, a2) * d; - a2 *= rayConeRadiusAtIntersection / max(0.0001, length(p2)); - // Compute texture coordinate gradients. - vec3 eP, delta = P - positions[0]; - vec3 e1 = positions[1] - positions[0]; - vec3 e2 = positions[2] - positions[0]; - float oneOverAreaTriangle = 1.0 / dot(f, cross(e1, e2)); - eP = delta + a1; - float u1 = dot(f, cross(eP, e2)) * oneOverAreaTriangle; - float v1 = dot(f, cross(e1, eP)) * oneOverAreaTriangle; - texGradient.xy = (1.0-u1-v1) * txcoords[0] + u1 * txcoords[1] + - v1 * txcoords[2] - interpolatedTexCoordsAtIntersection; - eP = delta + a2; - float u2 = dot(f, cross(eP, e2)) * oneOverAreaTriangle; - float v2 = dot(f, cross(e1, eP)) * oneOverAreaTriangle; - texGradient.zw = (1.0-u2-v2) * txcoords[0] + u2 * txcoords[1] + - v2 * txcoords[2] - interpolatedTexCoordsAtIntersection; - return texGradient; -} - -struct Geometry { - vec3 pos; - - vec2 uv; - vec4 uv_lods; - - vec3 normal_geometry; - vec3 normal_shading; - - int kusok_index; -}; - -Geometry readHitGeometry() { - Geometry geom; - - const int instance_kusochki_offset = gl_InstanceCustomIndexEXT; - geom.kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT; - const Kusok kusok = kusochki[geom.kusok_index]; - - const uint first_index_offset = kusok.index_offset + gl_PrimitiveID * 3; - const uint vi1 = uint(indices[first_index_offset+0]) + kusok.vertex_offset; - const uint vi2 = uint(indices[first_index_offset+1]) + kusok.vertex_offset; - const uint vi3 = uint(indices[first_index_offset+2]) + kusok.vertex_offset; - - const vec3 pos[3] = { - gl_ObjectToWorldEXT * vec4(vertices[vi1].pos, 1.f), - gl_ObjectToWorldEXT * vec4(vertices[vi2].pos, 1.f), - gl_ObjectToWorldEXT * vec4(vertices[vi3].pos, 1.f), - }; - - const vec2 uvs[3] = { - vertices[vi1].gl_tc, - vertices[vi2].gl_tc, - vertices[vi3].gl_tc, - }; - - geom.pos = baryMix(pos[0], pos[1], pos[2], bary); - geom.uv = baryMix(uvs[0], uvs[1], uvs[2], bary); - //TODO or not TODO? const vec2 texture_uv = texture_uv_stationary + push_constants.time * kusok.uv_speed; - - // NOTE: need to flip if back-facing - geom.normal_geometry = normalize(cross(pos[2]-pos[0], pos[1]-pos[0])); - - // NOTE: only support rotations, for arbitrary transform would need to do transpose(inverse(mat3(gl_ObjectToWorldEXT))) - const mat3 normalTransform = mat3(gl_ObjectToWorldEXT); - geom.normal_shading = normalTransform * baryMix( - vertices[vi1].normal, - vertices[vi2].normal, - vertices[vi3].normal, - bary); - - geom.uv_lods = computeAnisotropicEllipseAxes(geom.pos, geom.normal_geometry, gl_WorldRayDirectionEXT, ubo.ray_cone_width * gl_HitTEXT, pos, uvs, geom.uv); - - return geom; -} - void main() { const Geometry geom = readHitGeometry(); diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index 8eb05841..11bc9b20 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -3,8 +3,9 @@ #include "ray_primary_common.glsl" -layout(set = 0, binding = 0, rgba8) uniform image2D out_image_base_color_r; +layout(set = 0, binding = 0, rgba8) uniform image2D out_image_base_color_a; layout(set = 0, binding = 7, rgba32f) uniform image2D out_image_position_t; +//layout(set = 0, binding = 8, rgba8) uniform image2D out_image_rough_metal; layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; @@ -29,5 +30,5 @@ void main() { PAYLOAD_LOCATION_PRIMARY); imageStore(out_image_position_t, ivec2(gl_LaunchIDEXT.xy), payload.hit_t); - imageStore(out_image_base_color_r, ivec2(gl_LaunchIDEXT.xy), payload.base_color_a); + imageStore(out_image_base_color_a, ivec2(gl_LaunchIDEXT.xy), payload.base_color_a); } diff --git a/ref_vk/shaders/ray_primary_alphatest.rahit b/ref_vk/shaders/ray_primary_alphatest.rahit new file mode 100644 index 00000000..07bac394 --- /dev/null +++ b/ref_vk/shaders/ray_primary_alphatest.rahit @@ -0,0 +1,26 @@ +#version 460 core +#extension GL_EXT_nonuniform_qualifier : enable +#extension GL_GOOGLE_include_directive : require + +#include "ray_primary_common.glsl" +#include "ray_kusochki.glsl" + +layout(constant_id = 6) const uint MAX_TEXTURES = 4096; +layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; +layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; + +hitAttributeEXT vec2 bary; + +#include "rt_geometry.glsl" + +const float alpha_mask_threshold = .1f; + +void main() { + const Geometry geom = readHitGeometry(); + const uint tex_index = kusochki[geom.kusok_index].tex_base_color; + const vec4 texture_color = texture(textures[nonuniformEXT(tex_index)], geom.uv); + + if (texture_color.a < alpha_mask_threshold) { + ignoreIntersectionEXT; + } +} diff --git a/ref_vk/shaders/rt_geometry.glsl b/ref_vk/shaders/rt_geometry.glsl new file mode 100644 index 00000000..b807fdc2 --- /dev/null +++ b/ref_vk/shaders/rt_geometry.glsl @@ -0,0 +1,101 @@ +vec3 baryMix(vec3 v1, vec3 v2, vec3 v3, vec2 bary) { + return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; +} + +vec2 baryMix(vec2 v1, vec2 v2, vec2 v3, vec2 bary) { + return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; +} + +// Taken from Journal of Computer Graphics Techniques, Vol. 10, No. 1, 2021. +// Improved Shader and Texture Level of Detail Using Ray Cones, +// by T. Akenine-Moller, C. Crassin, J. Boksansky, L. Belcour, A. Panteleev, and O. Wright +// https://jcgt.org/published/0010/01/01/ +// P is the intersection point +// f is the triangle normal +// d is the ray cone direction +vec4 computeAnisotropicEllipseAxes(in vec3 P, in vec3 f, + in vec3 d, in float rayConeRadiusAtIntersection, + in vec3 positions[3], in vec2 txcoords[3], + in vec2 interpolatedTexCoordsAtIntersection) +{ + vec4 texGradient; + // Compute ellipse axes. + vec3 a1 = d - dot(f, d) * f; + vec3 p1 = a1 - dot(d, a1) * d; + a1 *= rayConeRadiusAtIntersection / max(0.0001, length(p1)); + vec3 a2 = cross(f, a1); + vec3 p2 = a2 - dot(d, a2) * d; + a2 *= rayConeRadiusAtIntersection / max(0.0001, length(p2)); + // Compute texture coordinate gradients. + vec3 eP, delta = P - positions[0]; + vec3 e1 = positions[1] - positions[0]; + vec3 e2 = positions[2] - positions[0]; + float oneOverAreaTriangle = 1.0 / dot(f, cross(e1, e2)); + eP = delta + a1; + float u1 = dot(f, cross(eP, e2)) * oneOverAreaTriangle; + float v1 = dot(f, cross(e1, eP)) * oneOverAreaTriangle; + texGradient.xy = (1.0-u1-v1) * txcoords[0] + u1 * txcoords[1] + + v1 * txcoords[2] - interpolatedTexCoordsAtIntersection; + eP = delta + a2; + float u2 = dot(f, cross(eP, e2)) * oneOverAreaTriangle; + float v2 = dot(f, cross(e1, eP)) * oneOverAreaTriangle; + texGradient.zw = (1.0-u2-v2) * txcoords[0] + u2 * txcoords[1] + + v2 * txcoords[2] - interpolatedTexCoordsAtIntersection; + return texGradient; +} + +struct Geometry { + vec3 pos; + + vec2 uv; + vec4 uv_lods; + + vec3 normal_geometry; + vec3 normal_shading; + + int kusok_index; +}; + +Geometry readHitGeometry() { + Geometry geom; + + const int instance_kusochki_offset = gl_InstanceCustomIndexEXT; + geom.kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT; + const Kusok kusok = kusochki[geom.kusok_index]; + + const uint first_index_offset = kusok.index_offset + gl_PrimitiveID * 3; + const uint vi1 = uint(indices[first_index_offset+0]) + kusok.vertex_offset; + const uint vi2 = uint(indices[first_index_offset+1]) + kusok.vertex_offset; + const uint vi3 = uint(indices[first_index_offset+2]) + kusok.vertex_offset; + + const vec3 pos[3] = { + gl_ObjectToWorldEXT * vec4(vertices[vi1].pos, 1.f), + gl_ObjectToWorldEXT * vec4(vertices[vi2].pos, 1.f), + gl_ObjectToWorldEXT * vec4(vertices[vi3].pos, 1.f), + }; + + const vec2 uvs[3] = { + vertices[vi1].gl_tc, + vertices[vi2].gl_tc, + vertices[vi3].gl_tc, + }; + + geom.pos = baryMix(pos[0], pos[1], pos[2], bary); + geom.uv = baryMix(uvs[0], uvs[1], uvs[2], bary); + //TODO or not TODO? const vec2 texture_uv = texture_uv_stationary + push_constants.time * kusok.uv_speed; + + // NOTE: need to flip if back-facing + geom.normal_geometry = normalize(cross(pos[2]-pos[0], pos[1]-pos[0])); + + // NOTE: only support rotations, for arbitrary transform would need to do transpose(inverse(mat3(gl_ObjectToWorldEXT))) + const mat3 normalTransform = mat3(gl_ObjectToWorldEXT); + geom.normal_shading = normalTransform * baryMix( + vertices[vi1].normal, + vertices[vi2].normal, + vertices[vi3].normal, + bary); + + geom.uv_lods = computeAnisotropicEllipseAxes(geom.pos, geom.normal_geometry, gl_WorldRayDirectionEXT, ubo.ray_cone_width * gl_HitTEXT, pos, uvs, geom.uv); + + return geom; +} diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index 7f5b7305..ce063536 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -1,4 +1,5 @@ #include "vk_ray_primary.h" +#include "vk_ray_internal.h" #include "vk_descriptor.h" #include "vk_pipeline.h" @@ -8,8 +9,13 @@ enum { RtPrim_SBT_RayGen, + RtPrim_SBT_RayMiss, + RtPrim_SBT_RayHit, + RtPrim_SBT_RayHit_WithAlphaTest, + RtPrim_SBT_RayHit_END = RtPrim_SBT_RayHit_WithAlphaTest, + RtPrim_SBT_COUNT, }; @@ -98,6 +104,7 @@ static VkPipeline createPipeline( void ) { ShaderStageIndex_RayGen, ShaderStageIndex_RayMiss, ShaderStageIndex_RayClosestHit, + ShaderStageIndex_RayAnyHit_AlphaTest, ShaderStageIndex_COUNT, }; @@ -126,22 +133,20 @@ static VkPipeline createPipeline( void ) { DEFINE_SHADER("ray_primary.rgen", RAYGEN, ShaderStageIndex_RayGen); DEFINE_SHADER("ray_primary.rchit", CLOSEST_HIT, ShaderStageIndex_RayClosestHit); + DEFINE_SHADER("ray_primary_alphatest.rahit", ANY_HIT, ShaderStageIndex_RayAnyHit_AlphaTest); DEFINE_SHADER("ray_primary.rmiss", MISS, ShaderStageIndex_RayMiss); // TODO static assert -/* #define ASSERT_SHADER_OFFSET(sbt_kind, sbt_index, offset) \ */ -/* ASSERT((offset) == (sbt_index - sbt_kind)) */ -/* */ -/* ASSERT_SHADER_OFFSET(ShaderBindingTable_RayGen, ShaderBindingTable_RayGen, 0); */ -/* ASSERT_SHADER_OFFSET(ShaderBindingTable_Miss, ShaderBindingTable_Miss, SHADER_OFFSET_MISS_REGULAR); */ -/* ASSERT_SHADER_OFFSET(ShaderBindingTable_Miss, ShaderBindingTable_Miss_Shadow, SHADER_OFFSET_MISS_SHADOW); */ -/* */ -/* ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_Base, SHADER_OFFSET_HIT_REGULAR_BASE + SHADER_OFFSET_HIT_REGULAR); */ -/* ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_WithAlphaTest, SHADER_OFFSET_HIT_REGULAR_BASE + SHADER_OFFSET_HIT_ALPHA_TEST); */ -/* ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_Additive, SHADER_OFFSET_HIT_REGULAR_BASE + SHADER_OFFSET_HIT_ADDITIVE); */ -/* */ -/* ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_Shadow_Base, SHADER_OFFSET_HIT_SHADOW_BASE + 0); */ -/* ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_Shadow_AlphaTest, SHADER_OFFSET_HIT_SHADOW_BASE + SHADER_OFFSET_HIT_ALPHA_TEST); */ +#define ASSERT_SHADER_OFFSET(sbt_kind, sbt_index, offset) \ + ASSERT((offset) == (sbt_index - sbt_kind)) + + ASSERT_SHADER_OFFSET(RtPrim_SBT_RayGen, RtPrim_SBT_RayGen, 0); + ASSERT_SHADER_OFFSET(RtPrim_SBT_RayMiss, RtPrim_SBT_RayMiss, SHADER_OFFSET_MISS_REGULAR); + + ASSERT_SHADER_OFFSET(RtPrim_SBT_RayHit, RtPrim_SBT_RayHit, SHADER_OFFSET_HIT_REGULAR_BASE + SHADER_OFFSET_HIT_REGULAR); + ASSERT_SHADER_OFFSET(RtPrim_SBT_RayHit, RtPrim_SBT_RayHit_WithAlphaTest, SHADER_OFFSET_HIT_REGULAR_BASE + SHADER_OFFSET_HIT_ALPHA_TEST); + + //ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_Additive, SHADER_OFFSET_HIT_REGULAR_BASE + SHADER_OFFSET_HIT_ADDITIVE); shader_groups[RtPrim_SBT_RayGen] = (VkRayTracingShaderGroupCreateInfoKHR) { .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, @@ -161,6 +166,15 @@ static VkPipeline createPipeline( void ) { .intersectionShader = VK_SHADER_UNUSED_KHR, }; + shader_groups[RtPrim_SBT_RayHit_WithAlphaTest] = (VkRayTracingShaderGroupCreateInfoKHR) { + .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, + .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR, + .anyHitShader = ShaderStageIndex_RayAnyHit_AlphaTest, + .closestHitShader = ShaderStageIndex_RayClosestHit, + .generalShader = VK_SHADER_UNUSED_KHR, + .intersectionShader = VK_SHADER_UNUSED_KHR, + }; + shader_groups[RtPrim_SBT_RayMiss] = (VkRayTracingShaderGroupCreateInfoKHR) { .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR, @@ -292,7 +306,7 @@ void XVK_RayTracePrimary( VkCommandBuffer cmdbuf, const xvk_ray_trace_primary_t } const VkStridedDeviceAddressRegionKHR sbt_raygen = SBT_INDEX(RtPrim_SBT_RayGen, 1); const VkStridedDeviceAddressRegionKHR sbt_miss = SBT_INDEX(RtPrim_SBT_RayMiss, 1); //ShaderBindingTable_Miss_Empty - ShaderBindingTable_Miss); - const VkStridedDeviceAddressRegionKHR sbt_hit = SBT_INDEX(RtPrim_SBT_RayHit, 1); //ShaderBindingTable_Hit__END - ShaderBindingTable_Hit_Base); + const VkStridedDeviceAddressRegionKHR sbt_hit = SBT_INDEX(RtPrim_SBT_RayHit, RtPrim_SBT_RayHit_END - RtPrim_SBT_RayHit); const VkStridedDeviceAddressRegionKHR sbt_callable = { 0 }; vkCmdTraceRaysKHR(cmdbuf, &sbt_raygen, &sbt_miss, &sbt_hit, &sbt_callable, args->width, args->height, 1 ); From c6ea7dc1fcfb2a2c24349112589535dfe8ef2725 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 9 Jan 2022 19:36:08 -0800 Subject: [PATCH 064/548] rt: list primary out bindings separately --- ref_vk/shaders/ray_primary.rgen | 7 +- ref_vk/shaders/ray_primary_iface.h | 3 + ref_vk/vk_ray_primary.c | 122 +++++++++++++---------------- ref_vk/vk_ray_primary.h | 9 ++- ref_vk/vk_rtx.c | 2 +- 5 files changed, 68 insertions(+), 75 deletions(-) create mode 100644 ref_vk/shaders/ray_primary_iface.h diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index 11bc9b20..5937244d 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -2,10 +2,11 @@ #extension GL_GOOGLE_include_directive : require #include "ray_primary_common.glsl" +#include "ray_primary_iface.h" -layout(set = 0, binding = 0, rgba8) uniform image2D out_image_base_color_a; -layout(set = 0, binding = 7, rgba32f) uniform image2D out_image_position_t; -//layout(set = 0, binding = 8, rgba8) uniform image2D out_image_rough_metal; +#define X(index, name, format) layout(set=0,binding=index,format) uniform image2D out_image_##name; +RAY_PRIMARY_OUTPUTS(X) +#undef X layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; diff --git a/ref_vk/shaders/ray_primary_iface.h b/ref_vk/shaders/ray_primary_iface.h new file mode 100644 index 00000000..8b5e7d19 --- /dev/null +++ b/ref_vk/shaders/ray_primary_iface.h @@ -0,0 +1,3 @@ +#define RAY_PRIMARY_OUTPUTS(X) \ + X(10, base_color_a, rgba8) \ + X(11, position_t, rgba32f) diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index ce063536..9b418d28 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -20,19 +20,18 @@ enum { }; enum { - // TODO set 1 - RtPrim_Desc_Out_BaseColorR = 0, - // TODO set 0 - RtPrim_Desc_TLAS = 1, - RtPrim_Desc_UBO = 2, - RtPrim_Desc_Kusochki = 3, - RtPrim_Desc_Indices = 4, - RtPrim_Desc_Vertices = 5, - RtPrim_Desc_Textures = 6, + RtPrim_Desc_TLAS, + RtPrim_Desc_UBO, + RtPrim_Desc_Kusochki, + RtPrim_Desc_Indices, + RtPrim_Desc_Vertices, + RtPrim_Desc_Textures, // TODO set 1 - RtPrim_Desc_Out_PositionT = 7, +#define X(index, name, ...) RtPrim_Desc_Out_##name, +RAY_PRIMARY_OUTPUTS(X) +#undef X RtPrim_Desc_COUNT }; @@ -66,37 +65,64 @@ static void initDescriptors( void ) { /* }, */ }; -#define INIT_BINDING(index, type, count, stages) \ - g_ray_primary.desc.bindings[index] = (VkDescriptorSetLayoutBinding){ \ +#define INIT_BINDING(index, name, type, count, stages) \ + g_ray_primary.desc.bindings[RtPrim_Desc_##name] = (VkDescriptorSetLayoutBinding){ \ .binding = index, \ .descriptorType = type, \ .descriptorCount = count, \ .stageFlags = stages, \ } - INIT_BINDING(RtPrim_Desc_Out_BaseColorR, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); - INIT_BINDING(RtPrim_Desc_Out_PositionT, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); - - INIT_BINDING(RtPrim_Desc_UBO, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); - INIT_BINDING(RtPrim_Desc_TLAS, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); - INIT_BINDING(RtPrim_Desc_Kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - INIT_BINDING(RtPrim_Desc_Indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - INIT_BINDING(RtPrim_Desc_Vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - - g_ray_primary.desc.bindings[RtPrim_Desc_Textures] = (VkDescriptorSetLayoutBinding){ - .binding = RtPrim_Desc_Textures, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = MAX_TEXTURES, - .stageFlags = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, - // FIXME on AMD using immutable samplers leads to nearest filtering ???! - .pImmutableSamplers = NULL, //samplers, - }; + INIT_BINDING(1, TLAS, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); + INIT_BINDING(2, UBO, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); + INIT_BINDING(3, Kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); + INIT_BINDING(4, Indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); + INIT_BINDING(5, Vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); + INIT_BINDING(6, Textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); +#define X(index, name, ...) \ + INIT_BINDING(index, Out_##name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); +RAY_PRIMARY_OUTPUTS(X) +#undef X #undef INIT_BINDING VK_DescriptorsCreate(&g_ray_primary.desc.riptors); } +static void updateDescriptors( const xvk_ray_trace_primary_t* args ) { +#define X(index, name, ...) \ + g_ray_primary.desc.values[RtPrim_Desc_Out_##name].image = (VkDescriptorImageInfo){ \ + .sampler = VK_NULL_HANDLE, \ + .imageView = args->out.name, \ + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ + }; +RAY_PRIMARY_OUTPUTS(X) + + g_ray_primary.desc.values[RtPrim_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, + .accelerationStructureCount = 1, + .pAccelerationStructures = &args->in.tlas, + }; + +#define DESC_SET_BUFFER(index, buffer_) \ + g_ray_primary.desc.values[index].buffer = (VkDescriptorBufferInfo){ \ + .buffer = args->in.buffer_.buffer, \ + .offset = args->in.buffer_.offset, \ + .range = args->in.buffer_.size, \ + } + + DESC_SET_BUFFER(RtPrim_Desc_UBO, ubo); + DESC_SET_BUFFER(RtPrim_Desc_Kusochki, kusochki); + DESC_SET_BUFFER(RtPrim_Desc_Indices, indices); + DESC_SET_BUFFER(RtPrim_Desc_Vertices, vertices); + +#undef DESC_SET_BUFFER + + g_ray_primary.desc.values[RtPrim_Desc_Textures].image_array = args->in.all_textures; + + VK_DescriptorsWrite(&g_ray_primary.desc.riptors); +} + static VkPipeline createPipeline( void ) { VkPipeline pipeline; @@ -240,44 +266,6 @@ void XVK_RayTracePrimaryReloadPipeline( void ) { g_ray_primary.pipeline = pipeline; } -static void updateDescriptors( const xvk_ray_trace_primary_t* args ) { - g_ray_primary.desc.values[RtPrim_Desc_Out_BaseColorR].image = (VkDescriptorImageInfo){ - .sampler = VK_NULL_HANDLE, - .imageView = args->out.base_color_r, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; - - g_ray_primary.desc.values[RtPrim_Desc_Out_PositionT].image = (VkDescriptorImageInfo){ - .sampler = VK_NULL_HANDLE, - .imageView = args->out.position_t, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; - - g_ray_primary.desc.values[RtPrim_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, - .accelerationStructureCount = 1, - .pAccelerationStructures = &args->in.tlas, - }; - -#define DESC_SET_BUFFER(index, buffer_) \ - g_ray_primary.desc.values[index].buffer = (VkDescriptorBufferInfo){ \ - .buffer = args->in.buffer_.buffer, \ - .offset = args->in.buffer_.offset, \ - .range = args->in.buffer_.size, \ - } - - DESC_SET_BUFFER(RtPrim_Desc_UBO, ubo); - DESC_SET_BUFFER(RtPrim_Desc_Kusochki, kusochki); - DESC_SET_BUFFER(RtPrim_Desc_Indices, indices); - DESC_SET_BUFFER(RtPrim_Desc_Vertices, vertices); - -#undef DESC_SET_BUFFER - - g_ray_primary.desc.values[RtPrim_Desc_Textures].image_array = args->in.all_textures; - - VK_DescriptorsWrite(&g_ray_primary.desc.riptors); -} - void XVK_RayTracePrimary( VkCommandBuffer cmdbuf, const xvk_ray_trace_primary_t *args ) { updateDescriptors( args ); diff --git a/ref_vk/vk_ray_primary.h b/ref_vk/vk_ray_primary.h index 29135f49..6be4944c 100644 --- a/ref_vk/vk_ray_primary.h +++ b/ref_vk/vk_ray_primary.h @@ -1,8 +1,7 @@ #pragma once - #include "vk_core.h" - #include "vk_rtx.h" +#include "shaders/ray_primary_iface.h" qboolean XVK_RayTracePrimaryInit( void ); void XVK_RayTracePrimaryDestroy( void ); @@ -12,6 +11,7 @@ typedef struct { uint32_t width, height; struct { + // TODO separate desc set VkAccelerationStructureKHR tlas; vk_buffer_region_t ubo; vk_buffer_region_t kusochki, indices, vertices; @@ -19,8 +19,9 @@ typedef struct { } in; struct { - VkImageView position_t; - VkImageView base_color_r; +#define X(index, name, ...) VkImageView name; +RAY_PRIMARY_OUTPUTS(X) +#undef X } out; } xvk_ray_trace_primary_t; diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 5723406c..b14b481e 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -1022,7 +1022,7 @@ LIST_GBUFFER_IMAGES(GBUFFER_WRITE_BARRIER) }, .out = { .position_t = current_frame->position_t.view, - .base_color_r = current_frame->base_color.view, + .base_color_a = current_frame->base_color.view, }, }; XVK_RayTracePrimary( cmdbuf, &primary_args ); From f281b40e34f02264836df6b2788298a92eaa2183 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 9 Jan 2022 19:50:43 -0800 Subject: [PATCH 065/548] rt: use the same primary out list for image creation --- ref_vk/shaders/ray_primary_iface.h | 2 +- ref_vk/vk_denoiser.c | 2 +- ref_vk/vk_denoiser.h | 2 +- ref_vk/vk_rtx.c | 60 ++++++++++++++++-------------- 4 files changed, 35 insertions(+), 31 deletions(-) diff --git a/ref_vk/shaders/ray_primary_iface.h b/ref_vk/shaders/ray_primary_iface.h index 8b5e7d19..1506c936 100644 --- a/ref_vk/shaders/ray_primary_iface.h +++ b/ref_vk/shaders/ray_primary_iface.h @@ -1,3 +1,3 @@ #define RAY_PRIMARY_OUTPUTS(X) \ X(10, base_color_a, rgba8) \ - X(11, position_t, rgba32f) + X(11, position_t, rgba32f) \ diff --git a/ref_vk/vk_denoiser.c b/ref_vk/vk_denoiser.c index a7434744..9c4b6fb9 100644 --- a/ref_vk/vk_denoiser.c +++ b/ref_vk/vk_denoiser.c @@ -134,7 +134,7 @@ void XVK_DenoiserDenoise( const xvk_denoiser_args_t* args ) { g_denoiser.desc_values[DenoiserBinding_Source_BaseColor].image = (VkDescriptorImageInfo){ .sampler = VK_NULL_HANDLE, - .imageView = args->src.base_color_view, + .imageView = args->src.base_color_a_view, .imageLayout = VK_IMAGE_LAYOUT_GENERAL, }; diff --git a/ref_vk/vk_denoiser.h b/ref_vk/vk_denoiser.h index dc58590f..67271531 100644 --- a/ref_vk/vk_denoiser.h +++ b/ref_vk/vk_denoiser.h @@ -12,7 +12,7 @@ typedef struct { uint32_t width, height; struct { - VkImageView base_color_view; + VkImageView base_color_a_view; VkImageView diffuse_gi_view; VkImageView specular_view; VkImageView additive_view; diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index b14b481e..c12a3869 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -92,9 +92,9 @@ enum { typedef struct { xvk_image_t denoised; - xvk_image_t position_t; - xvk_image_t base_color; - //xvk_image_t rough_metal_trans; +#define X(index, name, ...) xvk_image_t name; +RAY_PRIMARY_OUTPUTS(X) +#undef X xvk_image_t diffuse_gi; xvk_image_t specular; @@ -625,11 +625,11 @@ static void updateDescriptors( const vk_ray_frame_render_args_t *args, int frame // 3. Update descriptor sets (bind dest image, tlas, projection matrix) VkDescriptorImageInfo dii_all_textures[MAX_TEXTURES]; - g_rtx.desc_values[RayDescBinding_Dest_ImageBaseColor].image = (VkDescriptorImageInfo){ - .sampler = VK_NULL_HANDLE, - .imageView = frame_dst->base_color.view, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; + /* g_rtx.desc_values[RayDescBinding_Dest_ImageBaseColor].image = (VkDescriptorImageInfo){ */ + /* .sampler = VK_NULL_HANDLE, */ + /* .imageView = frame_dst->base_color.view, */ + /* .imageLayout = VK_IMAGE_LAYOUT_GENERAL, */ + /* }; */ g_rtx.desc_values[RayDescBinding_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, @@ -943,20 +943,16 @@ static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int fr static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_args_t* args, int frame_index, const xvk_ray_frame_images_t *current_frame, float fov_angle_y) { uploadLights(); - prepareTlas(cmdbuf); - prepareUniformBuffer(args, frame_index, fov_angle_y); - - updateDescriptors(args, frame_index, current_frame); + //updateDescriptors(args, frame_index, current_frame); #define LIST_GBUFFER_IMAGES(X) \ - X(position_t) \ - X(base_color) \ - X(diffuse_gi) \ - X(specular) \ - X(additive) \ - X(normals) \ + RAY_PRIMARY_OUTPUTS(X) \ + X(0, diffuse_gi) \ + X(0, specular) \ + X(0, additive) \ + X(0, normals) \ // 4. Barrier for TLAS build and dest image layout transfer { @@ -969,7 +965,7 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar .size = VK_WHOLE_SIZE, } }; VkImageMemoryBarrier image_barrier[] = { -#define GBUFFER_WRITE_BARRIER(img) { \ +#define GBUFFER_WRITE_BARRIER(index, img, ...) { \ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, \ .image = current_frame->img.image, \ .srcAccessMask = 0, \ @@ -1021,8 +1017,9 @@ LIST_GBUFFER_IMAGES(GBUFFER_WRITE_BARRIER) .all_textures = tglob.dii_all_textures, }, .out = { - .position_t = current_frame->position_t.view, - .base_color_a = current_frame->base_color.view, +#define X(index, name, ...) .name = current_frame->name.view, +RAY_PRIMARY_OUTPUTS(X) +#undef X }, }; XVK_RayTracePrimary( cmdbuf, &primary_args ); @@ -1031,7 +1028,7 @@ LIST_GBUFFER_IMAGES(GBUFFER_WRITE_BARRIER) { const VkImageMemoryBarrier image_barriers[] = { -#define GBUFFER_READ_BARRIER(img) { \ +#define GBUFFER_READ_BARRIER(index, img, ...) { \ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, \ .image = current_frame->img.image, \ .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, \ @@ -1075,8 +1072,9 @@ LIST_GBUFFER_IMAGES(GBUFFER_READ_BARRIER) .width = FRAME_WIDTH, .height = FRAME_HEIGHT, .src = { - .position_t_view = current_frame->position_t.view, - .base_color_view = current_frame->base_color.view, +#define X(index, name, ...) .name##_view = current_frame->name.view, +RAY_PRIMARY_OUTPUTS(X) +#undef X .diffuse_gi_view = current_frame->diffuse_gi.view, .specular_view = current_frame->specular.view, .additive_view = current_frame->additive.view, @@ -1399,8 +1397,13 @@ qboolean VK_RayInit( void ) CREATE_GBUFFER_IMAGE(denoised, VK_FORMAT_R16G16B16A16_SFLOAT, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT); - CREATE_GBUFFER_IMAGE(position_t, VK_FORMAT_R32G32B32A32_SFLOAT, 0); - CREATE_GBUFFER_IMAGE(base_color, VK_FORMAT_R8G8B8A8_UNORM, 0); +#define rgba8 VK_FORMAT_R8G8B8A8_UNORM +#define rgba32f VK_FORMAT_R32G32B32A32_SFLOAT +#define X(index, name, format) CREATE_GBUFFER_IMAGE(name, format, 0); +RAY_PRIMARY_OUTPUTS(X) +#undef X +#undef rgba8 +#undef rgba32f CREATE_GBUFFER_IMAGE(diffuse_gi, VK_FORMAT_R16G16B16A16_SFLOAT, 0); CREATE_GBUFFER_IMAGE(specular, VK_FORMAT_R16G16B16A16_SFLOAT, 0); CREATE_GBUFFER_IMAGE(additive, VK_FORMAT_R16G16B16A16_SFLOAT, 0); @@ -1423,8 +1426,9 @@ void VK_RayShutdown( void ) { for (int i = 0; i < ARRAYSIZE(g_rtx.frames); ++i) { XVK_ImageDestroy(&g_rtx.frames[i].denoised); - XVK_ImageDestroy(&g_rtx.frames[i].position_t); - XVK_ImageDestroy(&g_rtx.frames[i].base_color); +#define X(index, name, ...) XVK_ImageDestroy(&g_rtx.frames[i].name); +RAY_PRIMARY_OUTPUTS(X) +#undef X XVK_ImageDestroy(&g_rtx.frames[i].diffuse_gi); XVK_ImageDestroy(&g_rtx.frames[i].specular); XVK_ImageDestroy(&g_rtx.frames[i].additive); From b9760aaaea5c690a48b3ecb12c3fce46725d1789 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 9 Jan 2022 20:02:27 -0800 Subject: [PATCH 066/548] rt: add normals to primary pass primary: 114us (+15), 62(64)v, 60(128)s, 16/16o reading: 13us (+3) --- ref_vk/shaders/denoiser.comp | 3 ++- ref_vk/shaders/ray_primary.rchit | 3 +++ ref_vk/shaders/ray_primary.rgen | 1 + ref_vk/shaders/ray_primary_common.glsl | 1 + ref_vk/shaders/ray_primary_iface.h | 2 ++ ref_vk/vk_rtx.c | 9 +++++---- 6 files changed, 14 insertions(+), 5 deletions(-) diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 7b7ca17a..14559d0a 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -69,9 +69,10 @@ void main() { const float material_index = imageLoad(src_diffuse_gi, pix).a; //imageStore(dest, pix, vec4(aces_tonemap(base_color.rgb), 0.)); return; - imageStore(dest, pix, vec4((base_color.rgb), 0.)); return; + //imageStore(dest, pix, vec4((base_color.rgb), 0.)); return; //imageStore(dest, pix, vec4(fract(imageLoad(src_position_t, pix).rgb / 10.), 0.)); return; //imageStore(dest, pix, vec4((imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; + imageStore(dest, pix, vec4(imageLoad(src_normals, pix)*.5 + .5f)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_specular, pix).rgb), 0.)); return; diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index 54453bda..58d92ac1 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -33,4 +33,7 @@ void main() { } else { payload.base_color_a = sampleTexture(tex_base_color, geom.uv, geom.uv_lods) * kusok.color; } + + payload.normals_gs.xy = geom.normal_geometry.xy; + payload.normals_gs.zw = geom.normal_shading.xy; } diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index 5937244d..28a8d94b 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -32,4 +32,5 @@ void main() { imageStore(out_image_position_t, ivec2(gl_LaunchIDEXT.xy), payload.hit_t); imageStore(out_image_base_color_a, ivec2(gl_LaunchIDEXT.xy), payload.base_color_a); + imageStore(out_image_normals_gs, ivec2(gl_LaunchIDEXT.xy), payload.normals_gs); } diff --git a/ref_vk/shaders/ray_primary_common.glsl b/ref_vk/shaders/ray_primary_common.glsl index 2a0b6dba..79ca3d24 100644 --- a/ref_vk/shaders/ray_primary_common.glsl +++ b/ref_vk/shaders/ray_primary_common.glsl @@ -7,6 +7,7 @@ struct RayPayloadPrimary { vec4 hit_t; vec4 base_color_a; + vec4 normals_gs; }; #define PAYLOAD_LOCATION_PRIMARY 0 diff --git a/ref_vk/shaders/ray_primary_iface.h b/ref_vk/shaders/ray_primary_iface.h index 1506c936..eebdc4d2 100644 --- a/ref_vk/shaders/ray_primary_iface.h +++ b/ref_vk/shaders/ray_primary_iface.h @@ -1,3 +1,5 @@ #define RAY_PRIMARY_OUTPUTS(X) \ X(10, base_color_a, rgba8) \ X(11, position_t, rgba32f) \ + X(12, normals_gs, rgba16f) \ + diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index c12a3869..b808c5bb 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -1072,13 +1072,12 @@ LIST_GBUFFER_IMAGES(GBUFFER_READ_BARRIER) .width = FRAME_WIDTH, .height = FRAME_HEIGHT, .src = { -#define X(index, name, ...) .name##_view = current_frame->name.view, -RAY_PRIMARY_OUTPUTS(X) -#undef X + .position_t_view = current_frame->position_t.view, + .base_color_a_view = current_frame->base_color_a.view, .diffuse_gi_view = current_frame->diffuse_gi.view, .specular_view = current_frame->specular.view, .additive_view = current_frame->additive.view, - .normals_view = current_frame->normals.view, + .normals_view = current_frame->normals_gs.view, }, .dst_view = current_frame->denoised.view, }; @@ -1399,11 +1398,13 @@ qboolean VK_RayInit( void ) #define rgba8 VK_FORMAT_R8G8B8A8_UNORM #define rgba32f VK_FORMAT_R32G32B32A32_SFLOAT +#define rgba16f VK_FORMAT_R16G16B16A16_SFLOAT #define X(index, name, format) CREATE_GBUFFER_IMAGE(name, format, 0); RAY_PRIMARY_OUTPUTS(X) #undef X #undef rgba8 #undef rgba32f +#undef rgba16f CREATE_GBUFFER_IMAGE(diffuse_gi, VK_FORMAT_R16G16B16A16_SFLOAT, 0); CREATE_GBUFFER_IMAGE(specular, VK_FORMAT_R16G16B16A16_SFLOAT, 0); CREATE_GBUFFER_IMAGE(additive, VK_FORMAT_R16G16B16A16_SFLOAT, 0); From af24afbcc37dd655567b149f6f78e3e19f599191 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Mon, 10 Jan 2022 19:13:08 -0800 Subject: [PATCH 067/548] rt: draft direct light compute shader it's bad: direct light: 1.8ms, 183(184)v, 59(128)s, LDS=0, 2/16o (-56 vgpr => +1 wavefront??) also, thread group size = 16, 8, 1, wave64 while ray tracing pipeline = 8, 4, 1, wave32 --- ref_vk/shaders/denoiser.comp | 11 +- ref_vk/shaders/light.glsl | 132 ++++++++++++++++++ ref_vk/shaders/light_common.glsl | 8 ++ ref_vk/shaders/light_polygon.glsl | 10 +- ref_vk/shaders/noise.glsl | 13 +- ref_vk/shaders/ray_light_direct.comp | 66 +++++++++ ref_vk/shaders/ray_light_direct_iface.h | 8 ++ ref_vk/shaders/ray_primary.rgen | 3 +- ref_vk/vk_ray_light_direct.c | 177 ++++++++++++++++++++++++ ref_vk/vk_ray_light_direct.h | 35 +++++ ref_vk/vk_rtx.c | 166 ++++++++++++++-------- 11 files changed, 562 insertions(+), 67 deletions(-) create mode 100644 ref_vk/shaders/light.glsl create mode 100644 ref_vk/shaders/ray_light_direct.comp create mode 100644 ref_vk/shaders/ray_light_direct_iface.h create mode 100644 ref_vk/vk_ray_light_direct.c create mode 100644 ref_vk/vk_ray_light_direct.h diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 14559d0a..82e5d872 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -72,7 +72,8 @@ void main() { //imageStore(dest, pix, vec4((base_color.rgb), 0.)); return; //imageStore(dest, pix, vec4(fract(imageLoad(src_position_t, pix).rgb / 10.), 0.)); return; //imageStore(dest, pix, vec4((imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; - imageStore(dest, pix, vec4(imageLoad(src_normals, pix)*.5 + .5f)); return; + //imageStore(dest, pix, vec4((imageLoad(src_diffuse_gi, pix).rgb * base_color.rgb), 0.)); return; + //imageStore(dest, pix, vec4(imageLoad(src_normals, pix)*.5 + .5f)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_specular, pix).rgb), 0.)); return; @@ -85,10 +86,13 @@ void main() { /* imageStore(dest, pix, vec4(rand3_f01(uvec3(mi,mi+1,mi+2)), 0.)); */ /* return; */ - const int KERNEL_SIZE = 8; +#if 1 + vec3 colour = imageLoad(src_diffuse_gi, pix).rgb * base_color.rgb; +#else float total_scale = 0.; - float specular_total_scale = 0.; vec3 colour = vec3(0.); + const int KERNEL_SIZE = 8; + float specular_total_scale = 0.; vec3 speculour = vec3(0.); for (int x = -KERNEL_SIZE; x <= KERNEL_SIZE; ++x) for (int y = -KERNEL_SIZE; y <= KERNEL_SIZE; ++y) { @@ -137,6 +141,7 @@ void main() { //colour += imageLoad(src_specular, pix).rgb; colour += imageLoad(src_additive, pix).rgb; +#endif // HACK: exposure // TODO: should be dynamic based on previous frames brightness diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl new file mode 100644 index 00000000..f003e5c0 --- /dev/null +++ b/ref_vk/shaders/light.glsl @@ -0,0 +1,132 @@ +layout (constant_id = 4) const float LIGHT_GRID_CELL_SIZE = 256.; +layout (constant_id = 5) const uint MAX_LIGHT_CLUSTERS = 32768; +layout (constant_id = 6) const uint MAX_TEXTURES = 4096; +layout (set = 0, binding = BINDING_LIGHTS) uniform UBOLights { Lights lights; }; +layout (set = 0, binding = BINDING_LIGHT_CLUSTERS, align = 1) readonly buffer UBOLightClusters { + ivec3 grid_min, grid_size; + //uint8_t clusters_data[MAX_LIGHT_CLUSTERS * LIGHT_CLUSTER_SIZE + HACK_OFFSET]; + LightCluster clusters[MAX_LIGHT_CLUSTERS]; +} light_grid; + +const float color_culling_threshold = 0;//600./color_factor; +const float shadow_offset_fudge = .1; + +#include "brdf.h" +#include "light_common.glsl" +#include "light_polygon.glsl" + +void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { + diffuse = specular = vec3(0.); + const uint num_point_lights = uint(light_grid.clusters[cluster_index].num_point_lights); + for (uint j = 0; j < num_point_lights; ++j) { + const uint i = uint(light_grid.clusters[cluster_index].point_lights[j]); + + vec3 color = lights.point_lights[i].color_stopdot.rgb * throughput; + if (dot(color,color) < color_culling_threshold) + continue; + + const vec4 origin_r = lights.point_lights[i].origin_r; + const float stopdot = lights.point_lights[i].color_stopdot.a; + const vec3 dir = lights.point_lights[i].dir_stopdot2.xyz; + const float stopdot2 = lights.point_lights[i].dir_stopdot2.a; + const bool not_environment = (lights.point_lights[i].environment == 0); + + const vec3 light_dir = not_environment ? (origin_r.xyz - P) : -dir; // TODO need to randomize sampling direction for environment soft shadow + const float radius = origin_r.w; + + const vec3 light_dir_norm = normalize(light_dir); + const float light_dot = dot(light_dir_norm, N); + if (light_dot < 1e-5) + continue; + + const float spot_dot = -dot(light_dir_norm, dir); + if (spot_dot < stopdot2) + continue; + + float spot_attenuation = 1.f; + if (spot_dot < stopdot) + spot_attenuation = (spot_dot - stopdot2) / (stopdot - stopdot2); + + //float fdist = 1.f; + float light_dist = 1e5; // TODO this is supposedly not the right way to do shadows for environment lights. qrad checks for hitting SURF_SKY, and maybe we should too? + const float d2 = dot(light_dir, light_dir); + const float r2 = origin_r.w * origin_r.w; + if (not_environment) { + if (radius < 1e-3) + continue; + + const float dist = length(light_dir); + if (radius > dist) + continue; +#if 1 + //light_dist = sqrt(d2); + light_dist = dist - radius; + //fdist = 2.f / (r2 + d2 + light_dist * sqrt(d2 + r2)); +#else + light_dist = dist; + //const float fdist = 2.f / (r2 + d2 + light_dist * sqrt(d2 + r2)); + //const float fdist = 2.f / (r2 + d2 + light_dist * sqrt(d2 + r2)); + //fdist = (light_dist > 1.) ? 1.f / d2 : 1.f; // qrad workaround +#endif + + //const float pdf = 1.f / (fdist * light_dot * spot_attenuation); + //const float pdf = TWO_PI / asin(radius / dist); + const float pdf = 1. / ((1. - sqrt(d2 - r2) / dist) * spot_attenuation); + color /= pdf; + } + + // if (dot(color,color) < color_culling_threshold) + // continue; + + vec3 ldiffuse, lspecular; + evalSplitBRDF(N, light_dir_norm, view_dir, material, ldiffuse, lspecular); + ldiffuse *= color; + lspecular *= color; + + vec3 combined = ldiffuse + lspecular; + + if (dot(combined,combined) < color_culling_threshold) + continue; + + if (not_environment) { + if (shadowed(P, light_dir_norm, light_dist + shadow_offset_fudge)) + continue; + } else { + // for environment light check that we've hit SURF_SKY + if (shadowedSky(P, light_dir_norm, light_dist + shadow_offset_fudge)) + continue; + } + + diffuse += ldiffuse; + specular += lspecular; + } // for all lights +} + +void computeLighting(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { + diffuse = specular = vec3(0.); + const ivec3 light_cell = ivec3(floor(P / LIGHT_GRID_CELL_SIZE)) - light_grid.grid_min; + const uint cluster_index = uint(dot(light_cell, ivec3(1, light_grid.grid_size.x, light_grid.grid_size.x * light_grid.grid_size.y))); + + if (any(greaterThanEqual(light_cell, light_grid.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) + return; // throughput * vec3(1., 0., 0.); + + // const uint cluster_offset = cluster_index * LIGHT_CLUSTER_SIZE + HACK_OFFSET; + // const int num_dlights = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_DLIGHTS_OFFSET]); + // const int num_emissive_surfaces = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_EMISSIVE_SURFACES_OFFSET]); + // const uint emissive_surfaces_offset = cluster_offset + LIGHT_CLUSTER_EMISSIVE_SURFACES_DATA_OFFSET; + //C = vec3(float(num_emissive_surfaces)); + + //C = vec3(float(int(light_grid.clusters[cluster_index].num_emissive_surfaces))); + //C += .3 * fract(vec3(light_cell) / 4.); + +#if 1 + sampleEmissiveSurfaces(P, N, throughput, view_dir, material, cluster_index, diffuse, specular); +#endif + +#if 1 + vec3 ldiffuse = vec3(0.), lspecular = vec3(0.); + computePointLights(P, N, cluster_index, throughput, view_dir, material, ldiffuse, lspecular); + diffuse += ldiffuse; + specular += lspecular; +#endif +} diff --git a/ref_vk/shaders/light_common.glsl b/ref_vk/shaders/light_common.glsl index 04c8fc49..b8a43e8b 100644 --- a/ref_vk/shaders/light_common.glsl +++ b/ref_vk/shaders/light_common.glsl @@ -1,4 +1,5 @@ bool shadowed(vec3 pos, vec3 dir, float dist) { +#if 0 payload_shadow.hit_type = SHADOW_HIT; const uint flags = 0 //| gl_RayFlagsCullFrontFacingTrianglesEXT @@ -12,10 +13,14 @@ bool shadowed(vec3 pos, vec3 dir, float dist) { SHADER_OFFSET_HIT_SHADOW_BASE, SBT_RECORD_SIZE, SHADER_OFFSET_MISS_SHADOW, pos, 0., dir, dist - shadow_offset_fudge, PAYLOAD_LOCATION_SHADOW); return payload_shadow.hit_type == SHADOW_HIT; +#else + return false; +#endif } // TODO join with just shadowed() bool shadowedSky(vec3 pos, vec3 dir, float dist) { +#if 0 payload_shadow.hit_type = SHADOW_HIT; const uint flags = 0 //| gl_RayFlagsCullFrontFacingTrianglesEXT @@ -29,6 +34,9 @@ bool shadowedSky(vec3 pos, vec3 dir, float dist) { SHADER_OFFSET_HIT_SHADOW_BASE, SBT_RECORD_SIZE, SHADER_OFFSET_MISS_SHADOW, pos, 0., dir, dist - shadow_offset_fudge, PAYLOAD_LOCATION_SHADOW); return payload_shadow.hit_type != SHADOW_SKY; +#else + return false; +#endif } // This is an entry point for evaluation of all other BRDFs based on selected configuration (for direct light) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 12ec36d2..0e76630b 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -3,6 +3,8 @@ #include "peters2021-sampling/polygon_clipping.glsl" #include "peters2021-sampling/polygon_sampling.glsl" +#include "noise.glsl" + struct SampleContext { mat4x3 world_to_shading; }; @@ -22,8 +24,10 @@ void sampleEmissiveSurface(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mater const EmissiveKusok ek = lights.kusochki[ekusok_index]; const uint emissive_kusok_index = lights.kusochki[ekusok_index].kusok_index; - if (emissive_kusok_index == uint(payload_opaque.kusok_index)) - return; + + // FIXME + // if (emissive_kusok_index == uint(payload_opaque.kusok_index)) + // return; const Kusok kusok = kusochki[emissive_kusok_index]; @@ -167,10 +171,12 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate #endif const uint index_into_emissive_kusochki = uint(light_grid.clusters[cluster_index].emissive_surfaces[i]); +#if 0 if (push_constants.debug_light_index_begin < push_constants.debug_light_index_end) { if (index_into_emissive_kusochki < push_constants.debug_light_index_begin || index_into_emissive_kusochki >= push_constants.debug_light_index_end) continue; } +#endif vec3 ldiffuse, lspecular; sampleEmissiveSurface(P, N, throughput, view_dir, material, ctx, index_into_emissive_kusochki, ldiffuse, lspecular); diff --git a/ref_vk/shaders/noise.glsl b/ref_vk/shaders/noise.glsl index 9cb03aa6..2154ccdd 100644 --- a/ref_vk/shaders/noise.glsl +++ b/ref_vk/shaders/noise.glsl @@ -1,3 +1,5 @@ +#ifndef NOISE_GLSL_INCLUDED +#define NOISE_GLSL_INCLUDED // Copypasted from Mark Jarzynski and Marc Olano, Hash Functions for GPU Rendering, Journal of Computer Graphics Techniques (JCGT), vol. 9, no. 3, 21-38, 2020 // http://www.jcgt.org/published/0009/03/02/ // https://www.shadertoy.com/view/XlGcRh @@ -119,19 +121,19 @@ uvec3 pcg3d16(uvec3 v) uvec4 pcg4d(uvec4 v) { v = v * 1664525u + 1013904223u; - + v.x += v.y*v.w; v.y += v.z*v.x; v.z += v.x*v.y; v.w += v.y*v.z; - + v ^= v >> 16u; - + v.x += v.y*v.w; v.y += v.z*v.x; v.z += v.x*v.y; v.w += v.y*v.z; - + return v; } @@ -154,4 +156,5 @@ float rand01() { vec3 rand3_f01(uvec3 seed) { uvec3 v = pcg3d(seed); return vec3(uintToFloat01(v.x), uintToFloat01(v.y), uintToFloat01(v.z)); -} \ No newline at end of file +} +#endif // NOISE_GLSL_INCLUDED diff --git a/ref_vk/shaders/ray_light_direct.comp b/ref_vk/shaders/ray_light_direct.comp new file mode 100644 index 00000000..5ba2ae35 --- /dev/null +++ b/ref_vk/shaders/ray_light_direct.comp @@ -0,0 +1,66 @@ +#version 460 core +#extension GL_GOOGLE_include_directive : require +//#extension GL_EXT_ray_query: require +#extension GL_EXT_control_flow_attributes : require + +layout(local_size_x = 16, local_size_y = 8, local_size_z = 1) in; + +#include "ray_light_direct_iface.h" + +#define GLSL +#include "ray_interop.h" +#undef GLSL + +#define X(index, name, format) layout(set=0,binding=index,format) uniform readonly image2D name; +RAY_LIGHT_DIRECT_INPUTS(X) +#undef X +#define X(index, name, format) layout(set=0,binding=index,format) uniform writeonly image2D out_image_##name; +RAY_LIGHT_DIRECT_OUTPUTS(X) +#undef X + +//layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; +layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; + +#include "ray_kusochki.glsl" + +#define BINDING_LIGHTS 7 +#define BINDING_LIGHT_CLUSTERS 8 +#include "light.glsl" + +void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { + const vec4 n = imageLoad(normals_gs, uv); + geometry_normal = vec3(n.xy, sqrt(1. - n.x*n.x - n.y*n.y)); + shading_normal = vec3(n.zw, sqrt(1. - n.z*n.z - n.w*n.w)); +} + +void main() { + //vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.; + const ivec2 res = ivec2(imageSize(position_t)); + const ivec2 pix = ivec2(gl_GlobalInvocationID); + const vec2 uv = (pix + .5) / res * 2. - 1.; + + rand01_state = /* FIXME push_constants.random_seed +*/ gl_GlobalInvocationID.x * 1833 + gl_GlobalInvocationID.y * 31337; + + // FIXME incorrect for reflection/refraction + vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); + vec3 direction = normalize((ubo.inv_view * vec4(target.xyz, 0)).xyz); + + MaterialProperties material; + material.baseColor = vec3(1.); + material.emissive = vec3(0.f); + material.metalness = 0.f; // TODO + material.roughness = 1.f; // TODO + + const vec3 pos = imageLoad(position_t, pix).xyz; + + vec3 geometry_normal, shading_normal; + readNormals(pix, geometry_normal, shading_normal); + + const vec3 throughput = vec3(1.); + vec3 diffuse = vec3(0.), specular = vec3(0.); + computeLighting(pos, shading_normal, throughput, -direction, material, diffuse, specular); + + imageStore(out_image_light_diffuse, pix, vec4(diffuse, 0.f)); + //imageStore(out_image_light_diffuse, pix, vec4(1.,0.,0.f, 0.f)); + imageStore(out_image_light_specular, pix, vec4(specular, 0.f)); +} diff --git a/ref_vk/shaders/ray_light_direct_iface.h b/ref_vk/shaders/ray_light_direct_iface.h new file mode 100644 index 00000000..92b3b786 --- /dev/null +++ b/ref_vk/shaders/ray_light_direct_iface.h @@ -0,0 +1,8 @@ +#define RAY_LIGHT_DIRECT_INPUTS(X) \ + X(10, position_t, rgba32f) \ + X(11, normals_gs, rgba16f) \ + +#define RAY_LIGHT_DIRECT_OUTPUTS(X) \ + X(13, light_diffuse, rgba16f) \ + X(14, light_specular, rgba16f) \ + diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index 28a8d94b..958bdabc 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -19,7 +19,8 @@ void main() { // FIXME start on a near plane vec3 origin = (ubo.inv_view * vec4(0, 0, 0, 1)).xyz; vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); - vec3 direction = (ubo.inv_view * vec4(normalize(target.xyz), 0)).xyz; + //vec3 direction = (ubo.inv_view * vec4(normalize(target.xyz), 0)).xyz; + vec3 direction = normalize((ubo.inv_view * vec4(target.xyz, 0)).xyz); const uint flags = gl_RayFlagsCullFrontFacingTrianglesEXT; const uint sbt_offset = 0; diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c new file mode 100644 index 00000000..e819c0a1 --- /dev/null +++ b/ref_vk/vk_ray_light_direct.c @@ -0,0 +1,177 @@ +#include "vk_ray_light_direct.h" +#include "vk_ray_internal.h" + +#include "vk_descriptor.h" +#include "vk_pipeline.h" +#include "vk_buffer.h" + +#include "eiface.h" // ARRAYSIZE + +enum { + // TODO set 0 + //RtLDir_Desc_TLAS, + RtLDir_Desc_UBO, + RtLDir_Desc_Kusochki, + RtLDir_Desc_Indices, + RtLDir_Desc_Vertices, + RtLDir_Desc_Textures, + RtLDir_Desc_Lights, + RtLDir_Desc_LightClusters, + + // TODO set 1 +#define X(index, name, ...) RtLDir_Desc_##name, + RAY_LIGHT_DIRECT_INPUTS(X) +#undef X +#define X(index, name, ...) RtLDir_Desc_##name, + RAY_LIGHT_DIRECT_OUTPUTS(X) +#undef X + + RtLDir_Desc_COUNT +}; + +static struct { + struct { + vk_descriptors_t riptors; + VkDescriptorSetLayoutBinding bindings[RtLDir_Desc_COUNT]; + vk_descriptor_value_t values[RtLDir_Desc_COUNT]; + + // TODO: split into two sets, one common to all rt passes (tlas, kusochki, etc), another one this pass only + VkDescriptorSet sets[1]; + } desc; + + VkPipeline pipeline; +} g_ray_light_direct; + +static void initDescriptors( void ) { + g_ray_light_direct.desc.riptors = (vk_descriptors_t) { + .bindings = g_ray_light_direct.desc.bindings, + .num_bindings = ARRAYSIZE(g_ray_light_direct.desc.bindings), + .values = g_ray_light_direct.desc.values, + .num_sets = ARRAYSIZE(g_ray_light_direct.desc.sets), + .desc_sets = g_ray_light_direct.desc.sets, + /* .push_constants = (VkPushConstantRange){ */ + /* .offset = 0, */ + /* .size = sizeof(vk_rtx_push_constants_t), */ + /* .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, */ + /* }, */ + }; + +#define INIT_BINDING(index, name, type, count) \ + g_ray_light_direct.desc.bindings[RtLDir_Desc_##name] = (VkDescriptorSetLayoutBinding){ \ + .binding = index, \ + .descriptorType = type, \ + .descriptorCount = count, \ + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, \ + } + + //INIT_BINDING(1, TLAS, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1); + INIT_BINDING(2, UBO, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1); + INIT_BINDING(3, Kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); + INIT_BINDING(4, Indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); + INIT_BINDING(5, Vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); + INIT_BINDING(6, Textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES); + INIT_BINDING(7, Lights, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1); + //INIT_BINDING(7, Lights, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); + INIT_BINDING(8, LightClusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); + +//#define X(index, name, ...) INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1); +#define X(index, name, ...) INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1); + RAY_LIGHT_DIRECT_INPUTS(X) +#undef X +#define X(index, name, ...) INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1); + RAY_LIGHT_DIRECT_OUTPUTS(X) +#undef X +#undef INIT_BINDING + + VK_DescriptorsCreate(&g_ray_light_direct.desc.riptors); +} + +static void updateDescriptors( const xvk_ray_trace_light_direct_t* args ) { +#define X(index, name, ...) \ + g_ray_light_direct.desc.values[RtLDir_Desc_##name].image = (VkDescriptorImageInfo){ \ + .sampler = VK_NULL_HANDLE, \ + .imageView = args->in.name, \ + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ + }; + RAY_LIGHT_DIRECT_INPUTS(X) +#undef X + +#define X(index, name, ...) \ + g_ray_light_direct.desc.values[RtLDir_Desc_##name].image = (VkDescriptorImageInfo){ \ + .sampler = VK_NULL_HANDLE, \ + .imageView = args->out.name, \ + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ + }; + RAY_LIGHT_DIRECT_OUTPUTS(X) + + /* g_ray_light_direct.desc.values[RtLDir_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ */ + /* .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, */ + /* .accelerationStructureCount = 1, */ + /* .pAccelerationStructures = &args->in.tlas, */ + /* }; */ + +#define DESC_SET_BUFFER(index, buffer_) \ + g_ray_light_direct.desc.values[index].buffer = (VkDescriptorBufferInfo){ \ + .buffer = args->in.buffer_.buffer, \ + .offset = args->in.buffer_.offset, \ + .range = args->in.buffer_.size, \ + } + + DESC_SET_BUFFER(RtLDir_Desc_UBO, ubo); + DESC_SET_BUFFER(RtLDir_Desc_Kusochki, kusochki); + DESC_SET_BUFFER(RtLDir_Desc_Indices, indices); + DESC_SET_BUFFER(RtLDir_Desc_Vertices, vertices); + DESC_SET_BUFFER(RtLDir_Desc_Lights, lights); + DESC_SET_BUFFER(RtLDir_Desc_LightClusters, light_clusters); + +#undef DESC_SET_BUFFER + + g_ray_light_direct.desc.values[RtLDir_Desc_Textures].image_array = args->in.all_textures; + + VK_DescriptorsWrite(&g_ray_light_direct.desc.riptors); +} + +static VkPipeline createPipeline( void ) { + const vk_pipeline_compute_create_info_t pcci = { + .layout = g_ray_light_direct.desc.riptors.pipeline_layout, + .shader_filename = "ray_light_direct.comp.spv", + .specialization_info = NULL, + }; + + return VK_PipelineComputeCreate( &pcci ); +} + +qboolean XVK_RayTraceLightDirectInit( void ) { + initDescriptors(); + + g_ray_light_direct.pipeline = createPipeline(); + ASSERT(g_ray_light_direct.pipeline != VK_NULL_HANDLE); + + return true; +} + +void XVK_RayTraceLightDirectDestroy( void ) { + vkDestroyPipeline(vk_core.device, g_ray_light_direct.pipeline, NULL); + VK_DescriptorsDestroy(&g_ray_light_direct.desc.riptors); +} + +void XVK_RayTraceLightDirectReloadPipeline( void ) { + VkPipeline pipeline = createPipeline(); + if (pipeline == VK_NULL_HANDLE) + return; + + vkDestroyPipeline(vk_core.device, g_ray_light_direct.pipeline, NULL); + g_ray_light_direct.pipeline = pipeline; +} + +void XVK_RayTraceLightDirect( VkCommandBuffer cmdbuf, const xvk_ray_trace_light_direct_t *args ) { + const uint32_t WG_W = 16; + const uint32_t WG_H = 8; + + updateDescriptors( args ); + + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_ray_light_direct.pipeline); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_ray_light_direct.desc.riptors.pipeline_layout, 0, 1, g_ray_light_direct.desc.riptors.desc_sets + 0, 0, NULL); + vkCmdDispatch(cmdbuf, (args->width + WG_W - 1) / WG_W, (args->height + WG_H - 1) / WG_H, 1); +} + diff --git a/ref_vk/vk_ray_light_direct.h b/ref_vk/vk_ray_light_direct.h new file mode 100644 index 00000000..4a209bde --- /dev/null +++ b/ref_vk/vk_ray_light_direct.h @@ -0,0 +1,35 @@ +#pragma once +#include "vk_core.h" +#include "vk_rtx.h" +#include "shaders/ray_light_direct_iface.h" + +qboolean XVK_RayTraceLightDirectInit( void ); +void XVK_RayTraceLightDirectDestroy( void ); +void XVK_RayTraceLightDirectReloadPipeline( void ); + +typedef struct { + uint32_t width, height; + + struct { + // TODO separate desc set + VkAccelerationStructureKHR tlas; + + // needed for alpha testing :( + vk_buffer_region_t ubo; + vk_buffer_region_t kusochki, indices, vertices; + VkDescriptorImageInfo *all_textures; // [MAX_TEXTURES] + + vk_buffer_region_t lights; + vk_buffer_region_t light_clusters; + +#define X(index, name, ...) VkImageView name; + RAY_LIGHT_DIRECT_INPUTS(X) + } in; + + struct { + RAY_LIGHT_DIRECT_OUTPUTS(X) +#undef X + } out; +} xvk_ray_trace_light_direct_t; + +void XVK_RayTraceLightDirect( VkCommandBuffer cmdbuf, const xvk_ray_trace_light_direct_t *args ); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index b808c5bb..f2b53ce7 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -1,6 +1,7 @@ #include "vk_rtx.h" #include "vk_ray_primary.h" +#include "vk_ray_light_direct.h" #include "vk_core.h" #include "vk_common.h" @@ -94,12 +95,12 @@ typedef struct { #define X(index, name, ...) xvk_image_t name; RAY_PRIMARY_OUTPUTS(X) +RAY_LIGHT_DIRECT_OUTPUTS(X) #undef X xvk_image_t diffuse_gi; xvk_image_t specular; xvk_image_t additive; - xvk_image_t normals; } xvk_ray_frame_images_t; static struct { @@ -711,7 +712,7 @@ static void updateDescriptors( const vk_ray_frame_render_args_t *args, int frame g_rtx.desc_values[RayDescBinding_Dest_ImageNormals].image = (VkDescriptorImageInfo){ .sampler = VK_NULL_HANDLE, - .imageView = frame_dst->normals.view, + //.imageView = frame_dst->normals.view, .imageLayout = VK_IMAGE_LAYOUT_GENERAL, }; @@ -948,11 +949,39 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar //updateDescriptors(args, frame_index, current_frame); #define LIST_GBUFFER_IMAGES(X) \ - RAY_PRIMARY_OUTPUTS(X) \ X(0, diffuse_gi) \ X(0, specular) \ X(0, additive) \ - X(0, normals) \ + +#define IMAGE_BARRIER(img, src_access, dst_access, old_layout, new_layout) { \ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, \ + .image = current_frame->img.image, \ + .srcAccessMask = src_access, \ + .dstAccessMask = dst_access, \ + .oldLayout = old_layout, \ + .newLayout = new_layout, \ + .subresourceRange = (VkImageSubresourceRange) { \ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, \ + .baseMipLevel = 0, \ + .levelCount = 1, \ + .baseArrayLayer = 0, \ + .layerCount = 1, \ + }, \ + }, + +#define IMAGE_BARRIER_READ(index, img, ...) \ + IMAGE_BARRIER(img, \ + VK_ACCESS_SHADER_WRITE_BIT, \ + VK_ACCESS_SHADER_READ_BIT, \ + VK_IMAGE_LAYOUT_GENERAL, \ + VK_IMAGE_LAYOUT_GENERAL) + +#define IMAGE_BARRIER_WRITE(index, img, ...) \ + IMAGE_BARRIER(img, \ + 0, \ + VK_ACCESS_SHADER_WRITE_BIT, \ + VK_IMAGE_LAYOUT_UNDEFINED, \ + VK_IMAGE_LAYOUT_GENERAL) // 4. Barrier for TLAS build and dest image layout transfer { @@ -965,23 +994,10 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar .size = VK_WHOLE_SIZE, } }; VkImageMemoryBarrier image_barrier[] = { -#define GBUFFER_WRITE_BARRIER(index, img, ...) { \ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, \ - .image = current_frame->img.image, \ - .srcAccessMask = 0, \ - .dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT, \ - .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, \ - .newLayout = VK_IMAGE_LAYOUT_GENERAL, \ - .subresourceRange = (VkImageSubresourceRange) { \ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, \ - .baseMipLevel = 0, \ - .levelCount = 1, \ - .baseArrayLayer = 0, \ - .layerCount = 1, \ - }, \ - }, -LIST_GBUFFER_IMAGES(GBUFFER_WRITE_BARRIER) + LIST_GBUFFER_IMAGES(IMAGE_BARRIER_WRITE) // TODO this is not true lol + RAY_PRIMARY_OUTPUTS(IMAGE_BARRIER_WRITE) }; + vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, @@ -1024,48 +1040,82 @@ RAY_PRIMARY_OUTPUTS(X) }; XVK_RayTracePrimary( cmdbuf, &primary_args ); } + //rayTrace(cmdbuf, current_frame, fov_angle_y); { const VkImageMemoryBarrier image_barriers[] = { -#define GBUFFER_READ_BARRIER(index, img, ...) { \ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, \ - .image = current_frame->img.image, \ - .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, \ - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, \ - .oldLayout = VK_IMAGE_LAYOUT_GENERAL, \ - .newLayout = VK_IMAGE_LAYOUT_GENERAL, \ - .subresourceRange = (VkImageSubresourceRange) { \ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, \ - .baseMipLevel = 0, \ - .levelCount = 1, \ - .baseArrayLayer = 0, \ - .layerCount = 1, \ - }, \ - }, -LIST_GBUFFER_IMAGES(GBUFFER_READ_BARRIER) - { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .image = current_frame->denoised.image, - .srcAccessMask = 0, - .dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT, - .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .subresourceRange = - (VkImageSubresourceRange){ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - } }; + RAY_PRIMARY_OUTPUTS(IMAGE_BARRIER_READ) + RAY_LIGHT_DIRECT_OUTPUTS(IMAGE_BARRIER_WRITE) + }; + vkCmdPipelineBarrier(args->cmdbuf, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, NULL, 0, NULL, ARRAYSIZE(image_barriers), image_barriers); } + { + const xvk_ray_trace_light_direct_t light_direct_args = { + .width = FRAME_WIDTH, + .height = FRAME_HEIGHT, + .in = { + .tlas = g_rtx.tlas, + .ubo = { + .buffer = g_rtx.uniform_buffer.buffer, + .offset = frame_index * sizeof(struct UniformBuffer), + .size = sizeof(struct UniformBuffer), + }, + .kusochki = { + .buffer = g_ray_model_state.kusochki_buffer.buffer, + .offset = 0, + .size = g_ray_model_state.kusochki_buffer.size, + }, + .indices = { + .buffer = args->geometry_data.buffer, + .offset = 0, + .size = args->geometry_data.size, + }, + .vertices = { + .buffer = args->geometry_data.buffer, + .offset = 0, + .size = args->geometry_data.size, + }, + .lights = { + .buffer = g_ray_model_state.lights_buffer.buffer, + .offset = 0, + .size = VK_WHOLE_SIZE, // TODO multiple frames + }, + .light_clusters = { + .buffer = g_rtx.light_grid_buffer.buffer, + .offset = 0, + .size = VK_WHOLE_SIZE, // TODO multiple frames + }, + .all_textures = tglob.dii_all_textures, +#define X(index, name, ...) .name = current_frame->name.view, + RAY_LIGHT_DIRECT_INPUTS(X) + }, + .out = { + RAY_LIGHT_DIRECT_OUTPUTS(X) +#undef X + }, + }; + XVK_RayTraceLightDirect( cmdbuf, &light_direct_args ); + } + + + { + const VkImageMemoryBarrier image_barriers[] = { + RAY_LIGHT_DIRECT_OUTPUTS(IMAGE_BARRIER_READ) + LIST_GBUFFER_IMAGES(IMAGE_BARRIER_READ) + IMAGE_BARRIER_WRITE(-1/*unused*/, denoised) + }; + vkCmdPipelineBarrier(args->cmdbuf, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + 0, 0, NULL, 0, NULL, ARRAYSIZE(image_barriers), image_barriers); + } + { const xvk_denoiser_args_t denoiser_args = { .cmdbuf = cmdbuf, @@ -1074,7 +1124,7 @@ LIST_GBUFFER_IMAGES(GBUFFER_READ_BARRIER) .src = { .position_t_view = current_frame->position_t.view, .base_color_a_view = current_frame->base_color_a.view, - .diffuse_gi_view = current_frame->diffuse_gi.view, + .diffuse_gi_view = current_frame->light_diffuse.view, .specular_view = current_frame->specular.view, .additive_view = current_frame->additive.view, .normals_view = current_frame->normals_gs.view, @@ -1136,6 +1186,7 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) createPipeline(); XVK_RayTracePrimaryReloadPipeline(); + XVK_RayTraceLightDirectReloadPipeline(); XVK_DenoiserReloadPipeline(); g_rtx.reload_pipeline = false; } @@ -1306,6 +1357,7 @@ qboolean VK_RayInit( void ) // TODO complain and cleanup on failure ASSERT(XVK_RayTracePrimaryInit()); + ASSERT(XVK_RayTraceLightDirectInit()); g_rtx.sbt_record_size = vk_core.physical_device.sbt_record_size; @@ -1400,7 +1452,10 @@ qboolean VK_RayInit( void ) #define rgba32f VK_FORMAT_R32G32B32A32_SFLOAT #define rgba16f VK_FORMAT_R16G16B16A16_SFLOAT #define X(index, name, format) CREATE_GBUFFER_IMAGE(name, format, 0); +// TODO better format for normals VK_FORMAT_R16G16B16A16_SNORM +// TODO make sure this format and usage is suppported RAY_PRIMARY_OUTPUTS(X) +RAY_LIGHT_DIRECT_OUTPUTS(X) #undef X #undef rgba8 #undef rgba32f @@ -1408,8 +1463,6 @@ RAY_PRIMARY_OUTPUTS(X) CREATE_GBUFFER_IMAGE(diffuse_gi, VK_FORMAT_R16G16B16A16_SFLOAT, 0); CREATE_GBUFFER_IMAGE(specular, VK_FORMAT_R16G16B16A16_SFLOAT, 0); CREATE_GBUFFER_IMAGE(additive, VK_FORMAT_R16G16B16A16_SFLOAT, 0); - // TODO make sure this format and usage is suppported - CREATE_GBUFFER_IMAGE(normals, VK_FORMAT_R16G16B16A16_SNORM, 0); #undef CREATE_GBUFFER_IMAGE } @@ -1423,17 +1476,18 @@ RAY_PRIMARY_OUTPUTS(X) void VK_RayShutdown( void ) { ASSERT(vk_core.rtx); + XVK_RayTraceLightDirectDestroy(); XVK_RayTracePrimaryDestroy(); for (int i = 0; i < ARRAYSIZE(g_rtx.frames); ++i) { XVK_ImageDestroy(&g_rtx.frames[i].denoised); #define X(index, name, ...) XVK_ImageDestroy(&g_rtx.frames[i].name); RAY_PRIMARY_OUTPUTS(X) +RAY_LIGHT_DIRECT_OUTPUTS(X) #undef X XVK_ImageDestroy(&g_rtx.frames[i].diffuse_gi); XVK_ImageDestroy(&g_rtx.frames[i].specular); XVK_ImageDestroy(&g_rtx.frames[i].additive); - XVK_ImageDestroy(&g_rtx.frames[i].normals); } vkDestroyPipeline(vk_core.device, g_rtx.pipeline, NULL); From a47fdcb8ca4b61c6eedaa130da7cdde408b96bb1 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Tue, 11 Jan 2022 18:13:59 -0800 Subject: [PATCH 068/548] rt: fix most glaring direct light glitches - fix normal2 packing - work around desynced light cluster sizes known issues: - static seed for random - no emissive - no shadows perf (direct): 6.4ms, 183(184)v, 59(128)s, lds=0, 2/16o (?!) --- ref_vk/shaders/denoiser.comp | 6 ++++-- ref_vk/shaders/light.glsl | 4 ++-- ref_vk/shaders/ray_light_direct.comp | 11 ++++++++--- ref_vk/shaders/ray_primary.rchit | 6 ++++-- ref_vk/shaders/utils.glsl | 29 ++++++++++++++++++++++++++++ ref_vk/vk_rtx.c | 2 +- 6 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 ref_vk/shaders/utils.glsl diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 82e5d872..87090f75 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -1,6 +1,7 @@ #version 460 #include "noise.glsl" +#include "utils.glsl" layout(local_size_x = 16, local_size_y = 8, local_size_z = 1) in; @@ -45,8 +46,8 @@ float normpdf(in float x, in float sigma) { return normpdf2(x*x, sigma); } void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { const vec4 n = imageLoad(src_normals, uv); - geometry_normal = vec3(n.xy, sqrt(1. - n.x*n.x - n.y*n.y)); - shading_normal = vec3(n.zw, sqrt(1. - n.z*n.z - n.w*n.w)); + geometry_normal = normalDecode(n.xy); + shading_normal = normalDecode(n.zw); } void main() { @@ -74,6 +75,7 @@ void main() { //imageStore(dest, pix, vec4((imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; //imageStore(dest, pix, vec4((imageLoad(src_diffuse_gi, pix).rgb * base_color.rgb), 0.)); return; //imageStore(dest, pix, vec4(imageLoad(src_normals, pix)*.5 + .5f)); return; + //imageStore(dest, pix, vec4(imageLoad(src_specular, pix)*.5 + .5f)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_specular, pix).rgb), 0.)); return; diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index f003e5c0..24533649 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -1,5 +1,5 @@ -layout (constant_id = 4) const float LIGHT_GRID_CELL_SIZE = 256.; -layout (constant_id = 5) const uint MAX_LIGHT_CLUSTERS = 32768; +layout (constant_id = 4) const float LIGHT_GRID_CELL_SIZE = 128.;// FIXME 256.; +layout (constant_id = 5) const uint MAX_LIGHT_CLUSTERS = 262144;// FIXME 32768; layout (constant_id = 6) const uint MAX_TEXTURES = 4096; layout (set = 0, binding = BINDING_LIGHTS) uniform UBOLights { Lights lights; }; layout (set = 0, binding = BINDING_LIGHT_CLUSTERS, align = 1) readonly buffer UBOLightClusters { diff --git a/ref_vk/shaders/ray_light_direct.comp b/ref_vk/shaders/ray_light_direct.comp index 5ba2ae35..5c1236a4 100644 --- a/ref_vk/shaders/ray_light_direct.comp +++ b/ref_vk/shaders/ray_light_direct.comp @@ -3,6 +3,8 @@ //#extension GL_EXT_ray_query: require #extension GL_EXT_control_flow_attributes : require +#include "utils.glsl" + layout(local_size_x = 16, local_size_y = 8, local_size_z = 1) in; #include "ray_light_direct_iface.h" @@ -29,8 +31,8 @@ layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { const vec4 n = imageLoad(normals_gs, uv); - geometry_normal = vec3(n.xy, sqrt(1. - n.x*n.x - n.y*n.y)); - shading_normal = vec3(n.zw, sqrt(1. - n.z*n.z - n.w*n.w)); + geometry_normal = normalDecode(n.xy); + shading_normal = normalDecode(n.zw); } void main() { @@ -58,8 +60,11 @@ void main() { const vec3 throughput = vec3(1.); vec3 diffuse = vec3(0.), specular = vec3(0.); - computeLighting(pos, shading_normal, throughput, -direction, material, diffuse, specular); + computeLighting(pos + geometry_normal * .001, shading_normal, throughput, -direction, material, diffuse, specular); + //specular = shading_normal; + //diffuse = geometry_normal; + //diffuse = shading_normal; imageStore(out_image_light_diffuse, pix, vec4(diffuse, 0.f)); //imageStore(out_image_light_diffuse, pix, vec4(1.,0.,0.f, 0.f)); imageStore(out_image_light_specular, pix, vec4(specular, 0.f)); diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index 58d92ac1..d5f4fe7e 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -2,6 +2,8 @@ #extension GL_GOOGLE_include_directive : require #extension GL_EXT_nonuniform_qualifier : enable +#include "utils.glsl" + #include "ray_primary_common.glsl" #include "ray_kusochki.glsl" @@ -34,6 +36,6 @@ void main() { payload.base_color_a = sampleTexture(tex_base_color, geom.uv, geom.uv_lods) * kusok.color; } - payload.normals_gs.xy = geom.normal_geometry.xy; - payload.normals_gs.zw = geom.normal_shading.xy; + payload.normals_gs.xy = normalEncode(geom.normal_geometry); + payload.normals_gs.zw = normalEncode(geom.normal_shading); } diff --git a/ref_vk/shaders/utils.glsl b/ref_vk/shaders/utils.glsl new file mode 100644 index 00000000..68fe6034 --- /dev/null +++ b/ref_vk/shaders/utils.glsl @@ -0,0 +1,29 @@ +float signP(float v) { return v >= 0.f ? 1.f : -1.f; } +vec2 signP(vec2 v) { return vec2(signP(v.x), signP(v.y)); } + +// https://knarkowicz.wordpress.com/2014/04/16/octahedron-normal-vector-encoding/ +// https://www.shadertoy.com/view/Mtfyzl +vec2 OctWrap( vec2 v ) +{ + return ( 1.0 - abs( v.yx ) ) * signP(v.xy); +} + +vec2 normalEncode( vec3 n ) +{ + n /= ( abs( n.x ) + abs( n.y ) + abs( n.z ) ); + n.xy = n.z >= 0.0 ? n.xy : OctWrap( n.xy ); + n.xy = n.xy * 0.5 + 0.5; + return n.xy; +} + +vec3 normalDecode( vec2 f ) +{ + f = f * 2.0 - 1.0; + + // https://twitter.com/Stubbesaurus/status/937994790553227264 + vec3 n = vec3( f, 1.0 - abs( f.x ) - abs( f.y ) ); + const float t = max( -n.z, 0.f ); + n.xy -= t * signP(n.xy); + return normalize( n ); +} + diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index f2b53ce7..652d3a0f 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -1125,7 +1125,7 @@ RAY_PRIMARY_OUTPUTS(X) .position_t_view = current_frame->position_t.view, .base_color_a_view = current_frame->base_color_a.view, .diffuse_gi_view = current_frame->light_diffuse.view, - .specular_view = current_frame->specular.view, + .specular_view = current_frame->light_specular.view, .additive_view = current_frame->additive.view, .normals_view = current_frame->normals_gs.view, }, From f4c56ead5499998f36b5255c0c7a69684d8c7301 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Tue, 11 Jan 2022 22:41:21 -0800 Subject: [PATCH 069/548] rt: add shadow ray query to direct light pass 7.557ms 212(216)v 65(128)s lds=8192 2/16o :( also, kernel panics on changelevel --- ref_vk/shaders/light_common.glsl | 13 ++++++++++++- ref_vk/shaders/ray.rgen | 2 ++ ref_vk/shaders/ray_light_direct.comp | 6 ++++-- ref_vk/vk_core.c | 13 +++++++++---- ref_vk/vk_ray_light_direct.c | 15 +++++++-------- 5 files changed, 34 insertions(+), 15 deletions(-) diff --git a/ref_vk/shaders/light_common.glsl b/ref_vk/shaders/light_common.glsl index b8a43e8b..ca8aecd1 100644 --- a/ref_vk/shaders/light_common.glsl +++ b/ref_vk/shaders/light_common.glsl @@ -1,5 +1,5 @@ bool shadowed(vec3 pos, vec3 dir, float dist) { -#if 0 +#ifdef RAY_TRACE payload_shadow.hit_type = SHADOW_HIT; const uint flags = 0 //| gl_RayFlagsCullFrontFacingTrianglesEXT @@ -13,6 +13,17 @@ bool shadowed(vec3 pos, vec3 dir, float dist) { SHADER_OFFSET_HIT_SHADOW_BASE, SBT_RECORD_SIZE, SHADER_OFFSET_MISS_SHADOW, pos, 0., dir, dist - shadow_offset_fudge, PAYLOAD_LOCATION_SHADOW); return payload_shadow.hit_type == SHADOW_HIT; +#elif defined(RAY_QUERY) + rayQueryEXT rq; + const uint flags = 0 + //| gl_RayFlagsCullFrontFacingTrianglesEXT + //| gl_RayFlagsOpaqueEXT + | gl_RayFlagsTerminateOnFirstHitEXT + //| gl_RayFlagsSkipClosestHitShaderEXT + ; + rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE, pos, 0., dir, dist - shadow_offset_fudge); + while (rayQueryProceedEXT(rq)) { } + return rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT; #else return false; #endif diff --git a/ref_vk/shaders/ray.rgen b/ref_vk/shaders/ray.rgen index b87fb4f2..6ec445ab 100644 --- a/ref_vk/shaders/ray.rgen +++ b/ref_vk/shaders/ray.rgen @@ -3,6 +3,8 @@ #extension GL_GOOGLE_include_directive : require #extension GL_EXT_control_flow_attributes : require +#define RAY_TRACE + #include "ray_common.glsl" #include "ray_kusochki.glsl" #include "noise.glsl" diff --git a/ref_vk/shaders/ray_light_direct.comp b/ref_vk/shaders/ray_light_direct.comp index 5c1236a4..cc870057 100644 --- a/ref_vk/shaders/ray_light_direct.comp +++ b/ref_vk/shaders/ray_light_direct.comp @@ -1,7 +1,7 @@ #version 460 core #extension GL_GOOGLE_include_directive : require -//#extension GL_EXT_ray_query: require #extension GL_EXT_control_flow_attributes : require +#extension GL_EXT_ray_query: require #include "utils.glsl" @@ -20,11 +20,13 @@ RAY_LIGHT_DIRECT_INPUTS(X) RAY_LIGHT_DIRECT_OUTPUTS(X) #undef X -//layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; +layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; #include "ray_kusochki.glsl" +#define RAY_QUERY + #define BINDING_LIGHTS 7 #define BINDING_LIGHT_CLUSTERS 8 #include "light.glsl" diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index cea2f569..4a5f51ac 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -134,6 +134,7 @@ static const char* device_extensions[] = { VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME, VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME, + VK_KHR_RAY_QUERY_EXTENSION_NAME, // FIXME make this not depend on RTX #ifdef USE_AFTERMATH @@ -520,16 +521,20 @@ static qboolean createDevice( void ) { .pNext = vk_core.rtx ? &ray_tracing_pipeline_feature: NULL, .features.samplerAnisotropy = candidate_device->features.features.samplerAnisotropy, }; + VkPhysicalDeviceRayQueryFeaturesKHR ray_query_feature = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR, + .pNext = &features, + .rayQuery = VK_TRUE, + }; + void *head = &ray_query_feature; #ifdef USE_AFTERMATH VkDeviceDiagnosticsConfigCreateInfoNV diag_config_nv = { .sType = VK_STRUCTURE_TYPE_DEVICE_DIAGNOSTICS_CONFIG_CREATE_INFO_NV, - .pNext = &features, + .pNext = head, .flags = VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_AUTOMATIC_CHECKPOINTS_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_RESOURCE_TRACKING_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_DEBUG_INFO_BIT_NV, }; - void *head = &diag_config_nv; -#else - void *head = &features; + head = &diag_config_nv; #endif const float queue_priorities[1] = {1.f}; diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index e819c0a1..aa64e800 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -9,7 +9,7 @@ enum { // TODO set 0 - //RtLDir_Desc_TLAS, + RtLDir_Desc_TLAS, RtLDir_Desc_UBO, RtLDir_Desc_Kusochki, RtLDir_Desc_Indices, @@ -64,14 +64,13 @@ static void initDescriptors( void ) { .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, \ } - //INIT_BINDING(1, TLAS, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1); + INIT_BINDING(1, TLAS, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1); INIT_BINDING(2, UBO, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1); INIT_BINDING(3, Kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); INIT_BINDING(4, Indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); INIT_BINDING(5, Vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); INIT_BINDING(6, Textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES); INIT_BINDING(7, Lights, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1); - //INIT_BINDING(7, Lights, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); INIT_BINDING(8, LightClusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); //#define X(index, name, ...) INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1); @@ -104,11 +103,11 @@ static void updateDescriptors( const xvk_ray_trace_light_direct_t* args ) { }; RAY_LIGHT_DIRECT_OUTPUTS(X) - /* g_ray_light_direct.desc.values[RtLDir_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ */ - /* .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, */ - /* .accelerationStructureCount = 1, */ - /* .pAccelerationStructures = &args->in.tlas, */ - /* }; */ + g_ray_light_direct.desc.values[RtLDir_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, + .accelerationStructureCount = 1, + .pAccelerationStructures = &args->in.tlas, + }; #define DESC_SET_BUFFER(index, buffer_) \ g_ray_light_direct.desc.values[index].buffer = (VkDescriptorBufferInfo){ \ From e4ade833e80047f6b5131257ae38c59c94c9c475 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Wed, 12 Jan 2022 22:39:09 -0800 Subject: [PATCH 070/548] rt: use a bit more reasonable compute groups doesn't really affect perf tho --- ref_vk/shaders/ray_light_direct.comp | 2 +- ref_vk/vk_ray_light_direct.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ref_vk/shaders/ray_light_direct.comp b/ref_vk/shaders/ray_light_direct.comp index cc870057..2f822942 100644 --- a/ref_vk/shaders/ray_light_direct.comp +++ b/ref_vk/shaders/ray_light_direct.comp @@ -5,7 +5,7 @@ #include "utils.glsl" -layout(local_size_x = 16, local_size_y = 8, local_size_z = 1) in; +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; #include "ray_light_direct_iface.h" diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index aa64e800..5780fddf 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -164,7 +164,7 @@ void XVK_RayTraceLightDirectReloadPipeline( void ) { } void XVK_RayTraceLightDirect( VkCommandBuffer cmdbuf, const xvk_ray_trace_light_direct_t *args ) { - const uint32_t WG_W = 16; + const uint32_t WG_W = 8; const uint32_t WG_H = 8; updateDescriptors( args ); From abbd0f92a48b6b8bdda0b57157e223b39a18b589 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 13 Jan 2022 15:52:59 +0300 Subject: [PATCH 071/548] engine: input: fix incorrect client notifying about mouse button states --- engine/client/input.c | 46 +++++++++++------------------------- engine/client/input.h | 2 +- engine/platform/sdl/events.c | 12 ++++------ 3 files changed, 20 insertions(+), 40 deletions(-) diff --git a/engine/client/input.c b/engine/client/input.c index e90a5848..09d01033 100644 --- a/engine/client/input.c +++ b/engine/client/input.c @@ -30,8 +30,7 @@ qboolean in_mouseinitialized; qboolean in_mouse_suspended; POINT in_lastvalidpos; qboolean in_mouse_savedpos; -static uint in_mouse_oldbuttonstate; -static int in_mouse_buttons = 5; // SDL maximum +static int in_mstate = 0; static struct inputstate_s { float lastpitch, lastyaw; @@ -337,50 +336,33 @@ void IN_MouseMove( void ) IN_MouseEvent =========== */ -void IN_MouseEvent( uint mstate ) +void IN_MouseEvent( int key, int down ) { int i; if( !in_mouseinitialized ) return; + if( down ) + SetBits( in_mstate, BIT( key )); + else ClearBits( in_mstate, BIT( key )); + if( cls.key_dest == key_game ) { // perform button actions - for( i = 0; i < in_mouse_buttons; i++ ) - { - if( FBitSet( mstate, BIT( i )) && !FBitSet( in_mouse_oldbuttonstate, BIT( i ))) - { - VGui_KeyEvent( K_MOUSE1 + i, true ); - } - - if( !FBitSet( mstate, BIT( i )) && FBitSet( in_mouse_oldbuttonstate, BIT( i ))) - { - VGui_KeyEvent( K_MOUSE1 + i, false ); - } - } + VGui_KeyEvent( K_MOUSE1 + key, down ); + // don't do Key_Event here + // client may override IN_MouseEvent + // but by default it calls back to Key_Event anyway if( in_mouseactive ) - clgame.dllFuncs.IN_MouseEvent( mstate ); - - in_mouse_oldbuttonstate = mstate; - return; + clgame.dllFuncs.IN_MouseEvent( in_mstate ); } - - // perform button actions - for( i = 0; i < in_mouse_buttons; i++ ) + else { - if( FBitSet( mstate, BIT( i )) && !FBitSet( in_mouse_oldbuttonstate, BIT( i ))) - { - Key_Event( K_MOUSE1 + i, true ); - } - - if( !FBitSet( mstate, BIT( i )) && FBitSet( in_mouse_oldbuttonstate, BIT( i ))) - { - Key_Event( K_MOUSE1 + i, false ); - } + // perform button actions + Key_Event( K_MOUSE1 + key, down ); } - in_mouse_oldbuttonstate = mstate; } /* diff --git a/engine/client/input.h b/engine/client/input.h index 4c647dfe..32ad2b6b 100644 --- a/engine/client/input.h +++ b/engine/client/input.h @@ -33,7 +33,7 @@ extern qboolean in_mouseinitialized; void IN_Init( void ); void Host_InputFrame( void ); void IN_Shutdown( void ); -void IN_MouseEvent( uint mstate ); +void IN_MouseEvent( int key, int down ); void IN_ActivateMouse( void ); void IN_DeactivateMouse( void ); void IN_MouseSavePos( void ); diff --git a/engine/platform/sdl/events.c b/engine/platform/sdl/events.c index 54e36e87..e2530909 100644 --- a/engine/platform/sdl/events.c +++ b/engine/platform/sdl/events.c @@ -281,19 +281,19 @@ static void SDLash_MouseEvent( SDL_MouseButtonEvent button ) switch( button.button ) { case SDL_BUTTON_LEFT: - if( down ) SetBits( mstate, BIT( 0 )); + IN_MouseEvent( 0, down ); break; case SDL_BUTTON_MIDDLE: - if( down ) SetBits( mstate, BIT( 1 )); + IN_MouseEvent( 1, down ); break; case SDL_BUTTON_RIGHT: - if( down ) SetBits( mstate, BIT( 2 )); + IN_MouseEvent( 2, down ); break; case SDL_BUTTON_X1: - if( down ) SetBits( mstate, BIT( 3 )); + IN_MouseEvent( 3, down ); break; case SDL_BUTTON_X2: - if( down ) SetBits( mstate, BIT( 4 )); + IN_MouseEvent( 4, down ); break; #if ! SDL_VERSION_ATLEAST( 2, 0, 0 ) case SDL_BUTTON_WHEELUP: @@ -306,8 +306,6 @@ static void SDLash_MouseEvent( SDL_MouseButtonEvent button ) default: Con_Printf( "Unknown mouse button ID: %d\n", button.button ); } - - IN_MouseEvent( mstate ); } /* From c3513b16155433722089fba1d1cf23e3615c7da5 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 13 Jan 2022 16:18:23 +0300 Subject: [PATCH 072/548] engine: input: clean m_enginemouse leftovers, always enable SDL relative mouse mode for our input interface --- engine/client/input.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/engine/client/input.c b/engine/client/input.c index 09d01033..83c89a06 100644 --- a/engine/client/input.c +++ b/engine/client/input.c @@ -37,11 +37,9 @@ static struct inputstate_s } inputstate; extern convar_t *vid_fullscreen; -convar_t *m_enginemouse; convar_t *m_pitch; convar_t *m_yaw; -convar_t *m_enginesens; convar_t *m_ignore; convar_t *cl_forwardspeed; convar_t *cl_sidespeed; @@ -116,8 +114,6 @@ void IN_StartupMouse( void ) { m_ignore = Cvar_Get( "m_ignore", DEFAULT_M_IGNORE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "ignore mouse events" ); - m_enginemouse = Cvar_Get( "m_enginemouse", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "read mouse events in engine instead of client" ); - m_enginesens = Cvar_Get( "m_enginesens", "0.3", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "mouse sensitivity, when m_enginemouse enabled" ); m_pitch = Cvar_Get( "m_pitch", "0.022", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "mouse pitch value" ); m_yaw = Cvar_Get( "m_yaw", "0.022", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "mouse yaw value" ); look_filter = Cvar_Get( "look_filter", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "filter look events making it smoother" ); @@ -211,7 +207,7 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) void IN_CheckMouseState( qboolean active ) { #if XASH_WIN32 - qboolean useRawInput = CVAR_TO_BOOL( m_rawinput ) && clgame.client_dll_uses_sdl; + qboolean useRawInput = CVAR_TO_BOOL( m_rawinput ) && clgame.client_dll_uses_sdl || clgame.dllFuncs.pfnLookEvent; #else qboolean useRawInput = true; // always use SDL code #endif @@ -498,13 +494,9 @@ static void IN_JoyAppendMove( usercmd_t *cmd, float forwardmove, float sidemove } } -void IN_CollectInput( float *forward, float *side, float *pitch, float *yaw, qboolean includeMouse, qboolean includeSdlMouse ) +static void IN_CollectInput( float *forward, float *side, float *pitch, float *yaw, qboolean includeMouse ) { - if( includeMouse -#if XASH_SDL - && includeSdlMouse -#endif - ) + if( includeMouse ) { float x, y; Platform_MouseMove( &x, &y ); @@ -553,7 +545,7 @@ void IN_EngineAppendMove( float frametime, void *cmd1, qboolean active ) { float sensitivity = 1;//( (float)cl.local.scr_fov / (float)90.0f ); - IN_CollectInput( &forward, &side, &pitch, &yaw, false, false ); + IN_CollectInput( &forward, &side, &pitch, &yaw, false ); IN_JoyAppendMove( cmd, forward, side ); @@ -577,7 +569,7 @@ void IN_Commands( void ) { float forward = 0, side = 0, pitch = 0, yaw = 0; - IN_CollectInput( &forward, &side, &pitch, &yaw, in_mouseinitialized && !CVAR_TO_BOOL( m_ignore ), true ); + IN_CollectInput( &forward, &side, &pitch, &yaw, in_mouseinitialized && !CVAR_TO_BOOL( m_ignore ) ); if( cls.key_dest == key_game ) { From e29dcb4125e17d3bbf0860361d77e141ddb69b12 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 13 Jan 2022 16:44:45 +0300 Subject: [PATCH 073/548] engine: filesystem: accept ZIP files with zip extension, do not assert with NULL free in Zip_Close --- engine/common/filesystem.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index e3c3e402..826430f4 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -928,7 +928,8 @@ void Zip_Close( zip_t *zip ) if( !zip ) return; - Mem_Free( zip->files ); + if( zip->files ) + Mem_Free( zip->files ); FS_EnsureOpenZip( NULL ); @@ -1209,7 +1210,7 @@ qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int if( already_loaded ) *already_loaded = false; - if( !Q_stricmp( ext, "pk3" ) ) + if( !Q_stricmp( ext, "pk3" ) || !Q_stricmp( ext, "zip" )) zip = FS_LoadZip( zipfile, &errorcode ); if( zip ) From 606b5354e63e5e4f5b6239002d9501fa8f33dcda Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 13 Jan 2022 17:46:54 +0300 Subject: [PATCH 074/548] engine: filesystem: fix loading ZIP files --- engine/common/filesystem.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index 826430f4..2f75ce88 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -717,7 +717,7 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) int numpackfiles = 0, i; zip_cdf_header_t header_cdf; zip_header_eocd_t header_eocd; - uint signature; + uint32_t signature; fs_offset_t filepos = 0, length; zipfile_t *info = NULL; char filename_buffer[MAX_SYSPATH]; @@ -794,7 +794,7 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) lseek( zip->handle, filepos, SEEK_SET ); c = read( zip->handle, &signature, sizeof( signature ) ); - if( c != sizeof( signature ) || signature == ZIP_HEADER_EOCD ) + if( c == sizeof( signature ) && signature == ZIP_HEADER_EOCD ) break; filepos -= sizeof( char ); // step back one byte From 13cf909b998e1b20f9ace178981b5ef20ab61395 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 13 Jan 2022 18:22:06 +0300 Subject: [PATCH 075/548] engine: filesystem: allow extras ZIPs --- engine/common/filesystem.c | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index 2f75ce88..ea44657f 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -1192,7 +1192,7 @@ static qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loade } } -qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ) +static qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ) { searchpath_t *search; zip_t *zip = NULL; @@ -1245,6 +1245,24 @@ qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int } } +/* +================ +FS_AddArchive_Fullpath +================ +*/ +static qboolean FS_AddArchive_Fullpath( const char *file, qboolean *already_loaded, int flags ) +{ + const char *ext = COM_FileExtension( file ); + + if( !Q_stricmp( ext, "zip" ) || !Q_stricmp( ext, "pk3" )) + return FS_AddZip_Fullpath( file, already_loaded, flags ); + else if ( !Q_stricmp( ext, "pak" )) + return FS_AddPak_Fullpath( file, already_loaded, flags ); + + // skip wads, this function only meant to be used for extras + return false; +} + /* ================ FS_AddGameDirectory @@ -1470,11 +1488,12 @@ void FS_Rescan( void ) } #else str = getenv( "XASH3D_EXTRAS_PAK1" ); - if( COM_CheckString( str ) ) - FS_AddPak_Fullpath( str, NULL, extrasFlags ); + if( COM_CheckString( str )) + FS_AddArchive_Fullpath( str, NULL, extrasFlags ); + str = getenv( "XASH3D_EXTRAS_PAK2" ); - if( COM_CheckString( str ) ) - FS_AddPak_Fullpath( str, NULL, extrasFlags ); + if( COM_CheckString( str )) + FS_AddArchive_Fullpath( str, NULL, extrasFlags ); #endif if( Q_stricmp( GI->basedir, GI->gamefolder )) From 6b332eabd29a6a328595644690c12f902b4a2665 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 13 Jan 2022 18:31:12 +0300 Subject: [PATCH 076/548] scripts: gha: include extras in MAGX and Win32 builds too (Linux and Android already have it) --- scripts/gha/build_motomagx.sh | 3 ++- scripts/gha/build_win32.sh | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/gha/build_motomagx.sh b/scripts/gha/build_motomagx.sh index 8ab397a1..c103bd42 100755 --- a/scripts/gha/build_motomagx.sh +++ b/scripts/gha/build_motomagx.sh @@ -23,13 +23,14 @@ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$LIBDIR1:$LIBDIR2:$LIBDIR3 export HOME=$mypath export SDL_QT_INVERT_ROTATION=1 export SWAP_PATH=$HOME/xash.swap +export XASH3D_EXTRAS_PAK1=$HOME/extras.pak cd $mypath sleep 1 exec $mypath/xash -dev $@ EOF +python3 scripts/makepak.py xash-extras/ Xash/extras.pak mkdir -p artifacts/ - 7z a -t7z artifacts/xash3d-fwgs-magx.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -r Xash/ diff --git a/scripts/gha/build_win32.sh b/scripts/gha/build_win32.sh index ad11f6bc..4676458f 100755 --- a/scripts/gha/build_win32.sh +++ b/scripts/gha/build_win32.sh @@ -24,5 +24,8 @@ else die fi +mkdir valve/ +python3 scripts/makepak.py xash-extras/ valve/extras.pak + mkdir -p artifacts/ -7z a -t7z artifacts/xash3d-fwgs-win32-$ARCH.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on *.dll *.exe *.pdb +7z a -t7z artifacts/xash3d-fwgs-win32-$ARCH.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on *.dll *.exe *.pdb valve/ From aa5594d11f2439520d11f65e335e11266bb32ffe Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 13 Jan 2022 23:52:05 +0300 Subject: [PATCH 077/548] engine: client: don't let servers clear console --- engine/client/console.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/console.c b/engine/client/console.c index 26982b70..2c2d6cf5 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -1159,7 +1159,7 @@ void Con_Init( void ) con.chat.widthInChars = con.linewidth; Cmd_AddCommand( "toggleconsole", Con_ToggleConsole_f, "opens or closes the console" ); - Cmd_AddCommand( "clear", Con_Clear_f, "clear console history" ); + Cmd_AddRestrictedCommand( "clear", Con_Clear_f, "clear console history" ); Cmd_AddCommand( "messagemode", Con_MessageMode_f, "enable message mode \"say\"" ); Cmd_AddCommand( "messagemode2", Con_MessageMode2_f, "enable message mode \"say_team\"" ); Cmd_AddCommand( "contimes", Con_SetTimes_f, "change number of console overlay lines (4-64)" ); From fa37d153c83c03ff23c40f23d2018562d582f90f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 14 Jan 2022 00:26:26 +0300 Subject: [PATCH 078/548] engine: common: restrict alias/unalias and stuffcmds from executing by server --- engine/common/cmd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/common/cmd.c b/engine/common/cmd.c index eb684a01..9b3e6c77 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -1384,13 +1384,13 @@ void Cmd_Init( void ) Cmd_AddCommand( "echo", Cmd_Echo_f, "print a message to the console (useful in scripts)" ); Cmd_AddCommand( "wait", Cmd_Wait_f, "make script execution wait for some rendered frames" ); Cmd_AddCommand( "cmdlist", Cmd_List_f, "display all console commands beginning with the specified prefix" ); - Cmd_AddCommand( "stuffcmds", Cmd_StuffCmds_f, "execute commandline parameters (must be present in .rc script)" ); + Cmd_AddRestrictedCommand( "stuffcmds", Cmd_StuffCmds_f, "execute commandline parameters (must be present in .rc script)" ); Cmd_AddCommand( "apropos", Cmd_Apropos_f, "lists all console variables/commands/aliases containing the specified string in the name or description" ); #if !XASH_DEDICATED Cmd_AddCommand( "cmd", Cmd_ForwardToServer, "send a console commandline to the server" ); #endif // XASH_DEDICATED - Cmd_AddCommand( "alias", Cmd_Alias_f, "create a script function. Without arguments show the list of all alias" ); - Cmd_AddCommand( "unalias", Cmd_UnAlias_f, "remove a script function" ); + Cmd_AddRestrictedCommand( "alias", Cmd_Alias_f, "create a script function. Without arguments show the list of all alias" ); + Cmd_AddRestrictedCommand( "unalias", Cmd_UnAlias_f, "remove a script function" ); Cmd_AddCommand( "if", Cmd_If_f, "compare and set condition bits" ); Cmd_AddCommand( "else", Cmd_Else_f, "invert condition bit" ); From 96f35f76e137e5e95c27de3fe02f10a8992a6e9f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 14 Jan 2022 00:42:24 +0300 Subject: [PATCH 079/548] engine: filesystem: fix creating directories with roDir --- engine/common/filesystem.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index ea44657f..cf9ccea3 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -2186,8 +2186,8 @@ void FS_Init( void ) for( i = 0; i < dirs.numstrings; i++ ) { - char *roPath = va( "%s" PATH_SPLITTER "%s", host.rodir, dirs.strings[i] ); - char *rwPath = va( "%s" PATH_SPLITTER "%s", host.rootdir, dirs.strings[i] ); + char *roPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, host.rodir, dirs.strings[i] ); + char *rwPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, host.rootdir, dirs.strings[i] ); // check if it's a directory if( !FS_SysFolderExists( roPath )) From 1f26710bd05771e59446150614eed0e3ee04dd7a Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 14 Jan 2022 01:06:48 +0300 Subject: [PATCH 080/548] engine: fix add command error messages --- engine/common/cmd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/common/cmd.c b/engine/common/cmd.c index 9b3e6c77..b1e510b6 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -671,21 +671,21 @@ static int Cmd_AddCommandEx( const char *funcname, const char *cmd_name, xcomman if( !COM_CheckString( cmd_name )) { - Con_Reportf( S_ERROR "Cmd_AddCommand: NULL name\n" ); + Con_Reportf( S_ERROR "%s: NULL name\n", funcname ); return 0; } // fail if the command is a variable name if( Cvar_FindVar( cmd_name )) { - Con_DPrintf( S_ERROR "Cmd_AddServerCommand: %s already defined as a var\n", cmd_name ); + Con_DPrintf( S_ERROR "%s: %s already defined as a var\n", funcname, cmd_name ); return 0; } // fail if the command already exists if( Cmd_Exists( cmd_name )) { - Con_DPrintf( S_ERROR "Cmd_AddServerCommand: %s already defined\n", cmd_name ); + Con_DPrintf( S_ERROR "%s: %s already defined\n", funcname, cmd_name ); return 0; } From 22e770e4d4d5e4d5cf1b23da4e2b9eab120c616e Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 14 Jan 2022 01:18:22 +0300 Subject: [PATCH 081/548] engine: client: disable viewsize without requested Quake compatibility --- engine/client/cl_view.c | 45 ++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/engine/client/cl_view.c b/engine/client/cl_view.c index 26930bd1..310d3aac 100644 --- a/engine/client/cl_view.c +++ b/engine/client/cl_view.c @@ -35,30 +35,39 @@ void V_CalcViewRect( void ) int sb_lines; float size; - // intermission is always full screen - if( cl.intermission ) size = 120.0f; - else size = scr_viewsize->value; + if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + { + // intermission is always full screen + if( cl.intermission ) size = 120.0f; + else size = scr_viewsize->value; - if( size >= 120.0f ) - sb_lines = 0; // no status bar at all - else if( size >= 110.0f ) - sb_lines = 24; // no inventory - else sb_lines = 48; + if( size >= 120.0f ) + sb_lines = 0; // no status bar at all + else if( size >= 110.0f ) + sb_lines = 24; // no inventory + else sb_lines = 48; - if( scr_viewsize->value >= 100.0f ) + if( scr_viewsize->value >= 100.0f ) + { + full = true; + size = 100.0f; + } + else size = scr_viewsize->value; + + if( cl.intermission ) + { + size = 100.0f; + sb_lines = 0; + full = true; + } + size /= 100.0f; + } + else { full = true; - size = 100.0f; - } - else size = scr_viewsize->value; - - if( cl.intermission ) - { - size = 100.0f; sb_lines = 0; - full = true; + size = 1.0f; } - size /= 100.0f; clgame.viewport[2] = refState.width * size; clgame.viewport[3] = refState.height * size; From 2c74a633d046f8da0b842af4a7e0bfb910995fb7 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 14 Jan 2022 01:29:33 +0300 Subject: [PATCH 082/548] mainui: update --- mainui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainui b/mainui index e8f8c9a0..e0b3b91b 160000 --- a/mainui +++ b/mainui @@ -1 +1 @@ -Subproject commit e8f8c9a0721913e75357728e05c2f5ab0b506e47 +Subproject commit e0b3b91b6e6a903d21ac934d8ff418b41c5b6555 From b31462fe18ed664e6b85ba585abdc7a0358a60de Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 13 Jan 2022 22:36:21 -0800 Subject: [PATCH 083/548] rt: stub better emissive surfaces representation path Light polygons that are: - self-sufficient (no kusochki indirection needed). - pre-transformed - contain enough metadata (area, center, normal) for quick sampling - are polygons with up to 7 vertices and not triangles (easier sampling) Also move rad file reading to before brush model loading, as it now requires lighting data. --- ref_vk/vk_brush.c | 67 +++++++++++++++++++++++++++++++++++++++++++ ref_vk/vk_light.c | 48 ++++++++++++++++++++----------- ref_vk/vk_light.h | 19 +++++++++++- ref_vk/vk_ray_model.c | 2 +- ref_vk/vk_scene.c | 7 +++-- 5 files changed, 122 insertions(+), 21 deletions(-) diff --git a/ref_vk/vk_brush.c b/ref_vk/vk_brush.c index 38bf5b5f..34d987c2 100644 --- a/ref_vk/vk_brush.c +++ b/ref_vk/vk_brush.c @@ -438,6 +438,63 @@ static model_sizes_t computeSizes( const model_t *mod ) { return sizes; } +static void loadEmissiveSurface(const model_t *mod, const int surface_index, const msurface_t *surf, const vec3_t emissive) { + rt_light_polygon_t lpoly; + lpoly.num_vertices = Q_min(7, surf->numedges); + + if (surf->numedges > 7) + gEngine.Con_Printf(S_WARN "emissive surface %d has %d vertices; clipping to 7\n", surface_index, surf->numedges); + + VectorCopy(emissive, lpoly.emissive); + + VectorSet(lpoly.center, 0, 0, 0); + VectorSet(lpoly.normal, 0, 0, 0); + + for (int i = 0; i < lpoly.num_vertices; ++i) { + const int iedge = mod->surfedges[surf->firstedge + i]; + const medge_t *edge = mod->edges + (iedge >= 0 ? iedge : -iedge); + const mvertex_t *vertex = mod->vertexes + (iedge >= 0 ? edge->v[0] : edge->v[1]); + VectorCopy(vertex->position, lpoly.vertices[i]); + VectorAdd(vertex->position, lpoly.center, lpoly.center); + + if (i > 1) { + vec3_t e[2], normal; + VectorSubtract(lpoly.vertices[i-1], lpoly.vertices[i-2], e[0]); + VectorSubtract(lpoly.vertices[i-0], lpoly.vertices[i-2], e[1]); + CrossProduct(e[0], e[1], normal); + VectorAdd(normal, lpoly.normal, lpoly.normal); + } + } + + VectorM(1.f / lpoly.num_vertices, lpoly.center, lpoly.center); + + lpoly.area = VectorLength(lpoly.normal); + + VectorNormalize(lpoly.normal); + { + const float dot = DotProduct(lpoly.normal, surf->plane->normal); + if (fabs(dot) < (1.f - 1e-5f)) { + gEngine.Con_Reportf(S_WARN "surf=%d normal=(%f, %f, %f) computed=(%f, %f, %f) dot=%f dir=%d\n", + surf->plane->normal[0], + surf->plane->normal[1], + surf->plane->normal[2], + lpoly.normal[0], + lpoly.normal[1], + lpoly.normal[2], + dot, + !!FBitSet( surf->flags, SURF_PLANEBACK ) + ); + } + } + + if( FBitSet( surf->flags, SURF_PLANEBACK )) + VectorNegate( surf->plane->normal, lpoly.normal ); + else + VectorCopy( surf->plane->normal, lpoly.normal ); + + RT_LightAddPolygon(&lpoly); +} + static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { vk_brush_model_t *bmodel = mod->cache.data; uint32_t vertex_offset = 0; @@ -460,6 +517,7 @@ static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { index_offset = index_buffer.buffer.unit.offset; // Load sorted by gl_texturenum + // TODO this does not make that much sense in vulkan (can sort later) for (int t = 0; t <= sizes.max_texture_id; ++t) { for( int i = 0; i < mod->nummodelsurfaces; ++i) @@ -480,6 +538,15 @@ static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { if (t != tex_id) continue; + { + vec3_t emissive; + if (psurf && (psurf->flags & Patch_Surface_Emissive)) { + loadEmissiveSurface(mod, surface_index, surf, psurf->emissive); + } else if (RT_GetEmissiveForTexture(emissive, tex_id)) { + loadEmissiveSurface(mod, surface_index, surf, emissive); + } + } + ++num_geometries; //gEngine.Con_Reportf( "surface %d: numverts=%d numedges=%d\n", i, surf->polys ? surf->polys->numverts : -1, surf->numedges ); diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 2b8d108d..271a5c38 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -489,6 +489,20 @@ void VK_LightsNewMap( void ) { clusterBitMapInit(); prepareSurfacesLeafVisibilityCache(); + + // Load RAD data based on map name + memset(g_lights.map.emissive_textures, 0, sizeof(g_lights.map.emissive_textures)); + loadRadData( map, "maps/lights.rad" ); + + { + int name_len = Q_strlen(map->name); + + // Strip ".bsp" suffix + if (name_len > 4 && 0 == Q_stricmp(map->name + name_len - 4, ".bsp")) + name_len -= 4; + + loadRadData( map, "%.*s.rad", name_len, map->name ); + } } void VK_LightsFrameInit( void ) { @@ -1019,20 +1033,6 @@ void VK_LightsLoadMapStaticLights( void ) { processStaticPointLights(); - // Load RAD data based on map name - memset(g_lights.map.emissive_textures, 0, sizeof(g_lights.map.emissive_textures)); - loadRadData( map, "maps/lights.rad" ); - - { - int name_len = Q_strlen(map->name); - - // Strip ".bsp" suffix - if (name_len > 4 && 0 == Q_stricmp(map->name + name_len - 4, ".bsp")) - name_len -= 4; - - loadRadData( map, "%.*s.rad", name_len, map->name ); - } - // Load static map model { matrix3x4 xform; @@ -1060,7 +1060,7 @@ void VK_LightsLoadMapStaticLights( void ) { } } -void XVK_GetEmissiveForTexture( vec3_t out, int texture_id ) { +qboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id ) { ASSERT(texture_id >= 0); ASSERT(texture_id < MAX_TEXTURES); @@ -1068,8 +1068,9 @@ void XVK_GetEmissiveForTexture( vec3_t out, int texture_id ) { vk_emissive_texture_t *const etex = g_lights.map.emissive_textures + texture_id; if (etex->set) { VectorCopy(etex->emissive, out); + return true; } else { - VectorSet(out, 0, 0, 0); + return false; } } } @@ -1158,3 +1159,18 @@ void VK_LightsFrameFinalize( void ) { debug_dump_lights.enabled = false; APROF_SCOPE_END(finalize); } + +int RT_LightAddPolygon(const rt_light_polygon_t *lpoly) { + gEngine.Con_Reportf("RT_LightAddPolygon center=(%f, %f, %f) normal=(%f, %f, %f) area=%f num_vertices=%d\n", + lpoly->center[0], + lpoly->center[1], + lpoly->center[2], + lpoly->normal[0], + lpoly->normal[1], + lpoly->normal[2], + lpoly->area, + lpoly->num_vertices + ); + + return -1; +} diff --git a/ref_vk/vk_light.h b/ref_vk/vk_light.h index d61aa045..a8b4a538 100644 --- a/ref_vk/vk_light.h +++ b/ref_vk/vk_light.h @@ -30,6 +30,16 @@ typedef struct { matrix3x4 transform; } vk_emissive_surface_t; +typedef struct { + vec3_t emissive; + vec3_t normal; + vec3_t center; + float area; + int num_vertices; + vec3_t vertices[7]; + // uint32_t kusok_index; +} rt_light_polygon_t; + enum { LightFlag_Environment = 0x1, }; @@ -57,9 +67,14 @@ typedef struct { vk_emissive_texture_t emissive_textures[MAX_TEXTURES]; } map; + // FIXME deprecate int num_emissive_surfaces; vk_emissive_surface_t emissive_surfaces[MAX_SURFACE_LIGHTS]; + int num_light_polygons; + rt_light_polygon_t light_polygons[MAX_SURFACE_LIGHTS]; + + int num_point_lights; vk_point_light_t point_lights[MAX_POINT_LIGHTS]; @@ -87,7 +102,9 @@ void VK_LightsFrameInit( void ); // separately in emissive surfaces. struct vk_render_geometry_s; void VK_LightsAddEmissiveSurface( const struct vk_render_geometry_s *geom, const matrix3x4 *transform_row, qboolean static_map ); -void XVK_GetEmissiveForTexture( vec3_t out, int texture_id ); +qboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id ); + +int RT_LightAddPolygon(const rt_light_polygon_t *light); void VK_LightsFrameFinalize( void ); diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index 1216c612..5798f7dd 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -400,7 +400,7 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render if (geom->material == kXVkMaterialEmissive) { VectorCopy( geom->emissive, kusok->emissive ); } else { - XVK_GetEmissiveForTexture( kusok->emissive, geom->texture ); + RT_GetEmissiveForTexture( kusok->emissive, geom->texture ); } if (geom->material == kXVkMaterialConveyor) { diff --git a/ref_vk/vk_scene.c b/ref_vk/vk_scene.c index ac68bcbb..4fa1fe3c 100644 --- a/ref_vk/vk_scene.c +++ b/ref_vk/vk_scene.c @@ -142,8 +142,6 @@ void R_NewMap( void ) // This is to ensure that we have computed lightstyles properly VK_RunLightStyles(); - VK_LightsNewMap(); - XVK_SetupSky( gEngine.pfnGetMoveVars()->skyName ); if (vk_core.rtx) @@ -167,9 +165,12 @@ void R_NewMap( void ) XVK_ReloadMaterials(); // Parse patch data - // Depens on loaded materials. Must preceed loading brush models. + // Depends on loaded materials. Must preceed loading brush models. XVK_ParseMapPatches(); + // Need parsed map entities, and also should happen before brush model loading + VK_LightsNewMap(); + // Load all models at once gEngine.Con_Reportf( "Num models: %d:\n", num_models ); for( int i = 0; i < num_models; i++ ) From 6e4ab74694961289a5351d35caecc8a17262c262 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 14 Jan 2022 14:33:13 +0300 Subject: [PATCH 084/548] engine: net_encode: split delta "no changes" copy into separate function --- engine/common/net_encode.c | 188 ++++++++++++++++++------------------- 1 file changed, 89 insertions(+), 99 deletions(-) diff --git a/engine/common/net_encode.c b/engine/common/net_encode.c index d6f854d3..10eb1920 100644 --- a/engine/common/net_encode.c +++ b/engine/common/net_encode.c @@ -1114,7 +1114,7 @@ assume from and to is valid qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, void *to, double timebase ) { qboolean bSigned = ( pField->flags & DT_SIGNED ) ? true : false; - float flValue, flAngle, flTime; + float flValue, flAngle; uint iValue; const char *pStr; @@ -1204,6 +1204,53 @@ qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, void *to return true; } +/* +==================== +Delta_CopyField + +==================== +*/ +static void Delta_CopyField( delta_t *pField, void *from, void *to, double timebase ) +{ + qboolean bSigned = FBitSet( pField->flags, DT_SIGNED ); + uint8_t *to_field = (uint8_t *)to + pField->offset; + uint8_t *from_field = (uint8_t *)from + pField->offset; + + if( FBitSet( pField->flags, DT_BYTE )) + { + if( bSigned ) + *(int8_t *)( to_field ) = *(int8_t *)( from_field ); + else + *(uint8_t *)( to_field ) = *(uint8_t *)( from_field ); + } + else if( FBitSet( pField->flags, DT_SHORT )) + { + if( bSigned ) + *(int16_t *)( to_field ) = *(int16_t *)( from_field ); + else + *(uint16_t *)( to_field ) = *(uint16_t *)( from_field ); + } + else if( FBitSet( pField->flags, DT_INTEGER )) + { + if( bSigned ) + *(int32_t *)( to_field ) = *(int32_t *)( from_field ); + else + *(uint32_t *)( to_field ) = *(uint32_t *)( from_field ); + } + else if( FBitSet( pField->flags, DT_FLOAT|DT_ANGLE|DT_TIMEWINDOW_8|DT_TIMEWINDOW_BIG )) + { + *(float *)( to_field ) = *(float *)( from_field ); + } + else if( FBitSet( pField->flags, DT_STRING )) + { + Q_strncpy( to_field, from_field, pField->size ); + } + else + { + Assert( 0 ); + } +} + /* ===================== Delta_ReadField @@ -1216,30 +1263,24 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, { qboolean bSigned = ( pField->flags & DT_SIGNED ) ? true : false; float flValue, flAngle, flTime; - qboolean bChanged; uint iValue; const char *pStr; char *pOut; - bChanged = MSG_ReadOneBit( msg ); + if( !MSG_ReadOneBit( msg ) ) + { + Delta_CopyField( pField, from, to, timebase ); + return false; + } Assert( pField->multiplier != 0.0f ); if( pField->flags & DT_BYTE ) { - if( bChanged ) - { - iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - if( !Q_equal( pField->multiplier, 1.0 ) ) - iValue /= pField->multiplier; - } - else - { - if( bSigned ) - iValue = *(int8_t *)((uint8_t *)from + pField->offset ); - else - iValue = *(uint8_t *)((uint8_t *)from + pField->offset ); - } + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + if( !Q_equal( pField->multiplier, 1.0 ) ) + iValue /= pField->multiplier; + if( bSigned ) *(int8_t *)((uint8_t *)to + pField->offset ) = iValue; else @@ -1247,19 +1288,10 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, } else if( pField->flags & DT_SHORT ) { - if( bChanged ) - { - iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - if( !Q_equal( pField->multiplier, 1.0 ) ) - iValue /= pField->multiplier; - } - else - { - if( bSigned ) - iValue = *(int16_t *)((uint8_t *)from + pField->offset ); - else - iValue = *(uint16_t *)((uint8_t *)from + pField->offset ); - } + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + if( !Q_equal( pField->multiplier, 1.0 ) ) + iValue /= pField->multiplier; + if( bSigned ) *(int16_t *)((uint8_t *)to + pField->offset ) = iValue; else @@ -1267,19 +1299,10 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, } else if( pField->flags & DT_INTEGER ) { - if( bChanged ) - { - iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - if( !Q_equal( pField->multiplier, 1.0 ) ) - iValue /= pField->multiplier; - } - else - { - if( bSigned ) - iValue = *(int32_t *)((uint8_t *)from + pField->offset ); - else - iValue = *(uint32_t *)((uint8_t *)from + pField->offset ); - } + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + if( !Q_equal( pField->multiplier, 1.0 ) ) + iValue /= pField->multiplier; + if( bSigned ) *(int32_t *)((uint8_t *)to + pField->offset ) = iValue; else @@ -1287,85 +1310,52 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, } else if( pField->flags & DT_FLOAT ) { - if( bChanged ) - { - iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - if( bSigned ) - flValue = (int)iValue; - else - flValue = iValue; - - if( !Q_equal( pField->multiplier, 1.0 ) ) - flValue = flValue / pField->multiplier; - - if( !Q_equal( pField->post_multiplier, 1.0 ) ) - flValue = flValue * pField->post_multiplier; - } + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + if( bSigned ) + flValue = (int)iValue; else - { - flValue = *(float *)((byte *)from + pField->offset ); - } + flValue = iValue; + + if( !Q_equal( pField->multiplier, 1.0 ) ) + flValue = flValue / pField->multiplier; + + if( !Q_equal( pField->post_multiplier, 1.0 ) ) + flValue = flValue * pField->post_multiplier; + *(float *)((byte *)to + pField->offset ) = flValue; } else if( pField->flags & DT_ANGLE ) { - if( bChanged ) - { - flAngle = MSG_ReadBitAngle( msg, pField->bits ); - } - else - { - flAngle = *(float *)((byte *)from + pField->offset ); - } + flAngle = MSG_ReadBitAngle( msg, pField->bits ); *(float *)((byte *)to + pField->offset ) = flAngle; } else if( pField->flags & DT_TIMEWINDOW_8 ) { - if( bChanged ) - { - bSigned = true; // timewindow is always signed - iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - flTime = (timebase * 100.0 - iValue) / 100.0; - } - else - { - flTime = *(float *)((byte *)from + pField->offset ); - } + bSigned = true; // timewindow is always signed + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + flTime = (timebase * 100.0 - iValue) / 100.0; + *(float *)((byte *)to + pField->offset ) = flTime; } else if( pField->flags & DT_TIMEWINDOW_BIG ) { - if( bChanged ) - { - bSigned = true; // timewindow is always signed - iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + bSigned = true; // timewindow is always signed + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - if( !Q_equal( pField->multiplier, 1.0 ) ) - flTime = ( timebase * pField->multiplier - iValue ) / pField->multiplier; - else - flTime = timebase - iValue; - } + if( !Q_equal( pField->multiplier, 1.0 ) ) + flTime = ( timebase * pField->multiplier - iValue ) / pField->multiplier; else - { - flTime = *(float *)((byte *)from + pField->offset ); - } + flTime = timebase - iValue; + *(float *)((byte *)to + pField->offset ) = flTime; } else if( pField->flags & DT_STRING ) { - if( bChanged ) - { - pStr = MSG_ReadString( msg ); - } - else - { - pStr = (char *)((byte *)from + pField->offset ); - } - + pStr = MSG_ReadString( msg ); pOut = (char *)((byte *)to + pField->offset ); Q_strncpy( pOut, pStr, pField->size ); } - return bChanged; + return true; } /* From ea5937bce0541fdf2f10995f122f5b4ab4011a54 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 14 Jan 2022 14:46:52 +0300 Subject: [PATCH 085/548] engine: net_encode: remove naive clientdata/weapondata copy, only copy fields explicitly listed in deltalst --- engine/common/net_encode.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/engine/common/net_encode.c b/engine/common/net_encode.c index 10eb1920..04aa323d 100644 --- a/engine/common/net_encode.c +++ b/engine/common/net_encode.c @@ -1605,6 +1605,7 @@ void MSG_ReadClientData( sizebuf_t *msg, clientdata_t *from, clientdata_t *to, d delta_t *pField; delta_info_t *dt; int i; + qboolean noChanges; dt = Delta_FindStruct( "clientdata_t" ); Assert( dt && dt->bInitialized ); @@ -1612,15 +1613,14 @@ void MSG_ReadClientData( sizebuf_t *msg, clientdata_t *from, clientdata_t *to, d pField = dt->pFields; Assert( pField != NULL ); - *to = *from; - - if( !cls.legacymode && !MSG_ReadOneBit( msg )) - return; // we have no changes + noChanges = !cls.legacymode && !MSG_ReadOneBit( msg ); // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { - Delta_ReadField( msg, pField, from, to, timebase ); + if( noChanges ) + Delta_CopyField( pField, from, to, timebase ); + else Delta_ReadField( msg, pField, from, to, timebase ); } #endif } @@ -1691,8 +1691,6 @@ void MSG_ReadWeaponData( sizebuf_t *msg, weapon_data_t *from, weapon_data_t *to, pField = dt->pFields; Assert( pField != NULL ); - *to = *from; - // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { From 2c1b862654a92b7c6b2c98cf4be67a0c46461818 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 14 Jan 2022 16:16:13 +0300 Subject: [PATCH 086/548] engine: client: ignore EF_BRIGHTFIELD for local client It must be OK, because GoldSrc don't call similar function for local client instead adds only flashlight and muzzleflash effects. By adding this check, behavior must be close enough --- engine/client/cl_tent.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c index 89d74aa5..dab743d6 100644 --- a/engine/client/cl_tent.c +++ b/engine/client/cl_tent.c @@ -2693,7 +2693,7 @@ apply various effects to entity origin or attachment void CL_AddEntityEffects( cl_entity_t *ent ) { // yellow flies effect 'monster stuck in the wall' - if( FBitSet( ent->curstate.effects, EF_BRIGHTFIELD )) + if( FBitSet( ent->curstate.effects, EF_BRIGHTFIELD ) && !RP_LOCALCLIENT( ent )) R_EntityParticles( ent ); if( FBitSet( ent->curstate.effects, EF_DIMLIGHT )) From df4f21680c40edccbf293dc7d6a0be79bb3ed9c0 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 14 Jan 2022 16:18:09 +0300 Subject: [PATCH 087/548] engine: client: apply local client effects only if it wasn't rejected by clientdll --- engine/client/cl_frame.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index 7582cfdf..2bd49c16 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -978,8 +978,13 @@ qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType ) } // don't add the player in firstperson mode - if( RP_LOCALCLIENT( ent ) && !CL_IsThirdPerson( ) && ( ent->index == cl.viewentity )) - return false; + if( RP_LOCALCLIENT( ent )) + { + cl.local.apply_effects = true; + + if( !CL_IsThirdPerson( ) && ( ent->index == cl.viewentity )) + return false; + } if( entityType == ET_BEAM ) { @@ -993,12 +998,8 @@ qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType ) // because pTemp->entity.curstate.effects // is already occupied by FTENT_FLICKER - if( entityType != ET_TEMPENTITY ) + if( entityType != ET_TEMPENTITY && !RP_LOCALCLIENT( ent ) ) { - // no reason to do it twice - if( RP_LOCALCLIENT( ent )) - cl.local.apply_effects = false; - // apply client-side effects CL_AddEntityEffects( ent ); @@ -1050,7 +1051,6 @@ void CL_LinkPlayers( frame_t *frame ) // apply muzzleflash to weaponmodel if( ent && FBitSet( ent->curstate.effects, EF_MUZZLEFLASH )) SetBits( clgame.viewent.curstate.effects, EF_MUZZLEFLASH ); - cl.local.apply_effects = true; // check all the clients but add only visible for( i = 0, state = frame->playerstate; i < MAX_CLIENTS; i++, state++ ) From 204544f50f89db83bede49e2bf6d67b678595027 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 15 Jan 2022 04:48:04 +0300 Subject: [PATCH 088/548] readme: update --- README.md | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 4b1e55ab..c20ba7ad 100644 --- a/README.md +++ b/README.md @@ -27,16 +27,15 @@ Read more about Xash3D on ModDB: https://www.moddb.com/engines/xash3d-engine * Vulkan renderer ## Installation & Running -0) Get Xash3D binaries: you can use [testing](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous) build or you can compile engine from source code. -Choose proper build package depending on which platform you're using. +0) Get Xash3D FWGS binaries: you can use [testing](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous) build or you can compile engine from source code. 1) Copy engine binaries to some directory. 2) Copy `valve` directory from [Half-Life](https://store.steampowered.com/app/70/HalfLife/) to directory with engine binaries. -Also if you're using Windows: you should copy `vgui.dll` library from Half-Life directory to Xash3D directory. -As alternative, you can compile [hlsdk-xash3d](https://github.com/FWGS/hlsdk-xash3d) instead of using official Valve game binaries, but you still needed to copy `valve` directory as all game resources located in there. -3) Download [extras.pak](https://github.com/FWGS/xash-extras/releases/tag/v0.19.2) and place it to `valve` directory. -4) Run `xash3d.exe`/`xash3d.sh`/`xash3d` depending on which platform you're using. +If your CPU is NOT x86 compatible or you're running 64-bit version of the engine, you may want to compile [Half-Life SDK](https://github.com/FWGS/hlsdk-xash3d). +This repository contains our fork of HLSDK and restored source code for some of the mods. Not all of them, of course. +You still needed to copy `valve` directory as all game resources located there. +3) Run the main executable (`xash3d.exe` or AppImage). -Note: on Linux, you may need to create an sh file with the command `LD_LIBRARY_PATH=. ./xash3d`. +Note: on Linux, without AppImage, you may need to create a shell-script with the command `LD_LIBRARY_PATH=. ./xash3d`. For additional info, run Xash3D with `-help` command line key. @@ -51,6 +50,13 @@ We are using Waf build system. If you have some Waf-related questions, I recomme NOTE: NEVER USE GitHub's ZIP ARCHIVES. GitHub doesn't include external dependencies we're using! ### Prerequisites + +If your CPU is x86 compatible, we are building 32-bit code by default. This was dont for keeping compatibility with Steam releases of Half-Life and based on it's engine games. +Even if Xash3D FWGS does support targetting 64-bit, you can't load games without recompiling them from source code! + +If your CPU is NOT x86 compatible or you decided build 64-bit version of engine, you may want to compile [Half-Life SDK](https://github.com/FWGS/hlsdk-xash3d). +This repository contains our fork of HLSDK and restored source code for some of the mods. Not all of them, of course. + #### Windows (Visual Studio) * Install Visual Studio. * Install latest [Python](https://python.org) **OR** run `cinst python.install` if you have Chocolatey. @@ -60,10 +66,6 @@ NOTE: NEVER USE GitHub's ZIP ARCHIVES. GitHub doesn't include external dependenc * Make sure you have at least 12GB of free space to store all build-time dependencies: ~10GB for Visual Studio, 300 MB for Git, 100 MB for Python and other. #### GNU/Linux -NOTE FOR USERS WITH X86 COMPATIBLE CPUs: -We have forced Waf to throw an error, if you're trying to build 64-bit engine. This was done for keeping compatibility with Steam releases of Half-Life and based on it's engine games. -Even if Xash3D FWGS does support targetting 64-bit, you can't load games without recompiling them from source code! - ##### Debian/Ubuntu * Enable i386 on your system, if you're compiling 32-bit engine on amd64. If not, skip this @@ -86,11 +88,10 @@ Even if Xash3D FWGS does support targetting 64-bit, you can't load games without 5) Install: `waf install --destdir=c:/path/to/any/output/directory` #### Linux +If compiling 32-bit on amd64, you may need to supply `export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig` prior to running configure. + 0) Examine which build options are available: `./waf --help` 1) Configure build: `./waf configure -T release` (You need to pass `-8` to compile 64-bit engine on 64-bit x86 processor) 2) Compile: `./waf build` 3) Install(optional): `./waf install --destdir=/path/to/any/output/directory` - -Note: if compiling 32-bit on amd64, you may need to supply `export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig` prior to running configure. - From 5aa6bfee85377a6919fb314d3e3758bb786b64b4 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 15 Jan 2022 06:24:57 +0300 Subject: [PATCH 089/548] engine: first attempts on fuzzing the engine --- engine/common/imagelib/img_main.c | 21 ++++++++++++++++ engine/common/soundlib/snd_main.c | 22 +++++++++++++++++ engine/common/tests.h | 17 +++++++------ engine/wscript | 7 ++++++ utils/run-fuzzer/run-fuzzer.c | 34 ++++++++++++++++++++++++++ utils/run-fuzzer/wscript | 40 +++++++++++++++++++++++++++++++ wscript | 13 ++++++++-- 7 files changed, 143 insertions(+), 11 deletions(-) create mode 100644 utils/run-fuzzer/run-fuzzer.c create mode 100644 utils/run-fuzzer/wscript diff --git a/engine/common/imagelib/img_main.c b/engine/common/imagelib/img_main.c index 70e67ce1..1b99e0c0 100644 --- a/engine/common/imagelib/img_main.c +++ b/engine/common/imagelib/img_main.c @@ -566,4 +566,25 @@ void Test_RunImagelib( void ) Z_Free( rgb.buffer ); } +#define IMPLEMENT_IMAGELIB_FUZZ_TARGET( export, target ) \ +int EXPORT export( const uint8_t *Data, size_t Size ) \ +{ \ + rgbdata_t *rgb; \ + host.type = HOST_NORMAL; \ + Memory_Init(); \ + Image_Init(); \ + if( target( "#internal", Data, Size )) \ + { \ + rgb = ImagePack(); \ + FS_FreeImage( rgb ); \ + } \ + Image_Shutdown(); \ + return 0; \ +} \ + +IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadBMP, Image_LoadBMP ) +IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadPNG, Image_LoadPNG ) +IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadDDS, Image_LoadDDS ) +IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadTGA, Image_LoadTGA ) + #endif /* XASH_ENGINE_TESTS */ diff --git a/engine/common/soundlib/snd_main.c b/engine/common/soundlib/snd_main.c index 265395a5..94b4ff9d 100644 --- a/engine/common/soundlib/snd_main.c +++ b/engine/common/soundlib/snd_main.c @@ -278,3 +278,25 @@ void FS_FreeStream( stream_t *stream ) stream->format->freefunc( stream ); } + +#if XASH_ENGINE_TESTS + +#define IMPLEMENT_SOUNDLIB_FUZZ_TARGET( export, target ) \ +int EXPORT export( const uint8_t *Data, size_t Size ) \ +{ \ + wavdata_t *wav; \ + host.type = HOST_NORMAL; \ + Memory_Init(); \ + Sound_Init(); \ + if( target( "#internal", Data, Size )) \ + { \ + wav = SoundPack(); \ + FS_FreeSound( wav ); \ + } \ + Sound_Shutdown(); \ + return 0; \ +} \ + +IMPLEMENT_SOUNDLIB_FUZZ_TARGET( Fuzz_Sound_LoadMPG, Sound_LoadMPG ) +IMPLEMENT_SOUNDLIB_FUZZ_TARGET( Fuzz_Sound_LoadWAV, Sound_LoadWAV ) +#endif diff --git a/engine/common/tests.h b/engine/common/tests.h index d4166974..3ff6e030 100644 --- a/engine/common/tests.h +++ b/engine/common/tests.h @@ -15,21 +15,20 @@ extern struct tests_stats_s tests_stats; x; \ Msg( "Finished " #x "\n" ) -#define TASSERT( exp ) \ - if(!( exp )) \ +#define _TASSERT( exp, msg ) \ + if( exp ) \ { \ tests_stats.failed++; \ - Msg( S_ERROR "assert failed at %s:%i\n", __FILE__, __LINE__ ); \ + msg; \ } \ else tests_stats.passed++; +#define TASSERT( exp ) \ + _TASSERT( !(exp), Msg( S_ERROR "assert failed at %s:%i\n", __FILE__, __LINE__ ) ) +#define TASSERT_EQi( val1, val2 ) \ + _TASSERT( ( val1 ) != ( val2 ), Msg( S_ERROR "assert failed at %s:%i, \"%d\" != \"%d\"\n", __FILE__, __LINE__, #val1, #val2 )) #define TASSERT_STR( str1, str2 ) \ - if( Q_strcmp(( str1 ), ( str2 ))) \ - { \ - tests_stats.failed++; \ - Msg( S_ERROR "assert failed at %s:%i, \"%s\" != \"%s\"\n", __FILE__, __LINE__, ( str1 ), ( str2 )); \ - } \ - else tests_stats.passed++; + _TASSERT( Q_strcmp(( str1 ), ( str2 )), Msg( S_ERROR "assert failed at %s:%i, \"%s\" != \"%s\"\n", __FILE__, __LINE__, ( str1 ), ( str2 ))) void Test_RunImagelib( void ); void Test_RunLibCommon( void ); diff --git a/engine/wscript b/engine/wscript index cf64f7b4..c1479c45 100644 --- a/engine/wscript +++ b/engine/wscript @@ -32,6 +32,9 @@ def options(opt): grp.add_option('--enable-engine-tests', action = 'store_true', dest = 'ENGINE_TESTS', default = False, help = 'embed tests into the engine, jump into them by -runtests command line switch [default: %default]') + grp.add_option('--enable-engine-fuzz', action = 'store_true', dest = 'ENGINE_FUZZ', default = False, + help = 'add LLVM libFuzzer [default: %default]' ) + opt.load('sdl2') def configure(conf): @@ -87,6 +90,10 @@ def configure(conf): conf.env.ENGINE_TESTS = conf.options.ENGINE_TESTS + if conf.options.ENGINE_FUZZ: + conf.env.append_unique('CFLAGS', '-fsanitize=fuzzer-no-link') + conf.env.append_unique('LINKFLAGS', '-fsanitize=fuzzer') + conf.define_cond('XASH_ENGINE_TESTS', conf.env.ENGINE_TESTS) conf.define_cond('XASH_STATIC_LIBS', conf.env.STATIC_LINKING) conf.define_cond('XASH_CUSTOM_SWAP', conf.options.CUSTOM_SWAP) diff --git a/utils/run-fuzzer/run-fuzzer.c b/utils/run-fuzzer/run-fuzzer.c new file mode 100644 index 00000000..d0bfae0b --- /dev/null +++ b/utils/run-fuzzer/run-fuzzer.c @@ -0,0 +1,34 @@ +#include +#include +#include + +#if !defined LIB || !defined FUNC +#error +#endif + +typedef int (*FuzzFunc)(const char *Data, size_t Size); + +void *handle = NULL; +FuzzFunc f = NULL; + +int LLVMFuzzerTestOneInput( const char *Data, size_t Size ) +{ + if( !handle ) + handle = dlopen( LIB, RTLD_NOW ); + + if( handle ) + { + if( !f ) + f = dlsym( handle, FUNC ); + + if( f ) + { + return f( Data, Size ); + } + } + + fprintf( stderr, "Fail: %s\n", dlerror() ); + + abort(); + return 0; +} diff --git a/utils/run-fuzzer/wscript b/utils/run-fuzzer/wscript new file mode 100644 index 00000000..11174c8a --- /dev/null +++ b/utils/run-fuzzer/wscript @@ -0,0 +1,40 @@ +#! /usr/bin/env python +# encoding: utf-8 +# a1batross, mittorn, 2018 + +def options(opt): + pass + +def configure(conf): + if conf.options.BUILD_TYPE != 'sanitize': + conf.fatal('useless without -T sanitize') + + if conf.env.COMPILER_CC != 'clang': + conf.fatal('only clang is supported') + + conf.env.append_unique('CFLAGS', '-fsanitize=fuzzer') + conf.env.append_unique('LINKFLAGS', '-fsanitize=fuzzer') + +def add_runner_target(bld, lib, func): + source = bld.path.ant_glob('*.c') + includes = '.' + libs = [ 'DL' ] + + bld( + source = source, + target = 'run-fuzzer-' + func, + features = 'c cprogram', + includes = includes, + use = libs, + defines = ['FUNC="Fuzz_' + func + '"', 'LIB="' + lib + '"'], + install_path = bld.env.BINDIR, + subsystem = bld.env.CONSOLE_SUBSYSTEM + ) + +def build(bld): + add_runner_target(bld, 'libxash.so', 'Sound_LoadMPG') + add_runner_target(bld, 'libxash.so', 'Sound_LoadWAV') + add_runner_target(bld, 'libxash.so', 'Image_LoadBMP') + add_runner_target(bld, 'libxash.so', 'Image_LoadPNG') + add_runner_target(bld, 'libxash.so', 'Image_LoadDDS') + add_runner_target(bld, 'libxash.so', 'Image_LoadTGA') diff --git a/wscript b/wscript index e9ba2d90..8879fbeb 100644 --- a/wscript +++ b/wscript @@ -20,12 +20,13 @@ class Subproject: ignore = False # if true will be ignored, set by user request mandatory = False - def __init__(self, name, dedicated=True, singlebin=False, mandatory = False, utility = False): + def __init__(self, name, dedicated=True, singlebin=False, mandatory = False, utility = False, fuzzer = False): self.name = name self.dedicated = dedicated self.singlebin = singlebin self.mandatory = mandatory self.utility = utility + self.fuzzer = fuzzer def is_enabled(self, ctx): if not self.mandatory: @@ -47,6 +48,9 @@ class Subproject: if self.utility and not ctx.env.ENABLE_UTILS: return False + if self.fuzzer and not ctx.env.ENABLE_FUZZER: + return False + return True SUBDIRS = [ @@ -60,7 +64,8 @@ SUBDIRS = [ Subproject('stub/client'), Subproject('dllemu'), Subproject('engine', dedicated=False), - Subproject('utils/mdldec', utility=True) + Subproject('utils/mdldec', utility=True), + Subproject('utils/run-fuzzer', fuzzer=True) ] def subdirs(): @@ -98,6 +103,9 @@ def options(opt): grp.add_option('--enable-utils', action = 'store_true', dest = 'ENABLE_UTILS', default = False, help = 'enable building various development utilities [default: %default]') + grp.add_option('--enable-fuzzer', action = 'store_true', dest = 'ENABLE_FUZZER', default = False, + help = 'enable building libFuzzer runner [default: %default]' ) + opt.load('compiler_optimizations subproject') for i in SUBDIRS: @@ -245,6 +253,7 @@ def configure(conf): conf.define('STDINT_H', 'pstdint.h') conf.env.ENABLE_UTILS = conf.options.ENABLE_UTILS + conf.env.ENABLE_FUZZER = conf.options.ENABLE_FUZZER conf.env.DEDICATED = conf.options.DEDICATED conf.env.SINGLE_BINARY = conf.options.SINGLE_BINARY or conf.env.DEDICATED From 04a201c40119a92c2a2c20cbbdeebf10f6a7c6e7 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 14 Jan 2022 23:48:57 -0800 Subject: [PATCH 090/548] rt: pass new polygon lights to shader naively also start refactoring light collection broken: - reloading lights after patching - wagonchik lights (attached to non-static models) missing: - clustering the new poly lights - proper sampling, only rough estimate for now - shadows probably a lot more --- ref_vk/infotool.c | 12 +-- ref_vk/shaders/light.glsl | 2 +- ref_vk/shaders/light_polygon.glsl | 27 ++++++ ref_vk/shaders/ray_interop.h | 19 +++- ref_vk/vk_brush.c | 46 ++------- ref_vk/vk_common.h | 2 + ref_vk/vk_light.c | 149 +++++++++++++++++++----------- ref_vk/vk_light.h | 70 ++++++++------ ref_vk/vk_ray_model.c | 4 +- ref_vk/vk_rtx.c | 39 +++++--- ref_vk/vk_scene.c | 15 +-- 11 files changed, 230 insertions(+), 155 deletions(-) diff --git a/ref_vk/infotool.c b/ref_vk/infotool.c index 5d486596..a7d61683 100644 --- a/ref_vk/infotool.c +++ b/ref_vk/infotool.c @@ -102,7 +102,7 @@ void XVK_CameraDebugPrintCenterEntity( void ) { const vk_lights_cell_t *cell = (cell_index >= 0 && cell_index < MAX_LIGHT_CLUSTERS) ? g_lights.cells + cell_index : NULL; p += Q_snprintf(p, end - p, - "light raw=(%d, %d, %d) cell=(%d, %d, %d) index=%d surf=%d point=%d\n", + "light raw=(%d, %d, %d) cell=(%d, %d, %d) index=%d poly=%d point=%d\n", cell_raw[0], cell_raw[1], cell_raw[2], @@ -110,13 +110,13 @@ void XVK_CameraDebugPrintCenterEntity( void ) { light_cell[1], light_cell[2], cell_index, - cell ? cell->num_emissive_surfaces : -1, + cell ? cell->num_polygons : -1, cell ? cell->num_point_lights : -1); - if (cell && cell->num_emissive_surfaces > 0) { - p += Q_snprintf(p, end - p, "surf:"); - for (int i = 0; i < cell->num_emissive_surfaces; ++i) { - p += Q_snprintf(p, end - p, " %d", cell->emissive_surfaces[i]); + if (cell && cell->num_polygons > 0) { + p += Q_snprintf(p, end - p, "poly:"); + for (int i = 0; i < cell->num_polygons; ++i) { + p += Q_snprintf(p, end - p, " %d", cell->polygons[i]); } p += Q_snprintf(p, end - p, "\n"); } diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index 24533649..d3827b0d 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -1,7 +1,7 @@ layout (constant_id = 4) const float LIGHT_GRID_CELL_SIZE = 128.;// FIXME 256.; layout (constant_id = 5) const uint MAX_LIGHT_CLUSTERS = 262144;// FIXME 32768; layout (constant_id = 6) const uint MAX_TEXTURES = 4096; -layout (set = 0, binding = BINDING_LIGHTS) uniform UBOLights { Lights lights; }; +layout (set = 0, binding = BINDING_LIGHTS) uniform UBOLights { Lights lights; }; // TODO this is pretty much static and should be a buffer, not UBO layout (set = 0, binding = BINDING_LIGHT_CLUSTERS, align = 1) readonly buffer UBOLightClusters { ivec3 grid_min, grid_size; //uint8_t clusters_data[MAX_LIGHT_CLUSTERS * LIGHT_CLUSTER_SIZE + HACK_OFFSET]; diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 0e76630b..bbea2ad8 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -19,6 +19,7 @@ SampleContext buildSampleContext(vec3 position, vec3 normal, vec3 view_dir) { return ctx; } +#if 0 void sampleEmissiveSurface(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, SampleContext ctx, uint ekusok_index, out vec3 out_diffuse, out vec3 out_specular) { out_diffuse = out_specular = vec3(0.); @@ -185,3 +186,29 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate specular += lspecular * sampling_light_scale; } // for all emissive kusochki } +#endif + +// FIXME this is a quick test that reading new lights is possible +void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { + + //diffuse = vec3(fract(float(lights.num_polygons) / 10.)); return; + for (uint i = 0; i < lights.num_polygons; ++i) { + const PolygonLight poly = lights.polygons[i]; + + /* diffuse += poly.emissive; */ + /* continue; */ + + const vec3 dir = poly.center - P; + const vec3 light_dir = normalize(dir); + const float contrib_estimate = dot(light_dir, poly.normal_area) / dot(dir, dir); + + if (contrib_estimate < 1e-6) + continue; + + //const float area = 1.f / length(poly.normal_area); + vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); + evalSplitBRDF(N, light_dir, view_dir, material, poly_diffuse, poly_specular); + diffuse += throughput * poly.emissive * contrib_estimate; + specular += throughput * poly.emissive * contrib_estimate; + } +} diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index d77d7226..1d68b21d 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -20,7 +20,7 @@ #define PAD(x) #define STRUCT -layout (constant_id = 0) const uint MAX_POINT_LIGHTS = 32; +layout (constant_id = 0) const uint MAX_POINT_LIGHTS = 256; layout (constant_id = 1) const uint MAX_EMISSIVE_KUSOCHKI = 256; layout (constant_id = 2) const uint MAX_VISIBLE_POINT_LIGHTS = 63; layout (constant_id = 3) const uint MAX_VISIBLE_SURFACE_LIGHTS = 255; @@ -82,12 +82,25 @@ struct EmissiveKusok { vec4 tx_row_x, tx_row_y, tx_row_z; }; +struct PolygonLight { + vec3 normal_area; + uint vertices_begin; + + vec3 center; + uint vertices_count; + + vec3 emissive; + PAD(1) +}; + struct Lights { - uint num_kusochki; + uint num_polygons; uint num_point_lights; PAD(2) - STRUCT EmissiveKusok kusochki[MAX_EMISSIVE_KUSOCHKI]; + //STRUCT EmissiveKusok kusochki[MAX_EMISSIVE_KUSOCHKI]; STRUCT PointLight point_lights[MAX_POINT_LIGHTS]; + STRUCT PolygonLight polygons[MAX_EMISSIVE_KUSOCHKI]; + vec3 polygon_vertices[MAX_EMISSIVE_KUSOCHKI * 7]; }; struct LightCluster { diff --git a/ref_vk/vk_brush.c b/ref_vk/vk_brush.c index 34d987c2..30b69923 100644 --- a/ref_vk/vk_brush.c +++ b/ref_vk/vk_brush.c @@ -439,59 +439,24 @@ static model_sizes_t computeSizes( const model_t *mod ) { } static void loadEmissiveSurface(const model_t *mod, const int surface_index, const msurface_t *surf, const vec3_t emissive) { - rt_light_polygon_t lpoly; + rt_light_add_polygon_t lpoly; + const qboolean flip = !!FBitSet( surf->flags, SURF_PLANEBACK ); lpoly.num_vertices = Q_min(7, surf->numedges); + // TODO split, don't clip if (surf->numedges > 7) gEngine.Con_Printf(S_WARN "emissive surface %d has %d vertices; clipping to 7\n", surface_index, surf->numedges); VectorCopy(emissive, lpoly.emissive); - VectorSet(lpoly.center, 0, 0, 0); - VectorSet(lpoly.normal, 0, 0, 0); - for (int i = 0; i < lpoly.num_vertices; ++i) { - const int iedge = mod->surfedges[surf->firstedge + i]; + const int index = flip ? lpoly.num_vertices - i - 1 : i; + const int iedge = mod->surfedges[surf->firstedge + index]; const medge_t *edge = mod->edges + (iedge >= 0 ? iedge : -iedge); const mvertex_t *vertex = mod->vertexes + (iedge >= 0 ? edge->v[0] : edge->v[1]); VectorCopy(vertex->position, lpoly.vertices[i]); - VectorAdd(vertex->position, lpoly.center, lpoly.center); - - if (i > 1) { - vec3_t e[2], normal; - VectorSubtract(lpoly.vertices[i-1], lpoly.vertices[i-2], e[0]); - VectorSubtract(lpoly.vertices[i-0], lpoly.vertices[i-2], e[1]); - CrossProduct(e[0], e[1], normal); - VectorAdd(normal, lpoly.normal, lpoly.normal); - } } - VectorM(1.f / lpoly.num_vertices, lpoly.center, lpoly.center); - - lpoly.area = VectorLength(lpoly.normal); - - VectorNormalize(lpoly.normal); - { - const float dot = DotProduct(lpoly.normal, surf->plane->normal); - if (fabs(dot) < (1.f - 1e-5f)) { - gEngine.Con_Reportf(S_WARN "surf=%d normal=(%f, %f, %f) computed=(%f, %f, %f) dot=%f dir=%d\n", - surf->plane->normal[0], - surf->plane->normal[1], - surf->plane->normal[2], - lpoly.normal[0], - lpoly.normal[1], - lpoly.normal[2], - dot, - !!FBitSet( surf->flags, SURF_PLANEBACK ) - ); - } - } - - if( FBitSet( surf->flags, SURF_PLANEBACK )) - VectorNegate( surf->plane->normal, lpoly.normal ); - else - VectorCopy( surf->plane->normal, lpoly.normal ); - RT_LightAddPolygon(&lpoly); } @@ -538,6 +503,7 @@ static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { if (t != tex_id) continue; + // FIXME move this to rt_light_bsp and static loading { vec3_t emissive; if (psurf && (psurf->flags & Patch_Surface_Emissive)) { diff --git a/ref_vk/vk_common.h b/ref_vk/vk_common.h index 03142e53..bd7c9966 100644 --- a/ref_vk/vk_common.h +++ b/ref_vk/vk_common.h @@ -46,5 +46,7 @@ #define ALIGN_UP(ptr, align) ((((ptr) + (align) - 1) / (align)) * (align)) +#define COUNTOF(a) (sizeof(a)/sizeof((a)[0])) + extern ref_api_t gEngine; extern ref_globals_t *gpGlobals; diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 271a5c38..93565294 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -312,7 +312,7 @@ vk_light_leaf_set_t *getMapLeafsAffectedByMapSurface( const msurface_t *surf ) { leafAccumFinalize(); - smeta->potentially_visible_leafs = (vk_light_leaf_set_t*)Mem_Malloc(vk_core.pool, sizeof(smeta->potentially_visible_leafs) + sizeof(int) * g_lights_bsp.accum.count); + smeta->potentially_visible_leafs = (vk_light_leaf_set_t*)Mem_Malloc(vk_core.pool, sizeof(smeta->potentially_visible_leafs[0]) + sizeof(int) * g_lights_bsp.accum.count); smeta->potentially_visible_leafs->num = g_lights_bsp.accum.count; for (int i = 0; i < g_lights_bsp.accum.count; ++i) { @@ -450,9 +450,7 @@ static void prepareSurfacesLeafVisibilityCache( void ) { g_lights_bsp.surfaces[i].potentially_visible_leafs = NULL; } -void VK_LightsNewMap( void ) { - const model_t *map = gEngine.pfnGetModelByIndex( 1 ); - +void RT_LightsNewMapBegin( const struct model_s *map ) { // 1. Determine map bounding box (and optimal grid size?) // map->mins, maxs vec3_t map_size, min_cell, max_cell; @@ -503,31 +501,45 @@ void VK_LightsNewMap( void ) { loadRadData( map, "%.*s.rad", name_len, map->name ); } + + // Clear static lights counts + { + g_lights.num_polygons = g_lights.num_static.polygons = 0; + g_lights.num_point_lights = g_lights.num_static.point_lights = 0; + g_lights.num_polygon_vertices = g_lights.num_static.polygon_vertices = 0; + + for (int i = 0; i < g_lights.map.grid_cells; ++i) { + vk_lights_cell_t *const cell = g_lights.cells + i; + cell->num_point_lights = cell->num_static.point_lights = 0; + cell->num_polygons = cell->num_static.polygons = 0; + } + } } -void VK_LightsFrameInit( void ) { - g_lights.num_emissive_surfaces = g_lights.num_static.emissive_surfaces; +void RT_LightsFrameInit( void ) { + g_lights.num_polygons = g_lights.num_static.polygons; g_lights.num_point_lights = g_lights.num_static.point_lights; + g_lights.num_polygon_vertices = g_lights.num_static.polygon_vertices; for (int i = 0; i < g_lights.map.grid_cells; ++i) { vk_lights_cell_t *const cell = g_lights.cells + i; + cell->num_polygons = cell->num_static.polygons; cell->num_point_lights = cell->num_static.point_lights; - cell->num_emissive_surfaces = cell->num_static.emissive_surfaces; } } static qboolean addSurfaceLightToCell( int cell_index, int emissive_surface_index ) { vk_lights_cell_t *const cluster = g_lights.cells + cell_index; - if (cluster->num_emissive_surfaces == MAX_VISIBLE_SURFACE_LIGHTS) { + if (cluster->num_polygons == MAX_VISIBLE_SURFACE_LIGHTS) { return false; } if (debug_dump_lights.enabled) { - gEngine.Con_Reportf(" adding surface light %d to cell %d (count=%d)\n", emissive_surface_index, cell_index, cluster->num_emissive_surfaces+1); + gEngine.Con_Reportf(" adding surface light %d to cell %d (count=%d)\n", emissive_surface_index, cell_index, cluster->num_polygons+1); } - cluster->emissive_surfaces[cluster->num_emissive_surfaces++] = emissive_surface_index; + cluster->polygons[cluster->num_polygons++] = emissive_surface_index; return true; } @@ -580,6 +592,7 @@ static qboolean canSurfaceLightAffectAABB(const model_t *mod, const msurface_t * return retval; } +#if 0 void VK_LightsAddEmissiveSurface( const struct vk_render_geometry_s *geom, const matrix3x4 *transform_row, qboolean static_map ) { APROF_SCOPE_BEGIN_EARLY(emissive_surface); const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); @@ -620,13 +633,13 @@ void VK_LightsAddEmissiveSurface( const struct vk_render_geometry_s *geom, const } } - if (g_lights.num_emissive_surfaces >= 256) + if (g_lights.num_polygons >= 256) goto fin; if (debug_dump_lights.enabled) { const vk_texture_t *tex = findTexture(texture_num); ASSERT(tex); - gEngine.Con_Reportf("surface light %d: %s (%f %f %f)\n", g_lights.num_emissive_surfaces, tex->name, + gEngine.Con_Reportf("surface light %d: %s (%f %f %f)\n", g_lights.num_polygons, tex->name, emissive_color[0], emissive_color[1], emissive_color[2]); @@ -636,7 +649,7 @@ void VK_LightsAddEmissiveSurface( const struct vk_render_geometry_s *geom, const const vk_light_leaf_set_t *const leafs = static_map ? getMapLeafsAffectedByMapSurface( geom->surf ) : getMapLeafsAffectedByMovingSurface( geom->surf, transform_row ); - vk_emissive_surface_t *esurf = g_lights.emissive_surfaces + g_lights.num_emissive_surfaces; + vk_emissive_surface_t *esurf = g_lights.polygons + g_lights.num_polygons; // Insert into emissive surfaces esurf->kusok_index = geom->kusok_index; @@ -697,7 +710,7 @@ void VK_LightsAddEmissiveSurface( const struct vk_render_geometry_s *geom, const if (static_map && !canSurfaceLightAffectAABB(world, geom->surf, esurf->emissive, minmaxs)) continue; - if (!addSurfaceLightToCell(cell_index, g_lights.num_emissive_surfaces)) { + if (!addSurfaceLightToCell(cell_index, g_lights.num_polygons)) { ERROR_THROTTLED(10, "Cluster %d,%d,%d(%d) ran out of emissive surfaces slots", cell[0], cell[1], cell[2], cell_index); } @@ -705,13 +718,14 @@ void VK_LightsAddEmissiveSurface( const struct vk_render_geometry_s *geom, const } } - ++g_lights.num_emissive_surfaces; + ++g_lights.num_polygons; retval = esurf; } fin: APROF_SCOPE_END(emissive_surface); } +#endif static void addLightIndexToleaf( const mleaf_t *leaf, int index ) { const int min_x = floorf(leaf->minmaxs[0] / LIGHT_GRID_CELL_SIZE); @@ -1014,25 +1028,12 @@ static void processStaticPointLights( void ) { APROF_SCOPE_END(static_lights); } -void VK_LightsLoadMapStaticLights( void ) { - const model_t *map = gEngine.pfnGetModelByIndex( 1 ); - +void RT_LightsNewMapEnd( const struct model_s *map ) { //debug_dump_lights.enabled = true; - // Clear static lights counts - { - g_lights.num_emissive_surfaces = g_lights.num_static.emissive_surfaces = 0; - g_lights.num_point_lights = g_lights.num_static.point_lights = 0; - - for (int i = 0; i < g_lights.map.grid_cells; ++i) { - vk_lights_cell_t *const cell = g_lights.cells + i; - cell->num_point_lights = cell->num_static.point_lights = 0; - cell->num_emissive_surfaces = cell->num_static.emissive_surfaces = 0; - } - } - processStaticPointLights(); +#if 0 // Load static map model { matrix3x4 xform; @@ -1046,16 +1047,18 @@ void VK_LightsLoadMapStaticLights( void ) { // TODO how to differentiate between this and non-emissive gEngine.Con_Printf(S_ERROR "Ran out of surface light slots, geom %d of %d\n", i, bmodel->render_model.num_geometries); } } +#endif // Fix static counts { - g_lights.num_static.emissive_surfaces = g_lights.num_emissive_surfaces; + g_lights.num_static.polygons = g_lights.num_polygons; g_lights.num_static.point_lights = g_lights.num_point_lights; + g_lights.num_static.polygon_vertices = g_lights.num_polygon_vertices; for (int i = 0; i < g_lights.map.grid_cells; ++i) { vk_lights_cell_t *const cell = g_lights.cells + i; cell->num_static.point_lights = cell->num_point_lights; - cell->num_static.emissive_surfaces = cell->num_emissive_surfaces; + cell->num_static.polygons = cell->num_polygons; } } } @@ -1079,9 +1082,9 @@ void VK_LightsFrameFinalize( void ) { APROF_SCOPE_BEGIN_EARLY(finalize); const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); - if (g_lights.num_emissive_surfaces > UINT8_MAX) { - ERROR_THROTTLED(10, "Too many emissive surfaces found: %d; some areas will be dark", g_lights.num_emissive_surfaces); - g_lights.num_emissive_surfaces = UINT8_MAX; + if (g_lights.num_polygons > UINT8_MAX) { + ERROR_THROTTLED(10, "Too many emissive surfaces found: %d; some areas will be dark", g_lights.num_polygons); + g_lights.num_polygons = UINT8_MAX; } /* for (int i = 0; i < MAX_ELIGHTS; ++i) { */ @@ -1115,14 +1118,14 @@ void VK_LightsFrameFinalize( void ) { if (debug_dump_lights.enabled) { #if 0 // Print light grid stats - gEngine.Con_Reportf("Emissive surfaces found: %d\n", g_lights.num_emissive_surfaces); + gEngine.Con_Reportf("Emissive surfaces found: %d\n", g_lights.num_polygons); { #define GROUPSIZE 4 int histogram[1 + (MAX_VISIBLE_SURFACE_LIGHTS + GROUPSIZE - 1) / GROUPSIZE] = {0}; for (int i = 0; i < g_lights.map.grid_cells; ++i) { const vk_lights_cell_t *cluster = g_lights.cells + i; - const int hist_index = cluster->num_emissive_surfaces ? 1 + cluster->num_emissive_surfaces / GROUPSIZE : 0; + const int hist_index = cluster->num_polygons ? 1 + cluster->num_polygons / GROUPSIZE : 0; histogram[hist_index]++; } @@ -1139,12 +1142,12 @@ void VK_LightsFrameFinalize( void ) { int num_clusters_with_lights_in_range = 0; for (int i = 0; i < g_lights.map.grid_cells; ++i) { const vk_lights_cell_t *cluster = g_lights.cells + i; - if (cluster->num_emissive_surfaces > 0) { - gEngine.Con_Reportf(" cluster %d: emissive_surfaces=%d\n", i, cluster->num_emissive_surfaces); + if (cluster->num_polygons > 0) { + gEngine.Con_Reportf(" cluster %d: polygons=%d\n", i, cluster->num_polygons); } - for (int j = 0; j < cluster->num_emissive_surfaces; ++j) { - const int index = cluster->emissive_surfaces[j]; + for (int j = 0; j < cluster->num_polygons; ++j) { + const int index = cluster->polygons[j]; if (index >= vk_rtx_light_begin->value && index < vk_rtx_light_end->value) { ++num_clusters_with_lights_in_range; } @@ -1160,17 +1163,57 @@ void VK_LightsFrameFinalize( void ) { APROF_SCOPE_END(finalize); } -int RT_LightAddPolygon(const rt_light_polygon_t *lpoly) { - gEngine.Con_Reportf("RT_LightAddPolygon center=(%f, %f, %f) normal=(%f, %f, %f) area=%f num_vertices=%d\n", - lpoly->center[0], - lpoly->center[1], - lpoly->center[2], - lpoly->normal[0], - lpoly->normal[1], - lpoly->normal[2], - lpoly->area, - lpoly->num_vertices - ); +int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { + if (g_lights.num_polygons == MAX_SURFACE_LIGHTS) { + gEngine.Con_Printf(S_ERROR "Max number of polygon lights %d reached\n", MAX_SURFACE_LIGHTS); + return -1; + } - return -1; + ASSERT(addpoly->num_vertices > 2); + ASSERT(addpoly->num_vertices < 8); + ASSERT(g_lights.num_polygon_vertices + addpoly->num_vertices <= COUNTOF(g_lights.polygon_vertices)); + + { + rt_light_polygon_t *const poly = g_lights.polygons + g_lights.num_polygons; + + poly->vertices.begin = g_lights.num_polygon_vertices; + poly->vertices.count = addpoly->num_vertices; + + VectorCopy(addpoly->emissive, poly->emissive); + VectorSet(poly->center, 0, 0, 0); + VectorSet(poly->normal_area, 0, 0, 0); + + for (int i = 0; i < addpoly->num_vertices; ++i) { + VectorCopy(addpoly->vertices[i], g_lights.polygon_vertices[poly->vertices.begin + i]); + VectorAdd(addpoly->vertices[i], poly->center, poly->center); + + if (i > 1) { + vec3_t e[2], normal; + VectorSubtract(addpoly->vertices[i-1], addpoly->vertices[i-2], e[0]); + VectorSubtract(addpoly->vertices[i-0], addpoly->vertices[i-2], e[1]); + CrossProduct(e[0], e[1], normal); + VectorAdd(normal, poly->normal_area, poly->normal_area); + } + } + + VectorM(1.f / poly->vertices.count, poly->center, poly->center); + + gEngine.Con_Reportf("added polygon light index=%d color=(%f, %f, %f) center=(%f, %f, %f) normal_area=(%f, %f, %f) area=%f num_vertices=%d\n", + g_lights.num_polygons, + poly->emissive[0], + poly->emissive[1], + poly->emissive[2], + poly->center[0], + poly->center[1], + poly->center[2], + poly->normal_area[0], + poly->normal_area[1], + poly->normal_area[2], + VectorLength(poly->normal_area), + poly->vertices.count + ); + + g_lights.num_polygon_vertices += addpoly->num_vertices; + return g_lights.num_polygons++; + } } diff --git a/ref_vk/vk_light.h b/ref_vk/vk_light.h index a8b4a538..aa048dd3 100644 --- a/ref_vk/vk_light.h +++ b/ref_vk/vk_light.h @@ -3,9 +3,9 @@ #include "vk_const.h" #include "xash3d_types.h" -#include "protocol.h" -#include "const.h" -#include "bspfile.h" +//#include "protocol.h" +//#include "const.h" +//#include "bspfile.h" typedef struct { vec3_t emissive; @@ -14,29 +14,28 @@ typedef struct { typedef struct { uint8_t num_point_lights; - uint8_t num_emissive_surfaces; + uint8_t num_polygons; + uint8_t point_lights[MAX_VISIBLE_POINT_LIGHTS]; - uint8_t emissive_surfaces[MAX_VISIBLE_SURFACE_LIGHTS]; + uint8_t polygons[MAX_VISIBLE_SURFACE_LIGHTS]; struct { uint8_t point_lights; - uint8_t emissive_surfaces; + uint8_t polygons; } num_static; } vk_lights_cell_t; typedef struct { vec3_t emissive; - uint32_t kusok_index; - matrix3x4 transform; -} vk_emissive_surface_t; -typedef struct { - vec3_t emissive; - vec3_t normal; + vec3_t normal_area; vec3_t center; - float area; - int num_vertices; - vec3_t vertices[7]; + //float area; + + struct { + int begin, count; // reference g_light.polygon_vertices + } vertices; + // uint32_t kusok_index; } rt_light_polygon_t; @@ -67,20 +66,19 @@ typedef struct { vk_emissive_texture_t emissive_textures[MAX_TEXTURES]; } map; - // FIXME deprecate - int num_emissive_surfaces; - vk_emissive_surface_t emissive_surfaces[MAX_SURFACE_LIGHTS]; - - int num_light_polygons; - rt_light_polygon_t light_polygons[MAX_SURFACE_LIGHTS]; - + int num_polygons; + rt_light_polygon_t polygons[MAX_SURFACE_LIGHTS]; int num_point_lights; vk_point_light_t point_lights[MAX_POINT_LIGHTS]; + int num_polygon_vertices; + vec3_t polygon_vertices[MAX_SURFACE_LIGHTS * 7]; + struct { - int emissive_surfaces; int point_lights; + int polygons; + int polygon_vertices; } num_static; vk_lights_cell_t cells[MAX_LIGHT_CLUSTERS]; @@ -91,10 +89,11 @@ extern vk_lights_t g_lights; void VK_LightsInit( void ); void VK_LightsShutdown( void ); -void VK_LightsNewMap( void ); -void VK_LightsLoadMapStaticLights( void ); +struct model_s; +void RT_LightsNewMapBegin( const struct model_s *map ); +void RT_LightsNewMapEnd( const struct model_s *map ); -void VK_LightsFrameInit( void ); +void RT_LightsFrameInit( void ); // TODO begin // TODO there is an arguably better way to organize this. // a. this only belongs to ray tracing mode @@ -104,11 +103,24 @@ struct vk_render_geometry_s; void VK_LightsAddEmissiveSurface( const struct vk_render_geometry_s *geom, const matrix3x4 *transform_row, qboolean static_map ); qboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id ); -int RT_LightAddPolygon(const rt_light_polygon_t *light); - -void VK_LightsFrameFinalize( void ); +void VK_LightsFrameFinalize( void ); // TODO end int R_LightCellIndex( const int light_cell[3] ); struct cl_entity_s; void R_LightAddFlashlight( const struct cl_entity_s *ent, qboolean local_player ); + +struct msurface_s; +typedef struct { + int num_vertices; + vec3_t vertices[7]; + + vec3_t emissive; + + // Needed for BSP visibilty purposes + // TODO can we layer light code? like: + // - bsp/xash/rad/patch-specific stuff + // - mostly engine-agnostic light clusters + const struct msurface_s *surface; +} rt_light_add_polygon_t; +int RT_LightAddPolygon(const rt_light_add_polygon_t *light); diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index 5798f7dd..4c5ee7ad 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -369,8 +369,8 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render const xvk_material_t *const mat = XVK_GetMaterialForTextureIndex( geom->texture ); ASSERT(mat); - if (!render_model->static_map) - VK_LightsAddEmissiveSurface( geom, transform_row, false ); + /* if (!render_model->static_map) */ + /* VK_LightsAddEmissiveSurface( geom, transform_row, false ); */ kusok->tex_base_color = mat->tex_base_color; kusok->tex_roughness = mat->tex_roughness; diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 652d3a0f..efee7364 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -354,15 +354,16 @@ void VK_RayFrameBegin( void ) // TODO: move all lighting update to scene? if (g_rtx.reload_lighting) { g_rtx.reload_lighting = false; - VK_LightsLoadMapStaticLights(); + // FIXME temporarily not supported VK_LightsLoadMapStaticLights(); } // TODO shouldn't we do this in freeze models mode anyway? - VK_LightsFrameInit(); + RT_LightsFrameInit(); } static void createPipeline( void ) { + return; // ... FIXME struct RayShaderSpec { int max_point_lights; int max_emissive_kusochki; @@ -757,7 +758,7 @@ static qboolean rayTrace( VkCommandBuffer cmdbuf, const xvk_ray_frame_images_t * } // Finalize and update dynamic lights -static void uploadLights ( void ) { +static void uploadLights( void ) { // Upload light grid { vk_ray_shader_light_grid *grid = g_rtx.light_grid_buffer.mapped; @@ -770,24 +771,28 @@ static void uploadLights ( void ) { struct LightCluster *const dst = grid->cells + i; dst->num_point_lights = src->num_point_lights; - dst->num_emissive_surfaces = src->num_emissive_surfaces; + dst->num_emissive_surfaces = src->num_polygons; memcpy(dst->point_lights, src->point_lights, sizeof(uint8_t) * src->num_point_lights); - memcpy(dst->emissive_surfaces, src->emissive_surfaces, sizeof(uint8_t) * src->num_emissive_surfaces); + memcpy(dst->emissive_surfaces, src->polygons, sizeof(uint8_t) * src->num_polygons); } } // Upload dynamic emissive kusochki { struct Lights *lights = g_ray_model_state.lights_buffer.mapped; - ASSERT(g_lights.num_emissive_surfaces <= MAX_EMISSIVE_KUSOCHKI); - lights->num_kusochki = g_lights.num_emissive_surfaces; - for (int i = 0; i < g_lights.num_emissive_surfaces; ++i) { - const vk_emissive_surface_t *const src_esurf = g_lights.emissive_surfaces + i; - struct EmissiveKusok *const dst_ekusok = lights->kusochki + i; + ASSERT(g_lights.num_polygons <= MAX_EMISSIVE_KUSOCHKI); + lights->num_polygons = g_lights.num_polygons; + for (int i = 0; i < g_lights.num_polygons; ++i) { + const rt_light_polygon_t *const src_poly = g_lights.polygons + i; + struct PolygonLight *const dst_poly = lights->polygons + i; - dst_ekusok->kusok_index = src_esurf->kusok_index; - Matrix3x4_Copy(dst_ekusok->tx_row_x, src_esurf->transform); - VectorCopy(src_esurf->emissive, dst_ekusok->emissive); + //dst_ekusok->kusok_index = src_esurf->kusok_index; + //Matrix3x4_Copy(dst_ekusok->tx_row_x, src_esurf->transform); + VectorCopy(src_poly->emissive, dst_poly->emissive); + VectorCopy(src_poly->normal_area, dst_poly->normal_area); + VectorCopy(src_poly->center, dst_poly->center); + dst_poly->vertices_begin = src_poly->vertices.begin; + dst_poly->vertices_count = src_poly->vertices.count; } lights->num_point_lights = g_lights.num_point_lights; @@ -806,6 +811,10 @@ static void uploadLights ( void ) { dst->environment = !!(src->flags & LightFlag_Environment); } + + // TODO static assert + ASSERT(sizeof(lights->polygon_vertices) == sizeof(g_lights.polygon_vertices)); + memcpy(lights->polygon_vertices, g_lights.polygon_vertices, sizeof(lights->polygon_vertices)); } } @@ -1182,7 +1191,7 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) deinitVk2d(); initVk2d(); // TODO gracefully handle reload errors: need to change createPipeline, loadShader, VK_PipelineCreate... - vkDestroyPipeline(vk_core.device, g_rtx.pipeline, NULL); + //vkDestroyPipeline(vk_core.device, g_rtx.pipeline, NULL); createPipeline(); XVK_RayTracePrimaryReloadPipeline(); @@ -1490,7 +1499,7 @@ RAY_LIGHT_DIRECT_OUTPUTS(X) XVK_ImageDestroy(&g_rtx.frames[i].additive); } - vkDestroyPipeline(vk_core.device, g_rtx.pipeline, NULL); + //vkDestroyPipeline(vk_core.device, g_rtx.pipeline, NULL); VK_DescriptorsDestroy(&g_rtx.descriptors); if (g_rtx.tlas != VK_NULL_HANDLE) diff --git a/ref_vk/vk_scene.c b/ref_vk/vk_scene.c index 4fa1fe3c..9b73e81d 100644 --- a/ref_vk/vk_scene.c +++ b/ref_vk/vk_scene.c @@ -54,7 +54,9 @@ static void reloadPatches( void ) { // Assumes that the map brush model has been loaded // Patching does disturb light sources, reinitialize - VK_LightsLoadMapStaticLights(); + + // FIXME loading patches will be broken now ;_; + // ?? VK_LightsLoadMapStaticLights(); } static void reloadMaterials( void ) { @@ -67,7 +69,8 @@ static void reloadMaterials( void ) { // Assumes that the map has been loaded // Might have loaded new patch data, need to reload lighting data just in case - VK_LightsLoadMapStaticLights(); + // FIXME loading patches will be broken now ;_; + // ??? VK_LightsLoadMapStaticLights(); } void VK_SceneInit( void ) @@ -118,9 +121,9 @@ int R_FIXME_GetEntityRenderMode( cl_entity_t *ent ) } // tell the renderer what new map is started -void R_NewMap( void ) -{ +void R_NewMap( void ) { const int num_models = gEngine.EngineGetParm( PARM_NUMMODELS, 0 ); + const model_t *const map = gEngine.pfnGetModelByIndex( 1 ); // Existence of cache.data for the world means that we've already have loaded this map // and this R_NewMap call is from within loading of a saved game. @@ -169,7 +172,7 @@ void R_NewMap( void ) XVK_ParseMapPatches(); // Need parsed map entities, and also should happen before brush model loading - VK_LightsNewMap(); + RT_LightsNewMapBegin(map); // Load all models at once gEngine.Con_Reportf( "Num models: %d:\n", num_models ); @@ -192,7 +195,7 @@ void R_NewMap( void ) // Load static map lights // Reads surfaces from loaded brush models (must happen after all brushes are loaded) - VK_LightsLoadMapStaticLights(); + RT_LightsNewMapEnd(map); if (vk_core.rtx) { From 85ea822512cdf6a4669477158a5dfcab2931425e Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 15 Jan 2022 17:16:45 -0800 Subject: [PATCH 091/548] rt: sample the new polygon lights with projected solid angle no shadow rays yet --- ref_vk/shaders/light_polygon.glsl | 34 ++++++++++++++++++++++++++++--- ref_vk/shaders/ray_interop.h | 4 ++-- ref_vk/vk_rtx.c | 8 +++++--- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index bbea2ad8..c4c1a17b 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -1,4 +1,4 @@ -#define MAX_POLYGON_VERTEX_COUNT 4 +#define MAX_POLYGON_VERTEX_COUNT 8 #define MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING 3 #include "peters2021-sampling/polygon_clipping.glsl" #include "peters2021-sampling/polygon_sampling.glsl" @@ -19,6 +19,29 @@ SampleContext buildSampleContext(vec3 position, vec3 normal, vec3 view_dir) { return ctx; } +vec4 getPolygonLightSample(vec3 P, vec3 N, vec3 view_dir, SampleContext ctx, uint poly_index) { + const PolygonLight poly = lights.polygons[poly_index]; + vec3 clipped[MAX_POLYGON_VERTEX_COUNT]; + + for (uint i = 0; i < poly.vertices_count; ++i) { + clipped[i] = ctx.world_to_shading * vec4(lights.polygon_vertices[poly.vertices_offset + i].xyz, 1.); + } + + const uint vertices_count = clip_polygon(poly.vertices_count, clipped); + if (vertices_count == 0) + return vec4(0.f); + + const projected_solid_angle_polygon_t sap = prepare_projected_solid_angle_polygon_sampling(vertices_count, clipped); + const float contrib = sap.projected_solid_angle; + if (contrib <= 0.f) + return vec4(0.f); + + vec2 rnd = vec2(rand01(), rand01()); + const vec3 light_dir = (transpose(ctx.world_to_shading) * sample_projected_solid_angle_polygon(sap, rnd)).xyz; + + return vec4(light_dir, contrib); +} + #if 0 void sampleEmissiveSurface(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, SampleContext ctx, uint ekusok_index, out vec3 out_diffuse, out vec3 out_specular) { out_diffuse = out_specular = vec3(0.); @@ -191,6 +214,9 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate // FIXME this is a quick test that reading new lights is possible void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { + // TODO do this after the loop + const SampleContext ctx = buildSampleContext(P, N, view_dir); + //diffuse = vec3(fract(float(lights.num_polygons) / 10.)); return; for (uint i = 0; i < lights.num_polygons; ++i) { const PolygonLight poly = lights.polygons[i]; @@ -205,10 +231,12 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate if (contrib_estimate < 1e-6) continue; + const float contrib = getPolygonLightSample(P, N, view_dir, ctx, i).w; + //const float area = 1.f / length(poly.normal_area); vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); evalSplitBRDF(N, light_dir, view_dir, material, poly_diffuse, poly_specular); - diffuse += throughput * poly.emissive * contrib_estimate; - specular += throughput * poly.emissive * contrib_estimate; + diffuse += throughput * poly.emissive * contrib; + specular += throughput * poly.emissive * contrib; } } diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index 1d68b21d..a71e88f4 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -84,7 +84,7 @@ struct EmissiveKusok { struct PolygonLight { vec3 normal_area; - uint vertices_begin; + uint vertices_offset; vec3 center; uint vertices_count; @@ -100,7 +100,7 @@ struct Lights { //STRUCT EmissiveKusok kusochki[MAX_EMISSIVE_KUSOCHKI]; STRUCT PointLight point_lights[MAX_POINT_LIGHTS]; STRUCT PolygonLight polygons[MAX_EMISSIVE_KUSOCHKI]; - vec3 polygon_vertices[MAX_EMISSIVE_KUSOCHKI * 7]; + vec4 polygon_vertices[MAX_EMISSIVE_KUSOCHKI * 7]; // vec3 but aligned }; struct LightCluster { diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index efee7364..71d4ab01 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -791,7 +791,7 @@ static void uploadLights( void ) { VectorCopy(src_poly->emissive, dst_poly->emissive); VectorCopy(src_poly->normal_area, dst_poly->normal_area); VectorCopy(src_poly->center, dst_poly->center); - dst_poly->vertices_begin = src_poly->vertices.begin; + dst_poly->vertices_offset = src_poly->vertices.begin; dst_poly->vertices_count = src_poly->vertices.count; } @@ -813,8 +813,10 @@ static void uploadLights( void ) { } // TODO static assert - ASSERT(sizeof(lights->polygon_vertices) == sizeof(g_lights.polygon_vertices)); - memcpy(lights->polygon_vertices, g_lights.polygon_vertices, sizeof(lights->polygon_vertices)); + ASSERT(sizeof(lights->polygon_vertices) >= sizeof(g_lights.polygon_vertices)); + for (int i = 0; i < g_lights.num_polygon_vertices; ++i) { + VectorCopy(g_lights.polygon_vertices[i], lights->polygon_vertices[i]); + } } } From 90bf0bc262de9d562de524e0e72c0c84e20a8c27 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 15 Jan 2022 19:45:41 -0800 Subject: [PATCH 092/548] rt: select only one light source per pixel known issues: - noise is fixed - overall light is too dark - some lights are facing the wrong direction test_brush2, green room, direct light: 336us, 192(192)v, 97(128)s, 4096lds, 2/16o (-45v => +1o) --- ref_vk/shaders/light_polygon.glsl | 36 ++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index c4c1a17b..85aa9f34 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -214,16 +214,12 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate // FIXME this is a quick test that reading new lights is possible void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { - // TODO do this after the loop - const SampleContext ctx = buildSampleContext(P, N, view_dir); - - //diffuse = vec3(fract(float(lights.num_polygons) / 10.)); return; + uint selected = 0; + float total_contrib = 0.; + float eps1 = rand01(); for (uint i = 0; i < lights.num_polygons; ++i) { const PolygonLight poly = lights.polygons[i]; - /* diffuse += poly.emissive; */ - /* continue; */ - const vec3 dir = poly.center - P; const vec3 light_dir = normalize(dir); const float contrib_estimate = dot(light_dir, poly.normal_area) / dot(dir, dir); @@ -231,12 +227,26 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate if (contrib_estimate < 1e-6) continue; - const float contrib = getPolygonLightSample(P, N, view_dir, ctx, i).w; + const float tau = total_contrib / (total_contrib + contrib_estimate); + total_contrib += contrib_estimate; - //const float area = 1.f / length(poly.normal_area); - vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); - evalSplitBRDF(N, light_dir, view_dir, material, poly_diffuse, poly_specular); - diffuse += throughput * poly.emissive * contrib; - specular += throughput * poly.emissive * contrib; + if (eps1 < tau) { + eps1 /= tau; + } else { + selected = i + 1; + eps1 = (eps1 - tau) / (1. - tau); + } } + + if (selected == 0) + return; + + const SampleContext ctx = buildSampleContext(P, N, view_dir); + const vec4 light_sample_dir = getPolygonLightSample(P, N, view_dir, ctx, selected - 1); + const vec3 emissive = lights.polygons[selected-1].emissive; + + vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); + evalSplitBRDF(N, light_sample_dir.xyz, view_dir, material, poly_diffuse, poly_specular); + diffuse += throughput * emissive * light_sample_dir.w; + specular += throughput * emissive * light_sample_dir.w; } From 6853ad51ea77fe06daf6f4f5bd54693f27401a38 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 15 Jan 2022 19:54:35 -0800 Subject: [PATCH 093/548] rt: fix polylights facing the wrong direction --- ref_vk/vk_brush.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ref_vk/vk_brush.c b/ref_vk/vk_brush.c index 30b69923..41b8646d 100644 --- a/ref_vk/vk_brush.c +++ b/ref_vk/vk_brush.c @@ -440,7 +440,6 @@ static model_sizes_t computeSizes( const model_t *mod ) { static void loadEmissiveSurface(const model_t *mod, const int surface_index, const msurface_t *surf, const vec3_t emissive) { rt_light_add_polygon_t lpoly; - const qboolean flip = !!FBitSet( surf->flags, SURF_PLANEBACK ); lpoly.num_vertices = Q_min(7, surf->numedges); // TODO split, don't clip @@ -450,8 +449,7 @@ static void loadEmissiveSurface(const model_t *mod, const int surface_index, con VectorCopy(emissive, lpoly.emissive); for (int i = 0; i < lpoly.num_vertices; ++i) { - const int index = flip ? lpoly.num_vertices - i - 1 : i; - const int iedge = mod->surfedges[surf->firstedge + index]; + const int iedge = mod->surfedges[surf->firstedge + i]; const medge_t *edge = mod->edges + (iedge >= 0 ? iedge : -iedge); const mvertex_t *vertex = mod->vertexes + (iedge >= 0 ? edge->v[0] : edge->v[1]); VectorCopy(vertex->position, lpoly.vertices[i]); From f7e42e5ae0582dda6fea2025b12404ea985dcee6 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 15 Jan 2022 21:34:43 -0800 Subject: [PATCH 094/548] rt: add shadows for polylights also change: - pass plane equation instead of just normal - pass area separately - pack vertices offset+count into single integer 582us, 224(224)v, 97(128)v, 4096lds, 2/16o (-53v => +1) --- ref_vk/shaders/light_polygon.glsl | 42 +++++++++++++++++++++++-------- ref_vk/shaders/ray_interop.h | 7 +++--- ref_vk/vk_light.c | 32 +++++++++++++---------- ref_vk/vk_light.h | 10 ++++---- ref_vk/vk_rtx.c | 17 ++++++++++--- 5 files changed, 72 insertions(+), 36 deletions(-) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 85aa9f34..d3633183 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -23,11 +23,14 @@ vec4 getPolygonLightSample(vec3 P, vec3 N, vec3 view_dir, SampleContext ctx, uin const PolygonLight poly = lights.polygons[poly_index]; vec3 clipped[MAX_POLYGON_VERTEX_COUNT]; - for (uint i = 0; i < poly.vertices_count; ++i) { - clipped[i] = ctx.world_to_shading * vec4(lights.polygon_vertices[poly.vertices_offset + i].xyz, 1.); + const uint vertices_offset = poly.vertices_count_offset & 0xffffu; + uint vertices_count = poly.vertices_count_offset >> 16; + + for (uint i = 0; i < vertices_count; ++i) { + clipped[i] = ctx.world_to_shading * vec4(lights.polygon_vertices[vertices_offset + i].xyz, 1.); } - const uint vertices_count = clip_polygon(poly.vertices_count, clipped); + vertices_count = clip_polygon(vertices_count, clipped); if (vertices_count == 0) return vec4(0.f); @@ -222,7 +225,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate const vec3 dir = poly.center - P; const vec3 light_dir = normalize(dir); - const float contrib_estimate = dot(light_dir, poly.normal_area) / dot(dir, dir); + const float contrib_estimate = poly.area * dot(-light_dir, poly.plane.xyz) / dot(dir, dir); if (contrib_estimate < 1e-6) continue; @@ -238,15 +241,34 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate } } - if (selected == 0) + if (selected == 0) { + //diffuse = vec3(1., 0., 0.); return; + } +#if 0 + const PolygonLight poly = lights.polygons[selected - 1]; + const vec3 emissive = poly.emissive; + vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); + evalSplitBRDF(N, normalize(poly.center-P), view_dir, material, poly_diffuse, poly_specular); + diffuse += throughput * emissive * total_contrib; + specular += throughput * emissive * total_contrib; +#else const SampleContext ctx = buildSampleContext(P, N, view_dir); const vec4 light_sample_dir = getPolygonLightSample(P, N, view_dir, ctx, selected - 1); - const vec3 emissive = lights.polygons[selected-1].emissive; + if (light_sample_dir.w <= 0.) + return; - vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); - evalSplitBRDF(N, light_sample_dir.xyz, view_dir, material, poly_diffuse, poly_specular); - diffuse += throughput * emissive * light_sample_dir.w; - specular += throughput * emissive * light_sample_dir.w; + const PolygonLight poly = lights.polygons[selected - 1]; + const float dist = - dot(vec4(P, 1.f), poly.plane) / dot(light_sample_dir.xyz, poly.plane.xyz); + const vec3 emissive = poly.emissive; + + //if (true) {//!shadowed(P, light_sample_dir.xyz, dist)) { + if (!shadowed(P, light_sample_dir.xyz, dist)) { + vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); + evalSplitBRDF(N, light_sample_dir.xyz, view_dir, material, poly_diffuse, poly_specular); + diffuse += throughput * emissive * light_sample_dir.w; // TODO * total_contrib ? + specular += throughput * emissive * light_sample_dir.w; + } +#endif } diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index a71e88f4..b8a9ff77 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -83,14 +83,13 @@ struct EmissiveKusok { }; struct PolygonLight { - vec3 normal_area; - uint vertices_offset; + vec4 plane; vec3 center; - uint vertices_count; + float area; vec3 emissive; - PAD(1) + uint vertices_count_offset; }; struct Lights { diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 93565294..22be3d23 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -1175,30 +1175,35 @@ int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { { rt_light_polygon_t *const poly = g_lights.polygons + g_lights.num_polygons; + vec3_t normal; - poly->vertices.begin = g_lights.num_polygon_vertices; + poly->vertices.offset = g_lights.num_polygon_vertices; poly->vertices.count = addpoly->num_vertices; VectorCopy(addpoly->emissive, poly->emissive); VectorSet(poly->center, 0, 0, 0); - VectorSet(poly->normal_area, 0, 0, 0); + VectorSet(normal, 0, 0, 0); for (int i = 0; i < addpoly->num_vertices; ++i) { - VectorCopy(addpoly->vertices[i], g_lights.polygon_vertices[poly->vertices.begin + i]); + VectorCopy(addpoly->vertices[i], g_lights.polygon_vertices[poly->vertices.offset + i]); VectorAdd(addpoly->vertices[i], poly->center, poly->center); if (i > 1) { - vec3_t e[2], normal; - VectorSubtract(addpoly->vertices[i-1], addpoly->vertices[i-2], e[0]); - VectorSubtract(addpoly->vertices[i-0], addpoly->vertices[i-2], e[1]); - CrossProduct(e[0], e[1], normal); - VectorAdd(normal, poly->normal_area, poly->normal_area); + vec3_t e[2], lnormal; + VectorSubtract(addpoly->vertices[i-0], addpoly->vertices[0], e[0]); + VectorSubtract(addpoly->vertices[i-1], addpoly->vertices[0], e[1]); + CrossProduct(e[0], e[1], lnormal); + VectorAdd(lnormal, normal, normal); } } + poly->area = VectorLength(normal); + VectorM(1.f / poly->area, normal, poly->plane); + poly->plane[3] = -DotProduct(addpoly->vertices[0], poly->plane); + VectorM(1.f / poly->vertices.count, poly->center, poly->center); - gEngine.Con_Reportf("added polygon light index=%d color=(%f, %f, %f) center=(%f, %f, %f) normal_area=(%f, %f, %f) area=%f num_vertices=%d\n", + gEngine.Con_Reportf("added polygon light index=%d color=(%f, %f, %f) center=(%f, %f, %f) plane=(%f, %f, %f, %f) area=%f num_vertices=%d\n", g_lights.num_polygons, poly->emissive[0], poly->emissive[1], @@ -1206,10 +1211,11 @@ int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { poly->center[0], poly->center[1], poly->center[2], - poly->normal_area[0], - poly->normal_area[1], - poly->normal_area[2], - VectorLength(poly->normal_area), + poly->plane[0], + poly->plane[1], + poly->plane[2], + poly->plane[3], + poly->area, poly->vertices.count ); diff --git a/ref_vk/vk_light.h b/ref_vk/vk_light.h index aa048dd3..f0509967 100644 --- a/ref_vk/vk_light.h +++ b/ref_vk/vk_light.h @@ -26,14 +26,14 @@ typedef struct { } vk_lights_cell_t; typedef struct { + vec4_t plane; + vec3_t center; + float area; + vec3_t emissive; - vec3_t normal_area; - vec3_t center; - //float area; - struct { - int begin, count; // reference g_light.polygon_vertices + int offset, count; // reference g_light.polygon_vertices } vertices; // uint32_t kusok_index; diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 71d4ab01..da1a44e8 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -788,11 +788,20 @@ static void uploadLights( void ) { //dst_ekusok->kusok_index = src_esurf->kusok_index; //Matrix3x4_Copy(dst_ekusok->tx_row_x, src_esurf->transform); - VectorCopy(src_poly->emissive, dst_poly->emissive); - VectorCopy(src_poly->normal_area, dst_poly->normal_area); + + Vector4Copy(src_poly->plane, dst_poly->plane); VectorCopy(src_poly->center, dst_poly->center); - dst_poly->vertices_offset = src_poly->vertices.begin; - dst_poly->vertices_count = src_poly->vertices.count; + dst_poly->area = src_poly->area; + VectorCopy(src_poly->emissive, dst_poly->emissive); + + // TODO DEBUG_ASSERT + ASSERT(src_poly->vertices.count > 2); + ASSERT(src_poly->vertices.offset < 0xffffu); + ASSERT(src_poly->vertices.count < 0xffffu); + + ASSERT(src_poly->vertices.offset + src_poly->vertices.count < COUNTOF(lights->polygon_vertices)); + + dst_poly->vertices_count_offset = (src_poly->vertices.count << 16) | (src_poly->vertices.offset); } lights->num_point_lights = g_lights.num_point_lights; From bb84a3cb7fe341b70776ea8903d066fd915128c7 Mon Sep 17 00:00:00 2001 From: NightFox <0x4E69676874466F78@users.noreply.github.com> Date: Sat, 15 Jan 2022 22:41:33 +0300 Subject: [PATCH 095/548] add /fsanitize=address for msvc (windows) when sanitize --- scripts/waifulib/compiler_optimizations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/waifulib/compiler_optimizations.py b/scripts/waifulib/compiler_optimizations.py index c708850b..15b70e7a 100644 --- a/scripts/waifulib/compiler_optimizations.py +++ b/scripts/waifulib/compiler_optimizations.py @@ -41,6 +41,7 @@ LINKFLAGS = { 'sanitize': { 'clang': ['-fsanitize=undefined', '-fsanitize=address', '-pthread'], 'gcc': ['-fsanitize=undefined', '-fsanitize=address', '-pthread'], + 'msvc': ['/SAFESEH:NO'] }, 'debug': { 'msvc': ['/INCREMENTAL', '/SAFESEH:NO'] @@ -81,7 +82,7 @@ CFLAGS = { 'default': ['-O0'] }, 'sanitize': { - 'msvc': ['/Od', '/RTC1', '/Zi'], + 'msvc': ['/Od', '/RTC1', '/Zi', '/fsanitize=address'], 'gcc': ['-O0', '-fsanitize=undefined', '-fsanitize=address', '-pthread'], 'clang': ['-O0', '-fsanitize=undefined', '-fsanitize=address', '-pthread'], 'default': ['-O0'] From 663306f4e16e2635b09cb7ce7caf91825647d9d5 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 15 Jan 2022 23:12:12 -0800 Subject: [PATCH 096/548] rt: clusterize the new polygon lights known issues: - visible cluster boundaries which affect sampling outcomes (essentially clusters act like very coarse shadows, and that's visible) moving brush models are not supported yet, affects perf measurements --- ref_vk/shaders/light_polygon.glsl | 17 ++++-- ref_vk/shaders/ray_interop.h | 4 +- ref_vk/vk_brush.c | 1 + ref_vk/vk_light.c | 92 +++++++++++++++++++++++++++++-- ref_vk/vk_rtx.c | 4 +- 5 files changed, 103 insertions(+), 15 deletions(-) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index d3633183..0804762a 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -217,11 +217,16 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate // FIXME this is a quick test that reading new lights is possible void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { + // TODO move this to pickPolygonLight function + const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); + uint selected = 0; float total_contrib = 0.; float eps1 = rand01(); - for (uint i = 0; i < lights.num_polygons; ++i) { - const PolygonLight poly = lights.polygons[i]; + for (uint i = 0; i < num_polygons; ++i) { + const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); + + const PolygonLight poly = lights.polygons[index]; const vec3 dir = poly.center - P; const vec3 light_dir = normalize(dir); @@ -236,7 +241,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate if (eps1 < tau) { eps1 /= tau; } else { - selected = i + 1; + selected = index + 1; eps1 = (eps1 - tau) / (1. - tau); } } @@ -265,10 +270,12 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate //if (true) {//!shadowed(P, light_sample_dir.xyz, dist)) { if (!shadowed(P, light_sample_dir.xyz, dist)) { + //const float estimate = total_contrib; + const float estimate = light_sample_dir.w; vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); evalSplitBRDF(N, light_sample_dir.xyz, view_dir, material, poly_diffuse, poly_specular); - diffuse += throughput * emissive * light_sample_dir.w; // TODO * total_contrib ? - specular += throughput * emissive * light_sample_dir.w; + diffuse += throughput * emissive * estimate; + specular += throughput * emissive * estimate; } #endif } diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index b8a9ff77..834ee1f4 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -104,9 +104,9 @@ struct Lights { struct LightCluster { uint8_t num_point_lights; - uint8_t num_emissive_surfaces; + uint8_t num_polygons; uint8_t point_lights[MAX_VISIBLE_POINT_LIGHTS]; - uint8_t emissive_surfaces[MAX_VISIBLE_SURFACE_LIGHTS]; + uint8_t polygons[MAX_VISIBLE_SURFACE_LIGHTS]; }; #define PUSH_FLAG_LIGHTMAP_ONLY 0x01 diff --git a/ref_vk/vk_brush.c b/ref_vk/vk_brush.c index 41b8646d..987f4727 100644 --- a/ref_vk/vk_brush.c +++ b/ref_vk/vk_brush.c @@ -455,6 +455,7 @@ static void loadEmissiveSurface(const model_t *mod, const int surface_index, con VectorCopy(vertex->position, lpoly.vertices[i]); } + lpoly.surface = surf; RT_LightAddPolygon(&lpoly); } diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 22be3d23..3d14c410 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -267,6 +267,12 @@ vk_light_leaf_set_t *getMapLeafsAffectedByMapSurface( const msurface_t *surf ) { const int surf_index = surf - map->surfaces; vk_surface_metadata_t * const smeta = g_lights_bsp.surfaces + surf_index; const qboolean verbose_debug = false; + + if (surf_index < 0 || surf_index >= g_lights_bsp.num_surfaces) { + gEngine.Con_Printf(S_ERROR "FIXME not implemented: attempting to add non-static polygon light\n"); + return NULL; + } + ASSERT(surf_index >= 0); ASSERT(surf_index < g_lights_bsp.num_surfaces); @@ -433,8 +439,7 @@ vk_light_leaf_set_t *getMapLeafsAffectedByMovingSurface( const msurface_t *surf, return (vk_light_leaf_set_t*)&g_lights_bsp.accum.count; } -static void prepareSurfacesLeafVisibilityCache( void ) { - const model_t *map = gEngine.pfnGetModelByIndex( 1 ); +static void prepareSurfacesLeafVisibilityCache( const struct model_s *map ) { if (g_lights_bsp.surfaces != NULL) { for (int i = 0; i < g_lights_bsp.num_surfaces; ++i) { vk_surface_metadata_t *smeta = g_lights_bsp.surfaces + i; @@ -486,7 +491,7 @@ void RT_LightsNewMapBegin( const struct model_s *map ) { clusterBitMapShutdown(); clusterBitMapInit(); - prepareSurfacesLeafVisibilityCache(); + prepareSurfacesLeafVisibilityCache( map ); // Load RAD data based on map name memset(g_lights.map.emissive_textures, 0, sizeof(g_lights.map.emissive_textures)); @@ -528,7 +533,7 @@ void RT_LightsFrameInit( void ) { } } -static qboolean addSurfaceLightToCell( int cell_index, int emissive_surface_index ) { +static qboolean addSurfaceLightToCell( int cell_index, int polygon_light_index ) { vk_lights_cell_t *const cluster = g_lights.cells + cell_index; if (cluster->num_polygons == MAX_VISIBLE_SURFACE_LIGHTS) { @@ -536,10 +541,10 @@ static qboolean addSurfaceLightToCell( int cell_index, int emissive_surface_inde } if (debug_dump_lights.enabled) { - gEngine.Con_Reportf(" adding surface light %d to cell %d (count=%d)\n", emissive_surface_index, cell_index, cluster->num_polygons+1); + gEngine.Con_Reportf(" adding polygon light %d to cell %d (count=%d)\n", polygon_light_index, cell_index, cluster->num_polygons+1); } - cluster->polygons[cluster->num_polygons++] = emissive_surface_index; + cluster->polygons[cluster->num_polygons++] = polygon_light_index; return true; } @@ -1163,6 +1168,76 @@ void VK_LightsFrameFinalize( void ) { APROF_SCOPE_END(finalize); } +static void addPolygonLeafSetToClusters(const vk_light_leaf_set_t *leafs, int poly_index) { + const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); + + // FIXME this shouldn't happen in prod + if (!leafs) + return; + + clusterBitMapClear(); + + // Iterate through each visible/potentially affected leaf to get a range of grid cells + for (int i = 0; i < leafs->num; ++i) { + const mleaf_t *const leaf = world->leafs + leafs->leafs[i]; + + const int min_x = floorf(leaf->minmaxs[0] / LIGHT_GRID_CELL_SIZE); + const int min_y = floorf(leaf->minmaxs[1] / LIGHT_GRID_CELL_SIZE); + const int min_z = floorf(leaf->minmaxs[2] / LIGHT_GRID_CELL_SIZE); + + const int max_x = floorf(leaf->minmaxs[3] / LIGHT_GRID_CELL_SIZE) + 1; + const int max_y = floorf(leaf->minmaxs[4] / LIGHT_GRID_CELL_SIZE) + 1; + const int max_z = floorf(leaf->minmaxs[5] / LIGHT_GRID_CELL_SIZE) + 1; + + const qboolean not_visible = false; //TODO static_map && !canSurfaceLightAffectAABB(world, geom->surf, esurf->emissive, leaf->minmaxs); + + if (debug_dump_lights.enabled) { + gEngine.Con_Reportf(" adding leaf %d (%d of %d) min=(%d, %d, %d), max=(%d, %d, %d) total=%d\n", + leaf->cluster, i, leafs->num, + min_x, min_y, min_z, + max_x, max_y, max_z, + (max_x - min_x) * (max_y - min_y) * (max_z - min_z) + ); + } + + if (not_visible) + continue; + + for (int x = min_x; x < max_x; ++x) + for (int y = min_y; y < max_y; ++y) + for (int z = min_z; z < max_z; ++z) { + const int cell[3] = { + x - g_lights.map.grid_min_cell[0], + y - g_lights.map.grid_min_cell[1], + z - g_lights.map.grid_min_cell[2] + }; + + const int cell_index = R_LightCellIndex( cell ); + if (cell_index < 0) + continue; + + if (clusterBitMapCheckOrSet( cell_index )) { + const float minmaxs[6] = { + x * LIGHT_GRID_CELL_SIZE, + y * LIGHT_GRID_CELL_SIZE, + z * LIGHT_GRID_CELL_SIZE, + (x+1) * LIGHT_GRID_CELL_SIZE, + (y+1) * LIGHT_GRID_CELL_SIZE, + (z+1) * LIGHT_GRID_CELL_SIZE, + }; + + /* TODO if (static_map && !canSurfaceLightAffectAABB(world, geom->surf, esurf->emissive, minmaxs)) */ + /* continue; */ + + if (!addSurfaceLightToCell(cell_index, poly_index)) { + ERROR_THROTTLED(10, "Cluster %d,%d,%d(%d) ran out of polygon light slots", + cell[0], cell[1], cell[2], cell_index); + } + } + } + } +} + int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { if (g_lights.num_polygons == MAX_SURFACE_LIGHTS) { gEngine.Con_Printf(S_ERROR "Max number of polygon lights %d reached\n", MAX_SURFACE_LIGHTS); @@ -1219,6 +1294,11 @@ int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { poly->vertices.count ); + { + const vk_light_leaf_set_t *const leafs = getMapLeafsAffectedByMapSurface(addpoly->surface); + addPolygonLeafSetToClusters(leafs, g_lights.num_polygons); + } + g_lights.num_polygon_vertices += addpoly->num_vertices; return g_lights.num_polygons++; } diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index da1a44e8..05024254 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -771,9 +771,9 @@ static void uploadLights( void ) { struct LightCluster *const dst = grid->cells + i; dst->num_point_lights = src->num_point_lights; - dst->num_emissive_surfaces = src->num_polygons; + dst->num_polygons = src->num_polygons; memcpy(dst->point_lights, src->point_lights, sizeof(uint8_t) * src->num_point_lights); - memcpy(dst->emissive_surfaces, src->polygons, sizeof(uint8_t) * src->num_polygons); + memcpy(dst->polygons, src->polygons, sizeof(uint8_t) * src->num_polygons); } } From 815866b353a1f563018224a0e6a6172d0b7f1d1c Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 15 Jan 2022 23:15:29 -0800 Subject: [PATCH 097/548] rt: add per-frame random seed to randomize sampling --- ref_vk/shaders/ray_interop.h | 3 ++- ref_vk/shaders/ray_light_direct.comp | 2 +- ref_vk/vk_rtx.c | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index 834ee1f4..912e5fab 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -124,7 +124,8 @@ struct PushConstants { struct UniformBuffer { mat4 inv_proj, inv_view; float ray_cone_width; - PAD(3) + uint random_seed; + PAD(2) }; #undef PAD diff --git a/ref_vk/shaders/ray_light_direct.comp b/ref_vk/shaders/ray_light_direct.comp index 2f822942..06a65d0e 100644 --- a/ref_vk/shaders/ray_light_direct.comp +++ b/ref_vk/shaders/ray_light_direct.comp @@ -43,7 +43,7 @@ void main() { const ivec2 pix = ivec2(gl_GlobalInvocationID); const vec2 uv = (pix + .5) / res * 2. - 1.; - rand01_state = /* FIXME push_constants.random_seed +*/ gl_GlobalInvocationID.x * 1833 + gl_GlobalInvocationID.y * 31337; + rand01_state = ubo.random_seed + gl_GlobalInvocationID.x * 1833 + gl_GlobalInvocationID.y * 31337; // FIXME incorrect for reflection/refraction vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 05024254..c829dd1e 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -960,6 +960,7 @@ static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int fr Matrix4x4_ToArrayFloatGL(view_inv, (float*)ubo->inv_view); ubo->ray_cone_width = atanf((2.0f*tanf(DEG2RAD(fov_angle_y) * 0.5f)) / (float)FRAME_HEIGHT); + ubo->random_seed = (uint32_t)gEngine.COM_RandomLong(0, INT32_MAX); } static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_args_t* args, int frame_index, const xvk_ray_frame_images_t *current_frame, float fov_angle_y) { From b9cec43e2c922c2d7c4d975265be580703ea3711 Mon Sep 17 00:00:00 2001 From: Valery Klachkov Date: Sun, 16 Jan 2022 04:57:12 +0300 Subject: [PATCH 098/548] ref_soft: Fix crashes on 64bit Just replace all long for unsigned long long :) --- ref_soft/r_edge.c | 2 +- ref_soft/r_main.c | 18 +++++++++--------- ref_soft/r_rast.c | 10 +++++----- ref_soft/r_scan.c | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/ref_soft/r_edge.c b/ref_soft/r_edge.c index 2a09ffb8..e65797d5 100644 --- a/ref_soft/r_edge.c +++ b/ref_soft/r_edge.c @@ -651,7 +651,7 @@ void R_ScanEdges (void) surf_t *s; basespan_p = (espan_t *) - ((long)(basespans + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + ((unsigned long long)(basespans + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); max_span_p = &basespan_p[MAXSPANS - RI.vrect.width]; span_p = basespan_p; diff --git a/ref_soft/r_main.c b/ref_soft/r_main.c index 96a1d160..77b4e792 100644 --- a/ref_soft/r_main.c +++ b/ref_soft/r_main.c @@ -1260,13 +1260,12 @@ void R_DrawBrushModel(cl_entity_t *pent) else { r_edges = (edge_t *) - (((long)&ledges[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + (((unsigned long long)&ledges[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); } if (r_surfsonstack) { - surfaces = (surf_t *) - (((long)&lsurfs[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + surfaces = (surf_t *)(((unsigned long long)&lsurfs[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); surf_max = &surfaces[r_cnumsurfs]; // surface 0 doesn't really exist; it's just a dummy because index 0 // is used to indicate no edge attached to surface @@ -1414,17 +1413,18 @@ void R_EdgeDrawing (void) else { r_edges = (edge_t *) - (((long)&ledges[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + (((unsigned long long)&ledges[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); } if (r_surfsonstack) { - surfaces = (surf_t *) - (((long)&lsurfs[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + surfaces = (surf_t *)(((unsigned long long)&lsurfs + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); surf_max = &surfaces[r_cnumsurfs]; - // surface 0 doesn't really exist; it's just a dummy because index 0 - // is used to indicate no edge attached to surface - memset(&surfaces[0], 0, sizeof(surf_t)); + + // surface 0 doesn't really exist; it's just a dummy because index 0 + // is used to indicate no edge attached to surface + + memset(surfaces, 0, sizeof(surf_t)); surfaces--; R_SurfacePatch (); } diff --git a/ref_soft/r_rast.c b/ref_soft/r_rast.c index 70993daa..193ccb41 100644 --- a/ref_soft/r_rast.c +++ b/ref_soft/r_rast.c @@ -521,7 +521,7 @@ void R_EmitCachedEdge (void) { edge_t *pedge_t; - pedge_t = (edge_t *)((unsigned long)r_edges + r_pedge->cachededgeoffset); + pedge_t = (edge_t *)((unsigned long long)r_edges + r_pedge->cachededgeoffset); if (!pedge_t->surfs[0]) pedge_t->surfs[0] = surface_p - surfaces; @@ -624,9 +624,9 @@ void R_RenderFace (msurface_t *fa, int clipflags) } else { - if ((((unsigned long)edge_p - (unsigned long)r_edges) > + if ((((unsigned long long)edge_p - (unsigned long long)r_edges) > r_pedge->cachededgeoffset) && - (((edge_t *)((unsigned long)r_edges + + (((edge_t *)((unsigned long long)r_edges + r_pedge->cachededgeoffset))->owner == r_pedge)) { R_EmitCachedEdge (); @@ -670,9 +670,9 @@ void R_RenderFace (msurface_t *fa, int clipflags) { // it's cached if the cached edge is valid and is owned // by this medge_t - if ((((unsigned long)edge_p - (unsigned long)r_edges) > + if ((((unsigned long long)edge_p - (unsigned long long)r_edges) > r_pedge->cachededgeoffset) && - (((edge_t *)((unsigned long)r_edges + + (((edge_t *)((unsigned long long)r_edges + r_pedge->cachededgeoffset))->owner == r_pedge)) { R_EmitCachedEdge (); diff --git a/ref_soft/r_scan.c b/ref_soft/r_scan.c index d096e4b7..2e3c9176 100644 --- a/ref_soft/r_scan.c +++ b/ref_soft/r_scan.c @@ -1390,7 +1390,7 @@ void D_DrawZSpans (espan_t *pspan) // we count on FP exceptions being turned off to avoid range problems izi = (int)(zi * 0x8000 * 0x10000); - if ((long)pdest & 0x02) + if ((unsigned long long)pdest & 0x02) { *pdest++ = (short)(izi >> 16); izi += izistep; From 5d90e8389e0e50f5d86b2264297d5ec12c902c9d Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Mon, 17 Jan 2022 21:12:14 -0800 Subject: [PATCH 099/548] rt: fix validation woes on windows --- ref_vk/vk_ray_primary.c | 2 +- ref_vk/vk_rtx.c | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index 9b418d28..321cdeb7 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -74,7 +74,7 @@ static void initDescriptors( void ) { } INIT_BINDING(1, TLAS, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); - INIT_BINDING(2, UBO, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); + INIT_BINDING(2, UBO, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); INIT_BINDING(3, Kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); INIT_BINDING(4, Indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); INIT_BINDING(5, Vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index c829dd1e..58e6ffe1 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -113,6 +113,7 @@ static struct { // Holds UniformBuffer data vk_buffer_t uniform_buffer; + uint32_t uniform_unit_size; // Shader binding table buffer vk_buffer_t sbt_buffer; @@ -641,7 +642,7 @@ static void updateDescriptors( const vk_ray_frame_render_args_t *args, int frame g_rtx.desc_values[RayDescBinding_UBOMatrices].buffer = (VkDescriptorBufferInfo){ .buffer = g_rtx.uniform_buffer.buffer, - .offset = frame_index * sizeof(struct UniformBuffer), + .offset = frame_index * g_rtx.uniform_unit_size, .range = sizeof(struct UniformBuffer), }; @@ -948,7 +949,7 @@ static void blitImage( const xvk_blit_args *blit_args ) { } static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int frame_index, float fov_angle_y ) { - struct UniformBuffer *ubo = (struct UniformBuffer*)g_rtx.uniform_buffer.mapped + frame_index; + struct UniformBuffer *ubo = (struct UniformBuffer*)((char*)g_rtx.uniform_buffer.mapped + frame_index * g_rtx.uniform_unit_size); matrix4x4 proj_inv, view_inv; Matrix4x4_Invert_Full(proj_inv, *args->projection); @@ -1033,7 +1034,7 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar .tlas = g_rtx.tlas, .ubo = { .buffer = g_rtx.uniform_buffer.buffer, - .offset = frame_index * sizeof(struct UniformBuffer), + .offset = frame_index * g_rtx.uniform_unit_size, .size = sizeof(struct UniformBuffer), }, .kusochki = { @@ -1084,7 +1085,7 @@ RAY_PRIMARY_OUTPUTS(X) .tlas = g_rtx.tlas, .ubo = { .buffer = g_rtx.uniform_buffer.buffer, - .offset = frame_index * sizeof(struct UniformBuffer), + .offset = frame_index * g_rtx.uniform_unit_size, .size = sizeof(struct UniformBuffer), }, .kusochki = { @@ -1381,8 +1382,9 @@ qboolean VK_RayInit( void ) ASSERT(XVK_RayTraceLightDirectInit()); g_rtx.sbt_record_size = vk_core.physical_device.sbt_record_size; + g_rtx.uniform_unit_size = ALIGN_UP(sizeof(struct UniformBuffer), vk_core.physical_device.properties.limits.minUniformBufferOffsetAlignment); - if (!createBuffer("ray uniform_buffer", &g_rtx.uniform_buffer, sizeof(struct UniformBuffer) * MAX_FRAMES_IN_FLIGHT, + if (!createBuffer("ray uniform_buffer", &g_rtx.uniform_buffer, g_rtx.uniform_unit_size * MAX_FRAMES_IN_FLIGHT, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { From 460b839a64be97411402d687f54a75f73577a591 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Tue, 18 Jan 2022 22:21:14 -0800 Subject: [PATCH 100/548] rt: add debug labels to command buffer --- ref_vk/vk_core.h | 31 +++++++++++++++++++++++++++++++ ref_vk/vk_denoiser.c | 8 +++++--- ref_vk/vk_ray_light_direct.c | 8 +++++--- ref_vk/vk_ray_model.c | 3 +++ ref_vk/vk_ray_primary.c | 2 ++ ref_vk/vk_rtx.c | 5 +++++ 6 files changed, 51 insertions(+), 6 deletions(-) diff --git a/ref_vk/vk_core.h b/ref_vk/vk_core.h index c95d2d02..c09bbba0 100644 --- a/ref_vk/vk_core.h +++ b/ref_vk/vk_core.h @@ -94,6 +94,37 @@ do { \ } \ } while (0) +#define DEBUG_BEGIN(cmdbuf, msg) \ + do { \ + if (vk_core.debug) { \ + const VkDebugUtilsLabelEXT label = { \ + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, \ + .pLabelName = msg, \ + }; \ + vkCmdBeginDebugUtilsLabelEXT(cmdbuf, &label); \ + } \ + } while(0) + +#define DEBUG_BEGINF(cmdbuf, fmt, ...) \ + do { \ + if (vk_core.debug) { \ + char buf[128]; \ + snprintf(buf, sizeof(buf), fmt, ##__VA_ARGS__); \ + const VkDebugUtilsLabelEXT label = { \ + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, \ + .pLabelName = buf, \ + }; \ + vkCmdBeginDebugUtilsLabelEXT(cmdbuf, &label); \ + } \ + } while(0) + +#define DEBUG_END(cmdbuf) \ + do { \ + if (vk_core.debug) { \ + vkCmdEndDebugUtilsLabelEXT(cmdbuf); \ + } \ + } while(0) + // TODO make this not fatal: devise proper error handling strategies // FIXME Host_Error does not cause process to exit, we need to handle this manually #define XVK_CHECK(f) do { \ diff --git a/ref_vk/vk_denoiser.c b/ref_vk/vk_denoiser.c index 9c4b6fb9..ec8fbf73 100644 --- a/ref_vk/vk_denoiser.c +++ b/ref_vk/vk_denoiser.c @@ -176,7 +176,9 @@ void XVK_DenoiserDenoise( const xvk_denoiser_args_t* args ) { VK_DescriptorsWrite(&g_denoiser.descriptors); - vkCmdBindPipeline(args->cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_denoiser.pipeline); - vkCmdBindDescriptorSets(args->cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_denoiser.descriptors.pipeline_layout, 0, 1, g_denoiser.descriptors.desc_sets + 0, 0, NULL); - vkCmdDispatch(args->cmdbuf, (args->width + WG_W - 1) / WG_W, (args->height + WG_H - 1) / WG_H, 1); + DEBUG_BEGIN(args->cmdbuf, "denoiser"); + vkCmdBindPipeline(args->cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_denoiser.pipeline); + vkCmdBindDescriptorSets(args->cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_denoiser.descriptors.pipeline_layout, 0, 1, g_denoiser.descriptors.desc_sets + 0, 0, NULL); + vkCmdDispatch(args->cmdbuf, (args->width + WG_W - 1) / WG_W, (args->height + WG_H - 1) / WG_H, 1); + DEBUG_END(args->cmdbuf); } diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 5780fddf..09ec4106 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -169,8 +169,10 @@ void XVK_RayTraceLightDirect( VkCommandBuffer cmdbuf, const xvk_ray_trace_light_ updateDescriptors( args ); - vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_ray_light_direct.pipeline); - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_ray_light_direct.desc.riptors.pipeline_layout, 0, 1, g_ray_light_direct.desc.riptors.desc_sets + 0, 0, NULL); - vkCmdDispatch(cmdbuf, (args->width + WG_W - 1) / WG_W, (args->height + WG_H - 1) / WG_H, 1); + DEBUG_BEGIN(cmdbuf, "lights direct"); + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_ray_light_direct.pipeline); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_ray_light_direct.desc.riptors.pipeline_layout, 0, 1, g_ray_light_direct.desc.riptors.desc_sets + 0, 0, NULL); + vkCmdDispatch(cmdbuf, (args->width + WG_W - 1) / WG_W, (args->height + WG_H - 1) / WG_H, 1); + DEBUG_END(cmdbuf); } diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index 4c5ee7ad..b5fabd21 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -246,7 +246,10 @@ vk_ray_model_t* VK_RayModelCreate( vk_ray_model_init_t args ) { } else { qboolean result; asrgs.p_accel = &ray_model->as; + + DEBUG_BEGINF(vk_core.cb, "build blas for %s", args.model->debug_name); result = createOrUpdateAccelerationStructure(vk_core.cb, &asrgs, ray_model); + DEBUG_END(vk_core.cb); if (!result) { diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index 28195300..0f28f37a 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -283,6 +283,7 @@ void XVK_RayTracePrimary( VkCommandBuffer cmdbuf, const xvk_ray_trace_primary_t /* vkCmdPushConstants(cmdbuf, g_ray_primary.descriptors.pipeline_layout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, 0, sizeof(push_constants), &push_constants); */ /* } */ + DEBUG_BEGIN(cmdbuf, "primary"); vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_primary.desc.riptors.pipeline_layout, 0, 1, g_ray_primary.desc.riptors.desc_sets + 0, 0, NULL); { @@ -299,5 +300,6 @@ void XVK_RayTracePrimary( VkCommandBuffer cmdbuf, const xvk_ray_trace_primary_t vkCmdTraceRaysKHR(cmdbuf, &sbt_raygen, &sbt_miss, &sbt_hit, &sbt_callable, args->width, args->height, 1 ); } + DEBUG_END(cmdbuf); } diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 4c383338..18bf3b0d 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -565,6 +565,8 @@ static void createPipeline( void ) static void prepareTlas( VkCommandBuffer cmdbuf ) { ASSERT(g_ray_model_state.frame.num_models > 0); + DEBUG_BEGIN(cmdbuf, "prepare tlas"); + // Upload all blas instances references to GPU mem { VkAccelerationStructureInstanceKHR* inst = g_rtx.tlas_geom_buffer.mapped; @@ -622,6 +624,7 @@ static void prepareTlas( VkCommandBuffer cmdbuf ) { // 2. Build TLAS createTlas(cmdbuf); + DEBUG_END(cmdbuf); } static void updateDescriptors( const vk_ray_frame_render_args_t *args, int frame_index, const xvk_ray_frame_images_t *frame_dst ) { @@ -965,6 +968,7 @@ static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int fr } static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_args_t* args, int frame_index, const xvk_ray_frame_images_t *current_frame, float fov_angle_y) { + DEBUG_BEGIN(cmdbuf, "yay tracing"); uploadLights(); prepareTlas(cmdbuf); prepareUniformBuffer(args, frame_index, fov_angle_y); @@ -1179,6 +1183,7 @@ RAY_PRIMARY_OUTPUTS(X) blitImage( &blit_args ); } + DEBUG_END(cmdbuf); } qboolean initVk2d(void); From c73f3d0dab5cd1d239d0b90f0b9b58e38b7c07ab Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Wed, 19 Jan 2022 20:23:38 -0800 Subject: [PATCH 101/548] rt: sample all polygon light sources in cluster --- ref_vk/shaders/light_polygon.glsl | 45 ++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 0804762a..cbb92da2 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -214,27 +214,63 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate } #endif -// FIXME this is a quick test that reading new lights is possible void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { +#define DO_ALL_IN_CLUSTER 1 +#if DO_ALL_IN_CLUSTER + const SampleContext ctx = buildSampleContext(P, N, view_dir); + const uint num_polygons = min(128, uint(light_grid.clusters[cluster_index].num_polygons)); + for (uint i = 0; i < num_polygons; ++i) { + const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); + const PolygonLight poly = lights.polygons[index]; + + if (false) + { + const vec3 dir = poly.center - P; + const vec3 light_dir = normalize(dir); + if (dot(-light_dir, poly.plane.xyz) <= 0.f) + continue; + } + + const vec4 light_sample_dir = getPolygonLightSample(P, N, view_dir, ctx, poly); + if (light_sample_dir.w <= 0.) + continue; + + const float dist = - dot(vec4(P, 1.f), poly.plane) / dot(light_sample_dir.xyz, poly.plane.xyz); + const vec3 emissive = poly.emissive; + + //if (true) {//!shadowed(P, light_sample_dir.xyz, dist)) { + if (!shadowed(P, light_sample_dir.xyz, dist)) { + //const float estimate = total_contrib; + const float estimate = light_sample_dir.w / 10; // FIXME WTF is this fudge + vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); + evalSplitBRDF(N, light_sample_dir.xyz, view_dir, material, poly_diffuse, poly_specular); + diffuse += throughput * emissive * estimate; + specular += throughput * emissive * estimate; + } + } +#else // TODO move this to pickPolygonLight function - const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); + //const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); + const uint num_polygons = lights.num_polygons; uint selected = 0; float total_contrib = 0.; float eps1 = rand01(); for (uint i = 0; i < num_polygons; ++i) { - const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); + //const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); + const uint index = i; const PolygonLight poly = lights.polygons[index]; const vec3 dir = poly.center - P; const vec3 light_dir = normalize(dir); - const float contrib_estimate = poly.area * dot(-light_dir, poly.plane.xyz) / dot(dir, dir); + float contrib_estimate = poly.area * dot(-light_dir, poly.plane.xyz) / (1e-3 + dot(dir, dir)); if (contrib_estimate < 1e-6) continue; + contrib_estimate = 1.f; const float tau = total_contrib / (total_contrib + contrib_estimate); total_contrib += contrib_estimate; @@ -278,4 +314,5 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate specular += throughput * emissive * estimate; } #endif +#endif } From 63358be4a37e39583698eded702e8fe80851bc2d Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 20 Jan 2022 00:06:38 -0800 Subject: [PATCH 102/548] rt: replace ray query with rt pipeline for direct light it stays roughly the same, vgpr 256, etc. perf is a tiny bit better (12ms vs 14ms for all poly lights in c1a0 lobby /w shadows), but it may be sampling artifact --- ref_vk/shaders/light_common.glsl | 10 + ref_vk/shaders/light_polygon.glsl | 21 +- ...atest.rahit => ray_common_alphatest.rahit} | 4 + ref_vk/shaders/ray_light_poly_direct.rgen | 85 +++++ .../{shadow.rmiss => ray_shadow.rmiss} | 4 +- ref_vk/shaders/ray_shadow_interface.glsl | 11 + ref_vk/vk_ray_light_direct.c | 292 ++++++++++++++++-- ref_vk/vk_ray_primary.c | 2 +- ref_vk/vk_rtx.c | 6 +- 9 files changed, 401 insertions(+), 34 deletions(-) rename ref_vk/shaders/{ray_primary_alphatest.rahit => ray_common_alphatest.rahit} (80%) create mode 100644 ref_vk/shaders/ray_light_poly_direct.rgen rename ref_vk/shaders/{shadow.rmiss => ray_shadow.rmiss} (50%) create mode 100644 ref_vk/shaders/ray_shadow_interface.glsl diff --git a/ref_vk/shaders/light_common.glsl b/ref_vk/shaders/light_common.glsl index ca8aecd1..894d91c7 100644 --- a/ref_vk/shaders/light_common.glsl +++ b/ref_vk/shaders/light_common.glsl @@ -1,3 +1,11 @@ +#ifndef LIGHT_COMMON_GLSL_INCLUDED +#define LIGHT_COMMON_GLSL_INCLUDED + +#ifdef RAY_TRACE2 +#include "ray_shadow_interface.glsl" +layout(location = 0) rayPayloadEXT RayPayloadShadow payload_shadow; +#endif + bool shadowed(vec3 pos, vec3 dir, float dist) { #ifdef RAY_TRACE payload_shadow.hit_type = SHADOW_HIT; @@ -68,3 +76,5 @@ void evalSplitBRDF(vec3 N, vec3 L, vec3 V, MaterialProperties material, out vec3 diffuse *= vec3(1.) - data.F; #endif } + +#endif //ifndef LIGHT_COMMON_GLSL_INCLUDED diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index cbb92da2..0f82313e 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -19,8 +19,19 @@ SampleContext buildSampleContext(vec3 position, vec3 normal, vec3 view_dir) { return ctx; } -vec4 getPolygonLightSample(vec3 P, vec3 N, vec3 view_dir, SampleContext ctx, uint poly_index) { - const PolygonLight poly = lights.polygons[poly_index]; +#if 0 +#define SAMPLE_TYPE_T projected_solid_angle_polygon_t +#define SAMPLE_PREPARE_FUNC(vertex_count, vertices) prepare_projected_solid_angle_polygon_sampling(vertex_count, vertices) +#define SAMPLE_FUNC sample_projected_solid_angle_polygon +#define SAMPLE_CONTRIB(sap) sap.projected_solid_angle +#else +#define SAMPLE_TYPE_T solid_angle_polygon_t +#define SAMPLE_PREPARE_FUNC(vertex_count, vertices) prepare_solid_angle_polygon_sampling(vertex_count, vertices, vec3(0.)) +#define SAMPLE_FUNC sample_solid_angle_polygon +#define SAMPLE_CONTRIB(sap) sap.solid_angle +#endif + +vec4 getPolygonLightSample(vec3 P, vec3 N, vec3 view_dir, SampleContext ctx, const PolygonLight poly) { vec3 clipped[MAX_POLYGON_VERTEX_COUNT]; const uint vertices_offset = poly.vertices_count_offset & 0xffffu; @@ -34,13 +45,13 @@ vec4 getPolygonLightSample(vec3 P, vec3 N, vec3 view_dir, SampleContext ctx, uin if (vertices_count == 0) return vec4(0.f); - const projected_solid_angle_polygon_t sap = prepare_projected_solid_angle_polygon_sampling(vertices_count, clipped); - const float contrib = sap.projected_solid_angle; + const SAMPLE_TYPE_T sap = SAMPLE_PREPARE_FUNC(vertices_count, clipped); + const float contrib = SAMPLE_CONTRIB(sap); if (contrib <= 0.f) return vec4(0.f); vec2 rnd = vec2(rand01(), rand01()); - const vec3 light_dir = (transpose(ctx.world_to_shading) * sample_projected_solid_angle_polygon(sap, rnd)).xyz; + const vec3 light_dir = (transpose(ctx.world_to_shading) * SAMPLE_FUNC(sap, rnd)).xyz; return vec4(light_dir, contrib); } diff --git a/ref_vk/shaders/ray_primary_alphatest.rahit b/ref_vk/shaders/ray_common_alphatest.rahit similarity index 80% rename from ref_vk/shaders/ray_primary_alphatest.rahit rename to ref_vk/shaders/ray_common_alphatest.rahit index 07bac394..3adf7c7a 100644 --- a/ref_vk/shaders/ray_primary_alphatest.rahit +++ b/ref_vk/shaders/ray_common_alphatest.rahit @@ -7,6 +7,10 @@ layout(constant_id = 6) const uint MAX_TEXTURES = 4096; layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; + +// TODO not really needed here? +// It's an artifact of readHitGeometry() computing uv_lods, which we don't really use in this shader +// Split readHitGeometry into basic and advanced layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; hitAttributeEXT vec2 bary; diff --git a/ref_vk/shaders/ray_light_poly_direct.rgen b/ref_vk/shaders/ray_light_poly_direct.rgen new file mode 100644 index 00000000..762ba33d --- /dev/null +++ b/ref_vk/shaders/ray_light_poly_direct.rgen @@ -0,0 +1,85 @@ +#version 460 core +#extension GL_GOOGLE_include_directive : require +#extension GL_EXT_control_flow_attributes : require +#extension GL_EXT_ray_tracing: require + +#include "utils.glsl" + +#include "ray_light_direct_iface.h" + +#define GLSL +#include "ray_interop.h" +#undef GLSL + +#define X(index, name, format) layout(set=0,binding=index,format) uniform readonly image2D name; +RAY_LIGHT_DIRECT_INPUTS(X) +#undef X +#define X(index, name, format) layout(set=0,binding=index,format) uniform writeonly image2D out_image_##name; +RAY_LIGHT_DIRECT_OUTPUTS(X) +#undef X + +layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; +layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; +layout (constant_id = 7) const uint SBT_RECORD_SIZE = 64; + +#include "ray_kusochki.glsl" + +#define RAY_TRACE +#define RAY_TRACE2 +#undef SHADER_OFFSET_HIT_SHADOW_BASE +#define SHADER_OFFSET_HIT_SHADOW_BASE 0 +#undef SHADER_OFFSET_MISS_SHADOW +#define SHADER_OFFSET_MISS_SHADOW 0 +#undef PAYLOAD_LOCATION_SHADOW +#define PAYLOAD_LOCATION_SHADOW 0 + +#define BINDING_LIGHTS 7 +#define BINDING_LIGHT_CLUSTERS 8 +#include "light.glsl" + +void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { + const vec4 n = imageLoad(normals_gs, uv); + geometry_normal = normalDecode(n.xy); + shading_normal = normalDecode(n.zw); +} + +void main() { + const vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.; + const ivec2 pix = ivec2(gl_LaunchIDEXT.xy); + + if (false) { + vec3 diffuse = vec3(uv, .5), specular = vec3(0.); + imageStore(out_image_light_diffuse, pix, vec4(diffuse, 0.f)); + imageStore(out_image_light_specular, pix, vec4(specular, 0.f)); + return; + } + + rand01_state = ubo.random_seed + gl_LaunchIDEXT.x * 1833 + gl_LaunchIDEXT.y * 31337; + + // FIXME incorrect for reflection/refraction + vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); + vec3 direction = normalize((ubo.inv_view * vec4(target.xyz, 0)).xyz); + + MaterialProperties material; + material.baseColor = vec3(1.); + material.emissive = vec3(0.f); + material.metalness = 0.f; // TODO + material.roughness = 1.f; // TODO + + const vec3 pos = imageLoad(position_t, pix).xyz; + + vec3 geometry_normal, shading_normal; + readNormals(pix, geometry_normal, shading_normal); + + const vec3 throughput = vec3(1.); + vec3 diffuse = vec3(0.), specular = vec3(0.); + computeLighting(pos + geometry_normal * .001, shading_normal, throughput, -direction, material, diffuse, specular); + + //specular = shading_normal; + //diffuse = geometry_normal; + //diffuse = shading_normal; + //diffuse.xy = uv; + imageStore(out_image_light_diffuse, pix, vec4(diffuse, 0.f)); + //imageStore(out_image_light_diffuse, pix, vec4(1.,0.,0.f, 0.f)); + imageStore(out_image_light_specular, pix, vec4(specular, 0.f)); +} diff --git a/ref_vk/shaders/shadow.rmiss b/ref_vk/shaders/ray_shadow.rmiss similarity index 50% rename from ref_vk/shaders/shadow.rmiss rename to ref_vk/shaders/ray_shadow.rmiss index 2a5fc9a8..7c39b9ff 100644 --- a/ref_vk/shaders/shadow.rmiss +++ b/ref_vk/shaders/ray_shadow.rmiss @@ -1,9 +1,9 @@ #version 460 core #extension GL_EXT_ray_tracing: require -#include "ray_common.glsl" +#include "ray_shadow_interface.glsl" -layout(location = PAYLOAD_LOCATION_SHADOW) rayPayloadInEXT RayPayloadShadow payload_shadow; +layout(location = 0) rayPayloadInEXT RayPayloadShadow payload_shadow; void main() { payload_shadow.hit_type = SHADOW_MISS; diff --git a/ref_vk/shaders/ray_shadow_interface.glsl b/ref_vk/shaders/ray_shadow_interface.glsl new file mode 100644 index 00000000..28a7dbe3 --- /dev/null +++ b/ref_vk/shaders/ray_shadow_interface.glsl @@ -0,0 +1,11 @@ +#ifndef RAY_SHADOW_INTERFACE_GLSL_INCLUDED +#define RAY_SHADOW_INTERFACE_GLSL_INCLUDED +#define SHADOW_MISS 0 +#define SHADOW_HIT 1 +#define SHADOW_SKY 2 + +struct RayPayloadShadow { + uint hit_type; +}; + +#endif //ifndef RAY_SHADOW_INTERFACE_GLSL_INCLUDED diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 09ec4106..733c9797 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -5,16 +5,216 @@ #include "vk_pipeline.h" #include "vk_buffer.h" -#include "eiface.h" // ARRAYSIZE +typedef struct { + const char *filename; + VkShaderStageFlagBits stage; +} vk_pipeline_shader_stage_t; + +typedef struct { + int closest; + int any; +} vk_pipeline_ray_shader_group_t; + +typedef struct { + const char *debug_name; + VkPipelineLayout layout; + + const vk_pipeline_shader_stage_t *shaders; + int shaders_count; + + struct { + const int *miss; + int miss_count; + + const vk_pipeline_ray_shader_group_t *hit; + int hit_count; + } group; +} vk_pipeline_ray_tracing_create_t; + +typedef struct { + VkPipeline pipeline; + vk_buffer_t sbt_buffer; // TODO suballocate this from a single central buffer or something + struct { + VkStridedDeviceAddressRegionKHR raygen, miss, hit, callable; + } sbt; + char debug_name[32]; +} vk_pipeline_ray_tracing_t; + +static vk_pipeline_ray_tracing_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_tracing_create_t *create) { +#define MAX_SHADER_STAGES 16 +#define MAX_SHADER_GROUPS 16 + vk_pipeline_ray_tracing_t ret = {0}; + VkPipelineShaderStageCreateInfo shaders[MAX_SHADER_STAGES]; + VkRayTracingShaderGroupCreateInfoKHR shader_groups[MAX_SHADER_GROUPS]; + const int shader_groups_count = create->group.hit_count + create->group.miss_count + 1; + int raygen_index = -1; + int group_index = 0; + + const VkRayTracingPipelineCreateInfoKHR rtpci = { + .sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR, + //TODO .flags = VK_PIPELINE_CREATE_RAY_TRACING_NO_NULL_ANY_HIT_SHADERS_BIT_KHR .... + .stageCount = create->shaders_count, + .pStages = shaders, + .groupCount = shader_groups_count, + .pGroups = shader_groups, + .maxPipelineRayRecursionDepth = 1, + .layout = create->layout, + }; + + ASSERT(create->shaders_count <= MAX_SHADER_STAGES); + ASSERT(shader_groups_count <= MAX_SHADER_GROUPS); + + for (int i = 0; i < create->shaders_count; ++i) { + const vk_pipeline_shader_stage_t *const stage = create->shaders + i; + + if (stage->stage == VK_SHADER_STAGE_RAYGEN_BIT_KHR) { + ASSERT(raygen_index == -1); + raygen_index = i; + } + + shaders[i] = (VkPipelineShaderStageCreateInfo){ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = stage->stage, + .module = loadShader(stage->filename), + .pName = "main", + .pSpecializationInfo = NULL, + }; + } + + ASSERT(raygen_index >= 0); + + shader_groups[group_index++] = (VkRayTracingShaderGroupCreateInfoKHR) { + .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, + .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR, + .anyHitShader = VK_SHADER_UNUSED_KHR, + .closestHitShader = VK_SHADER_UNUSED_KHR, + .generalShader = raygen_index, + .intersectionShader = VK_SHADER_UNUSED_KHR, + }; + + for (int i = 0; i < create->group.miss_count; ++i) { + const int miss_index = create->group.miss[i]; + + ASSERT(miss_index >= 0); + ASSERT(miss_index < create->shaders_count); + ASSERT(create->shaders[miss_index].stage == VK_SHADER_STAGE_MISS_BIT_KHR); + + shader_groups[group_index++] = (VkRayTracingShaderGroupCreateInfoKHR) { + .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, + .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR, + .anyHitShader = VK_SHADER_UNUSED_KHR, + .closestHitShader = VK_SHADER_UNUSED_KHR, + .generalShader = miss_index, + .intersectionShader = VK_SHADER_UNUSED_KHR, + }; + } + + for (int i = 0; i < create->group.hit_count; ++i) { + const vk_pipeline_ray_shader_group_t *const group = create->group.hit + i; + const int closest_index = group->closest >= 0 ? group->closest : VK_SHADER_UNUSED_KHR; + const int any_index = group->any >= 0 ? group->any : VK_SHADER_UNUSED_KHR; + + if (closest_index != VK_SHADER_UNUSED_KHR) { + ASSERT(closest_index < create->shaders_count); + ASSERT(create->shaders[closest_index].stage == VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); + } + + if (any_index != VK_SHADER_UNUSED_KHR) { + ASSERT(any_index < create->shaders_count); + ASSERT(create->shaders[any_index].stage == VK_SHADER_STAGE_ANY_HIT_BIT_KHR); + } + + shader_groups[group_index++] = (VkRayTracingShaderGroupCreateInfoKHR) { + .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, + .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR, + .anyHitShader = any_index, + .closestHitShader = closest_index, + .generalShader = VK_SHADER_UNUSED_KHR, + .intersectionShader = VK_SHADER_UNUSED_KHR, + }; + } + + XVK_CHECK(vkCreateRayTracingPipelinesKHR(vk_core.device, VK_NULL_HANDLE, g_pipeline_cache, 1, &rtpci, NULL, &ret.pipeline)); + + for (int i = 0; i < create->shaders_count; ++i) + vkDestroyShaderModule(vk_core.device, shaders[i].module, NULL); + + if (ret.pipeline == VK_NULL_HANDLE) + return ret; + + // TODO: do not allocate sbt buffer per pipeline. make a central buffer and use that + // TODO: does it really need to be host-visible? + { + char buf[64]; + Q_snprintf(buf, sizeof(buf), "%s sbt", create->debug_name); + if (!VK_BufferCreate(buf, &ret.sbt_buffer, shader_groups_count * vk_core.physical_device.sbt_record_size, + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) + { + vkDestroyPipeline(vk_core.device, ret.pipeline, NULL); + ret.pipeline = VK_NULL_HANDLE; + return ret; + } + } + + { + const uint32_t sbt_handle_size = vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupHandleSize; + const uint32_t sbt_handles_buffer_size = shader_groups_count * sbt_handle_size; + uint8_t *sbt_handles = Mem_Malloc(vk_core.pool, sbt_handles_buffer_size); + XVK_CHECK(vkGetRayTracingShaderGroupHandlesKHR(vk_core.device, ret.pipeline, 0, shader_groups_count, sbt_handles_buffer_size, sbt_handles)); + for (int i = 0; i < shader_groups_count; ++i) + { + uint8_t *sbt_dst = ret.sbt_buffer.mapped; + memcpy(sbt_dst + vk_core.physical_device.sbt_record_size * i, sbt_handles + sbt_handle_size * i, sbt_handle_size); + } + Mem_Free(sbt_handles); + } + + { + const VkDeviceAddress sbt_addr = XVK_BufferGetDeviceAddress(ret.sbt_buffer.buffer); + const uint32_t sbt_record_size = vk_core.physical_device.sbt_record_size; + uint32_t index = 0; + + ASSERT(sbt_record_size == 64); // FIXME in shader constant specialization +#define SBT_INDEX(count) (VkStridedDeviceAddressRegionKHR){ \ + .deviceAddress = sbt_addr + sbt_record_size * index, \ + .size = sbt_record_size * (count), \ + .stride = sbt_record_size, \ + }; index += count + ret.sbt.raygen = SBT_INDEX(1); + ret.sbt.miss = SBT_INDEX(create->group.miss_count); + ret.sbt.hit = SBT_INDEX(create->group.hit_count); + ret.sbt.callable = (VkStridedDeviceAddressRegionKHR){ 0 }; + } + + Q_strncpy(ret.debug_name, create->debug_name, sizeof(ret.debug_name)); + + return ret; +} + +static void VK_PipelineRayTracingDestroy(vk_pipeline_ray_tracing_t* pipeline) { + vkDestroyPipeline(vk_core.device, pipeline->pipeline, NULL); + VK_BufferDestroy(&pipeline->sbt_buffer); + pipeline->pipeline = VK_NULL_HANDLE; +} + +static void VK_PipelineRayTracingTrace(VkCommandBuffer cmdbuf, const vk_pipeline_ray_tracing_t *pipeline, uint32_t width, uint32_t height) { + DEBUG_BEGIN(cmdbuf, pipeline->debug_name); + // TODO bind this and accepts descriptors as args? vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline->pipeline); + vkCmdTraceRaysKHR(cmdbuf, &pipeline->sbt.raygen, &pipeline->sbt.miss, &pipeline->sbt.hit, &pipeline->sbt.callable, width, height, 1 ); + DEBUG_END(cmdbuf); +} enum { - // TODO set 0 + // TODO set 0 ? RtLDir_Desc_TLAS, RtLDir_Desc_UBO, RtLDir_Desc_Kusochki, RtLDir_Desc_Indices, RtLDir_Desc_Vertices, RtLDir_Desc_Textures, + + // TODO set 1 RtLDir_Desc_Lights, RtLDir_Desc_LightClusters, @@ -29,6 +229,13 @@ enum { RtLDir_Desc_COUNT }; +#define LIST_SHADER_MODULES(X) \ + X(RayGen, "ray_light_poly_direct.rgen", RAYGEN) \ + X(AlphaTest, "ray_common_alphatest.rahit", ANY_HIT) \ + X(Miss, "ray_shadow.rmiss", MISS) \ + +#define LIST_SBT + static struct { struct { vk_descriptors_t riptors; @@ -39,15 +246,15 @@ static struct { VkDescriptorSet sets[1]; } desc; - VkPipeline pipeline; + vk_pipeline_ray_tracing_t pipeline; } g_ray_light_direct; static void initDescriptors( void ) { g_ray_light_direct.desc.riptors = (vk_descriptors_t) { .bindings = g_ray_light_direct.desc.bindings, - .num_bindings = ARRAYSIZE(g_ray_light_direct.desc.bindings), + .num_bindings = COUNTOF(g_ray_light_direct.desc.bindings), .values = g_ray_light_direct.desc.values, - .num_sets = ARRAYSIZE(g_ray_light_direct.desc.sets), + .num_sets = COUNTOF(g_ray_light_direct.desc.sets), .desc_sets = g_ray_light_direct.desc.sets, /* .push_constants = (VkPushConstantRange){ */ /* .offset = 0, */ @@ -56,12 +263,13 @@ static void initDescriptors( void ) { /* }, */ }; + // FIXME more conservative shader stages #define INIT_BINDING(index, name, type, count) \ g_ray_light_direct.desc.bindings[RtLDir_Desc_##name] = (VkDescriptorSetLayoutBinding){ \ .binding = index, \ .descriptorType = type, \ .descriptorCount = count, \ - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, \ + .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, \ } INIT_BINDING(1, TLAS, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1); @@ -102,6 +310,7 @@ static void updateDescriptors( const xvk_ray_trace_light_direct_t* args ) { .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ }; RAY_LIGHT_DIRECT_OUTPUTS(X) +#undef X g_ray_light_direct.desc.values[RtLDir_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, @@ -130,49 +339,84 @@ static void updateDescriptors( const xvk_ray_trace_light_direct_t* args ) { VK_DescriptorsWrite(&g_ray_light_direct.desc.riptors); } -static VkPipeline createPipeline( void ) { - const vk_pipeline_compute_create_info_t pcci = { - .layout = g_ray_light_direct.desc.riptors.pipeline_layout, - .shader_filename = "ray_light_direct.comp.spv", - .specialization_info = NULL, +static vk_pipeline_ray_tracing_t createPipeline( void ) { + enum { +#define X(name, file, type) \ + ShaderStageIndex_##name, + LIST_SHADER_MODULES(X) +#undef X }; - return VK_PipelineComputeCreate( &pcci ); + const vk_pipeline_shader_stage_t stages[] = { +#define X(name, file, type) \ + {.filename = file ".spv", .stage = VK_SHADER_STAGE_##type##_BIT_KHR}, + LIST_SHADER_MODULES(X) +#undef X + }; + + const int misses[] = { + ShaderStageIndex_Miss, + }; + + const vk_pipeline_ray_shader_group_t hits[] = { + // TODO rigidly specify the expected sbt structure w/ offsets and materials + { // 0: fully opaque: no need for closest nor any hits + .closest = -1, + .any = -1, + }, + { // 1: materials w/ alpha mask: need alpha test + .closest = -1, + .any = ShaderStageIndex_AlphaTest, // TODO these can directly be a string + }, + }; + + const vk_pipeline_ray_tracing_create_t prtc = { + .debug_name = "light direct", + .shaders = stages, + .shaders_count = COUNTOF(stages), + .group = { + .miss = misses, + .miss_count = COUNTOF(misses), + .hit = hits, + .hit_count = COUNTOF(hits), + }, + .layout = g_ray_light_direct.desc.riptors.pipeline_layout, + }; + + return VK_PipelineRayTracingCreate(&prtc); } qboolean XVK_RayTraceLightDirectInit( void ) { initDescriptors(); g_ray_light_direct.pipeline = createPipeline(); - ASSERT(g_ray_light_direct.pipeline != VK_NULL_HANDLE); + ASSERT(g_ray_light_direct.pipeline.pipeline != VK_NULL_HANDLE); return true; } void XVK_RayTraceLightDirectDestroy( void ) { - vkDestroyPipeline(vk_core.device, g_ray_light_direct.pipeline, NULL); + VK_PipelineRayTracingDestroy(&g_ray_light_direct.pipeline); VK_DescriptorsDestroy(&g_ray_light_direct.desc.riptors); } void XVK_RayTraceLightDirectReloadPipeline( void ) { - VkPipeline pipeline = createPipeline(); - if (pipeline == VK_NULL_HANDLE) + vk_pipeline_ray_tracing_t new_pipeline = createPipeline(); + if (new_pipeline.pipeline == VK_NULL_HANDLE) return; - vkDestroyPipeline(vk_core.device, g_ray_light_direct.pipeline, NULL); - g_ray_light_direct.pipeline = pipeline; + VK_PipelineRayTracingDestroy(&g_ray_light_direct.pipeline); + + g_ray_light_direct.pipeline = new_pipeline; } void XVK_RayTraceLightDirect( VkCommandBuffer cmdbuf, const xvk_ray_trace_light_direct_t *args ) { - const uint32_t WG_W = 8; - const uint32_t WG_H = 8; - updateDescriptors( args ); DEBUG_BEGIN(cmdbuf, "lights direct"); - vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_ray_light_direct.pipeline); - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_ray_light_direct.desc.riptors.pipeline_layout, 0, 1, g_ray_light_direct.desc.riptors.desc_sets + 0, 0, NULL); - vkCmdDispatch(cmdbuf, (args->width + WG_W - 1) / WG_W, (args->height + WG_H - 1) / WG_H, 1); + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_light_direct.pipeline.pipeline); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_light_direct.desc.riptors.pipeline_layout, 0, 1, g_ray_light_direct.desc.riptors.desc_sets + 0, 0, NULL); + VK_PipelineRayTracingTrace(cmdbuf, &g_ray_light_direct.pipeline, args->width, args->height); DEBUG_END(cmdbuf); } diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index 0f28f37a..d1199946 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -159,7 +159,7 @@ static VkPipeline createPipeline( void ) { DEFINE_SHADER("ray_primary.rgen", RAYGEN, ShaderStageIndex_RayGen); DEFINE_SHADER("ray_primary.rchit", CLOSEST_HIT, ShaderStageIndex_RayClosestHit); - DEFINE_SHADER("ray_primary_alphatest.rahit", ANY_HIT, ShaderStageIndex_RayAnyHit_AlphaTest); + DEFINE_SHADER("ray_common_alphatest.rahit", ANY_HIT, ShaderStageIndex_RayAnyHit_AlphaTest); DEFINE_SHADER("ray_primary.rmiss", MISS, ShaderStageIndex_RayMiss); // TODO static assert diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 18bf3b0d..1354dcdc 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -1077,7 +1077,8 @@ RAY_PRIMARY_OUTPUTS(X) vkCmdPipelineBarrier(args->cmdbuf, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + //VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, NULL, 0, NULL, ARRAYSIZE(image_barriers), image_barriers); } @@ -1137,7 +1138,8 @@ RAY_PRIMARY_OUTPUTS(X) IMAGE_BARRIER_WRITE(-1/*unused*/, denoised) }; vkCmdPipelineBarrier(args->cmdbuf, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + //VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, NULL, 0, NULL, ARRAYSIZE(image_barriers), image_barriers); } From 94b4066d8d15344bb26f5ac92774633a78dcf4cc Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 20 Jan 2022 18:48:01 -0800 Subject: [PATCH 103/548] rt: move ray pipeline to vk_pipeline --- ref_vk/vk_pipeline.c | 165 ++++++++++++++++++++++++++ ref_vk/vk_pipeline.h | 38 +++++- ref_vk/vk_ray_light_direct.c | 222 ++--------------------------------- 3 files changed, 212 insertions(+), 213 deletions(-) diff --git a/ref_vk/vk_pipeline.c b/ref_vk/vk_pipeline.c index c775ec5b..748a3d05 100644 --- a/ref_vk/vk_pipeline.c +++ b/ref_vk/vk_pipeline.c @@ -161,3 +161,168 @@ VkPipeline VK_PipelineComputeCreate(const vk_pipeline_compute_create_info_t *ci) return pipeline; } + +vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_t *create) { +#define MAX_SHADER_STAGES 16 +#define MAX_SHADER_GROUPS 16 + vk_pipeline_ray_t ret = {0}; + VkPipelineShaderStageCreateInfo stages[MAX_SHADER_STAGES]; + VkRayTracingShaderGroupCreateInfoKHR shader_groups[MAX_SHADER_GROUPS]; + const int shader_groups_count = create->groups.hit_count + create->groups.miss_count + 1; + int raygen_index = -1; + int group_index = 0; + + const VkRayTracingPipelineCreateInfoKHR rtpci = { + .sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR, + //TODO .flags = VK_PIPELINE_CREATE_RAY_TRACING_NO_NULL_ANY_HIT_SHADERS_BIT_KHR .... + .stageCount = create->stages_count, + .pStages = stages, + .groupCount = shader_groups_count, + .pGroups = shader_groups, + .maxPipelineRayRecursionDepth = 1, + .layout = create->layout, + }; + + ASSERT(create->stages_count <= MAX_SHADER_STAGES); + ASSERT(shader_groups_count <= MAX_SHADER_GROUPS); + + for (int i = 0; i < create->stages_count; ++i) { + const vk_shader_stage_t *const stage = create->stages + i; + + if (stage->stage == VK_SHADER_STAGE_RAYGEN_BIT_KHR) { + ASSERT(raygen_index == -1); + raygen_index = i; + } + + stages[i] = (VkPipelineShaderStageCreateInfo){ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = stage->stage, + .module = loadShader(stage->filename), + .pName = "main", + .pSpecializationInfo = stage->specialization_info, + }; + } + + ASSERT(raygen_index >= 0); + + shader_groups[group_index++] = (VkRayTracingShaderGroupCreateInfoKHR) { + .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, + .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR, + .anyHitShader = VK_SHADER_UNUSED_KHR, + .closestHitShader = VK_SHADER_UNUSED_KHR, + .generalShader = raygen_index, + .intersectionShader = VK_SHADER_UNUSED_KHR, + }; + + for (int i = 0; i < create->groups.miss_count; ++i) { + const int miss_index = create->groups.miss[i]; + + ASSERT(miss_index >= 0); + ASSERT(miss_index < create->stages_count); + ASSERT(create->stages[miss_index].stage == VK_SHADER_STAGE_MISS_BIT_KHR); + + shader_groups[group_index++] = (VkRayTracingShaderGroupCreateInfoKHR) { + .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, + .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR, + .anyHitShader = VK_SHADER_UNUSED_KHR, + .closestHitShader = VK_SHADER_UNUSED_KHR, + .generalShader = miss_index, + .intersectionShader = VK_SHADER_UNUSED_KHR, + }; + } + + for (int i = 0; i < create->groups.hit_count; ++i) { + const vk_pipeline_ray_hit_group_t *const groups = create->groups.hit + i; + const int closest_index = groups->closest >= 0 ? groups->closest : VK_SHADER_UNUSED_KHR; + const int any_index = groups->any >= 0 ? groups->any : VK_SHADER_UNUSED_KHR; + + if (closest_index != VK_SHADER_UNUSED_KHR) { + ASSERT(closest_index < create->stages_count); + ASSERT(create->stages[closest_index].stage == VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); + } + + if (any_index != VK_SHADER_UNUSED_KHR) { + ASSERT(any_index < create->stages_count); + ASSERT(create->stages[any_index].stage == VK_SHADER_STAGE_ANY_HIT_BIT_KHR); + } + + shader_groups[group_index++] = (VkRayTracingShaderGroupCreateInfoKHR) { + .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, + .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR, + .anyHitShader = any_index, + .closestHitShader = closest_index, + .generalShader = VK_SHADER_UNUSED_KHR, + .intersectionShader = VK_SHADER_UNUSED_KHR, + }; + } + + XVK_CHECK(vkCreateRayTracingPipelinesKHR(vk_core.device, VK_NULL_HANDLE, g_pipeline_cache, 1, &rtpci, NULL, &ret.pipeline)); + + for (int i = 0; i < create->stages_count; ++i) + vkDestroyShaderModule(vk_core.device, stages[i].module, NULL); + + if (ret.pipeline == VK_NULL_HANDLE) + return ret; + + // TODO: do not allocate sbt buffer per pipeline. make a central buffer and use that + // TODO: does it really need to be host-visible? + { + char buf[64]; + Q_snprintf(buf, sizeof(buf), "%s sbt", create->debug_name); + if (!VK_BufferCreate(buf, &ret.sbt_buffer, shader_groups_count * vk_core.physical_device.sbt_record_size, + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) + { + vkDestroyPipeline(vk_core.device, ret.pipeline, NULL); + ret.pipeline = VK_NULL_HANDLE; + return ret; + } + } + + { + const uint32_t sbt_handle_size = vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupHandleSize; + const uint32_t sbt_handles_buffer_size = shader_groups_count * sbt_handle_size; + uint8_t *sbt_handles = Mem_Malloc(vk_core.pool, sbt_handles_buffer_size); + XVK_CHECK(vkGetRayTracingShaderGroupHandlesKHR(vk_core.device, ret.pipeline, 0, shader_groups_count, sbt_handles_buffer_size, sbt_handles)); + for (int i = 0; i < shader_groups_count; ++i) + { + uint8_t *sbt_dst = ret.sbt_buffer.mapped; + memcpy(sbt_dst + vk_core.physical_device.sbt_record_size * i, sbt_handles + sbt_handle_size * i, sbt_handle_size); + } + Mem_Free(sbt_handles); + } + + { + const VkDeviceAddress sbt_addr = XVK_BufferGetDeviceAddress(ret.sbt_buffer.buffer); + const uint32_t sbt_record_size = vk_core.physical_device.sbt_record_size; + uint32_t index = 0; + + ASSERT(sbt_record_size == 64); // FIXME in shader constant specialization +#define SBT_INDEX(count) (VkStridedDeviceAddressRegionKHR){ \ + .deviceAddress = sbt_addr + sbt_record_size * index, \ + .size = sbt_record_size * (count), \ + .stride = sbt_record_size, \ + }; index += count + ret.sbt.raygen = SBT_INDEX(1); + ret.sbt.miss = SBT_INDEX(create->groups.miss_count); + ret.sbt.hit = SBT_INDEX(create->groups.hit_count); + ret.sbt.callable = (VkStridedDeviceAddressRegionKHR){ 0 }; + } + + Q_strncpy(ret.debug_name, create->debug_name, sizeof(ret.debug_name)); + + return ret; +} + +void VK_PipelineRayTracingDestroy(vk_pipeline_ray_t* pipeline) { + vkDestroyPipeline(vk_core.device, pipeline->pipeline, NULL); + VK_BufferDestroy(&pipeline->sbt_buffer); + pipeline->pipeline = VK_NULL_HANDLE; +} + +void VK_PipelineRayTracingTrace(VkCommandBuffer cmdbuf, const vk_pipeline_ray_t *pipeline, uint32_t width, uint32_t height) { + DEBUG_BEGIN(cmdbuf, pipeline->debug_name); + // TODO bind this and accepts descriptors as args? vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline->pipeline); + vkCmdTraceRaysKHR(cmdbuf, &pipeline->sbt.raygen, &pipeline->sbt.miss, &pipeline->sbt.hit, &pipeline->sbt.callable, width, height, 1 ); + DEBUG_END(cmdbuf); +} diff --git a/ref_vk/vk_pipeline.h b/ref_vk/vk_pipeline.h index 3582d7f3..c39d1e85 100644 --- a/ref_vk/vk_pipeline.h +++ b/ref_vk/vk_pipeline.h @@ -1,4 +1,5 @@ #include "vk_core.h" +#include "vk_buffer.h" typedef struct { const char *filename; @@ -41,7 +42,42 @@ typedef struct { VkPipeline VK_PipelineComputeCreate(const vk_pipeline_compute_create_info_t *ci); +typedef struct { + int closest; + int any; +} vk_pipeline_ray_hit_group_t; + +typedef struct { + const char *debug_name; + VkPipelineLayout layout; + + const vk_shader_stage_t *stages; + int stages_count; + + struct { + const int *miss; + int miss_count; + + const vk_pipeline_ray_hit_group_t *hit; + int hit_count; + } groups; +} vk_pipeline_ray_create_info_t; + +typedef struct { + VkPipeline pipeline; + vk_buffer_t sbt_buffer; // TODO suballocate this from a single central buffer or something + struct { + VkStridedDeviceAddressRegionKHR raygen, miss, hit, callable; + } sbt; + char debug_name[32]; +} vk_pipeline_ray_t; + +vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_t *create); +void VK_PipelineRayTracingTrace(VkCommandBuffer cmdbuf, const vk_pipeline_ray_t *pipeline, uint32_t width, uint32_t height); +void VK_PipelineRayTracingDestroy(vk_pipeline_ray_t* pipeline); + + qboolean VK_PipelineInit( void ); void VK_PipelineShutdown( void ); -extern VkPipelineCache g_pipeline_cache; \ No newline at end of file +extern VkPipelineCache g_pipeline_cache; diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 733c9797..656fe00a 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -5,205 +5,6 @@ #include "vk_pipeline.h" #include "vk_buffer.h" -typedef struct { - const char *filename; - VkShaderStageFlagBits stage; -} vk_pipeline_shader_stage_t; - -typedef struct { - int closest; - int any; -} vk_pipeline_ray_shader_group_t; - -typedef struct { - const char *debug_name; - VkPipelineLayout layout; - - const vk_pipeline_shader_stage_t *shaders; - int shaders_count; - - struct { - const int *miss; - int miss_count; - - const vk_pipeline_ray_shader_group_t *hit; - int hit_count; - } group; -} vk_pipeline_ray_tracing_create_t; - -typedef struct { - VkPipeline pipeline; - vk_buffer_t sbt_buffer; // TODO suballocate this from a single central buffer or something - struct { - VkStridedDeviceAddressRegionKHR raygen, miss, hit, callable; - } sbt; - char debug_name[32]; -} vk_pipeline_ray_tracing_t; - -static vk_pipeline_ray_tracing_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_tracing_create_t *create) { -#define MAX_SHADER_STAGES 16 -#define MAX_SHADER_GROUPS 16 - vk_pipeline_ray_tracing_t ret = {0}; - VkPipelineShaderStageCreateInfo shaders[MAX_SHADER_STAGES]; - VkRayTracingShaderGroupCreateInfoKHR shader_groups[MAX_SHADER_GROUPS]; - const int shader_groups_count = create->group.hit_count + create->group.miss_count + 1; - int raygen_index = -1; - int group_index = 0; - - const VkRayTracingPipelineCreateInfoKHR rtpci = { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR, - //TODO .flags = VK_PIPELINE_CREATE_RAY_TRACING_NO_NULL_ANY_HIT_SHADERS_BIT_KHR .... - .stageCount = create->shaders_count, - .pStages = shaders, - .groupCount = shader_groups_count, - .pGroups = shader_groups, - .maxPipelineRayRecursionDepth = 1, - .layout = create->layout, - }; - - ASSERT(create->shaders_count <= MAX_SHADER_STAGES); - ASSERT(shader_groups_count <= MAX_SHADER_GROUPS); - - for (int i = 0; i < create->shaders_count; ++i) { - const vk_pipeline_shader_stage_t *const stage = create->shaders + i; - - if (stage->stage == VK_SHADER_STAGE_RAYGEN_BIT_KHR) { - ASSERT(raygen_index == -1); - raygen_index = i; - } - - shaders[i] = (VkPipelineShaderStageCreateInfo){ - .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - .stage = stage->stage, - .module = loadShader(stage->filename), - .pName = "main", - .pSpecializationInfo = NULL, - }; - } - - ASSERT(raygen_index >= 0); - - shader_groups[group_index++] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR, - .anyHitShader = VK_SHADER_UNUSED_KHR, - .closestHitShader = VK_SHADER_UNUSED_KHR, - .generalShader = raygen_index, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - - for (int i = 0; i < create->group.miss_count; ++i) { - const int miss_index = create->group.miss[i]; - - ASSERT(miss_index >= 0); - ASSERT(miss_index < create->shaders_count); - ASSERT(create->shaders[miss_index].stage == VK_SHADER_STAGE_MISS_BIT_KHR); - - shader_groups[group_index++] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR, - .anyHitShader = VK_SHADER_UNUSED_KHR, - .closestHitShader = VK_SHADER_UNUSED_KHR, - .generalShader = miss_index, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - } - - for (int i = 0; i < create->group.hit_count; ++i) { - const vk_pipeline_ray_shader_group_t *const group = create->group.hit + i; - const int closest_index = group->closest >= 0 ? group->closest : VK_SHADER_UNUSED_KHR; - const int any_index = group->any >= 0 ? group->any : VK_SHADER_UNUSED_KHR; - - if (closest_index != VK_SHADER_UNUSED_KHR) { - ASSERT(closest_index < create->shaders_count); - ASSERT(create->shaders[closest_index].stage == VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); - } - - if (any_index != VK_SHADER_UNUSED_KHR) { - ASSERT(any_index < create->shaders_count); - ASSERT(create->shaders[any_index].stage == VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - } - - shader_groups[group_index++] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR, - .anyHitShader = any_index, - .closestHitShader = closest_index, - .generalShader = VK_SHADER_UNUSED_KHR, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - } - - XVK_CHECK(vkCreateRayTracingPipelinesKHR(vk_core.device, VK_NULL_HANDLE, g_pipeline_cache, 1, &rtpci, NULL, &ret.pipeline)); - - for (int i = 0; i < create->shaders_count; ++i) - vkDestroyShaderModule(vk_core.device, shaders[i].module, NULL); - - if (ret.pipeline == VK_NULL_HANDLE) - return ret; - - // TODO: do not allocate sbt buffer per pipeline. make a central buffer and use that - // TODO: does it really need to be host-visible? - { - char buf[64]; - Q_snprintf(buf, sizeof(buf), "%s sbt", create->debug_name); - if (!VK_BufferCreate(buf, &ret.sbt_buffer, shader_groups_count * vk_core.physical_device.sbt_record_size, - VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) - { - vkDestroyPipeline(vk_core.device, ret.pipeline, NULL); - ret.pipeline = VK_NULL_HANDLE; - return ret; - } - } - - { - const uint32_t sbt_handle_size = vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupHandleSize; - const uint32_t sbt_handles_buffer_size = shader_groups_count * sbt_handle_size; - uint8_t *sbt_handles = Mem_Malloc(vk_core.pool, sbt_handles_buffer_size); - XVK_CHECK(vkGetRayTracingShaderGroupHandlesKHR(vk_core.device, ret.pipeline, 0, shader_groups_count, sbt_handles_buffer_size, sbt_handles)); - for (int i = 0; i < shader_groups_count; ++i) - { - uint8_t *sbt_dst = ret.sbt_buffer.mapped; - memcpy(sbt_dst + vk_core.physical_device.sbt_record_size * i, sbt_handles + sbt_handle_size * i, sbt_handle_size); - } - Mem_Free(sbt_handles); - } - - { - const VkDeviceAddress sbt_addr = XVK_BufferGetDeviceAddress(ret.sbt_buffer.buffer); - const uint32_t sbt_record_size = vk_core.physical_device.sbt_record_size; - uint32_t index = 0; - - ASSERT(sbt_record_size == 64); // FIXME in shader constant specialization -#define SBT_INDEX(count) (VkStridedDeviceAddressRegionKHR){ \ - .deviceAddress = sbt_addr + sbt_record_size * index, \ - .size = sbt_record_size * (count), \ - .stride = sbt_record_size, \ - }; index += count - ret.sbt.raygen = SBT_INDEX(1); - ret.sbt.miss = SBT_INDEX(create->group.miss_count); - ret.sbt.hit = SBT_INDEX(create->group.hit_count); - ret.sbt.callable = (VkStridedDeviceAddressRegionKHR){ 0 }; - } - - Q_strncpy(ret.debug_name, create->debug_name, sizeof(ret.debug_name)); - - return ret; -} - -static void VK_PipelineRayTracingDestroy(vk_pipeline_ray_tracing_t* pipeline) { - vkDestroyPipeline(vk_core.device, pipeline->pipeline, NULL); - VK_BufferDestroy(&pipeline->sbt_buffer); - pipeline->pipeline = VK_NULL_HANDLE; -} - -static void VK_PipelineRayTracingTrace(VkCommandBuffer cmdbuf, const vk_pipeline_ray_tracing_t *pipeline, uint32_t width, uint32_t height) { - DEBUG_BEGIN(cmdbuf, pipeline->debug_name); - // TODO bind this and accepts descriptors as args? vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline->pipeline); - vkCmdTraceRaysKHR(cmdbuf, &pipeline->sbt.raygen, &pipeline->sbt.miss, &pipeline->sbt.hit, &pipeline->sbt.callable, width, height, 1 ); - DEBUG_END(cmdbuf); -} enum { // TODO set 0 ? @@ -234,8 +35,6 @@ enum { X(AlphaTest, "ray_common_alphatest.rahit", ANY_HIT) \ X(Miss, "ray_shadow.rmiss", MISS) \ -#define LIST_SBT - static struct { struct { vk_descriptors_t riptors; @@ -246,7 +45,7 @@ static struct { VkDescriptorSet sets[1]; } desc; - vk_pipeline_ray_tracing_t pipeline; + vk_pipeline_ray_t pipeline; } g_ray_light_direct; static void initDescriptors( void ) { @@ -339,17 +138,16 @@ static void updateDescriptors( const xvk_ray_trace_light_direct_t* args ) { VK_DescriptorsWrite(&g_ray_light_direct.desc.riptors); } -static vk_pipeline_ray_tracing_t createPipeline( void ) { +static vk_pipeline_ray_t createPipeline( void ) { enum { #define X(name, file, type) \ ShaderStageIndex_##name, LIST_SHADER_MODULES(X) #undef X }; - - const vk_pipeline_shader_stage_t stages[] = { +const vk_shader_stage_t stages[] = { #define X(name, file, type) \ - {.filename = file ".spv", .stage = VK_SHADER_STAGE_##type##_BIT_KHR}, + {.filename = file ".spv", .stage = VK_SHADER_STAGE_##type##_BIT_KHR, .specialization_info = NULL}, LIST_SHADER_MODULES(X) #undef X }; @@ -358,7 +156,7 @@ static vk_pipeline_ray_tracing_t createPipeline( void ) { ShaderStageIndex_Miss, }; - const vk_pipeline_ray_shader_group_t hits[] = { + const vk_pipeline_ray_hit_group_t hits[] = { // TODO rigidly specify the expected sbt structure w/ offsets and materials { // 0: fully opaque: no need for closest nor any hits .closest = -1, @@ -370,11 +168,11 @@ static vk_pipeline_ray_tracing_t createPipeline( void ) { }, }; - const vk_pipeline_ray_tracing_create_t prtc = { + const vk_pipeline_ray_create_info_t prtc = { .debug_name = "light direct", - .shaders = stages, - .shaders_count = COUNTOF(stages), - .group = { + .stages = stages, + .stages_count = COUNTOF(stages), + .groups = { .miss = misses, .miss_count = COUNTOF(misses), .hit = hits, @@ -401,7 +199,7 @@ void XVK_RayTraceLightDirectDestroy( void ) { } void XVK_RayTraceLightDirectReloadPipeline( void ) { - vk_pipeline_ray_tracing_t new_pipeline = createPipeline(); + const vk_pipeline_ray_t new_pipeline = createPipeline(); if (new_pipeline.pipeline == VK_NULL_HANDLE) return; From 23b4bdd4f83e93e4b00b1ab46a1c38e98d6b358d Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 20 Jan 2022 20:04:43 -0800 Subject: [PATCH 104/548] rt: consolidate specialization data --- ref_vk/shaders/additive.rahit | 1 - ref_vk/shaders/alphamask.rahit | 1 - ref_vk/shaders/light.glsl | 3 --- ref_vk/shaders/ray.rchit | 1 - ref_vk/shaders/ray.rgen | 11 -------- ref_vk/shaders/ray_common_alphatest.rahit | 1 - ref_vk/shaders/ray_interop.h | 31 ++++++++++++++++++----- ref_vk/shaders/ray_light_poly_direct.rgen | 1 - ref_vk/shaders/ray_primary.rchit | 1 - ref_vk/vk_pipeline.c | 1 - ref_vk/vk_ray_light_direct.c | 19 +++++++++++++- 11 files changed, 43 insertions(+), 28 deletions(-) diff --git a/ref_vk/shaders/additive.rahit b/ref_vk/shaders/additive.rahit index db71122b..4461d00c 100644 --- a/ref_vk/shaders/additive.rahit +++ b/ref_vk/shaders/additive.rahit @@ -5,7 +5,6 @@ #include "ray_common.glsl" #include "ray_kusochki.glsl" -layout (constant_id = 6) const uint MAX_TEXTURES = 4096; layout (set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; layout (push_constant) uniform PC_ { diff --git a/ref_vk/shaders/alphamask.rahit b/ref_vk/shaders/alphamask.rahit index 45fa7eda..30bc2569 100644 --- a/ref_vk/shaders/alphamask.rahit +++ b/ref_vk/shaders/alphamask.rahit @@ -5,7 +5,6 @@ #include "ray_common.glsl" #include "ray_kusochki.glsl" -layout (constant_id = 6) const uint MAX_TEXTURES = 4096; layout (set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; hitAttributeEXT vec2 bary; diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index d3827b0d..fc2f5e81 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -1,6 +1,3 @@ -layout (constant_id = 4) const float LIGHT_GRID_CELL_SIZE = 128.;// FIXME 256.; -layout (constant_id = 5) const uint MAX_LIGHT_CLUSTERS = 262144;// FIXME 32768; -layout (constant_id = 6) const uint MAX_TEXTURES = 4096; layout (set = 0, binding = BINDING_LIGHTS) uniform UBOLights { Lights lights; }; // TODO this is pretty much static and should be a buffer, not UBO layout (set = 0, binding = BINDING_LIGHT_CLUSTERS, align = 1) readonly buffer UBOLightClusters { ivec3 grid_min, grid_size; diff --git a/ref_vk/shaders/ray.rchit b/ref_vk/shaders/ray.rchit index f36a1915..e87837b5 100644 --- a/ref_vk/shaders/ray.rchit +++ b/ref_vk/shaders/ray.rchit @@ -5,7 +5,6 @@ #include "ray_kusochki.glsl" #include "ray_common.glsl" -layout (constant_id = 6) const uint MAX_TEXTURES = 4096; layout (set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; layout (set = 0, binding = 13) uniform samplerCube skybox; diff --git a/ref_vk/shaders/ray.rgen b/ref_vk/shaders/ray.rgen index 6ec445ab..a8f49db2 100644 --- a/ref_vk/shaders/ray.rgen +++ b/ref_vk/shaders/ray.rgen @@ -19,17 +19,6 @@ const float color_factor = 1.; const float color_culling_threshold = 0;//600./color_factor; const float throughput_threshold = 1e-3; -layout (constant_id = 4) const float LIGHT_GRID_CELL_SIZE = 256.; -layout (constant_id = 5) const uint MAX_LIGHT_CLUSTERS = 32768; -layout (constant_id = 6) const uint MAX_TEXTURES = 4096; -layout (constant_id = 7) const uint SBT_RECORD_SIZE = 64; - -//const uint LIGHT_CLUSTER_SIZE = 2 + MAX_VISIBLE_POINT_LIGHTS + MAX_VISIBLE_SURFACE_LIGHTS; -//const uint LIGHT_CLUSTER_NUM_DLIGHTS_OFFSET = 0; -//const uint LIGHT_CLUSTER_NUM_EMISSIVE_SURFACES_OFFSET = 1; -//const uint LIGHT_CLUSTER_DLIGHTS_DATA_OFFSET = 2; -//const uint LIGHT_CLUSTER_EMISSIVE_SURFACES_DATA_OFFSET = 3 + MAX_VISIBLE_DLIGHTS; - layout(set = 0, binding = 0, rgba8) uniform image2D out_image_base_color; layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; layout(set = 0, binding = 9, rgba16f) uniform image2D out_image_diffuse_gi; diff --git a/ref_vk/shaders/ray_common_alphatest.rahit b/ref_vk/shaders/ray_common_alphatest.rahit index 3adf7c7a..9279651b 100644 --- a/ref_vk/shaders/ray_common_alphatest.rahit +++ b/ref_vk/shaders/ray_common_alphatest.rahit @@ -5,7 +5,6 @@ #include "ray_primary_common.glsl" #include "ray_kusochki.glsl" -layout(constant_id = 6) const uint MAX_TEXTURES = 4096; layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; // TODO not really needed here? diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index 912e5fab..368c2b55 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -2,6 +2,16 @@ #ifndef RAY_INTEROP_H_INCLUDED #define RAY_INTEROP_H_INCLUDED +#define LIST_SPECIALIZATION_CONSTANTS(X) \ + X(0, uint, MAX_POINT_LIGHTS, 256) \ + X(1, uint, MAX_EMISSIVE_KUSOCHKI, 256) \ + X(2, uint, MAX_VISIBLE_POINT_LIGHTS, 63) \ + X(3, uint, MAX_VISIBLE_SURFACE_LIGHTS, 255) \ + X(4, float, LIGHT_GRID_CELL_SIZE, 128.) \ + X(5, uint, MAX_LIGHT_CLUSTERS, 262144) \ + X(6, uint, MAX_TEXTURES, 4096) \ + X(7, uint, SBT_RECORD_SIZE, 32) \ + #ifndef GLSL #include "xash3d_types.h" #define MAX_EMISSIVE_KUSOCHKI 256 @@ -14,17 +24,26 @@ #define TOKENPASTE2(x, y) TOKENPASTE(x, y) #define PAD(x) float TOKENPASTE2(pad_, __LINE__)[x]; #define STRUCT struct -#else + +enum { +#define DECLARE_SPECIALIZATION_CONSTANT(index, type, name, default_value) \ + SPEC_##name##_INDEX = index, +LIST_SPECIALIZATION_CONSTANTS(DECLARE_SPECIALIZATION_CONSTANT) +#undef DECLARE_SPECIALIZATION_CONSTANT +}; + +#else // if GLSL else #extension GL_EXT_shader_8bit_storage : require #define PAD(x) #define STRUCT -layout (constant_id = 0) const uint MAX_POINT_LIGHTS = 256; -layout (constant_id = 1) const uint MAX_EMISSIVE_KUSOCHKI = 256; -layout (constant_id = 2) const uint MAX_VISIBLE_POINT_LIGHTS = 63; -layout (constant_id = 3) const uint MAX_VISIBLE_SURFACE_LIGHTS = 255; -#endif +#define DECLARE_SPECIALIZATION_CONSTANT(index, type, name, default_value) \ + layout (constant_id = index) const type name = default_value; +LIST_SPECIALIZATION_CONSTANTS(DECLARE_SPECIALIZATION_CONSTANT) +#undef DECLARE_SPECIALIZATION_CONSTANT + +#endif // not GLSL #define GEOMETRY_BIT_OPAQUE 0x01 #define GEOMETRY_BIT_ADDITIVE 0x02 diff --git a/ref_vk/shaders/ray_light_poly_direct.rgen b/ref_vk/shaders/ray_light_poly_direct.rgen index 762ba33d..42147938 100644 --- a/ref_vk/shaders/ray_light_poly_direct.rgen +++ b/ref_vk/shaders/ray_light_poly_direct.rgen @@ -20,7 +20,6 @@ RAY_LIGHT_DIRECT_OUTPUTS(X) layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; -layout (constant_id = 7) const uint SBT_RECORD_SIZE = 64; #include "ray_kusochki.glsl" diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index d5f4fe7e..289969ac 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -8,7 +8,6 @@ #include "ray_kusochki.glsl" -layout(constant_id = 6) const uint MAX_TEXTURES = 4096; layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; diff --git a/ref_vk/vk_pipeline.c b/ref_vk/vk_pipeline.c index 748a3d05..e0c5c73f 100644 --- a/ref_vk/vk_pipeline.c +++ b/ref_vk/vk_pipeline.c @@ -297,7 +297,6 @@ vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_ const uint32_t sbt_record_size = vk_core.physical_device.sbt_record_size; uint32_t index = 0; - ASSERT(sbt_record_size == 64); // FIXME in shader constant specialization #define SBT_INDEX(count) (VkStridedDeviceAddressRegionKHR){ \ .deviceAddress = sbt_addr + sbt_record_size * index, \ .size = sbt_record_size * (count), \ diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 656fe00a..ae49ebe7 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -4,6 +4,7 @@ #include "vk_descriptor.h" #include "vk_pipeline.h" #include "vk_buffer.h" +#include "shaders/ray_interop.h" enum { @@ -139,6 +140,22 @@ static void updateDescriptors( const xvk_ray_trace_light_direct_t* args ) { } static vk_pipeline_ray_t createPipeline( void ) { + // FIXME move this into vk_pipeline + const struct SpecializationData { + uint32_t sbt_record_size; + } spec_data = { + .sbt_record_size = vk_core.physical_device.sbt_record_size, + }; + const VkSpecializationMapEntry spec_map[] = { + {.constantID = SPEC_SBT_RECORD_SIZE_INDEX, .offset = offsetof(struct SpecializationData, sbt_record_size), .size = sizeof(uint32_t) }, + }; + VkSpecializationInfo spec = { + .mapEntryCount = COUNTOF(spec_map), + .pMapEntries = spec_map, + .dataSize = sizeof(spec_data), + .pData = &spec_data, + }; + enum { #define X(name, file, type) \ ShaderStageIndex_##name, @@ -147,7 +164,7 @@ static vk_pipeline_ray_t createPipeline( void ) { }; const vk_shader_stage_t stages[] = { #define X(name, file, type) \ - {.filename = file ".spv", .stage = VK_SHADER_STAGE_##type##_BIT_KHR, .specialization_info = NULL}, + {.filename = file ".spv", .stage = VK_SHADER_STAGE_##type##_BIT_KHR, .specialization_info = &spec}, LIST_SHADER_MODULES(X) #undef X }; From 5b4a22e97ee9605da11b1ce4831ba2556c2c7cee Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 20 Jan 2022 20:08:46 -0800 Subject: [PATCH 105/548] rt: remove ray_query extension dependency --- ref_vk/vk_core.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index 8e65db6a..4c84f3e2 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -137,7 +137,6 @@ static const char* device_extensions[] = { VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME, VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME, - VK_KHR_RAY_QUERY_EXTENSION_NAME, // FIXME make this not depend on RTX #ifdef USE_AFTERMATH @@ -499,39 +498,44 @@ static qboolean createDevice( void ) { VkPhysicalDeviceAccelerationStructureFeaturesKHR accel_feature = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR, - .pNext = NULL, + .pNext = head, .accelerationStructure = VK_TRUE, }; + head = &accel_feature; VkPhysicalDevice16BitStorageFeatures sixteen_bit_feature = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES, - .pNext = &accel_feature, + .pNext = head, .storageBuffer16BitAccess = VK_TRUE, }; + head = &sixteen_bit_feature; VkPhysicalDeviceVulkan12Features vk12_features = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, - .pNext = &sixteen_bit_feature, + .pNext = head, .shaderSampledImageArrayNonUniformIndexing = VK_TRUE, // Needed for texture sampling in closest hit shader .storageBuffer8BitAccess = VK_TRUE, .uniformAndStorageBuffer8BitAccess = VK_TRUE, .bufferDeviceAddress = VK_TRUE, }; + head = &vk12_features; VkPhysicalDeviceRayTracingPipelineFeaturesKHR ray_tracing_pipeline_feature = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR, - .pNext = &vk12_features, + .pNext = head, .rayTracingPipeline = VK_TRUE, // TODO .rayTraversalPrimitiveCulling = VK_TRUE, }; + + if (vk_core.rtx) { + head = &ray_tracing_pipeline_feature; + } else { + head = NULL; + } + VkPhysicalDeviceFeatures2 features = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, - .pNext = vk_core.rtx ? &ray_tracing_pipeline_feature: NULL, + .pNext = head, .features.samplerAnisotropy = candidate_device->features.features.samplerAnisotropy, }; - VkPhysicalDeviceRayQueryFeaturesKHR ray_query_feature = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR, - .pNext = &features, - .rayQuery = VK_TRUE, - }; - void *head = &ray_query_feature; + head = &features; #ifdef USE_AFTERMATH VkDeviceDiagnosticsConfigCreateInfoNV diag_config_nv = { From 70eb2fed6f56b7bb02715678de1b7cb1185c1a00 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 20 Jan 2022 20:09:21 -0800 Subject: [PATCH 106/548] rt: read tangents and normal map --- ref_vk/shaders/ray_primary.rchit | 13 ++++++++++++- ref_vk/shaders/rt_geometry.glsl | 10 ++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index 289969ac..ccb67030 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -21,7 +21,7 @@ vec4 sampleTexture(uint tex_index, vec2 uv, vec4 uv_lods) { } void main() { - const Geometry geom = readHitGeometry(); + Geometry geom = readHitGeometry(); payload.hit_t = vec4(geom.pos, gl_HitTEXT); @@ -33,8 +33,19 @@ void main() { payload.base_color_a = vec4(1.,0.,1.,1.); } else { payload.base_color_a = sampleTexture(tex_base_color, geom.uv, geom.uv_lods) * kusok.color; + + const uint tex_normal = kusok.tex_normalmap; + vec3 T = geom.tangent; + if (tex_normal > 0 && dot(T,T) > .5) { + T = normalize(T - dot(T, geom.normal_shading) * geom.normal_shading); + const vec3 B = normalize(cross(geom.normal_shading, T)); + const mat3 TBN = mat3(T, B, geom.normal_shading); + const vec3 tnorm = sampleTexture(tex_normal, geom.uv, geom.uv_lods).xyz * 2. - 1.; // TODO is this sampling correct for normal data? + geom.normal_shading = normalize(TBN * tnorm); + } } + payload.normals_gs.xy = normalEncode(geom.normal_geometry); payload.normals_gs.zw = normalEncode(geom.normal_shading); } diff --git a/ref_vk/shaders/rt_geometry.glsl b/ref_vk/shaders/rt_geometry.glsl index b807fdc2..45687422 100644 --- a/ref_vk/shaders/rt_geometry.glsl +++ b/ref_vk/shaders/rt_geometry.glsl @@ -52,6 +52,7 @@ struct Geometry { vec3 normal_geometry; vec3 normal_shading; + vec3 tangent; int kusok_index; }; @@ -89,11 +90,16 @@ Geometry readHitGeometry() { // NOTE: only support rotations, for arbitrary transform would need to do transpose(inverse(mat3(gl_ObjectToWorldEXT))) const mat3 normalTransform = mat3(gl_ObjectToWorldEXT); - geom.normal_shading = normalTransform * baryMix( + geom.normal_shading = normalize(normalTransform * baryMix( vertices[vi1].normal, vertices[vi2].normal, vertices[vi3].normal, - bary); + bary)); + geom.tangent = normalize(normalTransform * baryMix( + vertices[vi1].tangent, + vertices[vi2].tangent, + vertices[vi3].tangent, + bary)); geom.uv_lods = computeAnisotropicEllipseAxes(geom.pos, geom.normal_geometry, gl_WorldRayDirectionEXT, ubo.ray_cone_width * gl_HitTEXT, pos, uvs, geom.uv); From 7b7d08a944b47d786910638ee254496380a75e28 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 20 Jan 2022 20:21:06 -0800 Subject: [PATCH 107/548] rt: make primary ray use ray pipeline framework --- ref_vk/vk_ray_light_direct.c | 8 +- ref_vk/vk_ray_primary.c | 235 +++++++++++------------------------ 2 files changed, 74 insertions(+), 169 deletions(-) diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index ae49ebe7..626ba017 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -228,10 +228,8 @@ void XVK_RayTraceLightDirectReloadPipeline( void ) { void XVK_RayTraceLightDirect( VkCommandBuffer cmdbuf, const xvk_ray_trace_light_direct_t *args ) { updateDescriptors( args ); - DEBUG_BEGIN(cmdbuf, "lights direct"); - vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_light_direct.pipeline.pipeline); - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_light_direct.desc.riptors.pipeline_layout, 0, 1, g_ray_light_direct.desc.riptors.desc_sets + 0, 0, NULL); - VK_PipelineRayTracingTrace(cmdbuf, &g_ray_light_direct.pipeline, args->width, args->height); - DEBUG_END(cmdbuf); + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_light_direct.pipeline.pipeline); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_light_direct.desc.riptors.pipeline_layout, 0, 1, g_ray_light_direct.desc.riptors.desc_sets + 0, 0, NULL); + VK_PipelineRayTracingTrace(cmdbuf, &g_ray_light_direct.pipeline, args->width, args->height); } diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index d1199946..c6dbcecc 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -7,18 +7,6 @@ #include "eiface.h" // ARRAYSIZE -enum { - RtPrim_SBT_RayGen, - - RtPrim_SBT_RayMiss, - - RtPrim_SBT_RayHit, - RtPrim_SBT_RayHit_WithAlphaTest, - RtPrim_SBT_RayHit_END = RtPrim_SBT_RayHit_WithAlphaTest, - - RtPrim_SBT_COUNT, -}; - enum { // TODO set 0 RtPrim_Desc_TLAS, @@ -46,9 +34,7 @@ static struct { VkDescriptorSet sets[1]; } desc; - vk_buffer_t sbt_buffer; - - VkPipeline pipeline; + vk_pipeline_ray_t pipeline; } g_ray_primary; static void initDescriptors( void ) { @@ -123,183 +109,104 @@ RAY_PRIMARY_OUTPUTS(X) VK_DescriptorsWrite(&g_ray_primary.desc.riptors); } -static VkPipeline createPipeline( void ) { - VkPipeline pipeline; - - enum { - ShaderStageIndex_RayGen, - ShaderStageIndex_RayMiss, - ShaderStageIndex_RayClosestHit, - ShaderStageIndex_RayAnyHit_AlphaTest, - ShaderStageIndex_COUNT, +static vk_pipeline_ray_t createPipeline( void ) { + // FIXME move this into vk_pipeline + const struct SpecializationData { + uint32_t sbt_record_size; + } spec_data = { + .sbt_record_size = vk_core.physical_device.sbt_record_size, + }; + const VkSpecializationMapEntry spec_map[] = { + {.constantID = SPEC_SBT_RECORD_SIZE_INDEX, .offset = offsetof(struct SpecializationData, sbt_record_size), .size = sizeof(uint32_t) }, + }; + VkSpecializationInfo spec = { + .mapEntryCount = COUNTOF(spec_map), + .pMapEntries = spec_map, + .dataSize = sizeof(spec_data), + .pData = &spec_data, }; - VkPipelineShaderStageCreateInfo shaders[ShaderStageIndex_COUNT]; - VkRayTracingShaderGroupCreateInfoKHR shader_groups[RtPrim_SBT_COUNT]; +#define LIST_SHADER_MODULES(X) \ + X(RayGen, "ray_primary.rgen", RAYGEN) \ + X(Miss, "ray_primary.rmiss", MISS) \ + X(HitClosest, "ray_primary.rchit", CLOSEST_HIT) \ + X(HitAnyAlphaTest, "ray_common_alphatest.rahit", ANY_HIT) \ - const VkRayTracingPipelineCreateInfoKHR rtpci = { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR, - //TODO .flags = VK_PIPELINE_CREATE_RAY_TRACING_NO_NULL_ANY_HIT_SHADERS_BIT_KHR .... - .stageCount = ARRAYSIZE(shaders), - .pStages = shaders, - .groupCount = ARRAYSIZE(shader_groups), - .pGroups = shader_groups, - .maxPipelineRayRecursionDepth = 1, + enum { +#define X(name, file, type) \ + ShaderStageIndex_##name, + LIST_SHADER_MODULES(X) +#undef X + }; + +const vk_shader_stage_t stages[] = { +#define X(name, file, type) \ + {.filename = file ".spv", .stage = VK_SHADER_STAGE_##type##_BIT_KHR, .specialization_info = &spec}, + LIST_SHADER_MODULES(X) +#undef X + }; + + const int misses[] = { + ShaderStageIndex_Miss, + }; + + const vk_pipeline_ray_hit_group_t hits[] = { + // TODO rigidly specify the expected sbt structure w/ offsets and materials + { // 0: fully opaque: no need for closest nor any hits + .closest = ShaderStageIndex_HitClosest, + .any = -1, + }, + { // 1: materials w/ alpha mask: need alpha test + .closest = ShaderStageIndex_HitClosest, + .any = ShaderStageIndex_HitAnyAlphaTest, // TODO these can directly be a string + }, + }; + + const vk_pipeline_ray_create_info_t prtc = { + .debug_name = "primary ray", + .stages = stages, + .stages_count = COUNTOF(stages), + .groups = { + .miss = misses, + .miss_count = COUNTOF(misses), + .hit = hits, + .hit_count = COUNTOF(hits), + }, .layout = g_ray_primary.desc.riptors.pipeline_layout, }; -#define DEFINE_SHADER(filename, bit, sbt_index) \ - shaders[sbt_index] = (VkPipelineShaderStageCreateInfo){ \ - .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, \ - .stage = VK_SHADER_STAGE_##bit##_BIT_KHR, \ - .module = loadShader(filename ".spv"), \ - .pName = "main", \ - .pSpecializationInfo = NULL, \ - } - - DEFINE_SHADER("ray_primary.rgen", RAYGEN, ShaderStageIndex_RayGen); - DEFINE_SHADER("ray_primary.rchit", CLOSEST_HIT, ShaderStageIndex_RayClosestHit); - DEFINE_SHADER("ray_common_alphatest.rahit", ANY_HIT, ShaderStageIndex_RayAnyHit_AlphaTest); - DEFINE_SHADER("ray_primary.rmiss", MISS, ShaderStageIndex_RayMiss); - - // TODO static assert -#define ASSERT_SHADER_OFFSET(sbt_kind, sbt_index, offset) \ - ASSERT((offset) == (sbt_index - sbt_kind)) - - ASSERT_SHADER_OFFSET(RtPrim_SBT_RayGen, RtPrim_SBT_RayGen, 0); - ASSERT_SHADER_OFFSET(RtPrim_SBT_RayMiss, RtPrim_SBT_RayMiss, SHADER_OFFSET_MISS_REGULAR); - - ASSERT_SHADER_OFFSET(RtPrim_SBT_RayHit, RtPrim_SBT_RayHit, SHADER_OFFSET_HIT_REGULAR_BASE + SHADER_OFFSET_HIT_REGULAR); - ASSERT_SHADER_OFFSET(RtPrim_SBT_RayHit, RtPrim_SBT_RayHit_WithAlphaTest, SHADER_OFFSET_HIT_REGULAR_BASE + SHADER_OFFSET_HIT_ALPHA_TEST); - - //ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_Additive, SHADER_OFFSET_HIT_REGULAR_BASE + SHADER_OFFSET_HIT_ADDITIVE); - - shader_groups[RtPrim_SBT_RayGen] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR, - .anyHitShader = VK_SHADER_UNUSED_KHR, - .closestHitShader = VK_SHADER_UNUSED_KHR, - .generalShader = ShaderStageIndex_RayGen, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - - shader_groups[RtPrim_SBT_RayHit] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR, - .anyHitShader = VK_SHADER_UNUSED_KHR, - .closestHitShader = ShaderStageIndex_RayClosestHit, - .generalShader = VK_SHADER_UNUSED_KHR, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - - shader_groups[RtPrim_SBT_RayHit_WithAlphaTest] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR, - .anyHitShader = ShaderStageIndex_RayAnyHit_AlphaTest, - .closestHitShader = ShaderStageIndex_RayClosestHit, - .generalShader = VK_SHADER_UNUSED_KHR, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - - shader_groups[RtPrim_SBT_RayMiss] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR, - .anyHitShader = VK_SHADER_UNUSED_KHR, - .closestHitShader = VK_SHADER_UNUSED_KHR, - .generalShader = ShaderStageIndex_RayMiss, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - - XVK_CHECK(vkCreateRayTracingPipelinesKHR(vk_core.device, VK_NULL_HANDLE, g_pipeline_cache, 1, &rtpci, NULL, &pipeline)); - - for (int i = 0; i < ARRAYSIZE(shaders); ++i) - vkDestroyShaderModule(vk_core.device, shaders[i].module, NULL); - - if (pipeline == VK_NULL_HANDLE) - return VK_NULL_HANDLE; - - { - const uint32_t sbt_handle_size = vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupHandleSize; - const uint32_t sbt_handles_buffer_size = ARRAYSIZE(shader_groups) * sbt_handle_size; - uint8_t *sbt_handles = Mem_Malloc(vk_core.pool, sbt_handles_buffer_size); - XVK_CHECK(vkGetRayTracingShaderGroupHandlesKHR(vk_core.device, pipeline, 0, ARRAYSIZE(shader_groups), sbt_handles_buffer_size, sbt_handles)); - for (int i = 0; i < ARRAYSIZE(shader_groups); ++i) - { - uint8_t *sbt_dst = g_ray_primary.sbt_buffer.mapped; - memcpy(sbt_dst + vk_core.physical_device.sbt_record_size * i, sbt_handles + sbt_handle_size * i, sbt_handle_size); - } - Mem_Free(sbt_handles); - } - - return pipeline; + return VK_PipelineRayTracingCreate(&prtc); } qboolean XVK_RayTracePrimaryInit( void ) { - if (!VK_BufferCreate("primary ray sbt_buffer", &g_ray_primary.sbt_buffer, RtPrim_SBT_COUNT * vk_core.physical_device.sbt_record_size, - VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) - { - return false; - } - initDescriptors(); g_ray_primary.pipeline = createPipeline(); - ASSERT(g_ray_primary.pipeline != VK_NULL_HANDLE); + ASSERT(g_ray_primary.pipeline.pipeline != VK_NULL_HANDLE); return true; } void XVK_RayTracePrimaryDestroy( void ) { - vkDestroyPipeline(vk_core.device, g_ray_primary.pipeline, NULL); + VK_PipelineRayTracingDestroy(&g_ray_primary.pipeline); VK_DescriptorsDestroy(&g_ray_primary.desc.riptors); - - VK_BufferDestroy(&g_ray_primary.sbt_buffer); } void XVK_RayTracePrimaryReloadPipeline( void ) { - VkPipeline pipeline = createPipeline(); - if (pipeline == VK_NULL_HANDLE) + const vk_pipeline_ray_t new_pipeline = createPipeline(); + if (new_pipeline.pipeline == VK_NULL_HANDLE) return; - vkDestroyPipeline(vk_core.device, g_ray_primary.pipeline, NULL); - g_ray_primary.pipeline = pipeline; + VK_PipelineRayTracingDestroy(&g_ray_primary.pipeline); + + g_ray_primary.pipeline = new_pipeline; } void XVK_RayTracePrimary( VkCommandBuffer cmdbuf, const xvk_ray_trace_primary_t *args ) { updateDescriptors( args ); - vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_primary.pipeline); - /* { */ - /* vk_rtx_push_constants_t push_constants = { */ - /* .time = gpGlobals->time, */ - /* .random_seed = (uint32_t)gEngine.COM_RandomLong(0, INT32_MAX), */ - /* .bounces = vk_rtx_bounces->value, */ - /* .pixel_cone_spread_angle = atanf((2.0f*tanf(DEG2RAD(fov_angle_y) * 0.5f)) / (float)FRAME_HEIGHT), */ - /* .debug_light_index_begin = (uint32_t)(vk_rtx_light_begin->value), */ - /* .debug_light_index_end = (uint32_t)(vk_rtx_light_end->value), */ - /* .flags = r_lightmap->value ? PUSH_FLAG_LIGHTMAP_ONLY : 0, */ - /* }; */ - /* vkCmdPushConstants(cmdbuf, g_ray_primary.descriptors.pipeline_layout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, 0, sizeof(push_constants), &push_constants); */ - /* } */ - - DEBUG_BEGIN(cmdbuf, "primary"); + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_primary.pipeline.pipeline); vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_primary.desc.riptors.pipeline_layout, 0, 1, g_ray_primary.desc.riptors.desc_sets + 0, 0, NULL); - - { - const uint32_t sbt_record_size = vk_core.physical_device.sbt_record_size; -#define SBT_INDEX(index, count) { \ -.deviceAddress = XVK_BufferGetDeviceAddress(g_ray_primary.sbt_buffer.buffer) + sbt_record_size * index, \ -.size = sbt_record_size * (count), \ -.stride = sbt_record_size, \ -} - const VkStridedDeviceAddressRegionKHR sbt_raygen = SBT_INDEX(RtPrim_SBT_RayGen, 1); - const VkStridedDeviceAddressRegionKHR sbt_miss = SBT_INDEX(RtPrim_SBT_RayMiss, 1); //ShaderBindingTable_Miss_Empty - ShaderBindingTable_Miss); - const VkStridedDeviceAddressRegionKHR sbt_hit = SBT_INDEX(RtPrim_SBT_RayHit, RtPrim_SBT_RayHit_END - RtPrim_SBT_RayHit); - const VkStridedDeviceAddressRegionKHR sbt_callable = { 0 }; - - vkCmdTraceRaysKHR(cmdbuf, &sbt_raygen, &sbt_miss, &sbt_hit, &sbt_callable, args->width, args->height, 1 ); - } - DEBUG_END(cmdbuf); + VK_PipelineRayTracingTrace(cmdbuf, &g_ray_primary.pipeline, args->width, args->height); } From e295f39c2b595a9cf501ccf930d4b00351d0fbc5 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 21 Jan 2022 09:52:16 -0800 Subject: [PATCH 108/548] rt: fix for X macro redefinition --- ref_vk/vk_ray_primary.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index c6dbcecc..9673d7ed 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -83,6 +83,7 @@ static void updateDescriptors( const xvk_ray_trace_primary_t* args ) { .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ }; RAY_PRIMARY_OUTPUTS(X) +#undef X g_ray_primary.desc.values[RtPrim_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, From 202eccb3d1eed34e72b28d9bf04a3a6531d7f372 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 21 Jan 2022 20:55:43 -0800 Subject: [PATCH 109/548] rt: cosolidate pass args in a central structure --- ref_vk/shaders/light.glsl | 2 +- ref_vk/vk_ray_light_direct.c | 22 +++--- ref_vk/vk_ray_light_direct.h | 31 +------- ref_vk/vk_ray_primary.c | 20 +++--- ref_vk/vk_ray_primary.h | 25 +------ ref_vk/vk_ray_resources.h | 32 +++++++++ ref_vk/vk_rtx.c | 136 +++++++++++++---------------------- 7 files changed, 109 insertions(+), 159 deletions(-) create mode 100644 ref_vk/vk_ray_resources.h diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index fc2f5e81..b48a9d96 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -120,7 +120,7 @@ void computeLighting(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialPro sampleEmissiveSurfaces(P, N, throughput, view_dir, material, cluster_index, diffuse, specular); #endif -#if 1 +#if 0 vec3 ldiffuse = vec3(0.), lspecular = vec3(0.); computePointLights(P, N, cluster_index, throughput, view_dir, material, ldiffuse, lspecular); diffuse += ldiffuse; diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 626ba017..0f10dea2 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -93,11 +93,11 @@ static void initDescriptors( void ) { VK_DescriptorsCreate(&g_ray_light_direct.desc.riptors); } -static void updateDescriptors( const xvk_ray_trace_light_direct_t* args ) { +static void updateDescriptors( const vk_ray_resources_t *res ) { #define X(index, name, ...) \ g_ray_light_direct.desc.values[RtLDir_Desc_##name].image = (VkDescriptorImageInfo){ \ .sampler = VK_NULL_HANDLE, \ - .imageView = args->in.name, \ + .imageView = res->primary.name, \ .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ }; RAY_LIGHT_DIRECT_INPUTS(X) @@ -106,7 +106,7 @@ static void updateDescriptors( const xvk_ray_trace_light_direct_t* args ) { #define X(index, name, ...) \ g_ray_light_direct.desc.values[RtLDir_Desc_##name].image = (VkDescriptorImageInfo){ \ .sampler = VK_NULL_HANDLE, \ - .imageView = args->out.name, \ + .imageView = res->light_direct_polygon.name, \ .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ }; RAY_LIGHT_DIRECT_OUTPUTS(X) @@ -115,14 +115,14 @@ static void updateDescriptors( const xvk_ray_trace_light_direct_t* args ) { g_ray_light_direct.desc.values[RtLDir_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, .accelerationStructureCount = 1, - .pAccelerationStructures = &args->in.tlas, + .pAccelerationStructures = &res->scene.tlas, }; #define DESC_SET_BUFFER(index, buffer_) \ g_ray_light_direct.desc.values[index].buffer = (VkDescriptorBufferInfo){ \ - .buffer = args->in.buffer_.buffer, \ - .offset = args->in.buffer_.offset, \ - .range = args->in.buffer_.size, \ + .buffer = res->scene.buffer_.buffer, \ + .offset = res->scene.buffer_.offset, \ + .range = res->scene.buffer_.size, \ } DESC_SET_BUFFER(RtLDir_Desc_UBO, ubo); @@ -134,7 +134,7 @@ static void updateDescriptors( const xvk_ray_trace_light_direct_t* args ) { #undef DESC_SET_BUFFER - g_ray_light_direct.desc.values[RtLDir_Desc_Textures].image_array = args->in.all_textures; + g_ray_light_direct.desc.values[RtLDir_Desc_Textures].image_array = res->scene.all_textures; VK_DescriptorsWrite(&g_ray_light_direct.desc.riptors); } @@ -225,11 +225,11 @@ void XVK_RayTraceLightDirectReloadPipeline( void ) { g_ray_light_direct.pipeline = new_pipeline; } -void XVK_RayTraceLightDirect( VkCommandBuffer cmdbuf, const xvk_ray_trace_light_direct_t *args ) { - updateDescriptors( args ); +void XVK_RayTraceLightDirect( VkCommandBuffer cmdbuf, const vk_ray_resources_t *res ) { + updateDescriptors( res ); vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_light_direct.pipeline.pipeline); vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_light_direct.desc.riptors.pipeline_layout, 0, 1, g_ray_light_direct.desc.riptors.desc_sets + 0, 0, NULL); - VK_PipelineRayTracingTrace(cmdbuf, &g_ray_light_direct.pipeline, args->width, args->height); + VK_PipelineRayTracingTrace(cmdbuf, &g_ray_light_direct.pipeline, res->width, res->height); } diff --git a/ref_vk/vk_ray_light_direct.h b/ref_vk/vk_ray_light_direct.h index 4a209bde..63371ba4 100644 --- a/ref_vk/vk_ray_light_direct.h +++ b/ref_vk/vk_ray_light_direct.h @@ -1,35 +1,8 @@ #pragma once -#include "vk_core.h" -#include "vk_rtx.h" -#include "shaders/ray_light_direct_iface.h" +#include "vk_ray_resources.h" qboolean XVK_RayTraceLightDirectInit( void ); void XVK_RayTraceLightDirectDestroy( void ); void XVK_RayTraceLightDirectReloadPipeline( void ); -typedef struct { - uint32_t width, height; - - struct { - // TODO separate desc set - VkAccelerationStructureKHR tlas; - - // needed for alpha testing :( - vk_buffer_region_t ubo; - vk_buffer_region_t kusochki, indices, vertices; - VkDescriptorImageInfo *all_textures; // [MAX_TEXTURES] - - vk_buffer_region_t lights; - vk_buffer_region_t light_clusters; - -#define X(index, name, ...) VkImageView name; - RAY_LIGHT_DIRECT_INPUTS(X) - } in; - - struct { - RAY_LIGHT_DIRECT_OUTPUTS(X) -#undef X - } out; -} xvk_ray_trace_light_direct_t; - -void XVK_RayTraceLightDirect( VkCommandBuffer cmdbuf, const xvk_ray_trace_light_direct_t *args ); +void XVK_RayTraceLightDirect( VkCommandBuffer cmdbuf, const vk_ray_resources_t *res ); diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index 9673d7ed..94e4ae55 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -75,11 +75,11 @@ RAY_PRIMARY_OUTPUTS(X) VK_DescriptorsCreate(&g_ray_primary.desc.riptors); } -static void updateDescriptors( const xvk_ray_trace_primary_t* args ) { +static void updateDescriptors( const vk_ray_resources_t* res ) { #define X(index, name, ...) \ g_ray_primary.desc.values[RtPrim_Desc_Out_##name].image = (VkDescriptorImageInfo){ \ .sampler = VK_NULL_HANDLE, \ - .imageView = args->out.name, \ + .imageView = res->primary.name, \ .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ }; RAY_PRIMARY_OUTPUTS(X) @@ -88,14 +88,14 @@ RAY_PRIMARY_OUTPUTS(X) g_ray_primary.desc.values[RtPrim_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, .accelerationStructureCount = 1, - .pAccelerationStructures = &args->in.tlas, + .pAccelerationStructures = &res->scene.tlas, }; #define DESC_SET_BUFFER(index, buffer_) \ g_ray_primary.desc.values[index].buffer = (VkDescriptorBufferInfo){ \ - .buffer = args->in.buffer_.buffer, \ - .offset = args->in.buffer_.offset, \ - .range = args->in.buffer_.size, \ + .buffer = res->scene.buffer_.buffer, \ + .offset = res->scene.buffer_.offset, \ + .range = res->scene.buffer_.size, \ } DESC_SET_BUFFER(RtPrim_Desc_UBO, ubo); @@ -105,7 +105,7 @@ RAY_PRIMARY_OUTPUTS(X) #undef DESC_SET_BUFFER - g_ray_primary.desc.values[RtPrim_Desc_Textures].image_array = args->in.all_textures; + g_ray_primary.desc.values[RtPrim_Desc_Textures].image_array = res->scene.all_textures; VK_DescriptorsWrite(&g_ray_primary.desc.riptors); } @@ -203,11 +203,11 @@ void XVK_RayTracePrimaryReloadPipeline( void ) { g_ray_primary.pipeline = new_pipeline; } -void XVK_RayTracePrimary( VkCommandBuffer cmdbuf, const xvk_ray_trace_primary_t *args ) { - updateDescriptors( args ); +void XVK_RayTracePrimary( VkCommandBuffer cmdbuf, const vk_ray_resources_t *res ) { + updateDescriptors( res ); vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_primary.pipeline.pipeline); vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_primary.desc.riptors.pipeline_layout, 0, 1, g_ray_primary.desc.riptors.desc_sets + 0, 0, NULL); - VK_PipelineRayTracingTrace(cmdbuf, &g_ray_primary.pipeline, args->width, args->height); + VK_PipelineRayTracingTrace(cmdbuf, &g_ray_primary.pipeline, res->width, res->height); } diff --git a/ref_vk/vk_ray_primary.h b/ref_vk/vk_ray_primary.h index 6be4944c..cae4c291 100644 --- a/ref_vk/vk_ray_primary.h +++ b/ref_vk/vk_ray_primary.h @@ -1,28 +1,9 @@ #pragma once -#include "vk_core.h" -#include "vk_rtx.h" -#include "shaders/ray_primary_iface.h" + +#include "vk_ray_resources.h" qboolean XVK_RayTracePrimaryInit( void ); void XVK_RayTracePrimaryDestroy( void ); void XVK_RayTracePrimaryReloadPipeline( void ); -typedef struct { - uint32_t width, height; - - struct { - // TODO separate desc set - VkAccelerationStructureKHR tlas; - vk_buffer_region_t ubo; - vk_buffer_region_t kusochki, indices, vertices; - VkDescriptorImageInfo *all_textures; // [MAX_TEXTURES] - } in; - - struct { -#define X(index, name, ...) VkImageView name; -RAY_PRIMARY_OUTPUTS(X) -#undef X - } out; -} xvk_ray_trace_primary_t; - -void XVK_RayTracePrimary( VkCommandBuffer cmdbuf, const xvk_ray_trace_primary_t *args ); +void XVK_RayTracePrimary( VkCommandBuffer cmdbuf, const vk_ray_resources_t *res ); diff --git a/ref_vk/vk_ray_resources.h b/ref_vk/vk_ray_resources.h new file mode 100644 index 00000000..c1b8ad1e --- /dev/null +++ b/ref_vk/vk_ray_resources.h @@ -0,0 +1,32 @@ +#pragma once + +#include "vk_rtx.h" + +#include "shaders/ray_primary_iface.h" +#include "shaders/ray_light_direct_iface.h" + +typedef struct { + uint32_t width, height; + + struct { + VkAccelerationStructureKHR tlas; + + vk_buffer_region_t ubo; + vk_buffer_region_t kusochki, indices, vertices; + VkDescriptorImageInfo *all_textures; // [MAX_TEXTURES] + + vk_buffer_region_t lights; + vk_buffer_region_t light_clusters; + } scene; + +#define X(index, name, ...) VkImageView name; + struct { + RAY_PRIMARY_OUTPUTS(X) + } primary; + + struct { + RAY_LIGHT_DIRECT_OUTPUTS(X) + } light_direct_polygon; +#undef X +} vk_ray_resources_t; + diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 1354dcdc..b3895e35 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -968,6 +968,54 @@ static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int fr } static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_args_t* args, int frame_index, const xvk_ray_frame_images_t *current_frame, float fov_angle_y) { + const vk_ray_resources_t res = { + .width = FRAME_WIDTH, + .height = FRAME_HEIGHT, + .scene = { + .tlas = g_rtx.tlas, + .ubo = { + .buffer = g_rtx.uniform_buffer.buffer, + .offset = frame_index * g_rtx.uniform_unit_size, + .size = sizeof(struct UniformBuffer), + }, + .kusochki = { + .buffer = g_ray_model_state.kusochki_buffer.buffer, + .offset = 0, + .size = g_ray_model_state.kusochki_buffer.size, + }, + .indices = { + .buffer = args->geometry_data.buffer, + .offset = 0, + .size = args->geometry_data.size, + }, + .vertices = { + .buffer = args->geometry_data.buffer, + .offset = 0, + .size = args->geometry_data.size, + }, + .all_textures = tglob.dii_all_textures, + .lights = { + .buffer = g_ray_model_state.lights_buffer.buffer, + .offset = 0, + .size = VK_WHOLE_SIZE, // TODO multiple frames + }, + .light_clusters = { + .buffer = g_rtx.light_grid_buffer.buffer, + .offset = 0, + .size = VK_WHOLE_SIZE, // TODO multiple frames + }, + }, +#define X(index, name, ...) .name = current_frame->name.view, + .primary = { + RAY_PRIMARY_OUTPUTS(X) + }, + .light_direct_polygon = { + RAY_LIGHT_DIRECT_OUTPUTS(X) + } +#undef X + }; + + DEBUG_BEGIN(cmdbuf, "yay tracing"); uploadLights(); prepareTlas(cmdbuf); @@ -1030,44 +1078,7 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar 0, 0, NULL, ARRAYSIZE(bmb), bmb, ARRAYSIZE(image_barrier), image_barrier); } - { - const xvk_ray_trace_primary_t primary_args = { - .width = FRAME_WIDTH, - .height = FRAME_HEIGHT, - .in = { - .tlas = g_rtx.tlas, - .ubo = { - .buffer = g_rtx.uniform_buffer.buffer, - .offset = frame_index * g_rtx.uniform_unit_size, - .size = sizeof(struct UniformBuffer), - }, - .kusochki = { - .buffer = g_ray_model_state.kusochki_buffer.buffer, - .offset = 0, - .size = g_ray_model_state.kusochki_buffer.size, - }, - .indices = { - .buffer = args->geometry_data.buffer, - .offset = 0, - .size = args->geometry_data.size, - }, - .vertices = { - .buffer = args->geometry_data.buffer, - .offset = 0, - .size = args->geometry_data.size, - }, - .all_textures = tglob.dii_all_textures, - }, - .out = { -#define X(index, name, ...) .name = current_frame->name.view, -RAY_PRIMARY_OUTPUTS(X) -#undef X - }, - }; - XVK_RayTracePrimary( cmdbuf, &primary_args ); - } - - //rayTrace(cmdbuf, current_frame, fov_angle_y); + XVK_RayTracePrimary( cmdbuf, &res ); { const VkImageMemoryBarrier image_barriers[] = { @@ -1082,54 +1093,7 @@ RAY_PRIMARY_OUTPUTS(X) 0, 0, NULL, 0, NULL, ARRAYSIZE(image_barriers), image_barriers); } - { - const xvk_ray_trace_light_direct_t light_direct_args = { - .width = FRAME_WIDTH, - .height = FRAME_HEIGHT, - .in = { - .tlas = g_rtx.tlas, - .ubo = { - .buffer = g_rtx.uniform_buffer.buffer, - .offset = frame_index * g_rtx.uniform_unit_size, - .size = sizeof(struct UniformBuffer), - }, - .kusochki = { - .buffer = g_ray_model_state.kusochki_buffer.buffer, - .offset = 0, - .size = g_ray_model_state.kusochki_buffer.size, - }, - .indices = { - .buffer = args->geometry_data.buffer, - .offset = 0, - .size = args->geometry_data.size, - }, - .vertices = { - .buffer = args->geometry_data.buffer, - .offset = 0, - .size = args->geometry_data.size, - }, - .lights = { - .buffer = g_ray_model_state.lights_buffer.buffer, - .offset = 0, - .size = VK_WHOLE_SIZE, // TODO multiple frames - }, - .light_clusters = { - .buffer = g_rtx.light_grid_buffer.buffer, - .offset = 0, - .size = VK_WHOLE_SIZE, // TODO multiple frames - }, - .all_textures = tglob.dii_all_textures, -#define X(index, name, ...) .name = current_frame->name.view, - RAY_LIGHT_DIRECT_INPUTS(X) - }, - .out = { - RAY_LIGHT_DIRECT_OUTPUTS(X) -#undef X - }, - }; - XVK_RayTraceLightDirect( cmdbuf, &light_direct_args ); - } - + XVK_RayTraceLightDirect( cmdbuf, &res ); { const VkImageMemoryBarrier image_barriers[] = { From 21ff19be831ae07593151dfeef508d4b1be6967a Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 22 Jan 2022 18:54:11 -0800 Subject: [PATCH 110/548] rt: convert denoiser to use resources --- ref_vk/shaders/denoiser.comp | 57 ++++++------ ref_vk/shaders/ray_light_direct.comp | 4 +- ref_vk/shaders/ray_light_direct_iface.h | 4 +- ref_vk/shaders/ray_light_poly_direct.rgen | 16 +--- ref_vk/vk_denoiser.c | 107 +++++----------------- ref_vk/vk_denoiser.h | 19 +--- ref_vk/vk_ray_light_direct.c | 4 +- ref_vk/vk_ray_primary.c | 2 +- ref_vk/vk_ray_resources.h | 10 +- ref_vk/vk_rtx.c | 28 +----- 10 files changed, 72 insertions(+), 179 deletions(-) diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 87090f75..7fb43dc8 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -3,16 +3,18 @@ #include "noise.glsl" #include "utils.glsl" -layout(local_size_x = 16, local_size_y = 8, local_size_z = 1) in; +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; layout(set = 0, binding = 0, rgba16f) uniform image2D dest; layout(set = 0, binding = 1, rgba8) uniform readonly image2D src_base_color; -layout(set = 0, binding = 2, rgba16f) uniform readonly image2D src_diffuse_gi; -layout(set = 0, binding = 3, rgba16f) uniform readonly image2D src_specular; -layout(set = 0, binding = 4, rgba16f) uniform readonly image2D src_additive; -layout(set = 0, binding = 5, rgba16f) uniform readonly image2D src_normals; -layout(set = 0, binding = 6, rgba32f) uniform readonly image2D src_position_t; +layout(set = 0, binding = 2, rgba16f) uniform readonly image2D src_light_direct_poly_diffuse; +//layout(set = 0, binding = 3, rgba16f) uniform readonly image2D src_light_direct_poly_specular; +//layout(set = 0, binding = 2, rgba16f) uniform readonly image2D src_light_direct_poly_diffuse; +/* layout(set = 0, binding = 3, rgba16f) uniform readonly image2D src_specular; */ +/* layout(set = 0, binding = 4, rgba16f) uniform readonly image2D src_additive; */ +/* layout(set = 0, binding = 5, rgba16f) uniform readonly image2D src_normals; */ +/* layout(set = 0, binding = 6, rgba32f) uniform readonly image2D src_position_t; */ // Blatantly copypasted from https://www.shadertoy.com/view/XsGfWV vec3 aces_tonemap(vec3 color){ @@ -44,11 +46,11 @@ vec3 reinhard02(vec3 c, vec3 Cwhite2) { float normpdf2(in float x2, in float sigma) { return 0.39894*exp(-0.5*x2/(sigma*sigma))/sigma; } float normpdf(in float x, in float sigma) { return normpdf2(x*x, sigma); } -void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { - const vec4 n = imageLoad(src_normals, uv); - geometry_normal = normalDecode(n.xy); - shading_normal = normalDecode(n.zw); -} +/* void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { */ +/* const vec4 n = imageLoad(src_normals, uv); */ +/* geometry_normal = normalDecode(n.xy); */ +/* shading_normal = normalDecode(n.zw); */ +/* } */ void main() { ivec2 res = ivec2(imageSize(src_base_color)); @@ -67,20 +69,20 @@ void main() { /* } */ const vec4 base_color = imageLoad(src_base_color, pix); - const float material_index = imageLoad(src_diffuse_gi, pix).a; + //const float material_index = imageLoad(src_light_direct_poly, pix).a; //imageStore(dest, pix, vec4(aces_tonemap(base_color.rgb), 0.)); return; //imageStore(dest, pix, vec4((base_color.rgb), 0.)); return; //imageStore(dest, pix, vec4(fract(imageLoad(src_position_t, pix).rgb / 10.), 0.)); return; - //imageStore(dest, pix, vec4((imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; - //imageStore(dest, pix, vec4((imageLoad(src_diffuse_gi, pix).rgb * base_color.rgb), 0.)); return; + //imageStore(dest, pix, vec4((imageLoad(src_light_direct_poly, pix).rgb), 0.)); return; + //imageStore(dest, pix, vec4((imageLoad(src_light_direct_poly, pix).rgb * base_color.rgb), 0.)); return; //imageStore(dest, pix, vec4(imageLoad(src_normals, pix)*.5 + .5f)); return; //imageStore(dest, pix, vec4(imageLoad(src_specular, pix)*.5 + .5f)); return; - //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_diffuse_gi, pix).rgb), 0.)); return; + //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_light_direct_poly, pix).rgb), 0.)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_specular, pix).rgb), 0.)); return; - vec3 geometry_normal, shading_normal; - readNormals(pix, geometry_normal, shading_normal); + /* vec3 geometry_normal, shading_normal; */ + /* readNormals(pix, geometry_normal, shading_normal); */ //imageStore(dest, pix, vec4(.5 + geometry_normal * .5, 0.)); return; @@ -89,11 +91,12 @@ void main() { /* return; */ #if 1 - vec3 colour = imageLoad(src_diffuse_gi, pix).rgb * base_color.rgb; + vec3 colour = imageLoad(src_light_direct_poly_diffuse, pix).rgb * base_color.rgb; + //colour += imageLoad(src_light_direct_poly_specular, pix).rgb * base_color.rgb; #else float total_scale = 0.; vec3 colour = vec3(0.); - const int KERNEL_SIZE = 8; + const int KERNEL_SIZE = 16; float specular_total_scale = 0.; vec3 speculour = vec3(0.); for (int x = -KERNEL_SIZE; x <= KERNEL_SIZE; ++x) @@ -103,22 +106,22 @@ void main() { continue; } - const vec4 c = imageLoad(src_diffuse_gi, p); - if (c.a != material_index) - continue; + // const vec4 c = imageLoad(src_light_direct_poly, p); + // if (c.a != material_index) + // continue; vec3 sample_geometry_normal, sample_shading_normal; readNormals(p, sample_geometry_normal, sample_shading_normal); // FIXME also filter by depth, (kusok index?), etc - if ( dot(sample_geometry_normal, geometry_normal) < .99 ) + if ( dot(sample_geometry_normal, geometry_normal) < .9 ) continue; // TODO bilaterally filter shading normals const float sigma = KERNEL_SIZE / 2.; const float scale = normpdf(x, sigma) * normpdf(y, sigma); - colour += scale * imageLoad(src_diffuse_gi, p).rgb; + colour += scale * imageLoad(src_light_direct_poly, p).rgb; total_scale += scale; const int SPECULAR_KERNEL_SIZE = 2; @@ -138,11 +141,11 @@ void main() { if (specular_total_scale > 0.) { speculour /= specular_total_scale; //speculour *= base_color.rgb; - colour += speculour; + //colour += speculour; } //colour += imageLoad(src_specular, pix).rgb; - colour += imageLoad(src_additive, pix).rgb; + //colour += imageLoad(src_additive, pix).rgb; #endif // HACK: exposure @@ -169,5 +172,5 @@ void main() { #endif imageStore(dest, pix, vec4(colour, 0.)); - //imageStore(dest, pix, imageLoad(src_diffuse_gi, pix)); + //imageStore(dest, pix, imageLoad(src_light_direct_poly, pix)); } diff --git a/ref_vk/shaders/ray_light_direct.comp b/ref_vk/shaders/ray_light_direct.comp index 06a65d0e..571ab7de 100644 --- a/ref_vk/shaders/ray_light_direct.comp +++ b/ref_vk/shaders/ray_light_direct.comp @@ -67,7 +67,7 @@ void main() { //specular = shading_normal; //diffuse = geometry_normal; //diffuse = shading_normal; - imageStore(out_image_light_diffuse, pix, vec4(diffuse, 0.f)); + imageStore(out_image_light_poly_diffuse, pix, vec4(diffuse, 0.f)); //imageStore(out_image_light_diffuse, pix, vec4(1.,0.,0.f, 0.f)); - imageStore(out_image_light_specular, pix, vec4(specular, 0.f)); + imageStore(out_image_light_poly_specular, pix, vec4(specular, 0.f)); } diff --git a/ref_vk/shaders/ray_light_direct_iface.h b/ref_vk/shaders/ray_light_direct_iface.h index 92b3b786..bbab5183 100644 --- a/ref_vk/shaders/ray_light_direct_iface.h +++ b/ref_vk/shaders/ray_light_direct_iface.h @@ -3,6 +3,6 @@ X(11, normals_gs, rgba16f) \ #define RAY_LIGHT_DIRECT_OUTPUTS(X) \ - X(13, light_diffuse, rgba16f) \ - X(14, light_specular, rgba16f) \ + X(13, light_poly_diffuse, rgba16f) \ + X(14, light_poly_specular, rgba16f) \ diff --git a/ref_vk/shaders/ray_light_poly_direct.rgen b/ref_vk/shaders/ray_light_poly_direct.rgen index 42147938..e2921f6d 100644 --- a/ref_vk/shaders/ray_light_poly_direct.rgen +++ b/ref_vk/shaders/ray_light_poly_direct.rgen @@ -46,13 +46,6 @@ void main() { const vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.; const ivec2 pix = ivec2(gl_LaunchIDEXT.xy); - if (false) { - vec3 diffuse = vec3(uv, .5), specular = vec3(0.); - imageStore(out_image_light_diffuse, pix, vec4(diffuse, 0.f)); - imageStore(out_image_light_specular, pix, vec4(specular, 0.f)); - return; - } - rand01_state = ubo.random_seed + gl_LaunchIDEXT.x * 1833 + gl_LaunchIDEXT.y * 31337; // FIXME incorrect for reflection/refraction @@ -74,11 +67,6 @@ void main() { vec3 diffuse = vec3(0.), specular = vec3(0.); computeLighting(pos + geometry_normal * .001, shading_normal, throughput, -direction, material, diffuse, specular); - //specular = shading_normal; - //diffuse = geometry_normal; - //diffuse = shading_normal; - //diffuse.xy = uv; - imageStore(out_image_light_diffuse, pix, vec4(diffuse, 0.f)); - //imageStore(out_image_light_diffuse, pix, vec4(1.,0.,0.f, 0.f)); - imageStore(out_image_light_specular, pix, vec4(specular, 0.f)); + imageStore(out_image_light_poly_diffuse, pix, vec4(diffuse, 0.f)); + imageStore(out_image_light_poly_specular, pix, vec4(specular, 0.f)); } diff --git a/ref_vk/vk_denoiser.c b/ref_vk/vk_denoiser.c index ec8fbf73..404e2766 100644 --- a/ref_vk/vk_denoiser.c +++ b/ref_vk/vk_denoiser.c @@ -9,12 +9,12 @@ enum { DenoiserBinding_DestImage = 0, DenoiserBinding_Source_BaseColor = 1, - DenoiserBinding_Source_DiffuseGI = 2, - DenoiserBinding_Source_Specular = 3, - DenoiserBinding_Source_Additive = 4, - DenoiserBinding_Source_Normals = 5, - - DenoiserBinding_Source_PositionT = 6, + DenoiserBinding_Source_LightPolyDiffuse = 2, + /* DenoiserBinding_Source_Specular = 3, */ + /* DenoiserBinding_Source_Additive = 4, */ + /* DenoiserBinding_Source_Normals = 5, */ + /* */ + /* DenoiserBinding_Source_PositionT = 6, */ DenoiserBinding_COUNT }; @@ -55,36 +55,8 @@ static void createLayouts( void ) { .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, }; - g_denoiser.desc_bindings[DenoiserBinding_Source_DiffuseGI] = (VkDescriptorSetLayoutBinding){ - .binding = DenoiserBinding_Source_DiffuseGI, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }; - - g_denoiser.desc_bindings[DenoiserBinding_Source_Specular] = (VkDescriptorSetLayoutBinding){ - .binding = DenoiserBinding_Source_Specular, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }; - - g_denoiser.desc_bindings[DenoiserBinding_Source_Additive] = (VkDescriptorSetLayoutBinding){ - .binding = DenoiserBinding_Source_Additive, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }; - - g_denoiser.desc_bindings[DenoiserBinding_Source_Normals] = (VkDescriptorSetLayoutBinding){ - .binding = DenoiserBinding_Source_Normals, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }; - - g_denoiser.desc_bindings[DenoiserBinding_Source_PositionT] = (VkDescriptorSetLayoutBinding){ - .binding = DenoiserBinding_Source_PositionT, + g_denoiser.desc_bindings[DenoiserBinding_Source_LightPolyDiffuse] = (VkDescriptorSetLayoutBinding){ + .binding = DenoiserBinding_Source_LightPolyDiffuse, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, @@ -128,57 +100,26 @@ void XVK_DenoiserReloadPipeline( void ) { g_denoiser.pipeline = createPipeline(); } -void XVK_DenoiserDenoise( const xvk_denoiser_args_t* args ) { - const uint32_t WG_W = 16; +void XVK_DenoiserDenoise( VkCommandBuffer cmdbuf, const vk_ray_resources_t* res ) { + const uint32_t WG_W = 8; const uint32_t WG_H = 8; - g_denoiser.desc_values[DenoiserBinding_Source_BaseColor].image = (VkDescriptorImageInfo){ - .sampler = VK_NULL_HANDLE, - .imageView = args->src.base_color_a_view, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; +#define BIND_IMAGE(index_name, name) \ + g_denoiser.desc_values[DenoiserBinding_##index_name].image = (VkDescriptorImageInfo){ \ + .sampler = VK_NULL_HANDLE, \ + .imageView = res->name, \ + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ + } - g_denoiser.desc_values[DenoiserBinding_Source_DiffuseGI].image = (VkDescriptorImageInfo){ - .sampler = VK_NULL_HANDLE, - .imageView = args->src.diffuse_gi_view, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; - - g_denoiser.desc_values[DenoiserBinding_Source_Specular].image = (VkDescriptorImageInfo){ - .sampler = VK_NULL_HANDLE, - .imageView = args->src.specular_view, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; - - g_denoiser.desc_values[DenoiserBinding_Source_Additive].image = (VkDescriptorImageInfo){ - .sampler = VK_NULL_HANDLE, - .imageView = args->src.additive_view, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; - - g_denoiser.desc_values[DenoiserBinding_Source_Normals].image = (VkDescriptorImageInfo){ - .sampler = VK_NULL_HANDLE, - .imageView = args->src.normals_view, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; - - g_denoiser.desc_values[DenoiserBinding_DestImage].image = (VkDescriptorImageInfo){ - .sampler = VK_NULL_HANDLE, - .imageView = args->dst_view, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; - - g_denoiser.desc_values[DenoiserBinding_Source_PositionT].image = (VkDescriptorImageInfo){ - .sampler = VK_NULL_HANDLE, - .imageView = args->src.position_t_view, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; + BIND_IMAGE(DestImage, denoised); + BIND_IMAGE(Source_BaseColor, base_color_a); + BIND_IMAGE(Source_LightPolyDiffuse, light_poly_diffuse); VK_DescriptorsWrite(&g_denoiser.descriptors); - DEBUG_BEGIN(args->cmdbuf, "denoiser"); - vkCmdBindPipeline(args->cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_denoiser.pipeline); - vkCmdBindDescriptorSets(args->cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_denoiser.descriptors.pipeline_layout, 0, 1, g_denoiser.descriptors.desc_sets + 0, 0, NULL); - vkCmdDispatch(args->cmdbuf, (args->width + WG_W - 1) / WG_W, (args->height + WG_H - 1) / WG_H, 1); - DEBUG_END(args->cmdbuf); + DEBUG_BEGIN(cmdbuf, "denoiser"); + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_denoiser.pipeline); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_denoiser.descriptors.pipeline_layout, 0, 1, g_denoiser.descriptors.desc_sets + 0, 0, NULL); + vkCmdDispatch(cmdbuf, (res->width + WG_W - 1) / WG_W, (res->height + WG_H - 1) / WG_H, 1); + DEBUG_END(cmdbuf); } diff --git a/ref_vk/vk_denoiser.h b/ref_vk/vk_denoiser.h index 67271531..3b248219 100644 --- a/ref_vk/vk_denoiser.h +++ b/ref_vk/vk_denoiser.h @@ -1,5 +1,6 @@ #pragma once +#include "vk_ray_resources.h" #include "vk_core.h" qboolean XVK_DenoiserInit( void ); @@ -7,20 +8,4 @@ void XVK_DenoiserDestroy( void ); void XVK_DenoiserReloadPipeline( void ); -typedef struct { - VkCommandBuffer cmdbuf; - uint32_t width, height; - - struct { - VkImageView base_color_a_view; - VkImageView diffuse_gi_view; - VkImageView specular_view; - VkImageView additive_view; - VkImageView normals_view; - VkImageView position_t_view; - } src; - - VkImageView dst_view; -} xvk_denoiser_args_t; - -void XVK_DenoiserDenoise( const xvk_denoiser_args_t* args ); +void XVK_DenoiserDenoise( VkCommandBuffer cmdbuf, const vk_ray_resources_t* res ); diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 0f10dea2..47ef0996 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -97,7 +97,7 @@ static void updateDescriptors( const vk_ray_resources_t *res ) { #define X(index, name, ...) \ g_ray_light_direct.desc.values[RtLDir_Desc_##name].image = (VkDescriptorImageInfo){ \ .sampler = VK_NULL_HANDLE, \ - .imageView = res->primary.name, \ + .imageView = res->name, \ .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ }; RAY_LIGHT_DIRECT_INPUTS(X) @@ -106,7 +106,7 @@ static void updateDescriptors( const vk_ray_resources_t *res ) { #define X(index, name, ...) \ g_ray_light_direct.desc.values[RtLDir_Desc_##name].image = (VkDescriptorImageInfo){ \ .sampler = VK_NULL_HANDLE, \ - .imageView = res->light_direct_polygon.name, \ + .imageView = res->name, \ .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ }; RAY_LIGHT_DIRECT_OUTPUTS(X) diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index 94e4ae55..49315fa7 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -79,7 +79,7 @@ static void updateDescriptors( const vk_ray_resources_t* res ) { #define X(index, name, ...) \ g_ray_primary.desc.values[RtPrim_Desc_Out_##name].image = (VkDescriptorImageInfo){ \ .sampler = VK_NULL_HANDLE, \ - .imageView = res->primary.name, \ + .imageView = res->name, \ .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ }; RAY_PRIMARY_OUTPUTS(X) diff --git a/ref_vk/vk_ray_resources.h b/ref_vk/vk_ray_resources.h index c1b8ad1e..86c3149a 100644 --- a/ref_vk/vk_ray_resources.h +++ b/ref_vk/vk_ray_resources.h @@ -20,13 +20,9 @@ typedef struct { } scene; #define X(index, name, ...) VkImageView name; - struct { - RAY_PRIMARY_OUTPUTS(X) - } primary; - - struct { - RAY_LIGHT_DIRECT_OUTPUTS(X) - } light_direct_polygon; + RAY_PRIMARY_OUTPUTS(X) + RAY_LIGHT_DIRECT_OUTPUTS(X) + X(-1, denoised) #undef X } vk_ray_resources_t; diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index b3895e35..f1017f02 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -1006,12 +1006,9 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar }, }, #define X(index, name, ...) .name = current_frame->name.view, - .primary = { - RAY_PRIMARY_OUTPUTS(X) - }, - .light_direct_polygon = { - RAY_LIGHT_DIRECT_OUTPUTS(X) - } + RAY_PRIMARY_OUTPUTS(X) + RAY_LIGHT_DIRECT_OUTPUTS(X) + X(-1, denoised) #undef X }; @@ -1108,24 +1105,7 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar 0, 0, NULL, 0, NULL, ARRAYSIZE(image_barriers), image_barriers); } - { - const xvk_denoiser_args_t denoiser_args = { - .cmdbuf = cmdbuf, - .width = FRAME_WIDTH, - .height = FRAME_HEIGHT, - .src = { - .position_t_view = current_frame->position_t.view, - .base_color_a_view = current_frame->base_color_a.view, - .diffuse_gi_view = current_frame->light_diffuse.view, - .specular_view = current_frame->light_specular.view, - .additive_view = current_frame->additive.view, - .normals_view = current_frame->normals_gs.view, - }, - .dst_view = current_frame->denoised.view, - }; - - XVK_DenoiserDenoise( &denoiser_args ); - } + XVK_DenoiserDenoise( cmdbuf, &res ); { const xvk_blit_args blit_args = { From 5d8e15bfacb93857b65ee52090bd69980e98be65 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 22 Jan 2022 18:54:40 -0800 Subject: [PATCH 111/548] rt: sample all poly lights more correctly --- ref_vk/shaders/light_polygon.glsl | 82 +++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 0f82313e..bbb9f56f 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -31,7 +31,7 @@ SampleContext buildSampleContext(vec3 position, vec3 normal, vec3 view_dir) { #define SAMPLE_CONTRIB(sap) sap.solid_angle #endif -vec4 getPolygonLightSample(vec3 P, vec3 N, vec3 view_dir, SampleContext ctx, const PolygonLight poly) { +vec4 getPolygonLightSampleProjected(vec3 P, vec3 N, vec3 view_dir, SampleContext ctx, const PolygonLight poly) { vec3 clipped[MAX_POLYGON_VERTEX_COUNT]; const uint vertices_offset = poly.vertices_count_offset & 0xffffu; @@ -45,13 +45,41 @@ vec4 getPolygonLightSample(vec3 P, vec3 N, vec3 view_dir, SampleContext ctx, con if (vertices_count == 0) return vec4(0.f); - const SAMPLE_TYPE_T sap = SAMPLE_PREPARE_FUNC(vertices_count, clipped); - const float contrib = SAMPLE_CONTRIB(sap); + const projected_solid_angle_polygon_t sap = prepare_projected_solid_angle_polygon_sampling(vertices_count, clipped); + const float contrib = sap.projected_solid_angle; if (contrib <= 0.f) return vec4(0.f); vec2 rnd = vec2(rand01(), rand01()); - const vec3 light_dir = (transpose(ctx.world_to_shading) * SAMPLE_FUNC(sap, rnd)).xyz; + const vec3 light_dir = (transpose(ctx.world_to_shading) * sample_projected_solid_angle_polygon(sap, rnd)).xyz; + + return vec4(light_dir, contrib); +} + +vec4 getPolygonLightSampleSolid(vec3 P, vec3 N, vec3 view_dir, SampleContext ctx, const PolygonLight poly) { + vec3 clipped[MAX_POLYGON_VERTEX_COUNT]; + + const uint vertices_offset = poly.vertices_count_offset & 0xffffu; + uint vertices_count = poly.vertices_count_offset >> 16; + + for (uint i = 0; i < vertices_count; ++i) { + clipped[i] = lights.polygon_vertices[vertices_offset + i].xyz; + } + +#define DONT_CLIP +#ifndef DONT_CLIP + vertices_count = clip_polygon(vertices_count, clipped); + if (vertices_count == 0) + return vec4(0.f); +#endif + + const solid_angle_polygon_t sap = prepare_solid_angle_polygon_sampling(vertices_count, clipped, P); + const float contrib = sap.solid_angle; + if (contrib <= 0.f) + return vec4(0.f); + + vec2 rnd = vec2(rand01(), rand01()); + const vec3 light_dir = sample_solid_angle_polygon(sap, rnd).xyz; return vec4(light_dir, contrib); } @@ -228,7 +256,32 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { #define DO_ALL_IN_CLUSTER 1 -#if DO_ALL_IN_CLUSTER +#if 0 + { + const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); + //diffuse = vec3(float(num_polygons) / MAX_VISIBLE_SURFACE_LIGHTS); + if (num_polygons > 0) { + const PolygonLight poly = lights.polygons[0]; + + const vec3 dir = poly.center - P; + const vec3 light_dir = normalize(dir); + if (dot(-light_dir, poly.plane.xyz) <= 0.f) // || dot(light_dir, N) <= 0.) + return; + + const SampleContext ctx = buildSampleContext(P, N, view_dir); + const vec4 light_sample_dir = getPolygonLightSampleSolid(P, N, view_dir, ctx, poly); + if (light_sample_dir.w <= 0.) + return; + const float estimate = 1.f;// light_sample_dir.w; + vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); + evalSplitBRDF(N, light_sample_dir.xyz, view_dir, material, poly_diffuse, poly_specular); + diffuse += throughput * poly.emissive * estimate * poly_diffuse; + specular += throughput * poly.emissive * estimate * poly_specular; + } + return; + } + +#elif DO_ALL_IN_CLUSTER const SampleContext ctx = buildSampleContext(P, N, view_dir); const uint num_polygons = min(128, uint(light_grid.clusters[cluster_index].num_polygons)); for (uint i = 0; i < num_polygons; ++i) { @@ -243,22 +296,33 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate continue; } - const vec4 light_sample_dir = getPolygonLightSample(P, N, view_dir, ctx, poly); +#define PROJECTED +#ifdef PROJECTED + const vec4 light_sample_dir = getPolygonLightSampleProjected(P, N, view_dir, ctx, poly); +#else + const vec4 light_sample_dir = getPolygonLightSampleSolid(P, N, view_dir, ctx, poly); +#endif if (light_sample_dir.w <= 0.) continue; const float dist = - dot(vec4(P, 1.f), poly.plane) / dot(light_sample_dir.xyz, poly.plane.xyz); const vec3 emissive = poly.emissive; +#if 0 + const float estimate = light_sample_dir.w; + diffuse += throughput * emissive * estimate; + specular += throughput * emissive * estimate; +#else //if (true) {//!shadowed(P, light_sample_dir.xyz, dist)) { if (!shadowed(P, light_sample_dir.xyz, dist)) { //const float estimate = total_contrib; - const float estimate = light_sample_dir.w / 10; // FIXME WTF is this fudge + const float estimate = light_sample_dir.w; vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); evalSplitBRDF(N, light_sample_dir.xyz, view_dir, material, poly_diffuse, poly_specular); - diffuse += throughput * emissive * estimate; - specular += throughput * emissive * estimate; + diffuse += throughput * emissive * estimate * poly_diffuse; + specular += throughput * emissive * estimate * poly_specular; } +#endif } #else // TODO move this to pickPolygonLight function From e384f2e1f7a8a0d03af86de4d530dc4741af7752 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 22 Jan 2022 19:13:08 -0800 Subject: [PATCH 112/548] rt: move shader binding defs to interop --- ref_vk/shaders/ray_interop.h | 13 ++++ ref_vk/shaders/ray_light_direct.comp | 73 ----------------------- ref_vk/shaders/ray_light_direct_iface.h | 8 --- ref_vk/shaders/ray_light_poly_direct.rgen | 2 - ref_vk/shaders/ray_primary.rgen | 1 - ref_vk/shaders/ray_primary_iface.h | 5 -- ref_vk/vk_ray_resources.h | 5 +- 7 files changed, 15 insertions(+), 92 deletions(-) delete mode 100644 ref_vk/shaders/ray_light_direct.comp delete mode 100644 ref_vk/shaders/ray_light_direct_iface.h delete mode 100644 ref_vk/shaders/ray_primary_iface.h diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index 368c2b55..9f6a1833 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -2,6 +2,19 @@ #ifndef RAY_INTEROP_H_INCLUDED #define RAY_INTEROP_H_INCLUDED +#define RAY_LIGHT_DIRECT_INPUTS(X) \ + X(10, position_t, rgba32f) \ + X(11, normals_gs, rgba16f) \ + +#define RAY_LIGHT_DIRECT_OUTPUTS(X) \ + X(13, light_poly_diffuse, rgba16f) \ + X(14, light_poly_specular, rgba16f) \ + +#define RAY_PRIMARY_OUTPUTS(X) \ + X(10, base_color_a, rgba8) \ + X(11, position_t, rgba32f) \ + X(12, normals_gs, rgba16f) \ + #define LIST_SPECIALIZATION_CONSTANTS(X) \ X(0, uint, MAX_POINT_LIGHTS, 256) \ X(1, uint, MAX_EMISSIVE_KUSOCHKI, 256) \ diff --git a/ref_vk/shaders/ray_light_direct.comp b/ref_vk/shaders/ray_light_direct.comp deleted file mode 100644 index 571ab7de..00000000 --- a/ref_vk/shaders/ray_light_direct.comp +++ /dev/null @@ -1,73 +0,0 @@ -#version 460 core -#extension GL_GOOGLE_include_directive : require -#extension GL_EXT_control_flow_attributes : require -#extension GL_EXT_ray_query: require - -#include "utils.glsl" - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - -#include "ray_light_direct_iface.h" - -#define GLSL -#include "ray_interop.h" -#undef GLSL - -#define X(index, name, format) layout(set=0,binding=index,format) uniform readonly image2D name; -RAY_LIGHT_DIRECT_INPUTS(X) -#undef X -#define X(index, name, format) layout(set=0,binding=index,format) uniform writeonly image2D out_image_##name; -RAY_LIGHT_DIRECT_OUTPUTS(X) -#undef X - -layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; -layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; - -#include "ray_kusochki.glsl" - -#define RAY_QUERY - -#define BINDING_LIGHTS 7 -#define BINDING_LIGHT_CLUSTERS 8 -#include "light.glsl" - -void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { - const vec4 n = imageLoad(normals_gs, uv); - geometry_normal = normalDecode(n.xy); - shading_normal = normalDecode(n.zw); -} - -void main() { - //vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.; - const ivec2 res = ivec2(imageSize(position_t)); - const ivec2 pix = ivec2(gl_GlobalInvocationID); - const vec2 uv = (pix + .5) / res * 2. - 1.; - - rand01_state = ubo.random_seed + gl_GlobalInvocationID.x * 1833 + gl_GlobalInvocationID.y * 31337; - - // FIXME incorrect for reflection/refraction - vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); - vec3 direction = normalize((ubo.inv_view * vec4(target.xyz, 0)).xyz); - - MaterialProperties material; - material.baseColor = vec3(1.); - material.emissive = vec3(0.f); - material.metalness = 0.f; // TODO - material.roughness = 1.f; // TODO - - const vec3 pos = imageLoad(position_t, pix).xyz; - - vec3 geometry_normal, shading_normal; - readNormals(pix, geometry_normal, shading_normal); - - const vec3 throughput = vec3(1.); - vec3 diffuse = vec3(0.), specular = vec3(0.); - computeLighting(pos + geometry_normal * .001, shading_normal, throughput, -direction, material, diffuse, specular); - - //specular = shading_normal; - //diffuse = geometry_normal; - //diffuse = shading_normal; - imageStore(out_image_light_poly_diffuse, pix, vec4(diffuse, 0.f)); - //imageStore(out_image_light_diffuse, pix, vec4(1.,0.,0.f, 0.f)); - imageStore(out_image_light_poly_specular, pix, vec4(specular, 0.f)); -} diff --git a/ref_vk/shaders/ray_light_direct_iface.h b/ref_vk/shaders/ray_light_direct_iface.h deleted file mode 100644 index bbab5183..00000000 --- a/ref_vk/shaders/ray_light_direct_iface.h +++ /dev/null @@ -1,8 +0,0 @@ -#define RAY_LIGHT_DIRECT_INPUTS(X) \ - X(10, position_t, rgba32f) \ - X(11, normals_gs, rgba16f) \ - -#define RAY_LIGHT_DIRECT_OUTPUTS(X) \ - X(13, light_poly_diffuse, rgba16f) \ - X(14, light_poly_specular, rgba16f) \ - diff --git a/ref_vk/shaders/ray_light_poly_direct.rgen b/ref_vk/shaders/ray_light_poly_direct.rgen index e2921f6d..306b356c 100644 --- a/ref_vk/shaders/ray_light_poly_direct.rgen +++ b/ref_vk/shaders/ray_light_poly_direct.rgen @@ -5,8 +5,6 @@ #include "utils.glsl" -#include "ray_light_direct_iface.h" - #define GLSL #include "ray_interop.h" #undef GLSL diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index 958bdabc..816ff964 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -2,7 +2,6 @@ #extension GL_GOOGLE_include_directive : require #include "ray_primary_common.glsl" -#include "ray_primary_iface.h" #define X(index, name, format) layout(set=0,binding=index,format) uniform image2D out_image_##name; RAY_PRIMARY_OUTPUTS(X) diff --git a/ref_vk/shaders/ray_primary_iface.h b/ref_vk/shaders/ray_primary_iface.h deleted file mode 100644 index eebdc4d2..00000000 --- a/ref_vk/shaders/ray_primary_iface.h +++ /dev/null @@ -1,5 +0,0 @@ -#define RAY_PRIMARY_OUTPUTS(X) \ - X(10, base_color_a, rgba8) \ - X(11, position_t, rgba32f) \ - X(12, normals_gs, rgba16f) \ - diff --git a/ref_vk/vk_ray_resources.h b/ref_vk/vk_ray_resources.h index 86c3149a..e45c44f5 100644 --- a/ref_vk/vk_ray_resources.h +++ b/ref_vk/vk_ray_resources.h @@ -1,9 +1,8 @@ #pragma once #include "vk_rtx.h" - -#include "shaders/ray_primary_iface.h" -#include "shaders/ray_light_direct_iface.h" +#include "vk_const.h" +#include "shaders/ray_interop.h" typedef struct { uint32_t width, height; From 0e286f31c54f772de3058ca8efa89cf059602892 Mon Sep 17 00:00:00 2001 From: Velaron Date: Mon, 24 Jan 2022 16:30:30 +0200 Subject: [PATCH 113/548] engine: client: fix DT_TIMEWINDOW_* signedness --- engine/common/net_encode.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/common/net_encode.c b/engine/common/net_encode.c index 04aa323d..dc1c0f6c 100644 --- a/engine/common/net_encode.c +++ b/engine/common/net_encode.c @@ -1333,7 +1333,7 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, { bSigned = true; // timewindow is always signed iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - flTime = (timebase * 100.0 - iValue) / 100.0; + flTime = (timebase * 100.0 - (int)iValue) / 100.0; *(float *)((byte *)to + pField->offset ) = flTime; } @@ -1343,9 +1343,9 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); if( !Q_equal( pField->multiplier, 1.0 ) ) - flTime = ( timebase * pField->multiplier - iValue ) / pField->multiplier; + flTime = ( timebase * pField->multiplier - (int)iValue ) / pField->multiplier; else - flTime = timebase - iValue; + flTime = timebase - (int)iValue; *(float *)((byte *)to + pField->offset ) = flTime; } From ffea7d9729d0a811483a1155a6ddb09bab098f4c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 21 Jan 2022 04:09:25 +0300 Subject: [PATCH 114/548] engine: add host_lowmemorymode cvar to indicate low memory mode level --- engine/common/host.c | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/common/host.c b/engine/common/host.c index 91e082c8..ec7c95a0 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -1102,6 +1102,7 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa build = Cvar_Get( "buildnum", va( "%i", Q_buildnum_compat()), FCVAR_READ_ONLY, "returns a current build number" ); ver = Cvar_Get( "ver", va( "%i/%s (hw build %i)", PROTOCOL_VERSION, XASH_COMPAT_VERSION, Q_buildnum_compat()), FCVAR_READ_ONLY, "shows an engine version" ); Cvar_Get( "host_ver", va( "%i %s %s %s %s", Q_buildnum(), XASH_VERSION, Q_buildos(), Q_buildarch(), Q_buildcommit() ), FCVAR_READ_ONLY, "detailed info about this build" ); + Cvar_Get( "host_lowmemorymode", va( "%i", XASH_LOW_MEMORY ), FCVAR_READ_ONLY, "indicates if engine compiled for low RAM consumption (0 - normal, 1 - low engine limits, 2 - low protocol limits)" ); Mod_Init(); NET_Init(); From 0027678a56a474555412f16ec82c3fad7242e078 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 26 Jan 2022 23:50:11 +0300 Subject: [PATCH 115/548] engine: client: print which tempentity type was overflowed --- engine/client/cl_tent.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c index dab743d6..a923f235 100644 --- a/engine/client/cl_tent.c +++ b/engine/client/cl_tent.c @@ -2393,7 +2393,7 @@ void CL_ParseTempEntity( sizebuf_t *msg ) // throw warning if( MSG_CheckOverflow( &buf )) - Con_DPrintf( S_WARN "ParseTempEntity: overflow TE message\n" ); + Con_DPrintf( S_WARN "ParseTempEntity: overflow TE message %i\n", type ); } From 2fa964e93980a69cbad99290dda927c1b317ec7c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 26 Jan 2022 23:50:32 +0300 Subject: [PATCH 116/548] engine: server: restore sv_trace_messages from old engine --- engine/server/server.h | 2 ++ engine/server/sv_game.c | 18 ++++++++++++++++++ engine/server/sv_main.c | 3 +++ 3 files changed, 23 insertions(+) diff --git a/engine/server/server.h b/engine/server/server.h index be144a9b..3c516372 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -325,6 +325,7 @@ typedef struct qboolean msg_started; // to avoid recursive included messages edict_t *msg_ent; // user message member entity vec3_t msg_org; // user message member origin + qboolean msg_trace; // trace this message void *hInstance; // pointer to game.dll qboolean config_executed; // should to execute config.cfg once time to restore FCVAR_ARCHIVE that specified in hl.dll @@ -431,6 +432,7 @@ extern convar_t sv_skyvec_z; extern convar_t sv_consistency; extern convar_t sv_password; extern convar_t sv_uploadmax; +extern convar_t sv_trace_messages; extern convar_t deathmatch; extern convar_t hostname; extern convar_t skill; diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index 0f9239f0..7ed36bcb 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -2608,6 +2608,13 @@ void GAME_EXPORT pfnMessageBegin( int msg_dest, int msg_num, const float *pOrigi svgame.msg_realsize = 0; svgame.msg_dest = msg_dest; svgame.msg_ent = ed; + + // enable message tracing + svgame.msg_trace = sv_trace_messages.value != 0 && + msg_num > svc_lastmsg && + Q_strcmp( svgame.msg_name, "ReqState" ); + + if( svgame.msg_trace ) Con_Printf( "^3%s( %i, %s )\n", __FUNCTION__, msg_dest, svgame.msg_name ); } /* @@ -2706,6 +2713,8 @@ void GAME_EXPORT pfnMessageEnd( void ) svgame.msg_dest = bound( MSG_BROADCAST, svgame.msg_dest, MSG_SPEC ); SV_Multicast( svgame.msg_dest, org, svgame.msg_ent, true, false ); + + if( svgame.msg_trace ) Con_Printf( "^3%s()\n", __FUNCTION__, svgame.msg_dest, svgame.msg_name ); } /* @@ -2718,6 +2727,7 @@ void GAME_EXPORT pfnWriteByte( int iValue ) { if( iValue == -1 ) iValue = 0xFF; // convert char to byte MSG_WriteByte( &sv.multicast, (byte)iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )", __FUNCTION__, iValue ); svgame.msg_realsize++; } @@ -2730,6 +2740,7 @@ pfnWriteChar void GAME_EXPORT pfnWriteChar( int iValue ) { MSG_WriteChar( &sv.multicast, (signed char)iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )", __FUNCTION__, iValue ); svgame.msg_realsize++; } @@ -2742,6 +2753,7 @@ pfnWriteShort void GAME_EXPORT pfnWriteShort( int iValue ) { MSG_WriteShort( &sv.multicast, (short)iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )", __FUNCTION__, iValue ); svgame.msg_realsize += 2; } @@ -2754,6 +2766,7 @@ pfnWriteLong void GAME_EXPORT pfnWriteLong( int iValue ) { MSG_WriteLong( &sv.multicast, iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )", __FUNCTION__, iValue ); svgame.msg_realsize += 4; } @@ -2769,6 +2782,7 @@ void GAME_EXPORT pfnWriteAngle( float flValue ) int iAngle = ((int)(( flValue ) * 256 / 360) & 255); MSG_WriteChar( &sv.multicast, iAngle ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )", __FUNCTION__, flValue ); svgame.msg_realsize += 1; } @@ -2781,6 +2795,7 @@ pfnWriteCoord void GAME_EXPORT pfnWriteCoord( float flValue ) { MSG_WriteCoord( &sv.multicast, flValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )", __FUNCTION__, flValue ); svgame.msg_realsize += 2; } @@ -2793,6 +2808,7 @@ pfnWriteBytes void pfnWriteBytes( const byte *bytes, int count ) { MSG_WriteBytes( &sv.multicast, bytes, count ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )", __FUNCTION__, count ); svgame.msg_realsize += count; } @@ -2854,6 +2870,7 @@ void GAME_EXPORT pfnWriteString( const char *src ) *dst = '\0'; // string end (not included in count) MSG_WriteString( &sv.multicast, string ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %s )", __FUNCTION__, string ); // NOTE: some messages with constant string length can be marked as known sized svgame.msg_realsize += len; @@ -2870,6 +2887,7 @@ void GAME_EXPORT pfnWriteEntity( int iValue ) if( iValue < 0 || iValue >= svgame.numEntities ) Host_Error( "MSG_WriteEntity: invalid entnumber %i\n", iValue ); MSG_WriteShort( &sv.multicast, (short)iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )", __FUNCTION__, iValue ); svgame.msg_realsize += 2; } diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 5639dd44..071164a3 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -55,6 +55,7 @@ CVAR_DEFINE_AUTO( mp_logecho, "1", 0, "log multiplayer frags to server logfile" CVAR_DEFINE_AUTO( mp_logfile, "1", 0, "log multiplayer frags to console" ); CVAR_DEFINE_AUTO( sv_log_singleplayer, "0", FCVAR_ARCHIVE, "allows logging in singleplayer games" ); CVAR_DEFINE_AUTO( sv_log_onefile, "0", FCVAR_ARCHIVE, "logs server information to only one file" ); +CVAR_DEFINE_AUTO( sv_trace_messages, "0", FCVAR_LATCH, "enable server usermessages tracing (good for developers)" ); // game-related cvars CVAR_DEFINE_AUTO( mapcyclefile, "mapcycle.txt", 0, "name of multiplayer map cycle configuration file" ); @@ -946,6 +947,8 @@ void SV_Init( void ) Cvar_RegisterVariable( &listipcfgfile ); Cvar_RegisterVariable( &mapchangecfgfile ); + Cvar_RegisterVariable( &sv_trace_messages ); + sv_allow_joystick = Cvar_Get( "sv_allow_joystick", "1", FCVAR_ARCHIVE, "allow connect with joystick enabled" ); sv_allow_mouse = Cvar_Get( "sv_allow_mouse", "1", FCVAR_ARCHIVE, "allow connect with mouse" ); sv_allow_touch = Cvar_Get( "sv_allow_touch", "1", FCVAR_ARCHIVE, "allow connect with touch controls" ); From 56103a90eb9ffa16183ea89d49501a4a2b7f50d4 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 27 Jan 2022 03:25:32 +0300 Subject: [PATCH 117/548] engine: client: limit token size in client dll parsefile to 1024 (GoldSrc value) --- engine/client/cl_game.c | 2 +- public/crtlib.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index 5cff0ac6..1a898a91 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -3089,7 +3089,7 @@ char *pfnParseFile( char *data, char *token ) { char *out; - out = _COM_ParseFileSafe( data, token, INT_MAX, PFILE_HANDLECOLON, NULL ); + out = _COM_ParseFileSafe( data, token, PFILE_TOKEN_MAX_LENGTH, PFILE_HANDLECOLON, NULL ); return out; } diff --git a/public/crtlib.h b/public/crtlib.h index 99752535..79bd414a 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -43,6 +43,7 @@ enum // exported APIs headers and will get nice warning in case of changing values #define PFILE_IGNOREBRACKET (1<<0) #define PFILE_HANDLECOLON (1<<1) +#define PFILE_TOKEN_MAX_LENGTH 1024 // // crtlib.c @@ -98,7 +99,6 @@ void COM_Hex2String( uint8_t hex, char *str ); #define COM_CheckStringEmpty( string ) ( ( !*string ) ? 0 : 1 ) char *_COM_ParseFileSafe( char *data, char *token, const int size, unsigned int flags, int *len ); #define COM_ParseFile( data, token, size ) _COM_ParseFileSafe( data, token, size, 0, NULL ) -#define COM_ParseFileLegacy( data, token ) COM_ParseFileSafe( data, token, INT_MAX ) int matchpattern( const char *in, const char *pattern, qboolean caseinsensitive ); int matchpattern_with_separator( const char *in, const char *pattern, qboolean caseinsensitive, const char *separators, qboolean wildcard_least_one ); From 0fa1b4f9446d5470de2ddb292a2f7c60e7aeda94 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 27 Jan 2022 03:31:45 +0300 Subject: [PATCH 118/548] engine: client: refactor tempentity parsing, make multipliers close to GoldSrc --- engine/client/cl_efx.c | 112 +++++++++++++++++-------------------- engine/client/cl_tent.c | 120 +++++++++++++++++++++------------------- 2 files changed, 113 insertions(+), 119 deletions(-) diff --git a/engine/client/cl_efx.c b/engine/client/cl_efx.c index 9db01f93..d7ad1196 100644 --- a/engine/client/cl_efx.c +++ b/engine/client/cl_efx.c @@ -46,7 +46,7 @@ short GAME_EXPORT R_LookupColor( byte r, byte g, byte b ) float rf, gf, bf; bestdiff = 999999; - best = 65535; + best = -1; for( i = 0; i < 256; i++ ) { @@ -683,42 +683,46 @@ void CL_ParseViewBeam( sizebuf_t *msg, int beamType ) switch( beamType ) { case TE_BEAMPOINTS: - start[0] = MSG_ReadCoord( msg ); - start[1] = MSG_ReadCoord( msg ); - start[2] = MSG_ReadCoord( msg ); - end[0] = MSG_ReadCoord( msg ); - end[1] = MSG_ReadCoord( msg ); - end[2] = MSG_ReadCoord( msg ); - modelIndex = MSG_ReadShort( msg ); - startFrame = MSG_ReadByte( msg ); - frameRate = (float)MSG_ReadByte( msg ); - life = (float)(MSG_ReadByte( msg ) * 0.1f); - width = (float)(MSG_ReadByte( msg ) * 0.1f); - noise = (float)(MSG_ReadByte( msg ) * 0.01f); - r = (float)MSG_ReadByte( msg ) / 255.0f; - g = (float)MSG_ReadByte( msg ) / 255.0f; - b = (float)MSG_ReadByte( msg ) / 255.0f; - a = (float)MSG_ReadByte( msg ) / 255.0f; - speed = (float)(MSG_ReadByte( msg ) * 0.1f); - R_BeamPoints( start, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); - break; case TE_BEAMENTPOINT: - startEnt = MSG_ReadShort( msg ); - end[0] = MSG_ReadCoord( msg ); - end[1] = MSG_ReadCoord( msg ); - end[2] = MSG_ReadCoord( msg ); + case TE_BEAMENTS: + if( beamType == TE_BEAMENTS ) + { + startEnt = MSG_ReadShort( msg ); + endEnt = MSG_ReadShort( msg ); + } + else + { + if( beamType == TE_BEAMENTPOINT ) + { + startEnt = MSG_ReadShort( msg ); + } + else + { + start[0] = MSG_ReadCoord( msg ); + start[1] = MSG_ReadCoord( msg ); + start[2] = MSG_ReadCoord( msg ); + } + end[0] = MSG_ReadCoord( msg ); + end[1] = MSG_ReadCoord( msg ); + end[2] = MSG_ReadCoord( msg ); + } modelIndex = MSG_ReadShort( msg ); startFrame = MSG_ReadByte( msg ); - frameRate = (float)MSG_ReadByte( msg ); - life = (float)(MSG_ReadByte( msg ) * 0.1f); - width = (float)(MSG_ReadByte( msg ) * 0.1f); - noise = (float)(MSG_ReadByte( msg ) * 0.01f); + frameRate = (float)MSG_ReadByte( msg ) * 0.1f; + life = (float)MSG_ReadByte( msg ) * 0.1f; + width = (float)MSG_ReadByte( msg ) * 0.1f; + noise = (float)MSG_ReadByte( msg ) * 0.01f; r = (float)MSG_ReadByte( msg ) / 255.0f; g = (float)MSG_ReadByte( msg ) / 255.0f; b = (float)MSG_ReadByte( msg ) / 255.0f; a = (float)MSG_ReadByte( msg ) / 255.0f; - speed = (float)(MSG_ReadByte( msg ) * 0.1f); - R_BeamEntPoint( startEnt, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); + speed = (float)MSG_ReadByte( msg ) * 0.1f; + if( beamType == TE_BEAMENTS ) + R_BeamEnts( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); + else if( beamType == TE_BEAMENTPOINT ) + R_BeamEntPoint( startEnt, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); + else + R_BeamPoints( start, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); break; case TE_LIGHTNING: start[0] = MSG_ReadCoord( msg ); @@ -727,27 +731,11 @@ void CL_ParseViewBeam( sizebuf_t *msg, int beamType ) end[0] = MSG_ReadCoord( msg ); end[1] = MSG_ReadCoord( msg ); end[2] = MSG_ReadCoord( msg ); - life = (float)(MSG_ReadByte( msg ) * 0.1f); - width = (float)(MSG_ReadByte( msg ) * 0.1f); - noise = (float)(MSG_ReadByte( msg ) * 0.01f); + life = (float)MSG_ReadByte( msg ) * 0.1f; + width = (float)MSG_ReadByte( msg ) * 0.1f; + noise = (float)MSG_ReadByte( msg ) * 0.01f; modelIndex = MSG_ReadShort( msg ); - R_BeamLightning( start, end, modelIndex, life, width, noise, 0.6F, 3.5f ); - break; - case TE_BEAMENTS: - startEnt = MSG_ReadShort( msg ); - endEnt = MSG_ReadShort( msg ); - modelIndex = MSG_ReadShort( msg ); - startFrame = MSG_ReadByte( msg ); - frameRate = (float)(MSG_ReadByte( msg ) * 0.1f); - life = (float)(MSG_ReadByte( msg ) * 0.1f); - width = (float)(MSG_ReadByte( msg ) * 0.1f); - noise = (float)(MSG_ReadByte( msg ) * 0.01f); - r = (float)MSG_ReadByte( msg ) / 255.0f; - g = (float)MSG_ReadByte( msg ) / 255.0f; - b = (float)MSG_ReadByte( msg ) / 255.0f; - a = (float)MSG_ReadByte( msg ) / 255.0f; - speed = (float)(MSG_ReadByte( msg ) * 0.1f); - R_BeamEnts( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); + R_BeamLightning( start, end, modelIndex, life, width, noise, 0.6f, 3.5f ); break; case TE_BEAM: break; @@ -773,21 +761,21 @@ void CL_ParseViewBeam( sizebuf_t *msg, int beamType ) end[2] = MSG_ReadCoord( msg ); modelIndex = MSG_ReadShort( msg ); startFrame = MSG_ReadByte( msg ); - frameRate = (float)(MSG_ReadByte( msg )); - life = (float)(MSG_ReadByte( msg ) * 0.1f); - width = (float)(MSG_ReadByte( msg )); - noise = (float)(MSG_ReadByte( msg ) * 0.1f); + frameRate = (float)MSG_ReadByte( msg ) * 0.1f; + life = (float)MSG_ReadByte( msg ) * 0.1f; + width = (float)MSG_ReadByte( msg ); + noise = (float)MSG_ReadByte( msg ) * 0.01f; r = (float)MSG_ReadByte( msg ) / 255.0f; g = (float)MSG_ReadByte( msg ) / 255.0f; b = (float)MSG_ReadByte( msg ) / 255.0f; a = (float)MSG_ReadByte( msg ) / 255.0f; - speed = (float)(MSG_ReadByte( msg ) / 0.1f); + speed = (float)MSG_ReadByte( msg ) * 0.1f; R_BeamCirclePoints( beamType, start, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); break; case TE_BEAMFOLLOW: startEnt = MSG_ReadShort( msg ); modelIndex = MSG_ReadShort( msg ); - life = (float)(MSG_ReadByte( msg ) * 0.1f); + life = (float)MSG_ReadByte( msg ) * 0.1f; width = (float)MSG_ReadByte( msg ); r = (float)MSG_ReadByte( msg ) / 255.0f; g = (float)MSG_ReadByte( msg ) / 255.0f; @@ -800,15 +788,15 @@ void CL_ParseViewBeam( sizebuf_t *msg, int beamType ) endEnt = MSG_ReadShort( msg ); modelIndex = MSG_ReadShort( msg ); startFrame = MSG_ReadByte( msg ); - frameRate = (float)MSG_ReadByte( msg ); - life = (float)(MSG_ReadByte( msg ) * 0.1f); - width = (float)(MSG_ReadByte( msg ) * 0.1f); - noise = (float)(MSG_ReadByte( msg ) * 0.01f); + frameRate = (float)MSG_ReadByte( msg ) * 0.1f; + life = (float)MSG_ReadByte( msg ) * 0.1f; + width = (float)MSG_ReadByte( msg ) * 0.1f; + noise = (float)MSG_ReadByte( msg ) * 0.01f; r = (float)MSG_ReadByte( msg ) / 255.0f; g = (float)MSG_ReadByte( msg ) / 255.0f; b = (float)MSG_ReadByte( msg ) / 255.0f; a = (float)MSG_ReadByte( msg ) / 255.0f; - speed = (float)(MSG_ReadByte( msg ) * 0.1f); + speed = (float)MSG_ReadByte( msg ) * 0.1f; R_BeamRing( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); break; case TE_BEAMHOSE: @@ -1265,7 +1253,7 @@ void GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t dir, int pcolor, particle_t *p; int i, j; float arc; - float accel = speed; + int accel = speed; // must be integer due to bug in GoldSrc for( arc = 0.05f, i = 0; i < 100; i++ ) { diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c index a923f235..2be8132f 100644 --- a/engine/client/cl_tent.c +++ b/engine/client/cl_tent.c @@ -1515,17 +1515,18 @@ R_RicochetSound Make a random ricochet sound ============== */ -void GAME_EXPORT R_RicochetSound( const vec3_t pos ) +static void R_RicochetSound_( const vec3_t pos, int sound ) { - int iPitch = COM_RandomLong( 90, 105 ); - float fvol = COM_RandomFloat( 0.7f, 0.9f ); - char soundpath[32]; sound_t handle; - Q_strncpy( soundpath, cl_ricochet_sounds[COM_RandomLong( 0, 4 )], sizeof( soundpath ) ); - handle = S_RegisterSound( soundpath ); + handle = S_RegisterSound( cl_ricochet_sounds[sound] ); - S_StartSound( pos, 0, CHAN_AUTO, handle, fvol, ATTN_NORM, iPitch, 0 ); + S_StartSound( pos, 0, CHAN_AUTO, handle, VOL_NORM, 1.0, 100, 0 ); +} + +void GAME_EXPORT R_RicochetSound( const vec3_t pos ) +{ + R_RicochetSound_( pos, COM_RandomLong( 0, 4 )); } /* @@ -1656,7 +1657,7 @@ void GAME_EXPORT R_Explosion( vec3_t pos, int model, float scale, float framerat dl->color.g = 250; dl->color.b = 150; dl->die = cl.time + 0.01f; - dl->decay = 80; + dl->decay = 800; // red glow dl = CL_AllocDlight( 0 ); @@ -1915,6 +1916,7 @@ void CL_ParseTempEntity( sizebuf_t *msg ) TEMPENTITY *pTemp; cl_entity_t *pEnt; dlight_t *dl; + sound_t hSound; if( cls.legacymode ) iSize = MSG_ReadByte( msg ); @@ -2014,15 +2016,46 @@ void CL_ParseTempEntity( sizebuf_t *msg ) color = MSG_ReadByte( &buf ); count = MSG_ReadByte( &buf ); R_ParticleExplosion2( pos, color, count ); + + dl = CL_AllocDlight( 0 ); + VectorCopy( pos, dl->origin ); + dl->radius = 350; + dl->die = cl.time + 0.5; + dl->decay = 300; + + hSound = S_RegisterSound( cl_explode_sounds[0] ); + S_StartSound( pos, 0, CHAN_STATIC, hSound, VOL_NORM, 0.6f, PITCH_NORM, 0 ); break; case TE_BSPDECAL: + case TE_DECAL: + case TE_WORLDDECAL: + case TE_WORLDDECALHIGH: + case TE_DECALHIGH: pos[0] = MSG_ReadCoord( &buf ); pos[1] = MSG_ReadCoord( &buf ); pos[2] = MSG_ReadCoord( &buf ); - decalIndex = MSG_ReadShort( &buf ); - entityIndex = MSG_ReadShort( &buf ); - if( entityIndex ) modelIndex = MSG_ReadShort( &buf ); - CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, pos, FDECAL_PERMANENT ); + if( type == TE_BSPDECAL ) + { + decalIndex = MSG_ReadShort( &buf ); + entityIndex = MSG_ReadShort( &buf ); + if( entityIndex ) + modelIndex = MSG_ReadShort( &buf ); + else modelIndex = 0; + } + else + { + decalIndex = MSG_ReadByte( &buf ); + if( type == TE_DECALHIGH || type == TE_WORLDDECALHIGH ) + decalIndex += 256; + + if( type == TE_DECALHIGH || type == TE_DECAL ) + entityIndex = MSG_ReadShort( &buf ); + else entityIndex = 0; + + pEnt = CL_GetEntityByIndex( entityIndex ); + modelIndex = pEnt->curstate.modelindex; + } + CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, pos, type == TE_BSPDECAL ? FDECAL_PERMANENT : 0 ); break; case TE_IMPLOSION: pos[0] = MSG_ReadCoord( &buf ); @@ -2042,10 +2075,12 @@ void CL_ParseTempEntity( sizebuf_t *msg ) pos2[2] = MSG_ReadCoord( &buf ); modelIndex = MSG_ReadShort( &buf ); count = MSG_ReadByte( &buf ); - life = (float)(MSG_ReadByte( &buf ) * 0.1f); - scale = (float)(MSG_ReadByte( &buf ) * 0.1f); - vel = (float)MSG_ReadByte( &buf ); - random = (float)MSG_ReadByte( &buf ); + life = (float)MSG_ReadByte( &buf ) * 0.1f; + scale = (float)MSG_ReadByte( &buf ); + if( !scale ) scale = 1.0f; + else scale *= 0.1f; + vel = (float)MSG_ReadByte( &buf ) * 10; + random = (float)MSG_ReadByte( &buf ) * 10; R_Sprite_Trail( type, pos, pos2, modelIndex, count, life, scale, random, 255, vel ); break; case TE_SPRITE: @@ -2053,36 +2088,23 @@ void CL_ParseTempEntity( sizebuf_t *msg ) pos[1] = MSG_ReadCoord( &buf ); pos[2] = MSG_ReadCoord( &buf ); modelIndex = MSG_ReadShort( &buf ); - scale = (float)(MSG_ReadByte( &buf ) * 0.1f); - brightness = (float)MSG_ReadByte( &buf ); + scale = (float)MSG_ReadByte( &buf ) * 0.1f; + brightness = (float)MSG_ReadByte( &buf ) / 255.0f; - if(( pTemp = R_DefaultSprite( pos, modelIndex, 0 )) != NULL ) - { - pTemp->entity.curstate.scale = scale; - pTemp->entity.baseline.renderamt = brightness; - pTemp->entity.curstate.renderamt = brightness; - pTemp->entity.curstate.rendermode = kRenderTransAdd; - } + R_TempSprite( pos, vec3_origin, scale, modelIndex, + kRenderTransAdd, kRenderFxNone, brightness, 0.0, FTENT_SPRANIMATE ); break; case TE_GLOWSPRITE: pos[0] = MSG_ReadCoord( &buf ); pos[1] = MSG_ReadCoord( &buf ); pos[2] = MSG_ReadCoord( &buf ); modelIndex = MSG_ReadShort( &buf ); - life = (float)(MSG_ReadByte( &buf ) * 0.1f); - scale = (float)(MSG_ReadByte( &buf ) * 0.1f); - brightness = (float)MSG_ReadByte( &buf ); + life = (float)MSG_ReadByte( &buf ) * 0.1f; + scale = (float)MSG_ReadByte( &buf ) * 0.1f; + brightness = (float)MSG_ReadByte( &buf ) / 255.0f; - if(( pTemp = R_DefaultSprite( pos, modelIndex, 0 )) != NULL ) - { - pTemp->entity.curstate.scale = scale; - pTemp->entity.curstate.rendermode = kRenderGlow; - pTemp->entity.curstate.renderfx = kRenderFxNoDissipation; - pTemp->entity.baseline.renderamt = brightness; - pTemp->entity.curstate.renderamt = brightness; - pTemp->flags = FTENT_FADEOUT; - pTemp->die = cl.time + life; - } + R_TempSprite( pos, vec3_origin, scale, modelIndex, + kRenderGlow, kRenderFxNoDissipation, brightness, life, FTENT_FADEOUT ); break; case TE_STREAK_SPLASH: pos[0] = MSG_ReadCoord( &buf ); @@ -2172,23 +2194,6 @@ void CL_ParseTempEntity( sizebuf_t *msg ) pos2[2] = MSG_ReadCoord( &buf ); R_ShowLine( pos, pos2 ); break; - case TE_DECAL: - case TE_DECALHIGH: - case TE_WORLDDECAL: - case TE_WORLDDECALHIGH: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - decalIndex = MSG_ReadByte( &buf ); - if( type == TE_DECAL || type == TE_DECALHIGH ) - entityIndex = MSG_ReadShort( &buf ); - else entityIndex = 0; - if( type == TE_DECALHIGH || type == TE_WORLDDECALHIGH ) - decalIndex += 256; - pEnt = CL_GetEntityByIndex( entityIndex ); - if( pEnt ) modelIndex = pEnt->curstate.modelindex; - CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, pos, 0 ); - break; case TE_FIZZ: entityIndex = MSG_ReadShort( &buf ); modelIndex = MSG_ReadShort( &buf ); @@ -2244,10 +2249,11 @@ void CL_ParseTempEntity( sizebuf_t *msg ) pos[2] = MSG_ReadCoord( &buf ); entityIndex = MSG_ReadShort( &buf ); decalIndex = MSG_ReadByte( &buf ); - pEnt = CL_GetEntityByIndex( entityIndex ); CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, 0, pos, 0 ); R_BulletImpactParticles( pos ); - R_RicochetSound( pos ); + flags = COM_RandomLong( 0, 0x7fff ); + if( flags < 0x3fff ) + R_RicochetSound_( pos, flags % 5 ); break; case TE_SPRAY: case TE_SPRITE_SPRAY: From 0891356ec51c18889ced88dea810e8e6c53c95b2 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 27 Jan 2022 03:32:21 +0300 Subject: [PATCH 119/548] ref_gl: use GoldSrc particle texture --- ref_gl/gl_image.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/ref_gl/gl_image.c b/ref_gl/gl_image.c index ee006a5a..e6d384b3 100644 --- a/ref_gl/gl_image.c +++ b/ref_gl/gl_image.c @@ -22,6 +22,19 @@ static gl_texture_t gl_textures[MAX_TEXTURES]; static gl_texture_t* gl_texturesHashTable[TEXTURES_HASH_SIZE]; static uint gl_numTextures; +static byte dottexture[8][8] = +{ + {0,1,1,0,0,0,0,0}, + {1,1,1,1,0,0,0,0}, + {1,1,1,1,0,0,0,0}, + {0,1,1,0,0,0,0,0}, + {0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0}, +}; + + #define IsLightMap( tex ) ( FBitSet(( tex )->flags, TF_ATLAS_PAGE )) /* ================= @@ -1986,18 +1999,15 @@ static void GL_CreateInternalTextures( void ) tr.defaultTexture = GL_LoadTextureInternal( REF_DEFAULT_TEXTURE, pic, TF_COLORMAP ); // particle texture from quake1 - pic = GL_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA ); + pic = GL_FakeImage( 8, 8, 1, IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA ); - for( x = 0; x < 16; x++ ) + for( x = 0; x < 8; x++ ) { - dx2 = x - 8; - dx2 = dx2 * dx2; - - for( y = 0; y < 16; y++ ) + for( y = 0; y < 8; y++ ) { - dy = y - 8; - d = 255 - 35 * sqrt( dx2 + dy * dy ); - pic->buffer[( y * 16 + x ) * 4 + 3] = bound( 0, d, 255 ); + if( dottexture[x][y] ) + pic->buffer[( y * 8 + x ) * 4 + 3] = 255; + else pic->buffer[( y * 8 + x ) * 4 + 3] = 0; } } From 36d757c74a9d8884abd9c7f6285a6850dc0a13a0 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 27 Jan 2022 03:48:54 +0300 Subject: [PATCH 120/548] engine: server: fix message trace output --- engine/server/sv_game.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index 7ed36bcb..c3f25eb0 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -2727,7 +2727,7 @@ void GAME_EXPORT pfnWriteByte( int iValue ) { if( iValue == -1 ) iValue = 0xFF; // convert char to byte MSG_WriteByte( &sv.multicast, (byte)iValue ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )", __FUNCTION__, iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); svgame.msg_realsize++; } @@ -2740,7 +2740,7 @@ pfnWriteChar void GAME_EXPORT pfnWriteChar( int iValue ) { MSG_WriteChar( &sv.multicast, (signed char)iValue ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )", __FUNCTION__, iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); svgame.msg_realsize++; } @@ -2753,7 +2753,7 @@ pfnWriteShort void GAME_EXPORT pfnWriteShort( int iValue ) { MSG_WriteShort( &sv.multicast, (short)iValue ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )", __FUNCTION__, iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); svgame.msg_realsize += 2; } @@ -2766,7 +2766,7 @@ pfnWriteLong void GAME_EXPORT pfnWriteLong( int iValue ) { MSG_WriteLong( &sv.multicast, iValue ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )", __FUNCTION__, iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); svgame.msg_realsize += 4; } @@ -2782,7 +2782,7 @@ void GAME_EXPORT pfnWriteAngle( float flValue ) int iAngle = ((int)(( flValue ) * 256 / 360) & 255); MSG_WriteChar( &sv.multicast, iAngle ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )", __FUNCTION__, flValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )\n", __FUNCTION__, flValue ); svgame.msg_realsize += 1; } @@ -2795,7 +2795,7 @@ pfnWriteCoord void GAME_EXPORT pfnWriteCoord( float flValue ) { MSG_WriteCoord( &sv.multicast, flValue ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )", __FUNCTION__, flValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )\n", __FUNCTION__, flValue ); svgame.msg_realsize += 2; } @@ -2808,7 +2808,7 @@ pfnWriteBytes void pfnWriteBytes( const byte *bytes, int count ) { MSG_WriteBytes( &sv.multicast, bytes, count ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )", __FUNCTION__, count ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, count ); svgame.msg_realsize += count; } @@ -2870,7 +2870,7 @@ void GAME_EXPORT pfnWriteString( const char *src ) *dst = '\0'; // string end (not included in count) MSG_WriteString( &sv.multicast, string ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %s )", __FUNCTION__, string ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %s )\n", __FUNCTION__, string ); // NOTE: some messages with constant string length can be marked as known sized svgame.msg_realsize += len; @@ -2887,7 +2887,7 @@ void GAME_EXPORT pfnWriteEntity( int iValue ) if( iValue < 0 || iValue >= svgame.numEntities ) Host_Error( "MSG_WriteEntity: invalid entnumber %i\n", iValue ); MSG_WriteShort( &sv.multicast, (short)iValue ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )", __FUNCTION__, iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); svgame.msg_realsize += 2; } From b3c69851dc222acef2f40292b955375558076429 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Wed, 26 Jan 2022 23:52:14 -0800 Subject: [PATCH 121/548] rt: draft a universal pass sketch allows easily creating arbitrary ray tracing pipelines out of sets of bindings and shaders what's missing: - optimal handling of duplicate shaders - reading bindings from shaders (parse SPIR-V) instead of specifying in code - automatic resource creation according to binding definitions - automatic barriers according to bindings interface --- ref_vk/ray_pass.c | 177 ++++++++++++++++++++++++++++++++++++++ ref_vk/ray_pass.h | 65 ++++++++++++++ ref_vk/vk_pipeline.h | 4 +- ref_vk/vk_ray_primary.c | 150 ++++++++------------------------ ref_vk/vk_ray_primary.h | 8 +- ref_vk/vk_ray_resources.h | 2 +- ref_vk/vk_rtx.c | 22 ++++- 7 files changed, 300 insertions(+), 128 deletions(-) create mode 100644 ref_vk/ray_pass.c create mode 100644 ref_vk/ray_pass.h diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c new file mode 100644 index 00000000..7dcf901d --- /dev/null +++ b/ref_vk/ray_pass.c @@ -0,0 +1,177 @@ +#include "ray_pass.h" +#include "vk_descriptor.h" +#include "vk_ray_resources.h" +#include "vk_ray_resources.h" + +#define MAX_STAGES 16 +#define MAX_MISS_GROUPS 8 +#define MAX_HIT_GROUPS 8 + +typedef struct ray_pass_s { + // TODO enum type + + struct { + vk_descriptors_t riptors; + ray_pass_write_values_f write_values_func; + VkDescriptorSet sets[1]; + } desc; + + union { + vk_pipeline_ray_t tracing; + }; +} ray_pass_t; + +#if 0 // TODO +qboolean createLayout( const ray_pass_layout_t *layout, ray_pass_t *pass ){ + // TODO return false on fail instead of crashing + { + const VkDescriptorSetLayoutCreateInfo dslci = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = layout->bindings_count, + .pBindings = layout->bindings, + }; + XVK_CHECK(vkCreateDescriptorSetLayout(vk_core.device, &dslci, NULL, &pass->desc_set_layout)); + } + + { + const VkPipelineLayoutCreateInfo plci = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .setLayoutCount = 1, + .pSetLayouts = &pass->desc_set_layout, + .pushConstantRangeCount = layout->push_constants.size > 0 ? 1 : 0, + .pPushConstantRanges = &layout->push_constants, + }; + XVK_CHECK(vkCreatePipelineLayout(vk_core.device, &plci, NULL, &pass->pipeline_layout)); + } + + return true; +} +#endif + +static ray_pass_t *createRayPass( const ray_pass_create_t *create ) { + ray_pass_t *pass = Mem_Malloc(vk_core.pool, sizeof(*pass)); + + { + pass->desc.riptors = (vk_descriptors_t) { + .bindings = create->layout.bindings, + .num_bindings = create->layout.bindings_count, + .num_sets = COUNTOF(pass->desc.sets), + .desc_sets = pass->desc.sets, + .push_constants = create->layout.push_constants, + }; + + VK_DescriptorsCreate(&pass->desc.riptors); + } + + { + int stage_index = 0; + vk_shader_stage_t stages[MAX_STAGES]; + int miss_index = 0; + int misses[MAX_MISS_GROUPS]; + int hit_index = 0; + vk_pipeline_ray_hit_group_t hits[MAX_HIT_GROUPS]; + + vk_pipeline_ray_create_info_t prci = { + .debug_name = create->debug_name, + .layout = pass->desc.riptors.pipeline_layout, + .stages = stages, + .groups = { + .hit = hits, + .miss = misses, + }, + }; + + stages[stage_index++] = (vk_shader_stage_t) { + .filename = create->tracing.raygen, + .stage = VK_SHADER_STAGE_RAYGEN_BIT_KHR, + .specialization_info = create->tracing.specialization, + }; + + for (int i = 0; i < create->tracing.miss_count; ++i) { + const ray_pass_shader_t *const shader = create->tracing.miss + i; + + ASSERT(stage_index < MAX_STAGES); + ASSERT(miss_index < MAX_MISS_GROUPS); + + // TODO handle duplicate filenames + misses[miss_index++] = stage_index; + stages[stage_index++] = (vk_shader_stage_t) { + .filename = *shader, + .stage = VK_SHADER_STAGE_MISS_BIT_KHR, + .specialization_info = create->tracing.specialization, + }; + } + + for (int i = 0; i < create->tracing.hit_count; ++i) { + const ray_pass_hit_group_t *const group = create->tracing.hit + i; + + ASSERT(hit_index < MAX_HIT_GROUPS); + + // TODO handle duplicate filenames + if (group->any) { + ASSERT(stage_index < MAX_STAGES); + hits[hit_index].any = stage_index; + stages[stage_index++] = (vk_shader_stage_t) { + .filename = group->any, + .stage = VK_SHADER_STAGE_ANY_HIT_BIT_KHR, + .specialization_info = create->tracing.specialization, + }; + } else { + hits[hit_index].any = -1; + } + + if (group->closest) { + ASSERT(stage_index < MAX_STAGES); + hits[hit_index].closest = stage_index; + stages[stage_index++] = (vk_shader_stage_t) { + .filename = group->closest, + .stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, + .specialization_info = create->tracing.specialization, + }; + } else { + hits[hit_index].closest = -1; + } + + ++hit_index; + } + + prci.groups.hit_count = hit_index; + prci.groups.miss_count = miss_index; + prci.stages_count = stage_index; + + pass->tracing = VK_PipelineRayTracingCreate(&prci); + } + + if (pass->tracing.pipeline == VK_NULL_HANDLE) { + VK_DescriptorsDestroy(&pass->desc.riptors); + Mem_Free(pass); + return NULL; + } + + pass->desc.riptors.values = Mem_Malloc(vk_core.pool, sizeof(pass->desc.riptors.values[0]) * create->layout.bindings_count); + pass->desc.write_values_func = create->layout.write_values_func; + ASSERT(create->layout.write_values_func); + + return pass; +} + +struct ray_pass_s *RayPassCreate( const ray_pass_create_t *create ) { + return createRayPass(create); +} + +void RayPassDestroy( struct ray_pass_s *pass ) { + VK_PipelineRayTracingDestroy(&pass->tracing); + VK_DescriptorsDestroy(&pass->desc.riptors); + Mem_Free(pass->desc.riptors.values); + Mem_Free(pass); +} + + +void RayPassPerform( VkCommandBuffer cmdbuf, struct ray_pass_s *pass, const struct vk_ray_resources_s *res) { + pass->desc.write_values_func( pass->desc.riptors.values, res ); + VK_DescriptorsWrite(&pass->desc.riptors); + + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pass->tracing.pipeline); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pass->desc.riptors.pipeline_layout, 0, 1, pass->desc.riptors.desc_sets + 0, 0, NULL); + VK_PipelineRayTracingTrace(cmdbuf, &pass->tracing, res->width, res->height); +} diff --git a/ref_vk/ray_pass.h b/ref_vk/ray_pass.h new file mode 100644 index 00000000..e1baba4f --- /dev/null +++ b/ref_vk/ray_pass.h @@ -0,0 +1,65 @@ +#pragma once + +#include "vk_core.h" +#include "vk_pipeline.h" +#include "vk_descriptor.h" + +typedef const char* ray_pass_shader_t; + +typedef struct { + ray_pass_shader_t closest; + ray_pass_shader_t any; +} ray_pass_hit_group_t; + +typedef struct { + ray_pass_shader_t raygen; + + const ray_pass_shader_t *miss; + int miss_count; + + const ray_pass_hit_group_t *hit; + int hit_count; + + const VkSpecializationInfo *specialization; +} ray_pass_tracing_t; + +// enum { +// RVkRayPassType_Compute, +// RVkRayPassType_Tracing, +// }; + + +// TODO these should be like: +// - parse the entire layout from shaders +// - expose it as a struct[] interface of the pass +// - resource/interface should prepare descriptors outside of pass code and just pass them to pass +struct vk_ray_resources_s; +typedef void (*ray_pass_write_values_f)( vk_descriptor_value_t *values, const struct vk_ray_resources_s *resources ); + +// TODO parse this out from shaders +typedef struct { + VkDescriptorSetLayoutBinding *bindings; + int bindings_count; + + VkPushConstantRange push_constants; + + ray_pass_write_values_f write_values_func; +} ray_pass_layout_t; + +typedef struct { + // TODO enum type + + const char *debug_name; + ray_pass_layout_t layout; + + union { + ray_pass_tracing_t tracing; + }; +} ray_pass_create_t; + +struct ray_pass_s; +struct ray_pass_s *RayPassCreate( const ray_pass_create_t *create ); +void RayPassDestroy( struct ray_pass_s *pass ); + +struct vk_ray_resources_s; +void RayPassPerform( VkCommandBuffer cmdbuf, struct ray_pass_s *pass, const struct vk_ray_resources_s *res); diff --git a/ref_vk/vk_pipeline.h b/ref_vk/vk_pipeline.h index c39d1e85..4c95a800 100644 --- a/ref_vk/vk_pipeline.h +++ b/ref_vk/vk_pipeline.h @@ -1,10 +1,12 @@ +#pragma once + #include "vk_core.h" #include "vk_buffer.h" typedef struct { const char *filename; VkShaderStageFlagBits stage; - VkSpecializationInfo *specialization_info; + const VkSpecializationInfo *specialization_info; } vk_shader_stage_t; typedef struct { diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index 49315fa7..a8df5aa0 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -2,10 +2,8 @@ #include "vk_ray_internal.h" #include "vk_descriptor.h" -#include "vk_pipeline.h" -#include "vk_buffer.h" - -#include "eiface.h" // ARRAYSIZE +#include "vk_ray_resources.h" +#include "ray_pass.h" enum { // TODO set 0 @@ -24,35 +22,11 @@ RAY_PRIMARY_OUTPUTS(X) RtPrim_Desc_COUNT }; -static struct { - struct { - vk_descriptors_t riptors; - VkDescriptorSetLayoutBinding bindings[RtPrim_Desc_COUNT]; - vk_descriptor_value_t values[RtPrim_Desc_COUNT]; - - // TODO: split into two sets, one common to all rt passes (tlas, kusochki, etc), another one this pass only - VkDescriptorSet sets[1]; - } desc; - - vk_pipeline_ray_t pipeline; -} g_ray_primary; +static VkDescriptorSetLayoutBinding bindings[RtPrim_Desc_COUNT]; static void initDescriptors( void ) { - g_ray_primary.desc.riptors = (vk_descriptors_t) { - .bindings = g_ray_primary.desc.bindings, - .num_bindings = ARRAYSIZE(g_ray_primary.desc.bindings), - .values = g_ray_primary.desc.values, - .num_sets = ARRAYSIZE(g_ray_primary.desc.sets), - .desc_sets = g_ray_primary.desc.sets, - /* .push_constants = (VkPushConstantRange){ */ - /* .offset = 0, */ - /* .size = sizeof(vk_rtx_push_constants_t), */ - /* .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, */ - /* }, */ - }; - #define INIT_BINDING(index, name, type, count, stages) \ - g_ray_primary.desc.bindings[RtPrim_Desc_##name] = (VkDescriptorSetLayoutBinding){ \ + bindings[RtPrim_Desc_##name] = (VkDescriptorSetLayoutBinding){ \ .binding = index, \ .descriptorType = type, \ .descriptorCount = count, \ @@ -71,13 +45,11 @@ static void initDescriptors( void ) { RAY_PRIMARY_OUTPUTS(X) #undef X #undef INIT_BINDING - - VK_DescriptorsCreate(&g_ray_primary.desc.riptors); } -static void updateDescriptors( const vk_ray_resources_t* res ) { +static void writeValues( vk_descriptor_value_t *values, const vk_ray_resources_t* res ) { #define X(index, name, ...) \ - g_ray_primary.desc.values[RtPrim_Desc_Out_##name].image = (VkDescriptorImageInfo){ \ + values[RtPrim_Desc_Out_##name].image = (VkDescriptorImageInfo){ \ .sampler = VK_NULL_HANDLE, \ .imageView = res->name, \ .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ @@ -85,14 +57,14 @@ static void updateDescriptors( const vk_ray_resources_t* res ) { RAY_PRIMARY_OUTPUTS(X) #undef X - g_ray_primary.desc.values[RtPrim_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ + values[RtPrim_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, .accelerationStructureCount = 1, .pAccelerationStructures = &res->scene.tlas, }; #define DESC_SET_BUFFER(index, buffer_) \ - g_ray_primary.desc.values[index].buffer = (VkDescriptorBufferInfo){ \ + values[index].buffer = (VkDescriptorBufferInfo){ \ .buffer = res->scene.buffer_.buffer, \ .offset = res->scene.buffer_.offset, \ .range = res->scene.buffer_.size, \ @@ -105,13 +77,11 @@ RAY_PRIMARY_OUTPUTS(X) #undef DESC_SET_BUFFER - g_ray_primary.desc.values[RtPrim_Desc_Textures].image_array = res->scene.all_textures; - - VK_DescriptorsWrite(&g_ray_primary.desc.riptors); + values[RtPrim_Desc_Textures].image_array = res->scene.all_textures; } -static vk_pipeline_ray_t createPipeline( void ) { - // FIXME move this into vk_pipeline +struct ray_pass_s *R_VkRayPrimaryPassCreate( void ) { + // FIXME move this into vk_pipeline or something const struct SpecializationData { uint32_t sbt_record_size; } spec_data = { @@ -120,94 +90,44 @@ static vk_pipeline_ray_t createPipeline( void ) { const VkSpecializationMapEntry spec_map[] = { {.constantID = SPEC_SBT_RECORD_SIZE_INDEX, .offset = offsetof(struct SpecializationData, sbt_record_size), .size = sizeof(uint32_t) }, }; - VkSpecializationInfo spec = { + const VkSpecializationInfo spec = { .mapEntryCount = COUNTOF(spec_map), .pMapEntries = spec_map, .dataSize = sizeof(spec_data), .pData = &spec_data, }; -#define LIST_SHADER_MODULES(X) \ - X(RayGen, "ray_primary.rgen", RAYGEN) \ - X(Miss, "ray_primary.rmiss", MISS) \ - X(HitClosest, "ray_primary.rchit", CLOSEST_HIT) \ - X(HitAnyAlphaTest, "ray_common_alphatest.rahit", ANY_HIT) \ - - enum { -#define X(name, file, type) \ - ShaderStageIndex_##name, - LIST_SHADER_MODULES(X) -#undef X + const ray_pass_shader_t miss[] = { + "ray_primary.rmiss.spv" }; -const vk_shader_stage_t stages[] = { -#define X(name, file, type) \ - {.filename = file ".spv", .stage = VK_SHADER_STAGE_##type##_BIT_KHR, .specialization_info = &spec}, - LIST_SHADER_MODULES(X) -#undef X - }; - - const int misses[] = { - ShaderStageIndex_Miss, - }; - - const vk_pipeline_ray_hit_group_t hits[] = { - // TODO rigidly specify the expected sbt structure w/ offsets and materials - { // 0: fully opaque: no need for closest nor any hits - .closest = ShaderStageIndex_HitClosest, - .any = -1, - }, - { // 1: materials w/ alpha mask: need alpha test - .closest = ShaderStageIndex_HitClosest, - .any = ShaderStageIndex_HitAnyAlphaTest, // TODO these can directly be a string + const ray_pass_hit_group_t hit[] = { { + .closest = "ray_primary.rchit.spv", + .any = NULL, + }, { + .closest = "ray_primary.rchit.spv", + .any = "ray_common_alphatest.rahit.spv", }, }; - const vk_pipeline_ray_create_info_t prtc = { + const ray_pass_create_t rpc = { .debug_name = "primary ray", - .stages = stages, - .stages_count = COUNTOF(stages), - .groups = { - .miss = misses, - .miss_count = COUNTOF(misses), - .hit = hits, - .hit_count = COUNTOF(hits), + .layout = { + .bindings = bindings, + .bindings_count = COUNTOF(bindings), + .write_values_func = writeValues, + .push_constants = {0}, + }, + .tracing = { + .raygen = "ray_primary.rgen.spv", + .miss = miss, + .miss_count = COUNTOF(miss), + .hit = hit, + .hit_count = COUNTOF(hit), + .specialization = &spec, }, - .layout = g_ray_primary.desc.riptors.pipeline_layout, }; - return VK_PipelineRayTracingCreate(&prtc); -} - -qboolean XVK_RayTracePrimaryInit( void ) { initDescriptors(); - - g_ray_primary.pipeline = createPipeline(); - ASSERT(g_ray_primary.pipeline.pipeline != VK_NULL_HANDLE); - - return true; + return RayPassCreate( &rpc ); } - -void XVK_RayTracePrimaryDestroy( void ) { - VK_PipelineRayTracingDestroy(&g_ray_primary.pipeline); - VK_DescriptorsDestroy(&g_ray_primary.desc.riptors); -} - -void XVK_RayTracePrimaryReloadPipeline( void ) { - const vk_pipeline_ray_t new_pipeline = createPipeline(); - if (new_pipeline.pipeline == VK_NULL_HANDLE) - return; - - VK_PipelineRayTracingDestroy(&g_ray_primary.pipeline); - - g_ray_primary.pipeline = new_pipeline; -} - -void XVK_RayTracePrimary( VkCommandBuffer cmdbuf, const vk_ray_resources_t *res ) { - updateDescriptors( res ); - - vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_primary.pipeline.pipeline); - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_primary.desc.riptors.pipeline_layout, 0, 1, g_ray_primary.desc.riptors.desc_sets + 0, 0, NULL); - VK_PipelineRayTracingTrace(cmdbuf, &g_ray_primary.pipeline, res->width, res->height); -} - diff --git a/ref_vk/vk_ray_primary.h b/ref_vk/vk_ray_primary.h index cae4c291..38f89f80 100644 --- a/ref_vk/vk_ray_primary.h +++ b/ref_vk/vk_ray_primary.h @@ -1,9 +1,3 @@ #pragma once -#include "vk_ray_resources.h" - -qboolean XVK_RayTracePrimaryInit( void ); -void XVK_RayTracePrimaryDestroy( void ); -void XVK_RayTracePrimaryReloadPipeline( void ); - -void XVK_RayTracePrimary( VkCommandBuffer cmdbuf, const vk_ray_resources_t *res ); +struct ray_pass_s *R_VkRayPrimaryPassCreate( void ); diff --git a/ref_vk/vk_ray_resources.h b/ref_vk/vk_ray_resources.h index e45c44f5..0876b9d6 100644 --- a/ref_vk/vk_ray_resources.h +++ b/ref_vk/vk_ray_resources.h @@ -4,7 +4,7 @@ #include "vk_const.h" #include "shaders/ray_interop.h" -typedef struct { +typedef struct vk_ray_resources_s { uint32_t width, height; struct { diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index f1017f02..c2467736 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -1,5 +1,6 @@ #include "vk_rtx.h" +#include "ray_pass.h" #include "vk_ray_primary.h" #include "vk_ray_light_direct.h" @@ -162,6 +163,10 @@ static struct { unsigned frame_number; xvk_ray_frame_images_t frames[MAX_FRAMES_IN_FLIGHT]; + struct { + struct ray_pass_s *primary_ray; + } pass; + qboolean reload_pipeline; qboolean reload_lighting; } g_rtx = {0}; @@ -1075,7 +1080,7 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar 0, 0, NULL, ARRAYSIZE(bmb), bmb, ARRAYSIZE(image_barrier), image_barrier); } - XVK_RayTracePrimary( cmdbuf, &res ); + RayPassPerform( cmdbuf, g_rtx.pass.primary_ray, &res ); { const VkImageMemoryBarrier image_barriers[] = { @@ -1158,7 +1163,14 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) //vkDestroyPipeline(vk_core.device, g_rtx.pipeline, NULL); createPipeline(); - XVK_RayTracePrimaryReloadPipeline(); + { + struct ray_pass_s *new_primary_pass = R_VkRayPrimaryPassCreate(); + if (new_primary_pass) { + RayPassDestroy(g_rtx.pass.primary_ray); + g_rtx.pass.primary_ray = new_primary_pass; + } + } + XVK_RayTraceLightDirectReloadPipeline(); XVK_DenoiserReloadPipeline(); g_rtx.reload_pipeline = false; @@ -1329,7 +1341,9 @@ qboolean VK_RayInit( void ) ASSERT(vk_core.rtx); // TODO complain and cleanup on failure - ASSERT(XVK_RayTracePrimaryInit()); + g_rtx.pass.primary_ray = R_VkRayPrimaryPassCreate(); + + ASSERT(g_rtx.pass.primary_ray); ASSERT(XVK_RayTraceLightDirectInit()); g_rtx.sbt_record_size = vk_core.physical_device.sbt_record_size; @@ -1451,7 +1465,7 @@ void VK_RayShutdown( void ) { ASSERT(vk_core.rtx); XVK_RayTraceLightDirectDestroy(); - XVK_RayTracePrimaryDestroy(); + RayPassDestroy(g_rtx.pass.primary_ray); for (int i = 0; i < ARRAYSIZE(g_rtx.frames); ++i) { XVK_ImageDestroy(&g_rtx.frames[i].denoised); From 1bf08cc2c367b1c3524fc8d0a071eb1901eb27b7 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 27 Jan 2022 17:10:50 +0300 Subject: [PATCH 122/548] engine: touch: fix button deletion --- engine/client/in_touch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/in_touch.c b/engine/client/in_touch.c index 343b41a2..f59c88c5 100644 --- a/engine/client/in_touch.c +++ b/engine/client/in_touch.c @@ -530,7 +530,7 @@ static void Touch_RemoveButtonFromList( touchbuttonlist_t *list, const char *nam IN_TouchEditClear(); - while(( button = Touch_FindFirst( &touch.list_user, name, !privileged ))) + while(( button = Touch_FindFirst( &touch.list_user, name, privileged ))) { if( button->prev ) button->prev->next = button->next; From 89baa633c86324d00eec841fc9c365992a635c27 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 28 Jan 2022 01:20:14 +0300 Subject: [PATCH 123/548] engine: sdl: fix mouse activated when focus gained but not game is active --- engine/platform/sdl/events.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/engine/platform/sdl/events.c b/engine/platform/sdl/events.c index e2530909..0411c41e 100644 --- a/engine/platform/sdl/events.c +++ b/engine/platform/sdl/events.c @@ -340,7 +340,11 @@ static void SDLash_ActiveEvent( int gain ) if( gain ) { host.status = HOST_FRAME; - IN_ActivateMouse( ); + if( cls.key_dest == key_game ) + { + IN_ActivateMouse( ); + } + if( dma.initialized && snd_mute_losefocus.value ) { SNDDMA_Activate( true ); @@ -360,7 +364,11 @@ static void SDLash_ActiveEvent( int gain ) } #endif host.status = HOST_NOFOCUS; - IN_DeactivateMouse(); + if( cls.key_dest == key_game ) + { + IN_DeactivateMouse(); + } + if( dma.initialized && snd_mute_losefocus.value ) { SNDDMA_Activate( false ); From c3a7a016204ad4f79fcdfda42c8a22e76f1d55f0 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 27 Jan 2022 17:47:00 -0800 Subject: [PATCH 124/548] rt: passify light direct poly --- ref_vk/ray_pass.c | 1 + ref_vk/vk_ray_light_direct.c | 147 ++++++++--------------------------- ref_vk/vk_ray_light_direct.h | 7 +- ref_vk/vk_ray_primary.c | 2 - ref_vk/vk_rtx.c | 28 ++++--- 5 files changed, 50 insertions(+), 135 deletions(-) diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c index 7dcf901d..6a67a4ed 100644 --- a/ref_vk/ray_pass.c +++ b/ref_vk/ray_pass.c @@ -94,6 +94,7 @@ static ray_pass_t *createRayPass( const ray_pass_create_t *create ) { ASSERT(miss_index < MAX_MISS_GROUPS); // TODO handle duplicate filenames + // TODO really, there should be a global table of shader modules as some of them are used across several passes (e.g. any hit alpha test) misses[miss_index++] = stage_index; stages[stage_index++] = (vk_shader_stage_t) { .filename = *shader, diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 47ef0996..88faf704 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -1,11 +1,7 @@ #include "vk_ray_light_direct.h" -#include "vk_ray_internal.h" - -#include "vk_descriptor.h" -#include "vk_pipeline.h" -#include "vk_buffer.h" -#include "shaders/ray_interop.h" +#include "vk_ray_resources.h" +#include "ray_pass.h" enum { // TODO set 0 ? @@ -31,41 +27,12 @@ enum { RtLDir_Desc_COUNT }; -#define LIST_SHADER_MODULES(X) \ - X(RayGen, "ray_light_poly_direct.rgen", RAYGEN) \ - X(AlphaTest, "ray_common_alphatest.rahit", ANY_HIT) \ - X(Miss, "ray_shadow.rmiss", MISS) \ - -static struct { - struct { - vk_descriptors_t riptors; - VkDescriptorSetLayoutBinding bindings[RtLDir_Desc_COUNT]; - vk_descriptor_value_t values[RtLDir_Desc_COUNT]; - - // TODO: split into two sets, one common to all rt passes (tlas, kusochki, etc), another one this pass only - VkDescriptorSet sets[1]; - } desc; - - vk_pipeline_ray_t pipeline; -} g_ray_light_direct; +static VkDescriptorSetLayoutBinding bindings[RtLDir_Desc_COUNT]; static void initDescriptors( void ) { - g_ray_light_direct.desc.riptors = (vk_descriptors_t) { - .bindings = g_ray_light_direct.desc.bindings, - .num_bindings = COUNTOF(g_ray_light_direct.desc.bindings), - .values = g_ray_light_direct.desc.values, - .num_sets = COUNTOF(g_ray_light_direct.desc.sets), - .desc_sets = g_ray_light_direct.desc.sets, - /* .push_constants = (VkPushConstantRange){ */ - /* .offset = 0, */ - /* .size = sizeof(vk_rtx_push_constants_t), */ - /* .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, */ - /* }, */ - }; - // FIXME more conservative shader stages #define INIT_BINDING(index, name, type, count) \ - g_ray_light_direct.desc.bindings[RtLDir_Desc_##name] = (VkDescriptorSetLayoutBinding){ \ + bindings[RtLDir_Desc_##name] = (VkDescriptorSetLayoutBinding){ \ .binding = index, \ .descriptorType = type, \ .descriptorCount = count, \ @@ -89,13 +56,11 @@ static void initDescriptors( void ) { RAY_LIGHT_DIRECT_OUTPUTS(X) #undef X #undef INIT_BINDING - - VK_DescriptorsCreate(&g_ray_light_direct.desc.riptors); } -static void updateDescriptors( const vk_ray_resources_t *res ) { +static void writeValues( vk_descriptor_value_t *values, const vk_ray_resources_t* res ) { #define X(index, name, ...) \ - g_ray_light_direct.desc.values[RtLDir_Desc_##name].image = (VkDescriptorImageInfo){ \ + values[RtLDir_Desc_##name].image = (VkDescriptorImageInfo){ \ .sampler = VK_NULL_HANDLE, \ .imageView = res->name, \ .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ @@ -104,7 +69,7 @@ static void updateDescriptors( const vk_ray_resources_t *res ) { #undef X #define X(index, name, ...) \ - g_ray_light_direct.desc.values[RtLDir_Desc_##name].image = (VkDescriptorImageInfo){ \ + values[RtLDir_Desc_##name].image = (VkDescriptorImageInfo){ \ .sampler = VK_NULL_HANDLE, \ .imageView = res->name, \ .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ @@ -112,14 +77,14 @@ static void updateDescriptors( const vk_ray_resources_t *res ) { RAY_LIGHT_DIRECT_OUTPUTS(X) #undef X - g_ray_light_direct.desc.values[RtLDir_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ + values[RtLDir_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, .accelerationStructureCount = 1, .pAccelerationStructures = &res->scene.tlas, }; #define DESC_SET_BUFFER(index, buffer_) \ - g_ray_light_direct.desc.values[index].buffer = (VkDescriptorBufferInfo){ \ + values[index].buffer = (VkDescriptorBufferInfo){ \ .buffer = res->scene.buffer_.buffer, \ .offset = res->scene.buffer_.offset, \ .range = res->scene.buffer_.size, \ @@ -134,12 +99,10 @@ static void updateDescriptors( const vk_ray_resources_t *res ) { #undef DESC_SET_BUFFER - g_ray_light_direct.desc.values[RtLDir_Desc_Textures].image_array = res->scene.all_textures; - - VK_DescriptorsWrite(&g_ray_light_direct.desc.riptors); + values[RtLDir_Desc_Textures].image_array = res->scene.all_textures; } -static vk_pipeline_ray_t createPipeline( void ) { +struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { // FIXME move this into vk_pipeline const struct SpecializationData { uint32_t sbt_record_size; @@ -156,80 +119,34 @@ static vk_pipeline_ray_t createPipeline( void ) { .pData = &spec_data, }; - enum { -#define X(name, file, type) \ - ShaderStageIndex_##name, - LIST_SHADER_MODULES(X) -#undef X - }; -const vk_shader_stage_t stages[] = { -#define X(name, file, type) \ - {.filename = file ".spv", .stage = VK_SHADER_STAGE_##type##_BIT_KHR, .specialization_info = &spec}, - LIST_SHADER_MODULES(X) -#undef X + const ray_pass_shader_t miss[] = { + "ray_shadow.rmiss.spv" }; - const int misses[] = { - ShaderStageIndex_Miss, - }; - - const vk_pipeline_ray_hit_group_t hits[] = { - // TODO rigidly specify the expected sbt structure w/ offsets and materials - { // 0: fully opaque: no need for closest nor any hits - .closest = -1, - .any = -1, - }, - { // 1: materials w/ alpha mask: need alpha test - .closest = -1, - .any = ShaderStageIndex_AlphaTest, // TODO these can directly be a string + const ray_pass_hit_group_t hit[] = { { + .closest = NULL, + .any = "ray_common_alphatest.rahit.spv", }, }; - const vk_pipeline_ray_create_info_t prtc = { - .debug_name = "light direct", - .stages = stages, - .stages_count = COUNTOF(stages), - .groups = { - .miss = misses, - .miss_count = COUNTOF(misses), - .hit = hits, - .hit_count = COUNTOF(hits), + const ray_pass_create_t rpc = { + .debug_name = "light direct poly", + .layout = { + .bindings = bindings, + .bindings_count = COUNTOF(bindings), + .write_values_func = writeValues, + .push_constants = {0}, + }, + .tracing = { + .raygen = "ray_light_poly_direct.rgen.spv", + .miss = miss, + .miss_count = COUNTOF(miss), + .hit = hit, + .hit_count = COUNTOF(hit), + .specialization = &spec, }, - .layout = g_ray_light_direct.desc.riptors.pipeline_layout, }; - return VK_PipelineRayTracingCreate(&prtc); -} - -qboolean XVK_RayTraceLightDirectInit( void ) { initDescriptors(); - - g_ray_light_direct.pipeline = createPipeline(); - ASSERT(g_ray_light_direct.pipeline.pipeline != VK_NULL_HANDLE); - - return true; + return RayPassCreate( &rpc ); } - -void XVK_RayTraceLightDirectDestroy( void ) { - VK_PipelineRayTracingDestroy(&g_ray_light_direct.pipeline); - VK_DescriptorsDestroy(&g_ray_light_direct.desc.riptors); -} - -void XVK_RayTraceLightDirectReloadPipeline( void ) { - const vk_pipeline_ray_t new_pipeline = createPipeline(); - if (new_pipeline.pipeline == VK_NULL_HANDLE) - return; - - VK_PipelineRayTracingDestroy(&g_ray_light_direct.pipeline); - - g_ray_light_direct.pipeline = new_pipeline; -} - -void XVK_RayTraceLightDirect( VkCommandBuffer cmdbuf, const vk_ray_resources_t *res ) { - updateDescriptors( res ); - - vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_light_direct.pipeline.pipeline); - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_light_direct.desc.riptors.pipeline_layout, 0, 1, g_ray_light_direct.desc.riptors.desc_sets + 0, 0, NULL); - VK_PipelineRayTracingTrace(cmdbuf, &g_ray_light_direct.pipeline, res->width, res->height); -} - diff --git a/ref_vk/vk_ray_light_direct.h b/ref_vk/vk_ray_light_direct.h index 63371ba4..1c03f190 100644 --- a/ref_vk/vk_ray_light_direct.h +++ b/ref_vk/vk_ray_light_direct.h @@ -1,8 +1,3 @@ #pragma once -#include "vk_ray_resources.h" -qboolean XVK_RayTraceLightDirectInit( void ); -void XVK_RayTraceLightDirectDestroy( void ); -void XVK_RayTraceLightDirectReloadPipeline( void ); - -void XVK_RayTraceLightDirect( VkCommandBuffer cmdbuf, const vk_ray_resources_t *res ); +struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ); diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index a8df5aa0..e46a3aac 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -1,7 +1,5 @@ #include "vk_ray_primary.h" -#include "vk_ray_internal.h" -#include "vk_descriptor.h" #include "vk_ray_resources.h" #include "ray_pass.h" diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index c2467736..6e047178 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -165,6 +165,7 @@ static struct { struct { struct ray_pass_s *primary_ray; + struct ray_pass_s *light_direct_poly; } pass; qboolean reload_pipeline; @@ -1095,7 +1096,7 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar 0, 0, NULL, 0, NULL, ARRAYSIZE(image_barriers), image_barriers); } - XVK_RayTraceLightDirect( cmdbuf, &res ); + RayPassPerform( cmdbuf, g_rtx.pass.light_direct_poly, &res ); { const VkImageMemoryBarrier image_barriers[] = { @@ -1140,6 +1141,14 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar qboolean initVk2d(void); void deinitVk2d(void); +static void reloadPass( struct ray_pass_s **slot, struct ray_pass_s *new_pass ) { + if (!new_pass) + return; + + RayPassDestroy( *slot ); + *slot = new_pass; +} + void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) { const VkCommandBuffer cmdbuf = args->cmdbuf; @@ -1163,15 +1172,9 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) //vkDestroyPipeline(vk_core.device, g_rtx.pipeline, NULL); createPipeline(); - { - struct ray_pass_s *new_primary_pass = R_VkRayPrimaryPassCreate(); - if (new_primary_pass) { - RayPassDestroy(g_rtx.pass.primary_ray); - g_rtx.pass.primary_ray = new_primary_pass; - } - } + reloadPass( &g_rtx.pass.primary_ray, R_VkRayPrimaryPassCreate()); + reloadPass( &g_rtx.pass.light_direct_poly, R_VkRayLightDirectPolyPassCreate()); - XVK_RayTraceLightDirectReloadPipeline(); XVK_DenoiserReloadPipeline(); g_rtx.reload_pipeline = false; } @@ -1342,9 +1345,10 @@ qboolean VK_RayInit( void ) // TODO complain and cleanup on failure g_rtx.pass.primary_ray = R_VkRayPrimaryPassCreate(); - ASSERT(g_rtx.pass.primary_ray); - ASSERT(XVK_RayTraceLightDirectInit()); + + g_rtx.pass.light_direct_poly = R_VkRayLightDirectPolyPassCreate(); + ASSERT(g_rtx.pass.light_direct_poly); g_rtx.sbt_record_size = vk_core.physical_device.sbt_record_size; g_rtx.uniform_unit_size = ALIGN_UP(sizeof(struct UniformBuffer), vk_core.physical_device.properties.limits.minUniformBufferOffsetAlignment); @@ -1464,7 +1468,7 @@ RAY_LIGHT_DIRECT_OUTPUTS(X) void VK_RayShutdown( void ) { ASSERT(vk_core.rtx); - XVK_RayTraceLightDirectDestroy(); + RayPassDestroy(g_rtx.pass.light_direct_poly); RayPassDestroy(g_rtx.pass.primary_ray); for (int i = 0; i < ARRAYSIZE(g_rtx.frames); ++i) { From 90fa6161ac372b84f77b4a757d27521f135d0074 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 27 Jan 2022 19:14:56 -0800 Subject: [PATCH 125/548] rt: add parallel direct point light pass (look how easy that was! yay passes! ... still need to do spir-v parsing to extract bindings though) perf (c1a0 lobby, 720p, 6900XT) - total ray tracing time: 15.2ms - primary: 0.7ms v:80/80 s:60/128 lds:2048 o:12/16 (-4v +1o) - dir poly: 13.8ms v:256/256 s:98/128 lds:2048 o:4/16 (-28v +1o) - dir point: 0.9ms v:85/96 s:68/128 lds:2048 o:10/16 (-6v +1o) dir point and poly are not synchronized and overlap. but poly takes most of the time, and point can only ramp up gradually at the very tail of poly. --- ref_vk/shaders/denoiser.comp | 15 ++++- ref_vk/shaders/light.glsl | 11 +++- ref_vk/shaders/ray_interop.h | 6 +- ref_vk/shaders/ray_light_direct.glsl | 74 ++++++++++++++++++++++ ref_vk/shaders/ray_light_direct_point.rgen | 6 ++ ref_vk/shaders/ray_light_poly_direct.rgen | 70 +------------------- ref_vk/vk_denoiser.c | 27 ++++++++ ref_vk/vk_ray_light_direct.c | 73 ++++++++++++++++++++- ref_vk/vk_ray_light_direct.h | 1 + ref_vk/vk_ray_resources.h | 4 +- ref_vk/vk_rtx.c | 29 ++++++--- 11 files changed, 230 insertions(+), 86 deletions(-) create mode 100644 ref_vk/shaders/ray_light_direct.glsl create mode 100644 ref_vk/shaders/ray_light_direct_point.rgen diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 7fb43dc8..85953359 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -8,8 +8,13 @@ layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; layout(set = 0, binding = 0, rgba16f) uniform image2D dest; layout(set = 0, binding = 1, rgba8) uniform readonly image2D src_base_color; + layout(set = 0, binding = 2, rgba16f) uniform readonly image2D src_light_direct_poly_diffuse; -//layout(set = 0, binding = 3, rgba16f) uniform readonly image2D src_light_direct_poly_specular; +layout(set = 0, binding = 3, rgba16f) uniform readonly image2D src_light_direct_poly_specular; + +layout(set = 0, binding = 4, rgba16f) uniform readonly image2D src_light_direct_point_diffuse; +layout(set = 0, binding = 5, rgba16f) uniform readonly image2D src_light_direct_point_specular; + //layout(set = 0, binding = 2, rgba16f) uniform readonly image2D src_light_direct_poly_diffuse; /* layout(set = 0, binding = 3, rgba16f) uniform readonly image2D src_specular; */ /* layout(set = 0, binding = 4, rgba16f) uniform readonly image2D src_additive; */ @@ -91,8 +96,12 @@ void main() { /* return; */ #if 1 - vec3 colour = imageLoad(src_light_direct_poly_diffuse, pix).rgb * base_color.rgb; - //colour += imageLoad(src_light_direct_poly_specular, pix).rgb * base_color.rgb; + vec3 colour = vec3(0.); + colour += imageLoad(src_light_direct_poly_diffuse, pix).rgb; + //colour += imageLoad(src_light_direct_poly_specular, pix).rgb; + colour += imageLoad(src_light_direct_point_diffuse, pix).rgb; + //colour += imageLoad(src_light_direct_point_specular, pix).rgb; + colour *= base_color.rgb; #else float total_scale = 0.; vec3 colour = vec3(0.); diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index b48a9d96..5ab60a08 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -10,8 +10,12 @@ const float shadow_offset_fudge = .1; #include "brdf.h" #include "light_common.glsl" -#include "light_polygon.glsl" +#if LIGHT_POLYGON +#include "light_polygon.glsl" +#endif + +#if LIGHT_POINT void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { diffuse = specular = vec3(0.); const uint num_point_lights = uint(light_grid.clusters[cluster_index].num_point_lights); @@ -98,6 +102,7 @@ void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec specular += lspecular; } // for all lights } +#endif void computeLighting(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { diffuse = specular = vec3(0.); @@ -116,11 +121,11 @@ void computeLighting(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialPro //C = vec3(float(int(light_grid.clusters[cluster_index].num_emissive_surfaces))); //C += .3 * fract(vec3(light_cell) / 4.); -#if 1 +#if LIGHT_POLYGON sampleEmissiveSurfaces(P, N, throughput, view_dir, material, cluster_index, diffuse, specular); #endif -#if 0 +#if LIGHT_POINT vec3 ldiffuse = vec3(0.), lspecular = vec3(0.); computePointLights(P, N, cluster_index, throughput, view_dir, material, ldiffuse, lspecular); diffuse += ldiffuse; diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index 9f6a1833..9f325df1 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -6,10 +6,14 @@ X(10, position_t, rgba32f) \ X(11, normals_gs, rgba16f) \ -#define RAY_LIGHT_DIRECT_OUTPUTS(X) \ +#define RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) \ X(13, light_poly_diffuse, rgba16f) \ X(14, light_poly_specular, rgba16f) \ +#define RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) \ + X(13, light_point_diffuse, rgba16f) \ + X(14, light_point_specular, rgba16f) \ + #define RAY_PRIMARY_OUTPUTS(X) \ X(10, base_color_a, rgba8) \ X(11, position_t, rgba32f) \ diff --git a/ref_vk/shaders/ray_light_direct.glsl b/ref_vk/shaders/ray_light_direct.glsl new file mode 100644 index 00000000..ca87beaf --- /dev/null +++ b/ref_vk/shaders/ray_light_direct.glsl @@ -0,0 +1,74 @@ +#extension GL_EXT_control_flow_attributes : require +#extension GL_EXT_ray_tracing: require + +#include "utils.glsl" +#include "noise.glsl" + +#define GLSL +#include "ray_interop.h" +#undef GLSL + +#define X(index, name, format) layout(set=0,binding=index,format) uniform readonly image2D name; +RAY_LIGHT_DIRECT_INPUTS(X) +#undef X +#define X(index, name, format) layout(set=0,binding=index,format) uniform writeonly image2D out_image_##name; +OUTPUTS(X) +#undef X + +layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; +layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; + +#include "ray_kusochki.glsl" + +#define RAY_TRACE +#define RAY_TRACE2 +#undef SHADER_OFFSET_HIT_SHADOW_BASE +#define SHADER_OFFSET_HIT_SHADOW_BASE 0 +#undef SHADER_OFFSET_MISS_SHADOW +#define SHADER_OFFSET_MISS_SHADOW 0 +#undef PAYLOAD_LOCATION_SHADOW +#define PAYLOAD_LOCATION_SHADOW 0 + +#define BINDING_LIGHTS 7 +#define BINDING_LIGHT_CLUSTERS 8 +#include "light.glsl" + +void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { + const vec4 n = imageLoad(normals_gs, uv); + geometry_normal = normalDecode(n.xy); + shading_normal = normalDecode(n.zw); +} + +void main() { + const vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.; + const ivec2 pix = ivec2(gl_LaunchIDEXT.xy); + + rand01_state = ubo.random_seed + gl_LaunchIDEXT.x * 1833 + gl_LaunchIDEXT.y * 31337; + + // FIXME incorrect for reflection/refraction + vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); + vec3 direction = normalize((ubo.inv_view * vec4(target.xyz, 0)).xyz); + + MaterialProperties material; + material.baseColor = vec3(1.); + material.emissive = vec3(0.f); + material.metalness = 0.f; // TODO + material.roughness = 1.f; // TODO + + const vec3 pos = imageLoad(position_t, pix).xyz; + + vec3 geometry_normal, shading_normal; + readNormals(pix, geometry_normal, shading_normal); + + const vec3 throughput = vec3(1.); + vec3 diffuse = vec3(0.), specular = vec3(0.); + computeLighting(pos + geometry_normal * .001, shading_normal, throughput, -direction, material, diffuse, specular); + +#if LIGHT_POINT + imageStore(out_image_light_point_diffuse, pix, vec4(diffuse, 0.f)); + imageStore(out_image_light_point_specular, pix, vec4(specular, 0.f)); +#else + imageStore(out_image_light_poly_diffuse, pix, vec4(diffuse, 0.f)); + imageStore(out_image_light_poly_specular, pix, vec4(specular, 0.f)); +#endif +} diff --git a/ref_vk/shaders/ray_light_direct_point.rgen b/ref_vk/shaders/ray_light_direct_point.rgen new file mode 100644 index 00000000..f8bbfc6c --- /dev/null +++ b/ref_vk/shaders/ray_light_direct_point.rgen @@ -0,0 +1,6 @@ +#version 460 core +#extension GL_GOOGLE_include_directive : require + +#define LIGHT_POINT 1 +#define OUTPUTS RAY_LIGHT_DIRECT_POINT_OUTPUTS +#include "ray_light_direct.glsl" diff --git a/ref_vk/shaders/ray_light_poly_direct.rgen b/ref_vk/shaders/ray_light_poly_direct.rgen index 306b356c..7fbd1945 100644 --- a/ref_vk/shaders/ray_light_poly_direct.rgen +++ b/ref_vk/shaders/ray_light_poly_direct.rgen @@ -1,70 +1,6 @@ #version 460 core #extension GL_GOOGLE_include_directive : require -#extension GL_EXT_control_flow_attributes : require -#extension GL_EXT_ray_tracing: require -#include "utils.glsl" - -#define GLSL -#include "ray_interop.h" -#undef GLSL - -#define X(index, name, format) layout(set=0,binding=index,format) uniform readonly image2D name; -RAY_LIGHT_DIRECT_INPUTS(X) -#undef X -#define X(index, name, format) layout(set=0,binding=index,format) uniform writeonly image2D out_image_##name; -RAY_LIGHT_DIRECT_OUTPUTS(X) -#undef X - -layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; -layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; - -#include "ray_kusochki.glsl" - -#define RAY_TRACE -#define RAY_TRACE2 -#undef SHADER_OFFSET_HIT_SHADOW_BASE -#define SHADER_OFFSET_HIT_SHADOW_BASE 0 -#undef SHADER_OFFSET_MISS_SHADOW -#define SHADER_OFFSET_MISS_SHADOW 0 -#undef PAYLOAD_LOCATION_SHADOW -#define PAYLOAD_LOCATION_SHADOW 0 - -#define BINDING_LIGHTS 7 -#define BINDING_LIGHT_CLUSTERS 8 -#include "light.glsl" - -void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { - const vec4 n = imageLoad(normals_gs, uv); - geometry_normal = normalDecode(n.xy); - shading_normal = normalDecode(n.zw); -} - -void main() { - const vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.; - const ivec2 pix = ivec2(gl_LaunchIDEXT.xy); - - rand01_state = ubo.random_seed + gl_LaunchIDEXT.x * 1833 + gl_LaunchIDEXT.y * 31337; - - // FIXME incorrect for reflection/refraction - vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); - vec3 direction = normalize((ubo.inv_view * vec4(target.xyz, 0)).xyz); - - MaterialProperties material; - material.baseColor = vec3(1.); - material.emissive = vec3(0.f); - material.metalness = 0.f; // TODO - material.roughness = 1.f; // TODO - - const vec3 pos = imageLoad(position_t, pix).xyz; - - vec3 geometry_normal, shading_normal; - readNormals(pix, geometry_normal, shading_normal); - - const vec3 throughput = vec3(1.); - vec3 diffuse = vec3(0.), specular = vec3(0.); - computeLighting(pos + geometry_normal * .001, shading_normal, throughput, -direction, material, diffuse, specular); - - imageStore(out_image_light_poly_diffuse, pix, vec4(diffuse, 0.f)); - imageStore(out_image_light_poly_specular, pix, vec4(specular, 0.f)); -} +#define LIGHT_POLYGON 1 +#define OUTPUTS RAY_LIGHT_DIRECT_POLY_OUTPUTS +#include "ray_light_direct.glsl" diff --git a/ref_vk/vk_denoiser.c b/ref_vk/vk_denoiser.c index 404e2766..4d094923 100644 --- a/ref_vk/vk_denoiser.c +++ b/ref_vk/vk_denoiser.c @@ -9,7 +9,12 @@ enum { DenoiserBinding_DestImage = 0, DenoiserBinding_Source_BaseColor = 1, + DenoiserBinding_Source_LightPolyDiffuse = 2, + DenoiserBinding_Source_LightPolySpecular = 3, + DenoiserBinding_Source_LightPointDiffuse = 4, + DenoiserBinding_Source_LightPointSpecular = 5, + /* DenoiserBinding_Source_Specular = 3, */ /* DenoiserBinding_Source_Additive = 4, */ /* DenoiserBinding_Source_Normals = 5, */ @@ -61,6 +66,24 @@ static void createLayouts( void ) { .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, }; + g_denoiser.desc_bindings[DenoiserBinding_Source_LightPolySpecular] = (VkDescriptorSetLayoutBinding){ + .binding = DenoiserBinding_Source_LightPolySpecular, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + }; + g_denoiser.desc_bindings[DenoiserBinding_Source_LightPointDiffuse] = (VkDescriptorSetLayoutBinding){ + .binding = DenoiserBinding_Source_LightPointDiffuse, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + }; + g_denoiser.desc_bindings[DenoiserBinding_Source_LightPointSpecular] = (VkDescriptorSetLayoutBinding){ + .binding = DenoiserBinding_Source_LightPointSpecular, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + }; VK_DescriptorsCreate(&g_denoiser.descriptors); } @@ -113,7 +136,11 @@ void XVK_DenoiserDenoise( VkCommandBuffer cmdbuf, const vk_ray_resources_t* res BIND_IMAGE(DestImage, denoised); BIND_IMAGE(Source_BaseColor, base_color_a); + BIND_IMAGE(Source_LightPolyDiffuse, light_poly_diffuse); + BIND_IMAGE(Source_LightPolySpecular, light_poly_specular); + BIND_IMAGE(Source_LightPointDiffuse, light_point_diffuse); + BIND_IMAGE(Source_LightPointSpecular, light_point_specular); VK_DescriptorsWrite(&g_denoiser.descriptors); diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 88faf704..4c4f2a79 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -20,8 +20,10 @@ enum { #define X(index, name, ...) RtLDir_Desc_##name, RAY_LIGHT_DIRECT_INPUTS(X) #undef X + + // FIXME it's an artifact that point and poly outputs have same bindings #define X(index, name, ...) RtLDir_Desc_##name, - RAY_LIGHT_DIRECT_OUTPUTS(X) + RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) #undef X RtLDir_Desc_COUNT @@ -53,7 +55,7 @@ static void initDescriptors( void ) { RAY_LIGHT_DIRECT_INPUTS(X) #undef X #define X(index, name, ...) INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1); - RAY_LIGHT_DIRECT_OUTPUTS(X) + RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) #undef X #undef INIT_BINDING } @@ -74,7 +76,7 @@ static void writeValues( vk_descriptor_value_t *values, const vk_ray_resources_t .imageView = res->name, \ .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ }; - RAY_LIGHT_DIRECT_OUTPUTS(X) + RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) #undef X values[RtLDir_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ @@ -150,3 +152,68 @@ struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { initDescriptors(); return RayPassCreate( &rpc ); } + +static void writePointValues( vk_descriptor_value_t *values, const vk_ray_resources_t* res ) { + writeValues( values, res ); + + values[RtLDir_Desc_light_poly_diffuse].image = (VkDescriptorImageInfo) { + .sampler = VK_NULL_HANDLE, + .imageView = res->light_point_diffuse, + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + + values[RtLDir_Desc_light_poly_specular].image = (VkDescriptorImageInfo) { + .sampler = VK_NULL_HANDLE, + .imageView = res->light_point_specular, + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + }; +} + +struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ) { + // FIXME move this into vk_pipeline + const struct SpecializationData { + uint32_t sbt_record_size; + } spec_data = { + .sbt_record_size = vk_core.physical_device.sbt_record_size, + }; + const VkSpecializationMapEntry spec_map[] = { + {.constantID = SPEC_SBT_RECORD_SIZE_INDEX, .offset = offsetof(struct SpecializationData, sbt_record_size), .size = sizeof(uint32_t) }, + }; + VkSpecializationInfo spec = { + .mapEntryCount = COUNTOF(spec_map), + .pMapEntries = spec_map, + .dataSize = sizeof(spec_data), + .pData = &spec_data, + }; + + const ray_pass_shader_t miss[] = { + "ray_shadow.rmiss.spv" + }; + + const ray_pass_hit_group_t hit[] = { { + .closest = NULL, + .any = "ray_common_alphatest.rahit.spv", + }, + }; + + const ray_pass_create_t rpc = { + .debug_name = "light direct point", + .layout = { + .bindings = bindings, + .bindings_count = COUNTOF(bindings), + .write_values_func = writePointValues, + .push_constants = {0}, + }, + .tracing = { + .raygen = "ray_light_direct_point.rgen.spv", + .miss = miss, + .miss_count = COUNTOF(miss), + .hit = hit, + .hit_count = COUNTOF(hit), + .specialization = &spec, + }, + }; + + initDescriptors(); + return RayPassCreate( &rpc ); +} diff --git a/ref_vk/vk_ray_light_direct.h b/ref_vk/vk_ray_light_direct.h index 1c03f190..c6339ddb 100644 --- a/ref_vk/vk_ray_light_direct.h +++ b/ref_vk/vk_ray_light_direct.h @@ -1,3 +1,4 @@ #pragma once struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ); +struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ); diff --git a/ref_vk/vk_ray_resources.h b/ref_vk/vk_ray_resources.h index 0876b9d6..621fe921 100644 --- a/ref_vk/vk_ray_resources.h +++ b/ref_vk/vk_ray_resources.h @@ -20,8 +20,10 @@ typedef struct vk_ray_resources_s { #define X(index, name, ...) VkImageView name; RAY_PRIMARY_OUTPUTS(X) - RAY_LIGHT_DIRECT_OUTPUTS(X) + RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) + RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) X(-1, denoised) #undef X } vk_ray_resources_t; + diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 6e047178..c396be6b 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -96,7 +96,8 @@ typedef struct { #define X(index, name, ...) xvk_image_t name; RAY_PRIMARY_OUTPUTS(X) -RAY_LIGHT_DIRECT_OUTPUTS(X) +RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) +RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) #undef X xvk_image_t diffuse_gi; @@ -166,6 +167,7 @@ static struct { struct { struct ray_pass_s *primary_ray; struct ray_pass_s *light_direct_poly; + struct ray_pass_s *light_direct_point; } pass; qboolean reload_pipeline; @@ -1013,7 +1015,8 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar }, #define X(index, name, ...) .name = current_frame->name.view, RAY_PRIMARY_OUTPUTS(X) - RAY_LIGHT_DIRECT_OUTPUTS(X) + RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) + RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) X(-1, denoised) #undef X }; @@ -1086,7 +1089,8 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar { const VkImageMemoryBarrier image_barriers[] = { RAY_PRIMARY_OUTPUTS(IMAGE_BARRIER_READ) - RAY_LIGHT_DIRECT_OUTPUTS(IMAGE_BARRIER_WRITE) + RAY_LIGHT_DIRECT_POLY_OUTPUTS(IMAGE_BARRIER_WRITE) + RAY_LIGHT_DIRECT_POINT_OUTPUTS(IMAGE_BARRIER_WRITE) }; vkCmdPipelineBarrier(args->cmdbuf, @@ -1097,10 +1101,12 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar } RayPassPerform( cmdbuf, g_rtx.pass.light_direct_poly, &res ); + RayPassPerform( cmdbuf, g_rtx.pass.light_direct_point, &res ); { const VkImageMemoryBarrier image_barriers[] = { - RAY_LIGHT_DIRECT_OUTPUTS(IMAGE_BARRIER_READ) + RAY_LIGHT_DIRECT_POLY_OUTPUTS(IMAGE_BARRIER_READ) + RAY_LIGHT_DIRECT_POINT_OUTPUTS(IMAGE_BARRIER_READ) LIST_GBUFFER_IMAGES(IMAGE_BARRIER_READ) IMAGE_BARRIER_WRITE(-1/*unused*/, denoised) }; @@ -1174,6 +1180,7 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) reloadPass( &g_rtx.pass.primary_ray, R_VkRayPrimaryPassCreate()); reloadPass( &g_rtx.pass.light_direct_poly, R_VkRayLightDirectPolyPassCreate()); + reloadPass( &g_rtx.pass.light_direct_point, R_VkRayLightDirectPointPassCreate()); XVK_DenoiserReloadPipeline(); g_rtx.reload_pipeline = false; @@ -1350,6 +1357,9 @@ qboolean VK_RayInit( void ) g_rtx.pass.light_direct_poly = R_VkRayLightDirectPolyPassCreate(); ASSERT(g_rtx.pass.light_direct_poly); + g_rtx.pass.light_direct_point = R_VkRayLightDirectPointPassCreate(); + ASSERT(g_rtx.pass.light_direct_point); + g_rtx.sbt_record_size = vk_core.physical_device.sbt_record_size; g_rtx.uniform_unit_size = ALIGN_UP(sizeof(struct UniformBuffer), vk_core.physical_device.properties.limits.minUniformBufferOffsetAlignment); @@ -1446,8 +1456,9 @@ qboolean VK_RayInit( void ) #define X(index, name, format) CREATE_GBUFFER_IMAGE(name, format, 0); // TODO better format for normals VK_FORMAT_R16G16B16A16_SNORM // TODO make sure this format and usage is suppported -RAY_PRIMARY_OUTPUTS(X) -RAY_LIGHT_DIRECT_OUTPUTS(X) + RAY_PRIMARY_OUTPUTS(X) + RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) + RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) #undef X #undef rgba8 #undef rgba32f @@ -1469,13 +1480,15 @@ void VK_RayShutdown( void ) { ASSERT(vk_core.rtx); RayPassDestroy(g_rtx.pass.light_direct_poly); + RayPassDestroy(g_rtx.pass.light_direct_point); RayPassDestroy(g_rtx.pass.primary_ray); for (int i = 0; i < ARRAYSIZE(g_rtx.frames); ++i) { XVK_ImageDestroy(&g_rtx.frames[i].denoised); #define X(index, name, ...) XVK_ImageDestroy(&g_rtx.frames[i].name); -RAY_PRIMARY_OUTPUTS(X) -RAY_LIGHT_DIRECT_OUTPUTS(X) + RAY_PRIMARY_OUTPUTS(X) + RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) + RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) #undef X XVK_ImageDestroy(&g_rtx.frames[i].diffuse_gi); XVK_ImageDestroy(&g_rtx.frames[i].specular); From 61ecda02f2cf9e559185936d0a405df8851b004b Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 27 Jan 2022 20:04:18 -0800 Subject: [PATCH 126/548] rt: add output material channel to primary ray perf (--//--) - total ray time: 14.5ms - primary: 0.2ms v:81/96 s:60/128 o:10/16 (-6v +1o) - light poly: 13.4ms v:256/256 s:98/128 o:4/16 (-28v +1o) - light point: 0.8ms v:87/96 s:68/128 o:10/16 (-6v +1o) --- ref_vk/shaders/denoiser.comp | 4 +-- ref_vk/shaders/ray_interop.h | 36 ++++++++++++++------------ ref_vk/shaders/ray_light_direct.glsl | 10 ++++--- ref_vk/shaders/ray_primary.rchit | 3 ++- ref_vk/shaders/ray_primary.rgen | 9 ++++--- ref_vk/shaders/ray_primary_common.glsl | 1 + 6 files changed, 35 insertions(+), 28 deletions(-) diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 85953359..3aaffc79 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -98,9 +98,9 @@ void main() { #if 1 vec3 colour = vec3(0.); colour += imageLoad(src_light_direct_poly_diffuse, pix).rgb; - //colour += imageLoad(src_light_direct_poly_specular, pix).rgb; + colour += imageLoad(src_light_direct_poly_specular, pix).rgb; colour += imageLoad(src_light_direct_point_diffuse, pix).rgb; - //colour += imageLoad(src_light_direct_point_specular, pix).rgb; + colour += imageLoad(src_light_direct_point_specular, pix).rgb; colour *= base_color.rgb; #else float total_scale = 0.; diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index 9f325df1..f7a5e27b 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -2,23 +2,6 @@ #ifndef RAY_INTEROP_H_INCLUDED #define RAY_INTEROP_H_INCLUDED -#define RAY_LIGHT_DIRECT_INPUTS(X) \ - X(10, position_t, rgba32f) \ - X(11, normals_gs, rgba16f) \ - -#define RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) \ - X(13, light_poly_diffuse, rgba16f) \ - X(14, light_poly_specular, rgba16f) \ - -#define RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) \ - X(13, light_point_diffuse, rgba16f) \ - X(14, light_point_specular, rgba16f) \ - -#define RAY_PRIMARY_OUTPUTS(X) \ - X(10, base_color_a, rgba8) \ - X(11, position_t, rgba32f) \ - X(12, normals_gs, rgba16f) \ - #define LIST_SPECIALIZATION_CONSTANTS(X) \ X(0, uint, MAX_POINT_LIGHTS, 256) \ X(1, uint, MAX_EMISSIVE_KUSOCHKI, 256) \ @@ -29,6 +12,25 @@ X(6, uint, MAX_TEXTURES, 4096) \ X(7, uint, SBT_RECORD_SIZE, 32) \ +#define RAY_PRIMARY_OUTPUTS(X) \ + X(10, base_color_a, rgba8) \ + X(11, position_t, rgba32f) \ + X(12, normals_gs, rgba16f) \ + X(13, material_rmxx, rgba8) \ + +#define RAY_LIGHT_DIRECT_INPUTS(X) \ + X(10, position_t, rgba32f) \ + X(11, normals_gs, rgba16f) \ + X(12, material_rmxx, rgba8) \ + +#define RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) \ + X(20, light_poly_diffuse, rgba16f) \ + X(21, light_poly_specular, rgba16f) \ + +#define RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) \ + X(20, light_point_diffuse, rgba16f) \ + X(21, light_point_specular, rgba16f) \ + #ifndef GLSL #include "xash3d_types.h" #define MAX_EMISSIVE_KUSOCHKI 256 diff --git a/ref_vk/shaders/ray_light_direct.glsl b/ref_vk/shaders/ray_light_direct.glsl index ca87beaf..59ac7e9b 100644 --- a/ref_vk/shaders/ray_light_direct.glsl +++ b/ref_vk/shaders/ray_light_direct.glsl @@ -46,14 +46,16 @@ void main() { rand01_state = ubo.random_seed + gl_LaunchIDEXT.x * 1833 + gl_LaunchIDEXT.y * 31337; // FIXME incorrect for reflection/refraction - vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); - vec3 direction = normalize((ubo.inv_view * vec4(target.xyz, 0)).xyz); + const vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); + const vec3 direction = normalize((ubo.inv_view * vec4(target.xyz, 0)).xyz); + + const vec4 material_data = imageLoad(material_rmxx, pix); MaterialProperties material; material.baseColor = vec3(1.); material.emissive = vec3(0.f); - material.metalness = 0.f; // TODO - material.roughness = 1.f; // TODO + material.metalness = material_data.g; + material.roughness = material_data.r; const vec3 pos = imageLoad(position_t, pix).xyz; diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index ccb67030..292011a1 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -33,6 +33,8 @@ void main() { payload.base_color_a = vec4(1.,0.,1.,1.); } else { payload.base_color_a = sampleTexture(tex_base_color, geom.uv, geom.uv_lods) * kusok.color; + payload.material_rmxx.r = (kusok.tex_roughness > 0) ? sampleTexture(kusok.tex_roughness, geom.uv, geom.uv_lods).r : kusok.roughness; + payload.material_rmxx.g = (kusok.tex_metalness > 0) ? sampleTexture(kusok.tex_metalness, geom.uv, geom.uv_lods).r : kusok.metalness; const uint tex_normal = kusok.tex_normalmap; vec3 T = geom.tangent; @@ -45,7 +47,6 @@ void main() { } } - payload.normals_gs.xy = normalEncode(geom.normal_geometry); payload.normals_gs.zw = normalEncode(geom.normal_shading); } diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index 816ff964..2ae51b47 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -13,13 +13,13 @@ layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; layout(location = PAYLOAD_LOCATION_PRIMARY) rayPayloadEXT RayPayloadPrimary payload; void main() { - vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.; + const vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.; // FIXME start on a near plane - vec3 origin = (ubo.inv_view * vec4(0, 0, 0, 1)).xyz; - vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); + const vec3 origin = (ubo.inv_view * vec4(0, 0, 0, 1)).xyz; + const vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); //vec3 direction = (ubo.inv_view * vec4(normalize(target.xyz), 0)).xyz; - vec3 direction = normalize((ubo.inv_view * vec4(target.xyz, 0)).xyz); + const vec3 direction = normalize((ubo.inv_view * vec4(target.xyz, 0)).xyz); const uint flags = gl_RayFlagsCullFrontFacingTrianglesEXT; const uint sbt_offset = 0; @@ -33,4 +33,5 @@ void main() { imageStore(out_image_position_t, ivec2(gl_LaunchIDEXT.xy), payload.hit_t); imageStore(out_image_base_color_a, ivec2(gl_LaunchIDEXT.xy), payload.base_color_a); imageStore(out_image_normals_gs, ivec2(gl_LaunchIDEXT.xy), payload.normals_gs); + imageStore(out_image_material_rmxx, ivec2(gl_LaunchIDEXT.xy), payload.material_rmxx); } diff --git a/ref_vk/shaders/ray_primary_common.glsl b/ref_vk/shaders/ray_primary_common.glsl index 79ca3d24..44623369 100644 --- a/ref_vk/shaders/ray_primary_common.glsl +++ b/ref_vk/shaders/ray_primary_common.glsl @@ -8,6 +8,7 @@ struct RayPayloadPrimary { vec4 hit_t; vec4 base_color_a; vec4 normals_gs; + vec4 material_rmxx; }; #define PAYLOAD_LOCATION_PRIMARY 0 From 3a55b9cd67e2ab285f777073371c37df54c6350a Mon Sep 17 00:00:00 2001 From: Bien Pham Date: Sat, 29 Jan 2022 04:03:00 +0700 Subject: [PATCH 127/548] engine: make pfnDrawString & pfnDrawStringReverse return width --- engine/client/cl_game.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index 1a898a91..27d5c462 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -2929,15 +2929,16 @@ pfnDrawString */ static int GAME_EXPORT pfnDrawString( int x, int y, const char *str, int r, int g, int b ) { + int iWidth = 0; Con_UtfProcessChar(0); // draw the string until we hit the null character or a newline character for ( ; *str != 0 && *str != '\n'; str++ ) { - x += pfnVGUI2DrawCharacterAdditive( x, y, (unsigned char)*str, r, g, b, 0 ); + iWidth += pfnVGUI2DrawCharacterAdditive( x + iWidth, y, (unsigned char)*str, r, g, b, 0 ); } - return x; + return iWidth; } /* @@ -2952,8 +2953,7 @@ static int GAME_EXPORT pfnDrawStringReverse( int x, int y, const char *str, int char *szIt; for( szIt = (char*)str; *szIt != 0; szIt++ ) x -= clgame.scrInfo.charWidths[ (unsigned char) *szIt ]; - pfnDrawString( x, y, str, r, g, b ); - return x; + return pfnDrawString( x, y, str, r, g, b ); } /* From 75e8a5105832b34b4ddc37c5fdf4c382d1c8864b Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 29 Jan 2022 02:32:46 +0300 Subject: [PATCH 128/548] ref_gl: disable VBO. Rename r_vbo to gl_vbo to ignore existing config values as it was enabled by default --- ref_gl/gl_opengl.c | 8 ++------ ref_gl/gl_rsurf.c | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/ref_gl/gl_opengl.c b/ref_gl/gl_opengl.c index 885ea5b3..bdf11d5f 100644 --- a/ref_gl/gl_opengl.c +++ b/ref_gl/gl_opengl.c @@ -901,12 +901,8 @@ static void R_CheckVBO( void ) def = "0"; } - r_vbo = gEngfuncs.Cvar_Get( "r_vbo", def, flags, "draw world using VBO" ); - r_vbo_dlightmode = gEngfuncs.Cvar_Get( "r_vbo_dlightmode", dlightmode, FCVAR_ARCHIVE, "vbo dlight rendering mode(0-1)" ); - - // check if enabled manually - if( CVAR_TO_BOOL(r_vbo) ) - r_vbo->flags |= FCVAR_ARCHIVE; + r_vbo = gEngfuncs.Cvar_Get( "gl_vbo", def, flags, "draw world using VBO (known to be glitchy)" ); + r_vbo_dlightmode = gEngfuncs.Cvar_Get( "gl_vbo_dlightmode", dlightmode, FCVAR_ARCHIVE, "vbo dlight rendering mode(0-1)" ); } /* diff --git a/ref_gl/gl_rsurf.c b/ref_gl/gl_rsurf.c index d9f3d4ae..caabf91f 100644 --- a/ref_gl/gl_rsurf.c +++ b/ref_gl/gl_rsurf.c @@ -1794,7 +1794,7 @@ void R_GenerateVBO( void ) // we do not want to write vbo code that does not use multitexture if( !GL_Support( GL_ARB_VERTEX_BUFFER_OBJECT_EXT ) || !GL_Support( GL_ARB_MULTITEXTURE ) || glConfig.max_texture_units < 2 ) { - gEngfuncs.Cvar_FullSet( "r_vbo", "0", FCVAR_READ_ONLY ); + gEngfuncs.Cvar_FullSet( "gl_vbo", "0", FCVAR_READ_ONLY ); return; } From 61643585e0ce0311ef8220b65224072e440e3647 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 29 Jan 2022 03:04:00 +0300 Subject: [PATCH 129/548] engine: fix NULL ptr dereference when log file cannot be opened --- engine/common/sys_con.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c index 1f29cb83..6ca3c7b9 100644 --- a/engine/common/sys_con.c +++ b/engine/common/sys_con.c @@ -113,11 +113,16 @@ void Sys_InitLog( void ) if( s_ld.log_active ) { s_ld.logfile = fopen( s_ld.log_path, mode ); - if( !s_ld.logfile ) Con_Reportf( S_ERROR "Sys_InitLog: can't create log file %s\n", s_ld.log_path ); - - fprintf( s_ld.logfile, "=================================================================================\n" ); - fprintf( s_ld.logfile, "\t%s (build %i) started at %s\n", s_ld.title, Q_buildnum(), Q_timestamp( TIME_FULL )); - fprintf( s_ld.logfile, "=================================================================================\n" ); + if( !s_ld.logfile ) + { + Con_Reportf( S_ERROR "Sys_InitLog: can't create log file %s\n", s_ld.log_path ); + } + else + { + fprintf( s_ld.logfile, "=================================================================================\n" ); + fprintf( s_ld.logfile, "\t%s (build %i) started at %s\n", s_ld.title, Q_buildnum(), Q_timestamp( TIME_FULL )); + fprintf( s_ld.logfile, "=================================================================================\n" ); + } } } From a6b43fbfffcf9e8ce72a920c2bc41ba20c3e6cfd Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 29 Jan 2022 03:04:54 +0300 Subject: [PATCH 130/548] engine: show additional errno string when log file cannot be opened --- engine/common/sys_con.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c index 6ca3c7b9..01af58fa 100644 --- a/engine/common/sys_con.c +++ b/engine/common/sys_con.c @@ -17,6 +17,8 @@ GNU General Public License for more details. #if XASH_ANDROID #include #endif +#include +#include #if !XASH_WIN32 && !XASH_MOBILE_PLATFORM #define XASH_COLORIZE_CONSOLE @@ -115,7 +117,7 @@ void Sys_InitLog( void ) s_ld.logfile = fopen( s_ld.log_path, mode ); if( !s_ld.logfile ) { - Con_Reportf( S_ERROR "Sys_InitLog: can't create log file %s\n", s_ld.log_path ); + Con_Reportf( S_ERROR "Sys_InitLog: can't create log file %s: %s\n", s_ld.log_path, strerror( errno ) ); } else { From 6c6e6e191d35ddea04ea39656fdf49a4c4c91e83 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 28 Jan 2022 22:58:07 -0800 Subject: [PATCH 131/548] rt: make array of resources this moves one step closer to fully dynamic resources and automatic barriers. next steps would be: - passes declaring their bindings at creation time - resources automatically preparing values based on that declaration - automatic barriers - single descriptor pool stored at resources (aware of frames in flight, etc) - parsing shaders to generate binding declaration --- ref_vk/vk_denoiser.c | 91 +++++++++------------------------ ref_vk/vk_ray_light_direct.c | 97 +++++++++++------------------------- ref_vk/vk_ray_primary.c | 66 +++++++++--------------- ref_vk/vk_ray_resources.h | 52 +++++++++++++------ ref_vk/vk_rtx.c | 63 +++++++++++------------ 5 files changed, 141 insertions(+), 228 deletions(-) diff --git a/ref_vk/vk_denoiser.c b/ref_vk/vk_denoiser.c index 4d094923..f8578d33 100644 --- a/ref_vk/vk_denoiser.c +++ b/ref_vk/vk_denoiser.c @@ -2,24 +2,20 @@ #include "vk_descriptor.h" #include "vk_pipeline.h" +#include "vk_ray_resources.h" -#include "eiface.h" // ARRAYSIZE +#define LIST_BINDINGS(X) \ + X(0, denoised) \ + X(1, base_color_a) \ + X(2, light_poly_diffuse) \ + X(3, light_poly_specular) \ + X(4, light_point_diffuse) \ + X(5, light_point_specular) \ enum { - DenoiserBinding_DestImage = 0, - - DenoiserBinding_Source_BaseColor = 1, - - DenoiserBinding_Source_LightPolyDiffuse = 2, - DenoiserBinding_Source_LightPolySpecular = 3, - DenoiserBinding_Source_LightPointDiffuse = 4, - DenoiserBinding_Source_LightPointSpecular = 5, - - /* DenoiserBinding_Source_Specular = 3, */ - /* DenoiserBinding_Source_Additive = 4, */ - /* DenoiserBinding_Source_Normals = 5, */ - /* */ - /* DenoiserBinding_Source_PositionT = 6, */ +#define X(index, name) DenoiserBinding_##name, + LIST_BINDINGS(X) +#undef X DenoiserBinding_COUNT }; @@ -36,7 +32,7 @@ static struct { static void createLayouts( void ) { g_denoiser.descriptors.bindings = g_denoiser.desc_bindings; - g_denoiser.descriptors.num_bindings = ARRAYSIZE(g_denoiser.desc_bindings); + g_denoiser.descriptors.num_bindings = COUNTOF(g_denoiser.desc_bindings); g_denoiser.descriptors.values = g_denoiser.desc_values; g_denoiser.descriptors.num_sets = 1; g_denoiser.descriptors.desc_sets = g_denoiser.desc_sets; @@ -46,44 +42,15 @@ static void createLayouts( void ) { .stageFlags = 0, }; - g_denoiser.desc_bindings[DenoiserBinding_DestImage] = (VkDescriptorSetLayoutBinding){ - .binding = DenoiserBinding_DestImage, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }; - - g_denoiser.desc_bindings[DenoiserBinding_Source_BaseColor] = (VkDescriptorSetLayoutBinding){ - .binding = DenoiserBinding_Source_BaseColor, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }; - - g_denoiser.desc_bindings[DenoiserBinding_Source_LightPolyDiffuse] = (VkDescriptorSetLayoutBinding){ - .binding = DenoiserBinding_Source_LightPolyDiffuse, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }; - g_denoiser.desc_bindings[DenoiserBinding_Source_LightPolySpecular] = (VkDescriptorSetLayoutBinding){ - .binding = DenoiserBinding_Source_LightPolySpecular, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }; - g_denoiser.desc_bindings[DenoiserBinding_Source_LightPointDiffuse] = (VkDescriptorSetLayoutBinding){ - .binding = DenoiserBinding_Source_LightPointDiffuse, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }; - g_denoiser.desc_bindings[DenoiserBinding_Source_LightPointSpecular] = (VkDescriptorSetLayoutBinding){ - .binding = DenoiserBinding_Source_LightPointSpecular, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, +#define BIND_IMAGE(index, name) \ + g_denoiser.desc_bindings[DenoiserBinding_##name] = (VkDescriptorSetLayoutBinding){ \ + .binding = index, \ + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, \ + .descriptorCount = 1, \ + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, \ }; +LIST_BINDINGS(BIND_IMAGE) +#undef BIND_IMAGE VK_DescriptorsCreate(&g_denoiser.descriptors); } @@ -127,20 +94,10 @@ void XVK_DenoiserDenoise( VkCommandBuffer cmdbuf, const vk_ray_resources_t* res const uint32_t WG_W = 8; const uint32_t WG_H = 8; -#define BIND_IMAGE(index_name, name) \ - g_denoiser.desc_values[DenoiserBinding_##index_name].image = (VkDescriptorImageInfo){ \ - .sampler = VK_NULL_HANDLE, \ - .imageView = res->name, \ - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ - } - - BIND_IMAGE(DestImage, denoised); - BIND_IMAGE(Source_BaseColor, base_color_a); - - BIND_IMAGE(Source_LightPolyDiffuse, light_poly_diffuse); - BIND_IMAGE(Source_LightPolySpecular, light_poly_specular); - BIND_IMAGE(Source_LightPointDiffuse, light_point_diffuse); - BIND_IMAGE(Source_LightPointSpecular, light_point_specular); +#define COPY_VALUE(index, name) \ + g_denoiser.desc_values[DenoiserBinding_##name] = res->values[RayResource_##name]; + LIST_BINDINGS(COPY_VALUE) +#undef COPY_VALUE VK_DescriptorsWrite(&g_denoiser.descriptors); diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 4c4f2a79..169e2354 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -5,16 +5,16 @@ enum { // TODO set 0 ? - RtLDir_Desc_TLAS, - RtLDir_Desc_UBO, - RtLDir_Desc_Kusochki, - RtLDir_Desc_Indices, - RtLDir_Desc_Vertices, - RtLDir_Desc_Textures, + RtLDir_Desc_tlas, + RtLDir_Desc_ubo, + RtLDir_Desc_kusochki, + RtLDir_Desc_indices, + RtLDir_Desc_vertices, + RtLDir_Desc_all_textures, // TODO set 1 - RtLDir_Desc_Lights, - RtLDir_Desc_LightClusters, + RtLDir_Desc_lights, + RtLDir_Desc_light_clusters, // TODO set 1 #define X(index, name, ...) RtLDir_Desc_##name, @@ -41,20 +41,17 @@ static void initDescriptors( void ) { .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, \ } - INIT_BINDING(1, TLAS, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1); - INIT_BINDING(2, UBO, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1); - INIT_BINDING(3, Kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); - INIT_BINDING(4, Indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); - INIT_BINDING(5, Vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); - INIT_BINDING(6, Textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES); - INIT_BINDING(7, Lights, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1); - INIT_BINDING(8, LightClusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); + INIT_BINDING(1, tlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1); + INIT_BINDING(2, ubo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1); + INIT_BINDING(3, kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); + INIT_BINDING(4, indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); + INIT_BINDING(5, vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); + INIT_BINDING(6, all_textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES); + INIT_BINDING(7, lights, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1); + INIT_BINDING(8, light_clusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); -//#define X(index, name, ...) INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1); #define X(index, name, ...) INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1); RAY_LIGHT_DIRECT_INPUTS(X) -#undef X -#define X(index, name, ...) INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1); RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) #undef X #undef INIT_BINDING @@ -62,46 +59,18 @@ static void initDescriptors( void ) { static void writeValues( vk_descriptor_value_t *values, const vk_ray_resources_t* res ) { #define X(index, name, ...) \ - values[RtLDir_Desc_##name].image = (VkDescriptorImageInfo){ \ - .sampler = VK_NULL_HANDLE, \ - .imageView = res->name, \ - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ - }; + values[RtLDir_Desc_##name] = res->values[RayResource_##name]; RAY_LIGHT_DIRECT_INPUTS(X) + RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) + X(-1, tlas) + X(-1, ubo); + X(-1, kusochki); + X(-1, indices); + X(-1, vertices); + X(-1, all_textures); + X(-1, lights); + X(-1, light_clusters); #undef X - -#define X(index, name, ...) \ - values[RtLDir_Desc_##name].image = (VkDescriptorImageInfo){ \ - .sampler = VK_NULL_HANDLE, \ - .imageView = res->name, \ - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ - }; - RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) -#undef X - - values[RtLDir_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, - .accelerationStructureCount = 1, - .pAccelerationStructures = &res->scene.tlas, - }; - -#define DESC_SET_BUFFER(index, buffer_) \ - values[index].buffer = (VkDescriptorBufferInfo){ \ - .buffer = res->scene.buffer_.buffer, \ - .offset = res->scene.buffer_.offset, \ - .range = res->scene.buffer_.size, \ - } - - DESC_SET_BUFFER(RtLDir_Desc_UBO, ubo); - DESC_SET_BUFFER(RtLDir_Desc_Kusochki, kusochki); - DESC_SET_BUFFER(RtLDir_Desc_Indices, indices); - DESC_SET_BUFFER(RtLDir_Desc_Vertices, vertices); - DESC_SET_BUFFER(RtLDir_Desc_Lights, lights); - DESC_SET_BUFFER(RtLDir_Desc_LightClusters, light_clusters); - -#undef DESC_SET_BUFFER - - values[RtLDir_Desc_Textures].image_array = res->scene.all_textures; } struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { @@ -155,18 +124,8 @@ struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { static void writePointValues( vk_descriptor_value_t *values, const vk_ray_resources_t* res ) { writeValues( values, res ); - - values[RtLDir_Desc_light_poly_diffuse].image = (VkDescriptorImageInfo) { - .sampler = VK_NULL_HANDLE, - .imageView = res->light_point_diffuse, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; - - values[RtLDir_Desc_light_poly_specular].image = (VkDescriptorImageInfo) { - .sampler = VK_NULL_HANDLE, - .imageView = res->light_point_specular, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; + values[RtLDir_Desc_light_poly_diffuse] = res->values[RayResource_light_point_diffuse]; + values[RtLDir_Desc_light_poly_specular] = res->values[RayResource_light_point_specular]; } struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ) { diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index e46a3aac..798fa2d2 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -5,15 +5,15 @@ enum { // TODO set 0 - RtPrim_Desc_TLAS, - RtPrim_Desc_UBO, - RtPrim_Desc_Kusochki, - RtPrim_Desc_Indices, - RtPrim_Desc_Vertices, - RtPrim_Desc_Textures, + RtPrim_Desc_tlas, + RtPrim_Desc_ubo, + RtPrim_Desc_kusochki, + RtPrim_Desc_indices, + RtPrim_Desc_vertices, + RtPrim_Desc_all_textures, // TODO set 1 -#define X(index, name, ...) RtPrim_Desc_Out_##name, +#define X(index, name, ...) RtPrim_Desc_##name, RAY_PRIMARY_OUTPUTS(X) #undef X @@ -31,51 +31,31 @@ static void initDescriptors( void ) { .stageFlags = stages, \ } - INIT_BINDING(1, TLAS, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); - INIT_BINDING(2, UBO, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - INIT_BINDING(3, Kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - INIT_BINDING(4, Indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - INIT_BINDING(5, Vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - INIT_BINDING(6, Textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); + INIT_BINDING(1, tlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); + INIT_BINDING(2, ubo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); + INIT_BINDING(3, kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); + INIT_BINDING(4, indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); + INIT_BINDING(5, vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); + INIT_BINDING(6, all_textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); #define X(index, name, ...) \ - INIT_BINDING(index, Out_##name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); -RAY_PRIMARY_OUTPUTS(X) + INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); + RAY_PRIMARY_OUTPUTS(X) #undef X #undef INIT_BINDING } static void writeValues( vk_descriptor_value_t *values, const vk_ray_resources_t* res ) { #define X(index, name, ...) \ - values[RtPrim_Desc_Out_##name].image = (VkDescriptorImageInfo){ \ - .sampler = VK_NULL_HANDLE, \ - .imageView = res->name, \ - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ - }; -RAY_PRIMARY_OUTPUTS(X) + values[RtPrim_Desc_##name] = res->values[RayResource_##name]; + RAY_PRIMARY_OUTPUTS(X) + X(-1, tlas) + X(-1, ubo); + X(-1, kusochki); + X(-1, indices); + X(-1, vertices); + X(-1, all_textures); #undef X - - values[RtPrim_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, - .accelerationStructureCount = 1, - .pAccelerationStructures = &res->scene.tlas, - }; - -#define DESC_SET_BUFFER(index, buffer_) \ - values[index].buffer = (VkDescriptorBufferInfo){ \ - .buffer = res->scene.buffer_.buffer, \ - .offset = res->scene.buffer_.offset, \ - .range = res->scene.buffer_.size, \ - } - - DESC_SET_BUFFER(RtPrim_Desc_UBO, ubo); - DESC_SET_BUFFER(RtPrim_Desc_Kusochki, kusochki); - DESC_SET_BUFFER(RtPrim_Desc_Indices, indices); - DESC_SET_BUFFER(RtPrim_Desc_Vertices, vertices); - -#undef DESC_SET_BUFFER - - values[RtPrim_Desc_Textures].image_array = res->scene.all_textures; } struct ray_pass_s *R_VkRayPrimaryPassCreate( void ) { diff --git a/ref_vk/vk_ray_resources.h b/ref_vk/vk_ray_resources.h index 621fe921..065e8dfd 100644 --- a/ref_vk/vk_ray_resources.h +++ b/ref_vk/vk_ray_resources.h @@ -2,28 +2,48 @@ #include "vk_rtx.h" #include "vk_const.h" +#include "vk_descriptor.h" + #include "shaders/ray_interop.h" -typedef struct vk_ray_resources_s { - uint32_t width, height; +#define RAY_SCENE_RESOURCES(X) \ + X(TLAS, tlas) \ + X(Buffer, ubo) \ + X(Buffer, kusochki) \ + X(Buffer, indices) \ + X(Buffer, vertices) \ + X(Buffer, lights) \ + X(Buffer, light_clusters) \ + X(Texture, all_textures) \ - struct { - VkAccelerationStructureKHR tlas; - - vk_buffer_region_t ubo; - vk_buffer_region_t kusochki, indices, vertices; - VkDescriptorImageInfo *all_textures; // [MAX_TEXTURES] - - vk_buffer_region_t lights; - vk_buffer_region_t light_clusters; - } scene; - -#define X(index, name, ...) VkImageView name; +enum { +#define X(type, name, ...) RayResource_##name, + RAY_SCENE_RESOURCES(X) RAY_PRIMARY_OUTPUTS(X) RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) X(-1, denoised) #undef X + RayResource__COUNT +}; + + /* TODO +typedef struct { + struct { + VkAccessFlags access_mask; + VkImageLayout image_layout; + VkPipelineStageFlagBits pipelines; + } state; + + union { + VkAccelerationStructureKHR tlas; + vk_buffer_region_t buffer; + VkImageView image; + } value; +} ray_resource_t; +*/ + +typedef struct vk_ray_resources_s { + uint32_t width, height; + vk_descriptor_value_t values[RayResource__COUNT]; } vk_ray_resources_t; - - diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index c396be6b..2398da1c 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -979,46 +979,43 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar const vk_ray_resources_t res = { .width = FRAME_WIDTH, .height = FRAME_HEIGHT, - .scene = { - .tlas = g_rtx.tlas, - .ubo = { - .buffer = g_rtx.uniform_buffer.buffer, - .offset = frame_index * g_rtx.uniform_unit_size, - .size = sizeof(struct UniformBuffer), - }, - .kusochki = { - .buffer = g_ray_model_state.kusochki_buffer.buffer, - .offset = 0, - .size = g_ray_model_state.kusochki_buffer.size, - }, - .indices = { - .buffer = args->geometry_data.buffer, - .offset = 0, - .size = args->geometry_data.size, - }, - .vertices = { - .buffer = args->geometry_data.buffer, - .offset = 0, - .size = args->geometry_data.size, - }, - .all_textures = tglob.dii_all_textures, - .lights = { - .buffer = g_ray_model_state.lights_buffer.buffer, - .offset = 0, - .size = VK_WHOLE_SIZE, // TODO multiple frames - }, - .light_clusters = { - .buffer = g_rtx.light_grid_buffer.buffer, - .offset = 0, - .size = VK_WHOLE_SIZE, // TODO multiple frames + .values = { + [RayResource_tlas] = { + .accel = (VkWriteDescriptorSetAccelerationStructureKHR){ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, + .accelerationStructureCount = 1, + .pAccelerationStructures = &g_rtx.tlas, + .pNext = NULL, + }, }, +#define RES_SET_BUFFER(name, source_, offset_, size_) \ + [RayResource_##name] = (VkDescriptorBufferInfo) { \ + .buffer = source_.buffer, \ + .offset = (offset_), \ + .range = (size_), \ + } +#define RES_SET_BUFFER_FULL(name, source_) RES_SET_BUFFER(name, source_, 0, source_.size) + RES_SET_BUFFER(ubo, g_rtx.uniform_buffer, frame_index * g_rtx.uniform_unit_size, sizeof(struct UniformBuffer)), + RES_SET_BUFFER_FULL(kusochki, g_ray_model_state.kusochki_buffer), + RES_SET_BUFFER_FULL(indices, args->geometry_data), + RES_SET_BUFFER_FULL(vertices, args->geometry_data), + RES_SET_BUFFER_FULL(lights, g_ray_model_state.lights_buffer), + RES_SET_BUFFER_FULL(light_clusters, g_rtx.light_grid_buffer), +#undef RES_SET_BUFFER_FULL +#undef RES_SET_BUFFER + [RayResource_all_textures].image_array = tglob.dii_all_textures, +#define X(index, name, ...) \ + [RayResource_##name].image = (VkDescriptorImageInfo) { \ + .sampler = VK_NULL_HANDLE, \ + .imageView = current_frame->name.view, \ + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ }, -#define X(index, name, ...) .name = current_frame->name.view, RAY_PRIMARY_OUTPUTS(X) RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) X(-1, denoised) #undef X + }, }; From d8fd0b3dcb9ed65e14a72e95976ba64f20fc77ad Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Sun, 30 Jan 2022 02:10:18 +0400 Subject: [PATCH 132/548] documentation: updated mainteiners list for Windows engine port --- Documentation/ports.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Documentation/ports.md b/Documentation/ports.md index 27dc4fe5..28dd74c0 100644 --- a/Documentation/ports.md +++ b/Documentation/ports.md @@ -15,24 +15,24 @@ Status: Table is sorted by status. -| Platform | Status | Maintainer | Note -| -------- | ------ | ---------- | ---- -| Windows | Supported | @a1batross | -| *BSD | Supported | @nekonomicon | -| GNU/Linux(x86, amd64, arm) | Supported | @a1batross, @mittorn | -| Android | Supported | @a1batross, @mittorn | -| MotoMAGX | Supported | @a1batross | -| GNU/Linux(elbrus) | Supported | @a1batross, @mittorn | Rare and eventual access to e2k machine -| Haiku | Supported | not maintained | Was added by #478 and #483 -| DOS4GW | Supported | @mittorn | -| GNU/Linux(mipsel) | Supported | @mittorn | -| Switch | In progress | @fgsfdsfgs | [Github Repository](https://github.com/fgsfdsfgs/xash3d-fwgs/tree/switch_new) -| Wii | In progress | Collaborative effort | [Github Repository](https://github.com/saucesaft/xash3d-wii) -| PSP | In progress | Collaborative effort | No sources available at this moment -| Emscripten | Old Engine | not maintained | -| Oculus Quest | Old Engine fork | @DrBeef | [GitHub Repository](https://github.com/DrBeef/Lambda1VR) -| PSVita | Old Engine fork | not maintained | [GitHub Repository](https://github.com/fgsfdsfgs/vitaXash3D) -| Switch | Old Engine fork | not maintained | [GitHub Repository](https://github.com/switchports/xash3d-switch) -| 3DS | Old Engine fork | not maintained | [GitHub Repository](https://github.com/masterfeizz/Xash3DS) -| macOS | Deprecated | not maintained | See GitHub issue #61 -| iOS | Deprecated | not maintained | See GitHub issue #61 +| Platform | Status | Maintainer | Note +| -------- | ------ | ---------- | ---- +| Windows | Supported | @a1batross, @SNMetamorph | +| *BSD | Supported | @nekonomicon | +| GNU/Linux(x86, amd64, arm) | Supported | @a1batross, @mittorn | +| Android | Supported | @a1batross, @mittorn | +| MotoMAGX | Supported | @a1batross | +| GNU/Linux(elbrus) | Supported | @a1batross, @mittorn | Rare and eventual access to e2k machine +| Haiku | Supported | not maintained | Was added by #478 and #483 +| DOS4GW | Supported | @mittorn | +| GNU/Linux(mipsel) | Supported | @mittorn | +| Switch | In progress | @fgsfdsfgs | [Github Repository](https://github.com/fgsfdsfgs/xash3d-fwgs/tree/switch_new) +| Wii | In progress | Collaborative effort | [Github Repository](https://github.com/saucesaft/xash3d-wii) +| PSP | In progress | Collaborative effort | No sources available at this moment +| Emscripten | Old Engine | not maintained | +| Oculus Quest | Old Engine fork | @DrBeef | [GitHub Repository](https://github.com/DrBeef/Lambda1VR) +| PSVita | Old Engine fork | not maintained | [GitHub Repository](https://github.com/fgsfdsfgs/vitaXash3D) +| Switch | Old Engine fork | not maintained | [GitHub Repository](https://github.com/switchports/xash3d-switch) +| 3DS | Old Engine fork | not maintained | [GitHub Repository](https://github.com/masterfeizz/Xash3DS) +| macOS | Deprecated | not maintained | See GitHub issue #61 +| iOS | Deprecated | not maintained | See GitHub issue #61 From 950b1ff6cf8d7822a4497a9192425ab2db5ac3d7 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 30 Jan 2022 20:49:37 -0800 Subject: [PATCH 133/548] rt: tie pass binding to resource semantic this allows removing pass write function and moves us closer to auto-solving pass dependencies --- ref_vk/ray_pass.c | 19 +++++-- ref_vk/ray_pass.h | 9 +--- ref_vk/vk_descriptor.h | 2 +- ref_vk/vk_ray_light_direct.c | 97 ++++++++++++++---------------------- ref_vk/vk_ray_primary.c | 54 ++++++++------------ 5 files changed, 76 insertions(+), 105 deletions(-) diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c index 6a67a4ed..1b4a11d8 100644 --- a/ref_vk/ray_pass.c +++ b/ref_vk/ray_pass.c @@ -12,8 +12,8 @@ typedef struct ray_pass_s { struct { vk_descriptors_t riptors; - ray_pass_write_values_f write_values_func; VkDescriptorSet sets[1]; + int *binding_semantics; } desc; union { @@ -150,8 +150,12 @@ static ray_pass_t *createRayPass( const ray_pass_create_t *create ) { } pass->desc.riptors.values = Mem_Malloc(vk_core.pool, sizeof(pass->desc.riptors.values[0]) * create->layout.bindings_count); - pass->desc.write_values_func = create->layout.write_values_func; - ASSERT(create->layout.write_values_func); + + { + const size_t semantics_size = sizeof(int) * create->layout.bindings_count; + pass->desc.binding_semantics = Mem_Malloc(vk_core.pool, semantics_size); + memcpy(pass->desc.binding_semantics, create->layout.bindings_semantics, semantics_size); + } return pass; } @@ -169,7 +173,14 @@ void RayPassDestroy( struct ray_pass_s *pass ) { void RayPassPerform( VkCommandBuffer cmdbuf, struct ray_pass_s *pass, const struct vk_ray_resources_s *res) { - pass->desc.write_values_func( pass->desc.riptors.values, res ); + for (int i = 0; i < pass->desc.riptors.num_bindings; ++i) { + const int res_index = pass->desc.binding_semantics[i]; + // TODO check early + ASSERT(res_index >= 0); + ASSERT(res_index < RayResource__COUNT); + pass->desc.riptors.values[i] = res->values[res_index]; + } + VK_DescriptorsWrite(&pass->desc.riptors); vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pass->tracing.pipeline); diff --git a/ref_vk/ray_pass.h b/ref_vk/ray_pass.h index e1baba4f..69a7108e 100644 --- a/ref_vk/ray_pass.h +++ b/ref_vk/ray_pass.h @@ -33,17 +33,12 @@ typedef struct { // - parse the entire layout from shaders // - expose it as a struct[] interface of the pass // - resource/interface should prepare descriptors outside of pass code and just pass them to pass -struct vk_ray_resources_s; -typedef void (*ray_pass_write_values_f)( vk_descriptor_value_t *values, const struct vk_ray_resources_s *resources ); - -// TODO parse this out from shaders typedef struct { - VkDescriptorSetLayoutBinding *bindings; + const int *bindings_semantics; // RayResource_something + const VkDescriptorSetLayoutBinding *bindings; int bindings_count; VkPushConstantRange push_constants; - - ray_pass_write_values_f write_values_func; } ray_pass_layout_t; typedef struct { diff --git a/ref_vk/vk_descriptor.h b/ref_vk/vk_descriptor.h index cc74c647..85c40110 100644 --- a/ref_vk/vk_descriptor.h +++ b/ref_vk/vk_descriptor.h @@ -34,7 +34,7 @@ typedef union { typedef struct { int num_bindings; - VkDescriptorSetLayoutBinding *bindings; + const VkDescriptorSetLayoutBinding *bindings; // Used in Write only vk_descriptor_value_t *values; diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 169e2354..11018380 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -3,53 +3,54 @@ #include "vk_ray_resources.h" #include "ray_pass.h" +#define LIST_SCENE_BINDINGS(X) \ + X(1, tlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1) \ + X(2, ubo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1) \ + X(3, kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ + X(4, indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ + X(5, vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ + X(6, all_textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES) \ + X(7, lights, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1) \ + X(8, light_clusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ + +#define LIST_COMMON_BINDINGS(X) \ + LIST_SCENE_BINDINGS(X) \ + RAY_LIGHT_DIRECT_INPUTS(X) + enum { - // TODO set 0 ? - RtLDir_Desc_tlas, - RtLDir_Desc_ubo, - RtLDir_Desc_kusochki, - RtLDir_Desc_indices, - RtLDir_Desc_vertices, - RtLDir_Desc_all_textures, - - // TODO set 1 - RtLDir_Desc_lights, - RtLDir_Desc_light_clusters, - - // TODO set 1 -#define X(index, name, ...) RtLDir_Desc_##name, - RAY_LIGHT_DIRECT_INPUTS(X) +#define X(index, name, ...) Binding_##name, + LIST_COMMON_BINDINGS(X) + // FIXME it's an artifact that point and poly outputs have same bindings indices + RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) #undef X - - // FIXME it's an artifact that point and poly outputs have same bindings -#define X(index, name, ...) RtLDir_Desc_##name, - RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) -#undef X - - RtLDir_Desc_COUNT + Binding__COUNT }; -static VkDescriptorSetLayoutBinding bindings[RtLDir_Desc_COUNT]; +static VkDescriptorSetLayoutBinding bindings[Binding__COUNT]; +static const int semantics_poly[Binding__COUNT] = { +#define X(index, name, ...) RayResource_##name, + LIST_COMMON_BINDINGS(X) + RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) +#undef X +}; + +static const int semantics_point[Binding__COUNT] = { +#define X(index, name, ...) RayResource_##name, + LIST_COMMON_BINDINGS(X) + RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) +#undef X +}; static void initDescriptors( void ) { // FIXME more conservative shader stages #define INIT_BINDING(index, name, type, count) \ - bindings[RtLDir_Desc_##name] = (VkDescriptorSetLayoutBinding){ \ + bindings[Binding_##name] = (VkDescriptorSetLayoutBinding){ \ .binding = index, \ .descriptorType = type, \ .descriptorCount = count, \ .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, \ - } - - INIT_BINDING(1, tlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1); - INIT_BINDING(2, ubo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1); - INIT_BINDING(3, kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); - INIT_BINDING(4, indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); - INIT_BINDING(5, vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); - INIT_BINDING(6, all_textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES); - INIT_BINDING(7, lights, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1); - INIT_BINDING(8, light_clusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1); - + }; + LIST_SCENE_BINDINGS(INIT_BINDING) #define X(index, name, ...) INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1); RAY_LIGHT_DIRECT_INPUTS(X) RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) @@ -57,22 +58,6 @@ static void initDescriptors( void ) { #undef INIT_BINDING } -static void writeValues( vk_descriptor_value_t *values, const vk_ray_resources_t* res ) { -#define X(index, name, ...) \ - values[RtLDir_Desc_##name] = res->values[RayResource_##name]; - RAY_LIGHT_DIRECT_INPUTS(X) - RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) - X(-1, tlas) - X(-1, ubo); - X(-1, kusochki); - X(-1, indices); - X(-1, vertices); - X(-1, all_textures); - X(-1, lights); - X(-1, light_clusters); -#undef X -} - struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { // FIXME move this into vk_pipeline const struct SpecializationData { @@ -104,8 +89,8 @@ struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { .debug_name = "light direct poly", .layout = { .bindings = bindings, + .bindings_semantics = semantics_poly, .bindings_count = COUNTOF(bindings), - .write_values_func = writeValues, .push_constants = {0}, }, .tracing = { @@ -122,12 +107,6 @@ struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { return RayPassCreate( &rpc ); } -static void writePointValues( vk_descriptor_value_t *values, const vk_ray_resources_t* res ) { - writeValues( values, res ); - values[RtLDir_Desc_light_poly_diffuse] = res->values[RayResource_light_point_diffuse]; - values[RtLDir_Desc_light_poly_specular] = res->values[RayResource_light_point_specular]; -} - struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ) { // FIXME move this into vk_pipeline const struct SpecializationData { @@ -159,8 +138,8 @@ struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ) { .debug_name = "light direct point", .layout = { .bindings = bindings, + .bindings_semantics = semantics_point, .bindings_count = COUNTOF(bindings), - .write_values_func = writePointValues, .push_constants = {0}, }, .tracing = { diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index 798fa2d2..8e3b3361 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -3,24 +3,30 @@ #include "vk_ray_resources.h" #include "ray_pass.h" -enum { - // TODO set 0 - RtPrim_Desc_tlas, - RtPrim_Desc_ubo, - RtPrim_Desc_kusochki, - RtPrim_Desc_indices, - RtPrim_Desc_vertices, - RtPrim_Desc_all_textures, +#define LIST_COMMON_BINDINGS(X) \ + X(1, tlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR) \ + X(2, ubo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR) \ + X(3, kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR) \ + X(4, indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR) \ + X(5, vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR) \ + X(6, all_textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR) \ - // TODO set 1 +enum { #define X(index, name, ...) RtPrim_Desc_##name, -RAY_PRIMARY_OUTPUTS(X) + LIST_COMMON_BINDINGS(X) + RAY_PRIMARY_OUTPUTS(X) #undef X RtPrim_Desc_COUNT }; static VkDescriptorSetLayoutBinding bindings[RtPrim_Desc_COUNT]; +static const int semantics[RtPrim_Desc_COUNT] = { +#define X(index, name, ...) RayResource_##name, + LIST_COMMON_BINDINGS(X) + RAY_PRIMARY_OUTPUTS(X) +#undef X +}; static void initDescriptors( void ) { #define INIT_BINDING(index, name, type, count, stages) \ @@ -29,35 +35,15 @@ static void initDescriptors( void ) { .descriptorType = type, \ .descriptorCount = count, \ .stageFlags = stages, \ - } - - INIT_BINDING(1, tlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); - INIT_BINDING(2, ubo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - INIT_BINDING(3, kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - INIT_BINDING(4, indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - INIT_BINDING(5, vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - INIT_BINDING(6, all_textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - + }; + LIST_COMMON_BINDINGS(INIT_BINDING) #define X(index, name, ...) \ - INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); + INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); RAY_PRIMARY_OUTPUTS(X) #undef X #undef INIT_BINDING } -static void writeValues( vk_descriptor_value_t *values, const vk_ray_resources_t* res ) { -#define X(index, name, ...) \ - values[RtPrim_Desc_##name] = res->values[RayResource_##name]; - RAY_PRIMARY_OUTPUTS(X) - X(-1, tlas) - X(-1, ubo); - X(-1, kusochki); - X(-1, indices); - X(-1, vertices); - X(-1, all_textures); -#undef X -} - struct ray_pass_s *R_VkRayPrimaryPassCreate( void ) { // FIXME move this into vk_pipeline or something const struct SpecializationData { @@ -92,8 +78,8 @@ struct ray_pass_s *R_VkRayPrimaryPassCreate( void ) { .debug_name = "primary ray", .layout = { .bindings = bindings, + .bindings_semantics = semantics, .bindings_count = COUNTOF(bindings), - .write_values_func = writeValues, .push_constants = {0}, }, .tracing = { From c2d8ecbf791000db6b479c1b1a381f403dcb0a83 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 30 Jan 2022 22:24:55 -0800 Subject: [PATCH 134/548] rt: make denoiser to be just another pass that requires also supporting compute shaders as passes. --- ref_vk/ray_pass.c | 194 +++++++++++++++++++++++------------ ref_vk/ray_pass.h | 69 ++++++------- ref_vk/vk_core.c | 5 - ref_vk/vk_denoiser.c | 101 +++++------------- ref_vk/vk_denoiser.h | 11 +- ref_vk/vk_pipeline.c | 2 - ref_vk/vk_pipeline.h | 2 +- ref_vk/vk_ray_light_direct.c | 36 +++---- ref_vk/vk_ray_primary.c | 18 ++-- ref_vk/vk_rtx.c | 12 ++- 10 files changed, 222 insertions(+), 228 deletions(-) diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c index 1b4a11d8..f60bd3f3 100644 --- a/ref_vk/ray_pass.c +++ b/ref_vk/ray_pass.c @@ -1,67 +1,63 @@ #include "ray_pass.h" +#include "vk_pipeline.h" #include "vk_descriptor.h" #include "vk_ray_resources.h" -#include "vk_ray_resources.h" #define MAX_STAGES 16 #define MAX_MISS_GROUPS 8 #define MAX_HIT_GROUPS 8 +typedef enum { + RayPassType_Compute, + RayPassType_Tracing, +} ray_pass_type_t; + typedef struct ray_pass_s { - // TODO enum type + ray_pass_type_t type; + char debug_name[32]; struct { vk_descriptors_t riptors; VkDescriptorSet sets[1]; int *binding_semantics; } desc; - - union { - vk_pipeline_ray_t tracing; - }; } ray_pass_t; -#if 0 // TODO -qboolean createLayout( const ray_pass_layout_t *layout, ray_pass_t *pass ){ - // TODO return false on fail instead of crashing - { - const VkDescriptorSetLayoutCreateInfo dslci = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - .bindingCount = layout->bindings_count, - .pBindings = layout->bindings, - }; - XVK_CHECK(vkCreateDescriptorSetLayout(vk_core.device, &dslci, NULL, &pass->desc_set_layout)); - } +typedef struct { + ray_pass_t header; + vk_pipeline_ray_t pipeline; +} ray_pass_tracing_impl_t; - { - const VkPipelineLayoutCreateInfo plci = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, - .setLayoutCount = 1, - .pSetLayouts = &pass->desc_set_layout, - .pushConstantRangeCount = layout->push_constants.size > 0 ? 1 : 0, - .pPushConstantRanges = &layout->push_constants, - }; - XVK_CHECK(vkCreatePipelineLayout(vk_core.device, &plci, NULL, &pass->pipeline_layout)); - } +typedef struct { + ray_pass_t header; + VkPipeline pipeline; +} ray_pass_compute_impl_t; - return true; +static void initPassDescriptors( ray_pass_t *header, const ray_pass_layout_t *layout ) { + header->desc.riptors = (vk_descriptors_t) { + .bindings = layout->bindings, + .num_bindings = layout->bindings_count, + .num_sets = COUNTOF(header->desc.sets), + .desc_sets = header->desc.sets, + .push_constants = layout->push_constants, + }; + + VK_DescriptorsCreate(&header->desc.riptors); } -#endif -static ray_pass_t *createRayPass( const ray_pass_create_t *create ) { - ray_pass_t *pass = Mem_Malloc(vk_core.pool, sizeof(*pass)); +static void finalizePassDescriptors( ray_pass_t *header, const ray_pass_layout_t *layout ) { + const size_t semantics_size = sizeof(int) * layout->bindings_count; + header->desc.binding_semantics = Mem_Malloc(vk_core.pool, semantics_size); + memcpy(header->desc.binding_semantics, layout->bindings_semantics, semantics_size); - { - pass->desc.riptors = (vk_descriptors_t) { - .bindings = create->layout.bindings, - .num_bindings = create->layout.bindings_count, - .num_sets = COUNTOF(pass->desc.sets), - .desc_sets = pass->desc.sets, - .push_constants = create->layout.push_constants, - }; + header->desc.riptors.values = Mem_Malloc(vk_core.pool, sizeof(header->desc.riptors.values[0]) * layout->bindings_count); +} - VK_DescriptorsCreate(&pass->desc.riptors); - } +struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create ) { + ray_pass_tracing_impl_t *const pass = Mem_Malloc(vk_core.pool, sizeof(*pass)); + ray_pass_t *const header = &pass->header; + + initPassDescriptors(header, &create->layout); { int stage_index = 0; @@ -73,7 +69,7 @@ static ray_pass_t *createRayPass( const ray_pass_create_t *create ) { vk_pipeline_ray_create_info_t prci = { .debug_name = create->debug_name, - .layout = pass->desc.riptors.pipeline_layout, + .layout = header->desc.riptors.pipeline_layout, .stages = stages, .groups = { .hit = hits, @@ -82,13 +78,13 @@ static ray_pass_t *createRayPass( const ray_pass_create_t *create ) { }; stages[stage_index++] = (vk_shader_stage_t) { - .filename = create->tracing.raygen, + .filename = create->raygen, .stage = VK_SHADER_STAGE_RAYGEN_BIT_KHR, - .specialization_info = create->tracing.specialization, + .specialization_info = create->specialization, }; - for (int i = 0; i < create->tracing.miss_count; ++i) { - const ray_pass_shader_t *const shader = create->tracing.miss + i; + for (int i = 0; i < create->miss_count; ++i) { + const ray_pass_shader_t *const shader = create->miss + i; ASSERT(stage_index < MAX_STAGES); ASSERT(miss_index < MAX_MISS_GROUPS); @@ -99,12 +95,12 @@ static ray_pass_t *createRayPass( const ray_pass_create_t *create ) { stages[stage_index++] = (vk_shader_stage_t) { .filename = *shader, .stage = VK_SHADER_STAGE_MISS_BIT_KHR, - .specialization_info = create->tracing.specialization, + .specialization_info = create->specialization, }; } - for (int i = 0; i < create->tracing.hit_count; ++i) { - const ray_pass_hit_group_t *const group = create->tracing.hit + i; + for (int i = 0; i < create->hit_count; ++i) { + const ray_pass_hit_group_t *const group = create->hit + i; ASSERT(hit_index < MAX_HIT_GROUPS); @@ -115,7 +111,7 @@ static ray_pass_t *createRayPass( const ray_pass_create_t *create ) { stages[stage_index++] = (vk_shader_stage_t) { .filename = group->any, .stage = VK_SHADER_STAGE_ANY_HIT_BIT_KHR, - .specialization_info = create->tracing.specialization, + .specialization_info = create->specialization, }; } else { hits[hit_index].any = -1; @@ -127,7 +123,7 @@ static ray_pass_t *createRayPass( const ray_pass_create_t *create ) { stages[stage_index++] = (vk_shader_stage_t) { .filename = group->closest, .stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, - .specialization_info = create->tracing.specialization, + .specialization_info = create->specialization, }; } else { hits[hit_index].closest = -1; @@ -140,37 +136,86 @@ static ray_pass_t *createRayPass( const ray_pass_create_t *create ) { prci.groups.miss_count = miss_index; prci.stages_count = stage_index; - pass->tracing = VK_PipelineRayTracingCreate(&prci); + pass->pipeline = VK_PipelineRayTracingCreate(&prci); } - if (pass->tracing.pipeline == VK_NULL_HANDLE) { - VK_DescriptorsDestroy(&pass->desc.riptors); + if (pass->pipeline.pipeline == VK_NULL_HANDLE) { + VK_DescriptorsDestroy(&header->desc.riptors); Mem_Free(pass); return NULL; } - pass->desc.riptors.values = Mem_Malloc(vk_core.pool, sizeof(pass->desc.riptors.values[0]) * create->layout.bindings_count); + finalizePassDescriptors(header, &create->layout); - { - const size_t semantics_size = sizeof(int) * create->layout.bindings_count; - pass->desc.binding_semantics = Mem_Malloc(vk_core.pool, semantics_size); - memcpy(pass->desc.binding_semantics, create->layout.bindings_semantics, semantics_size); - } + Q_strncpy(header->debug_name, create->debug_name, sizeof(header->debug_name)); + header->type = RayPassType_Tracing; - return pass; + return header; } -struct ray_pass_s *RayPassCreate( const ray_pass_create_t *create ) { - return createRayPass(create); +struct ray_pass_s *RayPassCreateCompute( const ray_pass_create_compute_t *create ) { + ray_pass_compute_impl_t *const pass = Mem_Malloc(vk_core.pool, sizeof(*pass)); + ray_pass_t *const header = &pass->header; + + initPassDescriptors(header, &create->layout); + + const vk_pipeline_compute_create_info_t pcci = { + .layout = header->desc.riptors.pipeline_layout, + .shader_filename = create->shader, + .specialization_info = create->specialization, + }; + + pass->pipeline = VK_PipelineComputeCreate( &pcci ); + if (pass->pipeline == VK_NULL_HANDLE) { + VK_DescriptorsDestroy(&header->desc.riptors); + Mem_Free(pass); + return NULL; + } + + finalizePassDescriptors(header, &create->layout); + + Q_strncpy(header->debug_name, create->debug_name, sizeof(header->debug_name)); + header->type = RayPassType_Compute; + + return header; } void RayPassDestroy( struct ray_pass_s *pass ) { - VK_PipelineRayTracingDestroy(&pass->tracing); + switch (pass->type) { + case RayPassType_Tracing: + { + ray_pass_tracing_impl_t *tracing = (ray_pass_tracing_impl_t*)pass; + VK_PipelineRayTracingDestroy(&tracing->pipeline); + break; + } + case RayPassType_Compute: + { + ray_pass_compute_impl_t *compute = (ray_pass_compute_impl_t*)pass; + vkDestroyPipeline(vk_core.device, compute->pipeline, NULL); + break; + } + } + VK_DescriptorsDestroy(&pass->desc.riptors); Mem_Free(pass->desc.riptors.values); + Mem_Free(pass->desc.binding_semantics); Mem_Free(pass); } +static void performTracing( VkCommandBuffer cmdbuf, const ray_pass_tracing_impl_t *tracing, const struct vk_ray_resources_s *res) { + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, tracing->pipeline.pipeline); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, tracing->header.desc.riptors.pipeline_layout, 0, 1, tracing->header.desc.riptors.desc_sets + 0, 0, NULL); + VK_PipelineRayTracingTrace(cmdbuf, &tracing->pipeline, res->width, res->height); +} + +static void performCompute( VkCommandBuffer cmdbuf, const ray_pass_compute_impl_t *compute, const struct vk_ray_resources_s *res) { + const uint32_t WG_W = 8; + const uint32_t WG_H = 8; + + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, compute->pipeline); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, compute->header.desc.riptors.pipeline_layout, 0, 1, compute->header.desc.riptors.desc_sets + 0, 0, NULL); + vkCmdDispatch(cmdbuf, (res->width + WG_W - 1) / WG_W, (res->height + WG_H - 1) / WG_H, 1); +} void RayPassPerform( VkCommandBuffer cmdbuf, struct ray_pass_s *pass, const struct vk_ray_resources_s *res) { for (int i = 0; i < pass->desc.riptors.num_bindings; ++i) { @@ -183,7 +228,22 @@ void RayPassPerform( VkCommandBuffer cmdbuf, struct ray_pass_s *pass, const stru VK_DescriptorsWrite(&pass->desc.riptors); - vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pass->tracing.pipeline); - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pass->desc.riptors.pipeline_layout, 0, 1, pass->desc.riptors.desc_sets + 0, 0, NULL); - VK_PipelineRayTracingTrace(cmdbuf, &pass->tracing, res->width, res->height); + DEBUG_BEGIN(cmdbuf, pass->debug_name); + + switch (pass->type) { + case RayPassType_Tracing: + { + ray_pass_tracing_impl_t *tracing = (ray_pass_tracing_impl_t*)pass; + performTracing(cmdbuf, tracing, res); + break; + } + case RayPassType_Compute: + { + ray_pass_compute_impl_t *compute = (ray_pass_compute_impl_t*)pass; + performCompute(cmdbuf, compute, res); + break; + } + } + + DEBUG_END(cmdbuf); } diff --git a/ref_vk/ray_pass.h b/ref_vk/ray_pass.h index 69a7108e..9f9cc847 100644 --- a/ref_vk/ray_pass.h +++ b/ref_vk/ray_pass.h @@ -1,33 +1,6 @@ #pragma once #include "vk_core.h" -#include "vk_pipeline.h" -#include "vk_descriptor.h" - -typedef const char* ray_pass_shader_t; - -typedef struct { - ray_pass_shader_t closest; - ray_pass_shader_t any; -} ray_pass_hit_group_t; - -typedef struct { - ray_pass_shader_t raygen; - - const ray_pass_shader_t *miss; - int miss_count; - - const ray_pass_hit_group_t *hit; - int hit_count; - - const VkSpecializationInfo *specialization; -} ray_pass_tracing_t; - -// enum { -// RVkRayPassType_Compute, -// RVkRayPassType_Tracing, -// }; - // TODO these should be like: // - parse the entire layout from shaders @@ -41,19 +14,45 @@ typedef struct { VkPushConstantRange push_constants; } ray_pass_layout_t; -typedef struct { - // TODO enum type +struct ray_pass_s; + +typedef const char* ray_pass_shader_t; + +typedef struct { const char *debug_name; ray_pass_layout_t layout; - union { - ray_pass_tracing_t tracing; - }; -} ray_pass_create_t; + ray_pass_shader_t shader; + const VkSpecializationInfo *specialization; +} ray_pass_create_compute_t; + +struct ray_pass_s *RayPassCreateCompute( const ray_pass_create_compute_t *create ); + + +typedef struct { + ray_pass_shader_t closest; + ray_pass_shader_t any; +} ray_pass_hit_group_t; + +typedef struct { + const char *debug_name; + ray_pass_layout_t layout; + + ray_pass_shader_t raygen; + + const ray_pass_shader_t *miss; + int miss_count; + + const ray_pass_hit_group_t *hit; + int hit_count; + + const VkSpecializationInfo *specialization; +} ray_pass_create_tracing_t; + +struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create ); + -struct ray_pass_s; -struct ray_pass_s *RayPassCreate( const ray_pass_create_t *create ); void RayPassDestroy( struct ray_pass_s *pass ); struct vk_ray_resources_s; diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index 4c84f3e2..4cb48b82 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -18,7 +18,6 @@ #include "vk_devmem.h" // FIXME move this rt-specific stuff out -#include "vk_denoiser.h" #include "vk_light.h" #include "xash3d_types.h" @@ -814,9 +813,6 @@ qboolean R_VkInit( void ) // FIXME move all this to rt-specific modules VK_LightsInit(); - - if (!XVK_DenoiserInit()) - return false; } return true; @@ -826,7 +822,6 @@ void R_VkShutdown( void ) { if (vk_core.rtx) { - XVK_DenoiserDestroy(); VK_LightsShutdown(); VK_RayShutdown(); } diff --git a/ref_vk/vk_denoiser.c b/ref_vk/vk_denoiser.c index f8578d33..ff24131f 100644 --- a/ref_vk/vk_denoiser.c +++ b/ref_vk/vk_denoiser.c @@ -1,8 +1,7 @@ #include "vk_denoiser.h" -#include "vk_descriptor.h" -#include "vk_pipeline.h" #include "vk_ray_resources.h" +#include "ray_pass.h" #define LIST_BINDINGS(X) \ X(0, denoised) \ @@ -20,90 +19,38 @@ enum { DenoiserBinding_COUNT }; -static struct { - vk_descriptors_t descriptors; - vk_descriptor_value_t desc_values[DenoiserBinding_COUNT]; - - VkDescriptorSetLayoutBinding desc_bindings[DenoiserBinding_COUNT]; - VkDescriptorSet desc_sets[1]; - - VkPipeline pipeline; -} g_denoiser = {0}; - -static void createLayouts( void ) { - g_denoiser.descriptors.bindings = g_denoiser.desc_bindings; - g_denoiser.descriptors.num_bindings = COUNTOF(g_denoiser.desc_bindings); - g_denoiser.descriptors.values = g_denoiser.desc_values; - g_denoiser.descriptors.num_sets = 1; - g_denoiser.descriptors.desc_sets = g_denoiser.desc_sets; - g_denoiser.descriptors.push_constants = (VkPushConstantRange){ - .offset = 0, - .size = 0, - .stageFlags = 0, - }; - +static const VkDescriptorSetLayoutBinding bindings[] = { #define BIND_IMAGE(index, name) \ - g_denoiser.desc_bindings[DenoiserBinding_##name] = (VkDescriptorSetLayoutBinding){ \ + { \ .binding = index, \ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, \ .descriptorCount = 1, \ .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, \ - }; -LIST_BINDINGS(BIND_IMAGE) + }, + LIST_BINDINGS(BIND_IMAGE) #undef BIND_IMAGE +}; - VK_DescriptorsCreate(&g_denoiser.descriptors); -} +static const int semantics[] = { +#define X(index, name) RayResource_##name, + LIST_BINDINGS(X) +#undef BIND_IMAGE +}; -static VkPipeline createPipeline( void ) { - const vk_pipeline_compute_create_info_t pcci = { - .layout = g_denoiser.descriptors.pipeline_layout, - .shader_filename = "denoiser.comp.spv", - .specialization_info = NULL, + +struct ray_pass_s *R_VkRayDenoiserCreate( void ) { + const ray_pass_create_compute_t rpcc = { + .debug_name = "denoiser", + .layout = { + .bindings = bindings, + .bindings_semantics = semantics, + .bindings_count = COUNTOF(bindings), + .push_constants = {0}, + }, + .shader = "denoiser.comp.spv", + .specialization = NULL, }; - return VK_PipelineComputeCreate( &pcci ); + return RayPassCreateCompute( &rpcc ); } -qboolean XVK_DenoiserInit( void ) { - ASSERT(vk_core.rtx); - - createLayouts(); - - ASSERT(!g_denoiser.pipeline); - g_denoiser.pipeline = createPipeline(); - - return g_denoiser.pipeline != VK_NULL_HANDLE; -} - -void XVK_DenoiserDestroy( void ) { - ASSERT(vk_core.rtx); - ASSERT(g_denoiser.pipeline); - - vkDestroyPipeline(vk_core.device, g_denoiser.pipeline, NULL); - VK_DescriptorsDestroy(&g_denoiser.descriptors); -} - -void XVK_DenoiserReloadPipeline( void ) { - // TODO handle errors gracefully - vkDestroyPipeline(vk_core.device, g_denoiser.pipeline, NULL); - g_denoiser.pipeline = createPipeline(); -} - -void XVK_DenoiserDenoise( VkCommandBuffer cmdbuf, const vk_ray_resources_t* res ) { - const uint32_t WG_W = 8; - const uint32_t WG_H = 8; - -#define COPY_VALUE(index, name) \ - g_denoiser.desc_values[DenoiserBinding_##name] = res->values[RayResource_##name]; - LIST_BINDINGS(COPY_VALUE) -#undef COPY_VALUE - - VK_DescriptorsWrite(&g_denoiser.descriptors); - - DEBUG_BEGIN(cmdbuf, "denoiser"); - vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_denoiser.pipeline); - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, g_denoiser.descriptors.pipeline_layout, 0, 1, g_denoiser.descriptors.desc_sets + 0, 0, NULL); - vkCmdDispatch(cmdbuf, (res->width + WG_W - 1) / WG_W, (res->height + WG_H - 1) / WG_H, 1); - DEBUG_END(cmdbuf); -} diff --git a/ref_vk/vk_denoiser.h b/ref_vk/vk_denoiser.h index 3b248219..4cb69f71 100644 --- a/ref_vk/vk_denoiser.h +++ b/ref_vk/vk_denoiser.h @@ -1,11 +1,4 @@ #pragma once -#include "vk_ray_resources.h" -#include "vk_core.h" - -qboolean XVK_DenoiserInit( void ); -void XVK_DenoiserDestroy( void ); - -void XVK_DenoiserReloadPipeline( void ); - -void XVK_DenoiserDenoise( VkCommandBuffer cmdbuf, const vk_ray_resources_t* res ); +struct ray_pass_s; +struct ray_pass_s *R_VkRayDenoiserCreate( void ); diff --git a/ref_vk/vk_pipeline.c b/ref_vk/vk_pipeline.c index e0c5c73f..fb032da2 100644 --- a/ref_vk/vk_pipeline.c +++ b/ref_vk/vk_pipeline.c @@ -320,8 +320,6 @@ void VK_PipelineRayTracingDestroy(vk_pipeline_ray_t* pipeline) { } void VK_PipelineRayTracingTrace(VkCommandBuffer cmdbuf, const vk_pipeline_ray_t *pipeline, uint32_t width, uint32_t height) { - DEBUG_BEGIN(cmdbuf, pipeline->debug_name); // TODO bind this and accepts descriptors as args? vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline->pipeline); vkCmdTraceRaysKHR(cmdbuf, &pipeline->sbt.raygen, &pipeline->sbt.miss, &pipeline->sbt.hit, &pipeline->sbt.callable, width, height, 1 ); - DEBUG_END(cmdbuf); } diff --git a/ref_vk/vk_pipeline.h b/ref_vk/vk_pipeline.h index 4c95a800..5c7b7814 100644 --- a/ref_vk/vk_pipeline.h +++ b/ref_vk/vk_pipeline.h @@ -39,7 +39,7 @@ VkPipeline VK_PipelineGraphicsCreate(const vk_pipeline_graphics_create_info_t *c typedef struct { VkPipelineLayout layout; const char *shader_filename; - VkSpecializationInfo *specialization_info; + const VkSpecializationInfo *specialization_info; } vk_pipeline_compute_create_info_t; VkPipeline VK_PipelineComputeCreate(const vk_pipeline_compute_create_info_t *ci); diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 11018380..1855e1b6 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -85,7 +85,7 @@ struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { }, }; - const ray_pass_create_t rpc = { + const ray_pass_create_tracing_t rpc = { .debug_name = "light direct poly", .layout = { .bindings = bindings, @@ -93,18 +93,16 @@ struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { .bindings_count = COUNTOF(bindings), .push_constants = {0}, }, - .tracing = { - .raygen = "ray_light_poly_direct.rgen.spv", - .miss = miss, - .miss_count = COUNTOF(miss), - .hit = hit, - .hit_count = COUNTOF(hit), - .specialization = &spec, - }, + .raygen = "ray_light_poly_direct.rgen.spv", + .miss = miss, + .miss_count = COUNTOF(miss), + .hit = hit, + .hit_count = COUNTOF(hit), + .specialization = &spec, }; initDescriptors(); - return RayPassCreate( &rpc ); + return RayPassCreateTracing( &rpc ); } struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ) { @@ -134,7 +132,7 @@ struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ) { }, }; - const ray_pass_create_t rpc = { + const ray_pass_create_tracing_t rpc = { .debug_name = "light direct point", .layout = { .bindings = bindings, @@ -142,16 +140,14 @@ struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ) { .bindings_count = COUNTOF(bindings), .push_constants = {0}, }, - .tracing = { - .raygen = "ray_light_direct_point.rgen.spv", - .miss = miss, - .miss_count = COUNTOF(miss), - .hit = hit, - .hit_count = COUNTOF(hit), - .specialization = &spec, - }, + .raygen = "ray_light_direct_point.rgen.spv", + .miss = miss, + .miss_count = COUNTOF(miss), + .hit = hit, + .hit_count = COUNTOF(hit), + .specialization = &spec, }; initDescriptors(); - return RayPassCreate( &rpc ); + return RayPassCreateTracing( &rpc ); } diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index 8e3b3361..5e8e4ecf 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -74,7 +74,7 @@ struct ray_pass_s *R_VkRayPrimaryPassCreate( void ) { }, }; - const ray_pass_create_t rpc = { + const ray_pass_create_tracing_t rpc = { .debug_name = "primary ray", .layout = { .bindings = bindings, @@ -82,16 +82,14 @@ struct ray_pass_s *R_VkRayPrimaryPassCreate( void ) { .bindings_count = COUNTOF(bindings), .push_constants = {0}, }, - .tracing = { - .raygen = "ray_primary.rgen.spv", - .miss = miss, - .miss_count = COUNTOF(miss), - .hit = hit, - .hit_count = COUNTOF(hit), - .specialization = &spec, - }, + .raygen = "ray_primary.rgen.spv", + .miss = miss, + .miss_count = COUNTOF(miss), + .hit = hit, + .hit_count = COUNTOF(hit), + .specialization = &spec, }; initDescriptors(); - return RayPassCreate( &rpc ); + return RayPassCreateTracing( &rpc ); } diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 2398da1c..16b4c804 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -1,6 +1,8 @@ #include "vk_rtx.h" #include "ray_pass.h" +#include "vk_ray_resources.h" + #include "vk_ray_primary.h" #include "vk_ray_light_direct.h" @@ -16,6 +18,7 @@ #include "vk_denoiser.h" #include "vk_math.h" + #include "eiface.h" #include "xash3d_mathlib.h" @@ -168,6 +171,7 @@ static struct { struct ray_pass_s *primary_ray; struct ray_pass_s *light_direct_poly; struct ray_pass_s *light_direct_point; + struct ray_pass_s *denoiser; } pass; qboolean reload_pipeline; @@ -1114,7 +1118,7 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar 0, 0, NULL, 0, NULL, ARRAYSIZE(image_barriers), image_barriers); } - XVK_DenoiserDenoise( cmdbuf, &res ); + RayPassPerform(cmdbuf, g_rtx.pass.denoiser, &res); { const xvk_blit_args blit_args = { @@ -1178,8 +1182,8 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) reloadPass( &g_rtx.pass.primary_ray, R_VkRayPrimaryPassCreate()); reloadPass( &g_rtx.pass.light_direct_poly, R_VkRayLightDirectPolyPassCreate()); reloadPass( &g_rtx.pass.light_direct_point, R_VkRayLightDirectPointPassCreate()); + reloadPass( &g_rtx.pass.denoiser, R_VkRayDenoiserCreate()); - XVK_DenoiserReloadPipeline(); g_rtx.reload_pipeline = false; } @@ -1357,6 +1361,9 @@ qboolean VK_RayInit( void ) g_rtx.pass.light_direct_point = R_VkRayLightDirectPointPassCreate(); ASSERT(g_rtx.pass.light_direct_point); + g_rtx.pass.denoiser = R_VkRayDenoiserCreate(); + ASSERT(g_rtx.pass.denoiser); + g_rtx.sbt_record_size = vk_core.physical_device.sbt_record_size; g_rtx.uniform_unit_size = ALIGN_UP(sizeof(struct UniformBuffer), vk_core.physical_device.properties.limits.minUniformBufferOffsetAlignment); @@ -1476,6 +1483,7 @@ qboolean VK_RayInit( void ) void VK_RayShutdown( void ) { ASSERT(vk_core.rtx); + RayPassDestroy(g_rtx.pass.denoiser); RayPassDestroy(g_rtx.pass.light_direct_poly); RayPassDestroy(g_rtx.pass.light_direct_point); RayPassDestroy(g_rtx.pass.primary_ray); From 988183a96a018ae35c6631d03aeed37d2fd38422 Mon Sep 17 00:00:00 2001 From: Bien Pham Date: Tue, 1 Feb 2022 03:37:13 +0700 Subject: [PATCH 135/548] engine: implement support for secured client dlls --- engine/client/cl_game.c | 12 + engine/client/cl_securedstub.c | 459 +++++++++++++++++++++++++++++++++ 2 files changed, 471 insertions(+) create mode 100644 engine/client/cl_securedstub.c diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index 27d5c462..66767d85 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -3932,6 +3932,8 @@ void CL_UnloadProgs( void ) Cmd_Unlink( CMD_CLIENTDLL ); } +void GetSecuredClientAPI( CL_EXPORT_FUNCS F ); + qboolean CL_LoadProgs( const char *name ) { static playermove_t gpMove; @@ -3984,7 +3986,17 @@ qboolean CL_LoadProgs( const char *name ) // trying to fill interface now GetClientAPI( &clgame.dllFuncs ); + } + else if(( GetClientAPI = (void *)COM_GetProcAddress( clgame.hInstance, "F" )) != NULL ) + { + Con_Reportf( "CL_LoadProgs: found single callback export (secured client dlls)\n" ); + // trying to fill interface now + GetSecuredClientAPI( GetClientAPI ); + } + + if ( GetClientAPI != NULL ) + { // check critical functions again for( func = cdll_exports; func && func->name; func++ ) { diff --git a/engine/client/cl_securedstub.c b/engine/client/cl_securedstub.c new file mode 100644 index 00000000..28034cf2 --- /dev/null +++ b/engine/client/cl_securedstub.c @@ -0,0 +1,459 @@ +/* +cl_securedstub.c - secured client dll stub +Copyright (C) 2022 FWGS + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "common.h" +#include "client.h" + +typedef struct cldll_func_src_s +{ + int (*pfnInitialize)( cl_enginefunc_t *pEnginefuncs, int iVersion ); + void (*pfnInit)( void ); + int (*pfnVidInit)( void ); + int (*pfnRedraw)( float flTime, int intermission ); + int (*pfnUpdateClientData)( client_data_t *cdata, float flTime ); + void (*pfnReset)( void ); + void (*pfnPlayerMove)( struct playermove_s *ppmove, int server ); + void (*pfnPlayerMoveInit)( struct playermove_s *ppmove ); + char (*pfnPlayerMoveTexture)( char *name ); + void (*IN_ActivateMouse)( void ); + void (*IN_DeactivateMouse)( void ); + void (*IN_MouseEvent)( int mstate ); + void (*IN_ClearStates)( void ); + void (*IN_Accumulate)( void ); + void (*CL_CreateMove)( float frametime, struct usercmd_s *cmd, int active ); + int (*CL_IsThirdPerson)( void ); + void (*CL_CameraOffset)( float *ofs ); // unused + void *(*KB_Find)( const char *name ); + void (*CAM_Think)( void ); // camera stuff + void (*pfnCalcRefdef)( ref_params_t *pparams ); + int (*pfnAddEntity)( int type, cl_entity_t *ent, const char *modelname ); + void (*pfnCreateEntities)( void ); + void (*pfnDrawNormalTriangles)( void ); + void (*pfnDrawTransparentTriangles)( void ); + void (*pfnStudioEvent)( const struct mstudioevent_s *event, const cl_entity_t *entity ); + void (*pfnPostRunCmd)( struct local_state_s *from, struct local_state_s *to, usercmd_t *cmd, int runfuncs, double time, unsigned int random_seed ); + void (*pfnShutdown)( void ); + void (*pfnTxferLocalOverrides)( entity_state_t *state, const clientdata_t *client ); + void (*pfnProcessPlayerState)( entity_state_t *dst, const entity_state_t *src ); + void (*pfnTxferPredictionData)( entity_state_t *ps, const entity_state_t *pps, clientdata_t *pcd, const clientdata_t *ppcd, weapon_data_t *wd, const weapon_data_t *pwd ); + void (*pfnDemo_ReadBuffer)( int size, byte *buffer ); + int (*pfnConnectionlessPacket)( const struct netadr_s *net_from, const char *args, char *buffer, int *size ); + int (*pfnGetHullBounds)( int hullnumber, float *mins, float *maxs ); + void (*pfnFrame)( double time ); + int (*pfnKey_Event)( int eventcode, int keynum, const char *pszCurrentBinding ); + void (*pfnTempEntUpdate)( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( cl_entity_t *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp )); + cl_entity_t *(*pfnGetUserEntity)( int index ); + void (*pfnVoiceStatus)( int entindex, qboolean bTalking ); + void (*pfnDirectorMessage)( int iSize, void *pbuf ); + int (*pfnGetStudioModelInterface)( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ); + void (*pfnChatInputPosition)( int *x, int *y ); + int (*pfnGetPlayerTeam)( int iPlayer ); + void *(*pfnClientFactory)( void ); +} cldll_func_src_t; + +typedef struct cldll_func_dst_s +{ + void (*pfnInitialize)( cl_enginefunc_t **pEnginefuncs, int *iVersion ); + void (*pfnInit)( void ); + void (*pfnVidInit)( void ); + void (*pfnRedraw)( float *flTime, int *intermission ); + void (*pfnUpdateClientData)( client_data_t **cdata, float *flTime ); + void (*pfnReset)( void ); + void (*pfnPlayerMove)( struct playermove_s **ppmove, int *server ); + void (*pfnPlayerMoveInit)( struct playermove_s **ppmove ); + void (*pfnPlayerMoveTexture)( char **name ); + void (*IN_ActivateMouse)( void ); + void (*IN_DeactivateMouse)( void ); + void (*IN_MouseEvent)( int *mstate ); + void (*IN_ClearStates)( void ); + void (*IN_Accumulate)( void ); + void (*CL_CreateMove)( float *frametime, struct usercmd_s **cmd, int *active ); + void (*CL_IsThirdPerson)( void ); + void (*CL_CameraOffset)( float **ofs ); + void (*KB_Find)( const char **name ); + void (*CAM_Think)( void ); + void (*pfnCalcRefdef)( ref_params_t **pparams ); + void (*pfnAddEntity)( int *type, cl_entity_t **ent, const char **modelname ); + void (*pfnCreateEntities)( void ); + void (*pfnDrawNormalTriangles)( void ); + void (*pfnDrawTransparentTriangles)( void ); + void (*pfnStudioEvent)( const struct mstudioevent_s **event, const cl_entity_t **entity ); + void (*pfnPostRunCmd)( struct local_state_s **from, struct local_state_s **to, usercmd_t **cmd, int *runfuncs, double *time, unsigned int *random_seed ); + void (*pfnShutdown)( void ); + void (*pfnTxferLocalOverrides)( entity_state_t **state, const clientdata_t **client ); + void (*pfnProcessPlayerState)( entity_state_t **dst, const entity_state_t **src ); + void (*pfnTxferPredictionData)( entity_state_t **ps, const entity_state_t **pps, clientdata_t **pcd, const clientdata_t **ppcd, weapon_data_t **wd, const weapon_data_t **pwd ); + void (*pfnDemo_ReadBuffer)( int *size, byte **buffer ); + void (*pfnConnectionlessPacket)( const struct netadr_s **net_from, const char **args, char **buffer, int **size ); + void (*pfnGetHullBounds)( int *hullnumber, float **mins, float **maxs ); + void (*pfnFrame)( double *time ); + void (*pfnKey_Event)( int *eventcode, int *keynum, const char **pszCurrentBinding ); + void (*pfnTempEntUpdate)( double *frametime, double *client_time, double *cl_gravity, struct tempent_s ***ppTempEntFree, struct tempent_s ***ppTempEntActive, int ( **Callback_AddVisibleEntity )( cl_entity_t *pEntity ), void ( **Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp )); + void (*pfnGetUserEntity)( int *index ); + void (*pfnVoiceStatus)( int *entindex, qboolean *bTalking ); + void (*pfnDirectorMessage)( int *iSize, void **pbuf ); + void (*pfnGetStudioModelInterface)( int *version, struct r_studio_interface_s ***ppinterface, struct engine_studio_api_s **pstudio ); + void (*pfnChatInputPosition)( int **x, int **y ); + void (*pfnGetPlayerTeam)( int *iPlayer ); +} cldll_func_dst_t; + +typedef struct modfuncs_s +{ + void (*m_pfnLoadMod)( char *pchModule ); + void (*m_pfnCloseMod)( void ); + int (*m_pfnNCall)( int ijump, int cnArg, ... ); + void (*m_pfnGetClDstAddrs)( cldll_func_dst_t *pcldstAddrs ); + void (*m_pfnGetEngDstAddrs)( struct cl_enginefunc_dst_s *pengdstAddrs ); + void (*m_pfnModuleLoaded)( void ); + void (*m_pfnProcessOutgoingNet)( struct netchan_s *pchan, struct sizebuf_s *psizebuf ); + qboolean (*m_pfnProcessIncomingNet)( struct netchan_s *pchan, struct sizebuf_s *psizebuf ); + void (*m_pfnTextureLoad)( char *pszName, int dxWidth, int dyHeight, char *pbData ); + void (*m_pfnModelLoad)( struct model_s *pmodel, void *pvBuf ); + void (*m_pfnFrameBegin)( void ); + void (*m_pfnFrameRender1)( void ); + void (*m_pfnFrameRender2)( void ); + void (*m_pfnSetModSHelpers)( struct modshelpers_s *pmodshelpers ); + void (*m_pfnSetModCHelpers)( struct modchelpers_s *pmodchelpers ); + void (*m_pfnSetEngData)( struct engdata_s *pengdata ); + int m_nVersion; + void (*m_pfnConnectClient)( int iPlayer ); + void (*m_pfnRecordIP)( unsigned int pnIP ); + void (*m_pfnPlayerStatus)( unsigned char *pbData, int cbData ); + void (*m_pfnSetEngineVersion)( int nVersion ); + int m_nVoid2; + int m_nVoid3; + int m_nVoid4; + int m_nVoid5; + int m_nVoid6; + int m_nVoid7; + int m_nVoid8; + int m_nVoid9; +} modfuncs_t; + +static void DstInitialize( cl_enginefunc_t **pEnginefuncs, int *iVersion ) +{ + // stub +} + +static void DstInit( void ) +{ + // stub +} + +static void DstVidInit( void ) +{ + // stub +} + +static void DstRedraw( float *flTime, int *intermission ) +{ + // stub +} + +static void DstUpdateClientData( client_data_t **cdata, float *flTime ) +{ + // stub +} + +static void DstReset( void ) +{ + // stub +} + +static void DstPlayerMove( struct playermove_s **ppmove, int *server ) +{ + // stub +} + +static void DstPlayerMoveInit( struct playermove_s **ppmove ) +{ + // stub +} + +static void DstPlayerMoveTexture( char **name ) +{ + // stub +} + +static void DstIN_ActivateMouse( void ) +{ + // stub +} + +static void DstIN_DeactivateMouse( void ) +{ + // stub +} + +static void DstIN_MouseEvent( int *mstate ) +{ + // stub +} + +static void DstIN_ClearStates( void ) +{ + // stub +} + +static void DstIN_Accumulate( void ) +{ + // stub +} + +static void DstCL_CreateMove( float *frametime, struct usercmd_s **cmd, int *active ) +{ + // stub +} + +static void DstCL_IsThirdPerson( void ) +{ + // stub +} + +static void DstCL_CameraOffset( float **ofs ) +{ + // stub +} + +static void DstKB_Find( const char **name ) +{ + // stub +} + +static void DstCAM_Think( void ) +{ + // stub +} + +static void DstCalcRefdef( ref_params_t **pparams ) +{ + // stub +} + +static void DstAddEntity( int *type, cl_entity_t **ent, const char **modelname ) +{ + // stub +} + +static void DstCreateEntities( void ) +{ + // stub +} + +static void DstDrawNormalTriangles( void ) +{ + // stub +} + +static void DstDrawTransparentTriangles( void ) +{ + // stub +} + +static void DstStudioEvent( const struct mstudioevent_s **event, const cl_entity_t **entity ) +{ + // stub +} + +static void DstPostRunCmd( struct local_state_s **from, struct local_state_s **to, usercmd_t **cmd, int *runfuncs, double *time, unsigned int *random_seed ) +{ + // stub +} + +static void DstShutdown( void ) +{ + // stub +} + +static void DstTxferLocalOverrides( entity_state_t **state, const clientdata_t **client ) +{ + // stub +} + +static void DstProcessPlayerState( entity_state_t **dst, const entity_state_t **src ) +{ + // stub +} + +static void DstTxferPredictionData( entity_state_t **ps, const entity_state_t **pps, clientdata_t **pcd, const clientdata_t **ppcd, weapon_data_t **wd, const weapon_data_t **pwd ) +{ + // stub +} + +static void DstDemo_ReadBuffer( int *size, byte **buffer ) +{ + // stub +} + +static void DstConnectionlessPacket( const struct netadr_s **net_from, const char **args, char **buffer, int **size ) +{ + // stub +} + +static void DstGetHullBounds( int *hullnumber, float **mins, float **maxs ) +{ + // stub +} + +static void DstFrame( double *time ) +{ + // stub +} + +static void DstKey_Event( int *eventcode, int *keynum, const char **pszCurrentBinding ) +{ + // stub +} + +static void DstTempEntUpdate( double *frametime, double *client_time, double *cl_gravity, struct tempent_s ***ppTempEntFree, struct tempent_s ***ppTempEntActive, int ( **Callback_AddVisibleEntity )( cl_entity_t *pEntity ), void ( **Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ) ) +{ + // stub +} + +static void DstGetUserEntity( int *index ) +{ + // stub +} + +static void DstVoiceStatus( int *entindex, qboolean *bTalking ) +{ + // stub +} + +static void DstDirectorMessage( int *iSize, void **pbuf ) +{ + // stub +} + +static void DstGetStudioModelInterface( int *version, struct r_studio_interface_s ***ppinterface, struct engine_studio_api_s **pstudio ) +{ + // stub +} + +static void DstChatInputPosition( int **x, int **y ) +{ + // stub +} + +static void DstGetPlayerTeam( int *iPlayer ) +{ + // stub +} + +static cldll_func_dst_t cldllFuncDst = +{ + DstInitialize, + DstInit, + DstVidInit, + DstRedraw, + DstUpdateClientData, + DstReset, + DstPlayerMove, + DstPlayerMoveInit, + DstPlayerMoveTexture, + DstIN_ActivateMouse, + DstIN_DeactivateMouse, + DstIN_MouseEvent, + DstIN_ClearStates, + DstIN_Accumulate, + DstCL_CreateMove, + DstCL_IsThirdPerson, + DstCL_CameraOffset, + DstKB_Find, + DstCAM_Think, + DstCalcRefdef, + DstAddEntity, + DstCreateEntities, + DstDrawNormalTriangles, + DstDrawTransparentTriangles, + DstStudioEvent, + DstPostRunCmd, + DstShutdown, + DstTxferLocalOverrides, + DstProcessPlayerState, + DstTxferPredictionData, + DstDemo_ReadBuffer, + DstConnectionlessPacket, + DstGetHullBounds, + DstFrame, + DstKey_Event, + DstTempEntUpdate, + DstGetUserEntity, + DstVoiceStatus, + DstDirectorMessage, + DstGetStudioModelInterface, + DstChatInputPosition, + DstGetPlayerTeam, +}; + +void GetSecuredClientAPI( CL_EXPORT_FUNCS F ) +{ + cldll_func_src_t cldllFuncSrc; + modfuncs_t modFuncs; + const dllfunc_t *func; + + memset( &cldllFuncSrc, 0, sizeof( cldllFuncSrc ) ); + memset( &modFuncs, 0, sizeof( modFuncs ) ); + + // secured client dlls need these + *(cldll_func_dst_t **)&cldllFuncSrc.pfnVidInit = &cldllFuncDst; + *(modfuncs_t **)&cldllFuncSrc.pfnInitialize = &modFuncs; + + // trying to fill interface now + F( &cldllFuncSrc ); + + // map exports to xash's cldll_func_t + clgame.dllFuncs.pfnInitialize = cldllFuncSrc.pfnInitialize; + clgame.dllFuncs.pfnInit = cldllFuncSrc.pfnInit; + clgame.dllFuncs.pfnVidInit = cldllFuncSrc.pfnVidInit; + clgame.dllFuncs.pfnRedraw = cldllFuncSrc.pfnRedraw; + clgame.dllFuncs.pfnUpdateClientData = cldllFuncSrc.pfnUpdateClientData; + clgame.dllFuncs.pfnReset = cldllFuncSrc.pfnReset; + clgame.dllFuncs.pfnPlayerMove = cldllFuncSrc.pfnPlayerMove; + clgame.dllFuncs.pfnPlayerMoveInit = cldllFuncSrc.pfnPlayerMoveInit; + clgame.dllFuncs.pfnPlayerMoveTexture = cldllFuncSrc.pfnPlayerMoveTexture; + clgame.dllFuncs.IN_ActivateMouse = cldllFuncSrc.IN_ActivateMouse; + clgame.dllFuncs.IN_DeactivateMouse = cldllFuncSrc.IN_DeactivateMouse; + clgame.dllFuncs.IN_MouseEvent = cldllFuncSrc.IN_MouseEvent; + clgame.dllFuncs.IN_ClearStates = cldllFuncSrc.IN_ClearStates; + clgame.dllFuncs.IN_Accumulate = cldllFuncSrc.IN_Accumulate; + clgame.dllFuncs.CL_CreateMove = cldllFuncSrc.CL_CreateMove; + clgame.dllFuncs.CL_IsThirdPerson = cldllFuncSrc.CL_IsThirdPerson; + clgame.dllFuncs.CL_CameraOffset = cldllFuncSrc.CL_CameraOffset; + clgame.dllFuncs.KB_Find = cldllFuncSrc.KB_Find; + clgame.dllFuncs.CAM_Think = cldllFuncSrc.CAM_Think; + clgame.dllFuncs.pfnCalcRefdef = cldllFuncSrc.pfnCalcRefdef; + clgame.dllFuncs.pfnAddEntity = cldllFuncSrc.pfnAddEntity; + clgame.dllFuncs.pfnCreateEntities = cldllFuncSrc.pfnCreateEntities; + clgame.dllFuncs.pfnDrawNormalTriangles = cldllFuncSrc.pfnDrawNormalTriangles; + clgame.dllFuncs.pfnDrawTransparentTriangles = cldllFuncSrc.pfnDrawTransparentTriangles; + clgame.dllFuncs.pfnStudioEvent = cldllFuncSrc.pfnStudioEvent; + clgame.dllFuncs.pfnPostRunCmd = cldllFuncSrc.pfnPostRunCmd; + clgame.dllFuncs.pfnShutdown = cldllFuncSrc.pfnShutdown; + clgame.dllFuncs.pfnTxferLocalOverrides = cldllFuncSrc.pfnTxferLocalOverrides; + clgame.dllFuncs.pfnProcessPlayerState = cldllFuncSrc.pfnProcessPlayerState; + clgame.dllFuncs.pfnTxferPredictionData = cldllFuncSrc.pfnTxferPredictionData; + clgame.dllFuncs.pfnDemo_ReadBuffer = cldllFuncSrc.pfnDemo_ReadBuffer; + clgame.dllFuncs.pfnConnectionlessPacket = cldllFuncSrc.pfnConnectionlessPacket; + clgame.dllFuncs.pfnGetHullBounds = cldllFuncSrc.pfnGetHullBounds; + clgame.dllFuncs.pfnFrame = cldllFuncSrc.pfnFrame; + clgame.dllFuncs.pfnKey_Event = cldllFuncSrc.pfnKey_Event; + clgame.dllFuncs.pfnTempEntUpdate = cldllFuncSrc.pfnTempEntUpdate; + clgame.dllFuncs.pfnGetUserEntity = cldllFuncSrc.pfnGetUserEntity; + clgame.dllFuncs.pfnVoiceStatus = cldllFuncSrc.pfnVoiceStatus; + clgame.dllFuncs.pfnDirectorMessage = cldllFuncSrc.pfnDirectorMessage; + clgame.dllFuncs.pfnGetStudioModelInterface = cldllFuncSrc.pfnGetStudioModelInterface; + clgame.dllFuncs.pfnChatInputPosition = cldllFuncSrc.pfnChatInputPosition; +} From b6c96ddebd754ea0f13c9bd883648a8ea0e66fbd Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Mon, 31 Jan 2022 20:40:41 -0800 Subject: [PATCH 136/548] rt: automate image barriers for passes --- ref_vk/ray_pass.c | 32 +++++--- ref_vk/ray_pass.h | 2 +- ref_vk/ray_resources.c | 97 +++++++++++++++++++++++ ref_vk/ray_resources.h | 63 +++++++++++++++ ref_vk/vk_denoiser.c | 25 +++--- ref_vk/vk_ray_light_direct.c | 20 +++-- ref_vk/vk_ray_primary.c | 10 ++- ref_vk/vk_ray_resources.h | 35 ++++++--- ref_vk/vk_rtx.c | 144 +++++++++++------------------------ 9 files changed, 279 insertions(+), 149 deletions(-) create mode 100644 ref_vk/ray_resources.c create mode 100644 ref_vk/ray_resources.h diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c index f60bd3f3..88d523ac 100644 --- a/ref_vk/ray_pass.c +++ b/ref_vk/ray_pass.c @@ -217,16 +217,30 @@ static void performCompute( VkCommandBuffer cmdbuf, const ray_pass_compute_impl_ vkCmdDispatch(cmdbuf, (res->width + WG_W - 1) / WG_W, (res->height + WG_H - 1) / WG_H, 1); } -void RayPassPerform( VkCommandBuffer cmdbuf, struct ray_pass_s *pass, const struct vk_ray_resources_s *res) { - for (int i = 0; i < pass->desc.riptors.num_bindings; ++i) { - const int res_index = pass->desc.binding_semantics[i]; - // TODO check early - ASSERT(res_index >= 0); - ASSERT(res_index < RayResource__COUNT); - pass->desc.riptors.values[i] = res->values[res_index]; - } +void RayPassPerform( VkCommandBuffer cmdbuf, struct ray_pass_s *pass, struct vk_ray_resources_s *res) { + { + ray_resources_fill_t fill = { + .resources = res, + .count = pass->desc.riptors.num_bindings, + .indices = pass->desc.binding_semantics, + .out_values = pass->desc.riptors.values, + }; - VK_DescriptorsWrite(&pass->desc.riptors); + switch (pass->type) { + case RayPassType_Tracing: + fill.dest_pipeline = VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; + break; + case RayPassType_Compute: + fill.dest_pipeline = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + break; + default: + ASSERT(!"Unexpected pass type"); + } + + RayResourcesFill(cmdbuf, fill); + + VK_DescriptorsWrite(&pass->desc.riptors); + } DEBUG_BEGIN(cmdbuf, pass->debug_name); diff --git a/ref_vk/ray_pass.h b/ref_vk/ray_pass.h index 9f9cc847..3073fe20 100644 --- a/ref_vk/ray_pass.h +++ b/ref_vk/ray_pass.h @@ -56,4 +56,4 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create void RayPassDestroy( struct ray_pass_s *pass ); struct vk_ray_resources_s; -void RayPassPerform( VkCommandBuffer cmdbuf, struct ray_pass_s *pass, const struct vk_ray_resources_s *res); +void RayPassPerform( VkCommandBuffer cmdbuf, struct ray_pass_s *pass, struct vk_ray_resources_s *res); diff --git a/ref_vk/ray_resources.c b/ref_vk/ray_resources.c new file mode 100644 index 00000000..db997b20 --- /dev/null +++ b/ref_vk/ray_resources.c @@ -0,0 +1,97 @@ +#include "vk_ray_resources.h" +#include "vk_core.h" + +#include + +#define MAX_BARRIERS 16 + +void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill) { + VkImageMemoryBarrier image_barriers[MAX_BARRIERS]; + int image_barriers_count = 0; + VkPipelineStageFlags src_stage_mask = VK_PIPELINE_STAGE_NONE_KHR; + + for (int i = 0; i < fill.count; ++i) { + const qboolean write = fill.indices[i] < 0; + const int index = abs(fill.indices[i]) - 1; + ray_resource_t *const res = fill.resources->resources + index; + + ASSERT(index >= 0); + ASSERT(index < RayResource__COUNT); + + if (res->type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) { + if (write) { + // No reads are happening + ASSERT(res->read.pipelines == 0); + + res->write = (ray_resource_state_t) { + .access_mask = VK_ACCESS_SHADER_WRITE_BIT, + .image_layout = VK_IMAGE_LAYOUT_GENERAL, + .pipelines = fill.dest_pipeline, + }; + + image_barriers[image_barriers_count++] = (VkImageMemoryBarrier) { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .image = res->image->image, + .srcAccessMask = 0, + .dstAccessMask = res->write.access_mask, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = res->write.image_layout, + .subresourceRange = (VkImageSubresourceRange) { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + } else { + // Write happened + ASSERT(res->write.pipelines != 0); + + + // No barrier was issued + if (!(res->read.pipelines & fill.dest_pipeline)) { + res->read.access_mask = VK_ACCESS_SHADER_READ_BIT; + res->read.pipelines |= fill.dest_pipeline; + res->read.image_layout = VK_IMAGE_LAYOUT_GENERAL; + + src_stage_mask |= res->write.pipelines; + + image_barriers[image_barriers_count++] = (VkImageMemoryBarrier) { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .image = res->image->image, + .srcAccessMask = res->write.access_mask, + .dstAccessMask = res->read.access_mask, + .oldLayout = res->write.image_layout, + .newLayout = res->read.image_layout, + .subresourceRange = (VkImageSubresourceRange) { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + } + } + fill.out_values[i].image = (VkDescriptorImageInfo) { + .imageLayout = write ? res->write.image_layout : res->read.image_layout, + .imageView = res->image->view, + .sampler = VK_NULL_HANDLE, + }; + } else { + fill.out_values[i] = res->value; + } + } + + if (image_barriers_count) { + if (!src_stage_mask) + src_stage_mask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + + vkCmdPipelineBarrier(cmdbuf, + src_stage_mask, + fill.dest_pipeline, + 0, 0, NULL, 0, NULL, image_barriers_count, image_barriers); + } +} diff --git a/ref_vk/ray_resources.h b/ref_vk/ray_resources.h new file mode 100644 index 00000000..6588da68 --- /dev/null +++ b/ref_vk/ray_resources.h @@ -0,0 +1,63 @@ +#pragma once + +#include "vk_rtx.h" +#include "vk_const.h" +#include "vk_descriptor.h" + +#include "shaders/ray_interop.h" + +#define RAY_SCENE_RESOURCES(X) \ + X(TLAS, tlas) \ + X(Buffer, ubo) \ + X(Buffer, kusochki) \ + X(Buffer, indices) \ + X(Buffer, vertices) \ + X(Buffer, lights) \ + X(Buffer, light_clusters) \ + X(Texture, all_textures) \ + +enum { +#define X(type, name, ...) RayResource_##name, + RAY_SCENE_RESOURCES(X) + RAY_PRIMARY_OUTPUTS(X) + RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) + RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) + X(-1, denoised) +#undef X + RayResource__COUNT +}; + +typedef struct { + VkAccessFlags access_mask; + VkImageLayout image_layout; + VkPipelineStageFlagBits pipelines; +} ray_resource_state_t; + +typedef struct { + VkDescriptorType type; + ray_resource_state_t write, read; + vk_descriptor_value_t value; +} ray_resource_t; + +#define RAY_RESOURCE_DEFAULT_STATE \ + (ray_resource_state_t) { \ + .access_mask = 0, \ + .image_layout = VK_IMAGE_LAYOUT_UNDEFINED, \ + .pipelines = 0, \ + } + +typedef struct vk_ray_resources_s { + uint32_t width, height; + ray_resource_t values[RayResource__COUNT]; +} vk_ray_resources_t; + +typedef struct { + vk_ray_resources_t *resources; + const int *indices; + int count; + VkPipelineStageFlagBits dest_pipeline; + + vk_descriptor_value_t *out_values; +} ray_resources_fill_t; + +void RayResourcesFill(ray_resources_fill_t fill); diff --git a/ref_vk/vk_denoiser.c b/ref_vk/vk_denoiser.c index ff24131f..a49ea6de 100644 --- a/ref_vk/vk_denoiser.c +++ b/ref_vk/vk_denoiser.c @@ -3,22 +3,16 @@ #include "vk_ray_resources.h" #include "ray_pass.h" -#define LIST_BINDINGS(X) \ +#define LIST_OUTPUTS(X) \ X(0, denoised) \ + +#define LIST_INPUTS(X) \ X(1, base_color_a) \ X(2, light_poly_diffuse) \ X(3, light_poly_specular) \ X(4, light_point_diffuse) \ X(5, light_point_specular) \ -enum { -#define X(index, name) DenoiserBinding_##name, - LIST_BINDINGS(X) -#undef X - - DenoiserBinding_COUNT -}; - static const VkDescriptorSetLayoutBinding bindings[] = { #define BIND_IMAGE(index, name) \ { \ @@ -27,17 +21,20 @@ static const VkDescriptorSetLayoutBinding bindings[] = { .descriptorCount = 1, \ .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, \ }, - LIST_BINDINGS(BIND_IMAGE) + LIST_OUTPUTS(BIND_IMAGE) + LIST_INPUTS(BIND_IMAGE) #undef BIND_IMAGE }; static const int semantics[] = { -#define X(index, name) RayResource_##name, - LIST_BINDINGS(X) -#undef BIND_IMAGE +#define IN(index, name, ...) (RayResource_##name + 1), +#define OUT(index, name, ...) -(RayResource_##name + 1), + LIST_OUTPUTS(OUT) + LIST_INPUTS(IN) +#undef IN +#undef OUT }; - struct ray_pass_s *R_VkRayDenoiserCreate( void ) { const ray_pass_create_compute_t rpcc = { .debug_name = "denoiser", diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 1855e1b6..a05534f4 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -28,17 +28,21 @@ enum { static VkDescriptorSetLayoutBinding bindings[Binding__COUNT]; static const int semantics_poly[Binding__COUNT] = { -#define X(index, name, ...) RayResource_##name, - LIST_COMMON_BINDINGS(X) - RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) -#undef X +#define IN(index, name, ...) (RayResource_##name + 1), +#define OUT(index, name, ...) -(RayResource_##name + 1), + LIST_COMMON_BINDINGS(IN) + RAY_LIGHT_DIRECT_POLY_OUTPUTS(OUT) +#undef IN +#undef OUT }; static const int semantics_point[Binding__COUNT] = { -#define X(index, name, ...) RayResource_##name, - LIST_COMMON_BINDINGS(X) - RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) -#undef X +#define IN(index, name, ...) (RayResource_##name + 1), +#define OUT(index, name, ...) -(RayResource_##name + 1), + LIST_COMMON_BINDINGS(IN) + RAY_LIGHT_DIRECT_POINT_OUTPUTS(OUT) +#undef IN +#undef OUT }; static void initDescriptors( void ) { diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index 5e8e4ecf..13c34209 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -22,10 +22,12 @@ enum { static VkDescriptorSetLayoutBinding bindings[RtPrim_Desc_COUNT]; static const int semantics[RtPrim_Desc_COUNT] = { -#define X(index, name, ...) RayResource_##name, - LIST_COMMON_BINDINGS(X) - RAY_PRIMARY_OUTPUTS(X) -#undef X +#define IN(index, name, ...) (RayResource_##name + 1), +#define OUT(index, name, ...) -(RayResource_##name + 1), + LIST_COMMON_BINDINGS(IN) + RAY_PRIMARY_OUTPUTS(OUT) +#undef IN +#undef OUT }; static void initDescriptors( void ) { diff --git a/ref_vk/vk_ray_resources.h b/ref_vk/vk_ray_resources.h index 065e8dfd..19bb968b 100644 --- a/ref_vk/vk_ray_resources.h +++ b/ref_vk/vk_ray_resources.h @@ -2,6 +2,7 @@ #include "vk_rtx.h" #include "vk_const.h" +#include "vk_image.h" #include "vk_descriptor.h" #include "shaders/ray_interop.h" @@ -27,23 +28,33 @@ enum { RayResource__COUNT }; - /* TODO typedef struct { - struct { - VkAccessFlags access_mask; - VkImageLayout image_layout; - VkPipelineStageFlagBits pipelines; - } state; + VkAccessFlags access_mask; + VkImageLayout image_layout; + VkPipelineStageFlagBits pipelines; +} ray_resource_state_t; +typedef struct { + VkDescriptorType type; + ray_resource_state_t write, read; union { - VkAccelerationStructureKHR tlas; - vk_buffer_region_t buffer; - VkImageView image; - } value; + vk_descriptor_value_t value; + const xvk_image_t *image; + }; } ray_resource_t; -*/ typedef struct vk_ray_resources_s { uint32_t width, height; - vk_descriptor_value_t values[RayResource__COUNT]; + ray_resource_t resources[RayResource__COUNT]; } vk_ray_resources_t; + +typedef struct { + vk_ray_resources_t *resources; + const int *indices; + int count; + VkPipelineStageFlagBits dest_pipeline; + + vk_descriptor_value_t *out_values; +} ray_resources_fill_t; + +void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 16b4c804..7a72e3ff 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -980,45 +980,57 @@ static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int fr } static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_args_t* args, int frame_index, const xvk_ray_frame_images_t *current_frame, float fov_angle_y) { - const vk_ray_resources_t res = { + vk_ray_resources_t res = { .width = FRAME_WIDTH, .height = FRAME_HEIGHT, - .values = { + .resources = { [RayResource_tlas] = { - .accel = (VkWriteDescriptorSetAccelerationStructureKHR){ + .type = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, + .value.accel = (VkWriteDescriptorSetAccelerationStructureKHR){ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, .accelerationStructureCount = 1, .pAccelerationStructures = &g_rtx.tlas, .pNext = NULL, }, }, -#define RES_SET_BUFFER(name, source_, offset_, size_) \ - [RayResource_##name] = (VkDescriptorBufferInfo) { \ - .buffer = source_.buffer, \ - .offset = (offset_), \ - .range = (size_), \ - } -#define RES_SET_BUFFER_FULL(name, source_) RES_SET_BUFFER(name, source_, 0, source_.size) - RES_SET_BUFFER(ubo, g_rtx.uniform_buffer, frame_index * g_rtx.uniform_unit_size, sizeof(struct UniformBuffer)), - RES_SET_BUFFER_FULL(kusochki, g_ray_model_state.kusochki_buffer), - RES_SET_BUFFER_FULL(indices, args->geometry_data), - RES_SET_BUFFER_FULL(vertices, args->geometry_data), - RES_SET_BUFFER_FULL(lights, g_ray_model_state.lights_buffer), - RES_SET_BUFFER_FULL(light_clusters, g_rtx.light_grid_buffer), -#undef RES_SET_BUFFER_FULL +#define RES_SET_BUFFER(name, type_, source_, offset_, size_) \ + [RayResource_##name] = { \ + .type = type_, \ + .value.buffer = (VkDescriptorBufferInfo) { \ + .buffer = source_.buffer, \ + .offset = (offset_), \ + .range = (size_), \ + } \ + } + RES_SET_BUFFER(ubo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, g_rtx.uniform_buffer, frame_index * g_rtx.uniform_unit_size, sizeof(struct UniformBuffer)), + +#define RES_SET_SBUFFER_FULL(name, source_) \ + RES_SET_BUFFER(name, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, source_, 0, source_.size) + RES_SET_SBUFFER_FULL(kusochki, g_ray_model_state.kusochki_buffer), + RES_SET_SBUFFER_FULL(indices, args->geometry_data), + RES_SET_SBUFFER_FULL(vertices, args->geometry_data), + RES_SET_SBUFFER_FULL(lights, g_ray_model_state.lights_buffer), + RES_SET_SBUFFER_FULL(light_clusters, g_rtx.light_grid_buffer), +#undef RES_SET_SBUFFER_FULL #undef RES_SET_BUFFER - [RayResource_all_textures].image_array = tglob.dii_all_textures, -#define X(index, name, ...) \ - [RayResource_##name].image = (VkDescriptorImageInfo) { \ - .sampler = VK_NULL_HANDLE, \ - .imageView = current_frame->name.view, \ - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, \ - }, - RAY_PRIMARY_OUTPUTS(X) - RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) - RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) - X(-1, denoised) -#undef X + + [RayResource_all_textures] = { + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .value.image_array = tglob.dii_all_textures, + }, + +#define RES_SET_IMAGE(index, name, ...) \ + [RayResource_##name] = { \ + .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, \ + .write = {0}, \ + .read = {0}, \ + .image = ¤t_frame->name, \ + }, + RAY_PRIMARY_OUTPUTS(RES_SET_IMAGE) + RAY_LIGHT_DIRECT_POLY_OUTPUTS(RES_SET_IMAGE) + RAY_LIGHT_DIRECT_POINT_OUTPUTS(RES_SET_IMAGE) + RES_SET_IMAGE(-1, denoised) +#undef RES_SET_IMAGE }, }; @@ -1029,41 +1041,6 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar prepareUniformBuffer(args, frame_index, fov_angle_y); //updateDescriptors(args, frame_index, current_frame); -#define LIST_GBUFFER_IMAGES(X) \ - X(0, diffuse_gi) \ - X(0, specular) \ - X(0, additive) \ - -#define IMAGE_BARRIER(img, src_access, dst_access, old_layout, new_layout) { \ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, \ - .image = current_frame->img.image, \ - .srcAccessMask = src_access, \ - .dstAccessMask = dst_access, \ - .oldLayout = old_layout, \ - .newLayout = new_layout, \ - .subresourceRange = (VkImageSubresourceRange) { \ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, \ - .baseMipLevel = 0, \ - .levelCount = 1, \ - .baseArrayLayer = 0, \ - .layerCount = 1, \ - }, \ - }, - -#define IMAGE_BARRIER_READ(index, img, ...) \ - IMAGE_BARRIER(img, \ - VK_ACCESS_SHADER_WRITE_BIT, \ - VK_ACCESS_SHADER_READ_BIT, \ - VK_IMAGE_LAYOUT_GENERAL, \ - VK_IMAGE_LAYOUT_GENERAL) - -#define IMAGE_BARRIER_WRITE(index, img, ...) \ - IMAGE_BARRIER(img, \ - 0, \ - VK_ACCESS_SHADER_WRITE_BIT, \ - VK_IMAGE_LAYOUT_UNDEFINED, \ - VK_IMAGE_LAYOUT_GENERAL) - // 4. Barrier for TLAS build and dest image layout transfer { VkBufferMemoryBarrier bmb[] = { { @@ -1074,51 +1051,16 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar .offset = 0, .size = VK_WHOLE_SIZE, } }; - VkImageMemoryBarrier image_barrier[] = { - LIST_GBUFFER_IMAGES(IMAGE_BARRIER_WRITE) // TODO this is not true lol - RAY_PRIMARY_OUTPUTS(IMAGE_BARRIER_WRITE) - }; - vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, - 0, 0, NULL, ARRAYSIZE(bmb), bmb, ARRAYSIZE(image_barrier), image_barrier); + 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } RayPassPerform( cmdbuf, g_rtx.pass.primary_ray, &res ); - - { - const VkImageMemoryBarrier image_barriers[] = { - RAY_PRIMARY_OUTPUTS(IMAGE_BARRIER_READ) - RAY_LIGHT_DIRECT_POLY_OUTPUTS(IMAGE_BARRIER_WRITE) - RAY_LIGHT_DIRECT_POINT_OUTPUTS(IMAGE_BARRIER_WRITE) - }; - - vkCmdPipelineBarrier(args->cmdbuf, - VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, - VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - //VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - 0, 0, NULL, 0, NULL, ARRAYSIZE(image_barriers), image_barriers); - } - RayPassPerform( cmdbuf, g_rtx.pass.light_direct_poly, &res ); RayPassPerform( cmdbuf, g_rtx.pass.light_direct_point, &res ); - - { - const VkImageMemoryBarrier image_barriers[] = { - RAY_LIGHT_DIRECT_POLY_OUTPUTS(IMAGE_BARRIER_READ) - RAY_LIGHT_DIRECT_POINT_OUTPUTS(IMAGE_BARRIER_READ) - LIST_GBUFFER_IMAGES(IMAGE_BARRIER_READ) - IMAGE_BARRIER_WRITE(-1/*unused*/, denoised) - }; - vkCmdPipelineBarrier(args->cmdbuf, - //VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - 0, 0, NULL, 0, NULL, ARRAYSIZE(image_barriers), image_barriers); - } - - RayPassPerform(cmdbuf, g_rtx.pass.denoiser, &res); + RayPassPerform( cmdbuf, g_rtx.pass.denoiser, &res ); { const xvk_blit_args blit_args = { From 6b381c8970c3be3d86aaa420be65db53226669cd Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Mon, 31 Jan 2022 20:50:39 -0800 Subject: [PATCH 137/548] rt: flatten pass bindings a bit --- ref_vk/ray_pass.c | 2 +- ref_vk/ray_resources.c | 2 +- ref_vk/ray_resources.h | 17 +++++----- ref_vk/vk_denoiser.c | 2 +- ref_vk/vk_ray_light_direct.c | 51 +++++++++++++----------------- ref_vk/vk_ray_primary.c | 43 ++++++++++---------------- ref_vk/vk_ray_resources.h | 60 ------------------------------------ ref_vk/vk_rtx.c | 2 +- 8 files changed, 50 insertions(+), 129 deletions(-) delete mode 100644 ref_vk/vk_ray_resources.h diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c index 88d523ac..dfef38af 100644 --- a/ref_vk/ray_pass.c +++ b/ref_vk/ray_pass.c @@ -1,7 +1,7 @@ #include "ray_pass.h" +#include "ray_resources.h" #include "vk_pipeline.h" #include "vk_descriptor.h" -#include "vk_ray_resources.h" #define MAX_STAGES 16 #define MAX_MISS_GROUPS 8 diff --git a/ref_vk/ray_resources.c b/ref_vk/ray_resources.c index db997b20..c547e18d 100644 --- a/ref_vk/ray_resources.c +++ b/ref_vk/ray_resources.c @@ -1,4 +1,4 @@ -#include "vk_ray_resources.h" +#include "ray_resources.h" #include "vk_core.h" #include diff --git a/ref_vk/ray_resources.h b/ref_vk/ray_resources.h index 6588da68..19bb968b 100644 --- a/ref_vk/ray_resources.h +++ b/ref_vk/ray_resources.h @@ -2,6 +2,7 @@ #include "vk_rtx.h" #include "vk_const.h" +#include "vk_image.h" #include "vk_descriptor.h" #include "shaders/ray_interop.h" @@ -36,19 +37,15 @@ typedef struct { typedef struct { VkDescriptorType type; ray_resource_state_t write, read; - vk_descriptor_value_t value; + union { + vk_descriptor_value_t value; + const xvk_image_t *image; + }; } ray_resource_t; -#define RAY_RESOURCE_DEFAULT_STATE \ - (ray_resource_state_t) { \ - .access_mask = 0, \ - .image_layout = VK_IMAGE_LAYOUT_UNDEFINED, \ - .pipelines = 0, \ - } - typedef struct vk_ray_resources_s { uint32_t width, height; - ray_resource_t values[RayResource__COUNT]; + ray_resource_t resources[RayResource__COUNT]; } vk_ray_resources_t; typedef struct { @@ -60,4 +57,4 @@ typedef struct { vk_descriptor_value_t *out_values; } ray_resources_fill_t; -void RayResourcesFill(ray_resources_fill_t fill); +void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill); diff --git a/ref_vk/vk_denoiser.c b/ref_vk/vk_denoiser.c index a49ea6de..44c4f6bf 100644 --- a/ref_vk/vk_denoiser.c +++ b/ref_vk/vk_denoiser.c @@ -1,6 +1,6 @@ #include "vk_denoiser.h" -#include "vk_ray_resources.h" +#include "ray_resources.h" #include "ray_pass.h" #define LIST_OUTPUTS(X) \ diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index a05534f4..dd7e138f 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -1,6 +1,6 @@ #include "vk_ray_light_direct.h" -#include "vk_ray_resources.h" +#include "ray_resources.h" #include "ray_pass.h" #define LIST_SCENE_BINDINGS(X) \ @@ -17,17 +17,29 @@ LIST_SCENE_BINDINGS(X) \ RAY_LIGHT_DIRECT_INPUTS(X) -enum { -#define X(index, name, ...) Binding_##name, - LIST_COMMON_BINDINGS(X) +// FIXME more conservative shader stages +#define INIT_BINDING(index, name, type, count) \ + { \ + .binding = index, \ + .descriptorType = type, \ + .descriptorCount = count, \ + .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, \ + }, + +#define INIT_IMAGE(index, name, ...) INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1) + +static const VkDescriptorSetLayoutBinding bindings[] = { + LIST_SCENE_BINDINGS(INIT_BINDING) + RAY_LIGHT_DIRECT_INPUTS(INIT_IMAGE) + // FIXME it's an artifact that point and poly outputs have same bindings indices - RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) -#undef X - Binding__COUNT + RAY_LIGHT_DIRECT_POLY_OUTPUTS(INIT_IMAGE) }; -static VkDescriptorSetLayoutBinding bindings[Binding__COUNT]; -static const int semantics_poly[Binding__COUNT] = { +#undef INIT_IMAGE +#undef INIT_BINDING + +static const int semantics_poly[] = { #define IN(index, name, ...) (RayResource_##name + 1), #define OUT(index, name, ...) -(RayResource_##name + 1), LIST_COMMON_BINDINGS(IN) @@ -36,7 +48,7 @@ static const int semantics_poly[Binding__COUNT] = { #undef OUT }; -static const int semantics_point[Binding__COUNT] = { +static const int semantics_point[] = { #define IN(index, name, ...) (RayResource_##name + 1), #define OUT(index, name, ...) -(RayResource_##name + 1), LIST_COMMON_BINDINGS(IN) @@ -45,23 +57,6 @@ static const int semantics_point[Binding__COUNT] = { #undef OUT }; -static void initDescriptors( void ) { - // FIXME more conservative shader stages -#define INIT_BINDING(index, name, type, count) \ - bindings[Binding_##name] = (VkDescriptorSetLayoutBinding){ \ - .binding = index, \ - .descriptorType = type, \ - .descriptorCount = count, \ - .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, \ - }; - LIST_SCENE_BINDINGS(INIT_BINDING) -#define X(index, name, ...) INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1); - RAY_LIGHT_DIRECT_INPUTS(X) - RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) -#undef X -#undef INIT_BINDING -} - struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { // FIXME move this into vk_pipeline const struct SpecializationData { @@ -105,7 +100,6 @@ struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { .specialization = &spec, }; - initDescriptors(); return RayPassCreateTracing( &rpc ); } @@ -152,6 +146,5 @@ struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ) { .specialization = &spec, }; - initDescriptors(); return RayPassCreateTracing( &rpc ); } diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index 13c34209..bfa96f56 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -1,6 +1,6 @@ #include "vk_ray_primary.h" -#include "vk_ray_resources.h" +#include "ray_resources.h" #include "ray_pass.h" #define LIST_COMMON_BINDINGS(X) \ @@ -11,17 +11,25 @@ X(5, vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR) \ X(6, all_textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR) \ -enum { -#define X(index, name, ...) RtPrim_Desc_##name, - LIST_COMMON_BINDINGS(X) - RAY_PRIMARY_OUTPUTS(X) -#undef X +static const VkDescriptorSetLayoutBinding bindings[] = { +#define INIT_BINDING(index, name, type, count, stages) \ + { \ + .binding = index, \ + .descriptorType = type, \ + .descriptorCount = count, \ + .stageFlags = stages, \ + }, +#define INIT_IMAGE(index, name, ...) \ + INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR) - RtPrim_Desc_COUNT + LIST_COMMON_BINDINGS(INIT_BINDING) + RAY_PRIMARY_OUTPUTS(INIT_IMAGE) + +#undef INIT_IMAGE +#undef INIT_BINDING }; -static VkDescriptorSetLayoutBinding bindings[RtPrim_Desc_COUNT]; -static const int semantics[RtPrim_Desc_COUNT] = { +static const int semantics[] = { #define IN(index, name, ...) (RayResource_##name + 1), #define OUT(index, name, ...) -(RayResource_##name + 1), LIST_COMMON_BINDINGS(IN) @@ -30,22 +38,6 @@ static const int semantics[RtPrim_Desc_COUNT] = { #undef OUT }; -static void initDescriptors( void ) { -#define INIT_BINDING(index, name, type, count, stages) \ - bindings[RtPrim_Desc_##name] = (VkDescriptorSetLayoutBinding){ \ - .binding = index, \ - .descriptorType = type, \ - .descriptorCount = count, \ - .stageFlags = stages, \ - }; - LIST_COMMON_BINDINGS(INIT_BINDING) -#define X(index, name, ...) \ - INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); - RAY_PRIMARY_OUTPUTS(X) -#undef X -#undef INIT_BINDING -} - struct ray_pass_s *R_VkRayPrimaryPassCreate( void ) { // FIXME move this into vk_pipeline or something const struct SpecializationData { @@ -92,6 +84,5 @@ struct ray_pass_s *R_VkRayPrimaryPassCreate( void ) { .specialization = &spec, }; - initDescriptors(); return RayPassCreateTracing( &rpc ); } diff --git a/ref_vk/vk_ray_resources.h b/ref_vk/vk_ray_resources.h deleted file mode 100644 index 19bb968b..00000000 --- a/ref_vk/vk_ray_resources.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include "vk_rtx.h" -#include "vk_const.h" -#include "vk_image.h" -#include "vk_descriptor.h" - -#include "shaders/ray_interop.h" - -#define RAY_SCENE_RESOURCES(X) \ - X(TLAS, tlas) \ - X(Buffer, ubo) \ - X(Buffer, kusochki) \ - X(Buffer, indices) \ - X(Buffer, vertices) \ - X(Buffer, lights) \ - X(Buffer, light_clusters) \ - X(Texture, all_textures) \ - -enum { -#define X(type, name, ...) RayResource_##name, - RAY_SCENE_RESOURCES(X) - RAY_PRIMARY_OUTPUTS(X) - RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) - RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) - X(-1, denoised) -#undef X - RayResource__COUNT -}; - -typedef struct { - VkAccessFlags access_mask; - VkImageLayout image_layout; - VkPipelineStageFlagBits pipelines; -} ray_resource_state_t; - -typedef struct { - VkDescriptorType type; - ray_resource_state_t write, read; - union { - vk_descriptor_value_t value; - const xvk_image_t *image; - }; -} ray_resource_t; - -typedef struct vk_ray_resources_s { - uint32_t width, height; - ray_resource_t resources[RayResource__COUNT]; -} vk_ray_resources_t; - -typedef struct { - vk_ray_resources_t *resources; - const int *indices; - int count; - VkPipelineStageFlagBits dest_pipeline; - - vk_descriptor_value_t *out_values; -} ray_resources_fill_t; - -void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 7a72e3ff..0b251f1b 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -1,7 +1,7 @@ #include "vk_rtx.h" #include "ray_pass.h" -#include "vk_ray_resources.h" +#include "ray_resources.h" #include "vk_ray_primary.h" #include "vk_ray_light_direct.h" From 51635995a2a1219e89367d97668787064be1f1ca Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 1 Feb 2022 19:25:49 +0300 Subject: [PATCH 138/548] mainui: update to temporary branch with fixes until I finish picbutton text renderer --- mainui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainui b/mainui index e0b3b91b..da881649 160000 --- a/mainui +++ b/mainui @@ -1 +1 @@ -Subproject commit e0b3b91b6e6a903d21ac934d8ff418b41c5b6555 +Subproject commit da881649a358981be75396b2403bd79580ce4706 From 0c26c24331b42ed86b7f7aafbfb8c038e5964a71 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 1 Feb 2022 19:27:46 +0300 Subject: [PATCH 139/548] ref_gl: really disable VBO by default for now --- ref_gl/gl_opengl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_gl/gl_opengl.c b/ref_gl/gl_opengl.c index bdf11d5f..34afb7dd 100644 --- a/ref_gl/gl_opengl.c +++ b/ref_gl/gl_opengl.c @@ -874,7 +874,7 @@ register VBO cvars and get default value */ static void R_CheckVBO( void ) { - const char *def = "1"; + const char *def = "0"; const char *dlightmode = "1"; int flags = FCVAR_ARCHIVE; qboolean disable = false; From 6dbb694fdc4847bd9acfc5dbb284f627f2871128 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 1 Feb 2022 19:42:38 +0300 Subject: [PATCH 140/548] wscript: define _FILE_OFFSET_BITS=64 on 32-bit systems if libc (presumably glibc) supports it --- wscript | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/wscript b/wscript index 8879fbeb..2254efa4 100644 --- a/wscript +++ b/wscript @@ -298,6 +298,19 @@ def configure(conf): else: conf.undefine('HAVE_TGMATH_H') + # set _FILE_OFFSET_BITS=64 for filesystems with 64-bit inodes + if conf.env.DEST_OS != 'win32' and conf.env.DEST_SIZEOF_VOID_P == 4: + file_offset_bits_usable = conf.check_cc(fragment='''#define _FILE_OFFSET_BITS 64 + #include + #ifndef __USE_FILE_OFFSET64 + #error + #endif + int main(void){ return 0; }''', + msg='Checking if _FILE_OFFSET_BITS can be defined to 64', mandatory=False) + if file_offset_bits_usable: + conf.define('_FILE_OFFSET_BITS', 64) + else: conf.undefine('_FILE_OFFSET_BITS') + # check if we can use alloca.h or malloc.h if conf.check_cc(header_name='alloca.h', mandatory=False): conf.define('ALLOCA_H', 'alloca.h') From f9bf0aadd0e53ee578580b50e57f3b46825c8e67 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Wed, 2 Feb 2022 19:04:45 -0800 Subject: [PATCH 141/548] rt: add emissive color --- ref_vk/shaders/denoiser.comp | 2 ++ ref_vk/shaders/ray_interop.h | 1 + ref_vk/shaders/ray_primary.rchit | 9 +++++++++ ref_vk/shaders/ray_primary.rgen | 7 +++++++ ref_vk/shaders/ray_primary_common.glsl | 1 + ref_vk/vk_denoiser.c | 1 + 6 files changed, 21 insertions(+) diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 3aaffc79..1812a143 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -14,6 +14,7 @@ layout(set = 0, binding = 3, rgba16f) uniform readonly image2D src_light_direct_ layout(set = 0, binding = 4, rgba16f) uniform readonly image2D src_light_direct_point_diffuse; layout(set = 0, binding = 5, rgba16f) uniform readonly image2D src_light_direct_point_specular; +layout(set = 0, binding = 6, rgba16f) uniform readonly image2D src_emissive; //layout(set = 0, binding = 2, rgba16f) uniform readonly image2D src_light_direct_poly_diffuse; /* layout(set = 0, binding = 3, rgba16f) uniform readonly image2D src_specular; */ @@ -102,6 +103,7 @@ void main() { colour += imageLoad(src_light_direct_point_diffuse, pix).rgb; colour += imageLoad(src_light_direct_point_specular, pix).rgb; colour *= base_color.rgb; + colour += imageLoad(src_emissive, pix).rgb; #else float total_scale = 0.; vec3 colour = vec3(0.); diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index f7a5e27b..c8c1fdab 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -17,6 +17,7 @@ X(11, position_t, rgba32f) \ X(12, normals_gs, rgba16f) \ X(13, material_rmxx, rgba8) \ + X(14, emissive, rgba16f) \ #define RAY_LIGHT_DIRECT_INPUTS(X) \ X(10, position_t, rgba32f) \ diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index 292011a1..b8b0ea20 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -49,4 +49,13 @@ void main() { payload.normals_gs.xy = normalEncode(geom.normal_geometry); payload.normals_gs.zw = normalEncode(geom.normal_shading); + +#if 1 + // Real correct emissive color + payload.emissive.rgb = kusok.emissive; +#else + // Fake texture color + if (any(greaterThan(kusok.emissive, vec3(0.)))) + payload.emissive.rgb = payload.base_color_a.rgb; +#endif } diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index 2ae51b47..912f899e 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -21,6 +21,12 @@ void main() { //vec3 direction = (ubo.inv_view * vec4(normalize(target.xyz), 0)).xyz; const vec3 direction = normalize((ubo.inv_view * vec4(target.xyz, 0)).xyz); + payload.hit_t = vec4(0.); + payload.base_color_a = vec4(0.); + payload.normals_gs = vec4(0.); + payload.material_rmxx = vec4(0.); + payload.emissive = vec4(0.); + const uint flags = gl_RayFlagsCullFrontFacingTrianglesEXT; const uint sbt_offset = 0; const uint sbt_stride = 0; @@ -34,4 +40,5 @@ void main() { imageStore(out_image_base_color_a, ivec2(gl_LaunchIDEXT.xy), payload.base_color_a); imageStore(out_image_normals_gs, ivec2(gl_LaunchIDEXT.xy), payload.normals_gs); imageStore(out_image_material_rmxx, ivec2(gl_LaunchIDEXT.xy), payload.material_rmxx); + imageStore(out_image_emissive, ivec2(gl_LaunchIDEXT.xy), payload.emissive); } diff --git a/ref_vk/shaders/ray_primary_common.glsl b/ref_vk/shaders/ray_primary_common.glsl index 44623369..fd593ee0 100644 --- a/ref_vk/shaders/ray_primary_common.glsl +++ b/ref_vk/shaders/ray_primary_common.glsl @@ -9,6 +9,7 @@ struct RayPayloadPrimary { vec4 base_color_a; vec4 normals_gs; vec4 material_rmxx; + vec4 emissive; }; #define PAYLOAD_LOCATION_PRIMARY 0 diff --git a/ref_vk/vk_denoiser.c b/ref_vk/vk_denoiser.c index 44c4f6bf..2d7fea73 100644 --- a/ref_vk/vk_denoiser.c +++ b/ref_vk/vk_denoiser.c @@ -12,6 +12,7 @@ X(3, light_poly_specular) \ X(4, light_point_diffuse) \ X(5, light_point_specular) \ + X(6, emissive) \ static const VkDescriptorSetLayoutBinding bindings[] = { #define BIND_IMAGE(index, name) \ From 3a6bed136aae774c955e193b7902758f25427ad2 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Wed, 2 Feb 2022 21:32:10 -0800 Subject: [PATCH 142/548] rt: add moving polygon lights support --- ref_vk/shaders/light_polygon.glsl | 2 +- ref_vk/vk_brush.c | 63 +++++++++++++++++++++++++++---- ref_vk/vk_brush.h | 7 +--- ref_vk/vk_light.c | 50 +++++++++++++----------- ref_vk/vk_light.h | 3 ++ ref_vk/vk_scene.c | 4 +- 6 files changed, 91 insertions(+), 38 deletions(-) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index bbb9f56f..4c6980b9 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -283,7 +283,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate #elif DO_ALL_IN_CLUSTER const SampleContext ctx = buildSampleContext(P, N, view_dir); - const uint num_polygons = min(128, uint(light_grid.clusters[cluster_index].num_polygons)); + const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); for (uint i = 0; i < num_polygons; ++i) { const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); const PolygonLight poly = lights.polygons[index]; diff --git a/ref_vk/vk_brush.c b/ref_vk/vk_brush.c index 987f4727..b65ad61e 100644 --- a/ref_vk/vk_brush.c +++ b/ref_vk/vk_brush.c @@ -19,6 +19,14 @@ #include #include +typedef struct vk_brush_model_s { + vk_render_model_t render_model; + int num_water_surfaces; + + rt_light_add_polygon_t *polylights; + int polylights_count; +} vk_brush_model_t; + static struct { struct { int num_vertices, num_indices; @@ -326,7 +334,7 @@ const texture_t *R_TextureAnimation( const cl_entity_t *ent, const msurface_t *s return base; } -void VK_BrushModelDraw( const cl_entity_t *ent, int render_mode ) +void VK_BrushModelDraw( const cl_entity_t *ent, int render_mode, const matrix4x4 model ) { // Expect all buffers to be bound const model_t *mod = ent->model; @@ -344,6 +352,13 @@ void VK_BrushModelDraw( const cl_entity_t *ent, int render_mode ) if (bmodel->render_model.num_geometries == 0) return; + for (int i = 0; i < bmodel->polylights_count; ++i) { + rt_light_add_polygon_t *polylight = bmodel->polylights + i; + polylight->transform_row = (const matrix3x4*)model; + polylight->dynamic = true; + RT_LightAddPolygon(polylight); + } + for (int i = 0; i < bmodel->render_model.num_geometries; ++i) { vk_render_geometry_t *geom = bmodel->render_model.geometries + i; const int surface_index = geom->surf - mod->surfaces; @@ -413,6 +428,7 @@ typedef struct { int max_texture_id; int water_surfaces; //int sky_surfaces; + int emissive_surfaces; } model_sizes_t; static model_sizes_t computeSizes( const model_t *mod ) { @@ -422,6 +438,7 @@ static model_sizes_t computeSizes( const model_t *mod ) { { const int surface_index = mod->firstmodelsurface + i; const msurface_t *surf = mod->surfaces + surface_index; + const int tex_id = surf->texinfo->texture->gl_texturenum; sizes.water_surfaces += !!(surf->flags & (SURF_DRAWTURB | SURF_DRAWTURB_QUADS)); @@ -431,15 +448,22 @@ static model_sizes_t computeSizes( const model_t *mod ) { ++sizes.num_surfaces; sizes.num_vertices += surf->numedges; sizes.num_indices += 3 * (surf->numedges - 1); - if (surf->texinfo->texture->gl_texturenum > sizes.max_texture_id) - sizes.max_texture_id = surf->texinfo->texture->gl_texturenum; + if (tex_id > sizes.max_texture_id) + sizes.max_texture_id = tex_id; + + { + const xvk_patch_surface_t *const psurf = g_map_entities.patch.surfaces ? g_map_entities.patch.surfaces + surface_index : NULL; + vec3_t emissive; + if ((psurf && (psurf->flags & Patch_Surface_Emissive)) || (RT_GetEmissiveForTexture(emissive, tex_id))) + ++sizes.emissive_surfaces; + } } return sizes; } -static void loadEmissiveSurface(const model_t *mod, const int surface_index, const msurface_t *surf, const vec3_t emissive) { - rt_light_add_polygon_t lpoly; +static rt_light_add_polygon_t loadPolyLight(const model_t *mod, const int surface_index, const msurface_t *surf, const vec3_t emissive) { + rt_light_add_polygon_t lpoly = {0}; lpoly.num_vertices = Q_min(7, surf->numedges); // TODO split, don't clip @@ -456,7 +480,7 @@ static void loadEmissiveSurface(const model_t *mod, const int surface_index, con } lpoly.surface = surf; - RT_LightAddPolygon(&lpoly); + return lpoly; } static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { @@ -504,11 +528,25 @@ static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { // FIXME move this to rt_light_bsp and static loading { + qboolean is_emissive = false; vec3_t emissive; + rt_light_add_polygon_t polylight; + if (psurf && (psurf->flags & Patch_Surface_Emissive)) { - loadEmissiveSurface(mod, surface_index, surf, psurf->emissive); + is_emissive = true; + VectorCopy(psurf->emissive, emissive); } else if (RT_GetEmissiveForTexture(emissive, tex_id)) { - loadEmissiveSurface(mod, surface_index, surf, emissive); + is_emissive = true; + } + + if (is_emissive) { + if (bmodel->polylights) { + ASSERT(bmodel->polylights_count < sizes.emissive_surfaces); + bmodel->polylights[bmodel->polylights_count++] = loadPolyLight(mod, surface_index, surf, emissive); + } else { + polylight = loadPolyLight(mod, surface_index, surf, emissive); + RT_LightAddPolygon(&polylight); + } } } @@ -607,6 +645,10 @@ static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { XVK_RenderBufferUnlock( index_buffer.buffer ); XVK_RenderBufferUnlock( vertex_buffer.buffer ); + if (bmodel->polylights) { + gEngine.Con_Reportf("WHAT %d %d \n", sizes.emissive_surfaces, bmodel->polylights_count); + ASSERT(sizes.emissive_surfaces == bmodel->polylights_count); + } ASSERT(sizes.num_surfaces == num_geometries); bmodel->render_model.num_geometries = num_geometries; @@ -640,6 +682,9 @@ qboolean VK_BrushModelLoad( model_t *mod, qboolean map ) if (sizes.num_surfaces != 0) { bmodel->render_model.geometries = (vk_render_geometry_t*)((char*)(bmodel + 1)); + if (!map && sizes.emissive_surfaces) + bmodel->polylights = Mem_Malloc(vk_core.pool, sizeof(bmodel->polylights[0]) * sizes.emissive_surfaces); + if (!loadBrushSurfaces(sizes, mod) || !VK_RenderModelInit(&bmodel->render_model)) { gEngine.Con_Printf(S_ERROR "Could not load model %s\n", mod->name); Mem_Free(bmodel); @@ -663,6 +708,8 @@ void VK_BrushModelDestroy( model_t *mod ) { return; VK_RenderModelDestroy(&bmodel->render_model); + if (bmodel->polylights) + Mem_Free(bmodel->polylights); Mem_Free(bmodel); mod->cache.data = NULL; } diff --git a/ref_vk/vk_brush.h b/ref_vk/vk_brush.h index cdc01ca2..f75ce129 100644 --- a/ref_vk/vk_brush.h +++ b/ref_vk/vk_brush.h @@ -8,18 +8,13 @@ struct draw_list_s; struct model_s; struct cl_entity_s; -typedef struct vk_brush_model_s { - vk_render_model_t render_model; - int num_water_surfaces; -} vk_brush_model_t; - qboolean VK_BrushInit( void ); void VK_BrushShutdown( void ); qboolean VK_BrushModelLoad( struct model_s *mod, qboolean map); void VK_BrushModelDestroy( struct model_s *mod ); -void VK_BrushModelDraw( const struct cl_entity_s *ent, int render_mode ); +void VK_BrushModelDraw( const cl_entity_t *ent, int render_mode, const matrix4x4 model ); void VK_BrushStatsClear( void ); const texture_t *R_TextureAnimation( const cl_entity_t *ent, const msurface_t *s, const struct texture_s *base_override ); diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 3d14c410..67420abe 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -1250,6 +1250,7 @@ int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { { rt_light_polygon_t *const poly = g_lights.polygons + g_lights.num_polygons; + vec3_t *vertices = g_lights.polygon_vertices + g_lights.num_polygon_vertices; vec3_t normal; poly->vertices.offset = g_lights.num_polygon_vertices; @@ -1260,13 +1261,16 @@ int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { VectorSet(normal, 0, 0, 0); for (int i = 0; i < addpoly->num_vertices; ++i) { - VectorCopy(addpoly->vertices[i], g_lights.polygon_vertices[poly->vertices.offset + i]); - VectorAdd(addpoly->vertices[i], poly->center, poly->center); + if (addpoly->transform_row) + Matrix3x4_VectorTransform(*addpoly->transform_row, addpoly->vertices[i], vertices[i]); + else + VectorCopy(addpoly->vertices[i], vertices[i]); + VectorAdd(vertices[i], poly->center, poly->center); if (i > 1) { vec3_t e[2], lnormal; - VectorSubtract(addpoly->vertices[i-0], addpoly->vertices[0], e[0]); - VectorSubtract(addpoly->vertices[i-1], addpoly->vertices[0], e[1]); + VectorSubtract(vertices[i-0], vertices[0], e[0]); + VectorSubtract(vertices[i-1], vertices[0], e[1]); CrossProduct(e[0], e[1], lnormal); VectorAdd(lnormal, normal, normal); } @@ -1274,28 +1278,32 @@ int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { poly->area = VectorLength(normal); VectorM(1.f / poly->area, normal, poly->plane); - poly->plane[3] = -DotProduct(addpoly->vertices[0], poly->plane); + poly->plane[3] = -DotProduct(vertices[0], poly->plane); VectorM(1.f / poly->vertices.count, poly->center, poly->center); - gEngine.Con_Reportf("added polygon light index=%d color=(%f, %f, %f) center=(%f, %f, %f) plane=(%f, %f, %f, %f) area=%f num_vertices=%d\n", - g_lights.num_polygons, - poly->emissive[0], - poly->emissive[1], - poly->emissive[2], - poly->center[0], - poly->center[1], - poly->center[2], - poly->plane[0], - poly->plane[1], - poly->plane[2], - poly->plane[3], - poly->area, - poly->vertices.count - ); + if (!addpoly->dynamic || debug_dump_lights.enabled) { + gEngine.Con_Reportf("added polygon light index=%d color=(%f, %f, %f) center=(%f, %f, %f) plane=(%f, %f, %f, %f) area=%f num_vertices=%d\n", + g_lights.num_polygons, + poly->emissive[0], + poly->emissive[1], + poly->emissive[2], + poly->center[0], + poly->center[1], + poly->center[2], + poly->plane[0], + poly->plane[1], + poly->plane[2], + poly->plane[3], + poly->area, + poly->vertices.count + ); + } { - const vk_light_leaf_set_t *const leafs = getMapLeafsAffectedByMapSurface(addpoly->surface); + const vk_light_leaf_set_t *const leafs = addpoly->dynamic + ? getMapLeafsAffectedByMovingSurface( addpoly->surface, addpoly->transform_row ) + : getMapLeafsAffectedByMapSurface( addpoly->surface ); addPolygonLeafSetToClusters(leafs, g_lights.num_polygons); } diff --git a/ref_vk/vk_light.h b/ref_vk/vk_light.h index f0509967..648b3564 100644 --- a/ref_vk/vk_light.h +++ b/ref_vk/vk_light.h @@ -122,5 +122,8 @@ typedef struct { // - bsp/xash/rad/patch-specific stuff // - mostly engine-agnostic light clusters const struct msurface_s *surface; + + qboolean dynamic; + const matrix3x4 *transform_row; } rt_light_add_polygon_t; int RT_LightAddPolygon(const rt_light_add_polygon_t *light); diff --git a/ref_vk/vk_scene.c b/ref_vk/vk_scene.c index 9b73e81d..0de1c64e 100644 --- a/ref_vk/vk_scene.c +++ b/ref_vk/vk_scene.c @@ -577,7 +577,7 @@ static void drawEntity( cl_entity_t *ent, int render_mode ) case mod_brush: R_RotateForEntity( model, ent ); VK_RenderStateSetMatrixModel( model ); - VK_BrushModelDraw( ent, render_mode ); + VK_BrushModelDraw( ent, render_mode, model ); break; case mod_studio: @@ -629,7 +629,7 @@ void VK_SceneRender( const ref_viewpass_t *rvp ) { //VK_LightsBakePVL( 0 /* FIXME frame number */); VK_RenderStateSetColor( 1.f, 1.f, 1.f, 1.f); - VK_BrushModelDraw( world, kRenderNormal ); + VK_BrushModelDraw( world, kRenderNormal, NULL ); } } From 4bb44c1569884312bf9d4ba71b6a17976b6217f0 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Wed, 2 Feb 2022 21:47:40 -0800 Subject: [PATCH 143/548] rt: add skybox support --- ref_vk/ray_resources.h | 1 + ref_vk/shaders/ray_primary.rchit | 5 +++-- ref_vk/vk_ray_primary.c | 1 + ref_vk/vk_rtx.c | 9 +++++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ref_vk/ray_resources.h b/ref_vk/ray_resources.h index 19bb968b..dd613f16 100644 --- a/ref_vk/ray_resources.h +++ b/ref_vk/ray_resources.h @@ -16,6 +16,7 @@ X(Buffer, lights) \ X(Buffer, light_clusters) \ X(Texture, all_textures) \ + X(Texture, skybox) \ enum { #define X(type, name, ...) RayResource_##name, diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index b8b0ea20..385f4775 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -10,6 +10,7 @@ layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; +layout(set = 0, binding = 7) uniform samplerCube skybox; layout(location = PAYLOAD_LOCATION_PRIMARY) rayPayloadInEXT RayPayloadPrimary payload; hitAttributeEXT vec2 bary; @@ -29,8 +30,8 @@ void main() { const uint tex_base_color = kusok.tex_base_color; if ((tex_base_color & KUSOK_MATERIAL_FLAG_SKYBOX) != 0) { - // FIXME read skybox - payload.base_color_a = vec4(1.,0.,1.,1.); + payload.emissive.rgb = pow(texture(skybox, gl_WorldRayDirectionEXT).rgb, vec3(2.2)); + return; } else { payload.base_color_a = sampleTexture(tex_base_color, geom.uv, geom.uv_lods) * kusok.color; payload.material_rmxx.r = (kusok.tex_roughness > 0) ? sampleTexture(kusok.tex_roughness, geom.uv, geom.uv_lods).r : kusok.roughness; diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index bfa96f56..ff7a7532 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -10,6 +10,7 @@ X(4, indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR) \ X(5, vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR) \ X(6, all_textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR) \ + X(7, skybox, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR) \ static const VkDescriptorSetLayoutBinding bindings[] = { #define INIT_BINDING(index, name, type, count, stages) \ diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 0b251f1b..79fda9fc 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -1019,6 +1019,15 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar .value.image_array = tglob.dii_all_textures, }, + [RayResource_skybox] = { + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .value.image = { + .sampler = vk_core.default_sampler, + .imageView = tglob.skybox_cube.vk.image.view ? tglob.skybox_cube.vk.image.view : tglob.cubemap_placeholder.vk.image.view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }, + }, + #define RES_SET_IMAGE(index, name, ...) \ [RayResource_##name] = { \ .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, \ From dd1f58d0b77e6a4fb18bf5243b5f4a71f386e0c2 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 4 Feb 2022 18:38:36 -0800 Subject: [PATCH 144/548] rt: simplify polygon light sampling do stupid first triangle sampling, w/ contrib estimation area * dot(LP) / dist^2 4.7ms all poly lights, occupancy 10, vgpr 86(96) --- ref_vk/TODO.md | 17 ++ ref_vk/shaders/denoiser.comp | 69 ++++--- ref_vk/shaders/light_polygon.glsl | 324 +++++++++--------------------- ref_vk/shaders/rt_geometry.glsl | 8 +- ref_vk/shaders/utils.glsl | 12 ++ ref_vk/vk_denoiser.c | 2 + 6 files changed, 171 insertions(+), 261 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index c79df7d8..ada2aa7e 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,3 +1,20 @@ +# Passes +- [ ] better simple sampling + - [ ] all triangles + - [ ] area based on triangles + - [ ] clipping? + - [ ] can we pack polygon lights better? e.g.: + - each light is strictly a triangle + - index is offset into triangles + - layout: + - vec4(plane) // is it really needed? is early culling important? can we shove area into there too? e.g plane_n.xy,plane_d, area + - vec4(v0xyz, e_r) + - vec4(v1xyz, e_g) + - vec4(v2xyz, e_b) +- [ ] additive transparency +- [ ] bounces +- [ ] skybox shadows + # Next - [ ] remove surface visibility cache - [ ] rtx: rename point lights to lampochki diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 1812a143..e35e6518 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -16,6 +16,9 @@ layout(set = 0, binding = 4, rgba16f) uniform readonly image2D src_light_direct_ layout(set = 0, binding = 5, rgba16f) uniform readonly image2D src_light_direct_point_specular; layout(set = 0, binding = 6, rgba16f) uniform readonly image2D src_emissive; +layout(set = 0, binding = 7, rgba32f) uniform readonly image2D src_position_t; +layout(set = 0, binding = 8, rgba16f) uniform readonly image2D src_normals_gs; + //layout(set = 0, binding = 2, rgba16f) uniform readonly image2D src_light_direct_poly_diffuse; /* layout(set = 0, binding = 3, rgba16f) uniform readonly image2D src_specular; */ /* layout(set = 0, binding = 4, rgba16f) uniform readonly image2D src_additive; */ @@ -52,11 +55,11 @@ vec3 reinhard02(vec3 c, vec3 Cwhite2) { float normpdf2(in float x2, in float sigma) { return 0.39894*exp(-0.5*x2/(sigma*sigma))/sigma; } float normpdf(in float x, in float sigma) { return normpdf2(x*x, sigma); } -/* void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { */ -/* const vec4 n = imageLoad(src_normals, uv); */ -/* geometry_normal = normalDecode(n.xy); */ -/* shading_normal = normalDecode(n.zw); */ -/* } */ +void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { + const vec4 n = imageLoad(src_normals_gs, uv); + geometry_normal = normalDecode(n.xy); + shading_normal = normalDecode(n.zw); +} void main() { ivec2 res = ivec2(imageSize(src_base_color)); @@ -74,7 +77,6 @@ void main() { /* imageStore(dest, pix, vec4(sqrt(float(pix.x) / res.x))); return; */ /* } */ - const vec4 base_color = imageLoad(src_base_color, pix); //const float material_index = imageLoad(src_light_direct_poly, pix).a; //imageStore(dest, pix, vec4(aces_tonemap(base_color.rgb), 0.)); return; @@ -87,8 +89,8 @@ void main() { //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_light_direct_poly, pix).rgb), 0.)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_specular, pix).rgb), 0.)); return; - /* vec3 geometry_normal, shading_normal; */ - /* readNormals(pix, geometry_normal, shading_normal); */ + vec3 geometry_normal, shading_normal; + readNormals(pix, geometry_normal, shading_normal); //imageStore(dest, pix, vec4(.5 + geometry_normal * .5, 0.)); return; @@ -102,14 +104,14 @@ void main() { colour += imageLoad(src_light_direct_poly_specular, pix).rgb; colour += imageLoad(src_light_direct_point_diffuse, pix).rgb; colour += imageLoad(src_light_direct_point_specular, pix).rgb; - colour *= base_color.rgb; - colour += imageLoad(src_emissive, pix).rgb; #else float total_scale = 0.; vec3 colour = vec3(0.); - const int KERNEL_SIZE = 16; + const int KERNEL_SIZE = 8; float specular_total_scale = 0.; vec3 speculour = vec3(0.); + + const vec4 center_pos = imageLoad(src_position_t, pix); for (int x = -KERNEL_SIZE; x <= KERNEL_SIZE; ++x) for (int y = -KERNEL_SIZE; y <= KERNEL_SIZE; ++y) { const ivec2 p = pix + ivec2(x, y); @@ -117,6 +119,8 @@ void main() { continue; } + float scale = 1.f; + // const vec4 c = imageLoad(src_light_direct_poly, p); // if (c.a != material_index) // continue; @@ -125,40 +129,53 @@ void main() { readNormals(p, sample_geometry_normal, sample_shading_normal); // FIXME also filter by depth, (kusok index?), etc - if ( dot(sample_geometry_normal, geometry_normal) < .9 ) + //scale *= smoothstep(.9, 1., dot(sample_geometry_normal, geometry_normal)); + + const vec4 sample_pos = imageLoad(src_position_t, p); + scale *= smoothstep(4. * center_pos.w / 100., 0., distance(center_pos.xyz, sample_pos.xyz)); + + if ( scale <= 0. ) continue; - // TODO bilaterally filter shading normals + vec3 diffuse = vec3(0.); + diffuse += imageLoad(src_light_direct_point_diffuse, p).rgb; + diffuse += imageLoad(src_light_direct_poly_diffuse, p).rgb; - const float sigma = KERNEL_SIZE / 2.; - const float scale = normpdf(x, sigma) * normpdf(y, sigma); - colour += scale * imageLoad(src_light_direct_poly, p).rgb; - total_scale += scale; + vec3 specular = vec3(0.); + specular += imageLoad(src_light_direct_poly_specular, p).rgb; + specular += imageLoad(src_light_direct_point_specular, p).rgb; - const int SPECULAR_KERNEL_SIZE = 2; + { + const float sigma = KERNEL_SIZE / 2.; + const float dscale = scale * normpdf(x, sigma) * normpdf(y, sigma); + colour += dscale * diffuse; + total_scale += dscale; + } + + const int SPECULAR_KERNEL_SIZE = 4; if (all(lessThan(abs(ivec2(x, y)), ivec2(SPECULAR_KERNEL_SIZE)))) { const float spigma = SPECULAR_KERNEL_SIZE / 2.; - const float specuale = normpdf(x, spigma) * normpdf(y, spigma); - speculour += specuale * imageLoad(src_specular, p).rgb; + const float specuale = scale * normpdf(x, spigma) * normpdf(y, spigma); + speculour += specuale * specular; specular_total_scale += specuale; } } if (total_scale > 0.) { colour /= total_scale; - colour *= base_color.rgb; } if (specular_total_scale > 0.) { speculour /= specular_total_scale; - //speculour *= base_color.rgb; - //colour += speculour; + colour += speculour; } - - //colour += imageLoad(src_specular, pix).rgb; - //colour += imageLoad(src_additive, pix).rgb; #endif + const vec4 base_color = imageLoad(src_base_color, pix); + colour *= base_color.rgb; + colour += imageLoad(src_emissive, pix).rgb; + //colour += imageLoad(src_additive, pix).rgb; + // HACK: exposure // TODO: should be dynamic based on previous frames brightness #if 0 diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 4c6980b9..75c3dbd8 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -4,6 +4,7 @@ #include "peters2021-sampling/polygon_sampling.glsl" #include "noise.glsl" +#include "utils.glsl" struct SampleContext { mat4x3 world_to_shading; @@ -19,19 +20,29 @@ SampleContext buildSampleContext(vec3 position, vec3 normal, vec3 view_dir) { return ctx; } -#if 0 -#define SAMPLE_TYPE_T projected_solid_angle_polygon_t -#define SAMPLE_PREPARE_FUNC(vertex_count, vertices) prepare_projected_solid_angle_polygon_sampling(vertex_count, vertices) -#define SAMPLE_FUNC sample_projected_solid_angle_polygon -#define SAMPLE_CONTRIB(sap) sap.projected_solid_angle -#else -#define SAMPLE_TYPE_T solid_angle_polygon_t -#define SAMPLE_PREPARE_FUNC(vertex_count, vertices) prepare_solid_angle_polygon_sampling(vertex_count, vertices, vec3(0.)) -#define SAMPLE_FUNC sample_solid_angle_polygon -#define SAMPLE_CONTRIB(sap) sap.solid_angle -#endif +vec4 getPolygonLightSampleSimple(vec3 P, vec3 view_dir, const PolygonLight poly) { + const uint vertices_offset = poly.vertices_count_offset & 0xffffu; + uint vertices_count = poly.vertices_count_offset >> 16; -vec4 getPolygonLightSampleProjected(vec3 P, vec3 N, vec3 view_dir, SampleContext ctx, const PolygonLight poly) { + vec3 v[3]; + vertices_count = 3; // FIXME + + for (uint i = 0; i < vertices_count; ++i) { + v[i] = lights.polygon_vertices[vertices_offset + i].xyz; + } + + vec2 rnd = vec2(sqrt(rand01()), rand01()); + rnd.y *= rnd.x; + rnd.x = 1.f - rnd.x; + + const vec3 light_dir = baryMix(v[0], v[1], v[2], rnd) - P; + const vec3 light_dir_n = normalize(light_dir); + float contrib = - poly.area * dot(light_dir_n, poly.plane.xyz ) / dot(light_dir, light_dir); + + return vec4(light_dir_n, contrib); +} + +vec4 getPolygonLightSampleProjected(vec3 view_dir, SampleContext ctx, const PolygonLight poly) { vec3 clipped[MAX_POLYGON_VERTEX_COUNT]; const uint vertices_offset = poly.vertices_count_offset & 0xffffu; @@ -56,7 +67,7 @@ vec4 getPolygonLightSampleProjected(vec3 P, vec3 N, vec3 view_dir, SampleContext return vec4(light_dir, contrib); } -vec4 getPolygonLightSampleSolid(vec3 P, vec3 N, vec3 view_dir, SampleContext ctx, const PolygonLight poly) { +vec4 getPolygonLightSampleSolid(vec3 P, vec3 view_dir, SampleContext ctx, const PolygonLight poly) { vec3 clipped[MAX_POLYGON_VERTEX_COUNT]; const uint vertices_offset = poly.vertices_count_offset & 0xffffu; @@ -84,236 +95,81 @@ vec4 getPolygonLightSampleSolid(vec3 P, vec3 N, vec3 view_dir, SampleContext ctx return vec4(light_dir, contrib); } -#if 0 -void sampleEmissiveSurface(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, SampleContext ctx, uint ekusok_index, out vec3 out_diffuse, out vec3 out_specular) { - out_diffuse = out_specular = vec3(0.); - - const EmissiveKusok ek = lights.kusochki[ekusok_index]; - const uint emissive_kusok_index = lights.kusochki[ekusok_index].kusok_index; - - // FIXME - // if (emissive_kusok_index == uint(payload_opaque.kusok_index)) - // return; - - const Kusok kusok = kusochki[emissive_kusok_index]; - - // TODO streamline matrices layouts - const mat4x3 to_world = mat4x3( - vec3(ek.tx_row_x.x, ek.tx_row_y.x, ek.tx_row_z.x), - vec3(ek.tx_row_x.y, ek.tx_row_y.y, ek.tx_row_z.y), - vec3(ek.tx_row_x.z, ek.tx_row_y.z, ek.tx_row_z.z), - vec3(ek.tx_row_x.w, ek.tx_row_y.w, ek.tx_row_z.w) - ); - - const mat4x3 to_shading = ctx.world_to_shading * mat4( - vec4(ek.tx_row_x.x, ek.tx_row_y.x, ek.tx_row_z.x, 0), - vec4(ek.tx_row_x.y, ek.tx_row_y.y, ek.tx_row_z.y, 0), - vec4(ek.tx_row_x.z, ek.tx_row_y.z, ek.tx_row_z.z, 0), - vec4(ek.tx_row_x.w, ek.tx_row_y.w, ek.tx_row_z.w, 1) - ); - - // Picking a triangle is taken from Ray Tracing Gems II, ch.47, p.776, listing 47-2 - int selected = -1; - float total_contrib = 0.; - float eps1 = rand01(); - - projected_solid_angle_polygon_t selected_angle; - vec4 selected_plane; - for (uint i = 0; i < kusok.triangles; ++i) { - const uint first_index_offset = kusok.index_offset + i * 3; - const uint vi1 = uint(indices[first_index_offset+0]) + kusok.vertex_offset; - const uint vi2 = uint(indices[first_index_offset+1]) + kusok.vertex_offset; - const uint vi3 = uint(indices[first_index_offset+2]) + kusok.vertex_offset; - - // Transform to shading space - vec3 v[MAX_POLYGON_VERTEX_COUNT]; - v[0] = to_shading * vec4(vertices[vi1].pos, 1.); - v[1] = to_shading * vec4(vertices[vi2].pos, 1.); - v[2] = to_shading * vec4(vertices[vi3].pos, 1.); - - // cull by triangle orientation - const vec3 tri_normal_dir = cross(v[1] - v[0], v[2] - v[0]); - if (dot(tri_normal_dir, v[0]) <= 0.) - continue; - - // Clip - const uint vertex_count = clip_polygon(3, v); - if (vertex_count == 0) - continue; - - // poly_angle - const projected_solid_angle_polygon_t sap = prepare_projected_solid_angle_polygon_sampling(vertex_count, v); - const float tri_contrib = sap.projected_solid_angle; - - if (tri_contrib <= 0.) - continue; - - const float tau = total_contrib / (total_contrib + tri_contrib); - total_contrib += tri_contrib; - -#if 0 - if (false) { -#else - if (eps1 < tau) { -#endif - eps1 /= tau; - } else { - selected = int(i); - eps1 = (eps1 - tau) / (1. - tau); - selected_angle = sap; - - vec3 vw[MAX_POLYGON_VERTEX_COUNT]; - vw[0] = to_world * vec4(vertices[vi1].pos, 1.); - vw[1] = to_world * vec4(vertices[vi2].pos, 1.); - vw[2] = to_world * vec4(vertices[vi3].pos, 1.); - - selected_plane.xyz = cross(vw[1] - vw[0], vw[2] - vw[0]); - selected_plane.w = -dot(vw[0], selected_plane.xyz); - } - -#define MAX_BELOW_ONE .99999 // FIXME what's the correct way to do this - eps1 = clamp(eps1, 0., MAX_BELOW_ONE); // Numerical stability (?) - } - - if (selected < 0 || selected_angle.projected_solid_angle <= 0.) - return; - - //sampleSurfaceTriangle(throughput * ek.emissive, view_dir, material, emissive_transform, selected, kusok.index_offset, kusok.vertex_offset, emissive_kusok_index, out_diffuse, out_specular); - - vec2 rnd = vec2(rand01(), rand01()); - const vec3 light_dir = (transpose(ctx.world_to_shading) * sample_projected_solid_angle_polygon(selected_angle, rnd)).xyz; - //const vec3 light_dir = sample_solid_angle_polygon(selected_angle, rnd); - - vec3 tri_diffuse = vec3(0.), tri_specular = vec3(0.); -#if 1 - evalSplitBRDF(N, light_dir, view_dir, material, tri_diffuse, tri_specular); - tri_diffuse *= throughput * ek.emissive; - tri_specular *= throughput * ek.emissive; -#else - tri_diffuse = vec3(selected_angle.solid_angle); -#endif - -#if 1 - vec3 combined = tri_diffuse + tri_specular; - if (dot(combined,combined) > color_culling_threshold) { - const float dist = -dot(vec4(P, 1.f), selected_plane) / dot(light_dir, selected_plane.xyz); - if (!shadowed(P, light_dir, dist)) { - const float tri_factor = total_contrib; // / selected_angle.solid_angle; - out_diffuse += tri_diffuse * tri_factor; - out_specular += tri_specular * tri_factor; - } - } else { -#ifdef DEBUG_LIGHT_CULLING - return vec3(1., 1., 0.) * color_factor; -#else - return; -#endif - } -#else - const float tri_factor = total_contrib; - out_diffuse += tri_diffuse * tri_factor; - out_specular += tri_specular * tri_factor; -#endif -} - -void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { - - const SampleContext ctx = buildSampleContext(P, N, view_dir); - - const uint num_emissive_kusochki = uint(light_grid.clusters[cluster_index].num_emissive_surfaces); - float sampling_light_scale = 1.; -#if 0 - const uint max_lights_per_frame = 4; - uint begin_i = 0, end_i = num_emissive_kusochki; - if (end_i > max_lights_per_frame) { - begin_i = rand() % (num_emissive_kusochki - max_lights_per_frame); - end_i = begin_i + max_lights_per_frame; - sampling_light_scale = float(num_emissive_kusochki) / float(max_lights_per_frame); - } - for (uint i = begin_i; i < end_i; ++i) { -#else - - for (uint i = 0; i < num_emissive_kusochki; ++i) { -#endif - const uint index_into_emissive_kusochki = uint(light_grid.clusters[cluster_index].emissive_surfaces[i]); - -#if 0 - if (push_constants.debug_light_index_begin < push_constants.debug_light_index_end) { - if (index_into_emissive_kusochki < push_constants.debug_light_index_begin || index_into_emissive_kusochki >= push_constants.debug_light_index_end) - continue; - } -#endif - - vec3 ldiffuse, lspecular; - sampleEmissiveSurface(P, N, throughput, view_dir, material, ctx, index_into_emissive_kusochki, ldiffuse, lspecular); - - diffuse += ldiffuse * sampling_light_scale; - specular += lspecular * sampling_light_scale; - } // for all emissive kusochki -} -#endif - -void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { - #define DO_ALL_IN_CLUSTER 1 -#if 0 - { - const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); - //diffuse = vec3(float(num_polygons) / MAX_VISIBLE_SURFACE_LIGHTS); - if (num_polygons > 0) { - const PolygonLight poly = lights.polygons[0]; +//#define PROJECTED +//#define SOLID - const vec3 dir = poly.center - P; - const vec3 light_dir = normalize(dir); - if (dot(-light_dir, poly.plane.xyz) <= 0.f) // || dot(light_dir, N) <= 0.) - return; +void sampleSinglePolygonLight(in vec3 P, in vec3 N, in vec3 view_dir, in SampleContext ctx, in MaterialProperties material, in PolygonLight poly, inout vec3 diffuse, inout vec3 specular) { + // TODO cull by poly plane - const SampleContext ctx = buildSampleContext(P, N, view_dir); - const vec4 light_sample_dir = getPolygonLightSampleSolid(P, N, view_dir, ctx, poly); - if (light_sample_dir.w <= 0.) - return; - const float estimate = 1.f;// light_sample_dir.w; - vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); - evalSplitBRDF(N, light_sample_dir.xyz, view_dir, material, poly_diffuse, poly_specular); - diffuse += throughput * poly.emissive * estimate * poly_diffuse; - specular += throughput * poly.emissive * estimate * poly_specular; - } +#ifdef PROJECTED + const vec4 light_sample_dir = getPolygonLightSampleProjected(view_dir, ctx, poly); +#else + const vec4 light_sample_dir = getPolygonLightSampleSolid(P, view_dir, ctx, poly); +#endif + if (light_sample_dir.w <= 0.) return; - } -#elif DO_ALL_IN_CLUSTER + const float dist = - dot(vec4(P, 1.f), poly.plane) / dot(light_sample_dir.xyz, poly.plane.xyz); + + if (shadowed(P, light_sample_dir.xyz, dist)) + return; + + vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); + evalSplitBRDF(N, light_sample_dir.xyz, view_dir, material, poly_diffuse, poly_specular); + const float estimate = light_sample_dir.w; + const vec3 emissive = poly.emissive * estimate; + diffuse += emissive * poly_diffuse; + specular += emissive * poly_specular; +} + +#if 0 +// Sample random one +void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { + const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); + + if (num_polygons == 0) + return; + + const uint selected = uint(light_grid.clusters[cluster_index].polygons[rand_range(num_polygons)]); + + const PolygonLight poly = lights.polygons[selected]; + const SampleContext ctx = buildSampleContext(P, N, view_dir); + sampleSinglePolygonLight(P, N, view_dir, ctx, material, poly, diffuse, specular); + + const float sampling_factor = float(num_polygons); + diffuse *= sampling_factor; + specular *= sampling_factor; +} + +#elif 1 +void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { +#if DO_ALL_IN_CLUSTER const SampleContext ctx = buildSampleContext(P, N, view_dir); const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); for (uint i = 0; i < num_polygons; ++i) { const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); const PolygonLight poly = lights.polygons[index]; - if (false) - { - const vec3 dir = poly.center - P; - const vec3 light_dir = normalize(dir); - if (dot(-light_dir, poly.plane.xyz) <= 0.f) - continue; - } + const float plane_dist = dot(poly.plane, vec4(P, 1.f)); + + if (plane_dist < 0.) + continue; -#define PROJECTED #ifdef PROJECTED - const vec4 light_sample_dir = getPolygonLightSampleProjected(P, N, view_dir, ctx, poly); + const vec4 light_sample_dir = getPolygonLightSampleProjected(view_dir, ctx, poly); +#elif defined(SOLID) + const vec4 light_sample_dir = getPolygonLightSampleSolid(P, view_dir, ctx, poly); #else - const vec4 light_sample_dir = getPolygonLightSampleSolid(P, N, view_dir, ctx, poly); + const vec4 light_sample_dir = getPolygonLightSampleSimple(P, view_dir, poly); #endif + if (light_sample_dir.w <= 0.) continue; - const float dist = - dot(vec4(P, 1.f), poly.plane) / dot(light_sample_dir.xyz, poly.plane.xyz); + const float dist = - plane_dist / dot(light_sample_dir.xyz, poly.plane.xyz); const vec3 emissive = poly.emissive; -#if 0 - const float estimate = light_sample_dir.w; - diffuse += throughput * emissive * estimate; - specular += throughput * emissive * estimate; -#else - //if (true) {//!shadowed(P, light_sample_dir.xyz, dist)) { if (!shadowed(P, light_sample_dir.xyz, dist)) { //const float estimate = total_contrib; const float estimate = light_sample_dir.w; @@ -322,19 +178,26 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate diffuse += throughput * emissive * estimate * poly_diffuse; specular += throughput * emissive * estimate * poly_specular; } -#endif } #else + +#define USE_CLUSTERS +#ifdef USE_CLUSTERS // TODO move this to pickPolygonLight function - //const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); + const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); +#else const uint num_polygons = lights.num_polygons; +#endif uint selected = 0; float total_contrib = 0.; float eps1 = rand01(); for (uint i = 0; i < num_polygons; ++i) { - //const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); +#ifdef USE_CLUSTERS + const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); +#else const uint index = i; +#endif const PolygonLight poly = lights.polygons[index]; @@ -371,11 +234,15 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate specular += throughput * emissive * total_contrib; #else const SampleContext ctx = buildSampleContext(P, N, view_dir); - const vec4 light_sample_dir = getPolygonLightSample(P, N, view_dir, ctx, selected - 1); + const PolygonLight poly = lights.polygons[selected - 1]; +#ifdef PROJECTED + const vec4 light_sample_dir = getPolygonLightSampleProjected(view_dir, ctx, poly); +#else + const vec4 light_sample_dir = getPolygonLightSampleSolid(P, view_dir, ctx, poly); +#endif if (light_sample_dir.w <= 0.) return; - const PolygonLight poly = lights.polygons[selected - 1]; const float dist = - dot(vec4(P, 1.f), poly.plane) / dot(light_sample_dir.xyz, poly.plane.xyz); const vec3 emissive = poly.emissive; @@ -391,3 +258,4 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate #endif #endif } +#endif diff --git a/ref_vk/shaders/rt_geometry.glsl b/ref_vk/shaders/rt_geometry.glsl index 45687422..ad2ddd2c 100644 --- a/ref_vk/shaders/rt_geometry.glsl +++ b/ref_vk/shaders/rt_geometry.glsl @@ -1,10 +1,4 @@ -vec3 baryMix(vec3 v1, vec3 v2, vec3 v3, vec2 bary) { - return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; -} - -vec2 baryMix(vec2 v1, vec2 v2, vec2 v3, vec2 bary) { - return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; -} +#include "utils.glsl" // Taken from Journal of Computer Graphics Techniques, Vol. 10, No. 1, 2021. // Improved Shader and Texture Level of Detail Using Ray Cones, diff --git a/ref_vk/shaders/utils.glsl b/ref_vk/shaders/utils.glsl index 68fe6034..d06d55af 100644 --- a/ref_vk/shaders/utils.glsl +++ b/ref_vk/shaders/utils.glsl @@ -1,3 +1,6 @@ +#ifndef UTILS_GLSL_INCLUDED +#define UTILS_GLSL_INCLUDED + float signP(float v) { return v >= 0.f ? 1.f : -1.f; } vec2 signP(vec2 v) { return vec2(signP(v.x), signP(v.y)); } @@ -27,3 +30,12 @@ vec3 normalDecode( vec2 f ) return normalize( n ); } +vec3 baryMix(vec3 v1, vec3 v2, vec3 v3, vec2 bary) { + return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; +} + +vec2 baryMix(vec2 v1, vec2 v2, vec2 v3, vec2 bary) { + return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; +} + +#endif // UTILS_GLSL_INCLUDED diff --git a/ref_vk/vk_denoiser.c b/ref_vk/vk_denoiser.c index 2d7fea73..d3ac9ece 100644 --- a/ref_vk/vk_denoiser.c +++ b/ref_vk/vk_denoiser.c @@ -13,6 +13,8 @@ X(4, light_point_diffuse) \ X(5, light_point_specular) \ X(6, emissive) \ + X(7, position_t) \ + X(8, normals_gs) \ static const VkDescriptorSetLayoutBinding bindings[] = { #define BIND_IMAGE(index, name) \ From e2694ecf363003ef67d817a5defac6e860ba5c0d Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Tue, 8 Feb 2022 22:26:39 -0800 Subject: [PATCH 145/548] rt: try single triangle solid angle sampling 1. it looks way better 2. still missing other triangles perf (c1a0): 4.2ms, v87(96), o10/16 (-6v +1o) --- ref_vk/shaders/light_polygon.glsl | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 75c3dbd8..d564f1f6 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -37,7 +37,29 @@ vec4 getPolygonLightSampleSimple(vec3 P, vec3 view_dir, const PolygonLight poly) const vec3 light_dir = baryMix(v[0], v[1], v[2], rnd) - P; const vec3 light_dir_n = normalize(light_dir); - float contrib = - poly.area * dot(light_dir_n, poly.plane.xyz ) / dot(light_dir, light_dir); + //const float contrib = - poly.area * dot(light_dir_n, poly.plane.xyz ) / dot(light_dir, light_dir); + + v[0] = normalize(v[0] - P); + v[1] = normalize(v[1] - P); + v[2] = normalize(v[2] - P); + + // effectively mindlessly copypasted from polygon_sampling.glsl, Peters 2021 + // https://github.com/MomentsInGraphics/vulkan_renderer/blob/main/src/shaders/polygon_sampling.glsl + const float householder_sign = (v[0].x > 0.0f) ? -1.0f : 1.0f; + const vec2 householder_yz = v[0].yz * (1.0f / (abs(v[0].x) + 1.0f)); + const float dot_0_1 = dot(v[0], v[1]); + const float dot_0_2 = dot(v[0], v[2]); + const float dot_1_2 = dot(v[1], v[2]); + const float dot_householder_0 = fma(-householder_sign, v[0].x, dot_0_1); + const float dot_householder_2 = fma(-householder_sign, v[2].x, dot_1_2); + const mat2 bottom_right_minor = mat2( + fma(vec2(-dot_householder_0), householder_yz, v[0].yz), + fma(vec2(-dot_householder_2), householder_yz, v[2].yz)); + const float simplex_volume = abs(determinant(bottom_right_minor)); + const float dot_0_2_plus_1_2 = dot_0_2 + dot_1_2; + const float one_plus_dot_0_1 = 1.0f + dot_0_1; + const float tangent = simplex_volume / (one_plus_dot_0_1 + dot_0_2_plus_1_2); + const float contrib = 2.f * (atan(tangent) + (tangent < 0.f ? M_PI : 0.)); return vec4(light_dir_n, contrib); } From 152cca3a472a83d6eeee73a1a19e14ce0ba4350c Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Tue, 8 Feb 2022 23:04:00 -0800 Subject: [PATCH 146/548] rt: select random triangle to sample based on solid angle looks correct enough(ish?) perf (c1a0): direct poly light 4.4ms, v85(96), o10/16 (-6v +1o) --- ref_vk/shaders/light_polygon.glsl | 85 +++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index d564f1f6..c4933724 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -37,31 +37,73 @@ vec4 getPolygonLightSampleSimple(vec3 P, vec3 view_dir, const PolygonLight poly) const vec3 light_dir = baryMix(v[0], v[1], v[2], rnd) - P; const vec3 light_dir_n = normalize(light_dir); - //const float contrib = - poly.area * dot(light_dir_n, poly.plane.xyz ) / dot(light_dir, light_dir); + const float contrib = - poly.area * dot(light_dir_n, poly.plane.xyz ) / dot(light_dir, light_dir); + return vec4(light_dir_n, contrib); +} - v[0] = normalize(v[0] - P); - v[1] = normalize(v[1] - P); - v[2] = normalize(v[2] - P); +vec4 getPolygonLightSampleSimpleSolid(vec3 P, vec3 view_dir, const PolygonLight poly) { + const uint vertices_offset = poly.vertices_count_offset & 0xffffu; + uint vertices_count = poly.vertices_count_offset >> 16; - // effectively mindlessly copypasted from polygon_sampling.glsl, Peters 2021 - // https://github.com/MomentsInGraphics/vulkan_renderer/blob/main/src/shaders/polygon_sampling.glsl + uint selected = 0; + float total_contrib = 0.; + float eps1 = rand01(); + vec3 v[3]; + v[0] = normalize(lights.polygon_vertices[vertices_offset + 0].xyz - P); + v[1] = normalize(lights.polygon_vertices[vertices_offset + 1].xyz - P); const float householder_sign = (v[0].x > 0.0f) ? -1.0f : 1.0f; const vec2 householder_yz = v[0].yz * (1.0f / (abs(v[0].x) + 1.0f)); - const float dot_0_1 = dot(v[0], v[1]); - const float dot_0_2 = dot(v[0], v[2]); - const float dot_1_2 = dot(v[1], v[2]); - const float dot_householder_0 = fma(-householder_sign, v[0].x, dot_0_1); - const float dot_householder_2 = fma(-householder_sign, v[2].x, dot_1_2); - const mat2 bottom_right_minor = mat2( - fma(vec2(-dot_householder_0), householder_yz, v[0].yz), - fma(vec2(-dot_householder_2), householder_yz, v[2].yz)); - const float simplex_volume = abs(determinant(bottom_right_minor)); - const float dot_0_2_plus_1_2 = dot_0_2 + dot_1_2; - const float one_plus_dot_0_1 = 1.0f + dot_0_1; - const float tangent = simplex_volume / (one_plus_dot_0_1 + dot_0_2_plus_1_2); - const float contrib = 2.f * (atan(tangent) + (tangent < 0.f ? M_PI : 0.)); + for (uint i = 2; i < vertices_count; ++i) { + v[2] = normalize(lights.polygon_vertices[vertices_offset + i].xyz - P); - return vec4(light_dir_n, contrib); + // effectively mindlessly copypasted from polygon_sampling.glsl, Peters 2021 + // https://github.com/MomentsInGraphics/vulkan_renderer/blob/main/src/shaders/polygon_sampling.glsl + const float dot_0_1 = dot(v[0], v[1]); + const float dot_0_2 = dot(v[1], v[2]); + const float dot_1_2 = dot(v[0], v[2]); + const float dot_householder_0 = fma(-householder_sign, v[1].x, dot_0_1); + const float dot_householder_2 = fma(-householder_sign, v[2].x, dot_1_2); + const mat2 bottom_right_minor = mat2( + fma(vec2(-dot_householder_0), householder_yz, v[1].yz), + fma(vec2(-dot_householder_2), householder_yz, v[2].yz)); + const float simplex_volume = abs(determinant(bottom_right_minor)); + const float dot_0_2_plus_1_2 = dot_0_2 + dot_1_2; + const float one_plus_dot_0_1 = 1.0f + dot_0_1; + const float tangent = simplex_volume / (one_plus_dot_0_1 + dot_0_2_plus_1_2); + const float contrib = 2.f * (atan(tangent) + (tangent < 0.f ? M_PI : 0.)); + + if (contrib < 1e-6) + continue; + + const float tau = total_contrib / (total_contrib + contrib); + total_contrib += contrib; + + if (eps1 < tau) { + eps1 /= tau; + } else { + selected = i; + eps1 = (eps1 - tau) / (1. - tau); + } + + // selected = 2; + // break; + v[1] = v[2]; + } + + if (selected == 0) + return vec4(0.); + + vec2 rnd = vec2(sqrt(rand01()), rand01()); + rnd.y *= rnd.x; + rnd.x = 1.f - rnd.x; + + const vec3 light_dir = baryMix( + lights.polygon_vertices[vertices_offset + 0].xyz, + lights.polygon_vertices[vertices_offset + selected - 1].xyz, + lights.polygon_vertices[vertices_offset + selected].xyz, + rnd) - P; + const vec3 light_dir_n = normalize(light_dir); + return vec4(light_dir_n, total_contrib); } vec4 getPolygonLightSampleProjected(vec3 view_dir, SampleContext ctx, const PolygonLight poly) { @@ -120,6 +162,7 @@ vec4 getPolygonLightSampleSolid(vec3 P, vec3 view_dir, SampleContext ctx, const #define DO_ALL_IN_CLUSTER 1 //#define PROJECTED //#define SOLID +#define SIMPLE_SOLID void sampleSinglePolygonLight(in vec3 P, in vec3 N, in vec3 view_dir, in SampleContext ctx, in MaterialProperties material, in PolygonLight poly, inout vec3 diffuse, inout vec3 specular) { // TODO cull by poly plane @@ -182,6 +225,8 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate const vec4 light_sample_dir = getPolygonLightSampleProjected(view_dir, ctx, poly); #elif defined(SOLID) const vec4 light_sample_dir = getPolygonLightSampleSolid(P, view_dir, ctx, poly); +#elif defined(SIMPLE_SOLID) + const vec4 light_sample_dir = getPolygonLightSampleSimpleSolid(P, view_dir, poly); #else const vec4 light_sample_dir = getPolygonLightSampleSimple(P, view_dir, poly); #endif From 203afda05322494463794daa0eb207ac9b63d066 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 10 Feb 2022 02:49:14 +0300 Subject: [PATCH 147/548] engine: add render_picbutton_text flag to use mainui_cpp font renderer for rendering WON buttons --- common/gameinfo.h | 1 + engine/client/cl_gameui.c | 2 ++ engine/common/common.h | 1 + engine/common/filesystem.c | 5 +++++ 4 files changed, 9 insertions(+) diff --git a/common/gameinfo.h b/common/gameinfo.h index 597ec1fb..467185cc 100644 --- a/common/gameinfo.h +++ b/common/gameinfo.h @@ -18,6 +18,7 @@ GNU General Public License for more details. #define GFL_NOMODELS (1<<0) #define GFL_NOSKILLS (1<<1) +#define GFL_RENDER_PICBUTTON_TEXT (1<<2) /* ======================================================================== diff --git a/engine/client/cl_gameui.c b/engine/client/cl_gameui.c index 382a5e21..427f96d1 100644 --- a/engine/client/cl_gameui.c +++ b/engine/client/cl_gameui.c @@ -417,6 +417,8 @@ static void UI_ConvertGameInfo( GAMEINFO *out, gameinfo_t *in ) out->flags |= GFL_NOMODELS; if( in->noskills ) out->flags |= GFL_NOSKILLS; + if( in->render_picbutton_text ) + out->flags |= GFL_RENDER_PICBUTTON_TEXT; } static qboolean PIC_Scissor( float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 ) diff --git a/engine/common/common.h b/engine/common/common.h index ad4ccc59..a21d1083 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -230,6 +230,7 @@ typedef struct gameinfo_s qboolean secure; // prevent to console acess qboolean nomodels; // don't let player to choose model (use player.mdl always) qboolean noskills; // disable skill menu selection + qboolean render_picbutton_text; // use font renderer to render WON buttons char sp_entity[32]; // e.g. info_player_start char mp_entity[32]; // e.g. info_player_deathmatch diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index cf9ccea3..a6a0af08 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -1863,6 +1863,11 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool pfile = COM_ParseFile( pfile, token, sizeof( token )); GameInfo->noskills = Q_atoi( token ); } + else if( !Q_stricmp( token, "render_picbutton_text" )) + { + pfile = COM_ParseFile( pfile, token, sizeof( token )); + GameInfo->render_picbutton_text = Q_atoi( token ); + } } } From 668d3fb81a3ba1853e5703822c9c88bf72f0b3b5 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Wed, 9 Feb 2022 21:57:55 -0800 Subject: [PATCH 148/548] rt: add shadows for environment lights --- ref_vk/TODO.md | 4 ++-- ref_vk/shaders/light.glsl | 1 + ref_vk/shaders/light_common.glsl | 28 ++++++++++++------------ ref_vk/shaders/ray_shadow.rchit | 15 +++++++++++++ ref_vk/shaders/ray_shadow_interface.glsl | 1 + ref_vk/vk_ray_light_direct.c | 2 +- 6 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 ref_vk/shaders/ray_shadow.rchit diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index ada2aa7e..3da5e5a2 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,7 +1,7 @@ # Passes - [ ] better simple sampling - - [ ] all triangles - - [ ] area based on triangles + - [x] all triangles + - [x] area based on triangles - [ ] clipping? - [ ] can we pack polygon lights better? e.g.: - each light is strictly a triangle diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index 5ab60a08..e06827a4 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -89,6 +89,7 @@ void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec if (dot(combined,combined) < color_culling_threshold) continue; + // FIXME split environment and other lights if (not_environment) { if (shadowed(P, light_dir_norm, light_dist + shadow_offset_fudge)) continue; diff --git a/ref_vk/shaders/light_common.glsl b/ref_vk/shaders/light_common.glsl index 894d91c7..7d2bc4be 100644 --- a/ref_vk/shaders/light_common.glsl +++ b/ref_vk/shaders/light_common.glsl @@ -3,23 +3,28 @@ #ifdef RAY_TRACE2 #include "ray_shadow_interface.glsl" -layout(location = 0) rayPayloadEXT RayPayloadShadow payload_shadow; +layout(location = PAYLOAD_LOCATION_SHADOW) rayPayloadEXT RayPayloadShadow payload_shadow; #endif +uint traceShadowRay(vec3 pos, vec3 dir, float dist, uint flags) { + payload_shadow.hit_type = SHADOW_HIT; + traceRayEXT(tlas, + flags, + GEOMETRY_BIT_OPAQUE, + SHADER_OFFSET_HIT_SHADOW_BASE, SBT_RECORD_SIZE, SHADER_OFFSET_MISS_SHADOW, + pos, 0., dir, dist - shadow_offset_fudge, PAYLOAD_LOCATION_SHADOW); + return payload_shadow.hit_type; +} + bool shadowed(vec3 pos, vec3 dir, float dist) { #ifdef RAY_TRACE - payload_shadow.hit_type = SHADOW_HIT; const uint flags = 0 //| gl_RayFlagsCullFrontFacingTrianglesEXT //| gl_RayFlagsOpaqueEXT | gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsSkipClosestHitShaderEXT ; - traceRayEXT(tlas, - flags, - GEOMETRY_BIT_OPAQUE, - SHADER_OFFSET_HIT_SHADOW_BASE, SBT_RECORD_SIZE, SHADER_OFFSET_MISS_SHADOW, - pos, 0., dir, dist - shadow_offset_fudge, PAYLOAD_LOCATION_SHADOW); + const uint hit_type = traceShadowRay(pos, dir, dist, flags); return payload_shadow.hit_type == SHADOW_HIT; #elif defined(RAY_QUERY) rayQueryEXT rq; @@ -39,19 +44,14 @@ bool shadowed(vec3 pos, vec3 dir, float dist) { // TODO join with just shadowed() bool shadowedSky(vec3 pos, vec3 dir, float dist) { -#if 0 - payload_shadow.hit_type = SHADOW_HIT; +#if 1 const uint flags = 0 //| gl_RayFlagsCullFrontFacingTrianglesEXT //| gl_RayFlagsOpaqueEXT //| gl_RayFlagsTerminateOnFirstHitEXT //| gl_RayFlagsSkipClosestHitShaderEXT ; - traceRayEXT(tlas, - flags, - GEOMETRY_BIT_OPAQUE, - SHADER_OFFSET_HIT_SHADOW_BASE, SBT_RECORD_SIZE, SHADER_OFFSET_MISS_SHADOW, - pos, 0., dir, dist - shadow_offset_fudge, PAYLOAD_LOCATION_SHADOW); + const uint hit_type = traceShadowRay(pos, dir, dist, flags); return payload_shadow.hit_type != SHADOW_SKY; #else return false; diff --git a/ref_vk/shaders/ray_shadow.rchit b/ref_vk/shaders/ray_shadow.rchit new file mode 100644 index 00000000..a44f422d --- /dev/null +++ b/ref_vk/shaders/ray_shadow.rchit @@ -0,0 +1,15 @@ +#version 460 core +#extension GL_EXT_ray_tracing: require + +#include "ray_kusochki.glsl" +#include "ray_common.glsl" + +layout(location = PAYLOAD_LOCATION_SHADOW) rayPayloadInEXT RayPayloadShadow payload_shadow; + +void main() { + const int instance_kusochki_offset = gl_InstanceCustomIndexEXT; + const int kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT; + const uint tex_base_color = kusochki[kusok_index].tex_base_color; + + payload_shadow.hit_type = ((tex_base_color & KUSOK_MATERIAL_FLAG_SKYBOX) == 0) ? SHADOW_HIT : SHADOW_SKY ; +} diff --git a/ref_vk/shaders/ray_shadow_interface.glsl b/ref_vk/shaders/ray_shadow_interface.glsl index 28a7dbe3..73d83be7 100644 --- a/ref_vk/shaders/ray_shadow_interface.glsl +++ b/ref_vk/shaders/ray_shadow_interface.glsl @@ -1,5 +1,6 @@ #ifndef RAY_SHADOW_INTERFACE_GLSL_INCLUDED #define RAY_SHADOW_INTERFACE_GLSL_INCLUDED + #define SHADOW_MISS 0 #define SHADOW_HIT 1 #define SHADOW_SKY 2 diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index dd7e138f..5cc4cdcb 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -125,7 +125,7 @@ struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ) { }; const ray_pass_hit_group_t hit[] = { { - .closest = NULL, + .closest = "ray_shadow.rchit.spv", .any = "ray_common_alphatest.rahit.spv", }, }; From 22917938d9a193dc2e1964dd0eb633b577787c1d Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 10 Feb 2022 22:46:23 -0800 Subject: [PATCH 149/548] vk: initial draft of parallel frames adds multiple command buffers and bits of synchronization. is not correct at all: - no depth buffer barrier - no ubo slots in render - no proper render memory management - no ray tracing support whatsoever - ... etc --- ref_vk/vk_core.c | 49 ++++++----- ref_vk/vk_core.h | 29 +++++-- ref_vk/vk_framectl.c | 190 +++++++++++++++++++++++++----------------- ref_vk/vk_framectl.h | 2 + ref_vk/vk_ray_model.c | 4 +- ref_vk/vk_render.c | 12 +-- ref_vk/vk_scene.c | 32 +++---- ref_vk/vk_swapchain.c | 4 +- ref_vk/vk_swapchain.h | 2 +- ref_vk/vk_textures.c | 25 +++--- 10 files changed, 207 insertions(+), 142 deletions(-) diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index 337c1b8d..209abd8d 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -593,30 +593,35 @@ static qboolean initSurface( void ) return true; } -static qboolean createCommandPool( void ) { - VkCommandPoolCreateInfo cpci = { +vk_command_pool_t R_VkCommandPoolCreate( int count ) { + vk_command_pool_t ret = {0}; + + const VkCommandPoolCreateInfo cpci = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .queueFamilyIndex = 0, .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, }; - VkCommandBuffer bufs[2]; - VkCommandBufferAllocateInfo cbai = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - .commandBufferCount = ARRAYSIZE(bufs), + .commandBufferCount = count, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, }; + XVK_CHECK(vkCreateCommandPool(vk_core.device, &cpci, NULL, &ret.pool)); - XVK_CHECK(vkCreateCommandPool(vk_core.device, &cpci, NULL, &vk_core.command_pool)); - cbai.commandPool = vk_core.command_pool; - XVK_CHECK(vkAllocateCommandBuffers(vk_core.device, &cbai, bufs)); + cbai.commandPool = ret.pool; + ret.buffers = Mem_Malloc(vk_core.pool, sizeof(VkCommandBuffer) * count); + ret.buffers_count = count; + XVK_CHECK(vkAllocateCommandBuffers(vk_core.device, &cbai, ret.buffers)); - vk_core.cb = bufs[0]; - vk_core.cb_tex = bufs[1]; + return ret; +} - return true; +void R_VkCommandPoolDestroy( vk_command_pool_t *pool ) { + ASSERT(pool->buffers); + vkDestroyCommandPool(vk_core.device, pool->pool, NULL); + Mem_Free(pool->buffers); } qboolean R_VkInit( void ) @@ -679,8 +684,7 @@ qboolean R_VkInit( void ) if (!initSurface()) return false; - if (!createCommandPool()) - return false; + vk_core.upload_pool = R_VkCommandPoolCreate( 1 ); if (!VK_DevMemInit()) return false; @@ -750,8 +754,9 @@ qboolean R_VkInit( void ) return true; } -void R_VkShutdown( void ) -{ +void R_VkShutdown( void ) { + XVK_CHECK(vkDeviceWaitIdle(vk_core.device)); + if (vk_core.rtx) { XVK_DenoiserDestroy(); @@ -778,7 +783,7 @@ void R_VkShutdown( void ) VK_DevMemDestroy(); - vkDestroyCommandPool(vk_core.device, vk_core.command_pool, NULL); + R_VkCommandPoolDestroy( &vk_core.upload_pool ); vkDestroyDevice(vk_core.device, NULL); @@ -829,7 +834,7 @@ VkShaderModule loadShader(const char *filename) { return shader; } -VkSemaphore createSemaphore( void ) { +VkSemaphore R_VkSemaphoreCreate( void ) { VkSemaphore sema; VkSemaphoreCreateInfo sci = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, @@ -839,20 +844,20 @@ VkSemaphore createSemaphore( void ) { return sema; } -void destroySemaphore(VkSemaphore sema) { +void R_VkSemaphoreDestroy(VkSemaphore sema) { vkDestroySemaphore(vk_core.device, sema, NULL); } -VkFence createFence( void ) { +VkFence R_VkFenceCreate( qboolean signaled ) { VkFence fence; - VkFenceCreateInfo fci = { + const VkFenceCreateInfo fci = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, - .flags = 0, + .flags = signaled ? VK_FENCE_CREATE_SIGNALED_BIT : 0, }; XVK_CHECK(vkCreateFence(vk_core.device, &fci, NULL, &fence)); return fence; } -void destroyFence(VkFence fence) { +void R_VkFenceDestroy(VkFence fence) { vkDestroyFence(vk_core.device, fence, NULL); } diff --git a/ref_vk/vk_core.h b/ref_vk/vk_core.h index 3a2aa621..90a171b4 100644 --- a/ref_vk/vk_core.h +++ b/ref_vk/vk_core.h @@ -10,13 +10,25 @@ qboolean R_VkInit( void ); void R_VkShutdown( void ); -// FIXME load from embedded static structs -VkShaderModule loadShader(const char *filename); -VkSemaphore createSemaphore( void ); -void destroySemaphore(VkSemaphore sema); -VkFence createFence( void ); -void destroyFence(VkFence fence); +typedef struct { + VkCommandPool pool; + VkCommandBuffer *buffers; + int buffers_count; +} vk_command_pool_t; +vk_command_pool_t R_VkCommandPoolCreate( int count ); +void R_VkCommandPoolDestroy( vk_command_pool_t *pool ); + +// TODO load from embedded static structs +VkShaderModule loadShader(const char *filename); + +VkSemaphore R_VkSemaphoreCreate( void ); +void R_VkSemaphoreDestroy(VkSemaphore sema); + +VkFence R_VkFenceCreate( qboolean signaled ); +void R_VkFenceDestroy(VkFence fence); + +// TODO move all these to vk_device.{h,c} or something typedef struct physical_device_s { VkPhysicalDevice device; VkPhysicalDeviceMemoryProperties2 memory_properties2; @@ -51,9 +63,7 @@ typedef struct vulkan_core_s { VkDevice device; VkQueue queue; - VkCommandPool command_pool; - VkCommandBuffer cb; - VkCommandBuffer cb_tex; + vk_command_pool_t upload_pool; VkSampler default_sampler; @@ -188,6 +198,7 @@ do { \ X(vkCmdPipelineBarrier) \ X(vkCmdCopyBufferToImage) \ X(vkQueueWaitIdle) \ + X(vkDeviceWaitIdle) \ X(vkDestroyImage) \ X(vkCmdBindDescriptorSets) \ X(vkCreateSampler) \ diff --git a/ref_vk/vk_framectl.c b/ref_vk/vk_framectl.c index e214589b..76a489c6 100644 --- a/ref_vk/vk_framectl.c +++ b/ref_vk/vk_framectl.c @@ -27,17 +27,18 @@ typedef enum { } frame_phase_t; static struct { - // TODO N frames in flight - VkSemaphore image_available; - VkSemaphore done; - VkFence fence; + vk_command_pool_t command; + VkSemaphore sem_framebuffer_ready[MAX_CONCURRENT_FRAMES]; + VkSemaphore sem_done[MAX_CONCURRENT_FRAMES]; + VkFence fence_done[MAX_CONCURRENT_FRAMES]; qboolean rtx_enabled; - r_vk_swapchain_framebuffer_t current_framebuffer; - - frame_phase_t phase; - VkCommandBuffer cmdbuf; + struct { + int index; + r_vk_swapchain_framebuffer_t framebuffer; + frame_phase_t phase; + } current; } g_frame; #define PROFILER_SCOPES(X) \ @@ -83,7 +84,7 @@ static const VkFormat depth_formats[] = { static VkRenderPass createRenderPass( VkFormat depth_format, qboolean ray_tracing ) { VkRenderPass render_pass; - VkAttachmentDescription attachments[] = {{ + const VkAttachmentDescription attachments[] = {{ .format = SWAPCHAIN_FORMAT, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = ray_tracing ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR /* TODO: prod renderer should not care VK_ATTACHMENT_LOAD_OP_DONT_CARE */, @@ -104,24 +105,24 @@ static VkRenderPass createRenderPass( VkFormat depth_format, qboolean ray_tracin .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }}; - VkAttachmentReference color_attachment = { + const VkAttachmentReference color_attachment = { .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }; - VkAttachmentReference depth_attachment = { + const VkAttachmentReference depth_attachment = { .attachment = 1, .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }; - VkSubpassDescription subdesc = { + const VkSubpassDescription subdesc = { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, .pColorAttachments = &color_attachment, .pDepthStencilAttachment = &depth_attachment, }; - VkRenderPassCreateInfo rpci = { + const VkRenderPassCreateInfo rpci = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = ARRAYSIZE(attachments), .pAttachments = attachments, @@ -133,35 +134,27 @@ static VkRenderPass createRenderPass( VkFormat depth_format, qboolean ray_tracin return render_pass; } -void R_BeginFrame( qboolean clearScene ) { - ASSERT(g_frame.phase == Phase_Submitted || g_frame.phase == Phase_Idle); - - if (vk_core.rtx && FBitSet( vk_rtx->flags, FCVAR_CHANGED )) { - g_frame.rtx_enabled = CVAR_TO_BOOL( vk_rtx ); - } - ClearBits( vk_rtx->flags, FCVAR_CHANGED ); - - { - gEngine.Con_NPrintf(5, "Perf scopes:"); - for (int i = 0; i < g_aprof.num_scopes; ++i) { - const aprof_scope_t *const scope = g_aprof.scopes + i; - gEngine.Con_NPrintf(6 + i, "%s: c%d t%.03f(%.03f)ms s%.03f(%.03f)ms", scope->name, - scope->frame.count, - scope->frame.duration / 1e6, - (scope->frame.duration / 1e6) / scope->frame.count, - (scope->frame.duration - scope->frame.duration_children) / 1e6, - (scope->frame.duration - scope->frame.duration_children) / 1e6 / scope->frame.count); +static void waitForFrameFence( void ) { + for(qboolean loop = true; loop; ) { +#define MAX_WAIT (10ull * 1000*1000*1000) + const VkResult fence_result = vkWaitForFences(vk_core.device, 1, g_frame.fence_done + g_frame.current.index, VK_TRUE, MAX_WAIT); +#undef MAX_WAIT + switch (fence_result) { + case VK_SUCCESS: + loop = false; + break; + case VK_TIMEOUT: + gEngine.Con_Printf(S_ERROR "Waitinf for frame fence to be signaled timed out after 10 seconds. Wat\n"); + break; + default: + XVK_CHECK(fence_result); } - - aprof_scope_frame(); } - ASSERT(!g_frame.current_framebuffer.framebuffer); - - g_frame.current_framebuffer = R_VkSwapchainAcquire( g_frame.image_available, g_frame.fence ); - vk_frame.width = g_frame.current_framebuffer.width; - vk_frame.height = g_frame.current_framebuffer.height; + XVK_CHECK(vkResetFences(vk_core.device, 1, g_frame.fence_done + g_frame.current.index)); +} +static void updateGamma( void ) { // FIXME when { cvar_t* vid_gamma = gEngine.pfnGetCvarPointer( "gamma", 0 ); @@ -181,20 +174,57 @@ void R_BeginFrame( qboolean clearScene ) { // FIXME rebuild lightmaps } } +} + +// FIXME move this to r print speeds or something like that +static void showProfilingData( void ) { + gEngine.Con_NPrintf(5, "Perf scopes:"); + for (int i = 0; i < g_aprof.num_scopes; ++i) { + const aprof_scope_t *const scope = g_aprof.scopes + i; + gEngine.Con_NPrintf(6 + i, "%s: c%d t%.03f(%.03f)ms s%.03f(%.03f)ms", scope->name, + scope->frame.count, + scope->frame.duration / 1e6, + (scope->frame.duration / 1e6) / scope->frame.count, + (scope->frame.duration - scope->frame.duration_children) / 1e6, + (scope->frame.duration - scope->frame.duration_children) / 1e6 / scope->frame.count); + } +} + +void R_BeginFrame( qboolean clearScene ) { + ASSERT(g_frame.current.phase == Phase_Submitted || g_frame.current.phase == Phase_Idle); + const int prev_frame_index = g_frame.current.index % MAX_CONCURRENT_FRAMES; + g_frame.current.index = (g_frame.current.index + 1) % MAX_CONCURRENT_FRAMES; + const VkCommandBuffer cmdbuf = g_frame.command.buffers[g_frame.current.index]; + + showProfilingData(); + aprof_scope_frame(); + + if (vk_core.rtx && FBitSet( vk_rtx->flags, FCVAR_CHANGED )) { + g_frame.rtx_enabled = CVAR_TO_BOOL( vk_rtx ); + } + ClearBits( vk_rtx->flags, FCVAR_CHANGED ); + + updateGamma(); + + ASSERT(!g_frame.current.framebuffer.framebuffer); + + waitForFrameFence(); + + g_frame.current.framebuffer = R_VkSwapchainAcquire( g_frame.sem_framebuffer_ready[g_frame.current.index] ); + vk_frame.width = g_frame.current.framebuffer.width; + vk_frame.height = g_frame.current.framebuffer.height; VK_RenderBegin( g_frame.rtx_enabled ); - g_frame.cmdbuf = vk_core.cb; - { - VkCommandBufferBeginInfo beginfo = { + const VkCommandBufferBeginInfo beginfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, }; - XVK_CHECK(vkBeginCommandBuffer(g_frame.cmdbuf, &beginfo)); + XVK_CHECK(vkBeginCommandBuffer(cmdbuf, &beginfo)); } - g_frame.phase = Phase_FrameBegan; + g_frame.current.phase = Phase_FrameBegan; } void VK_RenderFrame( const struct ref_viewpass_s *rvp ) @@ -208,31 +238,31 @@ static void enqueueRendering( VkCommandBuffer cmdbuf ) { {.depthStencil = {1., 0.}} // TODO reverse-z }; - ASSERT(g_frame.phase == Phase_FrameBegan); + ASSERT(g_frame.current.phase == Phase_FrameBegan); if (g_frame.rtx_enabled) - VK_RenderEndRTX( cmdbuf, g_frame.current_framebuffer.view, g_frame.current_framebuffer.image, g_frame.current_framebuffer.width, g_frame.current_framebuffer.height ); + VK_RenderEndRTX( cmdbuf, g_frame.current.framebuffer.view, g_frame.current.framebuffer.image, g_frame.current.framebuffer.width, g_frame.current.framebuffer.height ); { VkRenderPassBeginInfo rpbi = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = g_frame.rtx_enabled ? vk_frame.render_pass.after_ray_tracing : vk_frame.render_pass.raster, - .renderArea.extent.width = g_frame.current_framebuffer.width, - .renderArea.extent.height = g_frame.current_framebuffer.height, + .renderArea.extent.width = g_frame.current.framebuffer.width, + .renderArea.extent.height = g_frame.current.framebuffer.height, .clearValueCount = ARRAYSIZE(clear_value), .pClearValues = clear_value, - .framebuffer = g_frame.current_framebuffer.framebuffer, + .framebuffer = g_frame.current.framebuffer.framebuffer, }; vkCmdBeginRenderPass(cmdbuf, &rpbi, VK_SUBPASS_CONTENTS_INLINE); } { const VkViewport viewport[] = { - {0.f, 0.f, (float)g_frame.current_framebuffer.width, (float)g_frame.current_framebuffer.height, 0.f, 1.f}, + {0.f, 0.f, (float)g_frame.current.framebuffer.width, (float)g_frame.current.framebuffer.height, 0.f, 1.f}, }; const VkRect2D scissor[] = {{ {0, 0}, - {g_frame.current_framebuffer.width, g_frame.current_framebuffer.height}, + {g_frame.current.framebuffer.width, g_frame.current.framebuffer.height}, }}; vkCmdSetViewport(cmdbuf, 0, ARRAYSIZE(viewport), viewport); @@ -246,11 +276,11 @@ static void enqueueRendering( VkCommandBuffer cmdbuf ) { vkCmdEndRenderPass(cmdbuf); - g_frame.phase = Phase_RenderingEnqueued; + g_frame.current.phase = Phase_RenderingEnqueued; } static void submit( VkCommandBuffer cmdbuf, qboolean wait ) { - ASSERT(g_frame.phase == Phase_RenderingEnqueued); + ASSERT(g_frame.current.phase == Phase_RenderingEnqueued); XVK_CHECK(vkEndCommandBuffer(cmdbuf)); @@ -262,30 +292,28 @@ static void submit( VkCommandBuffer cmdbuf, qboolean wait ) { .commandBufferCount = 1, .pCommandBuffers = &cmdbuf, .waitSemaphoreCount = 1, - .pWaitSemaphores = &g_frame.image_available, + .pWaitSemaphores = g_frame.sem_framebuffer_ready + g_frame.current.index, .signalSemaphoreCount = 1, - .pSignalSemaphores = &g_frame.done, + .pSignalSemaphores = g_frame.sem_done + g_frame.current.index, .pWaitDstStageMask = &stageflags, }; - XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, g_frame.fence)); - g_frame.phase = Phase_Submitted; + XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, g_frame.fence_done[g_frame.current.index])); + g_frame.current.phase = Phase_Submitted; } - R_VkSwapchainPresent(g_frame.current_framebuffer.index, g_frame.done); - g_frame.current_framebuffer = (r_vk_swapchain_framebuffer_t){0}; + R_VkSwapchainPresent(g_frame.current.framebuffer.index, g_frame.sem_done[g_frame.current.index]); + g_frame.current.framebuffer = (r_vk_swapchain_framebuffer_t){0}; if (wait) { APROF_SCOPE_BEGIN(frame_gpu_wait); - // TODO bad sync - XVK_CHECK(vkWaitForFences(vk_core.device, 1, &g_frame.fence, VK_TRUE, INT64_MAX)); - XVK_CHECK(vkResetFences(vk_core.device, 1, &g_frame.fence)); + XVK_CHECK(vkWaitForFences(vk_core.device, 1, g_frame.fence_done + g_frame.current.index, VK_TRUE, INT64_MAX)); APROF_SCOPE_END(frame_gpu_wait); if (vk_core.debug) { // FIXME more scopes XVK_CHECK(vkQueueWaitIdle(vk_core.queue)); } - g_frame.phase = Phase_Idle; + g_frame.current.phase = Phase_Idle; } // TODO better sync implies multiple frames in flight, which means that we must @@ -293,17 +321,20 @@ static void submit( VkCommandBuffer cmdbuf, qboolean wait ) { // (this probably means that we should really have some kind of refcount going on...) // For now we can just erase these buffers now because of sync with fence XVK_RenderBufferFrameClear(); +} - g_frame.cmdbuf = VK_NULL_HANDLE; +inline static VkCommandBuffer currentCommandBuffer( void ) { + return g_frame.command.buffers[g_frame.current.index]; } void R_EndFrame( void ) { APROF_SCOPE_BEGIN_EARLY(end_frame); - if (g_frame.phase == Phase_FrameBegan) { - enqueueRendering( vk_core.cb ); - submit( vk_core.cb, true ); + if (g_frame.current.phase == Phase_FrameBegan) { + const VkCommandBuffer cmdbuf = currentCommandBuffer(); + enqueueRendering( cmdbuf ); + submit( cmdbuf, false ); } APROF_SCOPE_END(end_frame); @@ -322,6 +353,8 @@ qboolean VK_FrameCtlInit( void ) const VkFormat depth_format = findSupportedImageFormat(depth_formats, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); + g_frame.command = R_VkCommandPoolCreate( MAX_CONCURRENT_FRAMES ); + // FIXME move this out to renderers vk_frame.render_pass.raster = createRenderPass(depth_format, false); if (vk_core.rtx) @@ -330,9 +363,11 @@ qboolean VK_FrameCtlInit( void ) if (!R_VkSwapchainInit(vk_frame.render_pass.raster, depth_format)) return false; - g_frame.image_available = createSemaphore(); - g_frame.done = createSemaphore(); - g_frame.fence = createFence(); + for (int i = 0; i < MAX_CONCURRENT_FRAMES; ++i) { + g_frame.sem_framebuffer_ready[i] = R_VkSemaphoreCreate(); + g_frame.sem_done[i] = R_VkSemaphoreCreate(); + g_frame.fence_done[i] = R_VkFenceCreate(true); + } g_frame.rtx_enabled = vk_core.rtx; @@ -343,17 +378,20 @@ qboolean VK_FrameCtlInit( void ) return true; } -void VK_FrameCtlShutdown( void ) -{ - destroyFence(g_frame.fence); - destroySemaphore(g_frame.done); - destroySemaphore(g_frame.image_available); +void VK_FrameCtlShutdown( void ) { + for (int i = 0; i < MAX_CONCURRENT_FRAMES; ++i) { + R_VkSemaphoreDestroy(g_frame.sem_framebuffer_ready[i]); + R_VkSemaphoreDestroy(g_frame.sem_done[i]); + R_VkFenceDestroy(g_frame.fence_done[i]); + } R_VkSwapchainShutdown(); vkDestroyRenderPass(vk_core.device, vk_frame.render_pass.raster, NULL); if (vk_core.rtx) vkDestroyRenderPass(vk_core.device, vk_frame.render_pass.after_ray_tracing, NULL); + + R_VkCommandPoolDestroy( &g_frame.command ); } static qboolean canBlitFromSwapchainToFormat( VkFormat dest_format ) { @@ -377,10 +415,10 @@ static qboolean canBlitFromSwapchainToFormat( VkFormat dest_format ) { static rgbdata_t *XVK_ReadPixels( void ) { const VkFormat dest_format = VK_FORMAT_R8G8B8A8_UNORM; xvk_image_t dest_image; - const VkImage frame_image = g_frame.current_framebuffer.image; + const VkImage frame_image = g_frame.current.framebuffer.image; rgbdata_t *r_shot = NULL; qboolean blit = canBlitFromSwapchainToFormat( dest_format ); - const VkCommandBuffer cmdbuf = g_frame.cmdbuf; + const VkCommandBuffer cmdbuf = currentCommandBuffer(); if (frame_image == VK_NULL_HANDLE) { gEngine.Con_Printf(S_ERROR "no current image, can't take screenshot\n"); diff --git a/ref_vk/vk_framectl.h b/ref_vk/vk_framectl.h index 704b26f4..206075f4 100644 --- a/ref_vk/vk_framectl.h +++ b/ref_vk/vk_framectl.h @@ -3,6 +3,8 @@ #include "xash3d_types.h" +#define MAX_CONCURRENT_FRAMES 2 + typedef struct vk_framectl_s { // TODO only used from 2d, remove uint32_t width, height; diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index 1216c612..8885414e 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -246,7 +246,9 @@ vk_ray_model_t* VK_RayModelCreate( vk_ray_model_init_t args ) { } else { qboolean result; asrgs.p_accel = &ray_model->as; - result = createOrUpdateAccelerationStructure(vk_core.cb, &asrgs, ray_model); + ASSERT(!"Not implemented"); + result = false; + //result = createOrUpdateAccelerationStructure(vk_core.cb, &asrgs, ray_model); if (!result) { diff --git a/ref_vk/vk_render.c b/ref_vk/vk_render.c index 3b8be52d..271b45c7 100644 --- a/ref_vk/vk_render.c +++ b/ref_vk/vk_render.c @@ -592,7 +592,7 @@ void VK_RenderEnd( VkCommandBuffer cmdbuf ) vkCmdBindIndexBuffer(cmdbuf, g_render.buffer.buffer, 0, VK_INDEX_TYPE_UINT16); } - vkCmdBindDescriptorSets(vk_core.cb, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 3, 1, vk_desc.ubo_sets + 1, 1, &dlights_ubo_offset); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 3, 1, vk_desc.ubo_sets + 1, 1, &dlights_ubo_offset); for (int i = 0; i < g_render_state.num_draw_commands; ++i) { const draw_command_t *const draw = g_render_state.draw_commands + i; @@ -615,29 +615,29 @@ void VK_RenderEnd( VkCommandBuffer cmdbuf ) if (ubo_offset != draw->draw.ubo_offset) { ubo_offset = draw->draw.ubo_offset; - vkCmdBindDescriptorSets(vk_core.cb, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 0, 1, vk_desc.ubo_sets, 1, &ubo_offset); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 0, 1, vk_desc.ubo_sets, 1, &ubo_offset); } if (pipeline != draw->draw.draw.render_mode) { pipeline = draw->draw.draw.render_mode; - vkCmdBindPipeline(vk_core.cb, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipelines[pipeline]); + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipelines[pipeline]); } if (lightmap != draw->draw.draw.lightmap) { lightmap = draw->draw.draw.lightmap; - vkCmdBindDescriptorSets(vk_core.cb, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 2, 1, &findTexture(lightmap)->vk.descriptor, 0, NULL); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 2, 1, &findTexture(lightmap)->vk.descriptor, 0, NULL); } if (texture != draw->draw.draw.texture) { texture = draw->draw.draw.texture; // TODO names/enums for binding points - vkCmdBindDescriptorSets(vk_core.cb, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 1, 1, &findTexture(texture)->vk.descriptor, 0, NULL); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 1, 1, &findTexture(texture)->vk.descriptor, 0, NULL); } // Only indexed mode is supported ASSERT(draw->draw.draw.index_offset >= 0); - vkCmdDrawIndexed(vk_core.cb, draw->draw.draw.element_count, 1, draw->draw.draw.index_offset, draw->draw.draw.vertex_offset, 0); + vkCmdDrawIndexed(cmdbuf, draw->draw.draw.element_count, 1, draw->draw.draw.index_offset, draw->draw.draw.vertex_offset, 0); } } diff --git a/ref_vk/vk_scene.c b/ref_vk/vk_scene.c index ac68bcbb..15c3c097 100644 --- a/ref_vk/vk_scene.c +++ b/ref_vk/vk_scene.c @@ -152,12 +152,13 @@ void R_NewMap( void ) // RTX map loading requires command buffer for building blases if (vk_core.rtx) { - const VkCommandBufferBeginInfo beginfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, - }; - - XVK_CHECK(vkBeginCommandBuffer(vk_core.cb, &beginfo)); + ASSERT(!"Not implemented"); + /* const VkCommandBufferBeginInfo beginfo = { */ + /* .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, */ + /* .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, */ + /* }; */ + /* */ + /* XVK_CHECK(vkBeginCommandBuffer(vk_core.cb, &beginfo)); */ } // Load light entities and patch data prior to loading map brush model @@ -195,15 +196,16 @@ void R_NewMap( void ) if (vk_core.rtx) { - const VkSubmitInfo subinfo = { - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .commandBufferCount = 1, - .pCommandBuffers = &vk_core.cb, - }; - - XVK_CHECK(vkEndCommandBuffer(vk_core.cb)); - XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, VK_NULL_HANDLE)); - XVK_CHECK(vkQueueWaitIdle(vk_core.queue)); + ASSERT(!"Not implemented"); + /* const VkSubmitInfo subinfo = { */ + /* .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, */ + /* .commandBufferCount = 1, */ + /* .pCommandBuffers = &vk_core.cb, */ + /* }; */ + /* */ + /* XVK_CHECK(vkEndCommandBuffer(vk_core.cb)); */ + /* XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, VK_NULL_HANDLE)); */ + /* XVK_CHECK(vkQueueWaitIdle(vk_core.queue)); */ } // TODO should we do something like VK_BrushEndLoad? diff --git a/ref_vk/vk_swapchain.c b/ref_vk/vk_swapchain.c index b3a961bd..04ebd4b3 100644 --- a/ref_vk/vk_swapchain.c +++ b/ref_vk/vk_swapchain.c @@ -188,7 +188,7 @@ void R_VkSwapchainShutdown( void ) { destroySwapchainAndFramebuffers( g_swapchain.swapchain ); } -r_vk_swapchain_framebuffer_t R_VkSwapchainAcquire( VkSemaphore semaphore, VkFence fence ) { +r_vk_swapchain_framebuffer_t R_VkSwapchainAcquire( VkSemaphore sem_image_available ) { r_vk_swapchain_framebuffer_t ret = {0}; qboolean force_recreate = false; @@ -196,7 +196,7 @@ r_vk_swapchain_framebuffer_t R_VkSwapchainAcquire( VkSemaphore semaphore, VkFen // Check that swapchain has the same size recreateSwapchain(force_recreate); - const VkResult acquire_result = vkAcquireNextImageKHR(vk_core.device, g_swapchain.swapchain, UINT64_MAX, semaphore, VK_NULL_HANDLE, &ret.index); + const VkResult acquire_result = vkAcquireNextImageKHR(vk_core.device, g_swapchain.swapchain, UINT64_MAX, sem_image_available, VK_NULL_HANDLE, &ret.index); switch (acquire_result) { case VK_ERROR_OUT_OF_DATE_KHR: case VK_ERROR_SURFACE_LOST_KHR: diff --git a/ref_vk/vk_swapchain.h b/ref_vk/vk_swapchain.h index 9934e1ac..84bcadff 100644 --- a/ref_vk/vk_swapchain.h +++ b/ref_vk/vk_swapchain.h @@ -17,6 +17,6 @@ typedef struct { VkImageView view; } r_vk_swapchain_framebuffer_t; -r_vk_swapchain_framebuffer_t R_VkSwapchainAcquire( VkSemaphore semaphore, VkFence fence ); +r_vk_swapchain_framebuffer_t R_VkSwapchainAcquire( VkSemaphore sem_image_available ); void R_VkSwapchainPresent( uint32_t index, VkSemaphore done ); diff --git a/ref_vk/vk_textures.c b/ref_vk/vk_textures.c index 7245cf30..99d141d7 100644 --- a/ref_vk/vk_textures.c +++ b/ref_vk/vk_textures.c @@ -313,8 +313,7 @@ static void VK_CreateInternalTextures( void ) } } -static VkFormat VK_GetFormat(pixformat_t format) -{ +static VkFormat VK_GetFormat(pixformat_t format) { switch(format) { case PF_RGBA_32: return VK_FORMAT_R8G8B8A8_UNORM; @@ -324,8 +323,7 @@ static VkFormat VK_GetFormat(pixformat_t format) } } -static size_t CalcImageSize( pixformat_t format, int width, int height, int depth ) -{ +static size_t CalcImageSize( pixformat_t format, int width, int height, int depth ) { size_t size = 0; // check the depth error @@ -352,6 +350,9 @@ static size_t CalcImageSize( pixformat_t format, int width, int height, int dept case PF_ATI2: size = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth; break; + default: + gEngine.Con_Printf(S_ERROR "unsupported pixformat_t %d\n", format); + ASSERT(!"Unsupported format encountered"); } return size; @@ -469,6 +470,7 @@ static void BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, in static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, int num_layers, qboolean cubemap) { const VkFormat format = VK_GetFormat(layers[0]->type); int mipCount = 0; + const VkCommandBuffer cmdbuf = vk_core.upload_pool.buffers[0]; // TODO non-rbga textures @@ -557,8 +559,8 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, .layerCount = num_layers, }}; - XVK_CHECK(vkBeginCommandBuffer(vk_core.cb_tex, &beginfo)); - vkCmdPipelineBarrier(vk_core.cb_tex, + XVK_CHECK(vkBeginCommandBuffer(cmdbuf, &beginfo)); + vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &image_barrier); @@ -596,7 +598,7 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, } // TODO we could do this only once w/ region array - vkCmdCopyBufferToImage(vk_core.cb_tex, g_vk_buffers.staging.buffer, tex->vk.image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + vkCmdCopyBufferToImage(cmdbuf, g_vk_buffers.staging.buffer, tex->vk.image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); staging_offset += mip_size; } @@ -615,18 +617,18 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, .baseArrayLayer = 0, .layerCount = num_layers, }; - vkCmdPipelineBarrier(vk_core.cb_tex, + vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, &image_barrier); - XVK_CHECK(vkEndCommandBuffer(vk_core.cb_tex)); + XVK_CHECK(vkEndCommandBuffer(cmdbuf)); } { VkSubmitInfo subinfo = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO}; subinfo.commandBufferCount = 1; - subinfo.pCommandBuffers = &vk_core.cb_tex; + subinfo.pCommandBuffers = &cmdbuf; XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, VK_NULL_HANDLE)); XVK_CHECK(vkQueueWaitIdle(vk_core.queue)); } @@ -813,6 +815,9 @@ void VK_FreeTexture( unsigned int texnum ) { gEngine.FS_FreeImage( tex->original ); */ + // TODO how to do this properly? + XVK_CHECK(vkDeviceWaitIdle(vk_core.device)); + XVK_ImageDestroy(&tex->vk.image); memset(tex, 0, sizeof(*tex)); } From 9917cba8fc6768d53ecc540a891b677f2ac8f0f1 Mon Sep 17 00:00:00 2001 From: Andrey Akhmichin <15944199+nekonomicon@users.noreply.github.com> Date: Sat, 12 Feb 2022 07:42:09 +0500 Subject: [PATCH 150/548] utils: mdldec: add fixed uv coords support. --- utils/mdldec/smd.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/utils/mdldec/smd.c b/utils/mdldec/smd.c index 4a4f9884..4e80e3b0 100644 --- a/utils/mdldec/smd.c +++ b/utils/mdldec/smd.c @@ -262,8 +262,16 @@ static void WriteTriangleInfo( FILE *fp, mstudiomodel_t *model, mstudiotexture_t Matrix3x4_VectorRotate( bonetransform[bone_index], studionorms[norm_index], norm ); VectorNormalize( norm ); - u = ( triverts[indices[i]]->s + 1.0f ) * s; - v = 1.0f - triverts[indices[i]]->t * t; + if( texture->flags & STUDIO_NF_UV_COORDS ) + { + u = HalfToFloat( triverts[indices[i]]->s ); + v = -HalfToFloat( triverts[indices[i]]->t ); + } + else + { + u = ( triverts[indices[i]]->s + 1.0f ) * s; + v = 1.0f - triverts[indices[i]]->t * t; + } fprintf( fp, "%3i %f %f %f %f %f %f %f %f\n", bone_index, From e35e2aa043a137469e4d5035e6cefe1c7c026da1 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 20 Feb 2022 23:30:23 -0800 Subject: [PATCH 151/548] rt: allocate as many desc sets as frames in flight still not fully correct or robust, but passes validation missing correctness things: - Nx light data buffers - Nx dynamic model buffers - Nx dynamic BLASes quite likely will also need (depending on update strategy, direct vs upload): - Nx light data buffers - Nx UBOs probably fine since frames don't really overlap: - single TLAS --- ref_vk/ray_pass.c | 21 ++++++++++++--------- ref_vk/ray_pass.h | 2 +- ref_vk/vk_descriptor.c | 13 +++++++------ ref_vk/vk_descriptor.h | 2 +- ref_vk/vk_rtx.c | 10 +++++----- 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c index dfef38af..cf7a540d 100644 --- a/ref_vk/ray_pass.c +++ b/ref_vk/ray_pass.c @@ -3,6 +3,9 @@ #include "vk_pipeline.h" #include "vk_descriptor.h" +// FIXME this is only needed for MAX_CONCURRENT_FRAMES +#include "vk_framectl.h" + #define MAX_STAGES 16 #define MAX_MISS_GROUPS 8 #define MAX_HIT_GROUPS 8 @@ -18,7 +21,7 @@ typedef struct ray_pass_s { struct { vk_descriptors_t riptors; - VkDescriptorSet sets[1]; + VkDescriptorSet sets[MAX_CONCURRENT_FRAMES]; int *binding_semantics; } desc; } ray_pass_t; @@ -202,22 +205,22 @@ void RayPassDestroy( struct ray_pass_s *pass ) { Mem_Free(pass); } -static void performTracing( VkCommandBuffer cmdbuf, const ray_pass_tracing_impl_t *tracing, const struct vk_ray_resources_s *res) { +static void performTracing( VkCommandBuffer cmdbuf, int set_slot, const ray_pass_tracing_impl_t *tracing, const struct vk_ray_resources_s *res) { vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, tracing->pipeline.pipeline); - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, tracing->header.desc.riptors.pipeline_layout, 0, 1, tracing->header.desc.riptors.desc_sets + 0, 0, NULL); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, tracing->header.desc.riptors.pipeline_layout, 0, 1, tracing->header.desc.riptors.desc_sets + set_slot, 0, NULL); VK_PipelineRayTracingTrace(cmdbuf, &tracing->pipeline, res->width, res->height); } -static void performCompute( VkCommandBuffer cmdbuf, const ray_pass_compute_impl_t *compute, const struct vk_ray_resources_s *res) { +static void performCompute( VkCommandBuffer cmdbuf, int set_slot, const ray_pass_compute_impl_t *compute, const struct vk_ray_resources_s *res) { const uint32_t WG_W = 8; const uint32_t WG_H = 8; vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, compute->pipeline); - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, compute->header.desc.riptors.pipeline_layout, 0, 1, compute->header.desc.riptors.desc_sets + 0, 0, NULL); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, compute->header.desc.riptors.pipeline_layout, 0, 1, compute->header.desc.riptors.desc_sets + set_slot, 0, NULL); vkCmdDispatch(cmdbuf, (res->width + WG_W - 1) / WG_W, (res->height + WG_H - 1) / WG_H, 1); } -void RayPassPerform( VkCommandBuffer cmdbuf, struct ray_pass_s *pass, struct vk_ray_resources_s *res) { +void RayPassPerform( VkCommandBuffer cmdbuf, int frame_set_slot, struct ray_pass_s *pass, struct vk_ray_resources_s *res) { { ray_resources_fill_t fill = { .resources = res, @@ -239,7 +242,7 @@ void RayPassPerform( VkCommandBuffer cmdbuf, struct ray_pass_s *pass, struct vk_ RayResourcesFill(cmdbuf, fill); - VK_DescriptorsWrite(&pass->desc.riptors); + VK_DescriptorsWrite(&pass->desc.riptors, frame_set_slot); } DEBUG_BEGIN(cmdbuf, pass->debug_name); @@ -248,13 +251,13 @@ void RayPassPerform( VkCommandBuffer cmdbuf, struct ray_pass_s *pass, struct vk_ case RayPassType_Tracing: { ray_pass_tracing_impl_t *tracing = (ray_pass_tracing_impl_t*)pass; - performTracing(cmdbuf, tracing, res); + performTracing(cmdbuf, frame_set_slot, tracing, res); break; } case RayPassType_Compute: { ray_pass_compute_impl_t *compute = (ray_pass_compute_impl_t*)pass; - performCompute(cmdbuf, compute, res); + performCompute(cmdbuf, frame_set_slot, compute, res); break; } } diff --git a/ref_vk/ray_pass.h b/ref_vk/ray_pass.h index 3073fe20..fe2baef3 100644 --- a/ref_vk/ray_pass.h +++ b/ref_vk/ray_pass.h @@ -56,4 +56,4 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create void RayPassDestroy( struct ray_pass_s *pass ); struct vk_ray_resources_s; -void RayPassPerform( VkCommandBuffer cmdbuf, struct ray_pass_s *pass, struct vk_ray_resources_s *res); +void RayPassPerform( VkCommandBuffer cmdbuf, int frame_set_slot, struct ray_pass_s *pass, struct vk_ray_resources_s *res); diff --git a/ref_vk/vk_descriptor.c b/ref_vk/vk_descriptor.c index 5696ba71..581d43fe 100644 --- a/ref_vk/vk_descriptor.c +++ b/ref_vk/vk_descriptor.c @@ -143,14 +143,14 @@ void VK_DescriptorsCreate(vk_descriptors_t *desc) int j; for (j = 0; j < dpci.poolSizeCount; ++j) { if (pools[j].type == bind->descriptorType) { - pools[j].descriptorCount += bind->descriptorCount; + pools[j].descriptorCount += bind->descriptorCount * desc->num_sets; break; } } if (j == dpci.poolSizeCount) { ASSERT(dpci.poolSizeCount < ARRAYSIZE(pools)); - pools[j].descriptorCount = bind->descriptorCount; + pools[j].descriptorCount = bind->descriptorCount * desc->num_sets; pools[j].type = bind->descriptorType; ++dpci.poolSizeCount; } @@ -159,18 +159,19 @@ void VK_DescriptorsCreate(vk_descriptors_t *desc) XVK_CHECK(vkCreateDescriptorPool(vk_core.device, &dpci, NULL, &desc->desc_pool)); } + for (int i = 0; i < desc->num_sets; ++i) { VkDescriptorSetAllocateInfo dsai = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = desc->desc_pool, - .descriptorSetCount = desc->num_sets, + .descriptorSetCount = 1, .pSetLayouts = &desc->desc_layout, }; - XVK_CHECK(vkAllocateDescriptorSets(vk_core.device, &dsai, desc->desc_sets)); + XVK_CHECK(vkAllocateDescriptorSets(vk_core.device, &dsai, desc->desc_sets + i)); } } -void VK_DescriptorsWrite(const vk_descriptors_t *desc) +void VK_DescriptorsWrite(const vk_descriptors_t *desc, int set_slot) { VkWriteDescriptorSet wds[16]; ASSERT(ARRAYSIZE(wds) >= desc->num_bindings); @@ -180,7 +181,7 @@ void VK_DescriptorsWrite(const vk_descriptors_t *desc) .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .descriptorCount = binding->descriptorCount, .descriptorType = binding->descriptorType, - .dstSet = /* TODO */ desc->desc_sets[0], + .dstSet = desc->desc_sets[set_slot], .dstBinding = binding->binding, .dstArrayElement = 0, }; diff --git a/ref_vk/vk_descriptor.h b/ref_vk/vk_descriptor.h index 85c40110..8d4adb7f 100644 --- a/ref_vk/vk_descriptor.h +++ b/ref_vk/vk_descriptor.h @@ -51,7 +51,7 @@ typedef struct { } vk_descriptors_t; void VK_DescriptorsCreate(vk_descriptors_t *desc); -void VK_DescriptorsWrite(const vk_descriptors_t *desc); +void VK_DescriptorsWrite(const vk_descriptors_t *desc, int set_slot); void VK_DescriptorsDestroy(const vk_descriptors_t *desc); // typedef enum { diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 79fda9fc..5df9de2c 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -733,7 +733,7 @@ static void updateDescriptors( const vk_ray_frame_render_args_t *args, int frame .imageLayout = VK_IMAGE_LAYOUT_GENERAL, }; - VK_DescriptorsWrite(&g_rtx.descriptors); + VK_DescriptorsWrite(&g_rtx.descriptors, 0); } static qboolean rayTrace( VkCommandBuffer cmdbuf, const xvk_ray_frame_images_t *current_frame, float fov_angle_y ) { @@ -1066,10 +1066,10 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } - RayPassPerform( cmdbuf, g_rtx.pass.primary_ray, &res ); - RayPassPerform( cmdbuf, g_rtx.pass.light_direct_poly, &res ); - RayPassPerform( cmdbuf, g_rtx.pass.light_direct_point, &res ); - RayPassPerform( cmdbuf, g_rtx.pass.denoiser, &res ); + RayPassPerform( cmdbuf, frame_index, g_rtx.pass.primary_ray, &res ); + RayPassPerform( cmdbuf, frame_index, g_rtx.pass.light_direct_poly, &res ); + RayPassPerform( cmdbuf, frame_index, g_rtx.pass.light_direct_point, &res ); + RayPassPerform( cmdbuf, frame_index, g_rtx.pass.denoiser, &res ); { const xvk_blit_args blit_args = { From 3ebdeb876e8a9252ccc5d63af7c55209ce60011a Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 20 Feb 2022 23:40:48 -0800 Subject: [PATCH 152/548] differentiate debug markers vs validation layers Split concerns: - validation layers help with correctness, but not performance (*generally) - debug markers help with profiling (and ony sometimes with correctness too) These were conflated into a single `-vkdebug` arg. And sometimes you need only markers and no validation for performance. (Validation is like 30% slower) Make it so that: - `-vkdebug` enables only debug names and time markers, but not validation - `-vkvalidate` enables validation layers and also debug markers --- ref_vk/vk_core.c | 31 ++++++++++++++++--------------- ref_vk/vk_core.h | 2 +- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index b30361d4..5787f925 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -201,7 +201,7 @@ static qboolean createInstance( void ) create_info.enabledExtensionCount = num_instance_extensions; create_info.ppEnabledExtensionNames = instance_extensions; - if (vk_core.debug) + if (vk_core.validate) { create_info.enabledLayerCount = ARRAYSIZE(validation_layers); create_info.ppEnabledLayerNames = validation_layers; @@ -214,22 +214,22 @@ static qboolean createInstance( void ) loadInstanceFunctions(instance_funcs, ARRAYSIZE(instance_funcs)); - if (vk_core.debug) + if (vk_core.debug || vk_core.validate) { loadInstanceFunctions(instance_debug_funcs, ARRAYSIZE(instance_debug_funcs)); - if (vkCreateDebugUtilsMessengerEXT) - { - VkDebugUtilsMessengerCreateInfoEXT debug_create_info = { - .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, - .messageSeverity = 0x1111, //:vovka: VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, - .messageType = 0x07, - .pfnUserCallback = debugCallback, - }; - XVK_CHECK(vkCreateDebugUtilsMessengerEXT(vk_core.instance, &debug_create_info, NULL, &vk_core.debug_messenger)); - } else - { - gEngine.Con_Printf(S_WARN "Vulkan debug utils messenger is not available\n"); + if (vk_core.validate) { + if (vkCreateDebugUtilsMessengerEXT) { + VkDebugUtilsMessengerCreateInfoEXT debug_create_info = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .messageSeverity = 0x1111, //:vovka: VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, + .messageType = 0x07, + .pfnUserCallback = debugCallback, + }; + XVK_CHECK(vkCreateDebugUtilsMessengerEXT(vk_core.instance, &debug_create_info, NULL, &vk_core.debug_messenger)); + } else { + gEngine.Con_Printf(S_WARN "Vulkan debug utils messenger is not available\n"); + } } } @@ -641,7 +641,8 @@ qboolean R_VkInit( void ) { // FIXME !!!! handle initialization errors properly: destroy what has already been created - vk_core.debug = !!(gEngine.Sys_CheckParm("-vkdebug") || gEngine.Sys_CheckParm("-gldebug")); + vk_core.validate = !!gEngine.Sys_CheckParm("-vkvalidate"); + vk_core.debug = vk_core.validate || !!(gEngine.Sys_CheckParm("-vkdebug") || gEngine.Sys_CheckParm("-gldebug")); vk_core.rtx = false; VK_LoadCvars(); diff --git a/ref_vk/vk_core.h b/ref_vk/vk_core.h index 10da888b..f6f0e464 100644 --- a/ref_vk/vk_core.h +++ b/ref_vk/vk_core.h @@ -50,7 +50,7 @@ typedef struct vulkan_core_s { // TODO store important capabilities that affect render code paths // (as rtx, dedicated gpu memory, bindless, etc) separately in a struct - qboolean debug, rtx; + qboolean debug, validate, rtx; struct { VkSurfaceKHR surface; uint32_t num_surface_formats; From 18933e7981757b65f92aa6e632295fa59d6414c2 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 22 Feb 2022 06:08:32 +0300 Subject: [PATCH 153/548] engine: ensure all data was written to stdout when exiting --- engine/common/sys_con.c | 113 ++++++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 46 deletions(-) diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c index 01af58fa..96f4bb98 100644 --- a/engine/common/sys_con.c +++ b/engine/common/sys_con.c @@ -97,6 +97,20 @@ int Sys_LogFileNo( void ) return s_ld.logfileno; } +static void Sys_FlushStdout( void ) +{ + // never printing anything to stdout on mobiles +#if !XASH_MOBILE_PLATFORM + fflush( stdout ); +#endif +} + +static void Sys_FlushLogfile( void ) +{ + if( s_ld.logfile ) + fflush( s_ld.logfile ); +} + void Sys_InitLog( void ) { const char *mode; @@ -147,6 +161,8 @@ void Sys_CloseLog( void ) break; } + Sys_FlushStdout(); // flush to stdout to ensure all data was written + if( s_ld.logfile ) { fprintf( s_ld.logfile, "\n"); @@ -160,6 +176,50 @@ void Sys_CloseLog( void ) } } +static void Sys_PrintColorized( const char *logtime, const char *msg ) +{ + char colored[4096]; + int len = 0; + + while( *msg && ( len < 4090 ) ) + { + static char q3ToAnsi[ 8 ] = + { + '0', // COLOR_BLACK + '1', // COLOR_RED + '2', // COLOR_GREEN + '3', // COLOR_YELLOW + '4', // COLOR_BLUE + '6', // COLOR_CYAN + '5', // COLOR_MAGENTA + 0 // COLOR_WHITE + }; + + if( IsColorString( msg ) ) + { + int color; + + msg++; + color = q3ToAnsi[ *msg++ % 8 ]; + colored[len++] = '\033'; + colored[len++] = '['; + if( color ) + { + colored[len++] = '3'; + colored[len++] = color; + } + else + colored[len++] = '0'; + colored[len++] = 'm'; + } + else + colored[len++] = *msg++; + } + colored[len] = 0; + + printf( "\033[34m%s\033[0m%s\033[0m", logtime, colored ); +} + void Sys_PrintLog( const char *pMsg ) { time_t crt_time; @@ -169,6 +229,8 @@ void Sys_PrintLog( const char *pMsg ) time( &crt_time ); crt_tm = localtime( &crt_time ); + + // platform-specific output #if XASH_ANDROID && !XASH_DEDICATED __android_log_print( ANDROID_LOG_DEBUG, "Xash", "%s", pMsg ); #endif @@ -178,58 +240,17 @@ void Sys_PrintLog( const char *pMsg ) IOS_Log(pMsg); #endif - if( !lastchar || lastchar == '\n') strftime( logtime, sizeof( logtime ), "[%H:%M:%S] ", crt_tm ); //short time + // spew to stdout, except mobiles +#if !XASH_MOBILE_PLATFORM #ifdef XASH_COLORIZE_CONSOLE - { - char colored[4096]; - const char *msg = pMsg; - int len = 0; - while( *msg && ( len < 4090 ) ) - { - static char q3ToAnsi[ 8 ] = - { - '0', // COLOR_BLACK - '1', // COLOR_RED - '2', // COLOR_GREEN - '3', // COLOR_YELLOW - '4', // COLOR_BLUE - '6', // COLOR_CYAN - '5', // COLOR_MAGENTA - 0 // COLOR_WHITE - }; - - if( IsColorString( msg ) ) - { - int color; - - msg++; - color = q3ToAnsi[ *msg++ % 8 ]; - colored[len++] = '\033'; - colored[len++] = '['; - if( color ) - { - colored[len++] = '3'; - colored[len++] = color; - } - else - colored[len++] = '0'; - colored[len++] = 'm'; - } - else - colored[len++] = *msg++; - } - colored[len] = 0; - printf( "\033[34m%s\033[0m%s\033[0m", logtime, colored ); - - } + Sys_PrintColorized( logtime, pMsg ); #else -#if !XASH_ANDROID || XASH_DEDICATED printf( "%s %s", logtime, pMsg ); - fflush( stdout ); #endif + Sys_FlushStdout(); #endif // save last char to detect when line was not ended @@ -242,7 +263,7 @@ void Sys_PrintLog( const char *pMsg ) strftime( logtime, sizeof( logtime ), "[%Y:%m:%d|%H:%M:%S]", crt_tm ); //full time fprintf( s_ld.logfile, "%s %s", logtime, pMsg ); - fflush( s_ld.logfile ); + Sys_FlushLogfile(); } /* From 3f7773f83bda59aa897b23ce7d9386cd2226732c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 22 Feb 2022 09:34:28 +0300 Subject: [PATCH 154/548] engine: client: rewrite VOX subsystem, fix spaces being taken into sentences, add tests for parser, remove dead code --- engine/client/s_vox.c | 1094 +++++++++++++++++++---------------------- engine/client/vox.h | 13 +- engine/common/host.c | 3 + engine/common/tests.h | 1 + 4 files changed, 513 insertions(+), 598 deletions(-) diff --git a/engine/client/s_vox.c b/engine/client/s_vox.c index b421fe35..f2060257 100644 --- a/engine/client/s_vox.c +++ b/engine/client/s_vox.c @@ -16,407 +16,12 @@ GNU General Public License for more details. #include "common.h" #include "sound.h" #include "const.h" +#include "sequence.h" #include -sentence_t g_Sentences[MAX_SENTENCES]; -static uint g_numSentences; -static char *rgpparseword[CVOXWORDMAX]; // array of pointers to parsed words -static char voxperiod[] = "_period"; // vocal pause -static char voxcomma[] = "_comma"; // vocal pause - -static int IsNextWord( const char c ) -{ - if( c == '.' || c == ',' || c == ' ' || c == '(' ) - return 1; - return 0; -} - -static int IsSkipSpace( const char c ) -{ - if( c == ',' || c == '.' || c == ' ' ) - return 1; - return 0; -} - -static int IsWhiteSpace( const char space ) -{ - if( space == ' ' || space == '\t' || space == '\r' || space == '\n' ) - return 1; - return 0; -} - -static int IsCommandChar( const char c ) -{ - if( c == 'v' || c == 'p' || c == 's' || c == 'e' || c == 't' ) - return 1; - return 0; -} - -static int IsDelimitChar( const char c ) -{ - if( c == '(' || c == ')' ) - return 1; - return 0; -} - -static char *ScanForwardUntil( char *string, const char scan ) -{ - while( string[0] ) - { - if( string[0] == scan ) - return string; - string++; - } - return string; -} - -// backwards scan psz for last '/' -// return substring in szpath null terminated -// if '/' not found, return 'vox/' -static char *VOX_GetDirectory( char *szpath, char *psz ) -{ - char c; - int cb = 0, len; - char *p; - - len = Q_strlen( psz ); - p = psz + len - 1; - - // scan backwards until first '/' or start of string - c = *p; - while( p > psz && c != '/' ) - { - c = *( --p ); - cb++; - } - - if( c != '/' ) - { - // didn't find '/', return default directory - Q_strcpy( szpath, "vox/" ); - return psz; - } - - cb = len - cb; - memcpy( szpath, psz, cb ); - szpath[cb] = 0; - - return p + 1; -} - -// scan g_Sentences, looking for pszin sentence name -// return pointer to sentence data if found, null if not -// CONSIDER: if we have a large number of sentences, should -// CONSIDER: sort strings in g_Sentences and do binary search. -char *VOX_LookupString( const char *pSentenceName, int *psentencenum ) -{ - int i; - - if( Q_isdigit( pSentenceName ) && (i = Q_atoi( pSentenceName )) < g_numSentences ) - { - if( psentencenum ) *psentencenum = i; - return (g_Sentences[i].pName + Q_strlen( g_Sentences[i].pName ) + 1 ); - } - - for( i = 0; i < g_numSentences; i++ ) - { - if( !Q_stricmp( pSentenceName, g_Sentences[i].pName )) - { - if( psentencenum ) *psentencenum = i; - return (g_Sentences[i].pName + Q_strlen( g_Sentences[i].pName ) + 1 ); - } - } - - return NULL; -} - -// parse a null terminated string of text into component words, with -// pointers to each word stored in rgpparseword -// note: this code actually alters the passed in string! -char **VOX_ParseString( char *psz ) -{ - int i, fdone = 0; - char c, *p = psz; - - memset( rgpparseword, 0, sizeof( char* ) * CVOXWORDMAX ); - - if( !psz ) return NULL; - - i = 0; - rgpparseword[i++] = psz; - - while( !fdone && i < CVOXWORDMAX ) - { - // scan up to next word - c = *p; - while( c && !IsNextWord( c )) - c = *(++p); - - // if '(' then scan for matching ')' - if( c == '(' ) - { - p = ScanForwardUntil( p, ')' ); - c = *(++p); - if( !c ) fdone = 1; - } - - if( fdone || !c ) - { - fdone = 1; - } - else - { - // if . or , insert pause into rgpparseword, - // unless this is the last character - if(( c == '.' || c == ',' ) && *(p+1) != '\n' && *(p+1) != '\r' && *(p+1) != 0 ) - { - if( c == '.' ) rgpparseword[i++] = voxperiod; - else rgpparseword[i++] = voxcomma; - - if( i >= CVOXWORDMAX ) - break; - } - - // null terminate substring - *p++ = 0; - - // skip whitespace - c = *p; - while( c && IsSkipSpace( c )) - c = *(++p); - - if( !c ) fdone = 1; - else rgpparseword[i++] = p; - } - } - - return rgpparseword; -} - -float VOX_GetVolumeScale( channel_t *pchan ) -{ - if( pchan->currentWord ) - { - if ( pchan->words[pchan->wordIndex].volume ) - { - float volume = pchan->words[pchan->wordIndex].volume * 0.01f; - if( volume < 1.0f ) return volume; - } - } - - return 1.0f; -} - -void VOX_SetChanVol( channel_t *ch ) -{ - float scale; - - if( !ch->currentWord ) - return; - - scale = VOX_GetVolumeScale( ch ); - if( scale == 1.0f ) return; - - ch->rightvol = (int)(ch->rightvol * scale); - ch->leftvol = (int)(ch->leftvol * scale); -} - -float VOX_ModifyPitch( channel_t *ch, float pitch ) -{ - if( ch->currentWord ) - { - if( ch->words[ch->wordIndex].pitch > 0 ) - { - pitch += ( ch->words[ch->wordIndex].pitch - PITCH_NORM ) * 0.01f; - } - } - - return pitch; -} - -//=============================================================================== -// Get any pitch, volume, start, end params into voxword -// and null out trailing format characters -// Format: -// someword(v100 p110 s10 e20) -// -// v is volume, 0% to n% -// p is pitch shift up 0% to n% -// s is start wave offset % -// e is end wave offset % -// t is timecompression % -// -// pass fFirst == 1 if this is the first string in sentence -// returns 1 if valid string, 0 if parameter block only. -// -// If a ( xxx ) parameter block does not directly follow a word, -// then that 'default' parameter block will be used as the default value -// for all following words. Default parameter values are reset -// by another 'default' parameter block. Default parameter values -// for a single word are overridden for that word if it has a parameter block. -// -//=============================================================================== -int VOX_ParseWordParams( char *psz, voxword_t *pvoxword, int fFirst ) -{ - char *pszsave = psz; - char c, ct, sznum[8]; - static voxword_t voxwordDefault; - int i; - - // init to defaults if this is the first word in string. - if( fFirst ) - { - voxwordDefault.pitch = -1; - voxwordDefault.volume = 100; - voxwordDefault.start = 0; - voxwordDefault.end = 100; - voxwordDefault.fKeepCached = 0; - voxwordDefault.timecompress = 0; - } - - *pvoxword = voxwordDefault; - - // look at next to last char to see if we have a - // valid format: - c = *( psz + Q_strlen( psz ) - 1 ); - - // no formatting, return - if( c != ')' ) return 1; - - // scan forward to first '(' - c = *psz; - while( !IsDelimitChar( c )) - c = *(++psz); - - // bogus formatting - if( c == ')' ) return 0; - - // null terminate - *psz = 0; - ct = *(++psz); - - while( 1 ) - { - // scan until we hit a character in the commandSet - while( ct && !IsCommandChar( ct )) - ct = *(++psz); - - if( ct == ')' ) - break; - - memset( sznum, 0, sizeof( sznum )); - i = 0; - - c = *(++psz); - - if( !isdigit( c )) - break; - - // read number - while( isdigit( c ) && i < sizeof( sznum ) - 1 ) - { - sznum[i++] = c; - c = *(++psz); - } - - // get value of number - i = Q_atoi( sznum ); - - switch( ct ) - { - case 'v': pvoxword->volume = i; break; - case 'p': pvoxword->pitch = i; break; - case 's': pvoxword->start = i; break; - case 'e': pvoxword->end = i; break; - case 't': pvoxword->timecompress = i; break; - } - - ct = c; - } - - // if the string has zero length, this was an isolated - // parameter block. Set default voxword to these - // values - if( Q_strlen( pszsave ) == 0 ) - { - voxwordDefault = *pvoxword; - return 0; - } - - return 1; -} - -void VOX_LoadWord( channel_t *pchan ) -{ - if( pchan->words[pchan->wordIndex].sfx ) - { - wavdata_t *pSource = S_LoadSound( pchan->words[pchan->wordIndex].sfx ); - - if( pSource ) - { - int start = pchan->words[pchan->wordIndex].start; - int end = pchan->words[pchan->wordIndex].end; - - // apply mixer - pchan->currentWord = &pchan->pMixer; - pchan->currentWord->pData = pSource; - - // don't allow overlapped ranges - if( end <= start ) end = 0; - - if( start || end ) - { - int sampleCount = pSource->samples; - - if( start ) - { - S_SetSampleStart( pchan, pSource, (int)(sampleCount * 0.01f * start)); - } - - if( end ) - { - S_SetSampleEnd( pchan, pSource, (int)(sampleCount * 0.01f * end)); - } - } - } - } -} - -void VOX_FreeWord( channel_t *pchan ) -{ - pchan->currentWord = NULL; // sentence is finished - memset( &pchan->pMixer, 0, sizeof( pchan->pMixer )); - - // release unused sounds - if( pchan->words[pchan->wordIndex].sfx ) - { - // If this wave wasn't precached by the game code - if( !pchan->words[pchan->wordIndex].fKeepCached ) - { - FS_FreeSound( pchan->words[pchan->wordIndex].sfx->cache ); - pchan->words[pchan->wordIndex].sfx->cache = NULL; - pchan->words[pchan->wordIndex].sfx = NULL; - } - } -} - -void VOX_LoadFirstWord( channel_t *pchan, voxword_t *pwords ) -{ - int i = 0; - - // copy each pointer in the sfx temp array into the - // sentence array, and set the channel to point to the - // sentence array - while( pwords[i].sfx != NULL ) - { - pchan->words[i] = pwords[i]; - i++; - } - pchan->words[i].sfx = NULL; - - pchan->wordIndex = 0; - VOX_LoadWord( pchan ); -} +static int cszrawsentences = 0; +static char *rgpszrawsentence[CVOXFILESENTENCEMAX]; +static char *voxperiod = "_period", *voxcomma = "_comma"; // return number of samples mixed int VOX_MixDataToDevice( channel_t *pchan, int sampleCount, int outputRate, int outputOffset ) @@ -451,244 +56,557 @@ int VOX_MixDataToDevice( channel_t *pchan, int sampleCount, int outputRate, int return outputOffset - startingOffset; } -// link all sounds in sentence, start playing first word. -void VOX_LoadSound( channel_t *pchan, const char *pszin ) +void VOX_LoadWord( channel_t *ch ) { - char buffer[512]; - int i, cword; - char pathbuffer[64]; - char szpath[32]; - voxword_t rgvoxword[CVOXWORDMAX]; - char *psz; + const voxword_t *word = &ch->words[ch->wordIndex]; + wavdata_t *data; + int start, end, samples; - if( !pszin || !*pszin ) + if( !word->sfx ) return; - memset( rgvoxword, 0, sizeof( voxword_t ) * CVOXWORDMAX ); - memset( buffer, 0, sizeof( buffer )); + data = S_LoadSound( word->sfx ); - // lookup actual string in g_Sentences, - // set pointer to string data - if( pszin[0] == '#' ) - psz = (char *)pszin + 1; - else - psz = VOX_LookupString( pszin, NULL ); + if( !data ) + return; + + ch->currentWord = &ch->pMixer; + ch->currentWord->pData = data; + + samples = data->samples; + start = word->start; + end = word->end; + + if( end <= start ) end = 0; + + if( start ) + S_SetSampleStart( ch, data, start * 0.01f * samples ); + + if( end ) + S_SetSampleEnd( ch, data, end * 0.01f * samples ); +} + +void VOX_FreeWord( channel_t *ch ) +{ + voxword_t *word = &ch->words[ch->wordIndex]; + + ch->currentWord = NULL; + memset( &ch->pMixer, 0, sizeof( ch->pMixer )); + + if( !word->sfx && !word->fKeepCached ) + return; + + FS_FreeSound( word->sfx->cache ); + word->sfx->cache = NULL; + word->sfx = NULL; +} + +void VOX_SetChanVol( channel_t *ch ) +{ + voxword_t *word; + if( !ch->currentWord ) + return; + + word = &ch->words[ch->wordIndex]; + + if( word->volume == 100 ) + return; + + ch->leftvol = ch->leftvol * word->volume * 0.01f; + ch->rightvol = ch->rightvol * word->volume * 0.01f; +} + +float VOX_ModifyPitch( channel_t *ch, float pitch ) +{ + voxword_t *word; + if( !ch->currentWord ) + return pitch; + + word = &ch->words[ch->wordIndex]; + + if( word->pitch < 0 ) + return pitch; + + pitch += ( word->pitch - PITCH_NORM ) * 0.01f; + + return pitch; +} + +static const char *VOX_GetDirectory( char *szpath, const char *psz, int nsize ) +{ + const char *p; + int len; + + // search / backwards + p = Q_strrchr( psz, '/' ); + + if( !p ) + { + Q_strcpy( szpath, "vox/" ); + return psz; + } + + len = p - psz + 1; + + if( len > nsize ) + { + Con_Printf( "VOX_GetDirectory: invalid directory in: %s\n", psz ); + return NULL; + } + + memcpy( szpath, psz, len ); + szpath[len] = 0; + + return p + 1; +} + +static const char *VOX_LookupString( const char *pszin ) +{ + int i = -1, len; + const char *c; + + // check if we are a CSCZ or immediate sentence + if( *pszin == '#' ) + { + // Q_atoi is too smart and allows negative values + // so check with Q_isdigit beforehand + if( Q_isdigit( pszin + 1 )) + { + sentenceEntry_s *sentenceEntry; + i = Q_atoi( pszin + 1 ); + if(( sentenceEntry = Sequence_GetSentenceByIndex( i ))) + return sentenceEntry->data; + } + else + { + // immediate sentence, probably coming from "speak" command + return pszin + 1; + } + } + + // check if we received an index + if( Q_isdigit( pszin )) + { + i = Q_atoi( pszin ); + + if( i >= cszrawsentences ) + i = -1; + } + + // last hope: find it in sentences array + if( i == -1 ) + { + for( i = 0; i < cszrawsentences; i++ ) + { + if( !Q_stricmp( pszin, rgpszrawsentence[i] )) + break; + } + } + + // not found, exit + if( i == cszrawsentences ) + return NULL; + + len = Q_strlen( rgpszrawsentence[i] ); + + c = &rgpszrawsentence[i][len + 1]; + for( ; *c == ' ' || *c == '\t'; c++ ); + + return c; +} + +static int VOX_ParseString( char *psz, char *rgpparseword[CVOXWORDMAX] ) +{ + int i = 0; + + if( !psz ) + return i; + + rgpparseword[i++] = psz; + + while( i < CVOXWORDMAX ) + { + // skip to next word + for( ; *psz && + *psz != ' ' && + *psz != '.' && + *psz != ','; psz++ ); + + // skip anything in between ( and ) + if( *psz == '(' ) + { + for( ; *psz && *psz != ')'; psz++ ); + psz++; + } + + if( !*psz ) + return i; + + // . and , are special but if not end of string + if(( *psz == '.' || *psz == ',' ) && + psz[1] != '\n' && psz[1] != '\r' && psz[1] != '\0' ) + { + if( *psz == '.' ) + rgpparseword[i++] = voxperiod; + else rgpparseword[i++] = voxcomma; + + if( i >= CVOXWORDMAX ) + return i; + } + + *psz++ = 0; + + for( ; *psz && ( *psz == '.' || *psz == ' ' || *psz == ',' ); + psz++ ); + + if( !*psz ) + return i; + + rgpparseword[i++] = psz; + } + + return i; +} + +static qboolean VOX_ParseWordParams( char *psz, voxword_t *pvoxword, qboolean fFirst ) +{ + int len, i; + char sznum[8], *pszsave = psz; + static voxword_t voxwordDefault; + + if( fFirst ) + { + voxwordDefault.fKeepCached = 0; + voxwordDefault.pitch = -1; + voxwordDefault.volume = 100; + voxwordDefault.start = 0; + voxwordDefault.end = 100; + voxwordDefault.timecompress = 0; + } + + *pvoxword = voxwordDefault; + + len = Q_strlen( psz ); + + if( len == 0 ) + return false; + + // no special params + if( psz[len-1] != ')' ) + return true; + + for( ; *psz != '(' && *psz != ')'; psz++ ); + + // invalid syntax + if( *psz == ')' ) + return false; + + // split filename and params + *psz++ = '\0'; + + for( ;; ) + { + char command; + + // find command + for( ; *psz && + *psz != 'v' && + *psz != 'p' && + *psz != 's' && + *psz != 'e' && + *psz != 't'; psz++ ) + { + if( *psz == ')' ) + break; + } + + command = *psz++; + + if( !isdigit( *psz )) + break; + + memset( sznum, 0, sizeof( sznum )); + for( i = 0; i < sizeof( sznum ) - 1 && isdigit( *psz ); i++, psz++ ) + sznum[i] = *psz; + + i = Q_atoi( sznum ); + switch( command ) + { + case 'e': pvoxword->end = i; break; + case 'p': pvoxword->pitch = i; break; + case 's': pvoxword->start = i; break; + case 't': pvoxword->timecompress = i; break; + case 'v': pvoxword->volume = i; break; + } + } + + // no actual word but new defaults + if( Q_strlen( pszsave ) == 0 ) + { + voxwordDefault = *pvoxword; + return false; + } + + return true; +} + +void VOX_LoadSound( channel_t *ch, const char *pszin ) +{ + char buffer[512], szpath[32], pathbuffer[64]; + char *rgpparseword[CVOXWORDMAX]; + const char *psz; + int i, j; + + if( !pszin ) + return; + + memset( buffer, 0, sizeof( buffer )); + memset( rgpparseword, 0, sizeof( rgpparseword )); + + psz = VOX_LookupString( pszin ); if( !psz ) { - Con_DPrintf( S_ERROR "VOX_LoadSound: no such sentence %s\n", pszin ); + Con_Printf( "VOX_LoadSound: no sentence named %s\n", pszin ); return; } - // get directory from string, advance psz - psz = VOX_GetDirectory( szpath, psz ); + psz = VOX_GetDirectory( szpath, psz, sizeof( szpath )); - if( Q_strlen( psz ) > sizeof( buffer ) - 1 ) + if( !psz ) { - Con_Printf( S_ERROR "VOX_LoadSound: sentence is too long %s\n", psz ); + Con_Printf( "VOX_LoadSound: failed getting directory for %s\n", pszin ); return; } - // copy into buffer - Q_strcpy( buffer, psz ); - psz = buffer; - - // parse sentence (also inserts null terminators between words) - VOX_ParseString( psz ); - - // for each word in the sentence, construct the filename, - // lookup the sfx and save each pointer in a temp array - - i = 0; - cword = 0; - while( rgpparseword[i] ) + if( Q_strlen( psz ) >= sizeof( buffer ) ) { - // Get any pitch, volume, start, end params into voxword - if( VOX_ParseWordParams( rgpparseword[i], &rgvoxword[cword], i == 0 )) - { - // this is a valid word (as opposed to a parameter block) - Q_strcpy( pathbuffer, szpath ); - Q_strncat( pathbuffer, rgpparseword[i], sizeof( pathbuffer )); - Q_strncat( pathbuffer, ".wav", sizeof( pathbuffer )); - - // find name, if already in cache, mark voxword - // so we don't discard when word is done playing - rgvoxword[cword].sfx = S_FindName( pathbuffer, &( rgvoxword[cword].fKeepCached )); - cword++; - } - i++; + Con_Printf( "VOX_LoadSound: sentence is too long %s", psz ); + return; } - VOX_LoadFirstWord( pchan, rgvoxword ); + Q_strncpy( buffer, psz, sizeof( buffer )); + VOX_ParseString( buffer, rgpparseword ); - pchan->isSentence = true; - pchan->sfx = rgvoxword[0].sfx; + j = 0; + for( i = 0; rgpparseword[i]; i++ ) + { + if( !VOX_ParseWordParams( rgpparseword[i], &ch->words[j], i == 0 )) + continue; + + Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, rgpparseword[i] ); + + ch->words[j].sfx = S_FindName( pathbuffer, &ch->words[j].fKeepCached ); + + j++; + } + + ch->words[j].sfx = NULL; + ch->sfx = ch->words[0].sfx; + ch->wordIndex = 0; + ch->isSentence = true; + + VOX_LoadWord( ch ); } -//----------------------------------------------------------------------------- -// Purpose: Take a NULL terminated sentence, and parse any commands contained in -// {}. The string is rewritten in place with those commands removed. -// -// Input : *pSentenceData - sentence data to be modified in place -// sentenceIndex - global sentence table index for any data that is -// parsed out -//----------------------------------------------------------------------------- -void VOX_ParseLineCommands( char *pSentenceData, int sentenceIndex ) +static void VOX_ReadSentenceFile_( byte *buf, fs_offset_t size ) { - char tempBuffer[512]; - char *pNext, *pStart; - int length, tempBufferPos = 0; + char *p, *last; - if( !pSentenceData ) - return; + p = (char *)buf; + last = p + size; - pStart = pSentenceData; - - while( *pSentenceData ) + while( p < last ) { - pNext = ScanForwardUntil( pSentenceData, '{' ); + char *name = NULL, *value = NULL; - // find length of "good" portion of the string (not a {} command) - length = pNext - pSentenceData; - if( tempBufferPos + length > sizeof( tempBuffer )) + if( cszrawsentences >= CVOXFILESENTENCEMAX ) + break; + + for( ; p < last && ( *p == '\n' || *p == '\r' || *p == '\t' || *p == ' ' ); + p++ ); + + if( *p != '/' ) { - Con_Printf( S_ERROR "Sentence too long (max length %lu characters)\n", sizeof(tempBuffer) - 1 ); - return; + name = p; + + for( ; p < last && *p != ' ' && *p != '\t' ; p++ ); + + if( p < last ) + *p++ = 0; + + value = p; } - // Copy good string to temp buffer - memcpy( tempBuffer + tempBufferPos, pSentenceData, length ); + for( ; p < last && *p != '\n' && *p != '\r'; p++ ); - // move the copy position - tempBufferPos += length; + if( p < last ) + *p++ = 0; - pSentenceData = pNext; - - // skip ahead of the opening brace - if( *pSentenceData ) pSentenceData++; - - // skip whitespace - while( *pSentenceData && *pSentenceData <= 32 ) - pSentenceData++; - - // simple comparison of string commands: - switch( Q_tolower( *pSentenceData )) + if( name ) { - case 'l': - // all commands starting with the letter 'l' here - if( !Q_strnicmp( pSentenceData, "len", 3 )) - { - g_Sentences[sentenceIndex].length = Q_atof( pSentenceData + 3 ); - } - break; - case 0: - default: - break; + int index = cszrawsentences; + int size = strlen( name ) + strlen( value ) + 2; + + rgpszrawsentence[index] = Mem_Malloc( host.mempool, size ); + memcpy( rgpszrawsentence[index], name, size ); + rgpszrawsentence[index][size - 1] = 0; + cszrawsentences++; } - - pSentenceData = ScanForwardUntil( pSentenceData, '}' ); - - // skip the closing brace - if( *pSentenceData ) pSentenceData++; - - // skip trailing whitespace - while( *pSentenceData && *pSentenceData <= 32 ) - pSentenceData++; - } - - if( tempBufferPos < sizeof( tempBuffer )) - { - // terminate cleaned up copy - tempBuffer[tempBufferPos] = 0; - - // copy it over the original data - Q_strcpy( pStart, tempBuffer ); } } -// Load sentence file into memory, insert null terminators to -// delimit sentence name/sentence pairs. Keep pointer to each -// sentence name so we can search later. -void VOX_ReadSentenceFile( const char *psentenceFileName ) +static void VOX_ReadSentenceFile( char *path ) { - char c, *pch, *pFileData; - char *pchlast, *pSentenceData; - fs_offset_t fileSize; + byte *buf; + fs_offset_t size; - // load file - pFileData = (char *)FS_LoadFile( psentenceFileName, &fileSize, false ); - if( !pFileData ) return; // this game just doesn't used vox sound system + VOX_Shutdown(); - pch = pFileData; - pchlast = pch + fileSize; + buf = FS_LoadFile( path, &size, false ); + if( !buf ) return; - while( pch < pchlast ) - { - if( g_numSentences >= MAX_SENTENCES ) - { - Con_Printf( S_ERROR "VOX_Init: too many sentences specified, max is %d\n", MAX_SENTENCES ); - break; - } + VOX_ReadSentenceFile_( buf, size ); - // only process this pass on sentences - pSentenceData = NULL; - - // skip newline, cr, tab, space - - c = *pch; - while( pch < pchlast && IsWhiteSpace( c )) - c = *(++pch); - - // skip entire line if first char is / - if( *pch != '/' ) - { - sentence_t *pSentence = &g_Sentences[g_numSentences++]; - - pSentence->pName = pch; - pSentence->length = 0; - - // scan forward to first space, insert null terminator - // after sentence name - - c = *pch; - while( pch < pchlast && c != ' ' ) - c = *(++pch); - - if( pch < pchlast ) - *pch++ = 0; - - // a sentence may have some line commands, make an extra pass - pSentenceData = pch; - } - - // scan forward to end of sentence or eof - while( pch < pchlast && pch[0] != '\n' && pch[0] != '\r' ) - pch++; - - // insert null terminator - if( pch < pchlast ) *pch++ = 0; - - // If we have some sentence data, parse out any line commands - if( pSentenceData && pSentenceData < pchlast ) - { - int index = g_numSentences - 1; - - // the current sentence has an index of count-1 - VOX_ParseLineCommands( pSentenceData, index ); - } - } + Mem_Free( buf ); } void VOX_Init( void ) { - memset( g_Sentences, 0, sizeof( g_Sentences )); - g_numSentences = 0; - VOX_ReadSentenceFile( DEFAULT_SOUNDPATH "sentences.txt" ); } - void VOX_Shutdown( void ) { - g_numSentences = 0; + int i; + + for( i = 0; i < cszrawsentences; i++ ) + Mem_Free( rgpszrawsentence[i] ); + + cszrawsentences = 0; } + +#if XASH_ENGINE_TESTS +#include "tests.h" + +static void Test_VOX_GetDirectory( void ) +{ + const char *data[] = + { + "", "", "vox/", + "bark bark", "bark bark", "vox/", + "barney/meow", "meow", "barney/" + + }; + int i; + + for( i = 0; i < sizeof( data ) / sizeof( data[0] ); i += 3 ) + { + string szpath; + const char *p = VOX_GetDirectory( szpath, data[i+0], sizeof( szpath )); + + TASSERT_STR( p, data[i+1] ); + TASSERT_STR( szpath, data[i+2] ); + } +} + +static void Test_VOX_LookupString( void ) +{ + int i; + const char *p, *data[] = + { + "0", "123", + "3", "SPAAACE", + "-2", NULL, + "404", NULL, + "not found", NULL, + "exactmatch", "123", + "caseinsensitive", "456", + "SentenceWithTabs", "789", + "SentenceWithSpaces", "SPAAACE", + }; + + VOX_Shutdown(); + + rgpszrawsentence[cszrawsentences++] = "exactmatch\000123"; + rgpszrawsentence[cszrawsentences++] = "CaseInsensitive\000456"; + rgpszrawsentence[cszrawsentences++] = "SentenceWithTabs\0\t\t\t789"; + rgpszrawsentence[cszrawsentences++] = "SentenceWithSpaces\0 SPAAACE"; + rgpszrawsentence[cszrawsentences++] = "SentenceWithTabsAndSpaces\0\t \t\t MEOW"; + + for( i = 0; i < sizeof( data ) / sizeof( data[0] ); i += 2 ) + { + p = VOX_LookupString( data[i] ); + + TASSERT_STR( p, data[i+1] ); + } + + cszrawsentences = 0; +} + +static void Test_VOX_ParseString( void ) +{ + char *rgpparseword[CVOXWORDMAX]; + const char *data[] = + { + "(p100) my ass is, heavy!(p80) clik.", + "(p100)", "my", "ass", "is", "_comma", "heavy!(p80)", "clik", NULL, + "freeman...", + "freeman", "_period", NULL, + }; + int i = 0; + + while( i < sizeof( data ) / sizeof( data[0] )) + { + char buffer[4096]; + int wordcount, j = 0; + Q_strncpy( buffer, data[i], sizeof( buffer )); + wordcount = VOX_ParseString( buffer, rgpparseword ); + + i++; + + while( data[i] ) + { + TASSERT_STR( data[i], rgpparseword[j] ); + i++; + j++; + } + + TASSERT( j == wordcount ); + + i++; + } +} + +static void Test_VOX_ParseWordParams( void ) +{ + string buffer; + qboolean ret; + voxword_t word; + + Q_strncpy( buffer, "heavy!(p80)", sizeof( buffer )); + ret = VOX_ParseWordParams( buffer, &word, true ); + TASSERT( word.pitch == 80 ); + TASSERT( ret ); + + Q_strncpy( buffer, "(p105)", sizeof( buffer )); + ret = VOX_ParseWordParams( buffer, &word, false ); + TASSERT( word.pitch == 105 ); + TASSERT( !ret ); + + Q_strncpy( buffer, "quiet(v50)", sizeof( buffer )); + ret = VOX_ParseWordParams( buffer, &word, false ); + TASSERT( word.pitch == 105 ); // defaulted + TASSERT( word.volume == 50 ); + TASSERT( !ret ); +} + +void Test_RunVOX( void ) +{ + TRUN( Test_VOX_GetDirectory() ); + TRUN( Test_VOX_LookupString() ); + TRUN( Test_VOX_ParseString() ); + TRUN( Test_VOX_ParseWordParams() ); +} + +#endif /* XASH_ENGINE_TESTS */ diff --git a/engine/client/vox.h b/engine/client/vox.h index 8e6e557b..56967959 100644 --- a/engine/client/vox.h +++ b/engine/client/vox.h @@ -16,9 +16,9 @@ GNU General Public License for more details. #ifndef VOX_H #define VOX_H -#define CVOXWORDMAX 64 -#define CVOXZEROSCANMAX 255 // scan up to this many samples for next zero crossing -#define MAX_SENTENCES 4096 +#define CVOXWORDMAX 64 +#define CVOXFILESENTENCEMAX 4096 + #define SENTENCE_INDEX -99999 // unique sentence index typedef struct voxword_s @@ -34,13 +34,6 @@ typedef struct voxword_s sfx_t *sfx; // name and cache pointer } voxword_t; - -typedef struct -{ - char *pName; - float length; -} sentence_t; - struct channel_s; void VOX_LoadWord( struct channel_s *pchan ); void VOX_FreeWord( struct channel_s *pchan ); diff --git a/engine/common/host.c b/engine/common/host.c index ec7c95a0..3b3b1f8b 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -826,6 +826,9 @@ static void Host_RunTests( int stage ) break; case 1: // after FS load Test_RunImagelib(); +#if !XASH_DEDICATED + Test_RunVOX(); +#endif Msg( "Done! %d passed, %d failed\n", tests_stats.passed, tests_stats.failed ); Sys_Quit(); } diff --git a/engine/common/tests.h b/engine/common/tests.h index 3ff6e030..e58f20a5 100644 --- a/engine/common/tests.h +++ b/engine/common/tests.h @@ -36,6 +36,7 @@ void Test_RunCommon( void ); void Test_RunCmd( void ); void Test_RunCvar( void ); void Test_RunCon( void ); +void Test_RunVOX( void ); #endif From 00652f0e7d9bfef6a93a642e392e06ef7653db3d Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Mon, 21 Feb 2022 23:46:50 -0800 Subject: [PATCH 155/548] rt: fix pcie bus thrashing; add profiler scopes turns out it's not cool to read color from on-gpu kusok data, modify it, and write back --- ref_vk/vk_framectl.c | 12 +++++++++++- ref_vk/vk_ray_model.c | 12 ++++++++---- ref_vk/vk_scene.c | 32 +++++++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/ref_vk/vk_framectl.c b/ref_vk/vk_framectl.c index dc116983..bce9a96f 100644 --- a/ref_vk/vk_framectl.c +++ b/ref_vk/vk_framectl.c @@ -42,7 +42,10 @@ static struct { } g_frame; #define PROFILER_SCOPES(X) \ - X(end_frame , "R_EndFrame"); \ + X(frame, "Frame"); \ + X(begin_frame, "R_BeginFrame"); \ + X(render_frame, "VK_RenderFrame"); \ + X(end_frame, "R_EndFrame"); \ X(frame_gpu_wait, "Wait for GPU"); \ #define SCOPE_DECLARE(scope, name) APROF_SCOPE_DECLARE(scope) @@ -199,6 +202,9 @@ void R_BeginFrame( qboolean clearScene ) { showProfilingData(); aprof_scope_frame(); + APROF_SCOPE_BEGIN(frame); + APROF_SCOPE_BEGIN(begin_frame); + if (vk_core.rtx && FBitSet( vk_rtx->flags, FCVAR_CHANGED )) { g_frame.rtx_enabled = CVAR_TO_BOOL( vk_rtx ); } @@ -225,11 +231,14 @@ void R_BeginFrame( qboolean clearScene ) { } g_frame.current.phase = Phase_FrameBegan; + APROF_SCOPE_END(begin_frame); } void VK_RenderFrame( const struct ref_viewpass_s *rvp ) { + APROF_SCOPE_BEGIN(render_frame); VK_SceneRender( rvp ); + APROF_SCOPE_END(render_frame); } static void enqueueRendering( VkCommandBuffer cmdbuf ) { @@ -340,6 +349,7 @@ void R_EndFrame( void ) } APROF_SCOPE_END(end_frame); + APROF_SCOPE_END(frame); } static void toggleRaytracing( void ) { diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index 59fdc4de..4d305330 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -395,10 +395,14 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render kusok->tex_base_color |= KUSOK_MATERIAL_FLAG_SKYBOX; } - Vector4Copy(color, kusok->color); - kusok->color[0] *= mat->base_color[0]; - kusok->color[1] *= mat->base_color[1]; - kusok->color[2] *= mat->base_color[2]; + { + vec4_t gcolor; + gcolor[0] = color[0] * mat->base_color[0]; + gcolor[1] = color[1] * mat->base_color[1]; + gcolor[2] = color[2] * mat->base_color[2]; + gcolor[3] = color[3]; + Vector4Copy(gcolor, kusok->color); + } if (geom->material == kXVkMaterialEmissive) { VectorCopy( geom->emissive, kusok->emissive ); diff --git a/ref_vk/vk_scene.c b/ref_vk/vk_scene.c index 442ae730..b3a1e1e4 100644 --- a/ref_vk/vk_scene.c +++ b/ref_vk/vk_scene.c @@ -16,6 +16,7 @@ #include "vk_materials.h" #include "camera.h" #include "vk_mapents.h" +#include "profiler.h" #include "com_strings.h" #include "ref_params.h" @@ -25,6 +26,19 @@ #include // qsort #include +#define PROFILER_SCOPES(X) \ + X(scene_render, "VK_SceneRender"); \ + X(draw_viewmodel, "draw viewmodel"); \ + X(draw_worldbrush, "draw worldbrush"); \ + X(draw_opaques, "draw opaque entities"); \ + X(draw_opaque_beams, "draw opaque beams"); \ + X(draw_translucent, "draw translucent entities"); \ + X(draw_transparent_beams, "draw transparent beams"); \ + +#define SCOPE_DECLARE(scope, name) APROF_SCOPE_DECLARE(scope) +PROFILER_SCOPES(SCOPE_DECLARE) +#undef SCOPE_DECLARE + typedef struct vk_trans_entity_s { struct cl_entity_s *entity; int render_mode; @@ -75,6 +89,8 @@ static void reloadMaterials( void ) { void VK_SceneInit( void ) { + PROFILER_SCOPES(APROF_SCOPE_INIT); + g_lists.draw_list = g_lists.draw_stack; g_lists.draw_stack_pos = 0; if (vk_core.rtx) { @@ -603,6 +619,7 @@ static void drawEntity( cl_entity_t *ent, int render_mode ) static float g_frametime = 0; void VK_SceneRender( const ref_viewpass_t *rvp ) { + APROF_SCOPE_BEGIN_EARLY(scene_render); const cl_entity_t* const local_player = gEngine.GetLocalPlayer(); g_frametime = /*FIXME VK RP_NORMALPASS( )) ? */ @@ -619,13 +636,16 @@ void VK_SceneRender( const ref_viewpass_t *rvp ) { // Draw view model { + APROF_SCOPE_BEGIN(draw_viewmodel); VK_RenderStateSetColor( 1.f, 1.f, 1.f, 1.f ); R_RunViewmodelEvents(); R_DrawViewModel(); + APROF_SCOPE_END(draw_viewmodel); } // Draw world brush { + APROF_SCOPE_BEGIN(draw_worldbrush); cl_entity_t *world = gEngine.GetEntityByIndex( 0 ); if( world && world->model ) { @@ -634,6 +654,7 @@ void VK_SceneRender( const ref_viewpass_t *rvp ) { VK_RenderStateSetColor( 1.f, 1.f, 1.f, 1.f); VK_BrushModelDraw( world, kRenderNormal, NULL ); } + APROF_SCOPE_END(draw_worldbrush); } { @@ -643,6 +664,7 @@ void VK_SceneRender( const ref_viewpass_t *rvp ) { } } + APROF_SCOPE_BEGIN(draw_opaques); // Draw opaque entities for (int i = 0; i < g_lists.draw_list->num_solid_entities; ++i) { @@ -654,15 +676,19 @@ void VK_SceneRender( const ref_viewpass_t *rvp ) { R_LightAddFlashlight(ent, false); } } + APROF_SCOPE_END(draw_opaques); // Draw opaque beams + APROF_SCOPE_BEGIN(draw_opaque_beams); gEngine.CL_DrawEFX( g_frametime, false ); + APROF_SCOPE_END(draw_opaque_beams); VK_RenderDebugLabelEnd(); VK_RenderDebugLabelBegin( "tranparent" ); { + APROF_SCOPE_BEGIN(draw_translucent); // sort translucents entities by rendermode and distance qsort( g_lists.draw_list->trans_entities, g_lists.draw_list->num_trans_entities, sizeof( vk_trans_entity_t ), R_TransEntityCompare ); @@ -672,11 +698,13 @@ void VK_SceneRender( const ref_viewpass_t *rvp ) { const vk_trans_entity_t *ent = g_lists.draw_list->trans_entities + i; drawEntity(ent->entity, ent->render_mode); } - + APROF_SCOPE_END(draw_translucent); } // Draw transparent beams + APROF_SCOPE_BEGIN(draw_transparent_beams); gEngine.CL_DrawEFX( g_frametime, true ); + APROF_SCOPE_END(draw_transparent_beams); VK_RenderDebugLabelEnd(); @@ -685,6 +713,8 @@ void VK_SceneRender( const ref_viewpass_t *rvp ) { if (ui_infotool->value > 0) XVK_CameraDebugPrintCenterEntity(); + + APROF_SCOPE_END(scene_render); } /* From 31e15e40c6d90145b9068768cc576a7f617084de Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Tue, 22 Feb 2022 21:46:44 -0800 Subject: [PATCH 156/548] vk: print allocations only under `-vkdebugmem` --- ref_vk/vk_devmem.c | 49 ++++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/ref_vk/vk_devmem.c b/ref_vk/vk_devmem.c index 75c59bae..f2e9c066 100644 --- a/ref_vk/vk_devmem.c +++ b/ref_vk/vk_devmem.c @@ -20,6 +20,8 @@ typedef struct { static struct { vk_device_memory_t allocs[MAX_DEVMEM_ALLOCS]; int num_allocs; + + qboolean verbose; } g_vk_devmem; static int findMemoryWithType(uint32_t type_index_bits, VkMemoryPropertyFlags flags) { @@ -62,15 +64,17 @@ static int allocateDeviceMemory(VkMemoryRequirements req, VkMemoryPropertyFlags .memoryTypeIndex = findMemoryWithType(req.memoryTypeBits, prop_flags), }; - gEngine.Con_Reportf("allocateDeviceMemory size=%zu memoryTypeBits=0x%x prop_flags=%c%c%c%c%c allocate_flags=0x%x => typeIndex=%d\n", - mai.allocationSize, req.memoryTypeBits, - prop_flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT ? 'D' : '.', - prop_flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT ? 'V' : '.', - prop_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ? 'C' : '.', - prop_flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT ? '$' : '.', - prop_flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT ? 'L' : '.', - allocate_flags, - mai.memoryTypeIndex); + if (g_vk_devmem.verbose) { + gEngine.Con_Reportf("allocateDeviceMemory size=%zu memoryTypeBits=0x%x prop_flags=%c%c%c%c%c allocate_flags=0x%x => typeIndex=%d\n", + mai.allocationSize, req.memoryTypeBits, + prop_flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT ? 'D' : '.', + prop_flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT ? 'V' : '.', + prop_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ? 'C' : '.', + prop_flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT ? '$' : '.', + prop_flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT ? 'L' : '.', + allocate_flags, + mai.memoryTypeIndex); + } ASSERT(mai.memoryTypeIndex != UINT32_MAX); vk_device_memory_t *device_memory = g_vk_devmem.allocs + g_vk_devmem.num_allocs; @@ -98,14 +102,16 @@ vk_devmem_t VK_DevMemAllocate(const char *name, VkMemoryRequirements req, VkMemo int device_memory_index = -1; alo_block_t block; - gEngine.Con_Reportf("VK_DevMemAllocate name=\"%s\" size=%zu alignment=%zu memoryTypeBits=0x%x prop_flags=%c%c%c%c%c allocate_flags=0x%x\n", - name, req.size, req.alignment, req.memoryTypeBits, - prop_flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT ? 'D' : '.', - prop_flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT ? 'V' : '.', - prop_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ? 'C' : '.', - prop_flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT ? '$' : '.', - prop_flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT ? 'L' : '.', - allocate_flags); + if (g_vk_devmem.verbose) { + gEngine.Con_Reportf("VK_DevMemAllocate name=\"%s\" size=%zu alignment=%zu memoryTypeBits=0x%x prop_flags=%c%c%c%c%c allocate_flags=0x%x\n", + name, req.size, req.alignment, req.memoryTypeBits, + prop_flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT ? 'D' : '.', + prop_flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT ? 'V' : '.', + prop_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ? 'C' : '.', + prop_flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT ? '$' : '.', + prop_flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT ? 'L' : '.', + allocate_flags); + } if (vk_core.rtx) { // TODO this is needed only for the ray tracer and only while there's no proper staging @@ -148,7 +154,9 @@ vk_devmem_t VK_DevMemAllocate(const char *name, VkMemoryRequirements req, VkMemo ret.offset = block.offset; ret.mapped = device_memory->map ? (char*)device_memory->map + block.offset : NULL; - gEngine.Con_Reportf("Allocated devmem=%d block=%d offset=%d size=%d\n", device_memory_index, block.index, (int)block.offset, (int)block.size); + if (g_vk_devmem.verbose) { + gEngine.Con_Reportf("Allocated devmem=%d block=%d offset=%d size=%d\n", device_memory_index, block.index, (int)block.offset, (int)block.size); + } device_memory->refcount++; ret.priv_.devmem = device_memory_index; @@ -165,7 +173,9 @@ void VK_DevMemFree(const vk_devmem_t *mem) { vk_device_memory_t *const device_memory = g_vk_devmem.allocs + mem->priv_.devmem; ASSERT(mem->device_memory == device_memory->device_memory); - gEngine.Con_Reportf("Freeing devmem=%d block=%d\n", mem->priv_.devmem, mem->priv_.block); + if (g_vk_devmem.verbose) { + gEngine.Con_Reportf("Freeing devmem=%d block=%d\n", mem->priv_.devmem, mem->priv_.block); + } aloPoolFree(device_memory->allocator, mem->priv_.block); @@ -173,6 +183,7 @@ void VK_DevMemFree(const vk_devmem_t *mem) { } qboolean VK_DevMemInit( void ) { + g_vk_devmem.verbose = !!gEngine.Sys_CheckParm("-vkdebugmem"); return true; } From a2d8a47376aa33b093f38f19d8e0f5a06a7524b9 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 24 Feb 2022 02:47:54 +0300 Subject: [PATCH 157/548] engine: client: fix misplaced arguments in memcpy --- engine/client/cl_frame.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index 2bd49c16..3c963e9c 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -219,7 +219,7 @@ void CL_UpdateLatchedVars( cl_entity_t *ent ) if( ent->curstate.sequence != ent->prevstate.sequence ) { - memcpy( ent->prevstate.blending, ent->latched.prevseqblending, sizeof( ent->prevstate.blending )); + memcpy( ent->latched.prevseqblending, ent->prevstate.blending, sizeof( ent->prevstate.blending )); ent->latched.prevsequence = ent->prevstate.sequence; ent->latched.sequencetime = ent->curstate.animtime; } From d4d39c66fb142fd008d534e5be8ae857412fbfbd Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 24 Feb 2022 02:49:10 +0300 Subject: [PATCH 158/548] engine: client: fix double call to CL_ParametricMove --- engine/client/cl_frame.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index 3c963e9c..eb4addb6 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -310,7 +310,7 @@ void CL_ProcessEntityUpdate( cl_entity_t *ent ) if( FBitSet( ent->curstate.entityType, ENTITY_NORMAL )) COM_NormalizeAngles( ent->curstate.angles ); - parametric = CL_ParametricMove( ent ); + parametric = ent->curstate.starttime != 0.0f && ent->curstate.impacttime != 0.0f; // allow interpolation on bmodels too if( ent->model && ent->model->type == mod_brush ) From f0783ddee11ff5faad3ca6a32577642c29bd4510 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 24 Feb 2022 04:51:04 +0300 Subject: [PATCH 159/548] engine: client: fix comparing floats in interpolation code --- engine/client/cl_frame.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index eb4addb6..683f2786 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -406,7 +406,7 @@ void CL_PureOrigin( cl_entity_t *ent, float t, vec3_t outorigin, vec3_t outangle VectorSubtract( ph0->origin, ph1->origin, delta ); - if( t0 != t1 ) + if( !Q_equal( t0, t1 )) frac = ( t - t1 ) / ( t0 - t1 ); else frac = 1.0f; @@ -486,7 +486,7 @@ int CL_InterpolateModel( cl_entity_t *e ) return 0; } - if( t2 == t1 ) + if( Q_equal( t2, t1 )) { VectorCopy( ph0->origin, e->origin ); VectorCopy( ph0->angles, e->angles ); From 7821f425e7a49ce3ca81fc9c17c59c5a1116bc01 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 24 Feb 2022 05:45:41 +0300 Subject: [PATCH 160/548] engine: client: position history indexes can't be negative, use unsigned with them --- engine/client/cl_frame.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index 683f2786..f0ab2d2b 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -349,7 +349,7 @@ find two timestamps qboolean CL_FindInterpolationUpdates( cl_entity_t *ent, float targettime, position_history_t **ph0, position_history_t **ph1 ) { qboolean extrapolate = true; - int i, i0, i1, imod; + uint i, i0, i1, imod; float at; imod = ent->current_position; From 13b36e66b351138a59b2135bed803fa6d7f00a62 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 25 Feb 2022 02:12:44 +0300 Subject: [PATCH 161/548] engine: client: workaround buggy position history times going backwards in interpolation code --- engine/client/cl_frame.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index f0ab2d2b..a9181ffb 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -486,7 +486,9 @@ int CL_InterpolateModel( cl_entity_t *e ) return 0; } - if( Q_equal( t2, t1 )) + // HACKHACK: workaround buggy position history animtime + // going backward sometimes + if( Q_equal( t2, t1 ) || t2 < t1 ) { VectorCopy( ph0->origin, e->origin ); VectorCopy( ph0->angles, e->angles ); From e07417aead9a40e4c35783e48d5b18a272a05a26 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 28 Feb 2022 04:29:32 +0300 Subject: [PATCH 162/548] engine: client: fix sentence word splitted by contents inside parentheses --- engine/client/s_vox.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/engine/client/s_vox.c b/engine/client/s_vox.c index f2060257..93595683 100644 --- a/engine/client/s_vox.c +++ b/engine/client/s_vox.c @@ -230,7 +230,8 @@ static int VOX_ParseString( char *psz, char *rgpparseword[CVOXWORDMAX] ) for( ; *psz && *psz != ' ' && *psz != '.' && - *psz != ','; psz++ ); + *psz != ',' && + *psz != '('; psz++ ); // skip anything in between ( and ) if( *psz == '(' ) @@ -549,8 +550,8 @@ static void Test_VOX_ParseString( void ) char *rgpparseword[CVOXWORDMAX]; const char *data[] = { - "(p100) my ass is, heavy!(p80) clik.", - "(p100)", "my", "ass", "is", "_comma", "heavy!(p80)", "clik", NULL, + "(p100) my ass is, heavy!(p80 t20) clik.", + "(p100)", "my", "ass", "is", "_comma", "heavy!(p80 t20)", "clik", NULL, "freeman...", "freeman", "_period", NULL, }; @@ -586,19 +587,22 @@ static void Test_VOX_ParseWordParams( void ) Q_strncpy( buffer, "heavy!(p80)", sizeof( buffer )); ret = VOX_ParseWordParams( buffer, &word, true ); + TASSERT_STR( buffer, "heavy!" ); TASSERT( word.pitch == 80 ); TASSERT( ret ); Q_strncpy( buffer, "(p105)", sizeof( buffer )); ret = VOX_ParseWordParams( buffer, &word, false ); + TASSERT_STR( buffer, "" ); TASSERT( word.pitch == 105 ); TASSERT( !ret ); Q_strncpy( buffer, "quiet(v50)", sizeof( buffer )); ret = VOX_ParseWordParams( buffer, &word, false ); + TASSERT_STR( buffer, "quiet" ); TASSERT( word.pitch == 105 ); // defaulted TASSERT( word.volume == 50 ); - TASSERT( !ret ); + TASSERT( ret ); } void Test_RunVOX( void ) From 24ea8fba4b31b79596786ec099befbafa3c008ce Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Mon, 28 Feb 2022 17:37:44 +0400 Subject: [PATCH 163/548] engine: common: fixed lightmap shifting caused by insufficent precision in Mod_CalcSurfaceExtents --- engine/common/mod_bmodel.c | 8 ++++---- public/xash3d_mathlib.h | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/engine/common/mod_bmodel.c b/engine/common/mod_bmodel.c index 97c2e059..957395a5 100644 --- a/engine/common/mod_bmodel.c +++ b/engine/common/mod_bmodel.c @@ -1020,8 +1020,8 @@ Fills in surf->texturemins[] and surf->extents[] */ static void Mod_CalcSurfaceExtents( msurface_t *surf ) { - float mins[2], maxs[2], val; - float lmmins[2], lmmaxs[2]; + double mins[2], maxs[2], val; + double lmmins[2], lmmaxs[2]; int bmins[2], bmaxs[2]; int i, j, e, sample_size; mextrasurf_t *info = surf->info; @@ -1049,14 +1049,14 @@ static void Mod_CalcSurfaceExtents( msurface_t *surf ) for( j = 0; j < 2; j++ ) { - val = DotProduct( v->position, surf->texinfo->vecs[j] ) + surf->texinfo->vecs[j][3]; + val = DotProductPrecise( v->position, surf->texinfo->vecs[j] ) + surf->texinfo->vecs[j][3]; mins[j] = Q_min( val, mins[j] ); maxs[j] = Q_max( val, maxs[j] ); } for( j = 0; j < 2; j++ ) { - val = DotProduct( v->position, info->lmvecs[j] ) + info->lmvecs[j][3]; + val = DotProductPrecise( v->position, info->lmvecs[j] ) + info->lmvecs[j][3]; lmmins[j] = Q_min( val, lmmins[j] ); lmmaxs[j] = Q_max( val, lmmaxs[j] ); } diff --git a/public/xash3d_mathlib.h b/public/xash3d_mathlib.h index b2ae966f..985e5d1f 100644 --- a/public/xash3d_mathlib.h +++ b/public/xash3d_mathlib.h @@ -93,6 +93,7 @@ GNU General Public License for more details. #define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) #define DotProductAbs(x,y) (abs((x)[0]*(y)[0])+abs((x)[1]*(y)[1])+abs((x)[2]*(y)[2])) #define DotProductFabs(x,y) (fabs((x)[0]*(y)[0])+fabs((x)[1]*(y)[1])+fabs((x)[2]*(y)[2])) +#define DotProductPrecise(x,y) ((double)(x)[0]*(double)(y)[0]+(double)(x)[1]*(double)(y)[1]+(double)(x)[2]*(double)(y)[2]) #define CrossProduct(a,b,c) ((c)[0]=(a)[1]*(b)[2]-(a)[2]*(b)[1],(c)[1]=(a)[2]*(b)[0]-(a)[0]*(b)[2],(c)[2]=(a)[0]*(b)[1]-(a)[1]*(b)[0]) #define Vector2Subtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1]) #define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) From 9047fe2e7404f9cf679cc74afb193949a304fcb3 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Mon, 28 Feb 2022 17:45:51 +0400 Subject: [PATCH 164/548] ref_gl: gl_rsurf: fixed invalid lightmap texture coordinates in some cases --- ref_gl/gl_rsurf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_gl/gl_rsurf.c b/ref_gl/gl_rsurf.c index caabf91f..c4a53edd 100644 --- a/ref_gl/gl_rsurf.c +++ b/ref_gl/gl_rsurf.c @@ -700,7 +700,7 @@ static void LM_UploadBlock( qboolean dynamic ) r_lightmap.size = r_lightmap.width * r_lightmap.height * 4; r_lightmap.flags = IMAGE_HAS_COLOR; r_lightmap.buffer = gl_lms.lightmap_buffer; - tr.lightmapTextures[i] = GL_LoadTextureInternal( lmName, &r_lightmap, TF_FONT|TF_ATLAS_PAGE ); + tr.lightmapTextures[i] = GL_LoadTextureInternal( lmName, &r_lightmap, TF_NOMIPMAP|TF_ATLAS_PAGE ); if( ++gl_lms.current_lightmap_texture == MAX_LIGHTMAPS ) gEngfuncs.Host_Error( "AllocBlock: full\n" ); From edc171be0462e1f3b908a0603fec7dc7b09f94ad Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Mon, 28 Feb 2022 19:46:19 +0400 Subject: [PATCH 165/548] engine: common: fixed lightmap shift again --- engine/common/mod_bmodel.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/engine/common/mod_bmodel.c b/engine/common/mod_bmodel.c index 957395a5..6edaa606 100644 --- a/engine/common/mod_bmodel.c +++ b/engine/common/mod_bmodel.c @@ -1020,8 +1020,10 @@ Fills in surf->texturemins[] and surf->extents[] */ static void Mod_CalcSurfaceExtents( msurface_t *surf ) { - double mins[2], maxs[2], val; - double lmmins[2], lmmaxs[2]; + // this place is VERY critical to precision + // keep it as float, don't use double, because it causes issues with lightmap + float mins[2], maxs[2], val; + float lmmins[2], lmmaxs[2]; int bmins[2], bmaxs[2]; int i, j, e, sample_size; mextrasurf_t *info = surf->info; From 85f99c723a2e42a55c16d7014e8d14ccafdc90fa Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 3 Mar 2022 05:38:36 +0300 Subject: [PATCH 166/548] engine: add sys_timescale implementation --- engine/client/s_main.c | 4 ++++ engine/common/common.h | 3 ++- engine/common/host.c | 11 +++++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/engine/client/s_main.c b/engine/client/s_main.c index ba7ed298..a88546b1 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -533,6 +533,8 @@ void S_StartSound( const vec3_t pos, int ent, int chan, sound_t handle, float fv // spatialize memset( target_chan, 0, sizeof( *target_chan )); + pitch *= (sys_timescale.value + 1) / 2; + VectorCopy( pos, target_chan->origin ); target_chan->staticsound = ( ent == 0 ) ? true : false; target_chan->use_loop = (flags & SND_STOP_LOOPING) ? false : true; @@ -788,6 +790,8 @@ void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, floa return; } + pitch *= (sys_timescale.value + 1) / 2; + // never update positions if source entity is 0 ch->staticsound = ( ent == 0 ) ? true : false; ch->use_loop = (flags & SND_STOP_LOOPING) ? false : true; diff --git a/engine/common/common.h b/engine/common/common.h index a21d1083..3b6d3277 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -184,7 +184,8 @@ extern convar_t host_developer; extern convar_t *host_limitlocal; extern convar_t *host_framerate; extern convar_t *host_maxfps; -extern convar_t cl_filterstuffcmd; +extern convar_t sys_timescale; +extern convar_t cl_filterstuffcmd; /* ============================================================== diff --git a/engine/common/host.c b/engine/common/host.c index 3b3b1f8b..44e46f67 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -51,6 +51,7 @@ struct tests_stats_s tests_stats; #endif CVAR_DEFINE( host_developer, "developer", "0", FCVAR_FILTERABLE, "engine is in development-mode" ); +CVAR_DEFINE_AUTO( sys_timescale, "1.0", FCVAR_CHEAT|FCVAR_FILTERABLE, "scale frame time" ); CVAR_DEFINE_AUTO( sys_ticrate, "100", 0, "framerate in dedicated mode" ); convar_t *host_serverstate; @@ -626,8 +627,9 @@ qboolean Host_FilterTime( float time ) { static double oldtime; double fps; + double scale = sys_timescale.value; - host.realtime += time; + host.realtime += time * scale; fps = Host_CalcFPS( ); // clamp the fps in multiplayer games @@ -638,12 +640,12 @@ qboolean Host_FilterTime( float time ) if( Host_IsDedicated() ) { - if(( host.realtime - oldtime ) < ( 1.0 / ( fps + 1.0 ))) + if(( host.realtime - oldtime ) < ( 1.0 / ( fps + 1.0 )) * scale) return false; } else { - if(( host.realtime - oldtime ) < ( 1.0 / fps )) + if(( host.realtime - oldtime ) < ( 1.0 / fps ) * scale ) return false; } } @@ -654,7 +656,7 @@ qboolean Host_FilterTime( float time ) // NOTE: allow only in singleplayer while demos are not active if( host_framerate->value > 0.0f && Host_IsLocalGame() && !CL_IsPlaybackDemo() && !CL_IsRecordDemo( )) - host.frametime = bound( MIN_FRAMETIME, host_framerate->value, MAX_FRAMETIME ); + host.frametime = bound( MIN_FRAMETIME, host_framerate->value * scale, MAX_FRAMETIME ); else host.frametime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME ); return true; @@ -1101,6 +1103,7 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa host_clientloaded = Cvar_Get( "host_clientloaded", "0", FCVAR_READ_ONLY, "inidcates a loaded client.dll" ); host_limitlocal = Cvar_Get( "host_limitlocal", "0", 0, "apply cl_cmdrate and rate to loopback connection" ); con_gamemaps = Cvar_Get( "con_mapfilter", "1", FCVAR_ARCHIVE, "when true show only maps in game folder" ); + Cvar_RegisterVariable( &sys_timescale ); build = Cvar_Get( "buildnum", va( "%i", Q_buildnum_compat()), FCVAR_READ_ONLY, "returns a current build number" ); ver = Cvar_Get( "ver", va( "%i/%s (hw build %i)", PROTOCOL_VERSION, XASH_COMPAT_VERSION, Q_buildnum_compat()), FCVAR_READ_ONLY, "shows an engine version" ); From 8169c2a40b08dca01da1b5e41454c2cbc99b21e1 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 3 Mar 2022 05:56:22 +0300 Subject: [PATCH 167/548] wscript: set rpath to current folder, LD_LIBRARY_PATH is unneeded anymore Fix README and AppImage launch script accordingly --- README.md | 2 -- game_launch/wscript | 1 + scripts/gha/build_linux.sh | 1 - vgui_support/wscript | 1 + 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c20ba7ad..119cf2b0 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,6 @@ This repository contains our fork of HLSDK and restored source code for some of You still needed to copy `valve` directory as all game resources located there. 3) Run the main executable (`xash3d.exe` or AppImage). -Note: on Linux, without AppImage, you may need to create a shell-script with the command `LD_LIBRARY_PATH=. ./xash3d`. - For additional info, run Xash3D with `-help` command line key. ## Contributing diff --git a/game_launch/wscript b/game_launch/wscript index 424d4ffc..4b9ad831 100644 --- a/game_launch/wscript +++ b/game_launch/wscript @@ -37,6 +37,7 @@ def build(bld): features = 'c cxx cxxprogram', includes = includes, use = libs, + rpath = '.', install_path = bld.env.BINDIR, subsystem = bld.env.MSVC_SUBSYSTEM ) diff --git a/scripts/gha/build_linux.sh b/scripts/gha/build_linux.sh index 99e76d43..c238e8d6 100755 --- a/scripts/gha/build_linux.sh +++ b/scripts/gha/build_linux.sh @@ -80,7 +80,6 @@ echo "Xash3D FWGS installed as AppImage." echo "Base directory is $XASH3D_BASEDIR. Set XASH3D_BASEDIR environment variable to override this" export XASH3D_EXTRAS_PAK1="${APPDIR}"/extras.pak -export LD_LIBRARY_PATH="${APPDIR}":$LD_LIBRARY_PATH ${DEBUGGER} "${APPDIR}"/xash3d "$@" exit $? EOF diff --git a/vgui_support/wscript b/vgui_support/wscript index 47a7317a..b3613ec2 100644 --- a/vgui_support/wscript +++ b/vgui_support/wscript @@ -119,6 +119,7 @@ def build(bld): features = 'cxx', includes = includes, use = libs, + rpath = '.', install_path = bld.env.LIBDIR, subsystem = bld.env.MSVC_SUBSYSTEM ) From 29ad3de78ab23ac0f0c470a3c7839102069c8daa Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Thu, 3 Mar 2022 13:23:00 +0400 Subject: [PATCH 168/548] ref_gl: added support for GL_ARB_texture_compression_bptc extension --- ref_gl/gl_export.h | 4 ++++ ref_gl/gl_local.h | 1 + ref_gl/gl_opengl.c | 1 + 3 files changed, 6 insertions(+) diff --git a/ref_gl/gl_export.h b/ref_gl/gl_export.h index 07d071d9..13b7b4d1 100644 --- a/ref_gl/gl_export.h +++ b/ref_gl/gl_export.h @@ -401,6 +401,10 @@ typedef float GLmatrix[16]; #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C +#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D +#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E +#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F #define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD #define GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI 0x8837 #define GL_COMPRESSED_ALPHA_ARB 0x84E9 diff --git a/ref_gl/gl_local.h b/ref_gl/gl_local.h index 09e657c2..deac6e94 100644 --- a/ref_gl/gl_local.h +++ b/ref_gl/gl_local.h @@ -623,6 +623,7 @@ enum GL_ARB_VERTEX_BUFFER_OBJECT_EXT, GL_DRAW_RANGEELEMENTS_EXT, GL_TEXTURE_MULTISAMPLE, + GL_ARB_TEXTURE_COMPRESSION_BPTC, GL_EXTCOUNT, // must be last }; diff --git a/ref_gl/gl_opengl.c b/ref_gl/gl_opengl.c index 34afb7dd..80c89e25 100644 --- a/ref_gl/gl_opengl.c +++ b/ref_gl/gl_opengl.c @@ -712,6 +712,7 @@ void GL_InitExtensionsBigGL( void ) GL_CheckExtension( "GL_EXT_gpu_shader4", NULL, NULL, GL_EXT_GPU_SHADER4 ); // don't confuse users GL_CheckExtension( "GL_ARB_vertex_buffer_object", vbofuncs, "gl_vertex_buffer_object", GL_ARB_VERTEX_BUFFER_OBJECT_EXT ); GL_CheckExtension( "GL_ARB_texture_multisample", multisampletexfuncs, "gl_texture_multisample", GL_TEXTURE_MULTISAMPLE ); + GL_CheckExtension( "GL_ARB_texture_compression_bptc", NULL, "gl_texture_bptc_compression", GL_ARB_TEXTURE_COMPRESSION_BPTC ); if( GL_CheckExtension( "GL_ARB_shading_language_100", NULL, NULL, GL_SHADER_GLSL100_EXT )) { pglGetIntegerv( GL_MAX_TEXTURE_COORDS_ARB, &glConfig.max_texture_coords ); From cf7852832faae5979caa14334eda872a2a77a471 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Thu, 3 Mar 2022 13:42:17 +0400 Subject: [PATCH 169/548] ref_gl: gl_image: added support for BC7 and BC6H compression formats of DDS textures --- ref_gl/gl_image.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ref_gl/gl_image.c b/ref_gl/gl_image.c index e6d384b3..ac5696dc 100644 --- a/ref_gl/gl_image.c +++ b/ref_gl/gl_image.c @@ -386,6 +386,9 @@ static size_t GL_CalcImageSize( pixformat_t format, int width, int height, int d break; case PF_DXT3: case PF_DXT5: + case PF_BC6H_SIGNED: + case PF_BC6H_UNSIGNED: + case PF_BC7: case PF_ATI2: size = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth; break; @@ -417,6 +420,10 @@ static size_t GL_CalcTextureSize( GLenum format, int width, int height, int dept case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: case GL_COMPRESSED_LUMINANCE_ALPHA_ARB: case GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI: + case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB: + case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB: + case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB: size = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth; break; case GL_RGBA8: @@ -694,6 +701,9 @@ static void GL_SetTextureFormat( gl_texture_t *tex, pixformat_t format, int chan case PF_DXT1: tex->format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; // never use DXT1 with 1-bit alpha case PF_DXT3: tex->format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; case PF_DXT5: tex->format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; + case PF_BC6H_SIGNED: tex->format = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB; break; + case PF_BC6H_UNSIGNED: tex->format = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB; break; + case PF_BC7: tex->format = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; break; case PF_ATI2: if( glConfig.hardware_type == GLHW_RADEON ) tex->format = GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI; From 6473efa9953f3b28a13425ea1819340a508538f1 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Thu, 3 Mar 2022 13:42:29 +0400 Subject: [PATCH 170/548] engine: added support for BC7 and BC6H compression formats of DDS textures --- common/com_image.h | 13 +-- engine/common/imagelib/img_dds.c | 96 +++++++++++++------- engine/common/imagelib/img_dds.h | 145 +++++++++++++++++++++++++++++++ 3 files changed, 220 insertions(+), 34 deletions(-) diff --git a/common/com_image.h b/common/com_image.h index 063309ab..c270cf26 100644 --- a/common/com_image.h +++ b/common/com_image.h @@ -9,7 +9,7 @@ NOTE: number at end of pixelformat name it's a total bitscount e.g. PF_RGB_24 == ======================================================================== */ #define ImageRAW( type ) (type == PF_RGBA_32 || type == PF_BGRA_32 || type == PF_RGB_24 || type == PF_BGR_24 || type == PF_LUMINANCE) -#define ImageDXT( type ) (type == PF_DXT1 || type == PF_DXT3 || type == PF_DXT5 || type == PF_ATI2) +#define ImageDXT( type ) (type == PF_DXT1 || type == PF_DXT3 || type == PF_DXT5 || type == PF_ATI2 || type == PF_BC6H_SIGNED || type == PF_BC6H_UNSIGNED || type == PF_BC7) typedef enum { @@ -21,10 +21,13 @@ typedef enum PF_RGB_24, // uncompressed dds or another 24-bit image PF_BGR_24, // big-endian RGB (MacOS) PF_LUMINANCE, - PF_DXT1, // s3tc DXT1 format - PF_DXT3, // s3tc DXT3 format - PF_DXT5, // s3tc DXT5 format - PF_ATI2, // latc ATI2N format + PF_DXT1, // s3tc DXT1/BC1 format + PF_DXT3, // s3tc DXT3/BC2 format + PF_DXT5, // s3tc DXT5/BC3 format + PF_ATI2, // latc ATI2N/BC5 format + PF_BC6H_SIGNED, // bptc BC6H signed FP16 format + PF_BC6H_UNSIGNED, // bptc BC6H unsigned FP16 format + PF_BC7, // bptc BC7 format PF_TOTALCOUNT, // must be last } pixformat_t; diff --git a/engine/common/imagelib/img_dds.c b/engine/common/imagelib/img_dds.c index 845813c6..361ddae2 100644 --- a/engine/common/imagelib/img_dds.c +++ b/engine/common/imagelib/img_dds.c @@ -91,7 +91,7 @@ qboolean Image_CheckDXT5Alpha( dds_t *hdr, byte *fin ) return false; } -void Image_DXTGetPixelFormat( dds_t *hdr ) +void Image_DXTGetPixelFormat( dds_t *hdr, dds_header_dxt10_t *headerExt ) { uint bits = hdr->dsPixelFormat.dwRGBBitCount; @@ -100,29 +100,53 @@ void Image_DXTGetPixelFormat( dds_t *hdr ) if( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_FOURCC )) { - switch( hdr->dsPixelFormat.dwFourCC ) + if( hdr->dsPixelFormat.dwFourCC == TYPE_DX10 ) { - case TYPE_DXT1: - image.type = PF_DXT1; - break; - case TYPE_DXT2: - image.flags &= ~IMAGE_HAS_ALPHA; // alpha is already premultiplied by color - // intentionally fallthrough - case TYPE_DXT3: - image.type = PF_DXT3; - break; - case TYPE_DXT4: - image.flags &= ~IMAGE_HAS_ALPHA; // alpha is already premultiplied by color - // intentionally fallthrough - case TYPE_DXT5: - image.type = PF_DXT5; - break; - case TYPE_ATI2: - image.type = PF_ATI2; - break; - default: - image.type = PF_UNKNOWN; // assume error - break; + switch( headerExt->dxgiFormat ) + { + case DXGI_FORMAT_BC6H_SF16: + image.type = PF_BC6H_SIGNED; + break; + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_TYPELESS: + image.type = PF_BC6H_UNSIGNED; + break; + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + case DXGI_FORMAT_BC7_TYPELESS: + image.type = PF_BC7; + break; + default: + image.type = PF_UNKNOWN; + break; + } + } + else + { + switch( hdr->dsPixelFormat.dwFourCC ) + { + case TYPE_DXT1: + image.type = PF_DXT1; + break; + case TYPE_DXT2: + image.flags &= ~IMAGE_HAS_ALPHA; // alpha is already premultiplied by color + // intentionally fallthrough + case TYPE_DXT3: + image.type = PF_DXT3; + break; + case TYPE_DXT4: + image.flags &= ~IMAGE_HAS_ALPHA; // alpha is already premultiplied by color + // intentionally fallthrough + case TYPE_DXT5: + image.type = PF_DXT5; + break; + case TYPE_ATI2: + image.type = PF_ATI2; + break; + default: + image.type = PF_UNKNOWN; // assume error + break; + } } } else @@ -171,6 +195,9 @@ size_t Image_DXTGetLinearSize( int type, int width, int height, int depth ) case PF_DXT1: return ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 8 ); case PF_DXT3: case PF_DXT5: + case PF_BC6H_SIGNED: + case PF_BC6H_UNSIGNED: + case PF_BC7: case PF_ATI2: return ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 16 ); case PF_LUMINANCE: return (width * height * depth); case PF_BGR_24: @@ -254,16 +281,18 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi { dds_t header; byte *fin; + int headersOffset; + dds_header_dxt10_t header2; - if( filesize < sizeof( dds_t )) + if( filesize < sizeof( header )) return false; - memcpy( &header, buffer, sizeof( dds_t )); + memcpy( &header, buffer, sizeof( header )); if( header.dwIdent != DDSHEADER ) return false; // it's not a dds file, just skip it - if( header.dwSize != sizeof( dds_t ) - sizeof( uint )) // size of the structure (minus MagicNum) + if( header.dwSize != sizeof( header ) - sizeof( uint )) // size of the structure (minus MagicNum) { Con_DPrintf( S_ERROR "Image_LoadDDS: (%s) have corrupted header\n", name ); return false; @@ -275,6 +304,13 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi return false; } + headersOffset = sizeof( header ); + if( header.dsPixelFormat.dwFourCC == TYPE_DX10 ) + { + memcpy( &header2, buffer + sizeof( header ), sizeof( header2 )); + headersOffset += sizeof( header2 ); + } + image.width = header.dwWidth; image.height = header.dwHeight; @@ -284,7 +320,7 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi if( !Image_ValidSize( name )) return false; - Image_DXTGetPixelFormat( &header ); // and image type too :) + Image_DXTGetPixelFormat( &header, &header2 ); // and image type too :) Image_DXTAdjustVolume( &header ); if( !Image_CheckFlag( IL_DDS_HARDWARE ) && ImageDXT( image.type )) @@ -296,9 +332,9 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi return false; } - image.size = Image_DXTCalcSize( name, &header, filesize - 128 ); + image.size = Image_DXTCalcSize( name, &header, filesize - headersOffset ); if( image.size == 0 ) return false; // just in case - fin = (byte *)(buffer + sizeof( dds_t )); + fin = (byte *)( buffer + headersOffset ); // copy an encode method image.encode = (word)header.dwReserved1[0]; @@ -320,6 +356,8 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi SetBits( image.flags, IMAGE_HAS_ALPHA ); else if( image.type == PF_DXT5 && Image_CheckDXT5Alpha( &header, fin )) SetBits( image.flags, IMAGE_HAS_ALPHA ); + else if ( image.type == PF_BC7 ) + SetBits(image.flags, IMAGE_HAS_ALPHA); if( !FBitSet( header.dsPixelFormat.dwFlags, DDS_LUMINANCE )) SetBits( image.flags, IMAGE_HAS_COLOR ); break; diff --git a/engine/common/imagelib/img_dds.h b/engine/common/imagelib/img_dds.h index 43329233..b13c342c 100644 --- a/engine/common/imagelib/img_dds.h +++ b/engine/common/imagelib/img_dds.h @@ -29,6 +29,7 @@ GNU General Public License for more details. #define TYPE_DXT3 (('3'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT3" #define TYPE_DXT4 (('4'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT4" #define TYPE_DXT5 (('5'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT5" +#define TYPE_DX10 (('0'<<24)+('1'<<16)+('X'<<8)+'D') // little-endian "DX10" #define TYPE_ATI1 (('1'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian "ATI1" #define TYPE_ATI2 (('2'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian "ATI2" #define TYPE_RXGB (('B'<<24)+('G'<<16)+('X'<<8)+'R') // little-endian "RXGB" doom3 normalmaps @@ -75,6 +76,141 @@ GNU General Public License for more details. #define DDS_CUBEMAP_ALL_SIDES 0x0000FC00L #define DDS_VOLUME 0x00200000L +typedef enum +{ + DXGI_FORMAT_UNKNOWN = 0, + DXGI_FORMAT_R32G32B32A32_TYPELESS = 1, + DXGI_FORMAT_R32G32B32A32_FLOAT = 2, + DXGI_FORMAT_R32G32B32A32_UINT = 3, + DXGI_FORMAT_R32G32B32A32_SINT = 4, + DXGI_FORMAT_R32G32B32_TYPELESS = 5, + DXGI_FORMAT_R32G32B32_FLOAT = 6, + DXGI_FORMAT_R32G32B32_UINT = 7, + DXGI_FORMAT_R32G32B32_SINT = 8, + DXGI_FORMAT_R16G16B16A16_TYPELESS = 9, + DXGI_FORMAT_R16G16B16A16_FLOAT = 10, + DXGI_FORMAT_R16G16B16A16_UNORM = 11, + DXGI_FORMAT_R16G16B16A16_UINT = 12, + DXGI_FORMAT_R16G16B16A16_SNORM = 13, + DXGI_FORMAT_R16G16B16A16_SINT = 14, + DXGI_FORMAT_R32G32_TYPELESS = 15, + DXGI_FORMAT_R32G32_FLOAT = 16, + DXGI_FORMAT_R32G32_UINT = 17, + DXGI_FORMAT_R32G32_SINT = 18, + DXGI_FORMAT_R32G8X24_TYPELESS = 19, + DXGI_FORMAT_D32_FLOAT_S8X24_UINT = 20, + DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS = 21, + DXGI_FORMAT_X32_TYPELESS_G8X24_UINT = 22, + DXGI_FORMAT_R10G10B10A2_TYPELESS = 23, + DXGI_FORMAT_R10G10B10A2_UNORM = 24, + DXGI_FORMAT_R10G10B10A2_UINT = 25, + DXGI_FORMAT_R11G11B10_FLOAT = 26, + DXGI_FORMAT_R8G8B8A8_TYPELESS = 27, + DXGI_FORMAT_R8G8B8A8_UNORM = 28, + DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29, + DXGI_FORMAT_R8G8B8A8_UINT = 30, + DXGI_FORMAT_R8G8B8A8_SNORM = 31, + DXGI_FORMAT_R8G8B8A8_SINT = 32, + DXGI_FORMAT_R16G16_TYPELESS = 33, + DXGI_FORMAT_R16G16_FLOAT = 34, + DXGI_FORMAT_R16G16_UNORM = 35, + DXGI_FORMAT_R16G16_UINT = 36, + DXGI_FORMAT_R16G16_SNORM = 37, + DXGI_FORMAT_R16G16_SINT = 38, + DXGI_FORMAT_R32_TYPELESS = 39, + DXGI_FORMAT_D32_FLOAT = 40, + DXGI_FORMAT_R32_FLOAT = 41, + DXGI_FORMAT_R32_UINT = 42, + DXGI_FORMAT_R32_SINT = 43, + DXGI_FORMAT_R24G8_TYPELESS = 44, + DXGI_FORMAT_D24_UNORM_S8_UINT = 45, + DXGI_FORMAT_R24_UNORM_X8_TYPELESS = 46, + DXGI_FORMAT_X24_TYPELESS_G8_UINT = 47, + DXGI_FORMAT_R8G8_TYPELESS = 48, + DXGI_FORMAT_R8G8_UNORM = 49, + DXGI_FORMAT_R8G8_UINT = 50, + DXGI_FORMAT_R8G8_SNORM = 51, + DXGI_FORMAT_R8G8_SINT = 52, + DXGI_FORMAT_R16_TYPELESS = 53, + DXGI_FORMAT_R16_FLOAT = 54, + DXGI_FORMAT_D16_UNORM = 55, + DXGI_FORMAT_R16_UNORM = 56, + DXGI_FORMAT_R16_UINT = 57, + DXGI_FORMAT_R16_SNORM = 58, + DXGI_FORMAT_R16_SINT = 59, + DXGI_FORMAT_R8_TYPELESS = 60, + DXGI_FORMAT_R8_UNORM = 61, + DXGI_FORMAT_R8_UINT = 62, + DXGI_FORMAT_R8_SNORM = 63, + DXGI_FORMAT_R8_SINT = 64, + DXGI_FORMAT_A8_UNORM = 65, + DXGI_FORMAT_R1_UNORM = 66, + DXGI_FORMAT_R9G9B9E5_SHAREDEXP = 67, + DXGI_FORMAT_R8G8_B8G8_UNORM = 68, + DXGI_FORMAT_G8R8_G8B8_UNORM = 69, + DXGI_FORMAT_BC1_TYPELESS = 70, + DXGI_FORMAT_BC1_UNORM = 71, + DXGI_FORMAT_BC1_UNORM_SRGB = 72, + DXGI_FORMAT_BC2_TYPELESS = 73, + DXGI_FORMAT_BC2_UNORM = 74, + DXGI_FORMAT_BC2_UNORM_SRGB = 75, + DXGI_FORMAT_BC3_TYPELESS = 76, + DXGI_FORMAT_BC3_UNORM = 77, + DXGI_FORMAT_BC3_UNORM_SRGB = 78, + DXGI_FORMAT_BC4_TYPELESS = 79, + DXGI_FORMAT_BC4_UNORM = 80, + DXGI_FORMAT_BC4_SNORM = 81, + DXGI_FORMAT_BC5_TYPELESS = 82, + DXGI_FORMAT_BC5_UNORM = 83, + DXGI_FORMAT_BC5_SNORM = 84, + DXGI_FORMAT_B5G6R5_UNORM = 85, + DXGI_FORMAT_B5G5R5A1_UNORM = 86, + DXGI_FORMAT_B8G8R8A8_UNORM = 87, + DXGI_FORMAT_B8G8R8X8_UNORM = 88, + DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM = 89, + DXGI_FORMAT_B8G8R8A8_TYPELESS = 90, + DXGI_FORMAT_B8G8R8A8_UNORM_SRGB = 91, + DXGI_FORMAT_B8G8R8X8_TYPELESS = 92, + DXGI_FORMAT_B8G8R8X8_UNORM_SRGB = 93, + DXGI_FORMAT_BC6H_TYPELESS = 94, + DXGI_FORMAT_BC6H_UF16 = 95, + DXGI_FORMAT_BC6H_SF16 = 96, + DXGI_FORMAT_BC7_TYPELESS = 97, + DXGI_FORMAT_BC7_UNORM = 98, + DXGI_FORMAT_BC7_UNORM_SRGB = 99, + DXGI_FORMAT_AYUV = 100, + DXGI_FORMAT_Y410 = 101, + DXGI_FORMAT_Y416 = 102, + DXGI_FORMAT_NV12 = 103, + DXGI_FORMAT_P010 = 104, + DXGI_FORMAT_P016 = 105, + DXGI_FORMAT_420_OPAQUE = 106, + DXGI_FORMAT_YUY2 = 107, + DXGI_FORMAT_Y210 = 108, + DXGI_FORMAT_Y216 = 109, + DXGI_FORMAT_NV11 = 110, + DXGI_FORMAT_AI44 = 111, + DXGI_FORMAT_IA44 = 112, + DXGI_FORMAT_P8 = 113, + DXGI_FORMAT_A8P8 = 114, + DXGI_FORMAT_B4G4R4A4_UNORM = 115, + DXGI_FORMAT_P208 = 130, + DXGI_FORMAT_V208 = 131, + DXGI_FORMAT_V408 = 132, + DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE, + DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE, + DXGI_FORMAT_FORCE_UINT = 0xffffffff +} dxgi_format_t; + +typedef enum +{ + D3D10_RESOURCE_DIMENSION_UNKNOWN = 0, + D3D10_RESOURCE_DIMENSION_BUFFER = 1, + D3D10_RESOURCE_DIMENSION_TEXTURE1D = 2, + D3D10_RESOURCE_DIMENSION_TEXTURE2D = 3, + D3D10_RESOURCE_DIMENSION_TEXTURE3D = 4 +} dds_resource_dimension_t; + typedef struct dds_pf_s { uint32_t dwSize; @@ -96,6 +232,15 @@ typedef struct dds_caps_s uint32_t dwCaps4; // currently unused } dds_caps_t; +typedef struct dds_header_dxt10_s +{ + dxgi_format_t dxgiFormat; + dds_resource_dimension_t resourceDimension; + uint32_t miscFlag; + uint32_t arraySize; + uint32_t miscFlags2; +} dds_header_dxt10_t; + typedef struct dds_s { uint32_t dwIdent; // must matched with DDSHEADER From 51fcae8cec1210cad2650283cd17cc4100d52252 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Thu, 3 Mar 2022 13:51:05 +0400 Subject: [PATCH 171/548] ref_gl: gl_image: added check for hardware support of BC6H/BC7 compression formats --- ref_gl/gl_image.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ref_gl/gl_image.c b/ref_gl/gl_image.c index ac5696dc..1b5ed033 100644 --- a/ref_gl/gl_image.c +++ b/ref_gl/gl_image.c @@ -1176,6 +1176,15 @@ static qboolean GL_UploadTexture( gl_texture_t *tex, rgbdata_t *pic ) return false; } + if( pic->type == PF_BC6H_SIGNED || pic->type == PF_BC6H_UNSIGNED || pic->type == PF_BC7 ) + { + if( !GL_Support( GL_ARB_TEXTURE_COMPRESSION_BPTC )) + { + gEngfuncs.Con_DPrintf( S_ERROR "GL_UploadTexture: BC6H/BC7 compression formats is not supported by your hardware\n" ); + return false; + } + } + GL_SetTextureDimensions( tex, pic->width, pic->height, pic->depth ); GL_SetTextureFormat( tex, pic->type, pic->flags ); From 12da44a3d75a723394f2b3e687487fd31c5c0580 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 8 Mar 2022 03:29:03 +0300 Subject: [PATCH 172/548] engine: server: send protocol version to distinguish old engine and new --- engine/server/sv_client.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c index 64ebfbe4..3f0c4bd0 100644 --- a/engine/server/sv_client.c +++ b/engine/server/sv_client.c @@ -843,6 +843,8 @@ void SV_Info( netadr_t from ) if( svs.clients[i].state >= cs_connected ) count++; + // a1ba: send protocol version to distinguish old engine and new + Info_SetValueForKey( string, "p", va( "%i", PROTOCOL_VERSION ), MAX_INFO_STRING ); Info_SetValueForKey( string, "host", hostname.string, MAX_INFO_STRING ); Info_SetValueForKey( string, "map", sv.name, MAX_INFO_STRING ); Info_SetValueForKey( string, "dm", va( "%i", (int)svgame.globals->deathmatch ), MAX_INFO_STRING ); From 668d528e3b9a02f020d3c05425388bab44fda274 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 8 Mar 2022 03:29:50 +0300 Subject: [PATCH 173/548] engine: client: delete unused function --- engine/client/cl_parse.c | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 24dc19d7..daf819d9 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -1322,30 +1322,6 @@ void CL_RegisterUserMessage( sizebuf_t *msg ) CL_LinkUserMessage( pszName, svc_num, size ); } -/* -================ -CL_RegisterUserMessage - -register new user message or update existing -================ -*/ -/* -void CL_LegacyRegisterUserMessage( sizebuf_t *msg ) -{ - char *pszName; - int svc_num, size; - - svc_num = MSG_ReadByte( msg ); - size = MSG_ReadByte( msg ); - pszName = MSG_ReadString( msg ); - - // important stuff - if( size == 0xFF ) size = -1; - svc_num = bound( 0, svc_num, 255 ); - - CL_LinkUserMessage( pszName, svc_num, size ); -} -*/ /* ================ CL_UpdateUserinfo From 37cf4da13642394c50ae138d8caa69742760fec0 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 8 Mar 2022 05:03:45 +0300 Subject: [PATCH 174/548] engine: client: switch to protocol info key in detecting old engine servers --- engine/client/cl_main.c | 14 +++----------- engine/client/client.h | 2 -- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index a0b79b95..688c7fc0 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -1563,7 +1563,6 @@ void CL_LocalServers_f( void ) Con_Printf( "Scanning for servers on the local network area...\n" ); NET_Config( true ); // allow remote - cls.legacyservercount = 0; // send a broadcast packet adr.type = NA_BROADCAST; @@ -1591,7 +1590,6 @@ void CL_InternetServers_f( void ) Info_SetValueForKey( info, "gamedir", GI->gamefolder, remaining ); Info_SetValueForKey( info, "clver", XASH_VERSION, remaining ); // let master know about client version // Info_SetValueForKey( info, "nat", cl_nat->string, remaining ); - cls.legacyservercount = 0; cls.internetservers_wait = NET_SendToMasters( NS_CLIENT, sizeof( MS_SCAN_REQUEST ) + Q_strlen( info ), fullquery ); cls.internetservers_pending = true; @@ -1722,8 +1720,6 @@ void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) { Netchan_OutOfBandPrint( NS_CLIENT, from, "info %i", PROTOCOL_LEGACY_VERSION ); Con_Printf( "^1Server^7: %s, Info: %s\n", NET_AdrToString( from ), infostring ); - if( cls.legacyservercount < MAX_LEGACY_SERVERS ) - cls.legacyservers[cls.legacyservercount++] = from; return; } @@ -1733,14 +1729,10 @@ void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) return; // unsupported proto } - for( i = 0; i < cls.legacyservercount; i++ ) + if( !COM_CheckString( Info_ValueForKey( infostring, "p" ))) { - if( NET_CompareAdr( cls.legacyservers[i], from ) ) - { - Info_SetValueForKey( infostring, "legacy", "1", sizeof( infostring ) ); - Con_Print("Legacy: "); - break; - } + Info_SetValueForKey( infostring, "legacy", "1", sizeof( infostring ) ); + Con_Print("Legacy: "); } // more info about servers diff --git a/engine/client/client.h b/engine/client/client.h index 0a15384a..03f75131 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -625,8 +625,6 @@ typedef struct // legacy mode support qboolean legacymode; // one-way 48 protocol compatibility netadr_t legacyserver; - netadr_t legacyservers[MAX_LEGACY_SERVERS]; - int legacyservercount; int extensions; netadr_t serveradr; From df616b950dbc7ff6f53b55231f9190310b7951f6 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 8 Mar 2022 07:00:20 +0300 Subject: [PATCH 175/548] engine: add NET_AdrToString to menu extended API, upgrade mainui submodule --- engine/client/cl_gameui.c | 3 ++- engine/common/protocol.h | 5 ----- engine/menu_int.h | 2 ++ mainui | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/engine/client/cl_gameui.c b/engine/client/cl_gameui.c index 427f96d1..6308e2e6 100644 --- a/engine/client/cl_gameui.c +++ b/engine/client/cl_gameui.c @@ -1235,7 +1235,8 @@ static ui_extendedfuncs_t gExtendedfuncs = Con_UtfMoveRight, pfnGetRenderers, Sys_DoubleTime, - _COM_ParseFileSafe + _COM_ParseFileSafe, + NET_AdrToString }; void UI_UnloadProgs( void ) diff --git a/engine/common/protocol.h b/engine/common/protocol.h index 8d1f83c5..7d00ba0b 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -299,11 +299,6 @@ extern const char *clc_strings[clc_lastmsg+1]; #define MAX_LEGACY_ENTITY_BITS 12 #define MAX_LEGACY_WEAPON_BITS 5 #define MAX_LEGACY_MODEL_BITS 11 -#if XASH_LOW_MEMORY >= 1 -#define MAX_LEGACY_SERVERS 32 -#else -#define MAX_LEGACY_SERVERS 256 -#endif #define MAX_LEGACY_TOTAL_CMDS 28 // magic number from old engine's sv_client.c #endif//NET_PROTOCOL_H diff --git a/engine/menu_int.h b/engine/menu_int.h index 85353c28..ee48ed48 100644 --- a/engine/menu_int.h +++ b/engine/menu_int.h @@ -212,6 +212,8 @@ typedef struct ui_extendedfuncs_s { double (*pfnDoubleTime)( void ); char *(*pfnParseFile)( char *data, char *buf, const int size, unsigned int flags, int *len ); + + const char *(*pfnAdrToString)( const struct netadr_s a ); } ui_extendedfuncs_t; // deprecated export from old engine diff --git a/mainui b/mainui index da881649..f63a6081 160000 --- a/mainui +++ b/mainui @@ -1 +1 @@ -Subproject commit da881649a358981be75396b2403bd79580ce4706 +Subproject commit f63a6081dd3c3e8d98cf3000e4070b0c1420d90a From a6fbf5fb74cb2a8db2e31fa09dff3f04ce8e54bf Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 9 Mar 2022 08:16:28 +0300 Subject: [PATCH 176/548] mainui: update --- mainui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainui b/mainui index f63a6081..01e964fd 160000 --- a/mainui +++ b/mainui @@ -1 +1 @@ -Subproject commit f63a6081dd3c3e8d98cf3000e4070b0c1420d90a +Subproject commit 01e964fdc26f5dce1512c030d0dfd68e17be2858 From f67d0ffa5a5b9b263172010c303ef54303634d56 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 10 Mar 2022 04:51:38 +0300 Subject: [PATCH 177/548] engine: common: fix broken function name and strings after find&replace in socket code --- engine/common/net_ws.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/engine/common/net_ws.c b/engine/common/net_ws.c index d8206fef..ef403b3c 100644 --- a/engine/common/net_ws.c +++ b/engine/common/net_ws.c @@ -1382,10 +1382,10 @@ qboolean NET_BufferToBufferDecompress( byte *dest, uint *destLen, byte *source, /* ==================== -NET_Isocket +NET_IPSocket ==================== */ -static int NET_Isocket( const char *net_interface, int port, qboolean multicast ) +static int NET_IPSocket( const char *net_interface, int port, qboolean multicast ) { struct sockaddr_in addr; int err, net_socket; @@ -1396,7 +1396,7 @@ static int NET_Isocket( const char *net_interface, int port, qboolean multicast { err = WSAGetLastError(); if( err != WSAEAFNOSUPPORT ) - Con_DPrintf( S_WARN "NET_UDsocket: port: %d socket: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "NET_UDPSocket: port: %d socket: %s\n", port, NET_ErrorString( )); return INVALID_SOCKET; } @@ -1404,7 +1404,7 @@ static int NET_Isocket( const char *net_interface, int port, qboolean multicast { struct timeval timeout; - Con_DPrintf( S_WARN "NET_UDsocket: port: %d ioctl FIONBIO: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "NET_UDPSocket: port: %d ioctl FIONBIO: %s\n", port, NET_ErrorString( )); // try timeout instead of NBIO timeout.tv_sec = timeout.tv_usec = 0; setsockopt( net_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); @@ -1413,14 +1413,14 @@ static int NET_Isocket( const char *net_interface, int port, qboolean multicast // make it broadcast capable if( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof( _true ) ) ) ) { - Con_DPrintf( S_WARN "NET_UDsocket: port: %d setsockopt SO_BROADCAST: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "NET_UDPSocket: port: %d setsockopt SO_BROADCAST: %s\n", port, NET_ErrorString( )); } if( Sys_CheckParm( "-reuse" ) || multicast ) { if( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof( optval )) ) ) { - Con_DPrintf( S_WARN "NET_UDsocket: port: %d setsockopt SO_REUSEADDR: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "NET_UDPSocket: port: %d setsockopt SO_REUSEADDR: %s\n", port, NET_ErrorString( )); closesocket( net_socket ); return INVALID_SOCKET; } @@ -1435,7 +1435,7 @@ static int NET_Isocket( const char *net_interface, int port, qboolean multicast { err = WSAGetLastError(); if( err != WSAENOPROTOOPT ) - Con_Printf( S_WARN "NET_UDsocket: port: %d setsockopt IP_TOS: %s\n", port, NET_ErrorString( )); + Con_Printf( S_WARN "NET_UDPSocket: port: %d setsockopt IP_TOS: %s\n", port, NET_ErrorString( )); closesocket( net_socket ); return INVALID_SOCKET; } @@ -1452,7 +1452,7 @@ static int NET_Isocket( const char *net_interface, int port, qboolean multicast if( NET_IsSocketError( bind( net_socket, (void *)&addr, sizeof( addr )) ) ) { - Con_DPrintf( S_WARN "NET_UDsocket: port: %d bind: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "NET_UDPSocket: port: %d bind: %s\n", port, NET_ErrorString( )); closesocket( net_socket ); return INVALID_SOCKET; } @@ -1461,7 +1461,7 @@ static int NET_Isocket( const char *net_interface, int port, qboolean multicast { optval = 1; if( NET_IsSocketError( setsockopt( net_socket, IPPROTO_IP, IP_MULTICAST_LOOP, (const char *)&optval, sizeof( optval )) ) ) - Con_DPrintf( S_WARN "NET_UDsocket: port %d setsockopt IP_MULTICAST_LOOP: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "NET_UDPSocket: port %d setsockopt IP_MULTICAST_LOOP: %s\n", port, NET_ErrorString( )); } return net_socket; @@ -1481,7 +1481,7 @@ static void NET_OpenIP( void ) port = net_iphostport->value; if( !port ) port = net_hostport->value; if( !port ) port = PORT_SERVER; // forcing to default - net.ip_sockets[NS_SERVER] = NET_Isocket( net_ipname->string, port, false ); + net.ip_sockets[NS_SERVER] = NET_IPSocket( net_ipname->string, port, false ); if( !NET_IsSocketValid( net.ip_sockets[NS_SERVER] ) && Host_IsDedicated() ) Host_Error( "Couldn't allocate dedicated server IP port %d.\n", port ); @@ -1496,10 +1496,10 @@ static void NET_OpenIP( void ) port = net_ipclientport->value; if( !port ) port = net_clientport->value; if( !port ) port = PORT_ANY; // forcing to default - net.ip_sockets[NS_CLIENT] = NET_Isocket( net_ipname->string, port, false ); + net.ip_sockets[NS_CLIENT] = NET_IPSocket( net_ipname->string, port, false ); if( !NET_IsSocketValid( net.ip_sockets[NS_CLIENT] ) ) - net.ip_sockets[NS_CLIENT] = NET_Isocket( net_ipname->string, PORT_ANY, false ); + net.ip_sockets[NS_CLIENT] = NET_IPSocket( net_ipname->string, PORT_ANY, false ); cl_port = port; } } From fba323cf1f9f71bfc78a9f7da9dc4c0654ac9b25 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 10 Mar 2022 04:52:16 +0300 Subject: [PATCH 178/548] engine: common: cosmetic changes to usage string generating code --- engine/common/host.c | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/engine/common/host.c b/engine/common/host.c index 44e46f67..07fbfd55 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -86,9 +86,11 @@ void Sys_PrintUsage( void ) O("-dev [level] ","set log verbosity 0-2") O("-log ","write log to \"engine.log\"") O("-nowriteconfig ","disable config save") + #if !XASH_WIN32 O("-casesensitive ","disable case-insensitive FS emulation") #endif // !XASH_WIN32 + #if !XASH_MOBILE_PLATFORM O("-daemonize ","run engine in background, dedicated only") #endif // !XASH_MOBILE_PLATFORM @@ -99,53 +101,53 @@ void Sys_PrintUsage( void ) O("-height ","set window height") O("-oldfont ","enable unused Quake font in Half-Life") - #if !XASH_MOBILE_PLATFORM +#if !XASH_MOBILE_PLATFORM O("-fullscreen ","run engine in fullscreen mode") O("-windowed ","run engine in windowed mode") O("-dedicated ","run engine in dedicated server mode") - #endif // XASH_MOBILE_PLATFORM +#endif // XASH_MOBILE_PLATFORM - #if XASH_ANDROID - O("-nativeegl ","use native egl implementation. Use if screen does not update or black") - #endif // XASH_ANDROID +#if XASH_ANDROID + O("-nativeegl ","use native egl implementation. Use if screen does not update or black") +#endif // XASH_ANDROID - #if XASH_WIN32 - O("-noavi ","disable AVI support") - O("-nointro ","disable intro video") - #endif // XASH_WIN32 +#if XASH_WIN32 + O("-noavi ","disable AVI support") + O("-nointro ","disable intro video") +#endif // XASH_WIN32 - #if XASH_DOS +#if XASH_DOS O("-novesa ","disable vesa") - #endif // XASH_DOS +#endif // XASH_DOS - #if XASH_VIDEO == VIDEO_FBDEV +#if XASH_VIDEO == VIDEO_FBDEV O("-fbdev ","open selected framebuffer") O("-ttygfx ","set graphics mode in tty") O("-doublebuffer ","enable doublebuffering") - #endif // XASH_VIDEO == VIDEO_FBDEV +#endif // XASH_VIDEO == VIDEO_FBDEV - #if XASH_SOUND == SOUND_ALSA +#if XASH_SOUND == SOUND_ALSA O("-alsadev ","open selected ALSA device") - #endif // XASH_SOUND == SOUND_ALSA +#endif // XASH_SOUND == SOUND_ALSA O("-nojoy ","disable joystick support") - #ifdef XASH_SDL + +#ifdef XASH_SDL O("-sdl_joy_old_api ","use SDL legacy joystick API") O("-sdl_renderer ","use alternative SDL_Renderer for software") - #endif // XASH_SDL +#endif // XASH_SDL O("-nosound ","disable sound") O("-noenginemouse ","disable mouse completely") O("-ref ","use selected renderer dll") - O("-gldebug ","enable OpenGL debug log") - + O("-gldebug ","enable OpenGL debug log") #endif // XASH_DEDICATED O("-noip ","disable TCP/IP") O("-noch ","disable crashhandler") O("-disablehelp ","disable this message") O("-dll ","override server DLL path") -#ifndef XASH_DEDICATED +#if !XASH_DEDICATED O("-clientlib ","override client DLL path") #endif O("-rodir ","set read-only base directory, experimental") From 91742d613dd2f5496af9c8bd1d8469e244312713 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 11 Mar 2022 13:41:47 +0300 Subject: [PATCH 179/548] github: goodbye, aarch64 runner --- .github/workflows/c-cpp.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 60dbc618..8e624d51 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -19,9 +19,9 @@ jobs: - os: ubuntu-18.04 targetos: linux targetarch: i386 - - os: ubuntu-aarch64-20.04 - targetos: linux - targetarch: aarch64 +# - os: ubuntu-aarch64-20.04 +# targetos: linux +# targetarch: aarch64 - os: ubuntu-18.04 targetos: android From d2f3b1974ef9382cea2a0ebcfae1f2521a93bd7d Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 11 Mar 2022 14:05:53 +0300 Subject: [PATCH 180/548] github: disable cleanup for self-hosted runners because there are none --- .github/workflows/c-cpp.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 8e624d51..e53a8b16 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -1,11 +1,11 @@ name: Build & Deploy Engine on: [push, pull_request] jobs: - cleanup: - runs-on: self-hosted - steps: - - name: Cleanup - run: rm -rf .* || true +# cleanup: +# runs-on: self-hosted +# steps: +# - name: Cleanup +# run: rm -rf .* || true build: runs-on: ${{ matrix.os }} continue-on-error: true From cd2720ba81c765ea06f31f0b7efef37625950080 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Sat, 12 Mar 2022 22:59:38 +0400 Subject: [PATCH 181/548] engine: common: imagelib: fixed loading BMP files with v4/v5 headers --- engine/common/imagelib/img_bmp.c | 11 ++++++----- engine/common/imagelib/img_bmp.h | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/engine/common/imagelib/img_bmp.c b/engine/common/imagelib/img_bmp.c index 74583716..c81c3701 100644 --- a/engine/common/imagelib/img_bmp.c +++ b/engine/common/imagelib/img_bmp.c @@ -41,7 +41,7 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi buf_p = (byte *)buffer; memcpy( &bhdr, buf_p, sizeof( bmp_t )); - buf_p += sizeof( bmp_t ); + buf_p += BI_FILE_HEADER_SIZE + bhdr.bitmapHeaderSize; // bogus file header check if( bhdr.reserved0 != 0 ) return false; @@ -53,9 +53,9 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi return false; } - if( bhdr.bitmapHeaderSize != 0x28 ) + if(!( bhdr.bitmapHeaderSize == 40 || bhdr.bitmapHeaderSize == 108 || bhdr.bitmapHeaderSize == 124 )) { - Con_DPrintf( S_ERROR "Image_LoadBMP: invalid header size %i\n", bhdr.bitmapHeaderSize ); + Con_DPrintf( S_ERROR "Image_LoadBMP: %s have non-standard header size %i\n", name, bhdr.bitmapHeaderSize ); return false; } @@ -187,6 +187,7 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi return false; } + image.depth = 1; image.size = image.width * image.height * bpp; image.rgba = Mem_Malloc( host.imagepool, image.size ); @@ -313,8 +314,8 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi } VectorDivide( reflectivity, ( image.width * image.height ), image.fogParams ); - if( image.palette ) Image_GetPaletteBMP( image.palette ); - image.depth = 1; + if( image.palette ) + Image_GetPaletteBMP( image.palette ); return true; } diff --git a/engine/common/imagelib/img_bmp.h b/engine/common/imagelib/img_bmp.h index 72b4d8a2..a5728eeb 100644 --- a/engine/common/imagelib/img_bmp.h +++ b/engine/common/imagelib/img_bmp.h @@ -22,6 +22,7 @@ GNU General Public License for more details. ======================================================================== */ +#define BI_FILE_HEADER_SIZE 14 #define BI_SIZE 40 // size of bitmap info header. #if !defined(BI_RGB) #define BI_RGB 0 // uncompressed RGB bitmap(defined in wingdi.h) From 65d624140efd6729da246634f2ce637b88d1c79f Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Tue, 15 Mar 2022 22:49:50 +0400 Subject: [PATCH 182/548] engine: filesystem: fixed FS_Search algorithm for ZIP files (fix #796) --- engine/common/filesystem.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index a6a0af08..01bd4676 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -3658,6 +3658,7 @@ search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) searchpath_t *searchpath; pack_t *pak; wfile_t *wad; + zip_t *zip; int i, basepathlength, numfiles, numchars; int resultlistindex, dirlistindex; const char *slash, *backslash, *colon, *separator; @@ -3725,6 +3726,42 @@ search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) } } } + else if( searchpath->zip ) + { + zip = searchpath->zip; + for( i = 0; i < zip->numfiles; i++ ) + { + Q_strncpy( temp, zip->files[i].name, sizeof(temp) ); + while( temp[0] ) + { + if( matchpattern( temp, (char *)pattern, true )) + { + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + { + if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) + break; + } + + if( resultlistindex == resultlist.numstrings ) + stringlistappend( &resultlist, temp ); + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } + } else if( searchpath->wad ) { string wadpattern, wadname, temp2; From fc7fb8b62da89805b9eb068ad796fe5a647d43d3 Mon Sep 17 00:00:00 2001 From: a1batross Date: Sat, 19 Mar 2022 01:57:25 +0300 Subject: [PATCH 183/548] engine: masterlist: replace master server domain by mentality.rip It is essentially the same as ms.xash.su but due to current situation, it is known to be blocked on some Ukrainian ISPs. ms2.xash.su is retired for now. --- engine/common/masterlist.c | 1 - engine/common/netchan.h | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/engine/common/masterlist.c b/engine/common/masterlist.c index 4dbd325b..192372e7 100644 --- a/engine/common/masterlist.c +++ b/engine/common/masterlist.c @@ -254,6 +254,5 @@ void NET_InitMasters( void ) // keep main master always there NET_AddMaster( MASTERSERVER_ADR, false ); - NET_AddMaster( MASTERSERVER_ADR2, false ); NET_LoadMasters( ); } diff --git a/engine/common/netchan.h b/engine/common/netchan.h index c2138acd..a1e7c3a7 100644 --- a/engine/common/netchan.h +++ b/engine/common/netchan.h @@ -69,8 +69,7 @@ GNU General Public License for more details. // bytes will be stripped by the networking channel layer #define NET_MAX_MESSAGE PAD_NUMBER(( NET_MAX_PAYLOAD + HEADER_BYTES ), 16 ) -#define MASTERSERVER_ADR "ms.xash.su:27010" -#define MASTERSERVER_ADR2 "ms2.xash.su:27010" +#define MASTERSERVER_ADR "mentality.rip:27010" #define MS_SCAN_REQUEST "1\xFF" "0.0.0.0:0\0" #define PORT_MASTER 27010 From 654bac83a51e401a59a2ec87a61079961c1fc316 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 20 Mar 2022 06:38:17 +0300 Subject: [PATCH 184/548] scripts: do not upload branches as main continious prerelease --- scripts/continious_upload.sh | 68 ++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/scripts/continious_upload.sh b/scripts/continious_upload.sh index e29d964e..fb91611b 100755 --- a/scripts/continious_upload.sh +++ b/scripts/continious_upload.sh @@ -84,29 +84,59 @@ if [ ! -z "$UPLOADTOOL_SUFFIX" ] ; then fi else - # ,, is a bash-ism to convert variable to lower case - case $(tr '[:upper:]' '[:lower:]' <<< "$GIT_TAG") in - "") - # Do not use "latest" as it is reserved by GitHub - RELEASE_NAME="continuous" - RELEASE_TITLE="Continuous build" - if [ -z "$UPLOADTOOL_ISPRERELEASE" ] ; then + if [ "$GITHUB_ACTIONS" = "true" ]; then + if [ "$GITHUB_REF_TYPE" == "branch" ]; then + if [ "$GITHUB_REF_NAME" == "master" ]; then + RELEASE_NAME="continuous" + RELEASE_TITLE="Continuous build" + else + RELEASE_NAME="continuous-$GITHUB_REF_NAME" + RELEASE_TITLE="Continuous build ($GITHUB_REF_NAME)" + fi + if [ -z "$UPLOADTOOL_ISPRERELEASE" ]; then is_prerelease="false" else is_prerelease="true" fi - ;; - *-alpha*|*-beta*|*-rc*) - RELEASE_NAME="$GIT_TAG" - RELEASE_TITLE="Pre-release build ($GIT_TAG)" - is_prerelease="true" - ;; - *) - RELEASE_NAME="$GIT_TAG" - RELEASE_TITLE="Release build ($GIT_TAG)" - is_prerelease="false" - ;; - esac + elif [ "$GITHUB_REF_TYPE" == "tag" ]; then + case $(tr '[:upper:]' '[:lower:]' <<< "$GITHUB_REF_NAME") in + *-alpha*|*-beta*|*-rc*) + RELEASE_NAME="$GITHUB_REF_NAME" + RELEASE_TITLE="Pre-release build ($GITHUB_REF_NAME)" + is_prerelease="true" + ;; + *) + RELEASE_NAME="$GITHUB_REF_NAME" + RELEASE_TITLE="Release build ($GITHUB_REF_NAME)" + is_prerelease="false" + ;; + esac + fi + else + # ,, is a bash-ism to convert variable to lower case + case $(tr '[:upper:]' '[:lower:]' <<< "$GIT_TAG") in + "") + # Do not use "latest" as it is reserved by GitHub + RELEASE_NAME="continuous" + RELEASE_TITLE="Continuous build" + if [ -z "$UPLOADTOOL_ISPRERELEASE" ] ; then + is_prerelease="false" + else + is_prerelease="true" + fi + ;; + *-alpha*|*-beta*|*-rc*) + RELEASE_NAME="$GIT_TAG" + RELEASE_TITLE="Pre-release build ($GIT_TAG)" + is_prerelease="true" + ;; + *) + RELEASE_NAME="$GIT_TAG" + RELEASE_TITLE="Release build ($GIT_TAG)" + is_prerelease="false" + ;; + esac + fi fi # Do not upload non-master branch builds From 80e20cb55bad5048bfd825c1e6d4627fb4bd3707 Mon Sep 17 00:00:00 2001 From: a1batross Date: Wed, 30 Mar 2022 22:30:32 +0300 Subject: [PATCH 185/548] Documentation: ports: update info about PSP port --- Documentation/ports.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/ports.md b/Documentation/ports.md index 28dd74c0..fa072aba 100644 --- a/Documentation/ports.md +++ b/Documentation/ports.md @@ -26,9 +26,9 @@ Table is sorted by status. | Haiku | Supported | not maintained | Was added by #478 and #483 | DOS4GW | Supported | @mittorn | | GNU/Linux(mipsel) | Supported | @mittorn | -| Switch | In progress | @fgsfdsfgs | [Github Repository](https://github.com/fgsfdsfgs/xash3d-fwgs/tree/switch_new) -| Wii | In progress | Collaborative effort | [Github Repository](https://github.com/saucesaft/xash3d-wii) -| PSP | In progress | Collaborative effort | No sources available at this moment +| Switch | In progress | @fgsfdsfgs | [GitHub Repository](https://github.com/fgsfdsfgs/xash3d-fwgs/tree/switch_new) +| Wii | In progress | Collaborative effort | [GitHub Repository](https://github.com/saucesaft/xash3d-wii) +| PSP | In progress | @Crow_bar, @Velaron | [GitHub Repository](https://github.com/Crow-bar/xash3d-fwgs) | Emscripten | Old Engine | not maintained | | Oculus Quest | Old Engine fork | @DrBeef | [GitHub Repository](https://github.com/DrBeef/Lambda1VR) | PSVita | Old Engine fork | not maintained | [GitHub Repository](https://github.com/fgsfdsfgs/vitaXash3D) From c8410f7e462f90693e9820893af09d10b85b359f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 2 Apr 2022 13:36:53 +0300 Subject: [PATCH 186/548] engine: server: don't issue an assert on invalid client names --- engine/server/sv_client.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c index 3f0c4bd0..e1215491 100644 --- a/engine/server/sv_client.c +++ b/engine/server/sv_client.c @@ -733,7 +733,8 @@ sv_client_t *SV_ClientByName( const char *name ) sv_client_t *cl; int i; - ASSERT( name && *name ); + if( !COM_CheckString( name )) + return NULL; for( i = 0, cl = svs.clients; i < svgame.globals->maxClients; i++, cl++ ) { From d7431783d4f3816584be3a23ba1cf5a3662c9480 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Mon, 25 Oct 2021 22:00:14 +0400 Subject: [PATCH 187/548] engine: sv_save: added entity_state_t->startpos to save fields --- engine/server/sv_save.c | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/server/sv_save.c b/engine/server/sv_save.c index 2e754ce1..0fcf66e4 100644 --- a/engine/server/sv_save.c +++ b/engine/server/sv_save.c @@ -174,6 +174,7 @@ static TYPEDESCRIPTION gStaticEntry[] = DEFINE_FIELD( entity_state_t, framerate, FIELD_FLOAT ), DEFINE_FIELD( entity_state_t, mins, FIELD_VECTOR ), DEFINE_FIELD( entity_state_t, maxs, FIELD_VECTOR ), + DEFINE_FIELD( entity_state_t, startpos, FIELD_VECTOR ), DEFINE_FIELD( entity_state_t, rendermode, FIELD_INTEGER ), DEFINE_FIELD( entity_state_t, renderamt, FIELD_FLOAT ), DEFINE_ARRAY( entity_state_t, rendercolor, FIELD_CHARACTER, sizeof( color24 )), From 58dbc1ba3105003dd7059b9ce3613338cefd13df Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Mon, 4 Apr 2022 23:44:44 +0400 Subject: [PATCH 188/548] engine: system: disabled skipping color codes for Wcon_WinPrint --- engine/common/system.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/engine/common/system.c b/engine/common/system.c index b2a0a659..1c1d6b4b 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -531,10 +531,6 @@ void Sys_Print( const char *pMsg ) { i++; // skip console pseudo graph } - else if( IsColorString( &msg[i] )) - { - i++; // skip color prefix - } else { if( msg[i] == '\1' || msg[i] == '\2' || msg[i] == '\3' ) From 95ee88720c89e34f5b2e991f5e303fe5d8e92e5e Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Wed, 6 Apr 2022 15:18:34 +0400 Subject: [PATCH 189/548] engine: platform: win32: major refactoring of console code --- engine/common/system.c | 3 - engine/common/system.h | 3 +- engine/platform/win32/con_win.c | 736 ++++++++++++++++++++------------ 3 files changed, 458 insertions(+), 284 deletions(-) diff --git a/engine/common/system.c b/engine/common/system.c index 1c1d6b4b..97b56b37 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -353,9 +353,6 @@ void Sys_WaitForQuit( void ) { #if XASH_WIN32 MSG msg; - - Wcon_RegisterHotkeys(); - msg.message = 0; // wait for the user to quit diff --git a/engine/common/system.h b/engine/common/system.h index 32b83053..5c9ffdb9 100644 --- a/engine/common/system.h +++ b/engine/common/system.h @@ -89,10 +89,9 @@ void Wcon_ShowConsole( qboolean show ); void Wcon_CreateConsole( void ); void Wcon_DestroyConsole( void ); void Wcon_DisableInput( void ); -void Wcon_Clear( void ); char *Wcon_Input( void ); void Wcon_WinPrint( const char *pMsg ); -void Wcon_RegisterHotkeys( void ); +void Wcon_SetStatus( const char *pStatus ); #endif // text messages diff --git a/engine/platform/win32/con_win.c b/engine/platform/win32/con_win.c index 68a5d896..c4d9597d 100644 --- a/engine/platform/win32/con_win.c +++ b/engine/platform/win32/con_win.c @@ -25,198 +25,429 @@ WIN32 CONSOLE */ // console defines -#define SUBMIT_ID 1 // "submit" button -#define QUIT_ON_ESCAPE_ID 2 // escape event -#define EDIT_ID 110 -#define INPUT_ID 109 -#define IDI_ICON1 101 - -#define SYSCONSOLE "XashConsole" #define COMMAND_HISTORY 64 // system console keep more commands than game console typedef struct { char title[64]; HWND hWnd; - HWND hwndBuffer; - HWND hwndButtonSubmit; - HBRUSH hbrEditBackground; - HFONT hfBufferFont; - HWND hwndInputLine; + HANDLE hInput; + HANDLE hOutput; + int inputLine; + int browseLine; + int cursorPosition; + int totalLines; + int savedConsoleTextLen; + int consoleTextLen; string consoleText; - string returnedText; - string historyLines[COMMAND_HISTORY]; - int nextHistoryLine; - int historyLine; - int status; - int windowWidth, windowHeight; - WNDPROC SysInputLineWndProc; - size_t outLen; + string savedConsoleText; + string statusLine; + string lineBuffer[COMMAND_HISTORY]; + qboolean inputEnabled; + qboolean consoleVisible; // log stuff - qboolean log_active; + qboolean log_active; char log_path[MAX_SYSPATH]; } WinConData; static WinConData s_wcd; +static WORD g_color_table[8] = +{ +FOREGROUND_INTENSITY, // black +FOREGROUND_RED, // red +FOREGROUND_GREEN, // green +FOREGROUND_RED | FOREGROUND_GREEN, // yellow +FOREGROUND_BLUE | FOREGROUND_INTENSITY, // blue +FOREGROUND_GREEN | FOREGROUND_BLUE, // cyan +FOREGROUND_RED | FOREGROUND_BLUE, // magenta +FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // default color (white) +}; + +static BOOL WINAPI Wcon_HandleConsole(DWORD CtrlType) +{ + return TRUE; +} + +static void Wcon_PrintInternal( const char *msg, int length ) +{ + char *pTemp; + DWORD cbWritten; + const char *pMsgString; + static char tmpBuf[2048]; + static char szOutput[2048]; + + Q_strncpy( szOutput, msg, length ? (length + 1) : ( sizeof( szOutput ) - 1 )); + if( length ) + szOutput[length + 1] = '\0'; + else + szOutput[sizeof( szOutput ) - 1] = '\0'; + + pTemp = tmpBuf; + pMsgString = szOutput; + while( pMsgString && *pMsgString ) + { + if( IsColorString( pMsgString )) + { + if (( pTemp - tmpBuf ) > 0 ) + { + // dump accumulated text before change color + *pTemp = 0; // terminate string + WriteFile( s_wcd.hOutput, tmpBuf, Q_strlen(tmpBuf), &cbWritten, 0 ); + pTemp = tmpBuf; + } + + // set new color + SetConsoleTextAttribute( s_wcd.hOutput, g_color_table[ColorIndex(*(pMsgString + 1))] ); + pMsgString += 2; // skip color info + } + else if(( pTemp - tmpBuf ) < sizeof( tmpBuf ) - 1 ) + { + *pTemp++ = *pMsgString++; + } + else + { + // temp buffer is full, dump it now + *pTemp = 0; // terminate string + WriteFile( s_wcd.hOutput, tmpBuf, Q_strlen(tmpBuf), &cbWritten, 0 ); + pTemp = tmpBuf; + } + } + + // check for last portion + if (( pTemp - tmpBuf ) > 0 ) + { + // dump accumulated text + *pTemp = 0; // terminate string + WriteFile( s_wcd.hOutput, tmpBuf, Q_strlen(tmpBuf), &cbWritten, 0 ); + pTemp = tmpBuf; + } + + // restore white color + SetConsoleTextAttribute( s_wcd.hOutput, g_color_table[7] ); +} void Wcon_ShowConsole( qboolean show ) { - if( !s_wcd.hWnd || show == s_wcd.status ) + if( !s_wcd.hWnd || show == s_wcd.consoleVisible ) return; - s_wcd.status = show; + s_wcd.consoleVisible = show; if( show ) - { - ShowWindow( s_wcd.hWnd, SW_SHOWNORMAL ); - SendMessage( s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xffff ); - } - else ShowWindow( s_wcd.hWnd, SW_HIDE ); + ShowWindow( s_wcd.hWnd, SW_SHOW ); + else + ShowWindow( s_wcd.hWnd, SW_HIDE ); } void Wcon_DisableInput( void ) { - if( host.type != HOST_DEDICATED ) return; - SendMessage( s_wcd.hwndButtonSubmit, WM_ENABLE, 0, 0 ); - SendMessage( s_wcd.hwndInputLine, WM_ENABLE, 0, 0 ); + if( host.type != HOST_DEDICATED ) + return; + + s_wcd.inputEnabled = false; } -void Wcon_SetInputText( const char *inputText ) +static void Wcon_SetInputText( const char *inputText ) { - if( host.type != HOST_DEDICATED ) return; - SetWindowText( s_wcd.hwndInputLine, inputText ); - SendMessage( s_wcd.hwndInputLine, EM_SETSEL, Q_strlen( inputText ), -1 ); + if( host.type != HOST_DEDICATED ) + return; + + while( s_wcd.consoleTextLen-- ) + { + Wcon_PrintInternal( "\b \b", 0 ); + } + Wcon_PrintInternal( inputText, 0 ); + Q_strncpy( s_wcd.consoleText, inputText, sizeof(s_wcd.consoleText) - 1 ); + s_wcd.consoleTextLen = Q_strlen( inputText ); + s_wcd.cursorPosition = s_wcd.consoleTextLen; + s_wcd.browseLine = s_wcd.inputLine; } static void Wcon_Clear_f( void ) { - if( host.type != HOST_DEDICATED ) return; - SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 ); - SendMessage( s_wcd.hwndBuffer, EM_REPLACESEL, FALSE, (LPARAM)"" ); - UpdateWindow( s_wcd.hwndBuffer ); + CONSOLE_SCREEN_BUFFER_INFO csbi; + SMALL_RECT scrollRect; + COORD scrollTarget; + CHAR_INFO fill; + + if( host.type != HOST_DEDICATED ) + return; + + if( !GetConsoleScreenBufferInfo( s_wcd.hOutput, &csbi )) + { + return; + } + + scrollRect.Left = 0; + scrollRect.Top = 0; + scrollRect.Right = csbi.dwSize.X; + scrollRect.Bottom = csbi.dwSize.Y; + scrollTarget.X = 0; + scrollTarget.Y = (SHORT)(0 - csbi.dwSize.Y); + fill.Char.UnicodeChar = TEXT(' '); + fill.Attributes = csbi.wAttributes; + ScrollConsoleScreenBuffer( s_wcd.hOutput, &scrollRect, NULL, scrollTarget, &fill ); + + csbi.dwCursorPosition.X = 0; + csbi.dwCursorPosition.Y = 0; + SetConsoleCursorPosition( s_wcd.hOutput, csbi.dwCursorPosition ); + + s_wcd.consoleText[0] = '\0'; + s_wcd.consoleTextLen = 0; + s_wcd.cursorPosition = 0; + s_wcd.inputLine = 0; + s_wcd.browseLine = 0; + s_wcd.totalLines = 0; + Wcon_PrintInternal( "\n", 0 ); } -static int Wcon_KeyEvent( int key, qboolean down ) +static void Wcon_EventUpArrow() { - char inputBuffer[1024]; + int nLastCommandInHistory = s_wcd.inputLine + 1; + if( nLastCommandInHistory > s_wcd.totalLines ) + nLastCommandInHistory = 0; - if( !down ) - return 0; + if( s_wcd.browseLine == nLastCommandInHistory ) + return; + + if( s_wcd.browseLine == s_wcd.inputLine ) + { + if( s_wcd.consoleTextLen > 0 ) + { + Q_strncpy( s_wcd.savedConsoleText, s_wcd.consoleText, s_wcd.consoleTextLen ); + } + s_wcd.savedConsoleTextLen = s_wcd.consoleTextLen; + } + + s_wcd.browseLine--; + if( s_wcd.browseLine < 0 ) + { + s_wcd.browseLine = s_wcd.totalLines - 1; + } + + while( s_wcd.consoleTextLen-- ) + { + Wcon_PrintInternal( "\b \b", 0 ); + } + + Wcon_PrintInternal( s_wcd.lineBuffer[s_wcd.browseLine], 0 ); + Q_strncpy( s_wcd.consoleText, s_wcd.lineBuffer[s_wcd.browseLine], sizeof( s_wcd.consoleText )); + s_wcd.consoleTextLen = Q_strlen( s_wcd.lineBuffer[s_wcd.browseLine] ); + s_wcd.cursorPosition = s_wcd.consoleTextLen; +} + +static void Wcon_EventDownArrow() +{ + if( s_wcd.browseLine == s_wcd.inputLine ) + return; + + if( ++s_wcd.browseLine > s_wcd.totalLines ) + s_wcd.browseLine = 0; + + while( s_wcd.consoleTextLen-- ) + { + Wcon_PrintInternal( "\b \b", 0 ); + } + + if( s_wcd.browseLine == s_wcd.inputLine ) + { + if( s_wcd.savedConsoleTextLen > 0 ) + { + Q_strncpy( s_wcd.consoleText, s_wcd.savedConsoleText, s_wcd.savedConsoleTextLen ); + Wcon_PrintInternal( s_wcd.consoleText, s_wcd.savedConsoleTextLen ); + } + s_wcd.consoleTextLen = s_wcd.savedConsoleTextLen; + } + else + { + Wcon_PrintInternal( s_wcd.lineBuffer[s_wcd.browseLine], 0 ); + Q_strncpy( s_wcd.consoleText, s_wcd.lineBuffer[s_wcd.browseLine], sizeof( s_wcd.consoleText )); + s_wcd.consoleTextLen = Q_strlen( s_wcd.lineBuffer[s_wcd.browseLine] ); + } + s_wcd.cursorPosition = s_wcd.consoleTextLen; +} + +static void Wcon_EventLeftArrow() +{ + if( s_wcd.cursorPosition == 0 ) + return; + + Wcon_PrintInternal( "\b", 0 ); + s_wcd.cursorPosition--; +} + +static void Wcon_EventRightArrow() +{ + if( s_wcd.cursorPosition == s_wcd.consoleTextLen ) + return; + + Wcon_PrintInternal( s_wcd.consoleText + s_wcd.cursorPosition, 1 ); + s_wcd.cursorPosition++; +} + +static int Wcon_EventNewline() +{ + int nLen; + + nLen = 0; + Wcon_PrintInternal( "\n", 0 ); + if( s_wcd.consoleTextLen ) + { + nLen = s_wcd.consoleTextLen; + + s_wcd.consoleText[s_wcd.consoleTextLen] = '\0'; + s_wcd.consoleTextLen = 0; + s_wcd.cursorPosition = 0; + + if (( s_wcd.inputLine == 0 ) || ( Q_strcmp( s_wcd.lineBuffer[s_wcd.inputLine - 1], s_wcd.consoleText ))) + { + Q_strncpy( s_wcd.lineBuffer[s_wcd.inputLine], s_wcd.consoleText, sizeof( s_wcd.consoleText )); + s_wcd.inputLine++; + + if( s_wcd.inputLine > s_wcd.totalLines ) + s_wcd.totalLines = s_wcd.inputLine; + + if( s_wcd.inputLine >= COMMAND_HISTORY ) + s_wcd.inputLine = 0; + } + s_wcd.browseLine = s_wcd.inputLine; + } + return nLen; +} + +static void Wcon_EventBackspace() +{ + int nCount; + + if( s_wcd.cursorPosition < 1 ) + { + return; + } + + s_wcd.consoleTextLen--; + s_wcd.cursorPosition--; + + Wcon_PrintInternal( "\b", 0 ); + + for( nCount = s_wcd.cursorPosition; nCount < s_wcd.consoleTextLen; ++nCount ) + { + s_wcd.consoleText[nCount] = s_wcd.consoleText[nCount + 1]; + Wcon_PrintInternal( s_wcd.consoleText + nCount, 1 ); + } + + Wcon_PrintInternal( " ", 0 ); + + nCount = s_wcd.consoleTextLen; + while( nCount >= s_wcd.cursorPosition ) + { + Wcon_PrintInternal( "\b", 0 ); + nCount--; + } + + s_wcd.browseLine = s_wcd.inputLine; +} + +static void Wcon_EventTab() +{ + s_wcd.consoleText[s_wcd.consoleTextLen] = '\0'; + Cmd_AutoComplete( s_wcd.consoleText ); + Wcon_SetInputText( s_wcd.consoleText ); +} + +static void Wcon_EventCharacter(char c) +{ + int nCount; + + if( s_wcd.consoleTextLen >= ( sizeof( s_wcd.consoleText ) - 2 )) + { + return; + } + + nCount = s_wcd.consoleTextLen; + while( nCount > s_wcd.cursorPosition ) + { + s_wcd.consoleText[nCount] = s_wcd.consoleText[nCount - 1]; + nCount--; + } + + s_wcd.consoleText[s_wcd.cursorPosition] = c; + Wcon_PrintInternal( s_wcd.consoleText + s_wcd.cursorPosition, s_wcd.consoleTextLen - s_wcd.cursorPosition + 1 ); + s_wcd.consoleTextLen++; + s_wcd.cursorPosition++; + + nCount = s_wcd.consoleTextLen; + while( nCount > s_wcd.cursorPosition ) + { + Wcon_PrintInternal( "\b", 0 ); + nCount--; + } + + s_wcd.browseLine = s_wcd.inputLine; +} + +static void Wcon_UpdateStatusLine() +{ + COORD coord; + WORD wAttrib; + DWORD dwWritten; + + coord.X = 0; + coord.Y = 0; + wAttrib = g_color_table[5] | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; + + FillConsoleOutputCharacter( s_wcd.hOutput, ' ', 80, coord, &dwWritten ); + FillConsoleOutputAttribute( s_wcd.hOutput, wAttrib, 80, coord, &dwWritten ); + WriteConsoleOutputCharacter( s_wcd.hOutput, s_wcd.statusLine, Q_strlen(s_wcd.statusLine), coord, &dwWritten ); +} + +static char *Wcon_KeyEvent( int key, WCHAR character ) +{ + int nLen; + char inputBuffer[1024]; switch( key ) { - case VK_TAB: - GetWindowText( s_wcd.hwndInputLine, inputBuffer, sizeof( inputBuffer )); - Cmd_AutoComplete( inputBuffer ); - Wcon_SetInputText( inputBuffer ); - return 1; - case VK_DOWN: - if( s_wcd.historyLine == s_wcd.nextHistoryLine ) - return 0; - s_wcd.historyLine++; - Wcon_SetInputText( s_wcd.historyLines[s_wcd.historyLine % COMMAND_HISTORY] ); - return 1; case VK_UP: - if( s_wcd.nextHistoryLine - s_wcd.historyLine < COMMAND_HISTORY && s_wcd.historyLine > 0 ) - s_wcd.historyLine--; - Wcon_SetInputText( s_wcd.historyLines[s_wcd.historyLine % COMMAND_HISTORY] ); - return 1; + Wcon_EventUpArrow(); + return NULL; + case VK_DOWN: + Wcon_EventDownArrow(); + return NULL; + case VK_LEFT: + Wcon_EventLeftArrow(); + return NULL; + case VK_RIGHT: + Wcon_EventRightArrow(); + return NULL; } - return 0; -} -static long _stdcall Wcon_WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch( uMsg ) + switch( character ) { - case WM_ACTIVATE: - if( LOWORD( wParam ) != WA_INACTIVE ) - SetFocus( s_wcd.hwndInputLine ); - break; - case WM_CLOSE: - if( host.status == HOST_ERR_FATAL ) - { - // send windows message - PostQuitMessage( 0 ); - } - else Sys_Quit(); // otherwise - return 0; - case WM_CTLCOLORSTATIC: - if((HWND)lParam == s_wcd.hwndBuffer ) - { - SetBkColor((HDC)wParam, RGB( 0x90, 0x90, 0x90 )); - SetTextColor((HDC)wParam, RGB( 0xff, 0xff, 0xff )); - return (long)s_wcd.hbrEditBackground; - } - break; - case WM_COMMAND: - if( wParam == SUBMIT_ID ) - { - SendMessage( s_wcd.hwndInputLine, WM_CHAR, 13, 0L ); - SetFocus( s_wcd.hwndInputLine ); - } - break; - case WM_HOTKEY: - switch( LOWORD( wParam )) - { - case QUIT_ON_ESCAPE_ID: - PostQuitMessage( 0 ); + case '\r': // Enter + nLen = Wcon_EventNewline(); + if (nLen) + { + return s_wcd.consoleText; + } + break; + case '\b': // Backspace + Wcon_EventBackspace(); + break; + case '\t': // TAB + Wcon_EventTab(); + break; + default: + // TODO implement converting wide chars to UTF-8 and properly handling it + if (( character >= ' ' ) && ( character <= '~' )) + { + Wcon_EventCharacter(character); + } break; - } - break; - case WM_CREATE: - s_wcd.hbrEditBackground = CreateSolidBrush( RGB( 0x90, 0x90, 0x90 )); - break; } - return DefWindowProc( hWnd, uMsg, wParam, lParam ); + + return NULL; } -long _stdcall Wcon_InputLineProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) -{ - char inputBuffer[1024]; - - switch( uMsg ) - { - case WM_KILLFOCUS: - if(( HWND )wParam == s_wcd.hWnd ) - { - SetFocus( hWnd ); - return 0; - } - break; - case WM_SYSKEYDOWN: - case WM_KEYDOWN: - if( Wcon_KeyEvent( LOWORD( wParam ), true )) - return 0; - break; - case WM_SYSKEYUP: - case WM_KEYUP: - if( Wcon_KeyEvent( LOWORD( wParam ), false )) - return 0; - break; - case WM_CHAR: - if( Wcon_KeyEvent( wParam, true )) - return 0; - if( wParam == 13 && host.status != HOST_ERR_FATAL ) - { - GetWindowText( s_wcd.hwndInputLine, inputBuffer, sizeof( inputBuffer )); - Q_strncat( s_wcd.consoleText, inputBuffer, sizeof( s_wcd.consoleText ) - Q_strlen( s_wcd.consoleText ) - 5 ); - Q_strcat( s_wcd.consoleText, "\n" ); - SetWindowText( s_wcd.hwndInputLine, "" ); - Con_Printf( ">%s\n", inputBuffer ); - - // copy line to history buffer - Q_strncpy( s_wcd.historyLines[s_wcd.nextHistoryLine % COMMAND_HISTORY], inputBuffer, MAX_STRING ); - s_wcd.nextHistoryLine++; - s_wcd.historyLine = s_wcd.nextHistoryLine; - return 0; - } - break; - } - return CallWindowProc( s_wcd.SysInputLineWndProc, hWnd, uMsg, wParam, lParam ); -} - - /* =============================================================================== @@ -224,6 +455,7 @@ WIN32 IO =============================================================================== */ + /* ================ Con_WinPrint @@ -233,24 +465,26 @@ print into window console */ void Wcon_WinPrint( const char *pMsg ) { - size_t len = Q_strlen( pMsg ); - - // replace selection instead of appending if we're overflowing - s_wcd.outLen += len; - if( s_wcd.outLen >= 0x7fff ) + int nLen; + if( s_wcd.consoleTextLen ) { - SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 ); - s_wcd.outLen = len; + nLen = s_wcd.consoleTextLen; + while (nLen--) + { + Wcon_PrintInternal( "\b \b", 0 ); + } } - SendMessage( s_wcd.hwndBuffer, EM_REPLACESEL, 0, (LPARAM)pMsg ); + Wcon_PrintInternal( pMsg, 0 ); - // put this text into the windows console - SendMessage( s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xffff ); - SendMessage( s_wcd.hwndBuffer, EM_SCROLLCARET, 0, 0 ); + if( s_wcd.consoleTextLen ) + { + Wcon_PrintInternal( s_wcd.consoleText, s_wcd.consoleTextLen ); + } + + Wcon_UpdateStatusLine(); } - /* ================ Con_CreateConsole @@ -260,107 +494,38 @@ create win32 console */ void Wcon_CreateConsole( void ) { - HDC hDC; - WNDCLASS wc; - RECT rect; - int nHeight; - int swidth, sheight, fontsize; - int DEDSTYLE = WS_POPUPWINDOW | WS_CAPTION; - int CONSTYLE = WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_BORDER|WS_EX_CLIENTEDGE|ES_LEFT|ES_MULTILINE|ES_AUTOVSCROLL|ES_READONLY; - string FontName; - - wc.style = 0; - wc.lpfnWndProc = (WNDPROC)Wcon_WndProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - wc.hInstance = host.hInst; - wc.hIcon = LoadIcon( host.hInst, MAKEINTRESOURCE( IDI_ICON1 )); - wc.hCursor = LoadCursor( NULL, IDC_ARROW ); - wc.hbrBackground = (void *)COLOR_3DSHADOW; - wc.lpszClassName = SYSCONSOLE; - wc.lpszMenuName = 0; - if( Sys_CheckParm( "-log" )) s_wcd.log_active = true; if( host.type == HOST_NORMAL ) { - rect.left = 0; - rect.right = 536; - rect.top = 0; - rect.bottom = 364; - Q_strncpy( FontName, "Fixedsys", sizeof( FontName )); Q_strncpy( s_wcd.title, va( "Xash3D %s", XASH_VERSION ), sizeof( s_wcd.title )); Q_strncpy( s_wcd.log_path, "engine.log", sizeof( s_wcd.log_path )); - fontsize = 8; } else // dedicated console { - rect.left = 0; - rect.right = 640; - rect.top = 0; - rect.bottom = 392; - Q_strncpy( FontName, "System", sizeof( FontName )); Q_strncpy( s_wcd.title, va( "XashDS %s", XASH_VERSION ), sizeof( s_wcd.title )); Q_strncpy( s_wcd.log_path, "dedicated.log", sizeof( s_wcd.log_path )); s_wcd.log_active = true; // always make log - fontsize = 14; } - if( !RegisterClass( &wc )) + AllocConsole(); + SetConsoleTitle( s_wcd.title ); + SetConsoleCP( CP_UTF8 ); + SetConsoleOutputCP( CP_UTF8 ); + + s_wcd.hWnd = GetConsoleWindow(); + s_wcd.hInput = GetStdHandle( STD_INPUT_HANDLE ); + s_wcd.hOutput = GetStdHandle( STD_OUTPUT_HANDLE ); + s_wcd.inputEnabled = true; + + if( !SetConsoleCtrlHandler( &Wcon_HandleConsole, TRUE )) { - // print into log - Con_Reportf( S_ERROR "Can't register window class '%s'\n", SYSCONSOLE ); + Con_Reportf( S_ERROR "Couldn't attach console handler function\n" ); return; } - AdjustWindowRect( &rect, DEDSTYLE, FALSE ); - - hDC = GetDC( GetDesktopWindow() ); - swidth = GetDeviceCaps( hDC, HORZRES ); - sheight = GetDeviceCaps( hDC, VERTRES ); - ReleaseDC( GetDesktopWindow(), hDC ); - - s_wcd.windowWidth = rect.right - rect.left; - s_wcd.windowHeight = rect.bottom - rect.top; - - s_wcd.hWnd = CreateWindowEx( WS_EX_DLGMODALFRAME, SYSCONSOLE, s_wcd.title, DEDSTYLE, ( swidth - 600 ) / 2, ( sheight - 450 ) / 2 , rect.right - rect.left + 1, rect.bottom - rect.top + 1, NULL, NULL, host.hInst, NULL ); - if( s_wcd.hWnd == NULL ) - { - Con_Reportf( S_ERROR "Can't create window '%s'\n", s_wcd.title ); - return; - } - - // create fonts - hDC = GetDC( s_wcd.hWnd ); - nHeight = -MulDiv( fontsize, GetDeviceCaps( hDC, LOGPIXELSY ), 72 ); - s_wcd.hfBufferFont = CreateFont( nHeight, 0, 0, 0, FW_LIGHT, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_MODERN|FIXED_PITCH, FontName ); - ReleaseDC( s_wcd.hWnd, hDC ); - - if( host.type == HOST_DEDICATED ) - { - // create the input line - s_wcd.hwndInputLine = CreateWindowEx( WS_EX_CLIENTEDGE, "edit", NULL, WS_CHILD|WS_VISIBLE|WS_BORDER|ES_LEFT|ES_AUTOHSCROLL, 0, 366, 550, 25, s_wcd.hWnd, (HMENU)INPUT_ID, host.hInst, NULL ); - - s_wcd.hwndButtonSubmit = CreateWindow( "button", NULL, BS_PUSHBUTTON|WS_VISIBLE|WS_CHILD|BS_DEFPUSHBUTTON, 552, 367, 87, 25, s_wcd.hWnd, (HMENU)SUBMIT_ID, host.hInst, NULL ); - SendMessage( s_wcd.hwndButtonSubmit, WM_SETTEXT, 0, ( LPARAM ) "submit" ); - } - - // create the scrollbuffer - GetClientRect( s_wcd.hWnd, &rect ); - - s_wcd.hwndBuffer = CreateWindowEx( WS_EX_DLGMODALFRAME|WS_EX_CLIENTEDGE, "edit", NULL, CONSTYLE, 0, 0, rect.right - rect.left, Q_min(365, rect.bottom), s_wcd.hWnd, (HMENU)EDIT_ID, host.hInst, NULL ); - SendMessage( s_wcd.hwndBuffer, WM_SETFONT, (WPARAM)s_wcd.hfBufferFont, 0 ); - - if( host.type == HOST_DEDICATED ) - { -#ifdef XASH_64BIT - s_wcd.SysInputLineWndProc = (WNDPROC)SetWindowLongPtr( s_wcd.hwndInputLine, GWLP_WNDPROC, (LONG_PTR)Wcon_InputLineProc ); -#else - s_wcd.SysInputLineWndProc = (WNDPROC)SetWindowLong( s_wcd.hwndInputLine, GWL_WNDPROC, (long)Wcon_InputLineProc ); -#endif - SendMessage( s_wcd.hwndInputLine, WM_SETFONT, ( WPARAM )s_wcd.hfBufferFont, 0 ); - } + SetWindowPos( s_wcd.hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOREPOSITION | SWP_SHOWWINDOW ); // show console if needed if( host.con_showalways ) @@ -369,13 +534,11 @@ void Wcon_CreateConsole( void ) ShowWindow( s_wcd.hWnd, SW_SHOWDEFAULT ); UpdateWindow( s_wcd.hWnd ); SetForegroundWindow( s_wcd.hWnd ); - - if( host.type != HOST_DEDICATED ) - SetFocus( s_wcd.hWnd ); - else SetFocus( s_wcd.hwndInputLine ); - s_wcd.status = true; + SetFocus( s_wcd.hWnd ); + s_wcd.consoleVisible = true; } - else s_wcd.status = false; + else + s_wcd.consoleVisible = false; } /* @@ -387,7 +550,9 @@ register console commands (dedicated only) */ void Wcon_InitConsoleCommands( void ) { - if( host.type != HOST_DEDICATED ) return; + if( host.type != HOST_DEDICATED ) + return; + Cmd_AddCommand( "clear", Wcon_Clear_f, "clear console history" ); } @@ -407,33 +572,15 @@ void Wcon_DestroyConsole( void ) if( s_wcd.hWnd ) { - DeleteObject( s_wcd.hbrEditBackground ); - DeleteObject( s_wcd.hfBufferFont ); - - if( host.type == HOST_DEDICATED ) - { - ShowWindow( s_wcd.hwndButtonSubmit, SW_HIDE ); - DestroyWindow( s_wcd.hwndButtonSubmit ); - s_wcd.hwndButtonSubmit = 0; - - ShowWindow( s_wcd.hwndInputLine, SW_HIDE ); - DestroyWindow( s_wcd.hwndInputLine ); - s_wcd.hwndInputLine = 0; - } - - ShowWindow( s_wcd.hwndBuffer, SW_HIDE ); - DestroyWindow( s_wcd.hwndBuffer ); - s_wcd.hwndBuffer = 0; - ShowWindow( s_wcd.hWnd, SW_HIDE ); - DestroyWindow( s_wcd.hWnd ); s_wcd.hWnd = 0; } - UnregisterClass( SYSCONSOLE, host.hInst ); + FreeConsole(); // place it here in case Sys_Crash working properly - if( host.hMutex ) CloseHandle( host.hMutex ); + if( host.hMutex ) + CloseHandle( host.hMutex ); } /* @@ -445,26 +592,57 @@ returned input text */ char *Wcon_Input( void ) { - if( s_wcd.consoleText[0] == 0 ) + int i; + int eventsCount; + static INPUT_RECORD events[1024]; + + if( !s_wcd.inputEnabled ) return NULL; - Q_strncpy( s_wcd.returnedText, s_wcd.consoleText, sizeof( s_wcd.returnedText )); - s_wcd.consoleText[0] = 0; + while( true ) + { + if( !GetNumberOfConsoleInputEvents( s_wcd.hInput, &eventsCount )) + { + return NULL; + } - return s_wcd.returnedText; + if( eventsCount <= 0 ) + break; + + if( !ReadConsoleInputW( s_wcd.hInput, events, ARRAYSIZE( events ), &eventsCount )) + { + return NULL; + } + + if( eventsCount == 0 ) + return NULL; + + for( i = 0; i < eventsCount; i++ ) + { + INPUT_RECORD *pRec = &events[i]; + if( pRec->EventType != KEY_EVENT ) + continue; + + if( pRec->Event.KeyEvent.bKeyDown ) + return Wcon_KeyEvent( pRec->Event.KeyEvent.wVirtualKeyCode, pRec->Event.KeyEvent.uChar.UnicodeChar ); + } + } + return NULL; } /* ================ -Con_SetFocus +Wcon_SetStatus -change focus to console hwnd +set server status string in console ================ */ -void Wcon_RegisterHotkeys( void ) +void Wcon_SetStatus( const char *pStatus ) { - SetFocus( s_wcd.hWnd ); + if( host.type != HOST_DEDICATED ) + return; - // user can hit escape for quit - RegisterHotKey( s_wcd.hWnd, QUIT_ON_ESCAPE_ID, 0, VK_ESCAPE ); + Q_strncpy( s_wcd.statusLine, pStatus, sizeof( s_wcd.statusLine ) - 1 ); + s_wcd.statusLine[sizeof( s_wcd.statusLine ) - 2] = '\0'; + Wcon_UpdateStatusLine(); } From 22815d278461b0afe321c91c5bc87c74978b9c26 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Wed, 6 Apr 2022 16:06:16 +0400 Subject: [PATCH 190/548] engine: server: sv_main: added status line for dedicated server console --- engine/platform/platform.h | 6 ++++ engine/platform/win32/sys_win.c | 24 +++++++++++++- engine/server/server.h | 1 + engine/server/sv_main.c | 55 ++++++++++++++++++++++++--------- 4 files changed, 71 insertions(+), 15 deletions(-) diff --git a/engine/platform/platform.h b/engine/platform/platform.h index c25ffe29..c66a8a49 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -45,6 +45,12 @@ const char *Android_LoadID( void ); void Android_SaveID( const char *id ); #endif +#if XASH_WIN32 +void Platform_UpdateStatusLine( void ); +#else +static inline void Platform_UpdateStatusLine( void ) { } +#endif + /* ============================================================================== diff --git a/engine/platform/win32/sys_win.c b/engine/platform/win32/sys_win.c index 78132f0a..8af51fd2 100644 --- a/engine/platform/win32/sys_win.c +++ b/engine/platform/win32/sys_win.c @@ -15,6 +15,7 @@ GNU General Public License for more details. #include "platform/platform.h" #include "menu_int.h" +#include "server.h" #include #if XASH_TIMER == TIMER_WIN32 @@ -53,6 +54,27 @@ void Platform_ShellExecute( const char *path, const char *parms ) ShellExecute( NULL, "open", path, parms, NULL, SW_SHOW ); } +void Platform_UpdateStatusLine( void ) +{ + int clientsCount; + char szStatus[128]; + static double lastTime; + + if( host.type != HOST_DEDICATED ) + return; + + // update only every 1/2 seconds + if(( sv.time - lastTime ) < 0.5f ) + return; + + clientsCount = SV_GetConnectedClientsCount( NULL ); + Q_snprintf( szStatus, sizeof( szStatus ) - 1, "%.1f fps %2i/%2i on %16s", 1.f / sv.frametime, clientsCount, svs.maxclients, host.game.levelName ); +#ifdef XASH_WIN32 + Wcon_SetStatus( szStatus ); +#endif + lastTime = sv.time; +} + #if XASH_MESSAGEBOX == MSGBOX_WIN32 void Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow ) { @@ -71,4 +93,4 @@ void Platform_Shutdown( void ) { Wcon_DestroyConsole(); } -#endif \ No newline at end of file +#endif diff --git a/engine/server/server.h b/engine/server/server.h index 3c516372..2bfd8b18 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -470,6 +470,7 @@ void SV_SendResource( resource_t *pResource, sizebuf_t *msg ); void SV_SendResourceList( sv_client_t *cl ); void SV_AddToMaster( netadr_t from, sizebuf_t *msg ); qboolean SV_ProcessUserAgent( netadr_t from, const char *useragent ); +int SV_GetConnectedClientsCount( int *bots ); void Host_SetServerState( int state ); qboolean SV_IsSimulating( void ); void SV_FreeClients( void ); diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 071164a3..b6756d47 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -16,6 +16,7 @@ GNU General Public License for more details. #include "common.h" #include "server.h" #include "net_encode.h" +#include "platform/platform.h" #define HEARTBEAT_SECONDS 300.0f // 300 seconds @@ -153,6 +154,41 @@ qboolean SV_HasActivePlayers( void ) return false; } +/* +================ +SV_GetConnectedClientsCount + +returns connected clients count (and optionally bots count) +================ +*/ +int SV_GetConnectedClientsCount(int *bots) +{ + int index; + int clients; + + clients = 0; + if( svs.clients ) + { + if( bots ) + *bots = 0; + + for( index = 0; index < svs.maxclients; index++ ) + { + if( svs.clients[index].state >= cs_connected ) + { + if( FBitSet( svs.clients[index].flags, FCL_FAKECLIENT )) + { + if( bots ) + *bots++; + } + else + clients++; + } + } + } + return clients; +} + /* =================== SV_UpdateMovevars @@ -650,6 +686,9 @@ void Host_ServerFrame( void ) // clear edict flags for next frame SV_PrepWorldFrame (); + // update dedicated server status line in console + Platform_UpdateStatusLine (); + // send a heartbeat to the master if needed Master_Heartbeat (); } @@ -738,22 +777,10 @@ void SV_AddToMaster( netadr_t from, sizebuf_t *msg ) { uint challenge; char s[MAX_INFO_STRING] = "0\n"; // skip 2 bytes of header - int clients = 0, bots = 0, index; + int clients = 0, bots = 0; int len = sizeof( s ); - if( svs.clients ) - { - for( index = 0; index < svs.maxclients; index++ ) - { - if( svs.clients[index].state >= cs_connected ) - { - if( FBitSet( svs.clients[index].flags, FCL_FAKECLIENT )) - bots++; - else clients++; - } - } - } - + clients = SV_GetConnectedClientsCount( &bots ); challenge = MSG_ReadUBitLong( msg, sizeof( uint ) << 3 ); Info_SetValueForKey( s, "protocol", va( "%d", PROTOCOL_VERSION ), len ); // protocol version From a7d02b8268b47e6d046d631441b6018e890cb86a Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Wed, 6 Apr 2022 20:38:04 +0400 Subject: [PATCH 191/548] ref_gl: gl_rlight: fixed game crash in Customize multiplayer menu --- ref_gl/gl_rlight.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ref_gl/gl_rlight.c b/ref_gl/gl_rlight.c index c0c0c5fc..89679989 100644 --- a/ref_gl/gl_rlight.c +++ b/ref_gl/gl_rlight.c @@ -153,7 +153,8 @@ void R_PushDlights( void ) tr.dlightframecount = tr.framecount; RI.currententity = gEngfuncs.GetEntityByIndex( 0 ); - RI.currentmodel = RI.currententity->model; + if( RI.currententity ) + RI.currentmodel = RI.currententity->model; for( i = 0; i < MAX_DLIGHTS; i++, l++ ) { @@ -165,7 +166,8 @@ void R_PushDlights( void ) if( GL_FrustumCullSphere( &RI.frustum, l->origin, l->radius, 15 )) continue; - R_MarkLights( l, 1<nodes ); + if( RI.currententity ) + R_MarkLights( l, 1<nodes ); } } From 9515cccb94d4f30dc03106d4426ab0e192fddad0 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Wed, 6 Apr 2022 20:40:39 +0400 Subject: [PATCH 192/548] engine: client: cl_frame: fixed position history animtime for non-brush entities --- engine/client/cl_frame.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index a9181ffb..70ce6e8e 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -58,10 +58,13 @@ void CL_UpdatePositions( cl_entity_t *ent ) ent->current_position = (ent->current_position + 1) & HISTORY_MASK; ph = &ent->ph[ent->current_position]; - VectorCopy( ent->curstate.origin, ph->origin ); VectorCopy( ent->curstate.angles, ph->angles ); - ph->animtime = ent->curstate.animtime; // !!! + + if( ent->model->type == mod_brush ) + ph->animtime = ent->curstate.animtime; + else + ph->animtime = cl.time; } /* From 92cfa2af48c8f38d1110814aa7f2c8d5d16e7b0c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 7 Apr 2022 22:02:31 +0300 Subject: [PATCH 193/548] ref_soft: light: fixed game crash in Customize multiplayer menu --- ref_soft/r_light.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ref_soft/r_light.c b/ref_soft/r_light.c index 8ca462de..1e84d77d 100644 --- a/ref_soft/r_light.c +++ b/ref_soft/r_light.c @@ -156,7 +156,8 @@ void R_PushDlights( void ) tr.dlightframecount = tr.framecount; RI.currententity = gEngfuncs.GetEntityByIndex( 0 ); - RI.currentmodel = RI.currententity->model; + if( RI.currententity ) + RI.currentmodel = RI.currententity->model; for( i = 0; i < MAX_DLIGHTS; i++, l++ ) { @@ -168,7 +169,8 @@ void R_PushDlights( void ) //if( GL_FrustumCullSphere( &RI.frustum, l->origin, l->radius, 15 )) //continue; - R_MarkLights( l, 1<nodes ); + if( RI.currententity ) + R_MarkLights( l, 1<nodes ); } } From 66dc20dc2d3db63d42fbc885126af8bb8306ac25 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Fri, 8 Apr 2022 15:13:41 +0400 Subject: [PATCH 194/548] engine: platform: con_win: fixed console window visibility without developer mode (fix #818) --- engine/platform/win32/con_win.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/engine/platform/win32/con_win.c b/engine/platform/win32/con_win.c index c4d9597d..4506063a 100644 --- a/engine/platform/win32/con_win.c +++ b/engine/platform/win32/con_win.c @@ -518,7 +518,7 @@ void Wcon_CreateConsole( void ) s_wcd.hInput = GetStdHandle( STD_INPUT_HANDLE ); s_wcd.hOutput = GetStdHandle( STD_OUTPUT_HANDLE ); s_wcd.inputEnabled = true; - + if( !SetConsoleCtrlHandler( &Wcon_HandleConsole, TRUE )) { Con_Reportf( S_ERROR "Couldn't attach console handler function\n" ); @@ -538,7 +538,10 @@ void Wcon_CreateConsole( void ) s_wcd.consoleVisible = true; } else + { s_wcd.consoleVisible = false; + ShowWindow( s_wcd.hWnd, SW_HIDE ); + } } /* From 691a305f8ecea652504013111a2855adfc8a2e08 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 9 Apr 2022 19:37:56 +0300 Subject: [PATCH 195/548] engine: common: reorder cmd_t fields to be compatible with GoldSrc --- engine/common/cmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/common/cmd.c b/engine/common/cmd.c index b1e510b6..3a268e5a 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -510,8 +510,8 @@ typedef struct cmd_s struct cmd_s *next; char *name; xcommand_t function; - char *desc; int flags; + char *desc; } cmd_t; static int cmd_argc; From 09b7f22cee399e127973b379a80f639e66a3dd04 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Sun, 10 Apr 2022 15:40:49 +0400 Subject: [PATCH 196/548] engine: cl_frame: fixed segfault in CL_UpdatePositions --- engine/client/cl_frame.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index 70ce6e8e..b86debed 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -61,7 +61,7 @@ void CL_UpdatePositions( cl_entity_t *ent ) VectorCopy( ent->curstate.origin, ph->origin ); VectorCopy( ent->curstate.angles, ph->angles ); - if( ent->model->type == mod_brush ) + if( ent->model && ent->model->type == mod_brush ) ph->animtime = ent->curstate.animtime; else ph->animtime = cl.time; From 6f2fda427ecb6f38cd4c2e784a4f218da04cbed2 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Sun, 10 Apr 2022 16:22:37 +0400 Subject: [PATCH 197/548] engine: keydefs: fixed mouse buttons code to match GoldSrc --- engine/keydefs.h | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/engine/keydefs.h b/engine/keydefs.h index ad050974..6bce443a 100644 --- a/engine/keydefs.h +++ b/engine/keydefs.h @@ -21,9 +21,9 @@ // #define K_TAB 9 #define K_ENTER 13 -#define K_ESCAPE 27 +#define K_ESCAPE 27 #define K_SPACE 32 -#define K_SCROLLOCK 70 +#define K_SCROLLOCK 70 // normal keys should be passed as lowercased ascii @@ -59,7 +59,7 @@ #define K_KP_UPARROW 161 #define K_KP_PGUP 162 #define K_KP_LEFTARROW 163 -#define K_KP_5 164 +#define K_KP_5 164 #define K_KP_RIGHTARROW 165 #define K_KP_END 166 #define K_KP_DOWNARROW 167 @@ -71,8 +71,9 @@ #define K_KP_MINUS 173 #define K_KP_PLUS 174 #define K_CAPSLOCK 175 -#define K_KP_NUMLOCK 176 +#define K_KP_MUL 176 #define K_WIN 177 +#define K_KP_NUMLOCK 178 // // joystick buttons @@ -143,30 +144,30 @@ #define K_AUX19 225 #define K_DPAD_RIGHT K_AUX19 -#define K_AUX20 226 -#define K_AUX21 227 -#define K_AUX22 228 -#define K_AUX23 229 -#define K_AUX24 230 -#define K_AUX25 231 -#define K_AUX26 232 -#define K_AUX27 233 -#define K_AUX28 234 -#define K_AUX29 235 -#define K_AUX30 236 -#define K_AUX31 237 -#define K_AUX32 238 +#define K_AUX20 226 +#define K_AUX21 227 +#define K_AUX22 228 +#define K_AUX23 229 +#define K_AUX24 230 +#define K_AUX25 231 +#define K_AUX26 232 +#define K_AUX27 233 +#define K_AUX28 234 +#define K_AUX29 235 +#define K_AUX30 236 +#define K_AUX31 237 +#define K_AUX32 238 #define K_MWHEELDOWN 239 #define K_MWHEELUP 240 -#define K_PAUSE 255 +#define K_PAUSE 255 // // mouse buttons generate virtual keys // #define K_MOUSE1 241 -#define K_MOUSE2 243 -#define K_MOUSE3 242 +#define K_MOUSE2 242 +#define K_MOUSE3 243 #define K_MOUSE4 244 #define K_MOUSE5 245 From 81c4acab663353ce48f9baad7bdaf74884c32850 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 10 Apr 2022 19:39:57 +0300 Subject: [PATCH 198/548] platform: sdl: fix right mouse button issuing wrong key code --- engine/platform/sdl/events.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/platform/sdl/events.c b/engine/platform/sdl/events.c index 0411c41e..64334fde 100644 --- a/engine/platform/sdl/events.c +++ b/engine/platform/sdl/events.c @@ -283,10 +283,10 @@ static void SDLash_MouseEvent( SDL_MouseButtonEvent button ) case SDL_BUTTON_LEFT: IN_MouseEvent( 0, down ); break; - case SDL_BUTTON_MIDDLE: + case SDL_BUTTON_RIGHT: IN_MouseEvent( 1, down ); break; - case SDL_BUTTON_RIGHT: + case SDL_BUTTON_MIDDLE: IN_MouseEvent( 2, down ); break; case SDL_BUTTON_X1: From 9c85d114e74ed292a19b53bd3478f494c5ef2d4a Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Tue, 12 Apr 2022 13:06:14 +0400 Subject: [PATCH 199/548] engine: client: fixed TriWorldToScreen behavior to match GoldSrc --- engine/client/cl_game.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index 66767d85..dfd1f1d9 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -3178,16 +3178,7 @@ convert world coordinates (x,y,z) into screen (x, y) */ int TriWorldToScreen( const float *world, float *screen ) { - int retval; - - retval = ref.dllFuncs.WorldToScreen( world, screen ); - - screen[0] = 0.5f * screen[0] * (float)clgame.viewport[2]; - screen[1] = -0.5f * screen[1] * (float)clgame.viewport[3]; - screen[0] += 0.5f * (float)clgame.viewport[2]; - screen[1] += 0.5f * (float)clgame.viewport[3]; - - return retval; + return ref.dllFuncs.WorldToScreen( world, screen ); } /* From 74086cc4c16b2eb29bc6e5b77629744c49e0071a Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Tue, 12 Apr 2022 13:06:58 +0400 Subject: [PATCH 200/548] ref_gl: fixed R_WorldToScreen behavior to match GoldSrc --- ref_gl/gl_rmain.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/ref_gl/gl_rmain.c b/ref_gl/gl_rmain.c index 21eabbdc..e8f93616 100644 --- a/ref_gl/gl_rmain.c +++ b/ref_gl/gl_rmain.c @@ -145,8 +145,6 @@ int R_WorldToScreen( const vec3_t point, vec3_t screen ) if( w < 0.001f ) { - screen[0] *= 100000; - screen[1] *= 100000; behind = true; } else From 5402e1a2597c40c603bd0f2b1a9cd6a16506ec84 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Tue, 12 Apr 2022 13:07:29 +0400 Subject: [PATCH 201/548] ref_soft: fixed R_WorldToScreen behavior to match GoldSrc --- ref_soft/r_main.c | 2 -- ref_soft/r_triapi.c | 12 +----------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/ref_soft/r_main.c b/ref_soft/r_main.c index 77b4e792..c4833e7c 100644 --- a/ref_soft/r_main.c +++ b/ref_soft/r_main.c @@ -253,8 +253,6 @@ int R_WorldToScreen( const vec3_t point, vec3_t screen ) if( w < 0.001f ) { - screen[0] *= 100000; - screen[1] *= 100000; behind = true; } else diff --git a/ref_soft/r_triapi.c b/ref_soft/r_triapi.c index c98f717c..c1867083 100644 --- a/ref_soft/r_triapi.c +++ b/ref_soft/r_triapi.c @@ -356,17 +356,7 @@ convert world coordinates (x,y,z) into screen (x, y) */ int GAME_EXPORT TriWorldToScreen( const float *world, float *screen ) { - int retval; - - retval = R_WorldToScreen( world, screen ); - - screen[0] = 0.5f * screen[0] * (float)RI.viewport[2]; - screen[1] = -0.5f * screen[1] * (float)RI.viewport[3]; - screen[0] += 0.5f * (float)RI.viewport[2]; - screen[1] += 0.5f * (float)RI.viewport[3]; - - - return retval; + return R_WorldToScreen( world, screen ); } /* From a3ff75b48f24a177152cc79b87bda6ee851a61df Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Thu, 28 Apr 2022 00:16:25 -0700 Subject: [PATCH 202/548] vk: draft staging api, use it for textures --- ref_vk/vk_buffer.c | 18 ------ ref_vk/vk_buffer.h | 9 --- ref_vk/vk_core.c | 6 +- ref_vk/vk_staging.c | 143 +++++++++++++++++++++++++++++++++++++++++++ ref_vk/vk_staging.h | 27 ++++++++ ref_vk/vk_textures.c | 18 +++--- 6 files changed, 182 insertions(+), 39 deletions(-) create mode 100644 ref_vk/vk_staging.c create mode 100644 ref_vk/vk_staging.h diff --git a/ref_vk/vk_buffer.c b/ref_vk/vk_buffer.c index a3326d20..b85ba75f 100644 --- a/ref_vk/vk_buffer.c +++ b/ref_vk/vk_buffer.c @@ -1,22 +1,5 @@ #include "vk_buffer.h" -#include - -vk_global_buffer_t g_vk_buffers = {0}; - -#define DEFAULT_STAGING_SIZE (16*1024*1024) - -qboolean VK_BuffersInit( void ) { - if (!VK_BufferCreate("staging", &g_vk_buffers.staging, DEFAULT_STAGING_SIZE, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) - return false; - - return true; -} - -void VK_BuffersDestroy( void ) { - VK_BufferDestroy(&g_vk_buffers.staging); -} - qboolean VK_BufferCreate(const char *debug_name, vk_buffer_t *buf, uint32_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags flags) { VkBufferCreateInfo bci = { @@ -50,7 +33,6 @@ void VK_BufferDestroy(vk_buffer_t *buf) { buf->buffer = VK_NULL_HANDLE; } - // FIXME when there are many allocation per VkDeviceMemory, fix this if (buf->devmem.device_memory) { VK_DevMemFree(&buf->devmem); buf->devmem.device_memory = VK_NULL_HANDLE; diff --git a/ref_vk/vk_buffer.h b/ref_vk/vk_buffer.h index 2b1538cb..15de1198 100644 --- a/ref_vk/vk_buffer.h +++ b/ref_vk/vk_buffer.h @@ -11,15 +11,6 @@ typedef struct vk_buffer_s { uint32_t size; } vk_buffer_t; -typedef struct { - vk_buffer_t staging; -} vk_global_buffer_t; - -extern vk_global_buffer_t g_vk_buffers; - -qboolean VK_BuffersInit( void ); -void VK_BuffersDestroy( void ); - qboolean VK_BufferCreate(const char *debug_name, vk_buffer_t *buf, uint32_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags flags); void VK_BufferDestroy(vk_buffer_t *buf); diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index 5787f925..67070665 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -4,7 +4,7 @@ #include "vk_textures.h" #include "vk_2d.h" #include "vk_renderstate.h" -#include "vk_buffer.h" +#include "vk_staging.h" #include "vk_framectl.h" #include "vk_brush.h" #include "vk_scene.h" @@ -703,7 +703,7 @@ qboolean R_VkInit( void ) if (!VK_DevMemInit()) return false; - if (!VK_BuffersInit()) + if (!R_VkStagingInit()) return false; // TODO move this to vk_texture module @@ -789,7 +789,7 @@ void R_VkShutdown( void ) { VK_DescriptorShutdown(); vkDestroySampler(vk_core.device, vk_core.default_sampler, NULL); - VK_BuffersDestroy(); + R_VkStagingShutdown(); VK_DevMemDestroy(); diff --git a/ref_vk/vk_staging.c b/ref_vk/vk_staging.c new file mode 100644 index 00000000..6f837b89 --- /dev/null +++ b/ref_vk/vk_staging.c @@ -0,0 +1,143 @@ +#include "vk_staging.h" +#include "vk_buffer.h" + +#include + +#define DEFAULT_STAGING_SIZE (16*1024*1024) +#define MAX_STAGING_ALLOCS (1024) + +typedef struct { + int offset, size; + enum { DestNone, DestBuffer, DestImage } dest_type; + union { + struct { + VkBuffer buffer; + VkDeviceSize offset; + } buffer; + struct { + VkImage image; + VkImageLayout layout; + VkBufferImageCopy region; + } image; + }; +} staging_alloc_t; + +static struct { + vk_buffer_t buffer; + staging_alloc_t allocs[MAX_STAGING_ALLOCS]; + int num_allocs; +} g_staging = {0}; + +qboolean R_VkStagingInit(void) { + if (!VK_BufferCreate("staging", &g_staging.buffer, DEFAULT_STAGING_SIZE, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) + return false; + + return true; +} + +void R_VkStagingShutdown(void) { + VK_BufferDestroy(&g_staging.buffer); +} + +vk_staging_region_t R_VkStagingLock(size_t size) { + const int offset = g_staging.num_allocs > 0 ? g_staging.allocs[g_staging.num_allocs - 1].offset + g_staging.allocs[g_staging.num_allocs - 1].size : 0; + + if ( g_staging.num_allocs >= MAX_STAGING_ALLOCS ) + return (vk_staging_region_t){0}; + + if ( offset + size > g_staging.buffer.size ) + return (vk_staging_region_t){0}; + + memset(g_staging.allocs + g_staging.num_allocs, 0, sizeof(staging_alloc_t)); + g_staging.allocs[g_staging.num_allocs].offset = offset; + g_staging.allocs[g_staging.num_allocs].size = size; + g_staging.num_allocs++; + return (vk_staging_region_t){(char*)g_staging.buffer.mapped + offset, size, g_staging.num_allocs - 1}; +} + +/* +void R_VkStagingUnlockToBuffer(const vk_staging_region_t* region, VkBuffer dest, size_t dest_offset) { + ASSERT(region->internal_id_ >= 0 && region->internal_id_ < g_staging.num_allocs); + ASSERT(g_staging.allocs[region->internal_id_].dest == VK_NULL_HANDLE); + + g_staging.allocs[region->internal_id_].dest = dest; + g_staging.allocs[region->internal_id_].dest_offset = dest_offset; +} +*/ + +void R_VkStagingUnlockToImage(const vk_staging_region_t* region, VkBufferImageCopy* dest_region, VkImageLayout layout, VkImage dest) { + staging_alloc_t *alloc; + ASSERT(region->internal_id_ >= 0 && region->internal_id_ < g_staging.num_allocs); + ASSERT(g_staging.allocs[region->internal_id_].dest_type == DestNone); + + alloc = g_staging.allocs + region->internal_id_; + alloc->dest_type = DestImage; + alloc->image.layout = layout; + alloc->image.image = dest; + alloc->image.region = *dest_region; + alloc->image.region.bufferOffset += alloc->offset; +} + +static void copyImage(VkCommandBuffer cmdbuf, const staging_alloc_t *alloc) { + vkCmdCopyBufferToImage(cmdbuf, g_staging.buffer.buffer, alloc->image.image, alloc->image.layout, 1, &alloc->image.region); +} + +void R_VkStagingCommit(VkCommandBuffer cmdbuf) { + for ( int i = 0; i < g_staging.num_allocs; i++ ) { + staging_alloc_t *const alloc = g_staging.allocs + i; + ASSERT(alloc->dest_type != DestNone); + switch (alloc->dest_type) { + case DestImage: + copyImage(cmdbuf, alloc); + break; + case DestBuffer: + ASSERT(!"staging dest buffer is not implemented"); + break; + } + + alloc->dest_type = DestNone; + +#if 0 + // TODO coalesce staging regions for the same dest buffer + + const VkBufferCopy copy = { + .srcOffset = g_staging.allocs[i].offset, + .dstOffset = g_staging.allocs[i].dest_offset, + .size = g_staging.allocs[i].size + }; + vkCmdCopyBuffer(cmdbuf, g_staging.buffer.buffer, g_staging.allocs[i].dest, 1, ©); + + // TODO decide whether this needs to do anything with barriers + // here we can only collect dirty regions for each dest buffer +#endif + } + + g_staging.num_allocs = 0; +} + +void R_VkStagingFlushSync(void) { + if ( !g_staging.num_allocs ) + return; + + { + // FIXME get the right one + const VkCommandBuffer cmdbuf = vk_core.upload_pool.buffers[0]; + + const VkCommandBufferBeginInfo beginfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + }; + + const VkSubmitInfo subinfo = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &cmdbuf, + }; + + XVK_CHECK(vkBeginCommandBuffer(cmdbuf, &beginfo)); + R_VkStagingCommit(cmdbuf); + XVK_CHECK(vkEndCommandBuffer(cmdbuf)); + XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, VK_NULL_HANDLE)); + XVK_CHECK(vkQueueWaitIdle(vk_core.queue)); + } +} diff --git a/ref_vk/vk_staging.h b/ref_vk/vk_staging.h new file mode 100644 index 00000000..ccd27b40 --- /dev/null +++ b/ref_vk/vk_staging.h @@ -0,0 +1,27 @@ +#pragma once + +#include "vk_core.h" + +qboolean R_VkStagingInit(void); +void R_VkStagingShutdown(void); + +//void *R_VkStagingAlloc(size_t size, VkBuffer dest, size_t dest_offset); + +typedef struct { + void *ptr; + size_t size; + int internal_id_; +} vk_staging_region_t; +vk_staging_region_t R_VkStagingLock(size_t size); +void R_VkStagingUnlockToBuffer(const vk_staging_region_t* region, VkBuffer dest, size_t dest_offset); +void R_VkStagingUnlockToImage(const vk_staging_region_t* region, VkBufferImageCopy* dest_region, VkImageLayout layout, VkImage dest); + +void R_VkStagingCommit(VkCommandBuffer cmdbuf); + +// Force commit synchronously +void R_VkStagingFlushSync(void); + +// TODO +// - [x] call init/shutdown from vk_core.ckkjkj +// - [x] use this in vk_texture.c +// - [ ] use this in vk_render.c diff --git a/ref_vk/vk_textures.c b/ref_vk/vk_textures.c index 76bc073c..042260af 100644 --- a/ref_vk/vk_textures.c +++ b/ref_vk/vk_textures.c @@ -2,7 +2,7 @@ #include "vk_common.h" #include "vk_core.h" -#include "vk_buffer.h" +#include "vk_staging.h" #include "vk_const.h" #include "vk_descriptor.h" #include "vk_mapents.h" // wadlist @@ -546,8 +546,6 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, } { - size_t staging_offset = 0; // TODO multiple staging buffer users params.staging->ptr - // 5. Create/get cmdbuf for transitions VkCommandBufferBeginInfo beginfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, @@ -587,7 +585,7 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, const size_t mip_size = CalcImageSize( pic->type, width, height, 1 ); VkBufferImageCopy region = {0}; - region.bufferOffset = staging_offset; + region.bufferOffset = 0; region.bufferRowLength = 0; region.bufferImageHeight = 0; region.imageSubresource = (VkImageSubresourceLayers){ @@ -602,20 +600,22 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, .depth = 1, }; - memcpy(((uint8_t*)g_vk_buffers.staging.mapped) + staging_offset, buf, mip_size); + vk_staging_region_t staging = R_VkStagingLock(mip_size); + ASSERT(staging.ptr); + memcpy(staging.ptr, buf, mip_size); + // Build mip in place for the next mip level if ( mip < mipCount - 1 ) { BuildMipMap( buf, width, height, 1, tex->flags ); } - // TODO we could do this only once w/ region array - vkCmdCopyBufferToImage(cmdbuf, g_vk_buffers.staging.buffer, tex->vk.image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); - - staging_offset += mip_size; + R_VkStagingUnlockToImage(&staging, ®ion, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, tex->vk.image.image); } } + R_VkStagingCommit(cmdbuf); + // 5.2 image:layout:DST -> image:layout:SAMPLED // 5.2.1 transitionToLayout(DST -> SHADER_READ_ONLY) image_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; From c5012328d67397645a0bc906ba0f67a725dd30e9 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 30 Apr 2022 00:28:52 -0700 Subject: [PATCH 203/548] vk: pick device memory slot based on type index --- ref_vk/vk_devmem.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/ref_vk/vk_devmem.c b/ref_vk/vk_devmem.c index f2e9c066..f7c1d422 100644 --- a/ref_vk/vk_devmem.c +++ b/ref_vk/vk_devmem.c @@ -5,7 +5,7 @@ #define DEFAULT_ALLOCATION_SIZE (64 * 1024 * 1024) typedef struct { - uint32_t type_bit; + uint32_t type_index; VkMemoryPropertyFlags property_flags; // device vs host VkMemoryAllocateFlags allocate_flags; VkDeviceMemory device_memory; @@ -47,7 +47,7 @@ static VkDeviceSize optimalSize(VkDeviceSize size) { return size; } -static int allocateDeviceMemory(VkMemoryRequirements req, VkMemoryPropertyFlags prop_flags, VkMemoryAllocateFlags allocate_flags) { +static int allocateDeviceMemory(VkMemoryRequirements req, int type_index, VkMemoryAllocateFlags allocate_flags) { if (g_vk_devmem.num_allocs == MAX_DEVMEM_ALLOCS) return -1; @@ -61,17 +61,12 @@ static int allocateDeviceMemory(VkMemoryRequirements req, VkMemoryPropertyFlags .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .pNext = allocate_flags ? &mafi : NULL, .allocationSize = optimalSize(req.size), - .memoryTypeIndex = findMemoryWithType(req.memoryTypeBits, prop_flags), + .memoryTypeIndex = type_index, }; if (g_vk_devmem.verbose) { gEngine.Con_Reportf("allocateDeviceMemory size=%zu memoryTypeBits=0x%x prop_flags=%c%c%c%c%c allocate_flags=0x%x => typeIndex=%d\n", mai.allocationSize, req.memoryTypeBits, - prop_flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT ? 'D' : '.', - prop_flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT ? 'V' : '.', - prop_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ? 'C' : '.', - prop_flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT ? '$' : '.', - prop_flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT ? 'L' : '.', allocate_flags, mai.memoryTypeIndex); } @@ -81,7 +76,7 @@ static int allocateDeviceMemory(VkMemoryRequirements req, VkMemoryPropertyFlags XVK_CHECK(vkAllocateMemory(vk_core.device, &mai, NULL, &device_memory->device_memory)); device_memory->property_flags = vk_core.physical_device.memory_properties2.memoryProperties.memoryTypes[mai.memoryTypeIndex].propertyFlags; device_memory->allocate_flags = allocate_flags; - device_memory->type_bit = (1 << mai.memoryTypeIndex); + device_memory->type_index = mai.memoryTypeIndex; device_memory->refcount = 0; device_memory->size = mai.allocationSize; @@ -101,16 +96,17 @@ vk_devmem_t VK_DevMemAllocate(const char *name, VkMemoryRequirements req, VkMemo vk_devmem_t ret = {0}; int device_memory_index = -1; alo_block_t block; + const int type_index = findMemoryWithType(req.memoryTypeBits, prop_flags); if (g_vk_devmem.verbose) { - gEngine.Con_Reportf("VK_DevMemAllocate name=\"%s\" size=%zu alignment=%zu memoryTypeBits=0x%x prop_flags=%c%c%c%c%c allocate_flags=0x%x\n", + gEngine.Con_Reportf("VK_DevMemAllocate name=\"%s\" size=%zu alignment=%zu memoryTypeBits=0x%x prop_flags=%c%c%c%c%c allocate_flags=0x%x => type_index=%d\n", name, req.size, req.alignment, req.memoryTypeBits, prop_flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT ? 'D' : '.', prop_flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT ? 'V' : '.', prop_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ? 'C' : '.', prop_flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT ? '$' : '.', prop_flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT ? 'L' : '.', - allocate_flags); + allocate_flags, type_index); } if (vk_core.rtx) { @@ -121,7 +117,7 @@ vk_devmem_t VK_DevMemAllocate(const char *name, VkMemoryRequirements req, VkMemo for (int i = 0; i < g_vk_devmem.num_allocs; ++i) { vk_device_memory_t *const device_memory = g_vk_devmem.allocs + i; - if ((device_memory->type_bit & req.memoryTypeBits) == 0) + if (device_memory->type_index != type_index) continue; if ((device_memory->allocate_flags & allocate_flags) != allocate_flags) @@ -139,7 +135,7 @@ vk_devmem_t VK_DevMemAllocate(const char *name, VkMemoryRequirements req, VkMemo } if (device_memory_index < 0) { - device_memory_index = allocateDeviceMemory(req, prop_flags, allocate_flags); + device_memory_index = allocateDeviceMemory(req, type_index, allocate_flags); ASSERT(device_memory_index >= 0); if (device_memory_index < 0) return ret; From 15eb6808c4a4155652cad1d629516dfe10c16475 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Fri, 22 Apr 2022 21:28:19 +0400 Subject: [PATCH 204/548] engine: platform: updated clipboard read/write functions --- engine/common/system.c | 2 +- engine/platform/platform.h | 4 ++-- engine/platform/sdl/in_sdl.c | 17 +++++++++++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/engine/common/system.c b/engine/common/system.c index 97b56b37..06ff7a03 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -91,7 +91,7 @@ write screenshot into clipboard */ void Sys_SetClipboardData( const char *buffer, size_t size ) { - Platform_SetClipboardText( buffer, size ); + Platform_SetClipboardText( buffer ); } #endif // XASH_DEDICATED diff --git a/engine/platform/platform.h b/engine/platform/platform.h index c66a8a49..f4894f36 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -80,8 +80,8 @@ void Platform_SetMousePos( int x, int y ); void Platform_PreCreateMove( void ); void Platform_MouseMove( float *x, float *y ); // Clipboard -void Platform_GetClipboardText( char *buffer, size_t size ); -void Platform_SetClipboardText( const char *buffer, size_t size ); +int Platform_GetClipboardText( char *buffer, size_t size ); +void Platform_SetClipboardText( const char *buffer ); #if XASH_SDL == 12 #define SDL_SetWindowGrab( wnd, state ) SDL_WM_GrabInput( (state) ) diff --git a/engine/platform/sdl/in_sdl.c b/engine/platform/sdl/in_sdl.c index b2635ea6..963e7d6c 100644 --- a/engine/platform/sdl/in_sdl.c +++ b/engine/platform/sdl/in_sdl.c @@ -71,19 +71,28 @@ Platform_GetClipobardText ============= */ -void Platform_GetClipboardText( char *buffer, size_t size ) +int Platform_GetClipboardText( char *buffer, size_t size ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) + int textLength; char *sdlbuffer = SDL_GetClipboardText(); if( !sdlbuffer ) - return; + return 0; - Q_strncpy( buffer, sdlbuffer, size ); + if (buffer && size > 0) + { + textLength = Q_strncpy( buffer, sdlbuffer, size ); + } + else { + textLength = Q_strlen( sdlbuffer ); + } SDL_free( sdlbuffer ); + return textLength; #else // SDL_VERSION_ATLEAST( 2, 0, 0 ) buffer[0] = 0; #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) + return 0; } /* @@ -92,7 +101,7 @@ Platform_SetClipobardText ============= */ -void Platform_SetClipboardText( const char *buffer, size_t size ) +void Platform_SetClipboardText( const char *buffer ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_SetClipboardText( buffer ); From 26e09c240a9888b08220be18a61d9a2144f6b99c Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Fri, 22 Apr 2022 21:31:10 +0400 Subject: [PATCH 205/548] engine: platform: added Platform_SetCursorType --- engine/cursor_type.h | 39 ++++++++++++++++++ engine/platform/platform.h | 2 + engine/platform/sdl/in_sdl.c | 77 ++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 engine/cursor_type.h diff --git a/engine/cursor_type.h b/engine/cursor_type.h new file mode 100644 index 00000000..6264eda7 --- /dev/null +++ b/engine/cursor_type.h @@ -0,0 +1,39 @@ +/* +cursor_type.h - enumeration of possible mouse cursor types +Copyright (C) 2022 FWGS Team + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#pragma once +#ifndef CURSOR_TYPE_H +#define CURSOR_TYPE_H + +typedef enum +{ + CursorType_User, + CursorType_None, + CursorType_Arrow, + CursorType_Ibeam, + CursorType_Wait, + CursorType_Crosshair, + CursorType_Up, + CursorType_SizeNwSe, + CursorType_SizeNeSw, + CursorType_SizeWe, + CursorType_SizeNs, + CursorType_SizeAll, + CursorType_No, + CursorType_Hand, + CursorType_Last +} cursor_type_t; + +#endif diff --git a/engine/platform/platform.h b/engine/platform/platform.h index f4894f36..02645265 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -20,6 +20,7 @@ GNU General Public License for more details. #include "common.h" #include "system.h" #include "defaults.h" +#include "cursor_type.h" /* ============================================================================== @@ -79,6 +80,7 @@ void Platform_GetMousePos( int *x, int *y ); void Platform_SetMousePos( int x, int y ); void Platform_PreCreateMove( void ); void Platform_MouseMove( float *x, float *y ); +void Platform_SetCursorType( cursor_type_t type ); // Clipboard int Platform_GetClipboardText( char *buffer, size_t size ); void Platform_SetClipboardText( const char *buffer ); diff --git a/engine/platform/sdl/in_sdl.c b/engine/platform/sdl/in_sdl.c index 963e7d6c..dec29ed1 100644 --- a/engine/platform/sdl/in_sdl.c +++ b/engine/platform/sdl/in_sdl.c @@ -25,6 +25,7 @@ GNU General Public License for more details. #include "vid_common.h" SDL_Joystick *g_joy = NULL; +static SDL_Cursor *g_pDefaultCursor[CursorType_Last]; #if !SDL_VERSION_ATLEAST( 2, 0, 0 ) #define SDL_WarpMouseInWindow( win, x, y ) SDL_WarpMouse( ( x ), ( y ) ) #endif @@ -246,4 +247,80 @@ int Platform_JoyInit( int numjoy ) return SDLash_JoyInit_Old(numjoy); } +/* +======================== +SDLash_InitCursors + +======================== +*/ +static void SDLash_InitCursors( void ) +{ + static qboolean initialized = false; + if( !initialized ) + { + // load up all default cursors +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) + g_pDefaultCursor[CursorType_None] = NULL; + g_pDefaultCursor[CursorType_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); + g_pDefaultCursor[CursorType_Ibeam] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); + g_pDefaultCursor[CursorType_Wait] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); + g_pDefaultCursor[CursorType_Crosshair] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR); + g_pDefaultCursor[CursorType_Up] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); + g_pDefaultCursor[CursorType_SizeNwSe] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); + g_pDefaultCursor[CursorType_SizeNeSw] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); + g_pDefaultCursor[CursorType_SizeWe] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); + g_pDefaultCursor[CursorType_SizeNs] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); + g_pDefaultCursor[CursorType_SizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); + g_pDefaultCursor[CursorType_No] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); + g_pDefaultCursor[CursorType_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); +#endif + initialized = true; + } +} + +/* +======================== +Platform_SetCursorType + +======================== +*/ +void Platform_SetCursorType( cursor_type_t type ) +{ + qboolean visible; + + if (cls.key_dest != key_game || cl.paused) + return; + + SDLash_InitCursors(); + + switch( type ) + { + case CursorType_User: + case CursorType_None: + visible = false; + break; + default: + visible = true; + break; + } + +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) + if( CVAR_TO_BOOL( touch_emulate )) + return; + + if (visible && !host.mouse_visible) + { + SDL_SetCursor( g_pDefaultCursor[type] ); + SDL_ShowCursor( true ); + Key_EnableTextInput( true, false ); + } + else if (!visible && host.mouse_visible) + { + SDL_ShowCursor( false ); + Key_EnableTextInput( false, false ); + } + host.mouse_visible = visible; +#endif +} + #endif // XASH_DEDICATED From d3e213aa1b87f64af57f5ce496ead215c9ff9663 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Fri, 22 Apr 2022 21:33:07 +0400 Subject: [PATCH 206/548] engine: platform: added Platform_GetKeyModifiers --- engine/key_modifiers.h | 35 ++++++++++++++++++++++++++++++++++ engine/platform/platform.h | 2 ++ engine/platform/sdl/in_sdl.c | 37 ++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 engine/key_modifiers.h diff --git a/engine/key_modifiers.h b/engine/key_modifiers.h new file mode 100644 index 00000000..a07e1829 --- /dev/null +++ b/engine/key_modifiers.h @@ -0,0 +1,35 @@ +/* +key_modifiers.h - enumeration of possible key modifiers +Copyright (C) 2022 FWGS Team + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#pragma once +#ifndef KEY_MODIFIERS_H +#define KEY_MODIFIERS_H + +typedef enum +{ + KeyModifier_None = 0, + KeyModifier_LeftShift = (1 << 0), + KeyModifier_RightShift = (1 << 1), + KeyModifier_LeftCtrl = (1 << 2), + KeyModifier_RightCtrl = (1 << 3), + KeyModifier_LeftAlt = (1 << 4), + KeyModifier_RightAlt = (1 << 5), + KeyModifier_LeftSuper = (1 << 6), + KeyModifier_RightSuper = (1 << 7), + KeyModifier_NumLock = (1 << 8), + KeyModifier_CapsLock = (1 << 9) +} key_modifier_t; + +#endif diff --git a/engine/platform/platform.h b/engine/platform/platform.h index 02645265..a353f407 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -21,6 +21,7 @@ GNU General Public License for more details. #include "system.h" #include "defaults.h" #include "cursor_type.h" +#include "key_modifiers.h" /* ============================================================================== @@ -73,6 +74,7 @@ void*Platform_GetNativeObject( const char *name ); int Platform_JoyInit( int numjoy ); // returns number of connected gamepads, negative if error // Text input void Platform_EnableTextInput( qboolean enable ); +key_modifier_t Platform_GetKeyModifiers( void ); // System events void Platform_RunEvents( void ); // Mouse diff --git a/engine/platform/sdl/in_sdl.c b/engine/platform/sdl/in_sdl.c index dec29ed1..1d1a19fe 100644 --- a/engine/platform/sdl/in_sdl.c +++ b/engine/platform/sdl/in_sdl.c @@ -323,4 +323,41 @@ void Platform_SetCursorType( cursor_type_t type ) #endif } +/* +======================== +Platform_GetKeyModifiers + +======================== +*/ +key_modifier_t Platform_GetKeyModifiers( void ) +{ + SDL_Keymod modFlags; + key_modifier_t resultFlags; + + resultFlags = KeyModifier_None; + modFlags = SDL_GetModState(); + if( FBitSet( modFlags, KMOD_LCTRL )) + SetBits( resultFlags, KeyModifier_LeftCtrl ); + if( FBitSet( modFlags, KMOD_RCTRL )) + SetBits( resultFlags, KeyModifier_RightCtrl ); + if( FBitSet( modFlags, KMOD_RSHIFT )) + SetBits( resultFlags, KeyModifier_RightShift ); + if( FBitSet( modFlags, KMOD_LSHIFT )) + SetBits( resultFlags, KeyModifier_LeftShift ); + if( FBitSet( modFlags, KMOD_LALT )) + SetBits( resultFlags, KeyModifier_LeftAlt ); + if( FBitSet( modFlags, KMOD_RALT )) + SetBits( resultFlags, KeyModifier_RightAlt ); + if( FBitSet( modFlags, KMOD_NUM )) + SetBits( resultFlags, KeyModifier_NumLock ); + if( FBitSet( modFlags, KMOD_CAPS )) + SetBits( resultFlags, KeyModifier_CapsLock ); + if( FBitSet( modFlags, KMOD_RGUI )) + SetBits( resultFlags, KeyModifier_RightSuper ); + if( FBitSet( modFlags, KMOD_LGUI )) + SetBits( resultFlags, KeyModifier_LeftSuper ); + + return resultFlags; +} + #endif // XASH_DEDICATED From b175d6d95f850dd5533c592ba5fa88ce9330d008 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Fri, 22 Apr 2022 21:34:54 +0400 Subject: [PATCH 207/548] engine: client: fixed forced text input turn off in Key_EnableTextInput --- engine/client/keys.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/client/keys.c b/engine/client/keys.c index fce03218..97b2e629 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -814,9 +814,9 @@ void Key_EnableTextInput( qboolean enable, qboolean force ) OSK_EnableTextInput( enable, force ); return; } - if( enable && ( !host.textmode || force ) ) + if( enable && ( !host.textmode || force )) Platform_EnableTextInput( true ); - else if( !enable ) + else if( !enable && ( host.textmode || force )) Platform_EnableTextInput( false ); host.textmode = enable; From 8044d23e7f9c5e8b0656313f88039a2634cee482 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Fri, 22 Apr 2022 21:37:01 +0400 Subject: [PATCH 208/548] engine: platform: sdl: fixed Key_Event callback when text input enabled --- engine/platform/sdl/events.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/platform/sdl/events.c b/engine/platform/sdl/events.c index 64334fde..619279ab 100644 --- a/engine/platform/sdl/events.c +++ b/engine/platform/sdl/events.c @@ -122,7 +122,7 @@ static void SDLash_KeyEvent( SDL_KeyboardEvent key ) #endif qboolean numLock = SDL_GetModState() & KMOD_NUM; - if( SDL_IsTextInputActive() && down ) + if( SDL_IsTextInputActive() && down && cls.key_dest != key_game ) { if( SDL_GetModState() & KMOD_CTRL ) { From cfcd58dd78c4f8c625043ac674382856cf81f5b7 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Wed, 27 Apr 2022 22:32:20 +0400 Subject: [PATCH 209/548] engine: client: extended VGUI API interface --- engine/client/vgui/vgui_draw.c | 93 ++++++---------------------------- engine/client/vgui/vgui_draw.h | 1 + engine/cursor_type.h | 32 ++++++------ engine/platform/platform.h | 2 +- engine/platform/sdl/events.c | 1 + engine/platform/sdl/in_sdl.c | 35 ++++++------- engine/vgui_api.h | 35 +++++-------- 7 files changed, 65 insertions(+), 134 deletions(-) diff --git a/engine/client/vgui/vgui_draw.c b/engine/client/vgui/vgui_draw.c index fa775a2d..a7456256 100644 --- a/engine/client/vgui/vgui_draw.c +++ b/engine/client/vgui/vgui_draw.c @@ -21,10 +21,6 @@ GNU General Public License for more details. #include "keydefs.h" #include "ref_common.h" #include "input.h" -#ifdef XASH_SDL -#include -static SDL_Cursor* s_pDefaultCursor[20]; -#endif #include "platform/platform.h" static enum VGUI_KeyCode s_pVirtualKeyTrans[256]; @@ -32,20 +28,6 @@ static enum VGUI_DefaultCursor s_currentCursor; static HINSTANCE s_pVGuiSupport; // vgui_support library static convar_t *vgui_utf8 = NULL; -// Helper functions for vgui backend - -/*void VGUI_HideCursor( void ) -{ - host.mouse_visible = false; - SDL_HideCursor(); -} - -void VGUI_ShowCursor( void ) -{ - host.mouse_visible = true; - SDL_ShowCursor(); -}*/ - void GAME_EXPORT *VGUI_EngineMalloc(size_t size) { return Z_Malloc( size ); @@ -66,63 +48,9 @@ void GAME_EXPORT VGUI_GetMousePos( int *_x, int *_y ) *_x = x / xscale, *_y = y / yscale; } -void VGUI_InitCursors( void ) -{ - // load up all default cursors -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - s_pDefaultCursor[dc_none] = NULL; - s_pDefaultCursor[dc_arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); - s_pDefaultCursor[dc_ibeam] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); - s_pDefaultCursor[dc_hourglass]= SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); - s_pDefaultCursor[dc_crosshair]= SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR); - s_pDefaultCursor[dc_up] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); - s_pDefaultCursor[dc_sizenwse] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); - s_pDefaultCursor[dc_sizenesw] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); - s_pDefaultCursor[dc_sizewe] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); - s_pDefaultCursor[dc_sizens] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); - s_pDefaultCursor[dc_sizeall] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); - s_pDefaultCursor[dc_no] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); - s_pDefaultCursor[dc_hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); -#endif -} - void GAME_EXPORT VGUI_CursorSelect( enum VGUI_DefaultCursor cursor ) { - qboolean visible; - - if( cls.key_dest != key_game || cl.paused ) - return; - - switch( cursor ) - { - case dc_user: - case dc_none: - visible = false; - break; - default: - visible = true; - break; - } - - host.mouse_visible = visible; - -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - /// TODO: platform cursors - - if( CVAR_TO_BOOL( touch_emulate ) ) - return; - - if( host.mouse_visible ) - { - SDL_SetCursor( s_pDefaultCursor[cursor] ); - SDL_ShowCursor( true ); - } - else - { - SDL_ShowCursor( false ); - Key_EnableTextInput( false, true ); - } -#endif + Platform_SetCursorType( cursor ); s_currentCursor = cursor; } @@ -134,7 +62,7 @@ byte GAME_EXPORT VGUI_GetColor( int i, int j) // Define and initialize vgui API int GAME_EXPORT VGUI_UtfProcessChar( int in ) { - if( CVAR_TO_BOOL( vgui_utf8 ) ) + if( CVAR_TO_BOOL( vgui_utf8 )) return Con_UtfProcessCharForce( in ); else return in; @@ -163,6 +91,9 @@ vguiapi_t vgui = NULL, VGUI_GetMousePos, VGUI_UtfProcessChar, + Platform_GetClipboardText, + Platform_SetClipboardText, + Platform_GetKeyModifiers, NULL, NULL, NULL, @@ -170,6 +101,7 @@ vguiapi_t vgui = NULL, NULL, NULL, + NULL }; qboolean VGui_IsActive( void ) @@ -478,8 +410,9 @@ void VGui_KeyEvent( int key, int down ) switch( key ) { case K_MOUSE1: - if( down && host.mouse_visible ) - Key_EnableTextInput( true, false ); + if( down && host.mouse_visible ) { + Key_EnableTextInput(true, false); + } vgui.Mouse( down ? MA_PRESSED : MA_RELEASED, MOUSE_LEFT ); return; case K_MOUSE2: @@ -515,7 +448,7 @@ void VGui_MouseMove( int x, int y ) void VGui_Paint( void ) { - if(vgui.initialized) + if( vgui.initialized ) vgui.Paint(); } @@ -531,3 +464,9 @@ void *GAME_EXPORT VGui_GetPanel( void ) return vgui.GetPanel(); return NULL; } + +void VGui_ReportTextInput( const char *text ) +{ + if ( vgui.initialized ) + vgui.TextInput( text ); +} diff --git a/engine/client/vgui/vgui_draw.h b/engine/client/vgui/vgui_draw.h index bf9c4396..dd5a4ad2 100644 --- a/engine/client/vgui/vgui_draw.h +++ b/engine/client/vgui/vgui_draw.h @@ -33,6 +33,7 @@ void VGui_KeyEvent( int key, int down ); void VGui_MouseMove( int x, int y ); qboolean VGui_IsActive( void ); void *VGui_GetPanel( void ); +void VGui_ReportTextInput( const char *text ); #ifdef __cplusplus } #endif diff --git a/engine/cursor_type.h b/engine/cursor_type.h index 6264eda7..da377083 100644 --- a/engine/cursor_type.h +++ b/engine/cursor_type.h @@ -19,21 +19,21 @@ GNU General Public License for more details. typedef enum { - CursorType_User, - CursorType_None, - CursorType_Arrow, - CursorType_Ibeam, - CursorType_Wait, - CursorType_Crosshair, - CursorType_Up, - CursorType_SizeNwSe, - CursorType_SizeNeSw, - CursorType_SizeWe, - CursorType_SizeNs, - CursorType_SizeAll, - CursorType_No, - CursorType_Hand, - CursorType_Last -} cursor_type_t; + dc_user, + dc_none, + dc_arrow, + dc_ibeam, + dc_hourglass, + dc_crosshair, + dc_up, + dc_sizenwse, + dc_sizenesw, + dc_sizewe, + dc_sizens, + dc_sizeall, + dc_no, + dc_hand, + dc_last +} VGUI_DefaultCursor; #endif diff --git a/engine/platform/platform.h b/engine/platform/platform.h index a353f407..e764421a 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -82,7 +82,7 @@ void Platform_GetMousePos( int *x, int *y ); void Platform_SetMousePos( int x, int y ); void Platform_PreCreateMove( void ); void Platform_MouseMove( float *x, float *y ); -void Platform_SetCursorType( cursor_type_t type ); +void Platform_SetCursorType( VGUI_DefaultCursor type ); // Clipboard int Platform_GetClipboardText( char *buffer, size_t size ); void Platform_SetClipboardText( const char *buffer ); diff --git a/engine/platform/sdl/events.c b/engine/platform/sdl/events.c index 619279ab..86d5cde0 100644 --- a/engine/platform/sdl/events.c +++ b/engine/platform/sdl/events.c @@ -318,6 +318,7 @@ SDLash_InputEvent static void SDLash_InputEvent( SDL_TextInputEvent input ) { char *text; + VGui_ReportTextInput( input.text ); for( text = input.text; *text; text++ ) { int ch; diff --git a/engine/platform/sdl/in_sdl.c b/engine/platform/sdl/in_sdl.c index 1d1a19fe..04caed32 100644 --- a/engine/platform/sdl/in_sdl.c +++ b/engine/platform/sdl/in_sdl.c @@ -25,9 +25,10 @@ GNU General Public License for more details. #include "vid_common.h" SDL_Joystick *g_joy = NULL; -static SDL_Cursor *g_pDefaultCursor[CursorType_Last]; #if !SDL_VERSION_ATLEAST( 2, 0, 0 ) #define SDL_WarpMouseInWindow( win, x, y ) SDL_WarpMouse( ( x ), ( y ) ) +#else +static SDL_Cursor *g_pDefaultCursor[dc_last]; #endif /* @@ -260,19 +261,19 @@ static void SDLash_InitCursors( void ) { // load up all default cursors #if SDL_VERSION_ATLEAST( 2, 0, 0 ) - g_pDefaultCursor[CursorType_None] = NULL; - g_pDefaultCursor[CursorType_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); - g_pDefaultCursor[CursorType_Ibeam] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); - g_pDefaultCursor[CursorType_Wait] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); - g_pDefaultCursor[CursorType_Crosshair] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR); - g_pDefaultCursor[CursorType_Up] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); - g_pDefaultCursor[CursorType_SizeNwSe] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); - g_pDefaultCursor[CursorType_SizeNeSw] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); - g_pDefaultCursor[CursorType_SizeWe] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); - g_pDefaultCursor[CursorType_SizeNs] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); - g_pDefaultCursor[CursorType_SizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); - g_pDefaultCursor[CursorType_No] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); - g_pDefaultCursor[CursorType_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); + g_pDefaultCursor[dc_none] = NULL; + g_pDefaultCursor[dc_arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); + g_pDefaultCursor[dc_ibeam] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); + g_pDefaultCursor[dc_hourglass] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); + g_pDefaultCursor[dc_crosshair] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR); + g_pDefaultCursor[dc_up] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); + g_pDefaultCursor[dc_sizenwse] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); + g_pDefaultCursor[dc_sizenesw] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); + g_pDefaultCursor[dc_sizewe] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); + g_pDefaultCursor[dc_sizens] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); + g_pDefaultCursor[dc_sizeall] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); + g_pDefaultCursor[dc_no] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); + g_pDefaultCursor[dc_hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); #endif initialized = true; } @@ -284,7 +285,7 @@ Platform_SetCursorType ======================== */ -void Platform_SetCursorType( cursor_type_t type ) +void Platform_SetCursorType( VGUI_DefaultCursor type ) { qboolean visible; @@ -295,8 +296,8 @@ void Platform_SetCursorType( cursor_type_t type ) switch( type ) { - case CursorType_User: - case CursorType_None: + case dc_user: + case dc_none: visible = false; break; default: diff --git a/engine/vgui_api.h b/engine/vgui_api.h index a54c4d8d..2ce89226 100644 --- a/engine/vgui_api.h +++ b/engine/vgui_api.h @@ -16,6 +16,8 @@ GNU General Public License for more details. #define VGUI_API_H #include "xash3d_types.h" +#include "key_modifiers.h" +#include "cursor_type.h" // VGUI generic vertex @@ -157,28 +159,10 @@ enum VGUI_MouseAction MA_WHEEL }; -enum VGUI_DefaultCursor -{ - dc_user, - dc_none, - dc_arrow, - dc_ibeam, - dc_hourglass, - dc_crosshair, - dc_up, - dc_sizenwse, - dc_sizenesw, - dc_sizewe, - dc_sizens, - dc_sizeall, - dc_no, - dc_hand, - dc_last -}; - typedef struct vguiapi_s { qboolean initialized; + // called from vgui_support void (*DrawInit)( void ); void (*DrawShutdown)( void ); void (*SetupDrawingText)( int *pColor ); @@ -193,18 +177,23 @@ typedef struct vguiapi_s void (*GetTextureSizes)( int *width, int *height ); int (*GenerateTexture)( void ); void *(*EngineMalloc)( size_t size ); - void (*CursorSelect)( enum VGUI_DefaultCursor cursor ); + void (*CursorSelect)( VGUI_DefaultCursor cursor ); byte (*GetColor)( int i, int j ); qboolean (*IsInGame)( void ); - void (*Unused)( void ); + void (*Unused)( void ); void (*GetCursorPos)( int *x, int *y ); int (*ProcessUtfChar)( int ch ); + int (*GetClipboardText)( char *buffer, size_t bufferSize ); + void (*SetClipboardText)( const char *text ); + key_modifier_t (*GetKeyModifiers)( void ); + // called from engine side void (*Startup)( int width, int height ); void (*Shutdown)( void ); void *(*GetPanel)( void ); void (*Paint)( void ); - void (*Mouse)(enum VGUI_MouseAction action, int code ); - void (*Key)(enum VGUI_KeyAction action,enum VGUI_KeyCode code ); + void (*Mouse)( enum VGUI_MouseAction action, int code ); + void (*Key)( enum VGUI_KeyAction action, enum VGUI_KeyCode code ); void (*MouseMove)( int x, int y ); + void (*TextInput)( const char *text ); } vguiapi_t; #endif // VGUI_API_H From 87ceb0f9cb6a3771b6da1373949f4065e0f37141 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Wed, 27 Apr 2022 22:38:33 +0400 Subject: [PATCH 210/548] engine: client: fixed checking client library for vgui_support interface export --- engine/client/cl_scrn.c | 4 +- engine/client/vgui/vgui_draw.c | 80 +++++++++++++++++----------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/engine/client/cl_scrn.c b/engine/client/cl_scrn.c index afc3a959..62a42047 100644 --- a/engine/client/cl_scrn.c +++ b/engine/client/cl_scrn.c @@ -781,6 +781,7 @@ SCR_VidInit */ void SCR_VidInit( void ) { + string libpath; if( !ref.initialized ) // don't call VidInit too soon return; @@ -795,7 +796,8 @@ void SCR_VidInit( void ) gameui.globals->scrHeight = refState.height; } - VGui_Startup( NULL, refState.width, refState.height ); // initialized already, so pass NULL + COM_GetCommonLibraryPath( LIBRARY_CLIENT, libpath, sizeof( libpath )); + VGui_Startup( libpath, refState.width, refState.height ); CL_ClearSpriteTextures(); // now all hud sprites are invalid diff --git a/engine/client/vgui/vgui_draw.c b/engine/client/vgui/vgui_draw.c index a7456256..b021ed74 100644 --- a/engine/client/vgui/vgui_draw.c +++ b/engine/client/vgui/vgui_draw.c @@ -152,7 +152,6 @@ void VGui_Startup( const char *clientlib, int width, int height ) VGui_FillAPIFromRef( &vgui, &ref.dllFuncs ); -#ifdef XASH_INTERNAL_GAMELIBS s_pVGuiSupport = COM_LoadLibrary( clientlib, false, false ); if( s_pVGuiSupport ) @@ -162,58 +161,57 @@ void VGui_Startup( const char *clientlib, int width, int height ) { F( &vgui ); vgui.initialized = true; - VGUI_InitCursors(); - Con_Reportf( "vgui_support: found interal client support\n" ); + Con_Reportf( "vgui_support: found internal client support\n" ); } } -#endif // XASH_INTERNAL_GAMELIBS - // HACKHACK: load vgui with correct path first if specified. - // it will be reused while resolving vgui support and client deps - if( Sys_GetParmFromCmdLine( "-vguilib", vguilib ) ) + if( !vgui.initialized ) { - if( Q_strstr( vguilib, ".dll") ) + // HACKHACK: load vgui with correct path first if specified. + // it will be reused while resolving vgui support and client deps + if( Sys_GetParmFromCmdLine( "-vguilib", vguilib )) + { + if( Q_strstr( vguilib, ".dll" )) + Q_strncpy( vguiloader, "vgui_support.dll", 256 ); + else + Q_strncpy( vguiloader, VGUI_SUPPORT_DLL, 256 ); + + if( !COM_LoadLibrary( vguilib, false, false )) + Con_Reportf( S_WARN "VGUI preloading failed. Default library will be used! Reason: %s\n", COM_GetLibraryError() ); + } + + if( Q_strstr( clientlib, ".dll" )) Q_strncpy( vguiloader, "vgui_support.dll", 256 ); - else + + if( !vguiloader[0] && !Sys_GetParmFromCmdLine( "-vguiloader", vguiloader )) Q_strncpy( vguiloader, VGUI_SUPPORT_DLL, 256 ); - if( !COM_LoadLibrary( vguilib, false, false ) ) - Con_Reportf( S_WARN "VGUI preloading failed. Default library will be used! Reason: %s\n", COM_GetLibraryError()); - } + s_pVGuiSupport = COM_LoadLibrary( vguiloader, false, false ); - if( Q_strstr( clientlib, ".dll" ) ) - Q_strncpy( vguiloader, "vgui_support.dll", 256 ); - - if( !vguiloader[0] && !Sys_GetParmFromCmdLine( "-vguiloader", vguiloader ) ) - Q_strncpy( vguiloader, VGUI_SUPPORT_DLL, 256 ); - - s_pVGuiSupport = COM_LoadLibrary( vguiloader, false, false ); - - if( !s_pVGuiSupport ) - { - s_pVGuiSupport = COM_LoadLibrary( va( "../%s", vguiloader ), false, false ); - } - - if( !s_pVGuiSupport ) - { - if( FS_FileExists( vguiloader, false ) ) - Con_Reportf( S_ERROR "Failed to load vgui_support library: %s\n", COM_GetLibraryError() ); - else - Con_Reportf( "vgui_support: not found\n" ); - } - else - { - F = COM_GetProcAddress( s_pVGuiSupport, "InitAPI" ); - if( F ) + if( !s_pVGuiSupport ) { - F( &vgui ); - vgui.initialized = true; - VGUI_InitCursors(); + s_pVGuiSupport = COM_LoadLibrary( va( "../%s", vguiloader ), false, false ); + } + + if( !s_pVGuiSupport ) + { + if( FS_FileExists( vguiloader, false )) + Con_Reportf( S_ERROR "Failed to load vgui_support library: %s\n", COM_GetLibraryError() ); + else + Con_Reportf( "vgui_support: not found\n" ); } else - Con_Reportf( S_ERROR "Failed to find vgui_support library entry point!\n" ); + { + F = COM_GetProcAddress( s_pVGuiSupport, "InitAPI" ); + if( F ) + { + F( &vgui ); + vgui.initialized = true; + } + else + Con_Reportf( S_ERROR "Failed to find vgui_support library entry point!\n" ); + } } - } if( height < 480 ) From 974f0787a4b78ea8f68a77bce59e50406972a43f Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Wed, 27 Apr 2022 22:39:15 +0400 Subject: [PATCH 211/548] vgui_support: added VGUI_TextInput callback stub --- vgui_support/vgui_input.cpp | 5 +++++ vgui_support/vgui_int.cpp | 1 + vgui_support/vgui_main.h | 2 ++ 3 files changed, 8 insertions(+) diff --git a/vgui_support/vgui_input.cpp b/vgui_support/vgui_input.cpp index 5280b5b7..0a6ee74b 100644 --- a/vgui_support/vgui_input.cpp +++ b/vgui_support/vgui_input.cpp @@ -83,4 +83,9 @@ void VGUI_MouseMove(int x, int y) return; pApp->internalCursorMoved( x, y, surface ); } + +void VGUI_TextInput(const char *text) +{ + // stub +} } diff --git a/vgui_support/vgui_int.cpp b/vgui_support/vgui_int.cpp index 777c53e3..1506767a 100644 --- a/vgui_support/vgui_int.cpp +++ b/vgui_support/vgui_int.cpp @@ -119,4 +119,5 @@ extern "C" EXPORT void InitAPI(vguiapi_t * api) g_api->Mouse = VGUI_Mouse; g_api->MouseMove = VGUI_MouseMove; g_api->Key = VGUI_Key; + g_api->TextInput = VGUI_TextInput; } diff --git a/vgui_support/vgui_main.h b/vgui_support/vgui_main.h index 454ecbae..5819531d 100644 --- a/vgui_support/vgui_main.h +++ b/vgui_support/vgui_main.h @@ -140,6 +140,8 @@ void VGui_Paint( void ); void VGUI_Mouse(VGUI_MouseAction action, int code); void VGUI_Key(VGUI_KeyAction action, VGUI_KeyCode code); void VGUI_MouseMove(int x, int y); +void VGUI_TextInput(const char *text); + // // vgui_clip.cpp // From 641f0632efde450a896a2612981eb33bf9a38b61 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Wed, 27 Apr 2022 23:09:20 +0400 Subject: [PATCH 212/548] engine: client: fixed compilation errors on Android --- engine/client/vgui/vgui_draw.c | 4 ++-- engine/platform/android/android.c | 16 ++++++++++++++-- engine/platform/sdl/in_sdl.c | 4 ++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/engine/client/vgui/vgui_draw.c b/engine/client/vgui/vgui_draw.c index b021ed74..a9b02640 100644 --- a/engine/client/vgui/vgui_draw.c +++ b/engine/client/vgui/vgui_draw.c @@ -24,7 +24,7 @@ GNU General Public License for more details. #include "platform/platform.h" static enum VGUI_KeyCode s_pVirtualKeyTrans[256]; -static enum VGUI_DefaultCursor s_currentCursor; +static VGUI_DefaultCursor s_currentCursor; static HINSTANCE s_pVGuiSupport; // vgui_support library static convar_t *vgui_utf8 = NULL; @@ -48,7 +48,7 @@ void GAME_EXPORT VGUI_GetMousePos( int *_x, int *_y ) *_x = x / xscale, *_y = y / yscale; } -void GAME_EXPORT VGUI_CursorSelect( enum VGUI_DefaultCursor cursor ) +void GAME_EXPORT VGUI_CursorSelect( VGUI_DefaultCursor cursor ) { Platform_SetCursorType( cursor ); s_currentCursor = cursor; diff --git a/engine/platform/android/android.c b/engine/platform/android/android.c index 45cd40fb..5a61bccf 100644 --- a/engine/platform/android/android.c +++ b/engine/platform/android/android.c @@ -776,17 +776,29 @@ void Platform_ShellExecute( const char *path, const char *parms ) // no need to free jstr } -void Platform_GetClipboardText( char *buffer, size_t size ) +int Platform_GetClipboardText( char *buffer, size_t size ) { // stub if( size ) buffer[0] = 0; + return 0; } -void Platform_SetClipboardText( const char *buffer, size_t size ) +void Platform_SetClipboardText( const char *buffer ) { // stub } +void Platform_SetCursorType( VGUI_DefaultCursor cursor ) +{ + // stub +} + +key_modifier_t Platform_GetKeyModifiers( void ) +{ + // stub + return KeyModifier_None; +} + void Platform_PreCreateMove( void ) { // stub diff --git a/engine/platform/sdl/in_sdl.c b/engine/platform/sdl/in_sdl.c index 04caed32..cc85c191 100644 --- a/engine/platform/sdl/in_sdl.c +++ b/engine/platform/sdl/in_sdl.c @@ -332,6 +332,7 @@ Platform_GetKeyModifiers */ key_modifier_t Platform_GetKeyModifiers( void ) { +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_Keymod modFlags; key_modifier_t resultFlags; @@ -359,6 +360,9 @@ key_modifier_t Platform_GetKeyModifiers( void ) SetBits( resultFlags, KeyModifier_LeftSuper ); return resultFlags; +#else + return KeyModifier_None; +#endif } #endif // XASH_DEDICATED From d1a5c33bf1543a7f87beb119b1e2411081e1786c Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Thu, 28 Apr 2022 17:15:47 +0400 Subject: [PATCH 213/548] engine: common: removed unused function Sys_SetClipboardData --- engine/common/dedicated.c | 5 ----- engine/common/system.c | 12 ------------ engine/common/system.h | 1 - 3 files changed, 18 deletions(-) diff --git a/engine/common/dedicated.c b/engine/common/dedicated.c index 965c75c7..07b77a50 100644 --- a/engine/common/dedicated.c +++ b/engine/common/dedicated.c @@ -306,11 +306,6 @@ void SCR_CheckStartupVids( void ) } -void Sys_SetClipboardData( const char *text, size_t size ) -{ - -} - void CL_StopPlayback( void ) { diff --git a/engine/common/system.c b/engine/common/system.c index 06ff7a03..6916fdd1 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -81,18 +81,6 @@ char *Sys_GetClipboardData( void ) return data; } - -/* -================ -Sys_SetClipboardData - -write screenshot into clipboard -================ -*/ -void Sys_SetClipboardData( const char *buffer, size_t size ) -{ - Platform_SetClipboardText( buffer ); -} #endif // XASH_DEDICATED /* diff --git a/engine/common/system.h b/engine/common/system.h index 5c9ffdb9..9656dbca 100644 --- a/engine/common/system.h +++ b/engine/common/system.h @@ -59,7 +59,6 @@ void Sys_ParseCommandLine( int argc, char **argv ); void Sys_MergeCommandLine( void ); void Sys_SetupCrashHandler( void ); void Sys_RestoreCrashHandler( void ); -void Sys_SetClipboardData( const char *buffer, size_t size ); #define Sys_GetParmFromCmdLine( parm, out ) _Sys_GetParmFromCmdLine( parm, out, sizeof( out )) qboolean _Sys_GetParmFromCmdLine( const char *parm, char *out, size_t size ); qboolean Sys_GetIntFromCmdLine( const char *parm, int *out ); From 608aa4137d66d86aad44cc3781be2a625300ba49 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Wed, 4 May 2022 09:23:37 -0700 Subject: [PATCH 214/548] vk: use staging for uploading geometry --- ref_vk/vk_beams.c | 21 +++++------- ref_vk/vk_brush.c | 42 +++++++++-------------- ref_vk/vk_core.h | 1 + ref_vk/vk_devmem.c | 2 +- ref_vk/vk_framectl.c | 4 +++ ref_vk/vk_render.c | 79 ++++++++++++++++++++++++++++++++------------ ref_vk/vk_render.h | 55 +++++++++++++++++------------- ref_vk/vk_scene.c | 3 ++ ref_vk/vk_sprite.c | 21 +++++------- ref_vk/vk_staging.c | 70 ++++++++++++++++++--------------------- ref_vk/vk_staging.h | 13 +++++--- ref_vk/vk_studio.c | 24 +++++--------- ref_vk/vk_textures.c | 7 ++-- 13 files changed, 185 insertions(+), 157 deletions(-) diff --git a/ref_vk/vk_beams.c b/ref_vk/vk_beams.c index 22cf3b5e..e0857899 100644 --- a/ref_vk/vk_beams.c +++ b/ref_vk/vk_beams.c @@ -163,7 +163,7 @@ static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, f beamseg_t curSeg; int total_vertices = 0; int total_indices = 0; - xvk_render_buffer_allocation_t vertex_buffer, index_buffer; + r_geometry_buffer_lock_t buffer; vk_vertex_t *dst_vtx; uint16_t *dst_idx; @@ -223,17 +223,13 @@ static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, f total_indices = (total_vertices - 2) * 3; // STRIP unrolled into LIST (TODO get rid of this) ASSERT(total_vertices < UINT16_MAX ); - vertex_buffer = XVK_RenderBufferAllocAndLock( sizeof(vk_vertex_t), total_vertices ); - index_buffer = XVK_RenderBufferAllocAndLock( sizeof(uint16_t), total_indices ); - if (!vertex_buffer.ptr || !index_buffer.ptr) - { - // TODO should we free one of the above if it still succeeded? - gEngine.Con_Printf(S_ERROR "Ran out of buffer space\n"); + if (!R_GeometryBufferAllocAndLock( &buffer, total_vertices, total_indices )) { + gEngine.Con_Printf(S_ERROR "Cannot allocate geometry for beam\n"); return; } - dst_vtx = vertex_buffer.ptr; - dst_idx = index_buffer.ptr; + dst_vtx = buffer.vertices.ptr; + dst_idx = buffer.indices.ptr; // specify all the segments. for( i = 0; i < segments; i++ ) @@ -377,8 +373,7 @@ static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, f } } - XVK_RenderBufferUnlock( index_buffer.buffer ); - XVK_RenderBufferUnlock( vertex_buffer.buffer ); + R_GeometryBufferUnlock( &buffer ); { const vk_render_geometry_t geometry = { @@ -386,10 +381,10 @@ static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, f .material = kXVkMaterialEmissive, .max_vertex = total_vertices, - .vertex_offset = vertex_buffer.buffer.unit.offset, + .vertex_offset = buffer.vertices.unit_offset, .element_count = total_indices, - .index_offset = index_buffer.buffer.unit.offset, + .index_offset = buffer.indices.unit_offset, .emissive = { color[0], color[1], color[2] }, }; diff --git a/ref_vk/vk_brush.c b/ref_vk/vk_brush.c index e15d32ad..5348dc4f 100644 --- a/ref_vk/vk_brush.c +++ b/ref_vk/vk_brush.c @@ -88,10 +88,9 @@ static void EmitWaterPolys( const cl_entity_t *ent, const msurface_t *warp, qboo glpoly_t *p; int i; int num_vertices = 0, num_indices = 0; - xvk_render_buffer_allocation_t vertex_buffer, index_buffer = {0}; int vertex_offset = 0; - vk_vertex_t *gpu_vertices; uint16_t *indices; + r_geometry_buffer_lock_t buffer; #define MAX_WATER_VERTICES 16 vk_vertex_t poly_vertices[MAX_WATER_VERTICES]; @@ -116,17 +115,12 @@ static void EmitWaterPolys( const cl_entity_t *ent, const msurface_t *warp, qboo num_indices += triangles * 3; } - vertex_buffer = XVK_RenderBufferAllocAndLock( sizeof(vk_vertex_t), num_vertices ); - index_buffer = XVK_RenderBufferAllocAndLock( sizeof(uint16_t), num_indices ); - if (vertex_buffer.ptr == NULL || index_buffer.ptr == NULL) - { - // TODO should we free one of the above if it still succeeded? - gEngine.Con_Printf(S_ERROR "Ran out of buffer space\n"); + if (!R_GeometryBufferAllocAndLock( &buffer, num_vertices, num_indices )) { + gEngine.Con_Printf(S_ERROR "Cannot allocate geometry for %s\n", ent->model->name ); return; } - gpu_vertices = vertex_buffer.ptr; - indices = index_buffer.ptr; + indices = buffer.indices.ptr; for( p = warp->polys; p; p = p->next ) { @@ -204,12 +198,11 @@ static void EmitWaterPolys( const cl_entity_t *ent, const msurface_t *warp, qboo } #endif - memcpy(gpu_vertices + vertex_offset, poly_vertices, sizeof(vk_vertex_t) * p->numverts); + memcpy(buffer.vertices.ptr + vertex_offset, poly_vertices, sizeof(vk_vertex_t) * p->numverts); vertex_offset += p->numverts; } - XVK_RenderBufferUnlock( vertex_buffer.buffer ); - XVK_RenderBufferUnlock( index_buffer.buffer ); + R_GeometryBufferUnlock( &buffer ); // Render { @@ -219,10 +212,10 @@ static void EmitWaterPolys( const cl_entity_t *ent, const msurface_t *warp, qboo .surf = warp, .max_vertex = num_vertices, - .vertex_offset = vertex_buffer.buffer.unit.offset, + .vertex_offset = buffer.vertices.unit_offset, .element_count = num_indices, - .index_offset = index_buffer.buffer.unit.offset, + .index_offset = buffer.indices.unit_offset, }; VK_RenderModelDynamicAddGeometry( &geometry ); @@ -487,22 +480,20 @@ static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { vk_brush_model_t *bmodel = mod->cache.data; uint32_t vertex_offset = 0; int num_geometries = 0; - xvk_render_buffer_allocation_t vertex_buffer, index_buffer; vk_vertex_t *bvert = NULL; uint16_t *bind = NULL; uint32_t index_offset = 0; + r_geometry_buffer_lock_t buffer; - vertex_buffer = XVK_RenderBufferAllocAndLock( sizeof(vk_vertex_t), sizes.num_vertices ); - index_buffer = XVK_RenderBufferAllocAndLock( sizeof(uint16_t), sizes.num_indices ); - if (vertex_buffer.ptr == NULL || index_buffer.ptr == NULL) { - gEngine.Con_Printf(S_ERROR "Ran out of buffer space\n"); + if (!R_GeometryBufferAllocAndLock( &buffer, sizes.num_vertices, sizes.num_indices )) { + gEngine.Con_Printf(S_ERROR "Cannot allocate geometry for %s\n", mod->name ); return false; } - bvert = vertex_buffer.ptr; - bind = index_buffer.ptr; + bvert = buffer.vertices.ptr; + bind = buffer.indices.ptr; - index_offset = index_buffer.buffer.unit.offset; + index_offset = buffer.indices.unit_offset; // Load sorted by gl_texturenum // TODO this does not make that much sense in vulkan (can sort later) @@ -564,7 +555,7 @@ static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { model_geometry->surf = surf; model_geometry->texture = tex_id; - model_geometry->vertex_offset = vertex_buffer.buffer.unit.offset; + model_geometry->vertex_offset = buffer.vertices.unit_offset; model_geometry->max_vertex = vertex_offset + surf->numedges; model_geometry->index_offset = index_offset; @@ -642,8 +633,7 @@ static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { } } - XVK_RenderBufferUnlock( index_buffer.buffer ); - XVK_RenderBufferUnlock( vertex_buffer.buffer ); + R_GeometryBufferUnlock( &buffer ); if (bmodel->polylights) { gEngine.Con_Reportf("WHAT %d %d \n", sizes.emissive_surfaces, bmodel->polylights_count); diff --git a/ref_vk/vk_core.h b/ref_vk/vk_core.h index f6f0e464..2b527276 100644 --- a/ref_vk/vk_core.h +++ b/ref_vk/vk_core.h @@ -229,6 +229,7 @@ do { \ X(vkBindImageMemory) \ X(vkCmdPipelineBarrier) \ X(vkCmdCopyBufferToImage) \ + X(vkCmdCopyBuffer) \ X(vkQueueWaitIdle) \ X(vkDeviceWaitIdle) \ X(vkDestroyImage) \ diff --git a/ref_vk/vk_devmem.c b/ref_vk/vk_devmem.c index f7c1d422..b225df1e 100644 --- a/ref_vk/vk_devmem.c +++ b/ref_vk/vk_devmem.c @@ -65,7 +65,7 @@ static int allocateDeviceMemory(VkMemoryRequirements req, int type_index, VkMemo }; if (g_vk_devmem.verbose) { - gEngine.Con_Reportf("allocateDeviceMemory size=%zu memoryTypeBits=0x%x prop_flags=%c%c%c%c%c allocate_flags=0x%x => typeIndex=%d\n", + gEngine.Con_Reportf("allocateDeviceMemory size=%zu memoryTypeBits=0x%x allocate_flags=0x%x => typeIndex=%d\n", mai.allocationSize, req.memoryTypeBits, allocate_flags, mai.memoryTypeIndex); diff --git a/ref_vk/vk_framectl.c b/ref_vk/vk_framectl.c index bce9a96f..3967e8d3 100644 --- a/ref_vk/vk_framectl.c +++ b/ref_vk/vk_framectl.c @@ -8,6 +8,7 @@ #include "vk_devmem.h" #include "vk_swapchain.h" #include "vk_image.h" +#include "vk_staging.h" #include "profiler.h" @@ -249,6 +250,9 @@ static void enqueueRendering( VkCommandBuffer cmdbuf ) { ASSERT(g_frame.current.phase == Phase_FrameBegan); + R_VkStagingCommit(cmdbuf); // FIXME where and when + R_VKStagingMarkEmpty_FIXME(); + if (g_frame.rtx_enabled) VK_RenderEndRTX( cmdbuf, g_frame.current.framebuffer.view, g_frame.current.framebuffer.image, g_frame.current.framebuffer.width, g_frame.current.framebuffer.height ); diff --git a/ref_vk/vk_render.c b/ref_vk/vk_render.c index 27a037d4..741da947 100644 --- a/ref_vk/vk_render.c +++ b/ref_vk/vk_render.c @@ -2,6 +2,7 @@ #include "vk_core.h" #include "vk_buffer.h" +#include "vk_staging.h" #include "vk_const.h" #include "vk_common.h" #include "vk_pipeline.h" @@ -235,9 +236,9 @@ qboolean VK_RenderInit( void ) // TODO device memory and friends (e.g. handle mobile memory ...) - if (!VK_BufferCreate("render buffer", &g_render.buffer, vertex_buffer_size + index_buffer_size, - VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | (vk_core.rtx ? VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR : 0), - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | (vk_core.rtx ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : 0))) // TODO staging buffer? + if (!VK_BufferCreate("geometry buffer", &g_render.buffer, vertex_buffer_size + index_buffer_size, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | (vk_core.rtx ? VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR : 0), + (vk_core.rtx ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : 0))) // TODO staging buffer? return false; if (!VK_BufferCreate("render uniform_buffer", &g_render.uniform_buffer, uniform_unit_size * MAX_UNIFORM_SLOTS, @@ -294,32 +295,51 @@ void VK_RenderShutdown( void ) VK_BufferDestroy( &g_render.uniform_buffer ); } -xvk_render_buffer_allocation_t XVK_RenderBufferAllocAndLock( uint32_t unit_size, uint32_t count ) { - const uint32_t alloc_size = unit_size * count; - uint32_t offset; - xvk_render_buffer_allocation_t retval = {0}; - - ASSERT(unit_size > 0); - - offset = VK_RingBuffer_Alloc(&g_render.buffer_alloc_ring, alloc_size, unit_size); +qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int vertex_count, int index_count ) { + const uint32_t vertices_size = vertex_count * sizeof(vk_vertex_t); + const uint32_t indices_size = index_count * sizeof(uint16_t); + const uint32_t total_size = vertices_size + indices_size; + const uint32_t offset = VK_RingBuffer_Alloc(&g_render.buffer_alloc_ring, total_size, sizeof(vk_vertex_t)); if (offset == AllocFailed) { - gEngine.Con_Printf(S_ERROR "Cannot allocate %u bytes aligned at %u from buffer; only %u are left", - alloc_size, unit_size, g_render.buffer_alloc_ring.free); - return retval; + gEngine.Con_Printf(S_ERROR "Cannot allocate geometry buffer for %d vertices (%d bytes) and %d indices (%d bytes), only %u bytes left", + vertex_count, vertices_size, index_count, indices_size, g_render.buffer_alloc_ring.free); + return false; } - // TODO bake sequence number into handle (to detect buffer lifetime misuse) - retval.buffer.unit.size = unit_size; - retval.buffer.unit.count = count; - retval.buffer.unit.offset = offset / unit_size; - retval.ptr = ((byte*)g_render.buffer.mapped) + offset; + { + const uint32_t vertices_offset = offset / sizeof(vk_vertex_t); + const uint32_t indices_offset = (offset + vertices_size) / sizeof(uint16_t); - return retval; + const vk_staging_region_t staging = R_VkStagingLock(total_size, 4); + ASSERT(staging.ptr); + + ASSERT( offset % sizeof(vk_vertex_t) == 0 ); + ASSERT( (offset + vertices_size) % sizeof(uint16_t) == 0 ); + + *lock = (r_geometry_buffer_lock_t) { + .vertices = { + .count = vertex_count, + .ptr = (vk_vertex_t *)staging.ptr, + .unit_offset = vertices_offset, + }, + .indices = { + .count = index_count, + .ptr = (uint16_t *)((char*)staging.ptr + vertices_size), + .unit_offset = indices_offset, + }, + .impl_ = { + .staging_handle = staging.handle, + .offset = offset, + }, + }; + } + + return true; } -void XVK_RenderBufferUnlock( xvk_render_buffer_t handle ) { - // TODO check whether we need to upload something from staging, etc +void R_GeometryBufferUnlock( const r_geometry_buffer_lock_t *lock ) { + R_VkStagingUnlockToBuffer(lock->impl_.staging_handle, g_render.buffer.buffer, lock->impl_.offset); } void XVK_RenderBufferMapFreeze( void ) { @@ -688,6 +708,21 @@ qboolean VK_RenderModelInit( VkCommandBuffer cmdbuf, vk_render_model_t *model ) .buffer = g_render.buffer.buffer, .model = model, }; + R_VkStagingCommit(cmdbuf); + { + const VkBufferMemoryBarrier bmb[] = { { + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR, // FIXME + .buffer = g_render.buffer.buffer, + .offset = 0, // FIXME + .size = VK_WHOLE_SIZE, // FIXME + } }; + vkCmdPipelineBarrier(cmdbuf, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, + 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); + } model->ray_model = VK_RayModelCreate(cmdbuf, args); return !!model->ray_model; } diff --git a/ref_vk/vk_render.h b/ref_vk/vk_render.h index 2ba88caf..156b9f16 100644 --- a/ref_vk/vk_render.h +++ b/ref_vk/vk_render.h @@ -13,21 +13,42 @@ void VK_RenderShutdown( void ); // 4. ... use it // 5. free (frame/map end) +// TODO is this a good place? +typedef struct vk_vertex_s { + // TODO padding needed for storage buffer reading, figure out how to fix in GLSL/SPV side + vec3_t pos; float p0_; + vec3_t normal; uint32_t flags; + vec3_t tangent; uint32_t p1_; + vec2_t gl_tc; //float p2_[2]; + vec2_t lm_tc; //float p3_[2]; + + rgba_t color; // per-vertex (non-rt lighting) color, color[3] == 1(255) => use color, discard lightmap; color[3] == 0 => use lightmap, discard color + float _padding[3]; +} vk_vertex_t; + typedef struct { struct { - uint32_t size; // single unit size in bytes - uint32_t offset; // offset in units from start of vulkan buffer - uint32_t count; // number of units in this allocation - } unit; -} xvk_render_buffer_t; + vk_vertex_t *ptr; + int count; + int unit_offset; + } vertices; -typedef struct { - void *ptr; - xvk_render_buffer_t buffer; -} xvk_render_buffer_allocation_t; + struct { + uint16_t *ptr; + int count; + int unit_offset; + } indices; + + struct { + int staging_handle; + uint32_t offset; + } impl_; +} r_geometry_buffer_lock_t; + +qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int vertex_count, int index_count ); +void R_GeometryBufferUnlock( const r_geometry_buffer_lock_t *lock ); +//void R_VkGeometryBufferFree( int handle ); -xvk_render_buffer_allocation_t XVK_RenderBufferAllocAndLock( uint32_t unit_size, uint32_t count ); -void XVK_RenderBufferUnlock( xvk_render_buffer_t handle ); void XVK_RenderBufferMapFreeze( void ); // Permanently freeze all allocations as map-permanent void XVK_RenderBufferMapClear( void ); // Free the entire buffer for a new map @@ -46,18 +67,6 @@ void VK_RenderStateSetMatrixProjection(const matrix4x4 proj, float fov_angle_y); void VK_RenderStateSetMatrixView(const matrix4x4 view); void VK_RenderStateSetMatrixModel(const matrix4x4 model); -// TODO is this a good place? -typedef struct vk_vertex_s { - // TODO padding needed for storage buffer reading, figure out how to fix in GLSL/SPV side - vec3_t pos; float p0_; - vec3_t normal; uint32_t flags; - vec3_t tangent; uint32_t p1_; - vec2_t gl_tc; //float p2_[2]; - vec2_t lm_tc; //float p3_[2]; - - rgba_t color; // per-vertex (non-rt lighting) color, color[3] == 1(255) => use color, discard lightmap; color[3] == 0 => use lightmap, discard color - float _padding[3]; -} vk_vertex_t; // Quirk for passing surface type to the renderer // xash3d does not really have a notion of materials. Instead there are custom code paths diff --git a/ref_vk/vk_scene.c b/ref_vk/vk_scene.c index b3a1e1e4..a3acff90 100644 --- a/ref_vk/vk_scene.c +++ b/ref_vk/vk_scene.c @@ -1,5 +1,6 @@ #include "vk_scene.h" #include "vk_brush.h" +#include "vk_staging.h" #include "vk_studio.h" #include "vk_lightmap.h" #include "vk_const.h" @@ -215,6 +216,8 @@ void R_NewMap( void ) { // Reads surfaces from loaded brush models (must happen after all brushes are loaded) RT_LightsNewMapEnd(map); + R_VKStagingMarkEmpty_FIXME(); + if (vk_core.rtx) { //ASSERT(!"Not implemented"); diff --git a/ref_vk/vk_sprite.c b/ref_vk/vk_sprite.c index 739ee232..bf869f5a 100644 --- a/ref_vk/vk_sprite.c +++ b/ref_vk/vk_sprite.c @@ -647,22 +647,18 @@ qboolean R_SpriteOccluded( cl_entity_t *e, vec3_t origin, float *pscale ) static void R_DrawSpriteQuad( const char *debug_name, mspriteframe_t *frame, vec3_t org, vec3_t v_right, vec3_t v_up, float scale, int texture, int render_mode, vec3_t color ) { vec3_t point; - xvk_render_buffer_allocation_t vertex_buffer, index_buffer; vk_vertex_t *dst_vtx; uint16_t *dst_idx; // Get buffer region for vertices and indices - vertex_buffer = XVK_RenderBufferAllocAndLock( sizeof(vk_vertex_t), 4 ); - index_buffer = XVK_RenderBufferAllocAndLock( sizeof(uint16_t), 6 ); - if (!vertex_buffer.ptr || !index_buffer.ptr) - { - // TODO should we free one of the above if it still succeeded? - gEngine.Con_Printf(S_ERROR "Ran out of buffer space\n"); + r_geometry_buffer_lock_t buffer; + if (!R_GeometryBufferAllocAndLock( &buffer, 4, 6 )) { + gEngine.Con_Printf(S_ERROR "Cannot allocate geometry for sprite quad\n"); return; } - dst_vtx = vertex_buffer.ptr; - dst_idx = index_buffer.ptr; + dst_vtx = buffer.vertices.ptr; + dst_idx = buffer.indices.ptr; // FIXME VK r_stats.c_sprite_polys++; @@ -697,8 +693,7 @@ static void R_DrawSpriteQuad( const char *debug_name, mspriteframe_t *frame, vec dst_idx[4] = 2; dst_idx[5] = 3; - XVK_RenderBufferUnlock( index_buffer.buffer ); - XVK_RenderBufferUnlock( vertex_buffer.buffer ); + R_GeometryBufferUnlock( &buffer ); { const vk_render_geometry_t geometry = { @@ -706,10 +701,10 @@ static void R_DrawSpriteQuad( const char *debug_name, mspriteframe_t *frame, vec .material = kXVkMaterialEmissive, .max_vertex = 4, - .vertex_offset = vertex_buffer.buffer.unit.offset, + .vertex_offset = buffer.vertices.unit_offset, .element_count = 6, - .index_offset = index_buffer.buffer.unit.offset, + .index_offset = buffer.indices.unit_offset, .emissive = {color[0], color[1], color[2]}, }; diff --git a/ref_vk/vk_staging.c b/ref_vk/vk_staging.c index 6f837b89..01c970cf 100644 --- a/ref_vk/vk_staging.c +++ b/ref_vk/vk_staging.c @@ -26,6 +26,7 @@ static struct { vk_buffer_t buffer; staging_alloc_t allocs[MAX_STAGING_ALLOCS]; int num_allocs; + int num_committed; } g_staging = {0}; qboolean R_VkStagingInit(void) { @@ -39,8 +40,10 @@ void R_VkStagingShutdown(void) { VK_BufferDestroy(&g_staging.buffer); } -vk_staging_region_t R_VkStagingLock(size_t size) { - const int offset = g_staging.num_allocs > 0 ? g_staging.allocs[g_staging.num_allocs - 1].offset + g_staging.allocs[g_staging.num_allocs - 1].size : 0; +vk_staging_region_t R_VkStagingLock(uint32_t size, uint32_t alignment) { + const int offset = g_staging.num_allocs > 0 + ? ALIGN_UP(g_staging.allocs[g_staging.num_allocs - 1].offset + g_staging.allocs[g_staging.num_allocs - 1].size, alignment) + : 0; if ( g_staging.num_allocs >= MAX_STAGING_ALLOCS ) return (vk_staging_region_t){0}; @@ -55,22 +58,24 @@ vk_staging_region_t R_VkStagingLock(size_t size) { return (vk_staging_region_t){(char*)g_staging.buffer.mapped + offset, size, g_staging.num_allocs - 1}; } -/* -void R_VkStagingUnlockToBuffer(const vk_staging_region_t* region, VkBuffer dest, size_t dest_offset) { - ASSERT(region->internal_id_ >= 0 && region->internal_id_ < g_staging.num_allocs); - ASSERT(g_staging.allocs[region->internal_id_].dest == VK_NULL_HANDLE); - - g_staging.allocs[region->internal_id_].dest = dest; - g_staging.allocs[region->internal_id_].dest_offset = dest_offset; -} -*/ - -void R_VkStagingUnlockToImage(const vk_staging_region_t* region, VkBufferImageCopy* dest_region, VkImageLayout layout, VkImage dest) { +void R_VkStagingUnlockToBuffer(staging_handle_t handle, VkBuffer dest, size_t dest_offset) { staging_alloc_t *alloc; - ASSERT(region->internal_id_ >= 0 && region->internal_id_ < g_staging.num_allocs); - ASSERT(g_staging.allocs[region->internal_id_].dest_type == DestNone); + ASSERT(handle >= 0 && handle < g_staging.num_allocs); + ASSERT(g_staging.allocs[handle].dest_type == DestNone); - alloc = g_staging.allocs + region->internal_id_; + alloc = g_staging.allocs + handle; + alloc->dest_type = DestBuffer; + + alloc->buffer.buffer = dest; + alloc->buffer.offset = dest_offset; +} + +void R_VkStagingUnlockToImage(staging_handle_t handle, VkBufferImageCopy* dest_region, VkImageLayout layout, VkImage dest) { + staging_alloc_t *alloc; + ASSERT(handle >= 0 && handle < g_staging.num_allocs); + ASSERT(g_staging.allocs[handle].dest_type == DestNone); + + alloc = g_staging.allocs + handle; alloc->dest_type = DestImage; alloc->image.layout = layout; alloc->image.image = dest; @@ -78,41 +83,29 @@ void R_VkStagingUnlockToImage(const vk_staging_region_t* region, VkBufferImageCo alloc->image.region.bufferOffset += alloc->offset; } -static void copyImage(VkCommandBuffer cmdbuf, const staging_alloc_t *alloc) { - vkCmdCopyBufferToImage(cmdbuf, g_staging.buffer.buffer, alloc->image.image, alloc->image.layout, 1, &alloc->image.region); -} - void R_VkStagingCommit(VkCommandBuffer cmdbuf) { - for ( int i = 0; i < g_staging.num_allocs; i++ ) { + for ( int i = g_staging.num_committed; i < g_staging.num_allocs; i++ ) { staging_alloc_t *const alloc = g_staging.allocs + i; ASSERT(alloc->dest_type != DestNone); switch (alloc->dest_type) { case DestImage: - copyImage(cmdbuf, alloc); + vkCmdCopyBufferToImage(cmdbuf, g_staging.buffer.buffer, alloc->image.image, alloc->image.layout, 1, &alloc->image.region); break; case DestBuffer: - ASSERT(!"staging dest buffer is not implemented"); + // TODO coalesce staging regions for the same dest buffer + gEngine.Con_Printf("vkCmdCopyBuffer %d src_offset=%d dst_offset=%d size=%d\n", i, alloc->offset, alloc->buffer.offset, alloc->size); + vkCmdCopyBuffer(cmdbuf, g_staging.buffer.buffer, alloc->buffer.buffer, 1, &(VkBufferCopy){alloc->offset, alloc->buffer.offset, alloc->size}); break; } alloc->dest_type = DestNone; - -#if 0 - // TODO coalesce staging regions for the same dest buffer - - const VkBufferCopy copy = { - .srcOffset = g_staging.allocs[i].offset, - .dstOffset = g_staging.allocs[i].dest_offset, - .size = g_staging.allocs[i].size - }; - vkCmdCopyBuffer(cmdbuf, g_staging.buffer.buffer, g_staging.allocs[i].dest, 1, ©); - - // TODO decide whether this needs to do anything with barriers - // here we can only collect dirty regions for each dest buffer -#endif } - g_staging.num_allocs = 0; + g_staging.num_committed = g_staging.num_allocs; +} + +void R_VKStagingMarkEmpty_FIXME(void) { + g_staging.num_committed = g_staging.num_allocs = 0; } void R_VkStagingFlushSync(void) { @@ -136,6 +129,7 @@ void R_VkStagingFlushSync(void) { XVK_CHECK(vkBeginCommandBuffer(cmdbuf, &beginfo)); R_VkStagingCommit(cmdbuf); + R_VKStagingMarkEmpty_FIXME(); XVK_CHECK(vkEndCommandBuffer(cmdbuf)); XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, VK_NULL_HANDLE)); XVK_CHECK(vkQueueWaitIdle(vk_core.queue)); diff --git a/ref_vk/vk_staging.h b/ref_vk/vk_staging.h index ccd27b40..6922df84 100644 --- a/ref_vk/vk_staging.h +++ b/ref_vk/vk_staging.h @@ -7,17 +7,22 @@ void R_VkStagingShutdown(void); //void *R_VkStagingAlloc(size_t size, VkBuffer dest, size_t dest_offset); +typedef int staging_handle_t; + typedef struct { void *ptr; size_t size; - int internal_id_; + staging_handle_t handle; } vk_staging_region_t; -vk_staging_region_t R_VkStagingLock(size_t size); -void R_VkStagingUnlockToBuffer(const vk_staging_region_t* region, VkBuffer dest, size_t dest_offset); -void R_VkStagingUnlockToImage(const vk_staging_region_t* region, VkBufferImageCopy* dest_region, VkImageLayout layout, VkImage dest); +vk_staging_region_t R_VkStagingLock(uint32_t size, uint32_t alignment); +void R_VkStagingUnlockToBuffer(staging_handle_t handle, VkBuffer dest, size_t dest_offset); +void R_VkStagingUnlockToImage(staging_handle_t handle, VkBufferImageCopy* dest_region, VkImageLayout layout, VkImage dest); void R_VkStagingCommit(VkCommandBuffer cmdbuf); +// FIXME Remove this with proper staging +void R_VKStagingMarkEmpty_FIXME(void); + // Force commit synchronously void R_VkStagingFlushSync(void); diff --git a/ref_vk/vk_studio.c b/ref_vk/vk_studio.c index af11f0a0..1b3dd094 100644 --- a/ref_vk/vk_studio.c +++ b/ref_vk/vk_studio.c @@ -1902,11 +1902,11 @@ static void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float float *lv; int i; int num_vertices = 0, num_indices = 0; - xvk_render_buffer_allocation_t vertex_buffer, index_buffer; vk_vertex_t *dst_vtx; uint16_t *dst_idx; uint32_t vertex_offset = 0, index_offset = 0; short* const ptricmds_initial = ptricmds; + r_geometry_buffer_lock_t buffer; // Compute counts of vertices and indices while(( i = *( ptricmds++ ))) @@ -1923,17 +1923,13 @@ static void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float ASSERT(num_indices > 0); // Get buffer region for vertices and indices - vertex_buffer = XVK_RenderBufferAllocAndLock( sizeof(vk_vertex_t), num_vertices ); - index_buffer = XVK_RenderBufferAllocAndLock( sizeof(uint16_t), num_indices ); - if (vertex_buffer.ptr == NULL || index_buffer.ptr == NULL) - { - // TODO should we free one of the above if it still succeeded? - gEngine.Con_Printf(S_ERROR "Ran out of buffer space\n"); + if (!R_GeometryBufferAllocAndLock( &buffer, num_vertices, num_indices )) { + gEngine.Con_Printf(S_ERROR "Cannot allocate geometry for studio model\n"); return; } - dst_vtx = vertex_buffer.ptr; - dst_idx = index_buffer.ptr; + dst_vtx = buffer.vertices.ptr; + dst_idx = buffer.indices.ptr; // Restore ptricmds and upload vertices ptricmds = ptricmds_initial; @@ -1945,7 +1941,7 @@ static void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float for(int j = 0; j < vertices ; ++j, ++dst_vtx, ptricmds += 4 ) { - ASSERT((((vk_vertex_t*)vertex_buffer.ptr) + num_vertices) > dst_vtx); + ASSERT((((vk_vertex_t*)buffer.vertices.ptr) + num_vertices) > dst_vtx); *dst_vtx = (vk_vertex_t){0}; VectorCopy(g_studio.verts[ptricmds[0]], dst_vtx->pos); @@ -2004,9 +2000,7 @@ static void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float ASSERT(index_offset == num_indices); ASSERT(vertex_offset == num_vertices); - XVK_RenderBufferUnlock( index_buffer.buffer ); - XVK_RenderBufferUnlock( vertex_buffer.buffer ); - + R_GeometryBufferUnlock( &buffer ); // Render { @@ -2015,10 +2009,10 @@ static void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float .texture = texture, .material = FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ) ? kXVkMaterialChrome : kXVkMaterialRegular, - .vertex_offset = vertex_buffer.buffer.unit.offset, + .vertex_offset = buffer.vertices.unit_offset, .max_vertex = num_vertices, - .index_offset = index_buffer.buffer.unit.offset, + .index_offset = buffer.indices.unit_offset, .element_count = num_indices, }; diff --git a/ref_vk/vk_textures.c b/ref_vk/vk_textures.c index 042260af..d55c0a4d 100644 --- a/ref_vk/vk_textures.c +++ b/ref_vk/vk_textures.c @@ -600,7 +600,9 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, .depth = 1, }; - vk_staging_region_t staging = R_VkStagingLock(mip_size); + const uint32_t texel_block_size = 4; // TODO compressed might be different + + vk_staging_region_t staging = R_VkStagingLock(mip_size, texel_block_size); ASSERT(staging.ptr); memcpy(staging.ptr, buf, mip_size); @@ -610,11 +612,12 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, BuildMipMap( buf, width, height, 1, tex->flags ); } - R_VkStagingUnlockToImage(&staging, ®ion, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, tex->vk.image.image); + R_VkStagingUnlockToImage(staging.handle, ®ion, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, tex->vk.image.image); } } R_VkStagingCommit(cmdbuf); + R_VKStagingMarkEmpty_FIXME(); // 5.2 image:layout:DST -> image:layout:SAMPLED // 5.2.1 transitionToLayout(DST -> SHADER_READ_ONLY) From fed12884dcbcd4649c602d9c966fc374a6a7f703 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Wed, 4 May 2022 09:55:13 -0700 Subject: [PATCH 215/548] vk: ne sri v log --- ref_vk/vk_staging.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_vk/vk_staging.c b/ref_vk/vk_staging.c index 01c970cf..80582b26 100644 --- a/ref_vk/vk_staging.c +++ b/ref_vk/vk_staging.c @@ -93,7 +93,7 @@ void R_VkStagingCommit(VkCommandBuffer cmdbuf) { break; case DestBuffer: // TODO coalesce staging regions for the same dest buffer - gEngine.Con_Printf("vkCmdCopyBuffer %d src_offset=%d dst_offset=%d size=%d\n", i, alloc->offset, alloc->buffer.offset, alloc->size); + //gEngine.Con_Printf("vkCmdCopyBuffer %d src_offset=%d dst_offset=%d size=%d\n", i, alloc->offset, alloc->buffer.offset, alloc->size); vkCmdCopyBuffer(cmdbuf, g_staging.buffer.buffer, alloc->buffer.buffer, 1, &(VkBufferCopy){alloc->offset, alloc->buffer.offset, alloc->size}); break; } From 771c359ed0c884ebb27c7a3f31d2f0d9e5d25f67 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 5 May 2022 04:04:24 +0300 Subject: [PATCH 216/548] engine: common: make blue-shift map detect more robust --- engine/common/mod_bmodel.c | 44 ++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/engine/common/mod_bmodel.c b/engine/common/mod_bmodel.c index 6edaa606..842a53e8 100644 --- a/engine/common/mod_bmodel.c +++ b/engine/common/mod_bmodel.c @@ -2736,6 +2736,42 @@ static void Mod_LoadLighting( dbspmodel_t *bmod ) } } +/* +================= +Mod_LumpLooksLikePlanes + +================= +*/ +static qboolean Mod_LumpLooksLikePlanes( const byte *in, dlump_t *lump, qboolean fast ) +{ + int numplanes, i; + const dplane_t *planes; + + if( lump->filelen < sizeof( dplane_t ) && + lump->filelen % sizeof( dplane_t ) != 0 ) + return false; + + if( fast ) + return true; + + numplanes = lump->filelen / sizeof( dplane_t ); + planes = (const dplane_t*)(in + lump->fileofs); + + for( i = 0; i < numplanes; i++ ) + { + if( IS_NAN( planes[i].dist ) ) + return false; + + if( VectorIsNAN( planes[i].normal )) + return false; + + if( planes[i].type > 6 ) + return false; + } + + return true; +} + /* ================= Mod_LoadBmodelLumps @@ -2785,8 +2821,8 @@ qboolean Mod_LoadBmodelLumps( const byte *mod_base, qboolean isworld ) if( header->version == HLBSP_VERSION ) { // only relevant for half-life maps - if( header->lumps[LUMP_ENTITIES].fileofs <= 1024 && - (header->lumps[LUMP_ENTITIES].filelen % sizeof( dplane_t )) == 0 ) + if( !Mod_LumpLooksLikePlanes( mod_base, &header->lumps[LUMP_PLANES], false ) && + Mod_LumpLooksLikePlanes( mod_base, &header->lumps[LUMP_ENTITIES], false )) { // blue-shift swapped lumps srclumps[0].lumpnumber = LUMP_PLANES; @@ -2913,8 +2949,8 @@ qboolean Mod_TestBmodelLumps( const char *name, const byte *mod_base, qboolean s if( header->version == HLBSP_VERSION ) { // only relevant for half-life maps - if( header->lumps[LUMP_ENTITIES].fileofs <= 1024 && - (header->lumps[LUMP_ENTITIES].filelen % sizeof( dplane_t )) == 0 ) + if( Mod_LumpLooksLikePlanes( mod_base, &header->lumps[LUMP_ENTITIES], true ) && + !Mod_LumpLooksLikePlanes( mod_base, &header->lumps[LUMP_PLANES], true )) { // blue-shift swapped lumps srclumps[0].lumpnumber = LUMP_PLANES; From 81b01ac5613419882fb99f85579c3de9b14ab96f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 5 May 2022 15:14:48 +0300 Subject: [PATCH 217/548] engine: common: remove NaN check, it's mostly useless on text data --- engine/common/mod_bmodel.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/engine/common/mod_bmodel.c b/engine/common/mod_bmodel.c index 842a53e8..294d0dd8 100644 --- a/engine/common/mod_bmodel.c +++ b/engine/common/mod_bmodel.c @@ -2759,13 +2759,8 @@ static qboolean Mod_LumpLooksLikePlanes( const byte *in, dlump_t *lump, qboolean for( i = 0; i < numplanes; i++ ) { - if( IS_NAN( planes[i].dist ) ) - return false; - - if( VectorIsNAN( planes[i].normal )) - return false; - - if( planes[i].type > 6 ) + // planes can only be from 0 to 5: PLANE_X, Y, Z and PLANE_ANYX, Y and Z + if( planes[i].type < 0 || planes[i].type > 5 ) return false; } From 4fbd96d20116fd420aec3e2586e252636c546e7e Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 5 May 2022 05:03:47 +0300 Subject: [PATCH 218/548] engine: common: filesystem: speed up directory existense check on POSIX by using stat() instead of opendir() --- engine/common/filesystem.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index 01bd4676..0438b153 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -2477,7 +2477,7 @@ qboolean FS_SysFileExists( const char *path, qboolean caseinsensitive ) close( desc ); return true; -#else +#elif XASH_POSIX int ret; struct stat buf; @@ -2495,6 +2495,8 @@ qboolean FS_SysFileExists( const char *path, qboolean caseinsensitive ) return false; return S_ISREG( buf.st_mode ); +#else +#error #endif } @@ -2529,23 +2531,18 @@ qboolean FS_SysFolderExists( const char *path ) DWORD dwFlags = GetFileAttributes( path ); return ( dwFlags != -1 ) && ( dwFlags & FILE_ATTRIBUTE_DIRECTORY ); -#else - DIR *dir = opendir( path ); +#elif XASH_POSIX + struct stat buf; - if( dir ) - { - closedir( dir ); - return true; - } - else if( (errno == ENOENT) || (errno == ENOTDIR) ) + if( stat( path, &buf ) < 0 ) { + Con_Reportf( S_ERROR "FS_SysFolderExists: problem while opening dir: %s\n", strerror( errno )); return false; } - else - { - Con_Reportf( S_ERROR "FS_SysFolderExists: problem while opening dir: %s\n", strerror( errno ) ); - return false; - } + + return S_ISDIR( buf.st_mode ); +#else +#error #endif } From cb427d08399486b27fe26c4346d5051c540e062a Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 14 May 2022 13:36:33 -0700 Subject: [PATCH 219/548] vk: ring-based dynamic geometry buf alloc - Clear split between static and dynamic geometry within the same buffer - Store previous frame geometry data longer - Use simpler ring buffer for allocation - Add waf unit tests for alolcator Known issues: - ray tracing still glitches a lot --- ref_vk/TODO.md | 21 ++++++ ref_vk/alolcator.c | 173 ++++++++++++++++++++++++++++++++++++++++++- ref_vk/alolcator.h | 18 +++++ ref_vk/vk_beams.c | 2 +- ref_vk/vk_brush.c | 4 +- ref_vk/vk_const.h | 4 - ref_vk/vk_framectl.c | 6 -- ref_vk/vk_render.c | 106 ++++++++++++++++---------- ref_vk/vk_render.h | 15 ++-- ref_vk/vk_scene.c | 1 - ref_vk/vk_sprite.c | 2 +- ref_vk/vk_studio.c | 2 +- ref_vk/wscript | 19 ++++- 13 files changed, 306 insertions(+), 67 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index 3da5e5a2..47e5f14b 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,3 +1,24 @@ +# Parallel frames +- [ ] allocate for N frames: + - [x] geometries + - [ ] rt models + - [ ] lights + +// O. 1 buffer i bardak v nyom +// - [SSSSAAS.SBAS....] +// - can become extremely fragmented + +// I. 2 buffer + bit indirection +// - lives longer than 2 frames [SSS.SSS..SS.....] +// - dynamic [AAAAA->.....<-BBBBBB] +// - high bits in shader point to buffer + +// II. 1 buffer bi-directional +// - [SSS.SS..S...|AAAABBBB->...] +// ^ - long-living "static" stuff +// ^ - dynamic ring buffer +// - [SSS.SS..S.|.....<-AAAABBBB] (dynamic split) + # Passes - [ ] better simple sampling - [x] all triangles diff --git a/ref_vk/alolcator.c b/ref_vk/alolcator.c index 27c6ee14..b7ed036f 100644 --- a/ref_vk/alolcator.c +++ b/ref_vk/alolcator.c @@ -2,8 +2,6 @@ #include // malloc/free #include // memcpy -//#include "vk_common.h" - #define MALLOC malloc #define FREE free @@ -244,7 +242,60 @@ void aloPoolFree(struct alo_pool_s *pool, int index) { } } +void aloRingInit(alo_ring_t* ring, uint32_t size) { + ring->size = size; + ring->head = 0; + ring->tail = size; +} + +// Marks everything up-to-pos as free (expects up-to-pos to be valid) +void aloRingFree(alo_ring_t* ring, uint32_t up_to_pos) { + ASSERT(up_to_pos < ring->size); + // FIXME assert that up_to_pos is valid and within allocated region + if (up_to_pos == ring->head) { + ring->head = 0; + ring->tail = ring->size; + } else + ring->tail = up_to_pos; +} + +// Allocates a new aligned region and returns offset to it (AllocFailed if allocation failed) +uint32_t aloRingAlloc(alo_ring_t* ring, uint32_t size, uint32_t alignment) { + const uint32_t align = (alignment > 0) ? alignment : 1; + const uint32_t pos = ALIGN_UP(ring->head, alignment); + + ASSERT(size != 0); + + // [XXX.....XXX] + // h t + if (ring->head <= ring->tail) { + if (pos + size > ring->tail) + return ALO_ALLOC_FAILED; + + ring->head = pos + size; + return pos; + } + + // [...XXXXXX...] + // t h + // 2 1 + + // 1. Check if we have enough space immediately in front of head + if (pos + size <= ring->size) { + ring->head = (pos + size) % ring->size; + return pos; + } + + // 2. wrap around + if (size > ring->tail) + return ALO_ALLOC_FAILED; + + ring->head = size; + return 0; +} + #if defined(ALOLCATOR_TEST) +#include uint32_t rand_pcg32(uint32_t max) { if (!max) return 0; #define PCG32_INITIALIZER { 0x853c49e6748fea9bULL, 0xda3e39cb94b95bdbULL } @@ -426,11 +477,129 @@ int test(void) { return 0; } +#define REQUIRE_EQUAL_UINT32(a, b) \ + do { \ + const uint32_t va = (a), vb = (b); \ + if (va != vb) { \ + fprintf(stderr, "%s:%d (%s == %s) FAILED: %u != %u\n", \ + __FILE__, __LINE__, \ + #a, #b, \ + va, vb); \ + } \ + ASSERT(va == vb); \ + } while(0) + +static void dumpRing(int line, const alo_ring_t* ring) { + fprintf(stderr, "%d ", line); + if (ring->tail < ring->head) { + fprintf(stderr, "t=%03d h=%03d [", ring->tail, ring->head); + for (int i = 0; i < (int)ring->tail; ++i) fputc('.', stderr); + fputc('T', stderr); + for (int i = (int)ring->tail + 1; i < (int)ring->head; ++i) fputc('#', stderr); + fputc('h', stderr); + for (int i = (int)ring->head + 1; i < (int)ring->size; ++i) fputc('.', stderr); + } else { + fprintf(stderr, "h=%03d t=%03d [", ring->head, ring->tail); + for (int i = 0; i < (int)ring->head; ++i) fputc('#', stderr); + fputc('h', stderr); + for (int i = (int)ring->head + 1; i < (int)ring->tail; ++i) fputc('.', stderr); + fputc('T', stderr); + for (int i = (int)ring->tail + 1; i < (int)ring->size; ++i) fputc('#', stderr); + } + fputs("]\n", stderr); +} + +#define TEST_ALLOC(name, size, expected, alignment) \ + const uint32_t name = aloRingAlloc(&ring, size, alignment); \ + dumpRing(__LINE__, &ring); \ + REQUIRE_EQUAL_UINT32(name, expected) + +#define TEST_FREE(to) \ + aloRingFree(&ring, to); \ + dumpRing(__LINE__, &ring) + +void testRing(void) { + alo_ring_t ring; + aloRingInit(&ring, 128); + + fprintf(stderr, "%s\n", __FUNCTION__); + + TEST_ALLOC(p0, 64, 0, 1); + TEST_ALLOC(p1, 64, 64, 1); + TEST_ALLOC(p2, 64, ALO_ALLOC_FAILED, 1); + TEST_FREE(p1); + TEST_ALLOC(p3, 32, 0, 1); + TEST_FREE(p3); + TEST_ALLOC(p4, 64, 32, 1); + TEST_ALLOC(p5, 64, ALO_ALLOC_FAILED, 1); + TEST_ALLOC(p6, 16, 96, 1); + TEST_ALLOC(p7, 32, ALO_ALLOC_FAILED, 1); + TEST_FREE(p4); + TEST_ALLOC(p8, 32, 0, 1); +} + +void stressTestRing(void) { + #define BUFSIZE 128 + #define NUM_ALLOCS 16 + const int rounds = 10000; + struct { uint32_t pos, size, val; } allocs[NUM_ALLOCS]; + int count = 0, wr = 0, rd = 0; + uint32_t buf[BUFSIZE]; + + alo_ring_t ring; + aloRingInit(&ring, BUFSIZE); + + for (int i = 0; i < NUM_ALLOCS; ++i) + allocs[i].pos = ALO_ALLOC_FAILED; + + fprintf(stderr, "%s\n", __FUNCTION__); + + for (int i = 0; i < rounds; ++i) { + if (count < NUM_ALLOCS) { + const uint32_t align = 1 << rand_pcg32(5); + const uint32_t size = 1 + rand_pcg32(BUFSIZE / 5); + const uint32_t pos = aloRingAlloc(&ring, size, align); + fprintf(stderr, "ALLOC(%d;%d) size=%d align=%d => pos=%d\n", wr, count, size, align, pos); + + if (pos != ALO_ALLOC_FAILED) { + dumpRing(__LINE__, &ring); + allocs[wr].pos = pos; + allocs[wr].size = size; + allocs[wr].val = rand_pcg32(0xFFFFFFFFul); + for (int i = 0; i < (int)size; ++i) + buf[pos + i] = allocs[wr].val; + wr = (wr + 1) % NUM_ALLOCS; + count++; + } else { + ASSERT(count); + } + } + + if (rand_pcg32(5) == 0) { + int to_remove = rand_pcg32(5) + 1; + while (to_remove-- > 0 && count > 0) { + ASSERT(allocs[rd].pos != ALO_ALLOC_FAILED); + fprintf(stderr, "FREE(%d;%d) pos=%d(%d) count=%d to_remove=%d\n", rd, count, allocs[rd].pos, allocs[rd].size, count, to_remove); + for (int i = 0; i < (int)allocs[rd].size; ++i) + REQUIRE_EQUAL_UINT32(buf[allocs[rd].pos + i], allocs[rd].val); + aloRingFree(&ring, allocs[rd].pos); + dumpRing(__LINE__, &ring); + allocs[rd].pos = ALO_ALLOC_FAILED; + rd = (rd + 1) % NUM_ALLOCS; + --count; + } + } + } +} + int main(void) { test(); ASSERT(1000 == testRandom(1000, 1000000, 1, 0)); testRandom(1000, 1000000, 32, 999); + + testRing(); + stressTestRing(); return 0; } #endif diff --git a/ref_vk/alolcator.h b/ref_vk/alolcator.h index a78680b3..864f3c0a 100644 --- a/ref_vk/alolcator.h +++ b/ref_vk/alolcator.h @@ -17,3 +17,21 @@ typedef struct { alo_block_t aloPoolAllocate(struct alo_pool_s*, alo_size_t size, alo_size_t alignment); void aloPoolFree(struct alo_pool_s *pool, int index); + +// <- size -> +// [.....|AAAAAAAAAAAAAAA|......] +// ^ -- tail ^ -- head +typedef struct { + uint32_t size, head, tail; +} alo_ring_t; + +#define ALO_ALLOC_FAILED 0xffffffffu + +// Marks the entire buffer as free +void aloRingInit(alo_ring_t* ring, uint32_t size); + +// Allocates a new aligned region and returns offset to it (AllocFailed if allocation failed) +uint32_t aloRingAlloc(alo_ring_t* ring, uint32_t size, uint32_t alignment); + +// Marks everything up-to-pos as free (expects up-to-pos to be valid) +void aloRingFree(alo_ring_t* ring, uint32_t up_to_pos); diff --git a/ref_vk/vk_beams.c b/ref_vk/vk_beams.c index e0857899..1072e78b 100644 --- a/ref_vk/vk_beams.c +++ b/ref_vk/vk_beams.c @@ -223,7 +223,7 @@ static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, f total_indices = (total_vertices - 2) * 3; // STRIP unrolled into LIST (TODO get rid of this) ASSERT(total_vertices < UINT16_MAX ); - if (!R_GeometryBufferAllocAndLock( &buffer, total_vertices, total_indices )) { + if (!R_GeometryBufferAllocAndLock( &buffer, total_vertices, total_indices, LifetimeSingleFrame )) { gEngine.Con_Printf(S_ERROR "Cannot allocate geometry for beam\n"); return; } diff --git a/ref_vk/vk_brush.c b/ref_vk/vk_brush.c index 5348dc4f..6934aca2 100644 --- a/ref_vk/vk_brush.c +++ b/ref_vk/vk_brush.c @@ -115,7 +115,7 @@ static void EmitWaterPolys( const cl_entity_t *ent, const msurface_t *warp, qboo num_indices += triangles * 3; } - if (!R_GeometryBufferAllocAndLock( &buffer, num_vertices, num_indices )) { + if (!R_GeometryBufferAllocAndLock( &buffer, num_vertices, num_indices, LifetimeSingleFrame )) { gEngine.Con_Printf(S_ERROR "Cannot allocate geometry for %s\n", ent->model->name ); return; } @@ -485,7 +485,7 @@ static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { uint32_t index_offset = 0; r_geometry_buffer_lock_t buffer; - if (!R_GeometryBufferAllocAndLock( &buffer, sizes.num_vertices, sizes.num_indices )) { + if (!R_GeometryBufferAllocAndLock( &buffer, sizes.num_vertices, sizes.num_indices, LifetimeLong )) { gEngine.Con_Printf(S_ERROR "Cannot allocate geometry for %s\n", mod->name ); return false; } diff --git a/ref_vk/vk_const.h b/ref_vk/vk_const.h index ed1e27df..d2a23acc 100644 --- a/ref_vk/vk_const.h +++ b/ref_vk/vk_const.h @@ -5,10 +5,6 @@ #define MAX_TEXTURES 4096 -// TODO count these properly -#define MAX_BUFFER_VERTICES (512 * 1024) -#define MAX_BUFFER_INDICES (MAX_BUFFER_VERTICES * 3) - // indexed by uint8_t #define MAX_SURFACE_LIGHTS 256 // indexed by uint8_t diff --git a/ref_vk/vk_framectl.c b/ref_vk/vk_framectl.c index 3967e8d3..154d7fd6 100644 --- a/ref_vk/vk_framectl.c +++ b/ref_vk/vk_framectl.c @@ -328,12 +328,6 @@ static void submit( VkCommandBuffer cmdbuf, qboolean wait ) { } g_frame.current.phase = Phase_Idle; } - - // TODO better sync implies multiple frames in flight, which means that we must - // retain temporary (SingleFrame) buffer contents for longer, until all users are done. - // (this probably means that we should really have some kind of refcount going on...) - // For now we can just erase these buffers now because of sync with fence - XVK_RenderBufferFrameClear(); } inline static VkCommandBuffer currentCommandBuffer( void ) { diff --git a/ref_vk/vk_render.c b/ref_vk/vk_render.c index 741da947..3b685670 100644 --- a/ref_vk/vk_render.c +++ b/ref_vk/vk_render.c @@ -10,7 +10,8 @@ #include "vk_math.h" #include "vk_rtx.h" #include "vk_descriptor.h" -#include "vk_framectl.h" // FIXME +#include "vk_framectl.h" // FIXME needed for dynamic models cmdbuf +#include "alolcator.h" #include "eiface.h" #include "xash3d_mathlib.h" @@ -20,27 +21,40 @@ #define MAX_UNIFORM_SLOTS (MAX_SCENE_ENTITIES * 2 /* solid + trans */ + 1) +#define MAX_BUFFER_VERTICES_STATIC (128 * 1024) +#define MAX_BUFFER_INDICES_STATIC (MAX_BUFFER_VERTICES_STATIC * 3) +#define GEOMETRY_BUFFER_STATIC_SIZE ALIGN_UP(MAX_BUFFER_VERTICES_STATIC * sizeof(vk_vertex_t) + MAX_BUFFER_INDICES_STATIC * sizeof(uint16_t), sizeof(vk_vertex_t)) + +#define MAX_BUFFER_VERTICES_DYNAMIC (128 * 1024) +#define MAX_BUFFER_INDICES_DYNAMIC (MAX_BUFFER_VERTICES_DYNAMIC * 3) +#define GEOMETRY_BUFFER_DYNAMIC_SIZE ALIGN_UP(MAX_BUFFER_VERTICES_DYNAMIC * sizeof(vk_vertex_t) + MAX_BUFFER_INDICES_DYNAMIC * sizeof(uint16_t), sizeof(vk_vertex_t)) + +#define GEOMETRY_BUFFER_SIZE (GEOMETRY_BUFFER_STATIC_SIZE + GEOMETRY_BUFFER_DYNAMIC_SIZE) + typedef struct { matrix4x4 mvp; vec4_t color; } uniform_data_t; -// TODO estimate -#define MAX_ALLOCS 1024 - static struct { VkPipelineLayout pipeline_layout; VkPipeline pipelines[kRenderTransAdd + 1]; - vk_buffer_t buffer; - vk_ring_buffer_t buffer_alloc_ring; - vk_buffer_t uniform_buffer; uint32_t ubo_align; float fov_angle_y; } g_render; +struct { + vk_buffer_t buffer; + alo_ring_t static_ring; + alo_ring_t dynamic_ring; + + int frame_index; + uint32_t dynamic_offsets[MAX_CONCURRENT_FRAMES]; +} g_geom; + static qboolean createPipelines( void ) { /* VkPushConstantRange push_const = { */ @@ -225,10 +239,7 @@ typedef struct { } light[MAX_DLIGHTS]; } vk_ubo_lights_t; -qboolean VK_RenderInit( void ) -{ - const uint32_t vertex_buffer_size = MAX_BUFFER_VERTICES * sizeof(vk_vertex_t); - const uint32_t index_buffer_size = MAX_BUFFER_INDICES * sizeof(uint16_t); +qboolean VK_RenderInit( void ) { uint32_t uniform_unit_size; g_render.ubo_align = Q_max(4, vk_core.physical_device.properties.limits.minUniformBufferOffsetAlignment); @@ -236,7 +247,7 @@ qboolean VK_RenderInit( void ) // TODO device memory and friends (e.g. handle mobile memory ...) - if (!VK_BufferCreate("geometry buffer", &g_render.buffer, vertex_buffer_size + index_buffer_size, + if (!VK_BufferCreate("geometry buffer", &g_geom.buffer, GEOMETRY_BUFFER_SIZE, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | (vk_core.rtx ? VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR : 0), (vk_core.rtx ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : 0))) // TODO staging buffer? return false; @@ -280,7 +291,7 @@ qboolean VK_RenderInit( void ) if (!createPipelines()) return false; - g_render.buffer_alloc_ring.size = g_render.buffer.size; + XVK_RenderBufferMapClear(); return true; } @@ -291,22 +302,31 @@ void VK_RenderShutdown( void ) vkDestroyPipeline(vk_core.device, g_render.pipelines[i], NULL); vkDestroyPipelineLayout( vk_core.device, g_render.pipeline_layout, NULL ); - VK_BufferDestroy( &g_render.buffer ); + VK_BufferDestroy( &g_geom.buffer ); VK_BufferDestroy( &g_render.uniform_buffer ); } -qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int vertex_count, int index_count ) { +qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int vertex_count, int index_count, r_geometry_lifetime_t lifetime ) { const uint32_t vertices_size = vertex_count * sizeof(vk_vertex_t); const uint32_t indices_size = index_count * sizeof(uint16_t); const uint32_t total_size = vertices_size + indices_size; + alo_ring_t * const ring = (lifetime != LifetimeSingleFrame) ? &g_geom.static_ring : &g_geom.dynamic_ring; - const uint32_t offset = VK_RingBuffer_Alloc(&g_render.buffer_alloc_ring, total_size, sizeof(vk_vertex_t)); - if (offset == AllocFailed) { - gEngine.Con_Printf(S_ERROR "Cannot allocate geometry buffer for %d vertices (%d bytes) and %d indices (%d bytes), only %u bytes left", - vertex_count, vertices_size, index_count, indices_size, g_render.buffer_alloc_ring.free); + const uint32_t alloc_offset = aloRingAlloc(ring, total_size, sizeof(vk_vertex_t)); + const uint32_t offset = alloc_offset + ((lifetime == LifetimeSingleFrame) ? GEOMETRY_BUFFER_STATIC_SIZE : 0); + if (alloc_offset == ALO_ALLOC_FAILED) { + gEngine.Con_Printf(S_ERROR "Cannot allocate %s geometry buffer for %d vertices (%d bytes) and %d indices (%d bytes)\n", + lifetime == LifetimeSingleFrame ? "dynamic" : "static", + vertex_count, vertices_size, index_count, indices_size); return false; } + // Store first dynamic allocation this frame + if (lifetime == LifetimeSingleFrame && g_geom.dynamic_offsets[g_geom.frame_index] == ALO_ALLOC_FAILED) { + gEngine.Con_Reportf("FRAME=%d FIRST_OFFSET=%d\n", g_geom.frame_index, alloc_offset); + g_geom.dynamic_offsets[g_geom.frame_index] = alloc_offset; + } + { const uint32_t vertices_offset = offset / sizeof(vk_vertex_t); const uint32_t indices_offset = (offset + vertices_size) / sizeof(uint16_t); @@ -339,26 +359,22 @@ qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int verte } void R_GeometryBufferUnlock( const r_geometry_buffer_lock_t *lock ) { - R_VkStagingUnlockToBuffer(lock->impl_.staging_handle, g_render.buffer.buffer, lock->impl_.offset); -} - -void XVK_RenderBufferMapFreeze( void ) { - VK_RingBuffer_Fix(&g_render.buffer_alloc_ring); + R_VkStagingUnlockToBuffer(lock->impl_.staging_handle, g_geom.buffer.buffer, lock->impl_.offset); } void XVK_RenderBufferMapClear( void ) { - VK_RingBuffer_Clear(&g_render.buffer_alloc_ring); -} - -void XVK_RenderBufferFrameClear( /*int frame_id*/void ) { - VK_RingBuffer_ClearFrame(&g_render.buffer_alloc_ring); + aloRingInit(&g_geom.static_ring, GEOMETRY_BUFFER_STATIC_SIZE); + aloRingInit(&g_geom.dynamic_ring, GEOMETRY_BUFFER_DYNAMIC_SIZE); + for (int i = 0; i < COUNTOF(g_geom.dynamic_offsets); ++i) { + g_geom.dynamic_offsets[i] = ALO_ALLOC_FAILED; + } + g_geom.frame_index = 0; } void XVK_RenderBufferPrintStats( void ) { // TODO get alignment holes size gEngine.Con_Reportf("Buffer usage: %uKiB of (%uKiB)\n", - g_render.buffer_alloc_ring.permanent_size / 1024, - g_render.buffer.size / 1024); + g_geom.static_ring.head / 1024, g_geom.static_ring.size / 1024); } #define MAX_DRAW_COMMANDS 8192 // TODO estimate @@ -417,7 +433,7 @@ enum { }; void VK_RenderBegin( qboolean ray_tracing ) { - g_render_state.uniform_free_offset = 0; + g_render_state.uniform_free_offset = 0; // FIXME multiple frames in flight g_render_state.uniform_data_set_mask = UNIFORM_UNSET; g_render_state.current_ubo_offset = UINT32_MAX; @@ -427,6 +443,16 @@ void VK_RenderBegin( qboolean ray_tracing ) { g_render_state.num_draw_commands = 0; g_render_state.current_frame_is_ray_traced = ray_tracing; + { + const int new_frame = (g_geom.frame_index + 1) % COUNTOF(g_geom.dynamic_offsets); + if (g_geom.dynamic_offsets[new_frame] != ALO_ALLOC_FAILED) { + gEngine.Con_Reportf("FRAME=%d FREE_OFFSET=%d\n", g_geom.frame_index, g_geom.dynamic_offsets[new_frame]); + aloRingFree(&g_geom.dynamic_ring, g_geom.dynamic_offsets[new_frame]); + g_geom.dynamic_offsets[new_frame] = ALO_ALLOC_FAILED; + } + g_geom.frame_index = new_frame; + } + if (ray_tracing) VK_RayFrameBegin(); } @@ -609,8 +635,8 @@ void VK_RenderEnd( VkCommandBuffer cmdbuf ) { const VkDeviceSize offset = 0; - vkCmdBindVertexBuffers(cmdbuf, 0, 1, &g_render.buffer.buffer, &offset); - vkCmdBindIndexBuffer(cmdbuf, g_render.buffer.buffer, 0, VK_INDEX_TYPE_UINT16); + vkCmdBindVertexBuffers(cmdbuf, 0, 1, &g_geom.buffer.buffer, &offset); + vkCmdBindIndexBuffer(cmdbuf, g_geom.buffer.buffer, 0, VK_INDEX_TYPE_UINT16); } vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 3, 1, vk_desc.ubo_sets + 1, 1, &dlights_ubo_offset); @@ -690,7 +716,7 @@ void VK_RenderEndRTX( VkCommandBuffer cmdbuf, VkImageView img_dst_view, VkImage .view = &g_render_state.view, .geometry_data = { - .buffer = g_render.buffer.buffer, + .buffer = g_geom.buffer.buffer, .size = VK_WHOLE_SIZE, }, @@ -705,7 +731,7 @@ qboolean VK_RenderModelInit( VkCommandBuffer cmdbuf, vk_render_model_t *model ) if (vk_core.rtx && (g_render_state.current_frame_is_ray_traced || !model->dynamic)) { // TODO runtime rtx switch: ??? const vk_ray_model_init_t args = { - .buffer = g_render.buffer.buffer, + .buffer = g_geom.buffer.buffer, .model = model, }; R_VkStagingCommit(cmdbuf); @@ -713,14 +739,16 @@ qboolean VK_RenderModelInit( VkCommandBuffer cmdbuf, vk_render_model_t *model ) const VkBufferMemoryBarrier bmb[] = { { .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR, // FIXME - .buffer = g_render.buffer.buffer, + //.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR, // FIXME + .dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_SHADER_READ_BIT, // FIXME + .buffer = g_geom.buffer.buffer, .offset = 0, // FIXME .size = VK_WHOLE_SIZE, // FIXME } }; vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, + //VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, + VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } model->ray_model = VK_RayModelCreate(cmdbuf, args); diff --git a/ref_vk/vk_render.h b/ref_vk/vk_render.h index 156b9f16..4a446dbb 100644 --- a/ref_vk/vk_render.h +++ b/ref_vk/vk_render.h @@ -45,15 +45,17 @@ typedef struct { } impl_; } r_geometry_buffer_lock_t; -qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int vertex_count, int index_count ); +typedef enum { + LifetimeLong, + LifetimeSingleFrame +} r_geometry_lifetime_t; + +qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int vertex_count, int index_count, r_geometry_lifetime_t lifetime ); void R_GeometryBufferUnlock( const r_geometry_buffer_lock_t *lock ); //void R_VkGeometryBufferFree( int handle ); -void XVK_RenderBufferMapFreeze( void ); // Permanently freeze all allocations as map-permanent void XVK_RenderBufferMapClear( void ); // Free the entire buffer for a new map -void XVK_RenderBufferFrameClear( /*int frame_id*/void ); // mark data for frame with given id as free (essentially, just forward the ring buffer) - void XVK_RenderBufferPrintStats( void ); // Set UBO state for next VK_RenderScheduleDraw calls @@ -138,15 +140,10 @@ qboolean VK_RenderModelInit( VkCommandBuffer cmdbuf, vk_render_model_t* model ); void VK_RenderModelDestroy( vk_render_model_t* model ); void VK_RenderModelDraw( const cl_entity_t *ent, vk_render_model_t* model ); -void VK_RenderFrameBegin( void ); - void VK_RenderModelDynamicBegin( int render_mode, const char *debug_name_fmt, ... ); void VK_RenderModelDynamicAddGeometry( const vk_render_geometry_t *geom ); void VK_RenderModelDynamicCommit( void ); -void VK_RenderFrameEnd( VkCommandBuffer cmdbuf ); -void VK_RenderFrameEndRTX( VkCommandBuffer cmdbuf, VkImageView img_dst_view, VkImage img_dst, uint32_t w, uint32_t h ); - void VK_RenderDebugLabelBegin( const char *label ); void VK_RenderDebugLabelEnd( void ); diff --git a/ref_vk/vk_scene.c b/ref_vk/vk_scene.c index a3acff90..aa57591f 100644 --- a/ref_vk/vk_scene.c +++ b/ref_vk/vk_scene.c @@ -234,7 +234,6 @@ void R_NewMap( void ) { // TODO should we do something like VK_BrushEndLoad? VK_UploadLightmap(); - XVK_RenderBufferMapFreeze(); XVK_RenderBufferPrintStats(); if (vk_core.rtx) VK_RayMapLoadEnd(); diff --git a/ref_vk/vk_sprite.c b/ref_vk/vk_sprite.c index bf869f5a..9a95e7e4 100644 --- a/ref_vk/vk_sprite.c +++ b/ref_vk/vk_sprite.c @@ -652,7 +652,7 @@ static void R_DrawSpriteQuad( const char *debug_name, mspriteframe_t *frame, vec // Get buffer region for vertices and indices r_geometry_buffer_lock_t buffer; - if (!R_GeometryBufferAllocAndLock( &buffer, 4, 6 )) { + if (!R_GeometryBufferAllocAndLock( &buffer, 4, 6, LifetimeSingleFrame )) { gEngine.Con_Printf(S_ERROR "Cannot allocate geometry for sprite quad\n"); return; } diff --git a/ref_vk/vk_studio.c b/ref_vk/vk_studio.c index 1b3dd094..83423746 100644 --- a/ref_vk/vk_studio.c +++ b/ref_vk/vk_studio.c @@ -1923,7 +1923,7 @@ static void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float ASSERT(num_indices > 0); // Get buffer region for vertices and indices - if (!R_GeometryBufferAllocAndLock( &buffer, num_vertices, num_indices )) { + if (!R_GeometryBufferAllocAndLock( &buffer, num_vertices, num_indices, LifetimeSingleFrame )) { gEngine.Con_Printf(S_ERROR "Cannot allocate geometry for studio model\n"); return; } diff --git a/ref_vk/wscript b/ref_vk/wscript index 4005a914..272e06c9 100644 --- a/ref_vk/wscript +++ b/ref_vk/wscript @@ -65,6 +65,17 @@ def configure(conf): if '-Werror=declaration-after-statement' in conf.env.CFLAGS: conf.env.CFLAGS.remove('-Werror=declaration-after-statement') +def printTestSummary(bld): + results = getattr(bld, 'utest_results', []) + for (f, code, out, err) in results: + if code == 0: + Logs.pprint('GREEN', '%s: ok' % f) + continue + + Logs.pprint('RED', '%s: test failed' % f) + Logs.pprint('CYAN', out.decode('utf-8')) + Logs.pprint('CYAN', err.decode('utf-8')) + def build(bld): if not bld.env.VK: return @@ -126,4 +137,10 @@ def build(bld): bld.install_files(bld.env.LIBDIR, bld.path.ant_glob('data/**'), cwd=bld.path.find_dir('data/'), - relative_trick=True) \ No newline at end of file + relative_trick=True) + + bld.program(features='test', defines=['ALOLCATOR_TEST'],source='alolcator.c', target='alolcator') + bld.add_post_fun(printTestSummary) + + #from waflib.Tools import waf_unit_test + #bld.add_post_fun(waf_unit_test.summary) From 10ec32e2647f54517849ef7622c4988e962ac340 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 16 May 2022 05:40:34 +0300 Subject: [PATCH 220/548] waf: add Python 3.10 scan in batch script --- waf.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/waf.bat b/waf.bat index dac6143c..2d15819d 100644 --- a/waf.bat +++ b/waf.bat @@ -29,7 +29,7 @@ Setlocal EnableDelayedExpansion set PYTHON_DIR_OK=FALSE set REGPATH= -for %%i in (3.9 3.8 3.7 3.6 3.5 3.4 3.3 3.2 3.1 3.0 2.7 2.6 2.5) do ( +for %%i in (3.10 3.9 3.8 3.7 3.6 3.5 3.4 3.3 3.2 3.1 3.0 2.7 2.6 2.5) do ( for %%j in (HKCU HKLM) do ( for %%k in (SOFTWARE\Wow6432Node SOFTWARE) do ( for %%l in (Python\PythonCore IronPython) do ( From 51526948c014bbf40b595f0a5413c0ff934cfc59 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 20 May 2022 18:09:52 +0300 Subject: [PATCH 221/548] engine: server: make PEntityOfEntIndex bug-compatible with GoldSrc Add new undocumented GoldSrc eiface function, PEntityOfEntIndexAllEntities, a bug-free version of PEntityOfEntIndex Ref: https://github.com/ValveSoftware/halflife/issues/2272 --- engine/common/common.h | 1 - engine/eiface.h | 3 +++ engine/server/server.h | 1 - engine/server/sv_game.c | 49 ++++++++++++++++++++++++++--------------- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/engine/common/common.h b/engine/common/common.h index 3b6d3277..f96eda0d 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -756,7 +756,6 @@ void pfnDrawSetTextColor( float r, float g, float b ); void pfnDrawConsoleStringLen( const char *pText, int *length, int *height ); void *Cache_Check( poolhandle_t mempool, struct cache_user_s *c ); void COM_TrimSpace( const char *source, char *dest ); -edict_t* pfnPEntityOfEntIndex( int iEntIndex ); void pfnGetModelBounds( model_t *mod, float *mins, float *maxs ); void pfnCVarDirectSet( cvar_t *var, const char *szValue ); int COM_CheckParm( char *parm, char **ppnext ); diff --git a/engine/eiface.h b/engine/eiface.h index 87b21130..e18cdedb 100644 --- a/engine/eiface.h +++ b/engine/eiface.h @@ -278,6 +278,9 @@ typedef struct enginefuncs_s void (*pfnQueryClientCvarValue)( const edict_t *player, const char *cvarName ); void (*pfnQueryClientCvarValue2)( const edict_t *player, const char *cvarName, int requestID ); int (*pfnCheckParm)( char *parm, char **ppnext ); + + // added in 8279 + edict_t* (*pfnPEntityOfEntIndexAllEntities)( int iEntIndex ); } enginefuncs_t; // ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 138 diff --git a/engine/server/server.h b/engine/server/server.h index 2bfd8b18..4775397f 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -628,7 +628,6 @@ void SV_StartSound( edict_t *ent, int chan, const char *sample, float vol, float edict_t *SV_FindGlobalEntity( string_t classname, string_t globalname ); qboolean SV_CreateStaticEntity( struct sizebuf_s *msg, int index ); void SV_SendUserReg( sizebuf_t *msg, sv_user_message_t *user ); -edict_t* pfnPEntityOfEntIndex( int iEntIndex ); int pfnIndexOfEdict( const edict_t *pEdict ); void pfnWriteBytes( const byte *bytes, int count ); void SV_UpdateBaseVelocity( edict_t *ent ); diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index c3f25eb0..35626d83 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -60,6 +60,21 @@ qboolean SV_CheckEdict( const edict_t *e, const char *file, const int line ) } #endif +static edict_t *SV_PEntityOfEntIndex( const int iEntIndex, const qboolean allentities ) +{ + edict_t *pEdict = EDICT_NUM( iEntIndex ); + qboolean player = allentities ? iEntIndex <= svs.maxclients : iEntIndex < svs.maxclients; + + if( !SV_IsValidEdict( pEdict )) + return NULL; + + if( !player && !pEdict->pvPrivateData ) + return NULL; + + return pEdict; +} + + /* ============= EntvarsDescription @@ -586,7 +601,7 @@ void SV_RestartAmbientSounds( void ) continue; S_StopSound( si->entnum, si->channel, si->name ); - SV_StartSound( pfnPEntityOfEntIndex( si->entnum ), CHAN_STATIC, si->name, si->volume, si->attenuation, 0, si->pitch ); + SV_StartSound( SV_PEntityOfEntIndex( si->entnum, true ), CHAN_STATIC, si->name, si->volume, si->attenuation, 0, si->pitch ); } #if !XASH_DEDICATED // TODO: ??? @@ -640,10 +655,10 @@ void SV_RestartDecals( void ) for( i = 0; i < host.numdecals; i++ ) { entry = &host.decalList[i]; - modelIndex = pfnPEntityOfEntIndex( entry->entityIndex )->v.modelindex; + modelIndex = SV_PEntityOfEntIndex( entry->entityIndex, true )->v.modelindex; // game override - if( SV_RestoreCustomDecal( entry, pfnPEntityOfEntIndex( entry->entityIndex ), false )) + if( SV_RestoreCustomDecal( entry, SV_PEntityOfEntIndex( entry->entityIndex, true ), false )) continue; decalIndex = pfnDecalIndex( entry->name ); @@ -3350,24 +3365,21 @@ pfnPEntityOfEntIndex ============= */ -edict_t *pfnPEntityOfEntIndex( int iEntIndex ) +static edict_t *pfnPEntityOfEntIndex( int iEntIndex ) { - if( iEntIndex >= 0 && iEntIndex < GI->max_edicts ) - { - edict_t *pEdict = EDICT_NUM( iEntIndex ); + // have to be bug-compatible with GoldSrc in this function + return SV_PEntityOfEntIndex( iEntIndex, false ); +} - if( !iEntIndex || FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) - return pEdict; // just get access to array +/* +============= +pfnPEntityOfEntIndexAllEntities - if( SV_IsValidEdict( pEdict ) && pEdict->pvPrivateData ) - return pEdict; - - // g-cont: world and clients can be acessed even without private data! - if( SV_IsValidEdict( pEdict ) && SV_IsPlayerIndex( iEntIndex )) - return pEdict; - } - - return NULL; +============= +*/ +static edict_t *pfnPEntityOfEntIndexAllEntities( int iEntIndex ) +{ + return SV_PEntityOfEntIndex( iEntIndex, true ); } /* @@ -4740,6 +4752,7 @@ static enginefuncs_t gEngfuncs = pfnQueryClientCvarValue, pfnQueryClientCvarValue2, COM_CheckParm, + pfnPEntityOfEntIndexAllEntities, }; /* From 5715023a0d2cdcc11c150119c848f89aa0429669 Mon Sep 17 00:00:00 2001 From: a1batross Date: Sat, 21 May 2022 03:14:41 +0300 Subject: [PATCH 222/548] ci: upgrade freebsd tasks --- .cirrus.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 06d46491..dbc2e87a 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,19 +1,7 @@ -task: - name: freebsd-11-amd64 - freebsd_instance: - image_family: freebsd-11-4 - setup_script: - - pkg update - - pkg install -y git sdl2 python - - git submodule update --init --recursive - test_script: - - ./scripts/cirrus/build_freebsd.sh dedicated - - ./scripts/cirrus/build_freebsd.sh full - task: name: freebsd-12-amd64 freebsd_instance: - image_family: freebsd-12-2 + image_family: freebsd-12-3 setup_script: - pkg update - pkg install -y git sdl2 python @@ -25,7 +13,19 @@ task: task: name: freebsd-13-amd64 freebsd_instance: - image_family: freebsd-13-0-snap + image_family: freebsd-13-0 + setup_script: + - pkg update + - pkg install -y git sdl2 python + - git submodule update --init --recursive + test_script: + - ./scripts/cirrus/build_freebsd.sh dedicated + - ./scripts/cirrus/build_freebsd.sh full + +task: + name: freebsd-14-amd64 + freebsd_instance: + image_family: freebsd-14-0-snap setup_script: - pkg update - pkg install -y git sdl2 python From fd83b0dc17e4262c7ea7176b3db42a52c48e0b56 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 25 May 2022 01:39:46 +0300 Subject: [PATCH 223/548] waf: upgrade to latest waf --- waf | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/waf b/waf index dbbf6844..90d59803 100755 --- a/waf +++ b/waf @@ -32,13 +32,13 @@ POSSIBILITY OF SUCH DAMAGE. import os, sys, inspect -VERSION="2.0.22" -REVISION="c7da3f216d29d06f37ce7acc9d571eeb" -GIT="51fa39db69ed5a58a1f1d256cbff0c35311064c3" +VERSION="2.0.24" +REVISION="af69e5d2e74a777b13b5753931cb3c85" +GIT="c140c3f538c4a21f3d88bab9403b42c696759dcb" INSTALL='' -C1='#h' -C2='#D' -C3='#:' +C1='#`' +C2='#C' +C3='#-' cwd = os.getcwd() join = os.path.join @@ -171,5 +171,5 @@ if __name__ == '__main__': Scripting.waf_entry_point(cwd, VERSION, wafdir) #==> -#BZh91AY&SY}€ âåÄÿÿÿ?ùÿÿÿÿÿÿÿÿÿÿÿÕè&_š$e>V#:ø0åc¾~-ÜËÐ#h#:#:#:#:#:#:#:#:#:#:#:#:#:P#:#:#:#:#:#:#:#:#:#:#:#:#:#:#:#:¾UQWa;¹í¾«|ûß|ù¬{·žÞ–™ ªh^öºÞíÉݯ=ôãß}í;gºçqlÚ—fÙÎîîmwnZ§ZFé{z¤îŽìõ‡Nïlž{Þ¼fmæ÷[¶Ãn»[3Žc¹·`ÜêÛ–=²÷ÀÛ]Ûs)”Ôr:J»L3[Üfm>»îûïmõéf7yÜöÞÏEkÞ¾ŽJ7_}¦nÛµ²ikl’·n}ð}hìeEyo\‹ÊuÀ4Г›hw4誩QRI@Û.Ý#:Û^Ú=6ÆûÛ¨ ]÷{ÎíÏ»ëÒù·ß#:Uë #:#:²nÍà°»dµ› ˆŠR¢"îçVÚ«¬UvÍh*‚0bU¦¡TfÖ•"[5S¡·{#D瑶-.²„rca‚Ù,ú÷<å®îåÛ¬Y{ÝáS›!Q‡vIëÎuµÞG©#D¶#:÷{=ç… Í-oG#¥KÞaÂó*g{Øëç’û)¶3»å»ÒB•Ði@ Š#:ª  vÊ”¾†E{/.℆íÀ‰ÉÝ<ÚxÜàçzÏ|=>u£'Ï»ºw|û¾O]·XùR^O uJÝŸ=â#:#:#:#:t#:#:͆¨4_}*ÃÛ}¾#:ù—CmUyàPy힎Ù[s´®Û9’væ´v÷Ëç+`#:(ÓGAÇk­YÖ‚*–ÂMØ®6(ëJŽF´#D#:†#: PUP¾ösa‚…P )A]#:i@H õ¡¦UH#Dë:ª t#hÓï·ß;ÛgvÞWÞ}u“N‚EÜhkºÆ´h÷nTUJöd Óí„}Ž‚÷};ÜÖÞÛM}é®”¨&÷€¯;¯{¶¦½>÷wÎÏ·¸ë[Àúû¯:®g»]>½k¶û}}ÛÞ¾ÒO»ß_8_Nû†v±¯w\ÝÜ7ß}ÒmªÓuóÛÊyÆî̔죷:¢ì#:h¥Û¹ÚÜEîÀw¶ QvÕdÝRŽÛ—n¸Ù’¢ÛV¶hwsT`ÖäÊAë']¯®ˆ}oG·ßxuékNî#:ß+ÀmÝêªzÔ7•éíwJöÒór^ÁëÓ®]¬uíïoty·›[e;®÷{G¶Ùî{·¾€Ï—o_ØÞîûU¼ä‹ç®èêÙ7]]­£B½°zz.Ç{yyÜÃÛ/m>àÄŸ}ÎÏmÖ™Ûµœsm²Ö½·½í»uå饷‰±Ý©g7Á³[™îî­>6í½»½íÓٻ…齽.kÐÐÙ˵VÖ²¥£^ÙÄ›Û=kXX7‹{_mö<«vÝ”ÖÔn·VÝÎW'jn®Ç®vzãvËŽù纽u·iܸ¹ËµÎ‡ª¡ßsQ÷Ýõç«í×·q›=h¡ u>®×7™º˜94\ÎA®z»ï5ÏbÜá¹¾óÚÔ3t=쪅ØÍš{¸Ä®„hÕªÁµç¹­èæÚH×ßìÔ9»¾W¡Ó*ÜçwÊTæ[§s®ÅíÞO#D99’ÖÛK4ÆÓnë;\²±.ž‚v®vk¹;fúÇßa} îã§s;ÇÞ{ÆÌñ,®íÊví…%ÝßH%zÝ(ë£Ó‚õ/®[ö}jñ+;ÇzVxP#: Õ‚¥CÐé:ÀšÀ+J4«mÐ ªörÕ#Û#:¨” ZÑ@nÝÙ °ë‹d‰mÝÚZëµV¶#:Wh§¶¢”ëè#D^c :÷ƒÜ5ëqK©»µŒÝe­ ÉD¨\÷es[ÙÞ·ˆ*‡¹ë^¢MÆ—XK£¶m3´ËëÒS›iªÓWŸ{¼#:#:T®÷®Y¨;·w{Ç–#½½ƒOhwvÚÛ²Õ¶÷fŒ:%¶}tëgÎp…. v×r¶”pîvÛaƒ«°p#:›¶]uÅõÊ|Op|sQj¬Ý]®²‚9™‰@×_@Ÿnï\>SuzT4 /"ë¡Ù´ð#:PMiU Z11w®áÝ«ŠÜ6ãÛ¯<î¦Ehu—!Ý7lÏnç¡÷ÙØÒß)#}ž÷ró¡L’#hŽæ5Q7lÞ¶i7G:éÇ›Ö{Íöß6-œ\áP}{wJ 퇽2§tȧÅMæ¶6ciDg_{{îvÌ®ÛâÓ#::¢—`Vqœ”ë^µÔPÛ[¶ìGujCmG„Ö”€«fUÓ«m¶Zîç¥Ù{]›ÖêÖU÷NyÝÄMëワ|WAõØA°#:"VØ5»Pë\DŠ9k´îteÎk›Ý®`ôª6n¨¶qÑmæJº¬_otÏ#DÙ ›;ðú>ödgØjTºÈfÚݵͶK<Ä¡\uK°—r;2Ûmë‚îEnZÖèzd#:÷¬k³C¤Ž«\´¬kjÍÖ-:ê;"èhS*°‘ݪ§@`³VÛT…hãµ¢óèò#D°}#D)P#:#:§»Ü=º½¸C¥ì¡£Î°Ów:¶ÓuÞ‚†»#DÛ»Üw;^­çf}]»·k·u7wvºíÛ»Ynå—\㹓™«µÐÝ€®ØÆÛEš4é :q-«®­µJ nøª­îpëR$MvÝZÚ Aãµ(…#D« ÊÃœ®ûs©´šÒjƦÖÈO]=ä#D•èX÷#:Þî]£m­Ì £Öøø}SÒÍo¥Îr¹*u³#DÞQ) ÇGª-«­÷ÜC>Z% UN€ä:»,u×l·hæ^íõ¯=ÛêϾúê¡o²:‰|÷=ÌrÏ–ƒ­uI =7µÆ¥íR[h­\;¸p<Ÿ]X¾Ûæéל4Ñ#: #:#@#:™#:F #:‚hÚQúPmM¤Ñ§¦Õ ƒÔzš4õCÔ4ÓjmOj‚S@„@@@! š`‰ä¦<•?Sd6§¤Ú™6£CF€#:#:#:#:#:#:#:#:Hˆš#:€£@CC*Ÿ”ا¤›jCôeOÐÕ¦F&Ÿª=#:#:#:#:#:#:OT”‘ŒšDô§±Ôõž¡£OÔ€Úˆ<£Ôh#:#:È#:Œšd#D“#:Ð2#D4ÓR"#:€&š#:&)…='“BO(ÍÔñ4Èdô¦G‘cPÐh#:#:#:#:#:‚MD@#:€ #: dbO)š#:£SôLLš™=CLC@#:#:#:#:#:#:à;çþÐ*–LDWü¸3 ‹gíL2–a®¢©EM ”TP%( Š á*ŠÃè#:€õHöÓÛìöû^ÌÐea,!#:øÑw‹ˆx"mÔ#a짛r©Õa+•Õ‘ðsž¿ôàƒþ„¢FÁÿû*×´@§`¾(#:FðPb DŠ @* MJÄÕ2ÌS##2³!IKDˆ$ˆ1µ2²„3lÂÂM)M ”Ì–Kb4™5$hÄa2Idm,”h¤Èm£Dlš4((Ã6HØ Lˆ¤‚M bÅ(4’dÊ$ŒT00ÊT’b”Í!#D‰ i+$R3ý8& 40Í6Ë2e&›d$¿ãvé„ØSl%6̳,“63#D¬™¡M¦`"ŠM4i¬ÆC3IŠU(šf+14ª$¤´²e¥BH›DTi™‚ˆVTÑFXÄYTXʼn6–1¥4Ù(Ò1ŠR1™˜,ˆ¡¤¦SM¤1¨I,Í‘š©ie)©D±‚™¢²R™¬¢BJ™JË,F¤#h@Ú5&“R¦Ë)M•™ Ó2J!´©Q›E–¢ –‘FÍ&AcM¤Í6¥oî¿#Dï+"1Œlj2AŠ$iRIˆ,S"+Ed#h”21‘™‚4bÊhJ&»®mL¶Í#0Ð3#:dŠ)°4…Id ±±4 ŒfD2)ÆŒÆfmX¨ÖÉcj’J˜`ÊdY¥d‚¢b4Ía-$1šÃ#IˆJ(ÌÙ4 ¨Í,Øƈ†3,6(ÙE,˜Œ˜DÔB0‘”¥IcA)™¤6’d‰”ÃDÓYLI‰¥¤6Š¤ØF¤š‰”Šb[d#b0¦c6f#D”Æ1,šdd²Ù6šJj#EF$ɶ-E¶%–16 ÆÔZ(Øј…£&˜„ÔÚ1³4±II‘°¦ØË2d%©5F¥ZEE²ÐL-fe iJÒÉSd²–›(Ñ&£2Ø¢«*DVÉ¥-S,¦ÖJÒ²’kA2ÊXM%©¢†,¨ØÑ–ÈÑ2© ”ڊŪ#h¨Ä©6³V¥i1¬RlT)¦ÚU$EbYlmd¶SD¦ŠÔU‹`ÔT‰F±b‰£ijHÛ‹F65£lXhÔjƒ`-’2h˶€Úf‘"&£!20+Jѱ£i‰¬Z’66ÖÔÛ[+‹HÄ„L¨’›)š›Y,I©¥”‚ÐmªUš£RÙªY¦¦l³©‘&ÛJ¥‘2•%³RÑ@µ”Ói¬Æ62i–©-•”cH[ƒfÔÒ1PˆšÆ3$’°"Å&’‰I!±%„£b2–ÅA©+21$‹&ÊADɵ’i¢ŠLš(©›,R(±,²$i•e™”Ô1D›@2d‚D´[6€LŒ6š²cCfšfHK ˆ05¦M3Ä…cAE†ÃD’’-“b4Hb™ƒI™#X¢Ä”ÈÓK,À,Z¦#:Éøv¹¤ÆÌ%*Sd„ÔZ2)–¢66e&LdŒX¤)LcJm’"˜XÚJ#RXhÔ¥&Æi£e²XQAdÁ!*£%¢Š(M@ÑHf$Ѣ̥L¤Ò¦[(a5ŠAe‹$Z!HQTlÖŒÑL¤Úƒ@‘¦F%"˜›[$J”Ò‹$†DÍ°”ÊÁ¬š¢“SIdØÍ“bÅL6hdÚdÍkeAHÁfZM(Ñ‹Åf“DT™¤Ì†Å(©!0hÕÂTlZ,bÖ3-†‚$fBi¦Ì³##De‰Q`)”A Æ¢(¢Œ‘¢Ée›%JDl–¢ÑE¨ÛEÄ•0K5Fˆ$´¢ØÑX31(M%™$æ© Í dª5“I¤BŠ4iJ I¨J„´chˆÆÙ…D[hÛMJ•cfj""É ,Ì¡ZM±‰4fÄ¢&Yf­DjÁ3LŠ½Ý‹%£#hb¦–£%bØ­i#le#Di#l[)&ÆÉŠ(ÔRm”µšUŠÅD©%)Ħ¤’ ƒc%’ŠÌ±%ˆÛ%"jC&fB-¦@ÐCQ¥53iˆ¦Ú“l‘XR4…4”ÔFˆÉm’Z QFÊVYi4cmV±¶”ѱ“j’DKh¶5%)M„ȦI™5"1(jfÉL­‹RXÚÉRͬšÐš²ª*0QS(£Z3lÒÚ‹b£mF£kI˜†(AbU+Ú+M*5¢3f¨ÙMjbŠP£4™0±+McE‚&TcFɪJ£fI%lÍIª#DR¥"fª ( ¥M1¶1db‰b¶ªjÖX`)*y¹Ä"4ÒZ6Å Å-©4È@4-*Ò ¥4ˆÂDC-l¥“mMM³V$‰Œ¢’1•D±©©•HÉ™“6X¡¢1I”“"LŒFB)+QS-M÷SCsøvòÿ’ܨ¢™ŸìKͤi¥\dþ¿ùß—½ ÆýYÚÿ³ŸöUÖÍ¢ž÷d¦ÿÅíb¤ÅE*}·çfìðë};ŸµÒ3û™Èêø6è,?ÈmX¤`ã#DY¯ú%€Þ¿í-/ýÖÚ"½mAOÉû~ÒoDlUme&{hcý¿ûu4aús^/Úü0P²EX‚ÍÈþxMá8ÎÔr0?à_\1ˆ||~Ó\ß\k3hèëeÝÞ‡ÜÏÁÿ/ÃþÛ 'ýÄùJòfšÅÙé†,#Dx‰àòÂ8Ï\•sÌ»Õé{¿Gü•OÙÿx«Ïý?g[ϹV]#htpž#Dõ#hÍI ñk,?æº4kSWnùõ$щñ òÓÞÿÝÔœ³Ãai¯¯;˜û–\7pZŸJ¯›•6KœÜõÂ_oÏÏÎç#D½lÕ-:u[°åµvÅÎtïlÄÕÿwÃÎD~ÃÖ—"žÁù21¿œËc)g;ÂæI£0Ý–÷¤âÙEòŒ&š$ƒ‚²6â¨EF,ìçæíZãVÔÿµßå/Ã&ûmt‘?î´j˜úW¸³b3JQQE_J˜Q?—•£s"U´{’†rMÚt„7IírÌÛճʨ2†P<˜L~käoÇ¿ŽÔÂÿŽPÆž,s(ýPôòõÚáò°†dHDoœþÜêÿ—ìç^¿g;v´ŸT¸¿í føÝ4¹I~—õHŒçÌôª§¦ú{3þFz¼húô£‰"‘á¬ÕÄ– ¶ì’Ô`³˜2„¤‚öª%¨(Õ(kT'ò„vï6»C¶èö\¢Dp­tšDaÜÊ„1ªöÊÊÆÒüž{%3âàŽÓ«*ý¹oÌùÊù Gü·”˜Â#hES« G¥:Ö>žëP2PSÚ_¾,÷Q·Š7+æNÍÜï(2õIfå}(×™aÍžŒö:}˜a=WÝxAˆÍ“&­é#DJ?üM!%ÜYˆI#(ª¦¶±(âûÊ‘aÞè þmœÕSJˆ/ü­;T§*†ãŒž’ÑÆÐÓ>MP(É!’jxËËœÎ#"%¤(¾òÃÔI¹? Ü¥%?ÍŸe¤(¦†÷aú ÒþBÜŠe~ný}Äוncrä>þF°À¢`_κÈ|TX¤3#h–e¸©#hAUKÏÞ¿Ë&1E%o“x-[Ö|EC…~¾ðir8k#D0b‘G篃!·Ù¥…°þ_#h·‡uçRIOýdíËt!:¡áfµçu8i}lœå5ŸEDa¦çZuj±tk›#D²09L½64B5ûn¬¼‹–rÉß#hŽ›.9—±Z¸úØ=¾~UïÛ1Ð3®É$¹ãû¹zþyGëͬ+QôæÖá!CŒë¹-k¥ËAM¨£àÒ³W£lûÿ’™‡ÁtÆ´fgãôøüÄLåw£_ÉÁßû¨ÚÏÕ{ø{ÈtÐQd Δ>eNVsˆ‹†˜‹ŸÜÙpa÷¡2xnp÷{3ÿ‰§‡F^þêÔL:+#htÏžâöA‡½ÚÊž,)t}óIDÜÊ áú¿vlˆé˜E·Ï¬£ïhÙTÄM>ü?6¡áµCš!x ;'ÚÌ^µaýP*a¦gÅ–’û±p/óõ•€X³úN—bˆ‡È”1zÑA†v_ee™ñh–ÉhB†ò£ªXáÓ¦,8JßóÑ-=u[;\¡9iõèUgÃJQ#h2¤t9×ÍãgY“$‰“×r¢ƒµ<³@_¥RDIÞÑUõfO¶?‘ Úv²0¼g!£ë¿|ºÝk­M¢=Ù}Ø1†Ž#kMì¦EJ “wâË`[[G½·E[h‡ÛuåË·L5–¥òf'娊Ҏ4'f—,ú·ÏøÊâß6:Ô¨œ<]Òþ¤’ÐA;Ð;ŸâÇØßJò¸|ç A²ˆ*wÔ’”Cà™.ƒÆ©:¥/wu6 I? àÆZ%ד,‹"† )Üû’Q£„-­nüG\…"´”Ç]³ðH{t÷ÝxÔçΣŠ€s;mp8>º=î»R'ä¯;1L¡]¨§v—OŽ1†L_åĤoƒôbZ_k%#¥„7”ýØ’q;1è‰P&2[#hN…J9iAÀ†VŽ­‡;Îø¦ã-šôöƒ…ÂnÈ´!Ä“r‹yëèSfÇ0°#h5­˜hÈÔù³XB °Ü\P#Dý0Ko¦¡¨ré^¾Ø¸ÑÖâæÅ9µ¢±LCòíœ9¤Ý&'˜Xp‚œÛlÅ|]ïŽ"'#r3øÅPAðeÝ$êoÛ‰ÉùÙGqô‚ @­¥"3 $âܸ½ulªONéÊû?‘Cô©±÷»YM¾L’ÀèËšÄgiœ2tgF¬|ÑNº™ó±r!Ž³eÒéÃ5 Øÿî]ÿw§œ÷poÂn{ïÆæÖÙ‰†nÊb„ɻ-q#ÝД®QÏs.±xýªzçnˆgãÆñ¶ög—nSJâÉc-0Þæ.­—cúSsçÏ^2='GÝà'÷Ccÿ ‚ÆšžøjX½çA‚è^KÏXßôXâ=ðæå-ãctÒŒD@®ÚûÄÈ9&„#:pÁýPÖêê@H@JA÷PG04§ Îâ ¤ 74ýÐýÉ×zD¢‡‚J2ÙyÀdo k/e‘¼0ß•…:‡¤­c‡#h4YßvÆ<÷ñGŒ“8„g(>¿mù=ˆ¹ýõo*-µånjá24ÊÍ®WÓÒð`F¤Š9âm¿„Šµ8฼S†¿š@Tü#DåÿGš|#DyðzÒ-bâRR å,·À;ßÆ›µ?¸¨ ú8=.xxïÓ6¾ ivø±‘J#h·Üé9KŒÈÓ6‚”°^Y9†ÂÅF9RO@ƒŽ7¯Ùïm =¿¢­QJÆ¡Ésž>"òÿ•'èÚˆ&óË„›Tn˜Ÿ¢ô‡#hëS‹²î«ÏÈü–sõi¿…?¿s„õ&“zY@,ª#U Ë.1Ëû{ÌOû¹œØ^î»ÒÒ#:DÄÉÆF›M~1/6hÆZŠW¶~èxü!ÇùSÝÎcX=z rst,¢©4©hç€Å`³ìhõ¨ÝŽ4ÿLï3ßÛüºÁmõŸÓ¿§F3æÉ„|»Ì’r×æy›ƒ¾n¼’‚‹ðÐñKPf<#RÏú#hšÿq2É…Ýãƒlѱάr•ñù³~´°BjN÷n̲š^Åùa®±ñÁ­Ff¥wpû±¶ØnCmϾÃòäàÁž®úy¨õï:dþÓå”W·„Ì„k¹.T F…=¦E=ª³õ°¡ur*¬µÃ®]ü¾žÛízl#½æš•ç"ððŠ$‡?‡—˜‹žÊ(Ù4 Ê€ÏrÉÊ}°³în3mÝm»±³ã¬ý^ÚSõïDš$‹6_öT²#hHÏ¥€’Oc#D_³çqÍÊ(}•/:ÇA°Ä¿!î‰|ò1ž$féôØ}zî6FòC•&-§õ:ÓXP©$òûEµJ|’®ŠrPù§s#Dª7X Lä}³Î?%û@ÿ½ýO×ý5Švwg¨žß8ý–±Õ¼bܧïÇñï?òn™"-ñ“<êyħdƒ;ëlÙœ}e7èC[aÒÒµ.Íðotø+ŸM¯ž×®íJ–pí c/ø7 kOúä`^>üÍÏñÍ™9‘?É'ŸëÍb5Ì<†bX”¡²ðì–úÍRAiî`ÅBhN%ü{ýÕ8|£:O´o¹B!;[¾iª‘m_“Þ çïI}úãlµÍò©Þ)ÒL´'Š×zbQ„oOŠÞsïGd[`Mü¡ØJÒ”Báêj¥ÙÓ-¶Ô· wåS›g\#S¦cËÓ*É[‡~#DW¯ù~é­t²Øtéó¯ª>ßRù­6Èùfá©%"àt@¾÷ŒÌE>Þ™¢ø{åɨ¶þ%Þ¬;¡(sìxhv³ôÚby=î™e­ëM\aÔÐÇ e“ÑÙ=øìâ‚4H`Îc2APª½ëÚ7ùõã„£µ4-ÝþÚ!®£?º®îXÚ&b§èpn¤`wŠLJ…6þÀ°«¢‚±‹ÓÓše.^7®#k‰+<ÙZ:²‹Å˜1œòÈáƒÜÿ’Ž#¶{K8â»]KŽ"öëcÍï ÍÌ¿%=ž…9z"„Èw}’æ™/7©åòxò¸´x±ä]zØϵ ­Ï³³Êµºç£Ž»ï®uohCU4¾úO ãµMÐÙ%¦[éXõg#h]L!Fj¦”*”ßÏaeµ¢F5Ñ¥½M=9\È râÏ®2dž|O§#rjÿuêàz#Dä10TR‚Å @˜r¹ì¯o_üjÚÖp›Ïg®éoüéÌ é|8pü‘+?\yK÷ëéÁ¼`–„Æ¿%Øü•LTújS"0FMÏí­OqOÏYÓžp»XÏÛhÉæ!läÂnH´‘r ¯Jb¢PZfS²Ëx¯àmå¾6µ´î¿ßã#:qQ8-#hïÅCWŸTÒaî#DL™ íŠÌƒséEææòà“#húL}²ÞL9(YdÑ¥œçMFZïøŸxTÚ:éZÇ_Ç\¯‡ò[ãîÓ½ñ×#h‡7nßÕs%Ä8èsá´[C±¼¾N"v§ƒ#hS uíW£ÓÍ_”«¼ÜâÜ« ?c(ÛÏ®ÐߥŠÎ®;Â#á¬vE‡ot0ûëpýVñ$!#D•š¾¹Ð[ÓŒ°¿s‡l%ÄBDÚ?°]Ų~,èÄtW¤ jÁÁGþíTsÁPDb€ñ#=ì0¨fŽwUTx´rgŠÜd.Í°1ÊþJ W’ç Pƒ#D×^  šÄVNq»’B¬fÌæõv8\rË#h?32—¤T)Iqo8HäåJߥ_ïcʽW;±Û¶6”çN¿}kÖ³“ªBCVAÛq¬QL‡™Cˆ’=¼?&‘òú=r¨¼“].uë”6öl˜Ç"€Ù|FDz)Q¼ }„ãþþ1ûwPt‚lŠ{¾uÜíÑG[¹ —Ýùâʤ`Õ$¯r‡†#hK¬Úª·´á­këZ#Õm²‹i•›ƒ4…ˆžcãÆjb1¬ï±ÏçÔ4¼C•™Ôͼ󇓠¬=òÍ º'o›•ª…Ⱦ]î!Â¥™A-öñt: °vŒqiÔ·á9ÍJ™#:¤HuD9ïÙ'Sû¡|ÍcU¯ŸÂväøñ×e\ñKp~#h÷lyA ÏKŠ‡„qóS6Œ®òe-õX?ủܿó~”“ûvÝ1#hÉRï(Xc÷PdøÖ@ÐÿÊ|=3èøyŸ¤øöçð=‡À]ZÍɺ'!§–O'ú˜3TìÁg&a9 nƒ¥|­"ºT5åGmf…FÃü/ ´¦|É}hÙ×Ød/q—ç¸~?×¾¢»÷îý:#h:æ$‡q?œS"€I-LR¡èø—pª³yXhåì-vÆ>5lJ‹»¾>“UîȤ3úµ¢d­5 •NeB‡´B©ö­rµ¬TÑì“97º\žÚ Tgww[0fùG2•Í€½¤0#hŠ Š8„coN-3n÷‡r†ù‹%¥M¶ußч]SR¸½5¬±‹Ò4¥ñ£Œ1“ë­·Ò(0ï‚RCéÙáösµáº-`ýZÕ3¬jâªü‚:ÆýO&iD¹%lá>Ôò¾¾>ýu¾ÉßIKvv¨tØz•¤–˜÷Tÿ,öìU?Ô™#D/EåÆÝ£!}}êŽ ½´:9§¾Pl××DÙ¿P,wÖDä/ÝäÅi’„³ª¶o‘E™Ô‹‰!Öþ]òë^\¼hØqÓsÇÑ<Ûö¤¶ÉBÿ‹êB¬½xûWJÌvïß:2ÆÁ$Âa$#DiBA² }1¿w‚J´Xåû`Vxëp‹ðuØ¢ùÒmíÖöêPÔa0 ¸þ¯'‰JAã#Þ|,v̱Â\®´SÌaXrŸ{2" ÷öÙq§ÄøBÜœ|{F@ÊCä`2_î|ì+>u-…³’UÔ¦€¡èÐäûôÐÚ”¡ŠR1¦F Á’Aê“Ìl‘¾#De2F"HØ»B¦µùu¦ÊÏèâàýFY2Ÿ>i §wÞã+ªú|ôw[”`‚JÇ‘A&k¸A”Ï'1R¼ˆV4ÁÈäî–GƒvKèwª4á06^KŽ¶ÓÌt˼”›ÏÉW‘I3•õ@ZàÙD|Õ¹K#h»Š^±?ßøþ}óÐ̺»2Y‰—R¯ÄheŸU”"Š¤œÙ jkÖÈ`V›3YFüÿ¾pŽ ÙÖ‹ ¬2Ã$Ï…s|"ƒÀˆF’:37u#h4ì_ð³çÂ%‹ÝC=º×Ï…$Ö¤+uüím °$òT#D¿a&0’%’¶‹=dò‚#˜ga´œ$Ê¢½nŒˆ‚º$¤To5zzc8†D“ÞÔ5ª`(n +‰C·“‰ÎìSßÖ)©¡R›I3Ät„í/ë8é¶úZ:1.IÆúÌ¥©õËFƒC1’Ù6I”…¤?›Jî۾ɪe‡êöyY2¤*4œ¨T«ôo±ëŸ×ê÷g (¾R@(H$’dû>碑ûîŸ$ƒÂ_жæ|n¯ %=x^.Œ¿{ˆÆÛ¬mq aYÙ‡]}­ñ,púI ¸„ï‡ù;šYu­äµìy«æ¿AE¡YóD9WT^x¾Féi•ÇÝÖöÍ<“¢£ðßìððЙU,ã ë(úTÙí]ÿ[” !3qý.À}þ¯éƒç”õü»½X9?NÔ>zŸ/¶ÏmÍ‘d›¥ì!‰x©¦}LÓr¸Ÿ¬ÇŠ°³ëX3Á½˜ë3[]à›ª‹˜"8y%OÍÉ?ÙF$¥zUaÍÿ=vq¹Õ%ýîiByoè“ìËGØ·“‘B=È*[ô`Ž%‘sÜl¾,Ìç’s¹7QA!fIÏÆ®›š‘|½jÿSè:ZÔ·ÎTìñcJÄY ˜39€x(Ƽög|­ûœï@ê¬ÞŽ£W\ݬ7 Ã8öU›°¾âë2ò•$™ð`LjbçºùÓ ¿é~3 RÆ¿µ7Rü¾¼Ì\Ú¸6Oàrû|Fþù3F¯#DÊêh½fñŒ×L…™E\²‘Gëõ^t;ü›oW¾Ÿz]kÎñמ®«ÆO6gÁÎóA}°U6uÈX)ébn YªÝ\]7áô¼sŽ€½PS¨(¦â¬IKb¶ÂÒ]ìçíüþUž¥3¸gtgTùœŠ°z‰|ìa…eUŠ:­ð}+šÇ'’øª8s¬*÷-·Qpé÷jVI—É»×æa¼Ÿ1ñçØEþÜÛýÉÉ°mÚ]aëMD1ªVÇøI´÷®Ôj*ëuSÑ-¿ê–€ù½âWJT9"†G ¿+üèÇwt»*”úl~]¨ÑW=Z(ºÛüõ(¾,LÒ‹ñûîr›lSÿ¡Þ~æÑ\×m¯×#hÐ#h>³Xšïüjtüpø™Çô}™#DÓaiЄñ“=C¾8dàïû;¿ÃUðáÌ;R2Â48¤dãßૉ=veh}#3 ýOè~Ñ÷o5ÇέøëßúÔBZ96³ñ;é’€¡^Ò_—°nš#h#: D‰ÑÏf„SéñÉ$]B¦Jƒ=°6¸r8NÐðï£%>#:(è½B8Š •#:4é½Î¹þÕq1Ú±,Ú™(`ÕšÞTüR­šÊáŠn˜gn(:ë|¿ƒÓ‹Õ=´kÁÌNhP"%ÕEbÄÛMµ4«LkO Óày1FÚ+on&}üBýð®¿9›™Ä„ò±—†)©ŸÂç¡Ÿ«ž=Ï¡„L9N>¬Ž*nùDcÆØfàcLÓ˜«„r;¦…š_§óñ!“X……Ì#h¿?b[õaÑ^ñ^enR®TX/VÆu¹UÚUdŠaª\a½rÕ>mœšåK¬d2#ù •!*tI(® <ãl:»›Ö¹x·’ö:›‘ijàتz>ú¿[©Ç”:BO¥¦6xŒ$¾×OH„R¸û§&CähÎ;ñ^¿Ÿ–ìv*R¢ʵDni¿› G‡–"ãg•‰·úšÝ7Z¶&£‹Q4­ÇSÁ¨¿Èš‰‹xJ(¡ù#D˜dâi`GÕ±ŒY¨ëãÎ4¨5·Å"‰¼„Î"É#DÛ¡+ €ô“Ö}#hUt͸ÙZ¬R·#:‘¶ÄAÕ’a*„OOi%4%î?Ýóûp,~Ÿ¿½u^!ò8rípÌtÕNV õÑ“¥I%J’I{É;î~Lµ"ÙuG¡È*G6ܼqX„qLÊR[+º‚Âc§Ñ¦«QŠ~Ô)Y«Iì,lêàã*4nǸ”û$,éÙ³_Ö°H讕[¼Dzsë3ɧÔÌ$Š4ÑÒªLÛÃB>RÄ©áBj&¬U"Zÿ‡¬Qeå*æ`Éœà`«‘Ìï"Ÿk†GÝ“yNþm ¹ƒ¾ÛQözy^íoë‡?8¸ó‹mœ1F{ËHƒü,ú¬çñ¬Ü¼¹Šýo_[Ãy#:ÁÌ2#lî„Žó 3¦ÉÜôq©C½RF`E{î t5(µ,„¿ŠùpIs¾ÒN+ñœ2…—whvòé«»=q2¥:§}¥GÁþh0«™:q, ñ#D¼‹NonËîx9ÚjŠÖ£ó^ÜzA†Ë„#h%Ìi#hG÷fU×åóÓ¸ùù`aòƒ:1ð¹.!3(303èV(!±H˜³P’Awfn\iåÃût?Œ½®W~Þ°×oÛç·kr†rœá4£\Tn;ÿž/\ý»gðç—ã”"S%H?6™I52:‹®¯g³E•ãE’âÛY±Ô­“Qïô²é#hî£Ýt(=hþ—ݧzÑí]R0øXqéÅ;q#ø¶6Êd¯2Eé?KTé¸tja”Œ×MCâÿà>Œ®©"ûjgù—}Ÿ3!2öCæÜHkFQÀ£éLïPøaä郎½o™2Š`”P¤m)¥îúÜ›Hð=™#:ê”Zíj¥#©F''ãóϘÀdU!ɧkŸÌ»dá‘¿%?…˜%gF`Gò±A¯È MÁ4GÖB\f_åY*¢“´«Š½GÓLä÷“âŠ8°_‰â·ÙÌó}ê“™œù×ßsÎ.5’0½Þ|FGÅ”´/.#hŽÅæ ;MëìÙ¹jŒ^¦eìý{ m„9ò«€Ó4AràÃ2n=ÚK)û™£6ÝÎ:öñWø¿ÀVînägŠ^ÜÊáè*q߉Ȓ¢ÿäí&ŸXñM$Þ¨—›ÅW##h*ÿï\ãxyô—2l«‡dy­øsã—Ù7¨¼K nÆ•èšôæÜëw¬q^u&—<õ¡ü*~vNBuMQ}ÏD~R3ÍéÏ)ădhž°=:q*ñ¼¡öÎóãÞïG™Ö“lˆM¥°Žè=6|ìí±zíO &×DÙ7ª™ùù_ãäرû±¿ºûZ=_á^ò’1…X(Ų³CÔªOddu¬Æ¥ˆ/Åöò¹;L¦©b&zMŠ#h[sm)bÂz•œ®)?*¹C잟ö-Ã.׃־Š²íÈ›‰tV_…HD9º`ø3*MÇÙF%µœG(j_‚Òƒµ68 ¼í8p{*Zª»Ù„H Ï#hñt b  —0ºryÿšÿƒ–B-“F~êΊ0‡LåAº$G¢m®a¶FõæmÝWÝêùFrzA(ã¯ñßÌ㪤t oŒŽDÃLòKyûZ6÷5V§õ¼>þïeàñÆ&Ånà˜Ü¢ÒhÎK‰FÈŒ}a‡‡þ¤ÚÎ+ÏÝ!Å÷îæDþú×zãyõ‡^ðå­‡ûè|&a²454kL5dX±Q‹諲‚¿¾¹8¯™ƒäõ¯e6Aà.((s>Mª¤1ü¡PLŒx¯“j QÅWƒ'³økûþ”žhWJÏ}æ“ýøÂÞïJ4š… ›xÃ_{Àƒn¹ÔÖ_ŽÂgÃlC¹\P¤ŽOcJ—Ì3-Öœˆò¬#h]ýù05:Š6µ‰½ƒtEhø˜vûZeÅJJ ÐûŠ8Uô]É#Dq˜<èëmùl<ïrsjü­µëbæn²7ºA ýÍœÞâÛèÄØ¡a”‹ÌHÅÊ yÔY>#D6IŸð+c s¼T‹Žå¸ÁÂY*^QŠ8¦²àA!Ö³N<ÎùŸÏÎ"—:Z)³Ð<k0oSH£M¯œpƒÝ´ËØ庪+/„>Ú}bþû:çÎ='v7pûã¶6£"ò²¼©äuêæ˜4¤®Ì;x¯#h­ìâæ_´»b£ÏÏLܤZŸž.©ÝÄÈ n_D%QðhÀZJ.xÞÒÑÒô³þY¶Ã”Uß"¥À;—ŠCÌŽ´µL››<$iÜø®cxjÁ¿§á3µŽI%´9ŸÍú%Û9˜âît®çˆ×W }®JìÊÕ’XT®£#hþ¶ï´ z ­)B@ýrùT£6@™¾.ÞÖçNøù¿ðvâBˆÂÿ#h‘Ç,öǶëg'›”„g lL¡Êáì)KVƒ¸©Qºý*©œÕÛëOí«L'½)n¯º¹;¡Ÿ#h5Ë’š x½Abæ#hZËÒ Á1"cü¨qUö8lî£#\Îw§Ô›7†;Ч#D ¤ÃI2Ú±Ø÷¸u{iAé_eqü¹È¸ÚžúÃvsehT£³es“c/ªŠÔP£rì?[”\À>=pÔ#1 c‚#DCò…a¡QÒ7àï’¥äåÒ8ɾ²í÷Ù©ÿ-}°¿ÓØë"¼ˆ/DC–.Ïù!|6¥¬€ùµ>9·…ŸqfÜ™Êé¤]G…Ä©!ÁwYDKÇü ¶{ú}q±¬áÇ#é»ñÑØÛ?]€=G˜#ô'RÓ¹JĆh@}½8fÿèŸßþ¯zݬ˜• NÜq3oeÈ‹û“D+_Ñ™Ž>þ¦Nzþœ‹®œNŸ3GóÛÙù1ê7³‚ßúì¤pé0Q¯øýôÍ#q”`<3PGëµcáòÐo›Ñ*©Å¿`LG£ÀýÜTL§Öâ £ ÉîúðU4Ï—˜áè!!…£—Ì~ÇéÆ?y1>ÇèÁªCíyæ⓯¡üâ~oåñO¦{¿=ç×£)<¨ÅŒÖÎ\óy¢åõ¿ÝùøüÌìòû(9¤0Â)!  #²ˆµÝ­ù2Ž­R<ÓkÜïü>o̾#ÇÃ_C”v)ÅG°êî^]ò-ösîc¯ëüD˜àªÄ9.7›ò/²Àt?‘>D1#hRD3#D‚4%!2‰ù¤ü˜ÿÍ×ìç<“1'È’UpQüéž+0?ÍSéeå¡ÿuÅ#j¨Á(©OÛ§x^®!3Y²sܼ3ëóº ¡ä”ŠÎï…Ùþ |×€4B‘íYzuÏ¢?´ü9þþüéÕˆ½%W%Â#:¼ðïêÎίÈX‡ûX¹ àúÂâ@Ìpþ¨OW(E¨Æ k|¯!Ü«£5ËËËÏÏz÷ÊÔÌ•ÖøæiR#syW\Þõuê÷¼½ï^½v"K‰ï6×7áÝóUÕÜÊ×5»·eÚ!m¶”Ò4™µ)¹m‹e4ˆÒcߟ‡ä Ç:\»ã£cÖ#hÜ'h?¾ ÏóU‹ütQ uëbôKáö8í°hª€Të!‹<êr)r÷tµÅ÷ËO‰Fjá‡'b}繯Ž<#D–Ê‚õF¦©CÞØÔ–¹ZEÖœŠF“åY€eh#((P“ÎP(6Eüò@JV”Õõg+äv|oÂËê·Î`÷™!HxFIqŸ¿FCfPg^]ò¨Å}Q[Œ”a³ep_gü˜Y•æÍ 9'ËuâÒ|NXÿƷО‰àÃÅQE1½V¸±Ê¢úçŽѼkËc±U»ò-‹Ž,ü«K†½¸ƒ^éšû?äŽëѶ:Mä]mxHÎu˜u³\Ûôë²û«Ï½_ÎÜú4®jÜ{\÷1bq?xœÑøÊ€æ³TŠ”PWÏíöü5í7¿‡×åØ—N¼ï^–ºôçžšÄ@>‘T…UyòCåý@?g¨qS÷B€€|—™ö¬üÊȨk(5áš*|U#ó¯ðHïm2Œuû¶d«#:Ê4ÿŒÛHóF7&ˆ:@:Æc•u²h©aa‹wc¦“ P?D– DÈŸßÛ'?9Ð9È…JÐáó³#ÇÑð³o7Üò)ÉR™›ÐÑ°Üê¶ú&úXÌz|¥²¹-›“„ =›ˆ(½M­!¥ëxk˶ϫè‰\=ghn/ f–öÂÄ—àêðA©´µF.£‘/Yþð¡r”þ;GóèÊh›† ¢+ÕÓÎ:È—#8kêÈ_úóæ|éê”J õ˜=¨aÏóÃÝ7}ÜMì©ò56“†¥%·øþž¸: d`ªÅ‡àaôCŒgÖé9t£ïúq9¯£é?ªM@,Ð#h áÚ¬‡›PõÕ\Š¥åbæý¾Æ›G­¡'ÕìÆ1õï¶QPÓ]ÌÝ“Ù䙟“³sñŒ)™8’„ùãbÊ£O*³ÈxùF¢‚C½F¾mC¯ä‚ä„Ùê绫¥ñó?~#:\qôK¡J—Hd¡Ã@<¸4h,™QÓ§MûÏyâÜ,®.’ öZ7}iCN{V%ÔçGÜÚ‰–CÙö~o»@c1ÞÝ$uH¤5ºÜð:&·gœ³9]Õ ãU\K®¹º§éy«Ÿ,ï~1x‡¿•C?3FkªŸà$*÷êc#D4‰:á#Ló•ïeòÆ|8¬•×Ék}îùù[¤«Âe«ô>FO‡¶ï#DÓÿyÜÖ²49ùuÀ¯+dÊ49®XB3‹t+˳Œ.ý¯Úñ d| ÙÑZ$ Ž =ücû|ª›b´HûuS3YTY¼«¢ f£´¶ùüÊSXQ¼ÓË|Bôšr£Ëe ˆò#:é´„‚FÓ‚ïçõŸ=_û¸{»ûô©Õœà?Hôb€tîÛÇè(ÅäÝœ¯µÕ¶áûÃ~vû¿ûJ+÷ßy%/LÁr?W¾Þ¹¬h#X¬¦Ø-/ÞîUÖ~èw{þŒy¿<„>0äKHÿ4¨ä#D'kÀ-™Oï€æ #DôŒbJüþº‡´ 'Ò~Ë$M)D%‚#hQ#:{û?ŸÙ²¸îêÖJα†ŸœŽy¥£#Dà#DªÈÀ1¨RhœŽ«Œ$ôS(0ox#hôBšWz-±êV¦Eª$~Û`9¤ñ#:9HRˆ ²`8ú­04l·&KTR•Ì“3FÉTç]žùß ¤¾{ªäB8LÔ™™HtÒb‡IÜqH8ÆÓ`‘: ‰L&µ}M B6¢¤´VqS\E‚î1¬—‹©ksî¢â¨íM…‘ Óœ;ó4Ê¢‚A–̳\bóœ\£šŒU µ#:V #D*KøU$zg87¦Ôã|í`hÎ÷Ó4‹÷ºŽŸ¶(Uε/#:Ø“µ#D˜0Ž½Þ8a§°J=/³zM5E,—w0˜É3!FÇñšï·ìZÇ×Ì8EÈàÁµç% ´ìß6´¼ Q OKC™¼““Nf=3QÑ›0-#¼®ÄLc3¢²ql9ÒƒA±Òíñ‡­‰±ªÒ†ß;Á#DŠd2kb9IÕ›²jfôÔΠŃÃæbMÆ.4÷IŒÒq`EºAVÀf¶Z‘a Î_B®ÔÐÝ&¤õæúyZ#ḎƓn9Ù‘„i´ Ç!ð»äÜ;táÖ7°ñ8#–f±qƒ®´½+Ë:~NŽ hI¼£"‘Þ#D4!­Ú•bhSwA›Önr½ÙbŠÑqÓ’ÛdzҚ„ŽØd4³ÔÌxÀ3’Ù.ŠvA‡DÖ|§ä࣒ÑÕ´dY×c(Ù+<¨«(&FàÒm.¸7¹¡ÍUê.Ú€°, Â,ƒnÌíW²læÒ°ŽŽ£b4… 7¾zðœ Á@t¬äÑœf#DŒQ#{œTA¢êU\5&0k›B´Á1‚!3D¡Ù¡ «I—DçŒíXP9r8Ì2†©[ÑÈÑÁq¸P[fÜC{aÅ#+SS´p˜â\ê•òráã*Ìk³*G/,éBóñ”á¥#:Ó(MŠáµPÙ“¢Z@ã‚ìv°Æ 6Ͳ͘¸Pn†5É`ZJOó8C‡¦…¨)L€ØY;Ä#h3Ô¨6W²YÓ‹°VM.¡®åLbtàÅ‹T™#DŽ°‘ÃuÃæÉ|ŽÝûÚRzëé}æ¾?'HWš]Æ#hwvùÝAµà¹É8¿¿L™å®åûÓ{kª½Î”^Ê vðOFLÊ©þgŽ¡ä é•hÅ+:S#hÞa½AD3ýCwo_ã=ߨõ)q>¿sÊá>×’*¡@œòû«ìŸPÉÏš¯VݼN!m‹tŸÎ° x^ðÌMŽïtÄ\Ñ0¡—ûÿHK]ã*A³Ô…Ã72y>:#DWC.‰^ÑØIÅøaj8…ÎN§F‚%ÐåY´_ rÜ CÈUå·\”ã°qÖŒ@3Öù5ßËùZóŒ´aÑJA.Ý·¥„Þ»8a;`¶[#“,àƒÀn¹ƒƒÄqu`ÊÙ0‚¯':=<¿Û?æü°¥—%õz>ix,pͶäb軞÷D9÷0þo£9E‚~#DI؆]´Ô·¤‹bñøÏ®lÛçÁZñ¿<`ìä:iÜ6YÓœØÁ82ráÊ ä¦¶ .¥ô{tÑf\_{Jã”Gcªc¥ªúÃ…˜ašÃ, ì\F²¡è¡Qn`Ä”ë ,°µ2†ŽéúU¶åº¶,²KPDqPšBF'É®4‚«>@)¯¦°íŒQábdŽ! £ ì᳚ŒÿÀ(z%¨7 U¿ÝÙ[ø~ŠžcÒGÉòËôÀníãÉO0Õäìô_7ØFÓÍJMmÞå½NÜŒL‚t¾Ì`ƒÈ}´ä¤–#•:Ô¸i_î/ÓVŠL}CÝ?„뜻n™§Å‘‡Ÿ•²ûÏ+’©²µi©5¸DbºymÖÀÃTçq]‘‡b2b8]Uç©ð°Vúž_…vÿš´ÝÑ´a ]‘Ø3¿>‰¯§Ž§¿žªGO³_ ^dwÖŸ&¥9UhÃ5#hA¾’žøÙ…øŸà÷ OÀ ‰œd«¯ÅªßxV¯Z}¯‹b8òa|ýlŸ£°] )ý86òc«Qáu#=QÝvÞGtNÉ‹ A«Õ5À~§¸#:~¸°²ôiäËá}ê¦Íð÷„èE³vT,$“¦·ÚTšk&d‘?cøFíugò[˜bÃyRª‘ W+"½w9@xÀ7ÔLˆy$ôדæðÝíÑ[·Ò¿x/Ó÷z9ŸüŸ"þÏŽMúÈek¿^ºZwªEªªQšÚ¿Èã0Qhªþî4¦:2Ø)s_Nýi=PûˆtI@yÀ¹㙌ªƒ#DÍx)¹íh¢&D´¯`×f.pñ$RÖwS‰òúg§ì‡ÜW_-Q|‡ÕVüߥ=’MdÂ#D‚ŠUCÊ0Bdêdÿ –÷íp¦êgŠìµ±ç©›½–xýŸIÉL‘E"Ô”*sr*"èϤø!;ŽùŸŸÎ´ûàÎíúCµÊ5Q7¦à§Dü>ûßîß5ÝéžÕ¯öÞY4¦f·¢RS*%-MÖ£½qùé×^z™E¹†Nè[NÓz&AÝE¾ojkß´,·\ß'|>q0yòN9vŒoå-º«|>Óïš7OMN³¼ŒóÜ\20ΰŒâñ‹¥3JäVËÓbPêTÆÚ£_ˆÿѵ@v\ÂÖËŽSœ»npšÃsˆ<}೦ñ§÷‰càAó}…BE†'+%zL™É#:6Jƒ¿$Ðþ`Ÿ•6@Š×ÃVŽ 4ü?ÓxÈ^Žƒ¸7ÎípL=yL¸f¢îËQÛ’‰Ð€˜(PF(›Ó/£O¤aâÓÞ´{‚Jh¢—yÌ©Ûw>'ž)Å/ÿ w¹H¾‚ùsöIø™*Ïa”67ã¼l_õ_ÀÔ0ŒTÛüW¡îLº6qÛÃWC±ºƒ¯cs\"Ð$¡ €qÚ6­¸‡¬â¼…_Éq–¦#DDß+rÕ@¼¼O­–ë°r\ª¤ÚüÄUu:rºh¤Q Fx»(u`ڇ˞JrÞùÂe9g1’¥°éyàb) ŸÓ #BE3#:Z$‡ä¼{7&×óÔP[ܲۯè[£Æl#h“´jP›lÙ¢P<Ù'£yÑDVØ„–Ù¼eØ>-¶·šÎkÄ‘#D/ð+shPa|çuG-®u°Q!#KîçëVBPÒ4̨ÛðB©´Æ·U@vÕš 6]Õ@%Œ•6;$G}ºÞ¹CG•2ä ã\FÀJ2#Dš_æåÕ»1Ÿ_¡œøj®|F·áA`|Æ{àÙBQ¾ûð+¥÷8é®ÿ/.@àýkL³#h3¹”qÏ[jš™~ž¿V"ZÍ(®Ò¿¯úåM¼¿H†áýßÁÒ/íã•ÓR9ºÀšf²åAÒ@¡R9ös¨±—CœEèB[‡£žAãÄ¥X?'j#hãرgn4"õ¯M¶±y¤¡ç." 8m7‰B†x;8TCûU{n»|“‡õÒó]Aé »kûõS¹ÓäÞʹGh#DOsÖýц÷ëŠæ6BZc&Z¤íRövmR FTZ>Ä0·}\ÁÎoØwˆC'Ž¹½ñ¶3Þ.`´¯ËÛV~ƒx&xÇ·)¹~$ƒ´Ž•á„Æ~ˆÆS˜×HC<îê0ÁpÅ„…6é|“›4²¦ŠèÂ-óÉZ½M«x¶·gŒ¯8H#D€R’ÐE<.Ü…öt¸Pţˇ‘¥X«°¨Ôó-öR®ß ã:Û$p!È@éo#:úæÍDHŠMŸÎü' å#D]Cš#hT¬#DݷϬm&^DôšŸ…ëeÏWÄüùÇÁY7ÄŽ˜0.Dï:OÃ]À/ó«J£ÍzîJ ¿,æÃãUb5‚‚ë—4žØ(í_jl[påí•éjU‘rðËŸU3÷>fUKý¯^+›ôq¦d÷¡¢uuQ:(bóiÔj&ô?tiºp8‹ƒÿ‹˜P'Þ£Ok‰²f?ˆwŠx‡FîéeÞ\¯ÆXÛ½f~?O"·íŒHJðX½TS‰s«^«ï3904\>m·]³F²å&g¿åÑÜÌ<|× Nÿ‰d"¾°áñJZv.”øó~î5›ë‹ãòŠšP=}@QEé·+§Òr,;òئÆ.¢>>ñ4N›·‡oÞ)ÇN90I¿šÃÃàó0¹é¡œÊ_Vö‰"cœ·Q®ê­Œ¤øŒ^áÒŠ@œRW‡;5#h7?ë|#D†ç·,r/Ð@®·lÔGdîâ‚ Ha¿šë­s–NÒåŽÌ#:ú°¿1ímQ¿=K‘gÜÌÇaðõ¯Ôæ4wÉÚ¼;0ï{Û™Naâ.”Ú#D<y3K!É­Éç޽ʷ_~:ƒ:npm6¹åÄocæY’Óñܘ(ÂnËzþF½Ïb¥Ï¶#:奄/kŸ ×÷a&௚êÆšÈÔ6½鼚E#Eog1fu·xŽŽ›¦[ àåØ™[Á䦿Xí½7ž( ¥HšÌŸpœÊžSNg‰Œ‚Hã¦Æ†½×áIwzÇÒ\Óù Ž\›á~f¨ºTrÜE=rÅÍ\C_šööÌë8„¹Œ»ªâfúÆa `A›.tŠrrÃYç€Ï¦ˆQXL•.k¸nw/+Ñ{Á7¨îKMþÿÛ'Ü€¼ôëÌ Êí»›UË{¡ ×Jþ«óÝMÌ.”qìE`«7#D´Á÷óÞv¡r@©ªÇ¦³ötÃ`uÜ‹xŒ_â¦å}, EpzŸ`»®Sá€î/Ó¬áÌŠSvn§yÀY¬ë¨“œym-°mÁö±¿“ãøo–ªæ‹g Ì“„]z8TÜ -BØ5Qîz«U8c¡¿ƒð‹þ 7ÚØMå$áõ‰o©£¡$d›o^hÚqHÍÈ$†¼zàÁäÀÖŠ@Û½"t;'Qd €@!VN[£ÒîO/Ñj‘k–Ý@€j2#£­öFÛ^ËJ"ƳE=š#DÚÞÖQ°´Â!€î.)nÝ·Î&+©xdżÏXIb»ÖB¶"#D>ß‚±Ýù—s~Y†U¿K喦Ƅ>wÌJðê^f0ˆpÆAPÓ$l™Ä9ëÎ{ÞÓ.Lâñ `1Õ²’³€Ûš¢s”ï[€5Ž{™ï®:†›òÓ)èm²gò~Ja5Õb=˜(ãá6ØØÖÂÛ9éÖ<Ð]>ÐLßmQß»µÞ‡&it@íoë‘-A0ðÅysðÇY£lu(~ï„GX"^§?€†I•Ñœ^‰ wÇ@1‹VÖl\!„Z׫J¨ë¶Áeˆ:¨ƒ àúÌ%·XahQŒcß¹g»&€ÅcduÏ/%ibT²G=KÄ#h¿†ê VƒÁ{ïw=!|ÜìÓñ÷yë»9kio_Pâ‹õoR5=6ò{Š"»#h d1óòŽé™¥¦&ˆù?3Úsõ#hSŠ:úÉ=’²{d½®›Ú}]ûzÊZB0ÚŽ‘As î,:5N¤vÉ{M$—…`á<’¬ü_Ç7¤+®qfÇ]4Æ\¸U€×€¦gáF|)§k„Øp˜dð„¼o¹›äüí¸\b[ÍHX/.ÅZ;û8þ??><º¿?4ùuEàâÛgp×Ò¶•Üˬ|#:ûPÿzÏõý~!KƧk>ý•BÈ£š¨‚®ïWë뛓îÇ’ävˆ!`0!ò’ÓÊÓ CyãáY}7}¡ÛÊþ\õîü˜ ?ïïD•GBªI$’!ÝO¿Îáv?7¾óÉÞönŸV}Ú<0Ô€€âf°Å€Q!©ƒµv#:$#:Ê»z€›¤4§[éý`Ée@Rcó¢î8¸Ô}¸ÃÓÝâp.›×w¼ðÑÜâé6l,TaW ª‚ì­*Ú{«qtË ¼Bi¨¾1Ãås>ßpoÏo~±¯§¾|¿øSGÍ’(ÉVÓI–(zÔ"›'÷…ÛËÓô é ‰†Âý¼÷4T?C¨xǺÃf"nçÕlnÖ2¦±"èº4cloqv§9RÃ"1ø"6ź=÷_¥EGVWŸ#:l.çn£À)(ý#DsNw®ß..}é#:äÊ”ŸÔÆÇìÿ‡ƒè÷ùë§puà+_V’]ȤcµGLŠ™`u 8€z¤sxoaøwz&(@ýÝC@—]Ÿ©sçêè¨Á„‚Bf#h#:ªÁŒQƒü„E’mç×ß^}üý½ÿÈÐ}#Dö#hižÝoMû¢ 6?¯û™±ãŸh¶ÚǨÞç<)#hCØ„ÿ~ƒáìýUºa$¿®ûû`¿X †A¤C¯¶Ú_ÀëD–ÍÊ€jíaÚL„‡³_¶p»€ÄSµõ¸Eƒ±ú5ý‰]ùd’ôÿŒ31D'ßÞnd1"=M 6Ÿi tPYBoÐOÇTªI"‰Í÷­&ˆˆ>nj½FúwŸ/l°æKZº#h¼ô#DzîtËiüàÞ_7Â)ò M ¹f¯w¦qß/™C"À )€‘icÎ^Y¤ƒ0ZOoèŒ%þÍþš91ëêè]Â7cÍŒ“  r]ä¹D>7ô䤠.¥î;à¢:² ¬ÐÍUT)p ñøxµ~ƒ«8LÕ#h€-`‚c² šÆ Ȧ©U’AÄ‚{w• HºrsüG!{‡$í 0#DV$êD‹Ù³#hkátí>ýßfä „’Kàö+ŒôE •ˆ#D@ƒÁ,Nw$¦,®]˜¡¸_ñ}>Uñ¯«i‹9;ºèv+ȯW-Á§0r#:#:ɱîÅÙ;ê@½å‰Úáuƒ•×$éÏ¡üþÃ2d”›ÕeÂZ(*˜Jõò×xùíö]Ít#:°y‰`&ýl¼<ã¯÷gPl.#É_9ùܬó½¶oÆœB"F-CHX Œ#Ê49y3&#:LÝpÁ›²Æ5ˆÏ”Ä(# î×ëü]á"'òåØYŸŸv4U1‹ ¨ªƒJÄ!#:Q#DíxF ñp[¾›'×}çýøøþëcÛ\½øeÜ0Ý!gUƒ½KR¹ÓðGî—š#D´#¥aúŠ÷n‚1Ñس¶„ÖŒ2e(Àqv©#:”&æúOhûþ­m©-Ù׆þÿÙ°tüÝùë¨vCyç+µïo#MQ;תFaŒÁVK&z¶iÚý2eÔüÙ5ш™²Š)vY¶¥#8åîA”¡Ðu÷»—7ÆÆ/+͹½?‰Q¦ßˆd#:ðÁ#hxäì¢r톙×ø8ýGo.äH€hÙ&2EÖ33"Š#:RÄÎuvAžbŠ@H*¼IÍ3·ómMúÝQ ÔïQaôEÁ‡BêqDD(Ž˜GA¢¸2f.Ô÷gÝHìÞœà@vÙͦÃ=sžqUjlD¯ó“ÓCµã+ÔqŽÈ#hwßÀÖÞA^B:£:Ì lÃ]A© š!™lž¥;µM„mÕöïShÉØ—d‚ g#q©w"iŽÑcVÁª¥#J‰n/}ùqý†sxܘwIé#:š„NœJ «$mcÚÔî¸G¤…#htÀÀxÙ¨XìvüŸÝÏ vf¢°Ý/#hº¡’ËWù2#D¥„‰9ä‚mF†Ì±"0PrI"GS£ëä¼¼³¯8GýéHïá?ôxAÖÖKü™ý¾ù|ðzèjŽðF4å—x)ÃÖ|:ïÓãzó™ôN;¨â©U¦ÐŸÍl#D£Zmû›qÁÆø·!fQ3‹-–Òf·¸×ž…Çv)lÿVéâ÷ðLþêΧ*ÛJaY]ž[gVŒ¡UZéQw%êýf'è.¶Wyzç̪{6¥y‰|{i`O© Ôxòb°iÕbffCzW ÖülD£&”#J/´¶Ïãaä)ßœíµÝ/~™ò¼B:Ž Ÿ¼æ„jƒ«¾°X EºCÇœ)ÝÛ~šI’cjÔF××®ü­FtªãŽ5•zÉ‹[k•AVhÎ*µ‰U[m\ÙÅêöÝ]4o¾ë%M£w…®Í y½ßL1œ¥p 8Ž!ÌÖÓ>dLqn­b¶58U,䦔fRˆÂÞS¥…ÏÞ…‡3‡#hTC¼ÚlKÑ»Í`Ö‰½dÌ?-¬Õ9ŽÆ àãaïNo’·¨¨-ñªkàÐCesP¦YÕ¸écQ3^CœÃá+s[g±§P>k0®vƒtûx‡BéîÛö£o€ä—”rHóäb«;iÞ14á±.ÊŽ”`QËÂÕ=ËáôEæá²¹PØydœÉJ®å#:Ù^&R¶QWI„„LD]¦BFù.ŸÃÕö álVó•²ílïýïIŽK‹ð8’TGRÇo9z¿'ðIÖâfnÚgªšâ' ôeº4š-.R©*(©0}F¿»LwS‘€z?Ï,ÑÂëÌ)r<²8ŒKÎ4é'2ãåΆ_NøÐؘÒ#DÈ‘ƒw$ÜÃœ¦¥oE!™Ý–{™áëÎ_àlÏ£4lì#:•MK¶•£‡x¦ƒÜ?üŽ*ö11U¸äÁ`ô¯Ô·‘q© ’šFc½Fm`)ƒ½æµÂÓ”µ©*½J<ì¤CÕÊÐ| j)¶›Z„WIÕá—î|n½xœÅáE–c ûkW‰ÄaK†¡E¢¡Ù5 ñ-ˆrâµÿ:'µÚÃ"¥¤™N.zsÓîÿG¿-¦¯AÊZ;.xïN›ßY*Ïß嘣.ÎÉfÉö‚ºÖÕ58ÜTMÊŽKCÞ]P检®ôP¹÷nä^˜¬©%çö_¤ùb¾ñÎ5Ðìxïd 0ƒHy9Ó#úxí×É­ûQâ5Öým‚ߣ#D4ÔH¨ÛmøÝ©ú‘ãd¹~¾gø|â13ýF£s×D3g‘ ù~Îëé͹Ÿî;ÝÒîÏ5e5.H³|‰jØ[í^“tÓ¬kÆ·ðÅÁ+êQgé#h›>ؘr®|¿Eñ°¥”uŽEÿd"«¨[N'œS°Wì—¹ªT¼a#:¡…‡†®´àFó?³×R#:˜Ü0êdrÌR˱s1ŠâpsÃüÉ„.x—!¯ÕÙw¸\±¶€0¨¼ÑÆ@,Èw.MÙz iÀ%c²­ä$½°|››/• ¼Ü—‘ȺF³l‚|ììzúk‡%]+¡µª0/ê¶i–RÆTªùeÍ#hÆ#:»ãþíµ¼£¶oõd aEÑ%äCbbŒ7˜(>õ¯lC*;ðOÁáèþž ‹#DÓÞÃö3ÇZ£J±vjVF™S]^;“ídò‡†.™œÎ_3­¬ #/Uõƒ Ó$ÝŽ ÎàšÆòIä¦lK„ÿl¿“ð‡ëÜûm_n”&­vŒfqšU ¡õ…3 ö7ʹꑲfdË`Iô2ñsÖYMXpoâÔ±ÀœŽ8Ú´ÌÁ>=\zU#hÉÂRÄ!$’¨‘›ÃýóíoÅ¥§¾º¥Óʬ/Ørð®¿jÁýüߟ D ¿žOû~eG{í´Ú¼ù5ÊñÖbew »rï7Yäs­9V\¡RT'º3z…!ÔR±™åãÆÞ{,‰?“åûq’‹L÷ò*°W<îÂø‰ÞxnñQÛù1߶tr™›gEýI™EK÷†rž'+8·îN¶šáù›ôÑM>_šw]û#h#¦—ÂL‹8fA(?×Ïù8ìÿ—óþ?áK³LþmHWšÃ”#â˜v“ÇÖ‘ôÞÛÓÜPÙnÈgâ£lü¿:÷¦' Þô{tá™-Œ„Hç#Ž9uPÙôywçò=–——SRëL±Ž4ëÓÔ2¶Lõ~®îÞØ°¡cnù\ÈÚm¼­½dc3 Û)Ì.h¦“L#D(Zp°p–Õ„e0‘Ž8«Zfjã5­Uƒ¦¬x³JšÍjÚóLbÊòg_Ìa²W؇IŠI!#Œ5Ò)xûœŠ6œò}øÖ]¢aÓ]Z9‘5â“ ‘+¡}ÑÌù1Â@‚g5œRwéÌ<|ÅÈX~*19b£˜ÒA׺aÁ˜<±å‡üÚåäËdˆ཯p~Õ ëÖ²fá' ²QB•žNå‡_Ê3î‚ŠkÏH(èÚ‰Ž’¸äÌÐH@&Š¶é­ˆÃI >óSIƒÊ‰¸¤ˆkOG•ÞgA(%´&óÌ9?ÊvÁæIÌï.ÐßËsœº¯Åh9ˆ‚ŸC@-1|Äî:Z›tt”f$ÎÜ̜þ,廫¨â5d¥‘3O“£ŒÃFÙ¸ÄИm„ ±Ês;Æ—$éÔ,ÙûK>V #h,è“Q¡õNI¼)Y(? ÕFÿËvín´©¦:B°2!J„#:&:o^ÕÀ¸¹—tŠ]®a +^îþó\íl^#m6ˆß°Ü¦øüùqÚz†L¨Îiœ[Ù&n½ÿñfhÉÝàyÜ77­㊅Å u—ž°&hPƒ d4¯ðΠë׈hoùo #‡€Å«šÉg¼Æc®+7O5ËØÄ3‹ˆe9sõÒx>{ j*–&ÎŽ©þØ°¤ÈC5ÁÅeÃÞ{5 ík^¿qùL|L¨×ƒñôL– âW»tÍåœã °V»ç ¾bù`2€<#D4~ÂX$n$’é¾…Y¦YaY:rAovR7 ðürÑÁéö¯ŒêËFÛDÔùWãÌÅ[sŽÞa­ÔÍpg#¦L¾92«Vʺ÷¸> ûAAIN×Ù†$Ê°%EÁ‚‚W$TJ²‘\¢üŒƒ!P]Å;Ížy<ËwZ…nú¨5«ÚÖ7D~‰…îæMc/tó;IüPWÐo®#Îïû'ÄÙ5Ž}åâ¨NßR$”ço´óÇÊMo5ñ,Q¼q\ñ<¢x_9“?ó‘7¯ChU²yaŠ^“LG’ˆŽ!s‘»1À5W{´¾Â5@¬«Zƒ+…Íâ#uT&FŸ5ž6ŒQàŽÓq¸"ì+KV§ªÛ¨€-]®¿¦Rž|ø.i4Õˆ‹HÖäÛ‡¢õGdn-&·òÚzØÄ`Ç´~0Í(æý,â Æ葉ªcMvJíôhG¦+ÔØuUr7Û/•-ì*Ž×ÚeA¼5ýW~Ý\]!×*8«“°¿¼º_Ó^géÄ©ækÂðø(W¨PT•L>w½Ïò9cz –qNTÝP±sÌÏ»»ùñ£Käæ<$áHfrvÒØ‘‚h.ÈYdµ 8hDÎòFÏÝ}R%6ÁÑÔÛ¹B£>;ì¸Cå’â1#:l¸C˵n£IvHä”Ç‚Œ-©Ø*½½Öl#Dê×Îgnþ¼¡#„6™Á¯Þn–*+³¾ùºÆ™ j\0hGîóãCàEM€·nÁÏ'Ÿn—Ò·ŸN[=Ë;aNßi?”Rõ‰僖»Îit"Ò–.3SuG®N„B²™Nšþ #D·Š#H®@Ž:Ë""À?xŽã—Ô¿bêÂ/s@a ît §Ï¢ñٱݹ‰ÕD†·h5쪒”%ãpÄ9ŠfBÃâ›ñø¤„}ÿcšùs)u+­¢®¹=®0Q¯2â5¼íƒàªá¥È7‡D›Dÿ3á3{¬òšú¡ BõÕõ†üCÜ"*¤1y†c—=3(–PíÁ#hfuÁã2ïË{ƒ##D¢©¨å ŸÊáBù#h¦ƒo+(âS‹ñyRÊGyÕgJ#hl‹ì*"'®|µjö³^KÔå5@€ /#:⃩9¸Ö@1ãËtøóa6îb©®Eêã£-^3“Y^7•ÂœO¨aFVgå¤ñ…̆ßàÁˆh‹–pr®ÙÖRÍ~<±¨©ÏI¾;7­\Çi@*ce¯4J#DÙLXøï„O2û߉™ äÛÙy\K’GYS2æS2Ê5Ê+,Tb'Œ@R(ŠÝçÛYaežíÝÏa»¿áîó1 å2 é‘‘2u5§êËÝ@úY}ÆÎI1m>VÐch«cÉ„Š}]PÁÂ>i½R#DÞµäHï/8ͱ­¦^ñããaèáéCW¶#h5Ú"Nd)Ê€BeI¦ñ#„12I¨M4¬3#håÊF˧2I­é¾uØ Ø¢M"¢ŽeW3tùùQAû–[„Îã·Ùv0É¿,{N ffHn8܇ÖEç0ÓÛ7WéÊž{w5Ÿ·Å‰IšØ½Ê¡&Í' ÉqÏK2g½(ŽB…BªW‡‘F_  ë)zÅ^ýløq¸èDÌ#DÉ)o®‚RTØdÈ!‰¼ôôó9§œ‰SÊrðà¯û<¹T¤% æñ{Ò$%<}B±ƒè½Ï‹O*øøÔÅRÎv§`NÄêß^¹FþwAþ\³B#DéÏH„—¹]ß3¶î|ýº,‡:¾ûÃTq’ð#h„,@îHÜ[H8ËEE"b¬5¡äIÉéz &[pYðØ “þlÂüÅâC‹ëO–üQ£ÑÞ|‡€G9GsÌß/0(‰0A#h#hÜ”kàz©“ì¤ìkLí´hw[ªÏÅTƒºì6éÇ$×€ÙͶ‰˜Gè8]#¯;ž}M:#:ñü–NXô¿„5æ­R¨‡£à …O?éŠ& øÃǵGmj8DK…â¸-V‰H†ÁôÀ¼‚@b²U½Q}Ýp¦öý¼Gº-D¼{´6Îì/kD¥Ço%ଌÙ'.7‘R@£0M˜¢ˆ• ¹m#hlSxõw>ÑgJÐrxúˆÏea®·=ÏD±’©þtƒP™hå.:|¼?‹-YÎsžº Ю‡‹•OÌ»0†ÏRû¥v£Ê7Cnů;fFòQ[àë´•Ôw1ðÆ€OF~c ºŸ4AU ¤–æ«ÔæÜôð¤—cÅ#:ôD+ê:i-7#hï£dÇîVŽ(vB»0™’”kàäPYò-dXj[Þ–-ˆ€É'#hš…]7!¼/PŒHX¸X5+„FÍe>øüÿ^/n¬Lü7hÑ\ï`׶ê²å½^ía‘Xçš… ”X‘+ˆc!FÆ4a‘GÜvÇÃÌÔøòC?‹.Dÿ(H€ž—cAÈ©y{ó]†Ý×S©á"`å#hŽf×Ò®‚ÁqMc- ¹—kj#:6Êtggg#h[÷ï?£‘™ÎÆ_D“£ºír¦ß¬Ù¾ò%„k­kÛgÊZK:ƒ#hbq1çQHô`cÈc,#D–¬¡ÌŽøsŒïB#ý–VÿÍa¯Ûô7çü·Ã̧M¨zf'OMV‡pº`:ýOä;ŒQ'%¯ãŽ=øÙ°Ä!ÿHŸÔjw¾y›Þ¸‰|ýNõý1‘C0}¨ÎÓÁ;º>'aÍqËèكǕu—'ïæNS åË;’6ó·âL”uçp |ÈÝ3äa¢¶ª~¯lì!:ʲi6äÏI*#:üe“L3Íœæ‡8[ƒÍÄ")pqm–$a«„5b[ 2sDQ‘m{"PêôŠz¨–åà¥á#h.]«ZûŒe3²æM±¸†töë{¯gÛZFTt¥6èJÔD@†$=ï³.©#DÊ'qŒkZˆ×`¹>Ô´ÆŸÎY9)‚>#DÜßâ»j;c®äd4Rª1Ç`>Š`³[Èi´œâÊ—žMQ탤%I0]ouÝ©Jf¢o\·V»ƒÆ­5V5¦’€À=wf¶Ú–ŠAr[Š#D2å)KG>Dp`ñÖæÉE‘ê4UÙf¾š;U¦³‚ì‹À£³e¼.FWÕWâT©ø2ëvu&!B›˜ì¹™ZÒãWáê0^ÿJ×….p¡œÙi©\ýXåÊ`ÊåUtß(ŒŠ´o= ¢QhJ¯è¸G5•Ê&’~0?ßÏVq#Dí¯Àå5X¶½—¶mZo£j€kå¬ù¬njF@«$ã#D6#DÂNÞ«±x[’‚æ‰;Wª–ÙÅT‹až›#€¾AÛR—FÒÎS¶Ø9"†“‡4îÚ.€üsß:V¬'TÏ.ÊòøÑò6|Dëtä4}w¸ß°?¾J 1¢ã+î|†*b܅ؼz’ÛîZ¼oC¥ÕSrŽCÈ£Œv7—Ø$[–EußGq'Ú<ëµòæL3£¿o„0“{zûsBŽAäfÓ™ÁuJ~Å\i–Ú«(i>:ÀžI0·ÇœîJûîvœ"“‚‹•°Xw § [lnÀpöS ³LQ‡¨íqìû4Ðh—a÷‡vרg˜<‚‘½À0n³¥†J··3¨/éÜ€héÛé§ö?⮌ìi-þ¡¬7ÇÛs°v„¤<6ùá“ò™†•;g VJù.nßÍ#Dm Ð÷׿¾™Ñúù¾òYÐî…‡t„Ñ¿Ý1ѶÅÌ› œ§¬à&¢T†)KÚTÓ a8ˆ  ÷¿â7<Ìs/}x×¥ÑÙZ+Úü̇e.6ˆ-/µ«®‰øvçU¬ßórq=qª«‡ãD<7ý#DPnhT@CÁ’ÐA›ÛZDþ½HJ#:]£ß0t¼bÌóO PðÌÈKgäU3Çæg5†äç—S]ö.dÏO×|«9J.('-°åÜÁkS0«O¸Ãäæx¹nÉЮ"ýuÓA•ú=C‘(€Ië›&¡€åÁØl ž:\²¼°’£:§¼m—ýáÈ\¾üúh׎2¾{¥¾ü@ÇLv±DÖnJ¢Ð;”þ_füÉŒý=ÚwôpéãøæÝä‚Ìâýã½lÆ=µ%©5wI·ZrJᩤµ˜3‹ÕÎG¸·7øÅ£#D4rÑú@Wuœ?U#]æð#xÕ¨·30P«¢¬\Ïgï´,@žË#ÖŸ)ó[Î~+[>dþ dm“/Ë«lw|’"‚1}1aqm•ÑB„A­/+mã;ýŠÊnÎg󽂼<2tú ôKˆC±Tc¾êÊ’žz(ºXû#†ø©t!#hµ)p[– –ÕABISõÀð$ÉJ@ÂoS5á6R€Ü¯–¥þLfÛ;<àûU¤©e(l@ð–dúI Ý{ÓHa`¡TÞظs¿jnsI9Eìx.Pý-£¦,êé]…nž¹µfൠ÷à™—ÄE3öSoˆ|b&f##<7‚_´rúíŸÃ;À¡úÙÉ^ÉRŸ¾Æ‡„Á~Ýù§ÉÁÀ7¾=m»3§’rÙ¤’@ªšŸæî—>©ûÝD¯–^üU¢[dùÃùsŒ#¶8u¬™ÇÒ}'ß3°òWQó$ÚHãù[Åö=šôÂvŸ^wlùÀ¿‚t+fÐè%<|Y9ŒóÙð7#:TA´žSÓø]°Öƒ5Ç—~ÌIÈÅe$(‚PÝao¦ê Bž§M*&Ɔ‚ò‚·ÆõÝ;YÑ˯Ë|˜‰À°9r#hM²täýÿ%cÐõ6ðžñ†¨¬cvƒy·12. ±žçyì*½ƒe BkJØGáª1‹æ×#DRHÖÔ' ƒY#Db×\UÞ 6Ž‹r2¸®¥#D28ÔoZ¡l æ7ID£š°ÒÒákÜ{§;‹4åuÍÊÕJ‚ˆ0ÐK)”˜†y}W«»¥Fg§æd´â¯Iú÷ë¼àoTvÏËovM3ëߘá9"•<.D ®ŸåKøþgÚýü˜JQk^œTvd71ܸ5õ½†ª:\®½õÕÿ»³k®aEq eb¼üÌ bñ$y,Àë#Dä WË8;–3£Šnѱõ,Ã^*7óm»ÖrUÏ>uí¾8}&–s¦#hå:$$…ÌÝ~ûO}}{w%v'ùNµWŸmaèïýOmòÞà³Îç¶o»Ï¦‘&TÉ“ rP°Š¢„ßáì{ýùp{#Ñ•Š>·ŽuTàáeüÓÙ¦Z(¶S#ƒ‚ƒjÅÀÅŸT¹@Ÿ&|£Ïh¢vÙ¾6¼pnÞ·…2F¿.äYèeóEoå)ûc´È©ãá<êñ"Ô“ˆ±ï[#h¨ ! Â@×ðéézG/’y·âû7ãLRøÇkdfxLWç Xë„Hj{’ô]8]æwVÿÑÛÝ3Ñ ýÞ†~Þ‡–‡ôë¹Ë6=¶nìMê9ºã­$*ªÊANPª¿±…Ûá®aJp:ÁP‘Ò9 ‚¸M°:zù/öGÐ禼r§TÍ^Ú« £ùÌ|µ–"#DÔïÄð#D±Û¥$0Ë/»é89ä üÛ#(EWVÜǗ؇ SàG£µ]ÙøMQùšÄxôܧá¸ÃëÒRWå˜+TqÃxÛ6¿HP#h}- îHA‚Å Ò“ž!‘©!•œ÷zfŠêÒt˜ ÷Ѐɲ³æ6εn@0*äOZ*e&ø:vƒº'R2ÊJ° mLfJügìÇèÚÊúLŽ61³â0 üß”xˆ€ Öçô&l¨A?yÖ `0Ôf5ЈË²#DW–e#DŒ± §þðÑí¿·¥‘…Ëh¾hõÇ»èCC˜éY `(Ü·4(³¹\+¨Yxλ®Óª@@b§-ñ‹À<ú± E®Xº®Æo€Ö#hÆc|†™û¿ŒßO/æÈ^]Ÿ™¨s ”9Ã.·AËzÙ'Ú*3®7#hlY D®d¹UJ´a¶÷>ªX‡ ÿ†Nvxú¹ 6N¸¹J©î4Ú£ôCH˜{ïÂÛæðØ Ñu«M)ªר*Xç†.EêZ^áEph(À\»^À‘¿¦ÒÚ&åvέAM—á 9±¦0²'ãnωô'Þ¼ä=gamÑîfÑä8;ñÁ¾p‚ßçꞧí¾ß4|$÷ªÇÃ#D0Îv®É¬mhìS(Ž‰®>×=¼úÖÇ;ê~ݦNÑÛŒœúx²SxvùŠÛíGYë+¢ˆ¦»‰ëðÍ‹Ñ#:æ0„·ŸQºó®ãËB[â‡>ìøs*sC}L*߶ש-"d{)œW®sVŸÙzPØù]únïÏ9NHrˆL‘sî'‰_i¶¼´w]Ö¶ÔãyGkÑcµUË|]¢ŽÛ•¨É¥ˆqw'ƒÚÊeLRhŽJ‹•â!`IÝ ¢œ†E´â¢‘× j¦JFÏ<„²!¨EM”¯=i7ÀŠ5ã+>è#‘0Ò2±ÑC Ú 3gC4=ø³lT®8Ô/ÊÈP°·2tz“áÓGƒÈÔ\éꦺZ G$S±g:ùÅç q’'³ó·åV°­ÆïÉ#7º îpó÷êgqˆ½ý'I™0¥ÁÜ¿Ï´‰zÙDpÂUñÔ×ÆJËßMú#Dl“lïfPónpÙ3™†`~=~=ç…g†áÏ”ñО©#hõCG#:{UÑ¢*;Îä G’4A¾Ú¶¹MCžûòúLË”%{s_>M5¦…§{LêÄ•s ·˜‚GSËê­1¤8¤w)4'uzɲͷ§/ŠqiPº#;ÌÞ=MàÀÑ'#:¨íÑx(êké݀贙‹ŽÈFÒz‘›s±Ðt^åÊŒòCÞÏ#h ¢•Î:oEW „Æ2tÔR‘ïáûÚ'X/Ԣ罟H¥žc¤Qè¾w‘1Õ4.FÑçåÈýðbôïýO¸w%ü‹VÛ /ðXæölê¤j`øPX¯šM€!ùTª_ gns{*_`óqƒ‹È]Ž¼£m•áãWõts_DÙfyðÛtãq×)’€ÙÏùa,` †&lýnV½”<Ôô”ÁÕVxýy-¯ŒÐ¸wMèëÊÕÆ9%®VãìæþåFø7~kƒæPáEµ¼Ž=i!Ý=}³?ä½u2ßw=t•WÒ.Iö¡ÿ¸’ì®0Ùlfh?rӠɣ™e#h#h*(›UB«ݺ]ÝÖžvD—\Ñ=Ú†(S%$‚ƒŠçþj¿-.§ñyœÏ‡Ç—ù¹ózÁŠ_Hü`î­šZ3û3—‹ƒ ççÒ=œ¿>¶zC?ô’ÂÚ;SÙbY,gÿBþ ànljÎ5#Dê»Dåþ’l§<‰²IG&~|±£:öç·Ng®¼¦/É"ôº9^2s[©bM§;ò-„.¢›+¦*ü¢%öÝ6¼ÌÖŽ¨{ïh6¶VÍ%™Œ§Fk²%ÃœŠxÞ>Luän!ι…¡›#h`'êÃdÄay¤5ÐtÔ¬bÅÜÑÖh„˜Z0TQHŠázÓ¦T³Ô ]xÑd m“Èa’fj4yë#D¸ê‚ã}úox£î•; šú©*4 çxå\¯'EE ²8 ±%S40r6f@¤92Š¸›t2Jd#:à;ƒ·žœQ#Dƒj™OCK¥iˆB%{9L²¥ b›ž\ÛÇRמê__!Áò1ÁVía¼OavRLí$™½†íH“‹ãâµsciEõÆ8èû#:Ιm1œŸŽ/D v{‹[iÓ?çì:ê£ÖCd·OïsÞ߉|lœ¸ÉZw”v7e|,‰••ü<¼|¼ï³¿ÇÏŸ¼^©’#DW¦á«,m-X¹2¸Œf€:ØÕÅf„#Ú,ŠQ]·ßõHx6HWm¬íNÍË6s¶þßi_âäŸy‹Dz¹B#h´§;Ýýô=߃húÍ›#DŽ#i>‡½‘8оªºÉ¨‘V–Ї¾Íž7¨AÜòfWÁêªÂËIEe‹÷+·¬x×ÆYÊÁ£igGVèæé(žáÓ1ît|ýžö_›$˜£6·8±½#DAB6<¶=4ÿO+×4lÎF=¶1~;IJþ I«£ ‹SmdD¨2ºó€" %õÞÌ–#DšŒò†é¶ŸËË›uëÏ>ËÉXh@ý½ÿ÷–tm¤ÃžÌbPiôÑ,Š–äóª ck5kŒmúpC2 ríÜÉ«q€ÄÑŒA]§· KÒZshìþjFÐÛI§Y!s´õL=Ò£'¨r„î8 Pò-ZLûH¹Àæœöá»&8¼[†H÷8 °›(dLø!ˆèŒòµ–sþSaJ±[Íçl&À`^.!¯1M›'9ØâìÂï#D°RcVíåwåúÎ…ýyÖ/·xÆt1¾{Lpò·Ú.&v®…þëù¡º*LÞ[Ïgí uœÍÐ$Ù N™œáTåÎÒRdPdµåëK,a–½ ç¨µ¸–•Ú…b(îLg7e%¹×µí† wJÆÜ“Gƒ°Êe±šZ)S8ØýGrÙ°9²/\ÉíÏ‘äß"U•ÜQß¡´Âl,ŽrWE ³B^Q3r¹5·,Y+¨·E+6b»¼Ç8dbRy©1:*¦ËÍ™IZ2‚å˜ãxÎaj¢õH"cˆö‘ga·l;ÄjÎhÎ7ÓªC•ÚEuÝGºœß™Ð®¢øh®s fƒ9¸lÔñ¡²˜rpŽôGuP9/´›~zfAËm£Ø€ª2yX¸›%qÆ(ìJv+Èàî*™ü²`ž[UãÏÒ¾ŸƒààÃ=¦y}cõ{¯8z„¼äÌ‘ìèQ~Ù?%Uùq¬¦±Bà¶âøgÀ`ò9_š¡Ú±öì–Ïé³Ô…ŒŠ‰ÝØãÓêÃ]"{2g1LÄUxbÙìúô²Ðú}EÃL×Eæ÷vjw¥ÄÕÎ,ÍÔÖwËMŠ#h¹±å„÷˜SnŸ û,]žëÁÿSú5÷ûà²ÝŒ#³ñÝW{ˆqöû"B…éCq6Ã8ýcˆû)½emϾÜÒæ!0Úù­×Ü0—3:v$Yiw&+—ÅD£3gw£iÓéëe¼2gÞ{1åÞ×€ò QFhpÔ¢˜E<Þu%þÆ”¡`ƒfú>®‘mù-¬^Îóq[)¾+uumÉm(#DíÐecý•ÏÝžKáñ/—'faÊ˱äæÍ”sT¿g+éœïœoŸSf§s"Dn‚:wëˆmÅI£‘Cåca“ßv»xG¡Þ—ÚaÀsË@þö”§Œ [A¹gÜDOg…pæË”%ˆÕƒì‚—#:aüÇ Ö§çÔsx×+·úýkÏ£“iɨýÖ×þ¢a‘iнûg¡÷„|°TSÑ/4{ªý7Þ5–gÀo—æ¥(e‡û3p¹+nAzaïË<£@>É2ê*)²Â±××öIÄvEhó'ePKÙ#h£R؃K ]™Í—ÇÕ¦§Zzw¤]êŒl•ôc9bóè´™â éÓ ZåÚŠ&^•f%bžgJ<µƒWÛíC#:ÓCÛϧ~ä¡ ¨DX,Rn]~®•ŽUÀ†¸ÍìÉÙ21Vn¨[%™ÑŠ#h„ÔÞ½óÚVÏ‚0ƒ‹Äo¶È0ãør·Ë0¤½ñ/“]ìÓxrõ‡óEÚÿ*ú+“•$l1È!ŽS#:â5#©2C‚Wc&‰Àï³#(éüøD§Zÿ?—ù¼cvg+PR–ÑPl¼Ð¢ja3M‰0§‡ÆrôdBßå‹7àI"DV—›]f-‚W­#èmç,;²Ã8и`B#hˆ’k–Ûüçíø›ÿ9·¯8û4ÿ#DÅžf€zá»öøÕÔv«Oíß#hH(eÙ¬Jtf¸0„âbu>üÄfÊòL±ñ #DoR²Áˆ§£N¦ÕÉΑ±KW¶uÖÑ´ðû±#Q§Ä5LSº^$¥ îŒ”Ö°yƒváÖ²Î#hŠóÆ’Öö\âŽ"ZÞ#D‘™ˆEj4<0å6”;Z úî}ã¾Ë/½þÓQ°T¶s#hÊûö®Vô#kÝÔ'ò,€í œ Ã>ØÕN«)ª“»á‚!Uƒ4;m\ÜÄ^/à ŽÚ‹ú‹²yrB‚ö÷þl#h[Ùet óø[²cätIü¸¹í¹„o=¼úÛÆפøí3³Š„Ò‘8ìUB­žûÕÎæ>>!ÜíÅ™6owî³¼ŸŸS|#DË6œ[Ȉ Š‡Š²Æ[M<ªº¤‚¿-¬¥4ñ˜yÞƒèœkyƒ uE'Â!AÍgCW,Eª¤#DbJN,ÚÁaˆëu¶•”‹#D…J(QE&‚eÑ;;â³f^óVT³#:;!,@ã@™5i>RD­ÅkJåXßóÆÍÑ«í|C}éÖÆ#‰â\â &äT̆¨"6¢ö‘eÖ"&SÂ(}E!SÚÛ13[;鹧6ABŒ0ÄaÍCµ&áaaÈýAu䦯à^¨Ü/Ý{‚ÄÓeyï‚_J\F»”Ó*–±ýrý.ªCÏ=Ežt‡Î½9îAT°c/CõÃDóDMb¡£O®½†±ûqNÊRÓ¸)úâLV'ã#DñÎ!"–øSëômÖÛí¹#hë|rïø#X–œN·ÅñyÒœÉër»7»x>¤óŒ=4Jg²×ß÷SE¶>÷ã˜.ßi‰«~Þ®Õ.‡°uÝ ù®nˆs ÏÅΰèç:?;ë1ršïÓ~ÜD{3„À#eØ3Ë]Ax®ƒ#D_æÛпí#:ƒçˆâ¯èîîþ„FÌîÔ{á¬A²æx$b05Ö¶€" Å“˜úHÃ#D"î±Z\ðC" „çò_Ž.R^Ь!Íñ£ íü‘çëF‘麽n¨ŸÂ;'뵜á¼#:Q~½c7<Ž³¹)Á6HŠ™¡“‘K2HÐà£AšÝãtu/ÜÕèxƒsöê¢8v5·ã¿¡3¿D»¢õxyÕ#:쮵¬õ?–³¯9ÉÉï©qÌD¦âeþý1·ÓoÞO2ðA„4¤{‚„$"°=ôéEÈôÒŒ­ã7ic« òJÉ1ÞdD¹¬JÖfYg§óÙPu²]N5-ýxŒ Ë ñ»q‹T‘Mʽ*>p÷¿…}µ‹ìÊ3Z‡õ×ç.âeiXÏÙ­-'çP˜¦GºW{묠KÕXI„¢±I:òª„(4ÂèÀ¤NÂÏ|ëä§#D6£S9@SÚm÷ìûf·¡ÔÿaG“ó1Tg’:Ymœ‘‚Î Ô;Äi…a†=!?ˆnxç<é³íü°RF37¥ŒO9Lévv?_&ÅWSªõˆ#Da#h1üÛÒi=IÝõ«Ç“ÿànë.3×ßÈÍDìpA¼nƒ_r¦V¾ãmBÿ:,«M¹LÔ9 ß`\h>©ã1êÇpv„ÉKã¹ûsÒ‹Ó›áÒ¹Ÿ.þ]ñÜRFu=öþV<¢[ŒO*Sv‰7ò ô,ãjg¦^·Øe“)à[ì䟓Ú#w6žðµÆ™¼u¦Û=œgB…µó¥Û»[=ðvÉ„rîÔoj%½k”mZ•;NJNZxÁ=êÑ°gYT”¶MsG…•ÃÍ1Ý@<¡P©i °#h”Y8(UñŒ'ÓZ¶pT¦(I«<6üuž³±Ws~þ+ûåíC–#O1ª³¡aŽj½¼èÜvV5æô»íÝ,†9Ç{¹Îr؃·Ã~c0hë‡å‹J™zèd¸À‘×=à“­0«Ü>5`ka%°zŽ„wâÕ,#:ß;j×Wnîœ7$‘ý×í¦1™<½zÙ4xcu¡ß³áë÷øæÇøôÊP¼#:ÅMWÃ@˜)Žþ-{ çrÊ#D¸Ërßha›Ã÷æ g¨8LŸ'*òîe4$LŒóE±dÃiŒ/o¤SÈÖiPÀ—.Ç(Àd2&ÉÞ¶‡5VøsƒÇ'ÓŠõû6¼c–dzíÑNÉÝÒ·ùŠ?ÜÛ;Ò˜|QÈ&÷åšhÒNXÕvn¬8ÆUèµñ“£©EúàA7ºeìuïŠgr®s<¯ÖES#h¨1#hŠdà¥ü#§P:§ ÍK‰f£_€À¡‰#hí‘V¬ªÐL\2tèéŸÁüƒ³àÕuÏnó‚\sÛ|ypçu#:½RU§ ý~Né ߌM`Þ>îHM?ÑP›o•H#D@¤vEÑÏE‹ªDUTVõ ÉàÄ7î÷Roâ†aþSánAøCÁ8ÛÛ¿ƒ3µÌ1f”JÒ0\ËÞæªÐµ{òHq×}÷CØmc³¨fnt« î¯ùoÜñÄ…ú>x‡ëj]µ)|,3nÕöErÆ-¯E&ͧ©icäÙÍë´Þ†Ê#DäG×5½D¿OÆWu½UÀ^ž-Š•`øß~+‘ , ^*¢Hˆ) ;aìï÷o§=`Oé _N½lCY m« Ëlj²¡ÆÛâi¹Ç*ªS‚“<¸Ã¼ù㮹ëè×f-#MK-Z Ê#Dà;.'€¨%8­ê¸âðBªà•dŸQr7®ÒAåÍ+Ç’•C•±ÉémM¹’ë¨è‰Îã:ÂBâ ¥ä&jÌ¡I_vG1"Þ8Pãm­[ª.]±ÊHàâ‚ýRçIÒÿ_V¡X„’‘â³\†iú*Ž/9v¬3¤sžìé<çþJìfa#h˲FèvÙ¶n›?îPVyj”ÆúÝÖ”q^û{(*.œÎuæsôé¿LQÉé³9Mxº¨š¢“Ò?¹.iÆÔs\gFj#h‡Â)}/÷OCž©½7‘õ]a¿bìômÕ¥Ù6¶RëE¬ùý˜yÄèéf.€fszE¹Ì,ã¤\: ÏQ»#¡ºª›ÁKã£%‰=lª«­j!(ÀT?0ˆF /áùêùBF‘ÄÑ®_I†‰7¨ì¿ÛSÏ…ÂÊ­àûÝ÷~îÇ •i‹¶T>]7Ý»?™‹ɽƒÔ à¦pÅcpmË€#Æ#h¯EnÒò%©ãwvôCž2©¥Od_ÕÇìßrÚDÈ£µÝ5ƒÚ‚’õÛÁ½½i",…N¦5Zè!6a„žÎ4òvÞjSãæ…šß„”*µny^á©èƒÈcß‹[I]Ó~M<¡à=°V#:EG»¸»ŒâO4V2@öYK#D*g[=ÕŸç¬GœQd#:‰¥ó¼q4y#:Ͳ5KÔ‚c‰‡gž"KtöÎ3†š`kPÎú£ ÍÒ#DÆDõJÕæ:ib¨{éé9:½MMH‹$#hõÝI“‘ú=XÂllë:L ©Ð‡“ŒØ.N;„æÁ•®wlØ0‹3Š‹sÎcU™HqÇ.l®¡™šûž<ãˆa·¡•À°í—1½GCJÈF#¥ÙtÂ(] 'M[¥ÍON7 ÙM'©Í&<€FŒfÔeˆ¬éh´Ùø>Í{³žÃÂAnùšgZ§3tŒÂJÆ\YŒ» Yƒå …[…ÔïnHv ‚ƹÐ+$'(p,bê-1h4ƒF(²,(D2¬º¦.òñ°§“41®ÌÆ«ár#Dc (On½ùçuæ6×<³#Ú11¯m)\Šo^WX¤l‘MAŽ¥ŠqÍ]Û?JØ–t–ÊÁÄ8¥s‘jå¹&ö~é{É~]]q2³}av´MpÛ‰hs‡ûû%Á‘€ÅwV1Œ=Ís`ѳÆècù80Þ4â*klƒ]g…;+dP<8s蔣8ç^x5sPd½aÒEA£¦,Ã`D`4ÎÚ‰XBAæ¢5‰oF%ÕžÃR&BÓƒ¯Q¤ùy“­öE¤Ì,üþ ØyûdÝ.xª$n7…WŸÒ¨œFøÂ÷7pé‘ †Òí WÝE„”iŒÃðNÂQõÆÔC!ñµ½–ñä ñäÆÇY~;s¯›ãvé$„#—Ú#:u”2Í/z£‚ø”´Éœþ…Múõ5!ZFiÍ¿æ‚ÝŸ'-bÆ<ËŠ›LjÕ­n¦qãX ß-Y,#hoØS S*ײóè˜0(6ü7þé)S·”6:(A„m‰]üyY¬,µO’ª•ÝŸM%Ø0U{zcÇq]J¤.ªBØ⯴’&£ƒR\[˜àFj°){hãTc‰~wK[Å|xi3aU8EnšˆìshÂø—²«PŒ©çÇ)4÷T¨ÔýÝ‹ëG‘çÁµç†ù>+­à£´EÁ¶Ô›2#Dm½“ì~ €c½PUêàüìXB÷ˆUçMˆ#: k‚#h<.|°F´_4“§Ê·¾Õ„íßEyO™ü´AI‡9ÙœM·–ôºæíD¹í‰ÙUsZ|1éäq$ã\¸àAD2ãàcôÉA6P$B¹„›ÊZ’a6ÅÄŽø.ZéûÜç(ZFëç4oY ŽU3r>iæUhtK 2X€^ÿsÅ_oèé…]Öbý6Òþ1œhÈØÂÜ&Ù´Æ÷¶K#DŽ.²ÊÊk©¼“?Íößà Òc~{E^¿›;QY ±é½?n’ÜÎnU8²wìÝ{ãÁ6û)Nn£cÆÄáyŒÿSûðž#h{ÊmǃCuÆ9r$@½ëû¦9@áŠiJÀNu-%ÍM/²¹ñ]†4…Ab#:HÚ6!+¹Û¦®–å®E¡ˆ„DÑÊ:#š4”ë¥iì£Ø§''¦¹ÄˆÒX›ÉMM¸F³Üw§V ,òò á1ÂL®_«<Œ?Ü÷–¸[—õ?«¶}0•)›gaaM™n{î”)­ÎÒË+M Pv ˜žÆ¹á‚ÈíóߊñÈ£ª¯WnÇ®Ú;ž†#DÄIŒÇŸ‚+Ɖéë—vŸÛ©%3òì9àÑÓçO³âþõ싧WV™¡ŠD«ÊA ¾éÉεk¾8;0ÿ¸TîÉÛ…¯ÖSöQTd 뜛=.Ôç¿Më¿|sY/îÀt @™&sOºh>O–5ˆI3‰ã'u2öèÂu£>Fø_Ó&0³ÃàóY§´ª^lùâ§Ë•)—/‡˜Pa2·›cú/e¹ËÃcŽlg¨û!Ÿ;@Âxç'® ïÙäR Ì4Úo;>”ýæw|Ï–/IÞõî‹;Œ±–iðà×Ýx„»Ä—ŒU³ù.ÊU¦ìͺË~ß<·wÃ3#DL•°ÝØIÇxfÇQ ûž>öŽÅÉÙr|Ü’8X=4˜m6½Ã€´§OMÔA »ÓE!ëo:‡í¦Èïƒg”RjT¡x^è,Òm¾º®Ó“zÇ°±ÖAò›ôí*dN—Ç2ÎÖ W`T¼†IpÀIÏl[ŒöN,¡lk5Ô*Òf¢A¨õ<£¬·©ä·RÏéÏ›#:–Ô¾×N›ß\ÿA›óòƒCtwq5Ä?Ï!íìV#h/÷oH‹œs³ˆÀ˜ )!c亷hç@ª…;yœ^ŽCáb)BqÅÜç®=žŒ,çˆüïË °3X;|Q >ngÛlíƒzãZ}zØ1huÆ7°I…¬ÿöôz9o¿¯9ÊCJh¢‚“¤zøslíAîrᶶÛF~Y(ììVµŠÕ®,gËÉóC¦ßð˶¯¨Äè!-Ÿ‰œ`ÉŠâï7B¬UW»Y2åCÇOˆ•©l:F.·Ûæãm”!a%G¥—YYÀÂÏ¡ñ• ·— aJÅÊú*¶÷<:0ÀŠÛ#hG#:ÿÉH%Æuà ƒÃð%òŽo”.¾u€™&ÿGßÃåliÎöça¤i†N¶q¹òÕZà¨ù×#hbŒ£™ÞÕc9ÇVn*g&°„úûÖh­âq5ç<÷Är²©ÒyÙÊ¢]‡æa¥f§QØŽ‹6ÚQO­¦¶æâ;ëˆ8Ãb3µÏ¥­NpõO%…JŠ(‘J‰5ÆΕ¥{–IrÆÏI×%ÚPII’n-gÊu¤ÆªÏ^/Yµ2¥ïÁi¨j¦9b'„®‹ï§¹~µÎ ‹ßk#'o7bxwÛí;˜kD,0²oÜãWk±$ ÛWQ[¥/”cY¹Ï|q¾¾\ãï³”ûñ$éc¾(KO¯3{æz·<úýóÉœqΕӉ'ìá¿Üí5·wW=Ï,ƦºÃÃ,ê¹®aãñ'Æ )ǘhtAÛ¾h¨ÏšÍæHâsŠ8ß^±ˆîŸu8ñg;DÛëQ6ÚˆðxT‡Í^˜Ÿ><·ÎÅñªáfJ8›#O«­Ízw–ÂÎHê÷Z¢<³«…ÙßSJÉÌ¡A·V“Æ`¬?ŒMáïÔ¦Êd’nè‡ç[ÎÜ ¾Zʲ+<ª’” Â’¼¬«T­!RÃv70HN‚Ug2æ¤]}Œs[EK<Â%qj:okJå[I9Ѫ³zÊ­Õc#h:ëiÏŽ8Û§ÚojÆ/tl©ð_4|HdS,ÖÄ^ù]SœÖ d„ŵ×,iEk>Í~RÁpƒ©×ƒŸ"Åq³9IôÆË9óis>ég,ĈŒbü¯£Æ9F2¾×I0†VÉ×ê¹KY©gµÐžàZÈ\˜M؉ãA3ã!fzµî×eYahëÂ"F$KL©G_WÅŒàR#D éƒçZ´:ɪ\X^ŒaÙeN/CùxÚ”ú"cb‡¸ëŽôØ A…IºCyâ1yN:án¥–ˆ}š'hsZ;÷žbu¾°±½i;/ò;ãb¼tæ*½'…w[ñDøìµL£(—½ñ|r½žòµ£ *_ 1‚ˆÊÆmDÐ4k<-Œ;º;##hù½ªk¿ß‹'dç¿mŒ¬-#v—ƒe­§FÙž–¤Æ–tŠWŒ4¡³ªºòŒÞ¶mÜÖ§¾6ªßOÛ3{eÓÑœáFx ï1#æÇñCÇxldÖ^/g;Ñ«¼c´ìÍbÆ£¤çXº5X$³Â`ªÆØçq´*|‰dJ üyÖÒ:Ee*ê»x£ÒÌuÚù{Û¶—øÌ#h¦'>SÝmF4»LÑÂÖó­ãOå.!v©·¬vr#¤J.Ù˜S³rc˜•æ°«D¾Ô×&ý©çˆÍJ&”gwìqþ#hôÔ‚‡ß[Þ4{k#D#DY§I×Eò*ñ‹œ²ÉζƋ`Ú]?¬ëÑrŒ¥a ˜>< ×w'.x²ãCö^&0ûá¼”9,ׯ•í@y¾t©¬°þ¦(˜\ÔJø‹?Æõ#¡‡T+¢ß.ñßîAÊ{Ú¨ýÏåC÷Ê i„éUù#h9» ›S)À™Í ˆŒú—0€îd.ÊKÂ^~ÝL^MÁ\×ðûõ…å6Xî—Ï1.: Lç .ÙÅt 3ð‡ù¤ï«4J—r7§e¥¬?Ú°Û¼þìT¶D¯—ÙqŒ‰IÒXˆQ³í#²$¯(ŠHõ–t°õ—üÓ§ÎÄþžM¾a‚8ùÁå’‘*%»Coùì*Í7}áGskÎ>üW¶*6²‚lÊ^R!ЄmWÖ·7¶]N÷#È IdVø`_Älš;ì÷¯ãSØÆæo[ƒÁ—c.ôí2C»íP½f/P¼ÌÞ‰ÃBtŒmT:­4ó¡öÇK-tÎ=í¦ƒÄã·>²ØX’ãê g|o¬ã˜.µò›ýúãÒš³”å\ôÿÝ–rdN˧×wiOO†¢‘J;©ý8|)Ö~t¼œt‘Ù3²yŒLÆÈ„Ì«^AºTšWºïúWG¶©K­¢6§W<¥y믑gíqútó³jÝìE|$èò|Qû÷â+×´€¤Õ4C%Èb=•…CÐ}±s‚€1¹Š#DÉpŒ“fC µ-½F´¿b™Ÿ°Å+Ý"kغ`ïØ(Ä"%I,êsÇ”¶•ÞNèk®×§î˜MÐ=d»Õ‹C² Ñå´}†ð*¢Šº#DÆó£}ðîa´uæÉ’çhbÝÙ=´g!£¬Œí$±‰þ <œ|<ÔóÉçxò–×¼¥ºb…Löy6Í~0ÉN¦ñeu§ñ{6þIYÊÏ›ÏÉϳÇdcÏ3•žüK»Å_lËŽžìÖ9d¬õHB²]Ðêº×9pMåJ%;ôgå!Õá•øx3Vt˜Uú°7ˆÃj!¼¬ÃƒJ%³â¦÷úãáõé#hø_7àYõ )‰! Ã‘¿Œ÷Íyo**úÚwöÊæD®_ž#D)¥´/²£j§¨r¡·žõº„èX" ¾«TÎYÜ’S#D„É‚L‰s[fYY/±B ¢Š²]gKü¸¾øÌ2.£JX’OV‚DÈ®¢’^ë뎹ݥºÞþ× X¦Z Þ‰U>D`êɳ­-W…y›ÂÀ$ƒC#:ì‘7hÙ(ØÉ­ø#Dæ‚q¾8:/¹ÚÝÌO“<µ-Ñ\è#f#hȤ£ˆd£ëW®òBC/‰ !/ËÝÈøz«ôuç§{A]ŒQC·xaÌ¥+Õ>MüÙ3©¤s¸!µZE! ÎI#hÛ,!«ýÞ”á|v• Z»]éu»ÛŽ3£&b'©µQ!ƒÛถ—bHÇmöY%x^~=/Åc^q‹¼‰Á!ß#…¬f;y5F ¨%’d-0e1Će™BòB‰ ;¾×Lç;è©]@#:à௠†aD…ƒ–ôyÊ5‘.UYǺc-nkµ«s(>¨yÀnfžùEW!ˆì€Í°êCâغ\3)ÒïØÓ+–a›æ<×=dÜ\¡è„Èf[íäl\ #Dïþpñ2G£on: )ŠîdõzðœkßN1ŽÅ¦JèÅa®;D¯Ä–'…büXX¡tø‡R/Îr9\Ê•¹¸?ˆµ·?;W0ÎäeÍö]Œç)÷0HãÅ]žIi›á¼3¢ø›ô£ï:Ux¬ÏspàÇ»â¹l=ðž#D¨öøuVÛÀð^MOM_Çƃ³|P4c´Ë3-(n/ùy‘$Þ_¼äÚ”$êK‹@,BT]Q`ánÝ!Òt#D@°.Û™öE¬Z#D{á‚æ²Óº–uoß’hZP$‚H(bªð¶ç€‡¦Ü?Ò‡>Kx——qôÉúþ³ ߨ#D¶GW¥˜h°Ôµ<%_¹sÝëùk”ñ=¶|9¡~s°³_© IëßW-ª(5ÌŸ²>%h¦à¶èÝêP8Üùöðvû©xñY Õô°Š•R%=¸9Ñ!(ð¡½ìQ§¦y;SÁRhÕ˜^ý¨ë˜¡}7w¾#ÉóµÕ,Ö]£Êœ¸Û3¬g ²{×FšVeùCã=§¯ù­Î¡=%ÜgüÕíÃZC¼ô·#hïÎ6°=ÚøÜÛÅWÓk©âCÞÙï=Oö¨o•âŽÖ\eåŽ&:®Êšn~ð®wroü{Î>Yƒ}½ ç±œÓøó!uEõ|ún³eþ8¾lÁ/kmÿ£>•ã®ŽЛÚ ñ"r«¾µƒœMËþ_JâAÎÑGÖ\öäa»Öì¯ê6´Œ8†çw¹ŠžC"#»­]­/‡f‰pT¿Ta-F8IªØËî=™âw#:¥2sœ5à±"z¿’ÙLñq9w¾c½¨gîŸ<¡\eçŠ?…×5ô‹ŒÝf-£´ÇúµœOòÖ6±”À^ËÇ(seˆJÅáo ·¤›Ã#hÔQv`‘3¯Æ9õ°1«“’ËÒ붉 A×Á©ÄøELZù*h( 1¼GXZŽK‚’â2!—Sy#:8“Í…p{÷ø_ONO·Íx÷vŸ°a#ŽÉF¥G¿åòó(ØѾÚ’®e#hRÑÍ„ MLº„L%")L‡•ÈW s{åå—|4ø‘¶'ÚD0i€<-F1#D¦1Þ÷}#Œ:hÁÒŒ¹vbº·53Cõ{SèÃÂ=ð6AŠÝ÷ÃáoPÞŒy«ÆŒ•#hÄb 33²Ç²ìbAuçP/L{9ðuM!mnþž=¬ÁÂÙǦMÇ–Ch§º[zxYrí™ñˆ–±_R6:yÞÔ1^5Çξk3ÙÓµ×7£`‡6ažX„¬2‰Ãº˜  ¤—!ôâyNoèö}‡ð/×ûþÙËú;p”ïð1wx Ù‹‡E*;GâAÏÏ!ûúÕ-ÐP"û¸™Š9ûÑÙþ›iþ±ÿiy§$퟈{’ïà}{üÙ ‚#:‚ó8h5ø9}»™¿B®µsv|6«··ßù3#D4Nr‰RÕâå[\ëzU1#: 9#h|GúúØ”ŸèÈÕ)ù`«X)ý2œÆÿÔ§¾TââE ]Cì‘â#:õÊáý"E?žEæþ)ë'h?Ã"{a?ü. °«ÒQý8X#:áa&Iñ— ÷aý?£^î p½ò¦}P÷ÀŽ ù—ë…2]K2eöÊ?îÎãì€óºË#hëáûq” º@9²ØûÃÌG<åëßÛå—RãéV‰ìØüg„Wés&Ä5(‘Ìpí¿·Ù4[pþqâ¨sÕÂȲû™/Ãå—'G¿Hêá«hÛQ¨'Ѥž(ÈwÏ}Ú<›¦ß/T¿W²MøCɺ£OŽ:Ð"@„¢#:„Š*d¢!ÿˆ"¡öJxJøËü‰‘Éd G„p~ÌÜ »~߆—¤JDV±C¥B<ÁÌ/2'a#h#âʯÎ ´ ĉBøBdþyÈÜ&E&àçÙ€=ÑçG2êNŸ>–(sI)”š0:°µò½8¨ „0š¡³¢a$'Ð'ë5(“šå8M»¾÷0ª¡ª´å/ówp±*i®*º¢0‹gL˜Ü‡.Â6Øsa¦‰¸'âpÈÊÿÌYëRßãòæ´ôÿtFw}&Vø\M]ÃâW(ŽL¼[º¦º&ìúêÌ>ª)=T)g&|µ×ŒgTe$õ´”Èêºâ¬¯ Ëùßо»M"#iMI’#I¨ÅRDÙfÑšl™Z-6‰éq(þO”(iþ¬¥"* göÊtƒSô™„Zûu–£) Zx<ËYSˆ"`ot{¼VúÜý¤jÇ—Ç Î4÷ž û¬)áìóô'?tÜWãoLÍóÏçt#hd»¸]à—葉ütÊ란;« ½ýyB{ùg8Ü°r¹·ä¿pˆù@Ç÷]^_Çmù_uy;ê£äû·ì>¿Ënºú?-üEòwø?σÖÂD|‘û]ü <àûœÿPQ¤NÔwß&B„’ò€ ôcÀtCÑÎ>+S/#H(¼t%ÒÆ(©ùì¿9‡Ê?WÍo´%â`´ÀP ¢)þ•iñÔ+¶ÜI¡LÕ LÌo6ŠdHi Ãè›b?#RYJ6ÎlÌáá—%:AÉd§ñSŒÙmÛ©sÔm톴â1  Œ»%Ü0@ìîUU$:,ŸëP©«a.£óy4Çw꺵Çí“M2dmjcŽ¿WÅ»ö­98»o[P1é”loE#h Ò&¦1Ò"Zf"1¼rOñÿÃ\,eôéJŽÚæØá¹5£!Œ#‰´o Hÿ­8PÃp8˜$ÂÜa±ŠueÍk4ØÀƒg3š(FD ‹VƒNŒÜ*ÓPaŒã‹½cÑŽ°((Á²žÆ_n#h$´W\4«H Ò·Š#®/†^5 m¢h•m’å­ÒÁ¦ RE)ȾžY |–äü—o§ RÔ𢤿ö{cÍ×ÃþÔ›f*8,¼»ÌmÆÖK[mXL”µšu˜ÍÈÅT3ŠYš§z2K¨¤iÒʶV¨•ÇÆy–±›É˜Ùƒ”1±R=¹‰é…iºÑ!ZE;L#hB1ÍZÕØ»«þf•lhoR·m;b²NÐ#hÒ­œµ.RœP·yA#½eô×w^kã¹tш¹ör¾A+l`ÑZÜchí¤‚ƒ‘Ã"³=| ÿdU!ô]•÷ÍÒwp衲R^£oOçÿ?¯P샃 ø~›#DÙ-ÿHÌÌ<Çuå¿kúõP'fÀî"XÕ tS»V¬r‡Ö½hiÿ8ýøæ¶A®î#DÆÖùd<#:Ê>ò¡ãᯀôû¿ hðûù#:æ¼¥¼„§#:Î2?¶F¿Ù¹BqØ`RÔA­öºæ}N­S6¤ÚMb6•/Jä¢þžq¦_Ðã#:¤–€þ1Ï\3K€Ò0æáô“Š)ÖƒÁߊ—$ëú1éfÐ7ÿp°ØÒöÿÝ¥îÑS<ÐÙ&@÷ªyÁЕ@õ0Š}ð¨ëH ¸U?Üâ(çx}£!ÙþqpÀˆM%H£MŠª)c#:`’þý}ÿNÚMM_9X(º%£ÓñÓáÿ3èÆ\#”S½§ "ëÊ_ñàq)TŽå]Û|IºjþtÛø–«ÒT8x##0ÂPõÄž9ö¿nšëxdgµÐÕU¶ØŒN"-˜B‰[0£T†ÝJªžÚ×» _q¬–Å~müæe#«l?ÑUì•ÐÅ¢÷͉µ:5£'ûOïýÍ(ü§ý ‡Ö,êû#DÅ>= ¡k|£L–gný8Kª_O|7Á% þXüËgmïêûçØìÃKÏñ¯Å Ä?µ4Tä]ŒÈ6܃šsòÜÝÅ8[iax¿8SÏ#D‡!òéLÜôÏÒ«â³#DX•ÖÜŠÝük-ž¸xz/bJ’]ÇLÁ«ÞÎ[¤ŸxfƒLé“~{¸®D*ùÇëÐ?¢þ©m›»çe”ßôHjâ©™AééQ„€ªù ÔgÌ.A®qî)Û57]­Èý5]ÑÝÇ—ô/ëó}õDïÅhB&…~ï7£÷rùüCëü{ÿ3ªuçͳÀbÐßë‡Ëçõ~Ï×™§n6¨?O+ÄzèDö#:»<_0‡ÆZ4lI)…ëãøØ<("cŘÓds—3?µ„ÁF}È~’,B½ÉäAËNÂx#4úy'ÔnÏd¤žŽ¹‘?Gæýç¤uúi§&ÞÍÞŽý4Ñ»òav†sÄyrö_ôÝÉÄs§‡!Úÿ¯«žûöþO/ä›®\½CÛÓºŸÓ¾žHïùWôôðŸÓÙqì×~ÁÕ½ßDüóãñ?N°Ê†´uNó™Fóýìÿ©Ø8Ö)²µžzÃÝgPÁ h‡Á‡ ©Òq1MjJMI#dvNea)#:{P·aB+šP‡úD”UHŒ§ #hâT)LÖr»Vu¤Ûˆ"À‹A´Dšš³*RV¦š³)²½üºx…ñðÎMRÄ ?"‹FÍï؈Ÿ° ˆ„#Ò\››ËAÿ;Ë#Dær ©JJR=ÒÇ÷c„ ,Â÷bd!îBØ™¼5@OÊ>ólÃ’É#É‚q¼NI€2„#:®çšôŸïr‚çþßÚ¿g‡ÛöïßÆÌù=Õòsé:þŽnzõË>·‡ íú>ˆ¸4Ûßô>¿Zgc¯éóVWßþO}:GÕû}| eêˆû?>¿Ïíùý÷Ixpü¾ |]\VŒ²­Nƒ&#œ¦õà4N Ñó{-íz¡÷ çÊÔùùôRôòïèûë–Ìâ¸ÝØE†ï÷S—ç»›Û»¯É.çû5þhÏ4'wO˜ù°â•ôA¾l·6£§’ϯaÿT4íõµ‹†‡ãæW-ß·²n89~¯R Q»¾<òw:·ªÀy±ÿåÉ×ÛâW°­FžûòÿGWg’- °èÁî]rñùÏî»ewNþÿ 0üÝ\6jçìÇæÒ[½Ù#DìîÑ$?R}Ï#:pFB‡©)¼6?&7Çý‹ös“{èuZ^;¼C³*'5~¯ªשãÜ=¸²8z©+Pú!t§ô¿ß©ô >Y;„¼¡þý¹ô»ÖoJjUÁyX_8öá‹ìÚ³¸·gÙzm¾Mbèúqüåð­K¹²ã#DtÅÑ!ÙíËå_!œïèæ•ÌßKùŸÕ†S€óÇÙº˜q”¼^óèôêÞ,rv?Û—#DJ<}Z9a(;ÙáÁ¸ÓWÇ“^¼Œ¹/Œ&C\mÇ?u×ôe•Ï’:Šöºîw¾æ.O“¨7^_t?Ææõ¯ŠÛüëôG=ørññé‹ÿ¤pw?ѲëOÂ`<«Ë®ã“={W/DyÚ½;ŽUüUæ'íaêݦ¹ø÷aAâzà7ráÇÁŽ#D½z!¯æt¹Üþ&L0on<ó¬=¾.Wu{5éÇn_#D›qìÕG]åØ<šÇ9ôr*+ío“^³ÐÜ>]v—O>¯73rÞz38ÛfÚÏ=w{Qi!.Ú@¹‚Á͸ƒàó"Íz³Oòý®{Ô[è4y!²ú¥ËªÑlgkìК†˜°=Ü&çŽÎØŽ¯¡·_mRDÊž§«‘ãµP°ÃÖéškÉ\6Ÿ˜¾yñýƒÙ¨÷½WîÆd.‹HÃ1Ù¶=];©Ãìþ?Âîì>Ï›«>:µjÉ&?Ãý¾­º~Z’£ÅóþÍËŸ£ôQÉrüßHkÞçC`ŽÉy×F—WÌëóóm§ ¼Kðz‡ŒKéö¯òù/üû5Áض’2~ÿw¯ÝjÐ{ ýǘ]¼kÂ#?‡º;ŸÅÔs©?ɳӶíFsçñ‚v3¿¦?#DÑóÒñ7Ü^ñ¼esjÒo½ëàñ¤¯Ý£K±àïWÅDpPw3R=ƒëÂÑt÷ Ž"Ô•¢·†9uwá"›yýwÃÅ”9ÖÏ^V4ù½€ä?G>W\¢);¡ößüŸÅìÙøsmçÃÿhç–ÀÜN^îôôwÛësv#DÛ‡ÿé—5y=‘?Ø+¤<#DÊîiåáÓÃGKƒåÑûÀ‡†Ã?¼ËhøFÉÇu°zøxtÇ µ¾ƒ¥Ï¡~zï‡Ë­€Dvh-|?¶ïÑ—[#Dû(¾õ¼|vêø|G]e%É´ëeÖ÷Ÿ Â®ŽjüïùgÆ#hep#hyn{KÉÿ»ÞÐßúó»ò@¡åÚÈF²Á÷}}¾_¿ÃUÝ õŸåóÔÃOötç¯Oëáþ#h#hñÀ†B„ )v%%~ïWv~ãÉ&õüÇÐ}iú<½Ëü_êc×ãî–Ûè½vàÝB3ù,×Ò8+“Ö~ƒé˳ޚºá1æAáïÚ/ñë§×«–šÿ–>Èg­fkpÿ`!/Î<ˆƒÁä^¡/wãËÌ3¼{Á/©Ü¤‹‡±ðæUõ’O§D>]/®;~x~©)Ö¨’AvÍà䨂æF*T1OÁ5åŽQ—µF =×õ‡qPÌ1KÖI‰3$©Öª®`LpàŽ·Ðé#Dá_^ñZŠXx€rOŒrð¿/¢p€÷ëˌ׳Vjj‡?˃Ó/1Hë°Ü4Ù“‹™de€Ëhv¸HñR$:™€ÚûøGDå@û§àîY¥û*›ªg;Õy‘%ç!ã+%_|æcÀLoN·y^<1×êâc騇›]GÌ>‘ù}zù%Ìi=>—vX@ç½ÔÓåÙ€`TW¸yfõæ•+FI9¼Ò^…qìÏs]6²N^Ññ}Ÿõ¾úÂ0iw~¬ËÌ°ý-hõÙp›#ha á;Ä¥z@¸EÓî:&hãPn÷#Dæ¶E¬ãïBd¹i²*3Óç´ïÎ,ÙÀ¾> i“~“ûGÁcn™Õ¹)R›C{˜º)ëv¢óBD™øñÁhÓœ’ɺÿ9Í)v˜FtlÜ-G¼+—ÆŽƒ¯Øƒ_v^­ƒná0F¾¡—¨¦Í£k'b&ïm`Ü''‰/é—­õ׺öåå÷‹ž:¨¡‚… UýŽ_[—<ŸÔÿ€ÙÌ™ yNÒC§©S©Þ«¤Œ9O( 9.Üú3Æ.W`Ú(Þ>vk%·«çQ÷2î1£Ûï ù$’WØÿ>’wÚ'>áð¨ÝæÇ{ýgËpÍ#Dd±:Ç*.æè×uð»?#äñ(ˆÝó9©G„HšT úF Éô8û³·ßïF¤žîlú© Ÿ^ýÙyŸøÍþ2eúÿ.®êÏ«“mSw+]™nS-„É™ü»UÀóòÑ›g)Èû#'»o“éè??Gˆðjà™æ:MǾÒ_ÓçÞÛØyOxøyx‡:Ÿ»¦1ôèœáÏç¯T{ìS¹4MûŸON¤>Œzé;2ÄóF"øÂ×Bú1#Dœ‚* –våè Š„E]+c·M*ü<_ÓY«úüO ÂxÕÊj¥"ݨ”ÏÛO‰¯àÆ9.[û"áÍ>–$•l¢˜*|‡ååádÀo‹…é~˜#DDxº7åpuD©“£i/'Ì=#ÃŽ Þ:KZ'çUá;dÕ ã¼#D¾¬§ÃÑø¢8¢k˜ ã×öý\9ôœ¶:?ÓÛ+Ózoj=Ò7ßí¯ÃéŸwóé•?¥…Qþªºª°ÿ ÁpM2HÛdi´ã$†EjC„‚¹I=Ý›"^é¦íCn¹vDgûcD¢‰rAh‡Äp`ì#DZèBA5‘ÀÕIW[DHGWB„Ó2g*n¶YHF4ÈÔa` µjêªfÀ»KÓ±M&½zëË—jï;*¢#DPR”¢4—Ëøÿ/]#DF"»Ô)‘*’†äX§õ1Š8"XÅ] •®ÛµnÚQŠ‘ëû;ðÌd)î:ýî ŒMÀ(”;ÿ“znn~ÂçÙšÿ€Ó8¤š&Ñ?òPö׌4éæ%tz2²“]ÛoÀúý¯]{·D¤¢*dW7B?o–ßËåxú>7ó»›‚Dâ¨0H)ƒ‡¾]FžÞÍ ¸‰7,bàK+¥55r¥Ýw}z¹‹þSÍ|]#:(P‚(˜z'ñè]Ÿ~¤ÑÃ?/²ü—èŠË•É«[]ú~OX¶¯©Ñô²dþýzS'¤çÓ舼mð_Í-%)`­ÙosÏ/¿g_?£ÎáÉð»:½Âzïß·órüZœ¦·~Íý4xWæ9vïíŠoÅ<=ú¿Ž0®•ü™M?‹½!æâ6±Ìk³´Êüuwm1|\ö¥}¾’/”1;í\¾ðˆþB‚²…\„éŸ •ßÜNbIî#D‚Åýo¦á‚ì&‰RŠ •USç˜(üŒ€?Ø”þW÷f&ÞúT¿àÌa±4²ª¤§JÔ" ñS_í,ÌB?á„r"®!,ÝË¡[G9Ý+«¢éî½–òöÞåz³ë·ÆÛÙNct»IÛ”H®šp2&Æ#A1¶’##:ŽXTöåŤ‰Š"·Æ'Î\9½¼âðoü]tµ©)#háe­#D6˜Ó^Uq˜V[k42ºÈXÆic¸ÙŽÐjÓ(Sc#Pi¨ÌáŒ(ù°lfâ¥{™ni Ô‰4Å#:qKû«®“ÛtÛ©e[ë;EìZ#h",lÿtìwñ³‡Bdž.3`7ˆ©±ÅEŒF­Ç RàÌÖšÇUEDw¥ˆmÛ¢4 Ljk!ZL2K lÎ*bž–8ßz³e‘„!Ž³'lî6Ú¦ ÝÅ ±Œ!­ˆç#Gƒ¯+m,â;#h¢ÎóT†!ÀdÎ*Áz4•E""Ÿä¿#:IŒ@ù6°À·€²•Q6ÁeŒRêš©CMä0PB ¹/7^Ñ9þ¾oÝùº>R_‰¢?±{cQþ~Î×gýåŽ#h=l/‹—PËåzÿ<#áó}iî>Ñ>CÛ¡¶~_²éýänz©„KÛ;Ѿ§óÕrßÍú‡êÓ8 Uö ÍóŽ¢›#h#Õ8s»æ«à Þ÷¹áìñŽÍŽÝÃìaá·ƒ“÷ªRŽ”>}Œ â©õ6HÄÓYä7â·i½þFq€†Ã C‘Ñ…í÷O\z¿GÂî>/eý3݆°“êÇ£åíaÎ:‡Ö½“öpN3ÓóB`8VDÿÀ³â_ÁyÙ­zòÒËìÙ‡#DŸK&ðFû­……Ÿá¯… »nYܘLáTR =/ŠoSk$…¨4Õ®¤¨cp|¤K¿Mîña´(Ô!Mðõý8nÐÒéÎ}< 0D<-2ÑÎWqjœw¾ ñ4‡¢®¨$wËØÿàë˜zMZ¨ÊÜÄtË0p(ïÖ£â?>ÿ¯£Ð¼¨ÄåG7Ø`|®…?žëûN÷¯^"o\Kºç½N@…¨jŠ6«­–A¨Ùù>S£ "FÉFÎaÆA»8,Æ"(ÉFT-o7„IM$Z°¯øËâì:n‰îÜÝK˜­îú]ñÍìšh?¸‰KÃr•-#DÙ%(FÆÐÜ”Š#D6Ò)!jD±Ê¤B¶Fh‰ GŒ]É„¦Š¿^ haˆ”… H#Ã#D=H-0×þ¥‰§e½30?äÿº\xȆ10Û½ D´‹™ÁäCë1àˆÄÂ7<àD=f® Ô²@n¡ÚÍåÛÐO‡g Hì$«YNLÆÕ¢‚Ë@F)2b±LU –A´ôÉÎCQ½¥CN´¦b¨ÙÒαš²°c1‘ŒvÆ:Y©„"ˆË¨SÏ™R\Ó@°À"Å`ƒˆ ®ÓE:zÄŒg/t8L­8FE%« 0 Œt<Ú"ç€nȕ˃’)¾]%+¢S`« ŠÀ¶F=ºâëÞPÝÎónŸo"?GWÅç7Dfƒ7]_žîóJšp#Ha؈ÿ#ÆÉ\Ôíße@ú¬Âáa¤ ØIbˆ)†qBÞ­°\j ÂmÕ»Ua¶¹5u[Ž5#:q¢Ð#,‹bÅ…¥n­vÌ1D‘0¡’¦Gün¨^2–‰94*`¢Œ(Ù&˜›DÄáhÁEÖÂw)O‘˜c¦MÁªµšÕ9«¨*†³Z™šœi˜0)ÉÕÎÅ—%¦4*¨i`CyBDÓcp11#hÌ È1‡sHè8À؆²ÃÈ “Xô3ÜӨĖ§wDù×|ítÑ>ww#:»—]Q»_~wßÜ×`“òêé}­Îæ¢c48Aì¢éI¨ÆN#h¨=(,i±ÒK46 ƒ#qZ*X °€Ò÷ºöPѱQICWN]'urLÓ]Ü˧';7.éqÎK#mEGŒè<Â@ƒa#r<>?Úyšƒ‘ŒlhîR4ìݲ2X¡XÑ0Ø´ÑEþ§ÇÜþsŸ;†ó–Õ~o¿'swplW¿=;ããsˆaQê‡<ÎuêÞs9 쌱294h&YÒMq²:&çu”A–[„a¨ÉO´ýH(#*Ç:%nž†Lìã#ƒƒ;Vñà0Ì(‚f#š'âðôh™‘^µÕñâùÝÛ‡pnsËÌîÌ5LE'Y^_8W¶–#Œ(l%3 mÐ|{Š(Ù´·väÞ²PÓêɨj¶‹1c )\cGü{0ÄgvºWJêrÛÞ¯µ×SòíÍR ¤J¸3ÜïÁ™ËÒÑ˽;öFñ$.ä_/§˜?‘` ɉg7&?Ã濫ì£&.ϵQ¿P7íÏÍlèþ{sA'ê6žÆœ­¯kS[f?h¥I¡¸Á%4.ü®Æ#ç!$b(¼ŠY·*%1íÌ¡4(Ü2e”W–fˆ×$V2lîO¡Ø¶ª¡[óÄ#D!”P¥x38ñ›é¯†™N6$x…BÒágôhm¶Ò;êK7Ž1´¬ëšÙ3wy£¹Ð\UšÑ·¡ãSS¤‹I ‘ºgl†'|ãE¼»¬îí ÕÓ߆a¦2MÈØV‰0„pãæá¼!‘6DZë‡^ÇcÐ3j°/g»ÄÍõðÎÓ»ë,)ß^'7ßZàC>AÆ¢c­Í¡¯FÞ˜º$V¨ºÞ¹¹´Ú‚ÅŠ#h{+ÁÓ5\o»3{ô×fô¯eV¶g“súÌЛVƒy‰(£q𘳀êã„#h6,Š}Ͷ.œÅµ †Ö&ÙØÜFÚäô܃½B“œœDæ9Ì[õ@©Ù¡8lìÜJƒõIåmå¿<ïéµ@ !$ï< ñ¾H'Â3¿·;àœŒá¾`Üðás,LýÄØa1(yŠÔ×ÑŒàLˆP7^UØâÍÅÙ™³MFIeðiìLõÛ|Æu©WFƒB;›:A¯ù6W`át9ñZ§2¶ë¡{\Ý#:- ±V°gy)T Xn^1,J$Á0“êynqx<+Ï=˜års –û¢ÙðÐ#`‡sPÀÞ\ĦVo)F0ýPa2n[mv®ÈóÓUöòa/­»Ž˜ïM,L¨‰x(D3ø\寊Ɏ¡³)c&Í@žš°®M#:îA†ÓÔØæ2û=Æë ߢÏd׌§½þõ´+`ïÁCJ8)®}ASQÖê+@µ¸à{Š Û¿¿^si$îúÅL™qõŠÖ5ãÇ >‰zñ¢L+,ÆË8rNœEÐ=Ý¡‚ª5ߨ*Û\JÿÆã"N':ŒîI'_á¥3ö6Q{ÃÀwÇ'”ÔðLÔŸÓ°ÝÍwë—£Ô8¹™a2G»þ ®‡P£ (…ìé:ïSÜCìø2k#ÃBf…:hÕ_ŠÒÅ,%˜ÕFfì´OØìaò\oƒ|_¹¦:i[W‡lñ¦ÿᬾ¼ïl—,ˆ‹Ë›¢‹?éõgI’UéY“,˜L™ kÚ7#´™y7gØëNÒÒÇ€êfõ[¶ó‹XÓ©µ˜‰··–Ì7–r2^ßóg&½Ñ¼!ñ•¼DÊÆ£|yºiÊC^¢e µÝ6ví#„„ÓIM aÎü‹–K¼Ã£¬.bbÒçX©)²b*Yñ Ï|b ¬NÓVz£îäÞ'•}cÒúË–´{­îZö­.Ï£´ò«=_m³þÊ<°Sc(Ãí&5}aÎý]ž{x»Ï¨WtlîÂÛ‰òàʼny „/?r›‡ô‰ä2›…o ~›A¾›7£»}׫åcCb±»mÜda™#DÉàiâ¥òg·2¨8ôJÊK’ôdêòïD\³†žæ2¾¹»¦Vy8µ#hôìÓ M+ï k8|UõõÅîkq­'WL7"â…ÊÆz#DÝC(,¦#DÙvtÀÅHÏ£»]vD·H¤J§y[Ò)Ù ˜ÊvÎe7ÜÙ«¥ÔQ‚¢ÅYÓ¯¶Ï5—>ß¡ƒÄÔxYoØ8¹²LØ©&Jg)Xk5¨$¦\«ÛJþDfN¥rþ®Áß}xqǼTùüîº{òRBG$ÏÛs3ôXv7Å{~ñÑö}ŽvòÔ›Àò;èü ÚË“»4‹æ"{Å&d¦M±Åo4¤v-÷Xõ¼*€•¢éOy˜¦¿‘GëÕýïÞí»äöG#h̸0ö8qOCPé~£OÌ&‘!ETH ¶cV­H›ÀP‚äúO£û†›ÿX4¸#hQ&¾¸Â”sï™õŒ¼R@,.TíÛþ-ó*áeˆ=^Ux5pÉCÆ#h°Ê‘þφ쿙™¥[#D¦°éKuþlM„ãîñžÑ½t.*;¶½W‡{Á#¿PV,}’½^\'~íT»ÍQ‹±€œa›ŠTVÙ .Þ’—¶H€!ð{>AÚú¦ë#hÓ£ FôGbÈš@ˈsŠ‚#:r¤¨yJvôuŸ+ õJÃÅ«û—Ê)¦•ñƒusUÚ3“V…µ9C Ѹ#:ÈŽ#:]人ižßÖ¿éÞ¿Ê|×»I0‘ßÓÌU-GüÿRn«ýlGÀybÑÌ;Z0¯ë¾H4ˆ±¬Ò&_ô 6ÆÄ5¶Û#Da§é©(ܺĻ»LRþ³ëR,0n™›ö&`#:$;#hSÅ(%Á,Gn.I˜(b'AL¾‘âQ™ÄÒ”I>8AèëN~%Gë®}ðì=X#D‡¬E†WääŽäpM³Ü;&*g¢±QWBQ¶hÐø An|žc ‰*Aw_»4ó‹^°e°$'ÑÇm˜.¬P“!JUIúh­J|þO;=)ثžÝ’pì<ÉbàJ*"Ý&)]¨;>¿\GÛn.½Ý¼×#f«’sOˆ6ÐìÑ»±†ïxc%1iÍÏëƒ9dÄ0ù“Ó2Ô GU›¶ ÅŒ¨wh‡CÀÎHý²&=žæNÿ7Ï‚MCÌ6žgî}·ÚÜüPÝ”ñ‡T¤Ü˜©¨–ôßãs w ;â¶YŠ?}Á¶Xþ3´Án뼜æ>Ù·¿]`Ðöùàá¬2¦±âq™>wY’“£¨õ¾OmËI+Õ“uåŸ –,Ë3?ùPñ¢¾@“6\ž”j§­,6¼Ù#hÖm~Ežâ4©ÛÍ%øÕVÄtëÁb纂QPJõ 1(4eX&ÿÁd{Lä˜y×#ð­{ÃRêvH² êíC:r̢օ͘#.:K1 ¿’˜?!|s”LäÉ—¨-»Ðè E7qÐ`€•âõC³¬¨÷Le 2´ì¾]Õr=‘µ öNòf}ÂK¨ÙôBò19ĉâøŽî êazT)`&ˆC>o&‡ÔúW\Nv~ÖhÞÙè¦g)gÒlëð5Ù'h?Z’õž™#hlªuúCNñåÉV¸ÍL‰#Oµ;¨¸ q¦ Ì?»šïœ¢í?}-0‡sÝsR×'ÄaúÝ—µI¦ºÐýhíŠVÛxƒ;þq#DœåTÔ£_.Û“vZÞ©sW ê!æaÛnë.:kEÄc'²khÆå0Žª-q[Äcš1TÁÝÒÂû<9TfͨúqïÑ­XèFÉÒòL]ÊôÔÏ™ñðOŒÀ/Næyw0bI6ë^» ƒ,Z#DMws¾·u-ÒÜÝÍßJä ’+¦x:³y°ÓÕ"âKÃH[Nî*¨ÑÞŠºcžÎ|Øÿˆ¦ Àç[3Ǭyù>;ösŽîS¥/Ÿ†Ö"÷îQ«ÉziÉIaNÏý2Ä7¥ÛÎ_õß…Fûz¶Ó§¦äHÐü[œ6ýî ড়<30ÄVB]òÑ/#h!V­&†…öQÖíícŽÉ4)4|¢¢$fF3@#D’p–$B*¡FÖ¤`¾JåÎ<®•ÉòLÐñ¥Ô}òÄ|¯™äçmÐÏDµÜ QüN¿"Bñ#:òĽоnåð¯bc³#:öaGcpêŽ:BHã‹ù‚Ü.l/‘•ô [?_œµÎ²-úÑÑÞÅ©ö5ëÛo›’5ÄÈ|¼ºŽ´{g/Æ÷¿—ùvÍâëx§””?…¼j¹rÑ´žóüß[éÉ¥ý‹8JMÃŒÈjŽnœÑc­Ô#Ÿ[à#h6cƒ…b±ªÅŸç“¥ž½ýÒIôƒÑe³¿6S׸à‹@*óë¬Ó¬#hÜñ'½G5wíŸp:îѶàNë…´aŠ‚Z!bÀ¯\À«®«ÄA·“ j¾1¥[³jí†4a•ÁCO#DùyÕ„6dzJ ¶I±XcRñ°šÈ8*Ĉ`údÍM"ÚÜ:GV÷¢§R5÷-ÑÀa¯¶ «Õ¶~\Þgµ]‚³%tr±ÛÒÒ#h{ËAnaáªã}céQž–YÞ;þo-ÜHPŸ‰ºHýïãëë¦3‡'«¾¶Ûœ%eŠ‘>Å"È‹MÐñåÈäs,HÛr±/P{èìc„$éj2, ­d†¸uK¤³TQæ J«Ù¯T[>#DIöÛ£¾{ðŒ¶ #hïøÌÍ+tzÜÇJ6}èú#hó'Jo §òîzûæ¢òn¶*å ¨U‘XÐdr fÆ5d»¶þ^,á"à ÞŠ–e6ÚÔ}Ó2Ïúï–T !#:(F¦YX ¡’éàq–#Dþ€EÛôóùtO) [è¶zeÀseõê«¥EœQ«V]ÓßÉ8Nu¤QXí“J,‰‰(5RIÝVä#np_¶×å®ÓÚÃ…Ëî÷û$ˆ¿TC­£Öôš]¨‚¸MýçÈ$š¨8ùc|- ¹ß#©ûzÔ%ÆPSW(²Ý4\Lˆ©¾ „uxŽ|¾S©±s;óT¿=è•Âí< àðn˜u4òº2[ƒ‡p.Nî6ŽIâºE®¬)t8n0wïPifÄÀÒ”s¨³ph=µLGo[FeìN 0ªÏ‘úÌ^L|ëßdØõÏXÂç—q„»¯†N|úW—Õtq`uÙ¾°´ò{#D¸ß7‰(?&ðí~ï[ UGF4LxñÁÑ2ÀÜ:–n‰ÏŒ>ˆL‰V-RÊ…°Ÿ÷wBU`ªž©{+)6°W½d£k=<ô»gžÓÎéÕ[®û…×#hivh+—Z: ¸ß\Wƒ¬ÕÊ#lJ“W¤ºàüŸ¹ík“}´L†ye“½ìæ‹ZŸ­íæáÅ^r³ª‰ÿ'ÇÌ´¤W}UòÍ`¶MµÂ|xSx†]Žw@€bß ¤"”ùxgÙ ¸E@÷wVs›Ä$õ$™ñàºÜ+îócù#³‡KtuÔÁÇr‹õê×èž{F#h©°‡[þMtzGÔÃîÓ*Jœâ0Õ^hφÑ7»Ó*r›a'¼[Ç5Òùï½}géMšSú?„qZXXßO]fj£Èw ²V~©õc³d4››?Ÿ®ÄÒîpDÓ_k#h{qËå‡å±~ÿu({ ÿ»_/S¿0ˆñdŠ|ùŸåûXÂ,4ñìVP —:S–C>˜$<Š'äWj[¼°}ŸgF–ÈO†ZüâlÏëíu‰õ¥¼ºCÙñßn&¸}¿V$–Ñnyöé I#h×ËiÅe‰"Eüßñ>É—†¾ÚÞYN¡ñh˜rðîÎÏ7ùðI‹Ö¢IΞ¸œíÔFÏ°°’öNšö¨ðõ¥)ÃoÏ™jÎ q)Bi›’¦©ØÇØö„‰ùΧ²k·Ì( µÛžÙDZìQ¯ônÚˆ›á6Ï%pŠÓÖNª›\RRÖ/¼MÏaj¶};¢–‡g¯+<)½X‚nÒŒ„j(%%K<(B;¹4´  ,rÓJɤ$5%׊®¦Œ ߯räáè¿Z¼B´Íó¼¨|J—ì[ºb÷8L©¹#DÝ#hág¨Ã{ªçÀü­mÏÔ•æGƒÍÍ{pm/ì³ѽA#D$(õç½´RYáâOÕ®ºBø–¥Àô¬™Æ%Å|\ÌXL0ŒÊÂé?„a¾ó†ºg(…†là朒ãbþKaHl®/–åã^ÙÞá\+7%š¼Òœ ÒV®ŽŽOžëäøÔÝE†Úˆ‘êœ'é{µÖâ‹ñ+%MWâÏ•èâÌÊGg¹Ü2}^llàíï¼2_ð¾ÿÆVòo§FÅ_Š?R¦U³0€›Ã˜lªÕÛŠY¬Ðy™‘©]¶k;Á.ì Ü£ƒµTçâ»Õº»Áéù;u¤ó§Çœc~9ÜTcÎìÙwYò;ÚÝ”Vî`5wjóÝض'•ÊRýælüÛzõG"> ¹4wÞÌMÛÂð.2´K#Dè›ØAódõÛ#,õñi‹sÇæ8]µžÁi„ƒãƒ¡à.!ñ™QU™b™ø‚ÜPDàV’jƒ™ñ—UÀÖÎôö~ž? Ñ®L¼‡àv¯HŸ·Ï?¶î{¹R>ž€Òy}ë¹u:¯ä8wuS…AÒ{ðÃ#h:èEë>}Y\ù»´Íœ³g(ghÍhÒèX¬C>pùu6˜\7OmÔ¬'#DxŠAïblø½Ï¼5.‡1Ù²[fëèöuZã#D8e‰ׂÛIÆÀÚbt„c(Øàk•N'•cX`3Ì—ÜÁÇ+ð|á*D–¯¢Y\ø!Ú}žËîŽíç…ö’†»šðî:š n{ð«†Ø¨±¡B†8V4£Ç2«±†î§Àκ‹rÝ#D‹`è8­Ü­[¥²·C@Ü×wß±³þ»Ç…±ëÂÖeG½uU[;㪪ªpÞô÷´1ÌF‚½6#±ò£VLeæ‚›–…îíZ¾nr_£Þìæ&¯ DM¥$@ÖÔ£¯U¾Û£Æ<…+­}¶ï-_qóÖ±‚¡2ò3×âÕ&vÎ¥©(Ù¶.+Ã@å4ŠMrÂKLÎ Bç¾rß.#DJÉ–,½<)»‘âåuï½âÝ6\¯€†µ¬^ì²å%`«™åsæà2ôt¹'HD´óa¬Ý–,îÒ¤NšÿÔéدGŒõ’;Ë}ì!L|ÝÌ9à;[v¥äzƒ~Š9t–(D(Ô5sº àÆQ¦t‡»f1ê;ÌéË›€ŽPÄ,:{{ü:·Ë·›,G%éÆœwÐÏ—›üú»°zÏ&þ¹‹üdž@Š'õf‚úá^9/i3Ö:ù¤ÝÎGÎW<%ne5,º#DÙ†ñeW[¯§òä↑íoš÷ë×€4Mû#Ûü°Pø@<¢)ãªhá]"ÄŽwÆ;é殜ÎìôqJÁÕ#De5(ÄÁÍE˹Âö×嘙"åðšNL–/q ñÁ˜a+‹Ž·ìçÛ+¼©fÕ,WÙT‚|Ì#DŠ¶JÉ®!‡: ½÷ ëòwéLöݯµýC£²xtªx~ºùÔA#hk\ÑÓG»–\”„H7 AM#:pè)/¹Á™]<å`  Óƒ‰8¬(ªœ&“s©¿ÂÞ,G²%õNõC«mwöÅñðFô³”ÅgG3Üè àöQÁËï’Co<š@Ÿ+¢*|á¶ñ#èøËf†näuÜÂÞŒoO8¿’×úÅÑêâE9Â,÷\O¤ÒúRšVé!žŸÖ=gÞܨø}&zb¬‡“Þ<{FvY6ê|ÕLE[©—À¾µïó,¬&qË“çŒGƒXQìçö»½vÿ³÷øÅRòãÀ¢’„5ý……åW”º<Ð^ëŽB#h©.J%íôÔH‰YjÇñ³éû]ÛOúA¡¢ª¼/ƒdF[Þ3µ ü®ÈJ9 °½uÎ×ëç*=þ„W³‘GâÄx¦¤ØêTa5w•ÃU< wj—.ÂÀ]F$({Å®çí—#Df<6¨ñ*8ì¥óŽk†ðw×ùœ°­ä :Í=ɽúŸ]6vÝ–T© Á[qÂcÍv^ ^/Æ-3#h+ç>¦ˆÜAåòt9%,Èïšól7ÞñªèŠˆº+ú=C×{å |vÒËÖVý›Í6áE•{ÔsíÛÎé±…ä«Šä%7Ùêéq½3a‡lÜ 8ì615ØåýÝÌ#ëb­ã¶Ô(?I³;ÛU½e’0dˆÝOB‘ÿ³ùF•Sß‘ûj#ãÉx³ëˆ¹àÆ#DÁºš'QÔÔ‰åvS+XNI¹–ØùkòZÞtpå8\¢à@:S#D[ÀÑÄV¾æ’_Lõ^÷óIÍäªh‹˜m¬*]¬ä. '"ˆr¾§¤}LwÆu3»ÏÖ°WßXÅuµSïè¾õÎüCëQËÄ1)„P‚ƺծºm*úµÂRöÎy˜œ]›•M£„ oÖ­b[ ¥öà¼Ñf¬íá@Ì|‡ òºfA uW:=ä;4òkŽ†‚¯:©SóÝóN3ÑLÃ8¾fo€R8¸{¹¹Þ"x‘Â*‰¬ÀµGm¢Ï`]Ê€â*‚Q™™A#hÑ–Ço'ªz£í#D_ì¶öªÁ(ÈÏ‘–vJgîõùú·ÑØÖZíSäIŒ~ŸköÐY<#:ä€PCÅrXNHœÙx±Vvú |:l䔇N½Þº0)?£ÅDŽÞøê½gÊ9è÷:yÕþ¥j׺¸ˆYòëxñYÖ(ÛfšS”¾Ì\>»„î:¹>{wöïºîFÖ-¨68kJ§7Þ¼9nÓ›šœî~pÓNµû›‰ gwä¢8oÛÈ€0ÉŠ»¥ÜÏ܇¢X¿GÁÁ:—L—8G9§8äÃÓ–-T¸¢`”ñ#:)f–,N­i'ø#DRìLG­M¬v¯çVSáú3à>j;»‚¸U4ó²ÉTlU…ê†Nçáw>55+1D@8>¡ï¸;°A]ôæyÀR7=j¼¯î”8Ih6DOÅbd[ú{#hÍêÓ‡×l@.yï#DUPH·,ßKŽ}vÛHÇ~#Èž¼Oƒ;‘¶¤ŒóD͗˜&ÈF.Q.™›ô.·ÒŠj­ö.ÛµísjVÒKs¨ó^¢h]b6ÄfC͹ÌŒzkyaû»œéò""nhš›Åô°ƒ,it“73 M£AG9®}vò¼Z•å˜ãú·ãJ]Dcvj‚ÔiÀåù­ÀbŒ~ɧDtr$P‰½jÉ#hânRº°k%5FƒÑ@¸Îm6ƒ¶¾ð[efÎ-9Êø`¨åpÃÀÏ1B%½¨æ aù'Óž#hN»DYé°WÉÙ¾èÁé-í|g^¦‡ôYàï—ÄU;bŒUgçêÙBépùï5´Õ® „@ÓÌÕsïx`G'IÿŸƒÛ)öæuÇ“”ws(t÷yê¢#NÙDm;¼˜G_)FØŠo€ì\ÝK{"}Rp@’â>Áâ Nµ&“/tm2J„§I½‡XñÞðcü‡ÊÂ19i¾pÛ²x—(ÅPˆ¥3¢XƤZW «SÈæ9Ü=§CLÚĪNÁväšIè?Ü=fnÚ´_–¶ãŽÓÙzÄ0ZÂQ„bdÙ´:ÔÍ°¥'«å‚Z»¶ÒÚý‡#^¼G=©v¨FÝñ}|&øÛZ4Š¿9³'$çg‘G¡ß i˜‰N¬8¹Ÿî¥I9¥‰žþÈWv_cÒª}õŸ/'.;'fè#™ßbƒÛÖãê-&@®l\Ô AÌ”+#:È6+ؽ0¼Í7Ú¾}~™BéˆÏO©—×Óo°*U7·wJŠpéÀrNñA&& ÞV®ÜnF“EÀ‡Eu>ˆioàð kËk­%à±B„9ñq£'¼Ü…|rˆhÛ÷Km‡C£“éÝh¹¨6r¶B¥ì˜ˆÚ†ÏÞ³rZ|“³¹öA-µMªé¨7£×ÃÅ»a Ö´‚&ÚOiâÖþ3‹ÿ0û²Ù)†®µßgeW¾²qÙϳ9o\äQhw)v¨¡ø‚èçÎ[#hD­Ä˜”óÓ~4¯QO÷ç>œvô=\òÍÞxÉÞ؆㯻½œß7φ‘[Nä•ÛÝ>ÈM±rz… Ý;¤Ç[Mƒo*xœÅ¸œÎjU·¤ š¤$É%JÅ·›’ÞTLî~&øQ%ƤnP8=‚ó²`+Yâ%ºD7‡a¼kn]òѱl÷x&w¥‰On„æÙŠñq‚5/‘q±¨âª3P€»Á.í·^®·‘)xîáxòÒ#:f ó—7Ù³ezÿ3#:À‹~_M8bºý[=l<ðW0‹ýÔøóùþCú>#.{ˆÁ™–º]˜xzŒo_?Ù“È *Ÿ/±ÞP·Ýàx ÿƒúAP­ƒ“¶ìiØ%Ï­Ùj7Ö΋ƒ—ÒÎ$í羚î#Dñ€ŒʶNN ¼_ &AÎbo8­WûzÕ$èR_­~Ï"í?u4–;ýð!à•s­õìþÝC±:ÜÍîæý°„×#hžÔ£Þ”²OûeR Î&,ïÇ?_Ïù_å_fLœ“Èÿl&>ž¸üøÉ£õõ¶wýíí($‘?Š|ãÀýÝàyãõ~@9 =JÃñR¨¢¨*Cߨkñ‘CÕÉ·^Çxà¢|ËŸŸÔ¬ wDrDßáå;vðÝ|Ò\v…ÐDÜ6™‘×ä¡ýÏÛi‘R/ ÎC5ÓåÇÔëÌhpåZ ßÕl>žÕµÎžß§xø?Óû\4l«}ºû8§×ò>œ¾¸#:VJþ0nßëãô¦z³>GŒy'YCŸèT£7$fF®f"OQ¥Ü’‡ÙA¶'öý™íâš}¥yþk5EŒwÂiDR¨j@sÛ\ïðÏ©ãO³Ç‰9U#DEù0Ɔ“¾É#D_ã 8<ªè%²eQ¶*R¾ªˆ(“ÌúJ…ˆ(z&÷@‰Þ…:Œ#h‚«!îÖ4½$w èw¥¬ë€øÙEZ#ZmfŠ•øªåb«®®­•+i5ÍfˆÖ½=nÓŒ¬þã™ÁùÉå¡.vñûúvC0›Éù¦Ç«Õü.#D²Š:ÕÙ­]Ê¿.³ùOzEŒT„UÔäÄÙú{{Ë«HŠÁµ 9@“ ¾õþu=ßô¼ óþOxÍœ€}£.å@ „€@‰ªÛ¸#D›‘é€ãDœm˜õ/]Ù1±û9exÕ„EˆÕèWûˆÚbS5ñ\îŸà›f=»F·mU†3øËêU;N„O‡_¯ `ÄUI^È|“ijê$È>ÖØõ ¢+*MvÉمƇAg¼´p|q¬Ñ¥y¹ÿDGô/‚å9WsÙ>³¨ŒóS†xÑÒæ–¼±ƒ»\uÀë¥óVË«UØñœ`œUšåâºoÿ.†5ž¹9;þý܇ˆÇ„Šl–‚b‚ˆ¢w•ÏonÝÙÙ‹÷¡î‚Gç{íÑ#:£z€¿FæéöÂNÌæC¹Kž ‘›*¡öJ ‚€…U]׬ý›ÈJ@RM¶©;0÷³’Rw¤¸OÛ‡ì‘ó¸”âö¡GÜoõÊ@™"?dt„ñG®^XÖ`3¹_Šž>šë’#DY%[5PMô åI׸…4×ráSzï}‚cˆ\“ú™9üÔOâ sçJÅ HTOÄ„R›A€ç_ÔõLÓ( sTËÃ?[êÁFŸ.l¼©—º>U/oej†!ÃÆè~Ú§Þ·ÛÕZOŽ¶Xžº…¤êÂ[s‚^~ƒÊÕ=1„Å7³Ë&_GòçÈŽŽîÉ3M¹•O˜÷uêZÀÊÅà*]…b¨dæ¦ÔØqaSFZHù[ážšM7#hòY#D¾N9aMµ4Þ‰§wõë† 0WÃ×:š™”ÀÌ:ÍË·ýý¹b{rq°iì¯ÑׇÚ]18ý›×Õˆzøšš2`ìýLŸ—.u.j—&Ãðø×›Œ'´ô$ž¾ÿ‡×Äç^o¥ iwV‘NéþhøáxúGœƒœ?]OÅ#DØB‹gèCgýø‘w·¸Ö—Ãlõ¹öUn°øK”².‚ªn_½¹âòŒÝ˜<‘OS{°(ž®ÿ\yã[0ñ[c‡dXO‘#D4€þQ“}}ÉÞâ(†™ÔBiMîŒ6™ó1#D#D«™z.úÛwÑ@®ãSTÓGטLa×:b­•L¢$@! I@r +õŠpõŠXSäK›Æ½Ã¸k^4ÛÅÌîšÐö«¾O¤•½¤ ‰V„`^#DÀ ê^  M|™H¡ó…ë7º¨€HøTF@Äßìzª2˜øeË–$§ù_WôúL좵XÆ.ÔåšêwQ}=ho»÷ºôîæ‡:8H‹Èä!$nLñú3yAØåøAž¶¶²… Y#hlÁ™Aãºzî@„üGθÀàÿ$n@êUAÕߦ0˜K@4ro9èîËÞî¶#h37«µöA fPm?Ó'y^Ÿ±w{îû^ÌEÉí”–iû¿¡‹¦‚ℨ!CØjƒ#:¿“¾Ý ¡¿¦[k®b}ª‰„á•£ãòcNójc„NØÏOàÊQîI£ö!ÐÙ ®Qs#hƒ¥§õŽßÓ¼ûë¾c¯ìvo»¶(¢ç¿ä©iÉÛ<¶1 Õ 2‘VLB(DˆQ«FÊo €ãYM!§»¨a+‡pŒO1ò„‘] µiï>5f€h@R]ª®ÎÄ̱Ú#:AC7®Ïwæ¥ÐàùÉÉ}¡SᦪùéJEÕvÍøÁÆ_}–]߬ðªéþPsæõ(ã~é \°n¹%ÉDBç×u,BZ‚}3kZB@(ä¤'˜Eß]==|†—]»+”ZÚIÔ"iZžøje=Š&cs}…IàCÍ¡²Þðà‹Ã÷#:Ç8½r‹"©¤ ±jƒÝç˜[xÂ]Æ5Þ_2«ÛuÐÞÙ¿oˆÅ#:³ê¨^6á Lm5~¦ñ\ôy–œ]y)Û?è=.ù¿&sfÕÚ!ÛÜ*—TÞ¯)Ö2 #h^Í¢ºº”Í#:½c'QÑ#DíõPQF¡¬F¤“ª( >,¢P~ ±.<=â,?»™nêÖ#D­@7p˜@(ñ(ì•H0ñe+÷h¡‹#hïU>zát0¸ÎÌ×7ñ‹¯_Sܾ3Gæ9[©V!V`Iî¸Y”ä÷üôÕN¸'cYü›J0œ ¡_²Q#:Re†ÄžÓ‡#:¶Ž“I¹iÏ°Òߘ#Doä[þ#h˜Úž×¯4ÐOèX£‹¢Róúûém#DŒq$xîãÎ68§¿ð·9ü–µ„9~§0à#X!C¬‚PD žh^5c¢ÿÊÄÓqÎ|ÃË»¥t>íŒyžØöŠOêF§’/õû’"‰Ý¼cß[b¡!ƒHLœ+¯OÈ.¨îºNN†[*Uµ¦€/‡#:˜/ô_‚-Gµ‚¸Dy|ï×)ØQ"Xë•X^ðżDG^.G£N¿,xó-P£}AÙeŽn±ò‚ÌíDÚ/pPåu\Qïð3ÝŸ7@Wk{Y®®§#D‚0Nóáʽ ‹õ¸¡ _0¶7ŽúA!~ “ê°¤gº›e^+— Ì„0l¶hªÄwÁžSj·ÑïôGž´PçöÖ¡¬_Ê+Q4~˜Ë–{È9ˆ yØtÓ^ÁùÉgŒÀb"…îW.hS#Dìñ×Éݽ¤%Í$RP›eE\nPëÞÚ×±y5‡¿bè'y—3^ýòÈcbµ÷ˆf+¥ú:ntÏlH½\yKÛö|+^?¼Ø0æ6ºàŠ ­M#:*”ïÖØorç¸Ð8Ö4$ƒ8Ÿë^vYÍ>,ŽP¸Ì~úcÚ­q/ïØsó”Àâ:sD%FáµÛc~D“ˆk#h¼Z="ç™=ýß}3¦ú§a-Ÿkåô¨Yø9d¿Ïªí±f{ÅxIÙðäS#h‡ŸG3xL³oGÿã— R‡G‹ =—=Ò»« ìá/LØ#h—In(úwqõ&‘išÞ£²J§ÍÆÇÔmò[éðp>wY Û‡$#D¥ÀtO¾}Ý6"¤\CÎ2ÛHˆ„#¯×§`¤ÔrËZC-¹raÀDgê"BwU]ù_«™…ج½<ÛËÄHÇZXiÕ#hÎåCOÖ}¼ÃxC¬xC².±š¹&íôvصëÏ7òçø6î3måÎÌiÕyÉŠ˜Vx´Ô¢P¤‚¢Š9Š'Ð:˜tÅ;A~K–ì¬UË°ÅâŽ&fŒvÇ”§#h\¹ã}ç?%`Î,Ñàéöï€ùs(ÇǶŽiâä#D–~Vð™6 ¶<¯œä4œ‡S˜öþ“P°>–-q×Êúƒ)õu¿šzÌy÷ÃÛî1'Ÿº>u½µÓm®^ë=Ç‘S»?Õû/î_içGõõ¨×Íü^ª‘XxŠ}6Q7ª£Ã#DÑ“%~Ÿ(¢n0<÷¸»ãyóAAà24!iMvk¸šfDu¢C¹]nl"ê^2rÚbh-²£a•SI#•Å ÝÑ(BèigÉb[YÌþ„ÇÊŸ 1}æÜÕWÙµoòÌ,˜ê—µ«þ§(`ª3:Õ•(w2êÔ8Òñ~°ÛÈS¦ç3ìåï<šâî2$“ 'áE#hœUz‰–é]¶ƒK4r¼àµåýµ!wF2:=AÑ d‰O@ËŽùâèÞôïsμÎç:ãi÷Y6²#D}ռݠnîG«sƒï@ËðÇ¡ßb!㘇o°ÑÕwä´ì„ËÂh<Ͼµùüü_ªåÓ0¿Ë'={ªð)åI:©rp‘Pâ–IÜ#Qª ÕPeqØ|@¹T(2Q€ÝÓ@$Êq. ù0çÅŸdºGŒ”ÐuÍÄî‚ß#Dìé.1TÆå¶c-¼ªPð(¤-Ê=xÝ<„£ÕüÖÛC 7öš¥|äL†˜7£0Ž*n~Ûþ4ÁDÝ­ý`= nBáø” Ü@w›éd@mW (ƒŸ³:j1:k÷]€@þØGáÃÇÔGÝÊ<#—X€÷«{7áÍô ü?`”Ý ˜/#DÂ!³uTpÝ9ÔwöÂHÂQÐQ]A!ß;¹j.L(Db1kîî’I€#DøCÁÙúÓÐœ8&NP€“úÙ£ƒ‚©BÇ÷ZÙAC+Zÿ"V-uä¥#h/ͺ÷Û¼Æwr 1Ðëûî?M¾ýÿåý­P­s¿C_'î¿ïa>Î\ÕTQaP@!»”P“ÏÑÑí#Dð+çôŸÈS×ì ç(ôVG#””‹s½e[µ®$ܾ~‘zv ?­ÿrDB¡”íßíŠ_¨ŽÊýrû£ðƒóJj°ºûÏ›[> QUU ‘ ¦ƒQ'ȆÎ06JÁú„¡„D¯Fþ¨Ô ÜB -$üÁGñæèO+“¾IÍA©AÇÜ" Íê!ýש÷&Ä°Öƒõ™ó×ÇÓîœ~?ÄÖ sŸŒñ#öïÌëv`^8«Ù‘u࿵'õúz÷$¸¶éÏîA'Ö<Ì£·V; $LY—¡G¤Cä_´lä€XÏqí?¤Öpz‚\íR¥3i>¶,I„àhã¹³ü¬#:ƒWúùPÒ[aåNF«ý”P{Çá_9kåRdX\ù};NQ#Öxí&¤ãÖÏÍËk™êzï ÷ ˜¾ERéUìûÈQTq|Ã3”Ùì·gFKÕÍÄ#:­`›P„P¿šO„êl9yš”kÀ`–‘Äì¢ÀÝþÚ3?7A#D“ž¥þlQVü!!ðù‡sTvä<ƒílíFmûqpŸ!øDwFÏÙi‹®ÌÙío8Gpü ÎÍþ27Ìû ÜÁH·pG™£µ¯/`ýX†ïí]‘ÝÔŸ.ª²£8¹¤øNÙRÏC‚wæ´:Ñ»e¾ÄÆGv¶´Z)JBJ6êOö[^áÚ„Ep"@À~±:G‘]Æ9;¦:ÆÊ(Ëá9e&5‹´WÁBûrõÆ_fÅð¹êÍqÍÚÕÒž")%ÝoW8ãþVÔN.â3ÍâÍ¢e¸—y¨L"ÅœÂ1¡ëj0‘Z+ÜÊÔŒÉ ˆæÈFr)‚#ID­³ôÇ¿<ݧ¡úТ#nølʧT)3Q“ÒöÖoÏ'£Á|\brÜm²S0ê#Dlï[Þ”l¢ó)ÊZ9°Ï ¬nbüK5n‹¢éÀiÉjJJ¦|Jòõ¬ciÇÌ˾ ™{i{ŽØ|åó³¾^†sU Ÿ¾ŠªÊ—ê¥Ó®–:î„ãv‰„„‚jÿ]uÎ?òÊ/X‘£ƒ˜„ÖÓ0ê€ÕŠÀF-¦Ü^ç #Dš7´ÐZ?Ðasé³8ëz+ÐÎf/²×ÓJŠI»OÝÌø#h“žÕH³°Ïe$cã¾mBôQAÃòù¡÷ºü‹‚‚%_s;#:@_Ö­‚Á‡KsÙò=#htPG'Ã8ò`)N2Xe[^ägT­ÑYÆÍe°V*èß(4A™û5TRøöbæt˜•ÝW]fïpµ:^Váp—#:1H‰¼‚2,Àõë#:æKŠ#:tæòþ_µuxC’îß“ÊUQ&‹ù„Î÷ÕT<ZD·ìxµ$U§„¾ÿãÔ¨vgcaÖ«9™ô¸–4Æí7ý‹ ¾#hnéG¸L>ÿ‘÷3ø" òD§ðîç÷ÐáNÂvSƒózà‹óTy:=ƃ½Ò£‘€3ƒ²2°eØ© S÷ìp”¦!Š”ï@èh4úzÂt‰ó¸:»ÏûtõØ÷ ÷¦ÄB”×O01t¯”hhÕúû„à!]²úˆT ¼£±?¨Ÿ\ i;ÿ#D/¡‰·¡*Bè–’0)0]Æ|m#Ü£Ârºv! ½…Àp€gN pÑôðyyòp: £ ˆy%BdÚw& oØ“¸“œãžMƒƒîI‘RºØªI°!‰ƒ¡D9ëÐÒ—ÿ€Ü ,5(5’r?fŠª%TRfpT‚#h²2y‘¬‚~ãö*ÿnž~|ÇÔR§`Ï­“âP8 Ö2%Ī— çëmi UѬa.S@¢>Ž;9¾çêCø÷=p¥ô‚¿Õék6±±ØÐlS {˜>_OÛ÷rc!,û‹^ÅåÓáýF3ï’Ì®q3Ã/œ×Âÿƒâ¡£–sMŒÕ2iR‹NK²w{rË\k4~9'³™‡^×ûÝí»ßùä±eõ™‹NÁ ¨<ò­/·5ÇÀ‘ѹ½4ûÐï0B0¢¨àï2ë\b®ž1íô p®'’…ìRÒ´Ƴ5Ž`Ù3±ˆ\†˜ùYlêè–Л{éÒ eÜH5TÀ6X2ðí”Úb¥Îî¼8äÉ âemvøq²_±ðòó¢kË"ÀerJÊÊ¿Å%\¯‚0Ï-@þX`ù˜ô¼ »KÜ—Ê3¾&튃Êb©,`ÙaM×/aŠuë)Ë ‡3»Õ„L0”•U5Éåb˪‡$$4f¨ÕC(y¥KŸ]S–˜°Ôhz0¥}6͇h÷u(ïÙ4؆…iñÙQ.ù|@Ž¡òŒe9©~,hE±ò$Œ!.rù“ÖžløÖQkI^óêmRq)DÖJq œmãG„ÕÍè÷”êŒ&E'½tb>‡|/×òæŠ(?…&~ª}~2Õ ”'öW¿ä¨ÀM¥[%¶$‰]×l«%3JË*ÌÍ)‰?aˆóX ŠI m„$>¢LõÃfsÑ$Ê*X|¦IÎœq‰ËR¥Çqß#h8˜2j׎»V#DvöCŒ\¹$NàžþG†Â»¥é!Û¼ãBìG«¯n#D ÂÛÖ¤2$ìÇI99/i#DH|wl!>£‘e2BÕîö,†&Ì5Þ8å_]óã×®ÉòQYÚé÷dÎ*(ÄŠ .Èw¼‡"Î_½â1DäA‘ &#h:ø~ éËfíÜm {0ù20v‡@ŠÏ=ƒ¦$P5½¶!¶³©˜Îôá!®DKö]ªñ³xƒ‰Nv#hÉža7Š€–#Þ’o“ºq¥ä#D÷ wÙºB" P{Ê(=ôL#D²è¨Mã^ƒØïÉ,›$g(#°ó:zöG»~öq5¾%ÝN½Ø,ôî(òå@ÍH„{SìÞKë)¿4–8¿íõ>>­€n‰)„ï…ö¥×Niôª÷x÷ÏŠ§)â‡q=!3ˆÕºÈZkB܈‘«Ñf'áTm pÃèMuV|_ø ³9£ÖóåÁ(†š¶N{þ#Ö)}îIǮóŽ 3òÝ#º>ys>Vt–;ýX!>P{#@®Ð¶fof¾­™n-kŽ rÝÄ´j=ZÁR’‡sM¸CB˜Y9 ™jÔ ¸#DÚCa)º%Ñî©h¥ÁL#´„2CPä† ’aä+ÀÁF%¨BmWw_¥¾96‚“F#häbâ‚%˜†¢`¢;p>e¤æéwƒ8]=±FAÙ ½k…èá¦$öE³Î(i6Œoüç´u“+²§µàö/ì®;6F†L­«iÑ®DEž1ˆúNþÇËà¸rx$X´ú{N>m*¨åôÕbõ!ƒ«_e@¡êÎâ×ׇÔûbV=•:ÌsŸ9=÷H¦ÒŽÀ~x¥-PÄ¥*Ò̃F»ŸÃCèKpÀ,õQRo~£;d¦qµè©áÏ:‡gßi)~'ÙÙCIògqÆÁ4›‡76ÉœVW Ç7&Òð2Õ%,¤L’P.¥Ý èdã(¡ºQ›wúV°w[< ¶æ¡‹oYLÅ—†l}ø£|¿9!M¶v·¢ðp‹#h÷puŒ!rTÂH¥¡B¨–³×Eˆ{IL<Ô®¾:×ÎNarÒµ¾]Kýþ¾¥|؈  f3h"<ÚŒãƒÁ˜ç:™$4‡ 5K僙s©5¤Ý‘¦"HŸÜC‹ß†yð¾QúwÀ-†áIŸt}‹ê–; iv9è:øÍ·=ŽÒžI£ls)ª×6cŒ“wY1–ÊFH ˆª.„’ ¢M" ”óô¹à?ù2Þ–Àcã“–»§TYu.¦#DË'‡í{ž¸‹ÓÄðÿЈâ©rF! Ñ ŠHD’Y˜Œ¢<#DêSe^™ð ‰URk2Îu hŽYh”¦(1?"a§Kîý|û,aÜÔþª¾Lâápô‰S?¯Î®­ÖÐœh„µ<âCæcr£Nº“¶»Á…áMÔÖ…œ+¢¡†¡~d‰$TPâ•#:A =ä0x–Œ5剿vÀ÷3†u'©òtV¼#ÆOo·9Û±YÙƒ‰&2õ1ƒh©¢ƒ˜xü8¾:sw"æ0}\½)¹"”#KMRŸÙÏ+ïÜ÷Š*{ lÀ MHË)óµÒ[ÞîoÝ#h]Š–б9°7f™æ}Î:ÝI>hŸ-žÛé®#êsƒM®aë‚1#hýßë©[>0"´yo›œ'Pwï™áŠvÞ²Pø:ùi{˜we'½oß¾7Á$lM{ä–¯hÂãƒCr˜tF‹€¬q6 ت7$p4Â0lH~¹Q¹86Á-[aPd!¡ "áüHÀü=ÏÉt5='8Z&Ný9¿6rÇ„ÃO—ÈGÚäÄÛ8]Go#h&ó¼@„NyŒˆw D½k€<¨+M7ù‡ÒOGùùÝdø?,Hîˆpõ÷¡¾ÎG”Îx´À¼Üú@ïœÀÇ;Œ3… J@¶cÕ›G]e¬êEŽ‹œ3ÞWJ(å0m€Æ#:VÍmTi{ñ̉ë‰K‘oÒäöºôÙxyéw­#D˜F‹™ÆýÎV®.Œã«“;UÜ‘Wsîug @Ý—+s>rÔÓO¢™#D i²0fÍØ>…ÐqÐÝŒÊÇU9ñ&ç¹³`î}ð¬!l%óŠØBWËõcÛ‹¯À¼ßo3›ë¢+~¯QÐÔÛ`Ø‘ATŽ†=›'›0~ h÷uükáuTµ™š‡#DÚgO#D+‘NÏEÛLøYWï-òfóâÂ{ÄîõT ìØ î@9=Eù¼.XoÙAÐÃ==.z@vÀ_IóäïyUOÖ7/»v¼ƒ—ãø#:hŠÏÒh|O£ËvG«–æó„>¬•¤~aû#DAOß’ŒVÅ›·sòÜ–°Ù¸WËŇãë£S»¼ÐÓ„Ÿ]œ=ÏÄÌ|5àÄ™ ÍúÙÞ‡*°mÿ‡Ô—QEVÝãäâ$QÀêÔIÍúºÕ ‚BCðß›¹$…yÏY1ò0èËïíqzôìm€÷»&îUÁ Ç æ4WŠý²ÁÏšÌ"@Ú51¤üΔ}uª5 Bîð?ºé8ªP9=ÉÔpn ‹¨ûÌ£“0DD{eAé;Û4{Ÿ®WÜ•\ÂN$‰a;ÔûFC1Dz|}$yi|Ì}}æ¾y¿Éœy!Ië#hƒ—„„aµ#:¹Â&#ÁůMaL¸l:вª@à÷’dâ08¶Û:uNØŠÊG/Yˆa'²A*†ƒÒ{¨ß¿{;ø2|†O͹]Ž¾¯õ"¤R "…hB&UH¿Õ»Eôùî}:â2¨ŸÚrö|”»žáð’H{ëPÌÁñ³à§Êhv$ˆý¾_Ó÷Y?é~w,®¡óq€ AÉü¯£DfÚhV#D‚+Ek{„}u±æ?½ý_ѽ 7*îÙ»!öpù‡qÆT£(ùfˆŠþvÒÊvYK¹ÕŸÎ~†R)7éZc",#h¨9æï+½°ü€²Ëz;ÄÙÈêìý‘ Øø$#h«ÏR ‹ÜÁèWˆòÿO€ ÿ2?3aö}<³†3¦n¯Ö|¾å9@ÝÄýÞÓbÜQ¦€@þ_—ø»ù‡~¿ö=¿¢ÿç#h#?ÁÑÿ„Wó*šƒu1…£2ÔsÐdÐéÂx4_y“ZjøNNŒG¾Sþ¶„éºá}i]þÑæÚGî$€@ÿÏHP~~<€¼ÊôOÉŸOÒo’ïÕ€ê^:÷&  @C5¸3‰ À~–Õb²°õyÀYý9ãíÑl0¯)gd<(¬°Â¨„›WFÍøDfq:Ë 0~r­ãøë|g 7 ”35`aê(auÖÅà'¿îöWü$§=}î ˆ(B#:{["_lå?;þ4’™ lý_#Ÿ³…=uŒUyW›¢wèìôO®UhÍâÿ³stã#:¥x³Ë5),[b‡·èïý-Ãò°Q„¸¸úQ˜0àÊE­Vwª@´8}!„ÃÜßT{WŽrBÃäyúä7*¢#D¦*ç7üÎךŸ'j<ç™çÔûv#D ËÏC8‡º³z;¦4Ï®çünAA_Wj³fÛOn.öþqÿý#€ŒMóY-ÀNDd£ªˆÅì`Š%Áà&¡µdV»Â?…´>>„N] Ì4,>Ȫޢə4MPì»™³ïúæ¯Vyî¦8V˜?çÿCGüüxÿÕþw¼½6\Ÿ ž°±f~c¡pðse˜ÍN{ý>”@‰N½3B=!u=F’Ǹ÷â¶Å¥Ë¹Ì_ñ¾Q°àÿ9»ŸóKüŒ:'G}ý¸ê?ý§,ƒq“¹ç΢k$¤äË @ÔßM›Äó¬’ëJ30Ù&¯DÒ«!Õ5a¿f©‚YØ &-5'‰¦öñLÕºç'#Ï6üÞN3Ï#:ãϵáy>¯y0©¡„F>ª®(‡Ìó¿<}â|ådÕe¤ùãSC;§-vú¹ûþ~_˜5&xb‹B b¢ˆ’",Ôdž`P1&<‡û½EþJyþÀ<¿ãùÒ}¾¿WNöÿd|^Ÿ  ª¨¤õ*eèÜõöqŽ¯dš~žåÞ^šª*áª{âäÄyEÃ*-Ü“õòûÒì:J† “ ®…A´_ü×õˆº Âs;TIKŠ¯›- ¬ç_ÄáÌ<v|šlf‰§‘Rdð¨ós(Ëšì¼ÿŠ’ŽCÈQÇxÅqç]ªÚ:PAë>iç΃Í?Bw'AðâÝLN%R>BJÙëP£ö<"¸ÝÐV'Î_Ú«ª×J›ñQøý |GUptˆ»Ž.Ìó‘QÖ$ñ™â#h…'pšø?w;ž)ëD¼/ÏÑó|›=)#:›ÿ«‘@#Dé%¤Or ož'ƒƒù{¹ìÊ®Pûœ½üáºCŠL8¼7uú ¹ýƒ–6ÌB<ùÑÕ%>x8(#hêHÉú^ᨠóÔãòÓ뜤¹…ïà9Bpû¾m¿5D1÷”ç<ú Ûø§³”| ‘í÷«]*XTÉ•(Aã빡ÖÞNЄUüý*?g¡CŠ ÿ ;ï=åþ Ö!÷=¢½½½ÔùéqÁVÍþ39®Š$ŠퟺƒòÄÉÈD"'"Ž#:Öƒ6ùú=\ÝûÄQ ùP®Ô>{"= înÊúœÞÜE$ϧnO›Ý…¡Ý>›>Š}áT¢TH•C ˆ•/¤fcK“Æ"ð€y’©ao¾œ@°ÐOÏÄ945Onß‘¤ #h PÿSùývö÷òC‘êüÅ3Cü“/eUÌÌSÏI©Vâ}cÍõŽÅv‡Ë!ì(óC0D }ʉ¨;®[”Ÿ£¦l’ù¿7; >Á²á»Ð |àò”¿xà‰µ‘Àf.{²æ ;£cûÏ&wF ˆtzTDž,Þd~ß/µaš"(ê.¦Æ?5,Í)æ»mN’ `p-VWÅ™ñ²±µlç*­Ì¸nûó#D‚ëUò,+ÌßúpÖƒzV,€Nå C6ŠmQ|oÕµâf#h™Cý `çõ'àˆUÚÿž.½¬üÛ[F“cl5s/#D}÷d¯:˜~)?*Xr—ø]˜aÇpΙñסÕà×1#DH#:¬€Ñפ%úÚå佃”Eç¸LîÏ,qïT¥#hbXÍܸúýúY÷û'&ßïÚ§}q’¬a:–4¹#hv™äKoLÀç¼`wr³™ß·|SýIÉêîèM«¸K#hxÁ„¢öÇMÆsé׫V[¯,Ä„žf¬ãG£eÖ}~]<ãüˆæ#h5[Šñ¬_7ëÔÔU"‘“MB·ü—ž™ñå®i{?­ûyë8¼éß:¿+¸œ%œ»ûyâóA/€fb4÷Hró{“À}ÛG°z=ˆˆ‹ÑôŠW´Eæjäpg–LþÐÄ#†¿Ú‚ë²ÑPjÈ#DClí]Ûõ.²ø=¡ÀëáØ"í6×%fô@‰"‰zöâ0HKÙ¤ RÀlü.عP¼±šLÖLÄŠmî{kôßE±°˜7ÏûÚàÓ’óÑÔ1›8»»Øû3#:(ËÚ;MîŒZÃ0͈§ôŒVr²C6#D˸¬CÂŒ?õça®;)ó€H ZE”Q«ô#hÄlIÌeŠB›½ñ´<`P”2B 쵤ùïß\ñÏèàn0R2cWÉ=©ìøiø¶‰ú´ÿŸCÙÉ·ŸwžÖk9"í×/“Ú>Š'Tnà#h%‰axü7mu 9åÌÎxíJr#hx´Ýh‚FÒšGÀw q»¥r² bà$Hglè7•T¿up™¾×ˆ¹0Ž—4cø”d¨ì·€ÁVÔ.Ì6ª®È #‰PƒÇwø˱G¿Ü½mìø—&0f#á#Dî³è"Pˆq#:‡²…ýŸƒõ¹ Ö5#h7Êß啺÷T]ñ5 úÊLüAQ©^ñá£pøÕ¥$T‰ <õKò™—#DTQÒ “ØmÀy†!èhz„[`·ßÿcõ}Ñ1•±Ús¤i;­âiýàŸY(ò\^ÌÝ‘¼S õôhþǼŸU[¿T¶{ÖåõÍÙÑrد…ø¯4„®sØd­£‚”#EŠd¡Ä@k­T­ë¼Ì!þO­ŸíßÏ¥À‡1ƒ15Žùª{ÄÏ×GÜ$ø»éa QîqÎL(Z|G#¦wçUTzIÄ(åâR§=Úñz̺¾>/nW‹Ú²^aúÃƳ(¹gcHûP€hBö~ëbßÚ°ø‰ï^ñ¯ë÷3˜ÝÛbåæSWEØä][Í=&"¼Ÿ›q&S")HfBü¤ÔšKj¢8Ëk^Öòm±»ý~–U#xŒ|œ~¨‹U°{§íZŽùûëägdBáâéw —‚ÖseÙÿ É×¥Ú‘J^jlG,.”‡4Öâ)9ê«àÊ àhDy¾”ÿ©;wS2¾Õ?ëOTéwXA²Ìá½æ³Ÿ£â¼d¾Ãíö_ 97¡ÍC„KÌwsÇÙÅ™þþÿͼû?\³žPíZ uÎø¯®^æDzgßsÃßF€’Ò¹0Ü®`£:3Míóö'†ºy'õã÷t²Ý“ûž¢3o{j:W^ؽüïf°v4UàWòZÙí­:en–Vÿ7¤«.e®(M!'Áïï¤Z°#D~íì#h²Š‚§aðÊoØÈ"åßâ«l³s¨ªÆb§ŒãœD;Nåk2ä: F/Úî|8²âƒ$œ„9*¡_5¥tç§C»ã{ØÄ`•ð® zÁ›õÈäã#DϤUú91;>31•ê7¢áG|K‰zI…öžOºy‘CxÀIœ å2‚Éþ"ÿÌü†fÐ ¢‡3ß72³ƒymÿ(ý^1—lCq;JšWïîŽ^`XÂ[¢Û·É€°z†l[‹àãC=ë9ùè‚bi»'w[¹]v³V#:á‚™»’r‡Ë~™1UVžMßLÕîÚ׎,1Ìkö¡Q aö\ M*…C<#Dßä%²‘xÃX„5O6A€…½·y`Yu¾[&†³^Ž?)€Â¥qVˆÆkÒ)Ž*f—«À»Ñ·ÓÑô{ãð_ àoMªHýüßEå5‹€†àã°ýÝõîÖ¦eGw«ŒÛÆä%•ð—XÚ%„žå9Q‰|ÇÙÕM¾6ü8l®’ŒÈvÉ„û5{êiÃþ¿L3¨t¸mþ'°‹]êáñ^7"ê5>ûëí}µÌŒvù'b8w«˜qÇ?†#Dï÷Üç‹¢›¶šcù¿?%îj¸ß·œ68åÌÍjLsÝ{?®^:s‡‘elN’Bo¤^ö„¼j)Ž©ÖýuÕf¸‚vXŽ¤œÜ Ët&¤c?5 h·ÁSNî$SŸt~K"Ñ}ÙýCm¨ÄÛ³b'v?ŸoΟ¤±÷zó:r¢ÐæÈTPÇ¥øŸ®çÑÓ~œd¶ê­v#hñidÉì5˜Þ¼¥tpË?n^æ‚÷ëJ¨ÉüÚƒÏkÎsú°ùe6ÿ§ k¿c¢b»óu:’ƒÍ€ðÉmÿF¶‰‚1øæK«öf¼c'™¯Ì&ðŽLm^‰­çÜ¢®¡¿ZÚ-B¿„G§·Û6ƒÔ†yýÑU*&AžÏïQ ¿Kóßát¿ñüúÑ×¢õsÈW‘Ìü"ÉÊ;h*#: ÚV(§ÁÍkDˆ2Bæ,ºóëty¥@3gh€˜ðœÍ—|s©cížcÂcH­Éƒ÷Ñö&=øãÛŒÔ6wûT É–0&¿/¬Cí™;Ù!ik÷J>§íþ,ýñ˜‰€ÚÁ»¸?yfâ#D\›È9}ì'Þ2$RUM—ÄÑ` ì‹nhgŠ»ƒV]é€pìEì’¬¯ ‹‘Ù-Œýó>ý^ABæiÍKX¾Ç¡…€‚øŒÞTXÆ«´ØñöUÎ#hP#hoÔÓÅE‹”Ä`!}õR¿q³°ÿŠï½›w(#Ë1Ê‘Yöã2ÏñšÓuÚ+÷ŸoÀƒHvOØwëZ”°ã‰ñ²ÇiŽ¬wvf§ÆÒ5Ðãóâ0p8?†iŘ–#5ÍÙÊ1!=LW/äŠ÷*‚„’â‰æèÎ]°ïf~o…¦-æçÕ­˜@„™*ðÿ£âò¼;ü<¼A(Ö¢&ºú÷»1Ïx•’~DAñz·”Ôé<¦ŒZêJâ¥Z€±\{Ø=#Œ)‹æ@׋\ä€óË‚tÅF#D®«™Þ:‰ú;Yp¤ÉsœÈ<yÈÜõD”­Öàó”.o)¢Í5(IP@…Üâ®vOX2,òwðdi”à‡rêd85º]ÕÑíè’e€ÌÜ\ÏVDÌŒ•á€b&@èýl`¢{¼aĆH”~©?ew¿¸Ã1ªNtÆ}Œ>aSqòüØä;†¤¦¿É2ŒÏ|WŒÓ­|Å&+²4عû|íýï¯ôÃ_•ùçÕÅRN+Ì¢cJ–K01ˆ!H!\½ðs²@Bd>8'ÅèÙfjh²$„’ü¿’ ú]iÇC2¾ðleI°S0kxüu«kÝøíć$A“#Ã-ÙUý?ñчþç‡ù×rüR¤¦f°o!™4d×Ã<5æ­÷£ž†$è©4‰•Qø?+õ§µš.áPJ€o/áù^«w|&Þ ;oè>›Ç(Ïf"˜vè-—u«h?ìñ_~Ùå8S†|c_Ô(*Ñ‘i²”ƒCž R~¤ûþ¯ Eyl§–[¡Õó—̵hôÙ<0Rk<µÝqe~ßËßÈÁ™â…‹ðîöj¬*â%«?l_glÓÊ1n}x¨c£íõóŒg2ŠQPÊ”4ºB”z µÁ[=Ão7Ë4•Ù'¹w†ý+oÆIRB9e{%á8Ö?e¦²nFôæ¢ÇÖã/W5Õã5m#Dó×F,ë}õT#:àñ(°Ø„2}[@mŽ¶£Üi óp^Á˧Ex@ºØ½(ˆÑudõ!AX›¢˜IDª*ÿlç•åîcv{÷çú¤iíäYÜã“YÞw1-tÌ?Þ$lÜ4Iýql :é3,&i}¯%o¬\!¡@×.”«IUÄÖ-Q5´#h¢)&ÔÁfò´JD#h"W¡oÆÎV¢‚¶ Ú6™”rBÌ6aë*:Sƒ§†Îc.ë}@ƸÞxLS’µg*GØo(!ãhHáÊÀ<Ž»ÓÈ\8¡(â„„Òùb¡ý=:f”¦VWŒ—cå1­ àj+¬ D@Ñ.GGs_ªuAÑÔÄBšYpßpd9É#:¯é=[Gö‘÷ýÐÛ™p©ü§ôí!`$#hìÝШˆÜŠ›¶É¸àYcõý ¨Ú„’‚_äÁx7sÄ@ýuCòcOG1§ÝñT¦*¤‹àùõ þ]¦¤.PÎñí#š½ÚïO:òÁÝrhýÄøç̲¯IZQCýƒíLë;îÙrc?üÊ|ã‰gc„X¹Áƒ;åÃòF–6?·’2Šm݇_×kµtwj©¾ØWÇæñ‰fÖ«–»îtMYì!C{UKƒ¡À\å7A÷¸M-Á#Òúþçr¢ˆœJ#:uuª²Ø<÷Ÿ¥ß‡Åü0?î=c„Ïí™$q,¿Nb )@J@¢ü>ŽŸŸ’ŸðsÒ`¹ "¬Ž!/ ÙÒƒ@Q÷ér4Æqœ*©­&Ë{1‰ãÈ£ /¿XhÍ'•¥ ۢݩÚëc¼ ÜIJêßÄßW6õ§zzŠòK§{n¡Ùj5oÔIîP%ÓìHÄ>ßɈ@½[µ¨pD#:r+bL†IáŸ÷L#:}½àÀzÿ€ýB #:‡ûÒJ#åÏ­ì» ZhýÓ’¯û’Q©$V•#h(¡MC’$ƒÒÖ¿˜a M¨ " Ú8y­þžo`ÿ49“z#D9þ°Ü„<ßjs0ußYìp$8Ù¿|Ô“#:ÿØ9wòÁømM&¨º„Lå+̃¾SûÂcúé DjÞÌ€—àŸ·Äœþ¡_/æ»xùÆÿC”CíᄵñO"wBU¢ÜÝþçútž½CgñLhü*} yÑnP¦”B΃@§Q#hAdæîò«£_~ïÓXg¦i™>Ög#:açÂø'O`™7†*hƒd8ÿÁŽ@›Ù£ƒà䢴¾£h‰,×zxtÒ"­S˜¿ÄI í?Ä÷˜ôJÿT”É “ûÆЖCÉU;$5QD(Î8Ø'þ2Gý†AÉÿ¥þC»|à2(^$?ÜŠ¯Ì¹µÓ…ù¯f™GºßkÉ߈'U'ÙPs>é|§šŽ²yZ (.NóF»f788Óf5YexΡÔÇ6; 1ï€ËËA%nÉfzXÐ’ô°2ñÒaÎxÔFiÇc›r@ÙÅ(o$#pÌÌÌÀǾÝAAÉ”>?÷±ÐUó®åÐ’å? ¶åçßáßPHó ñ'5%ž6’.10ŠWÕP©Á:LÀ#:¢†ÇXfјᯣÓíìÿ‘Ý®s纥çk¦‹—BJHˆÈÈôèØÆ#DI#hIDPa‚깟n¯lÞïy]s”rèë¸cf{õ® 2)ZP  „=%GJ¦j£fÊšmªò™v®˜Ý«;»ºÓ‹¹º”ìÚÖý*7I L‰ÍEd}ãØO>ú¬¥Âý2Þ‰¨ëXvâÅ#r3«Z8bK‡XPA(*§g¡Äöpã›FÁÍH%9¶o-FU™€‰é( åI@yúzq¾ "©:'¤«‰xAÔ‰¸"·#:ªÄrîG @Ä¡5%#h‘D ¦k,#(bÖº€S¤ÕŠÒŽ¦I7#D˜R!²SP#:uÃ)¤Dø§DšæÉN7Ó|XHR䯑o#:Ùªh3rAB™u Īîx!Eï¯LæCƒ›¤#:îL ö0§åC´ ×]ÐMP Gèöý_Š~ïöçó‡ø4,»Ñ UîùÃíýoýª—ØãüøYßzjSûîÌ8t‡‘ÌFl¤Tö{Éì~zèpÉÜ?€¤.9,Çà\K¼Ø’ž— 2!ú}²ƒ:‚þ¯8eçÕ—Pv¿pPñïÂøçVj¼Ð©énš>IÁø’dR‡‡aâ7‡`óO›ûwêŒcxÉï;#D]”OÜ9Ž<ƒór–é#:åðbÅ:`®Qœ24п¾Ô™:fýphÊÖËy {¾DîáQ¶öC•N­¹±²,MQ©ˆ˜È¨ÉÖ4Ô5•>#Dê‘ð$ D4e•–ŽE’4Ó ²*²Bw‚‡Ø~Ý~£þbý{g÷Òc²d8uiûŸÓ®øæ6]QõmÄx‹rôI‚"syP" ÊÀG#:‘`hz¸–!¤üFò1½Ï³èÁúßà3ÄK¿¦~_´Yñ(÷}2ÍVëp“5gãólùpRž Ò%ØZjÖÄÉû`šÿ\Jþ\l?X9Ǿ°·;Qm„J4”é¨*äDiú5ŸºpeÜÙñ£½)℉ĺ×8§®è-jã+šÝà‰N¶. P°[ÞÍaÿ7øù9‚î·Q.Ï$pGW.¾Ôý‡”Nž«[Z;ÉéTun3s$–ž 8=žTß)ôá²:‰ad>l6Y17͇;~˜nɆçØÌOÄXb–‘¤™¬"1ˆaÇ pIv`ºu}#D<Š o,Þø²Ïƒäç)„Ý‹cýßimÉòVŽ’€¢Q 3£3ñX^‘ û)ªÎµ®vAhæ‹6ㆤû%¼ºñÉÓe±uý$ƒÛè‡cúÒñ˜€¾¶¸G§¯üÿ£‡³ôtù‡Óë_OœjÿVµjÄ c÷&!ö/²*1-ìOï_/¬ÙÒ£Ñ(æ´ÛѪ:~5L$Vu!úŒ#,qŒåã•œ±ü(ƒS!ƒŸpwú7»åÝ´SPÖç>†;ˆÆÞò ˜B‘:!0ƒÔyëõߎèøõéí²õÉBÞš/‘·ÏLH9*š»3@ш‘Y€'K8¦Þs⿇ˆÿ_û?Þ ýó2/†ø{™DxÇð‡'í¿˜-#:Öíû«O¡ðíßËÇ~™…Îä­Þ_¿ê~=}ÃÀvî\pÿH[v3‹’ñý!poJÒZ±‹ÂL\ˆz?`ÖdQí*,ñÁkuÁ‘Ãø#D„#;O^íŸO·0T™7ùÿѸ‘D»¾^èy °¢%ÁÔ ³óú´*§ÔaÄ/IC¤¡¤˜‡òÕOaŒ±B¾\´éÿSG‘#Dš|Æ^µ›Ž.Uø—Lc"ºX½ßÕõGïüÃü?¿ƒ”å×/ÿ}ÿP3§ò£üù_õÿx1¨Bóö– }_æ­¡®ßi Ž.–‘¦c®gý ×²õW:½‡ê‘ÈèZÿÖ»Ÿæ!¤- BGÝþü؃ö4HzzRÅç~¤\2ÁÁœˆAæC±Ìà€$ˆCYýöª—¤–sœ#D¦lŽ{¦Ám×Xf€21#žæ¶Û·¯¬7‘ý-{¾|ð¸GSÇ¿Õ/ù&Ä÷\öâ<€Ã§üdaü-_#vÃ[ddcvõ‘=ýýÍ»€6Ì#:õ#:ð0AÚ¢úòñÄèùy“5øýO¨åOè·®|Vhrç3Äõ0Îe#h(°¯Wh‡㸰Œ}]ØŸR¦‡¢#ÐÞ>í%]0\r®ÓüþÐéÊp?>·×ë4{ÔXÙû;»„çpï—éöžcIÒOhb[ÞbHÖªÈt„–ªp·œo#xfRŒ ¼žï‘\èªM'Eúé|™êžôEùA#:NISúÔ?Ø•öÖFIâ’hÃOù>{ ø_òj`Düë ߤC>„ú9N÷ñ,âîSËì÷dó¾n8“Xñ8ÁÑýYž2ÉØÌ›Žçt«wë×Ó¾ÂC3`6“™RRÔHcÝ?£åâôäüÏdø¯ÖwÁc Ð§Ëª¨žGõý'µ“ŸWø’éƒó!¶Ûû2 ÏHõFfœ\õ2˜‚¤?ˆ)¨|2$V#h$ j´ä¼á`dÒ#h2,#DRÚ`JLyÿpf P£ê Þ¶” Aüâ[Ð\ Ö˜óÑP9û$€šÕdªíÐË€Q—ÆŽêS“ØtðYC&y¡éГÈ=«ßenfÛœqž~†§tØïçaÏ#:ɾøçg‰*úprV" Ê…BANôU(@©VO“ûáAJo˜A™D%3æö}ÿi_“ßýßd¾tÛÞqåúÙßÇÈuû‰jŸa†õ«ÈѯI÷†t°Ž2ªðö†),k,Äü jh‘#ÌúŽ&! mRS#DÝeðçËË…|ŠÏÌZ)UíÍIþ²b­`fKž ÃðÚ¿xzxQ€•Õ{«{õn 2QˆXÂã²€Ëãž'>ïV°¦NiIçÞt€c±ÂOéàv¶öž‡¡58î#h:d—bwy³Ð‰ÆÕEyÁ Øo´×Ýðo•¨ú—ÅDò ð¡vîU˦ru÷ üI&Ód’"?¾Oœû`§¨ÏâI©Þ‡¬{äõ·+Ôwf¾mûg˜1ËÈòØàmø ¦ ˆv¿™ê€Ýo‘P9ÌÉ}1ù(<κ̜§ãTΔV`±=h«îç‘7NšpÀq „£Õ4”¡C³M£Ó܇€×ÔlÔÕÅ0áCPCêùK5A’zƒŒò„ô5ÖJ«œ¹PÞ)69N©£ cÕH>¡ÏŸ»ö—°‡Q&¥Òü¦ÿ€lÑaÂŒb`%­Q¡ÖBüµñ_¯ŽÐro‹ÉƒúþÄQjq‰Ÿ+Á°Ž?>û6Ö¥§¯¨Ö?³ô.@ÁÅMô¢0~_ôØhø‚t ª(0XKÒÀ?¬ø¡ÅÈR¢?á=¦ð6‚\0ùO°leѤÔgC Õ³ÜÎæµRå›Bw(©þ`1sCˆñÞй­/™XYhI;Àü˜¨“`DT*þW;öà--µë©ÃãÖ§/8„“š–#hˆBÍ$œ//çXò O|®ÍfdaBt]db@¶¡‰¦yQ,Prâ ¿'ù-Æ5 t\%EKLj†›Y˶YXXðR…DÅ:(9µÒøÛXxÅþ?f#:üº(dŠ°8†^‘Z·:9Á­Ÿ³5}|ü“©Æ&+[ólh}õ5_uýø¨}ÈÕ0ÀqŸÆFŠá÷\š'åy?‚wv«ü´S>G¾|y(Àþv™HµÑñëXi9"=«\`3tx°0$ÎõlðN@€#hC5P(~À7_¤Rﳨ·æåúcq"™5¥Úöªz6ü}©ÐëÅÿwÿ†ð¾HŸ~(zP}‚°° D“Ä=ÿÏü^pXô‰(=u'W)ì5Ð(œµ7ûu…î…ýhõ)uÄþb0î¿iíœB #D»ÿ°.ÝÜ;¤û'óO ¯¡<*v!Ó­,hÇm*µZŒM­‚Ïkz÷Äô[Ð|ˆfâ@Ù#h'™S0øÿÑYèå7×c‰ŒÉàeƒŸÐ£SÛh‰¬³ºSÒ1jS#D@0²…$@š¬ãŒbËp»Q-T”ªe'‘µ¦p•+Aÿw©Ò#hkO/í.Í°É$,–…¾ «wI$«µV.ê!qãÝÃÎ…Ù “”ìí¯ïôž„XyÁAdqCk#ÑH@¹v÷ñ ¦÷ ŵ ®+äéë ÷LÎû*S/–Cü.Û'c§ÀìnÁųݯôìÛ1#DÞá¬v¦£#:æFVäQâM~Ÿ›õÏ?|±CFä´#hÿ3”˜4Çî?¼ÿ€”qüç…ãñüÃÏåÿ–´Yc?åwpÈ`d7þ`“//CSTº†¦Iþ©å¾¦lWóœ¡2‚œ[™UÓTDDª#D.ŸåÙK“­=ÉûÀ|§ÐPT¿«Y÷ñZ3øY?òíÃf¶­mâ åf¦#Dõ†Kb3öíþÁijŒÙÃËjÅÙbq©ƒÇ›ë#:Á'A^Ýìå;už§Ó{îa9ïs L(\÷ÍLƒ9IIÔôÛÎò=ÿ#h7íÕ26ÊB'/Œ5À0 $Â]'‰ÅÚÂ")ȱ@;@˜–ˆhÉ9ìc‡9 °òÇÌ ©è›×ÞuAr"&2H@P%¡#‰] yÔ+·ù¿?ü¿óy€•Éd#Ù!ßù¢Ç?Hi¶P‚Š=(v°ëýê¾ ÖprO?„’z „ø׳Z,ö–¬Œ ÐÔÍñ vA‹mŒ7Ì#EÐ&$Ál4²bR ÁÜ-7,©ÉÁ|½†_ÜÁûƒöú¯Ñ?Hͼÿ†ý—aòmVMb³&øXµøõ-§ý@çŸ.òFôov¤ß ”™|åc„ôÊï_Y2Y¼Ïêýy£ü3«ã'ŒÒwqÆ£Õ”ora­ãÖ«a[Ü4ú½Rpnd‚ÊqV8òÂåu×aM•6¤uêI$“7Q¦±¤8…çØëÀû&MN2ÙÎJÊv#hÀðGкTüÚä.úñÑg×˽ƒë÷Îî¾Vø¢ÐrrýzcÞ#:Ø6|#•4Т [¹=ú@!!‰*íq›W–—‘Uo-—þÞhúÒê Ðv5ì=^]ÇÊ4{©‰¤b…¢ ‘‰Gƒç=T9|Áƒ£ˆöƒ!d#:>ƒHT$œýª5ÇvxO\9Ïiú¶4 ÎÚÔþ¥¨ÎÂzÔv6Êž²½®̹+¤:`ïçúþIŸ›øòÔÑQCcOëÕ_Ú "H‹ô+A›7)$u~Þ0 ù&#çùVA=õ_XÄ{z?hçˆ §½€_í ólZkSÄ{X~ñ$[Òõ ÃAgò“¶#äôØ¢Õ`õrA ü†Ó>°5í?ã‘Wàj*MØ0˜RѨ‰ø¤úÄÛAýËÆ×ÞPèÏ&†r‰Õ>OÓõ}ÅþÔìȘìWL‰âCLh“})ä‡é€]2¡ÈçóÅAQúÐ6¤ ó ¿©~òtH%<^A.bJЈòý¿z¿è$¥uE??ˉóû´I÷aωãðañ¶~!çt_º”±ýFžîi·¯L‰ê3}ñÔ Šsš7ËkuqKµÁ#hÓÜ$üö­‹Ž?R#:ýš™8øðIñ#:’|æ9þ‰.yýk $›€}Mnõ>ºZЙ'8wyýA¿#¸šÎ¢,0"Dç1真aUü¬qÈö" >Ô¨£ä‰…¨2}÷ýþcÜÈzãۃр$äc'¤m“O2têwËæ9óÅÿ ân]GÑ­…’¤×{Þ©èV&Àµìö¶":.-‘Ýœ«ií´†¶MMÀ@lù¡aG#hUSQ #:פ¸U;–¹TN{;•#hÁY`™ßýÞˆðÈlô¢""Xª¢PK9êÌsÐ춘©*–c¢æÃ-æ#:9Ó!Yólž¦'¹éÒwü>lØ-¼6ÙŠuXpiWá îú)sÏ@Ò´#D~=½È(a#hà¢ÓȦŒôö?bT±p0QÏâu¹ôÏB5\ª\°QlKK¾G™§—Ržù}à3$§?4u>pîÂPý”ÞyÏPz„I5‡Ÿ¯ÃÁN‚DI!ñ N\äõ§»º¼ðDܬ6–¤Î òþwÚ;7·9Dâ|-çO^"9½b¶Ô*#h½#:(ï.®¯juŸ5€ØÝ‚š¶‡ó|tЙL3LpÕk]árJ™¢C/Ú1ö9û/ûhS˜œ“û'šF‡º¤¡_ùœÚÞúZÆeÔÐÕµMM%‚ÅÆ•Ë5rB¯R£KT$îži“»ôü~u>ÓÔ+óbÄ~³™=ÇáGæçá ¾Ï#hIü¾0P¼'êÖ}ãcúˆ$Nµ¯f#D“”rAmäýûUÿmþ×ÐÔr`>dž¶1Tý¨bôYýžfñ9÷v®¿‡å:÷b5ØÌ„gªˆGèn*ˆØÀQ‚Rv¿Â>ñ?`iûw’N X&:Tð1®}OS=]ÌVÎÚhý Ùׂp6›m!õ…%tŒ€#•(B[BØb{¼Úlsóß‚§ÕÕ|#Øà‡ÈìW“ª(¿?q0xˆj4Cä’˜  ‚žÊÜ â~‘ë_=×èêOÏ©õš*ê!™ûOñà÷w{>=üA/ȳ )ñ$ +bD‚»Jƒù(ì$‹­”e@?+„LG<פæx¹äÇÃAü$)|ù(u(U*$¤#:Pk6“M˜§2êŠuªÆå3óä¨c”oâ‚O·äû1ëHåýÝ,¯oÉÈÛ?|¼õÃbÈ ÎÝC±¿O°Ôì{ß>³Ô„B x¾!9wÇ&t$û¤YŒ—boÀĆÌ5Œ:ô|³—¤’¥‡Y‚ ûC`æ!65êsŠ~ÉÚzt32¤dȪ=Ÿ7™Ù!–2•BwF¨$µŽê#hȃ@œiãX§½ˆéáåÙÉÐönŽè„^XŽ‘o{3!çå$É&6d'm*¡ò´÷>³Æuî ‰Â ÎLlœù€Ô/™“ÖoAæIÙE×ùpÖ¾Ot:(ã§åïFêg˜œKäÁüíøáƒzó‰ñ_OšuyC¸ Ã;yˆ6¦ò÷ÜàâA$|PòÌÙ쌉ž(ôÛÂŒKd!ǹ]§½öH€=m` ‚€êp ˜‰g#D!ß&Ä¢½ÿDêÄŠŠ¯s³WS¶²¦>ƒèú¿ŒÒdÖ[#:~g…*Q¡XþÒL€ýÏ…–˼뾼ÿ { â©# ªæ¨W‡Wi {`Ê°@>!UU‰RÀrvó3ñô@y0Nñ$”üȈƒ¸)Ú+«j^ô·³ôGõ˜AÜ0¥eÓ4ta3Õ³ý¤J~ahU¥§3Ó%ÒÔ9f $„»º&ÆO·¡`}zpð›²KD`{$dÀ!wôiå±V<#D#D1¶Bù{Ü?­ú‡O;dáÜàŽ;P\#:dÞØk¥9……œQ®³sa`«1^Ùåýœ’CÔÅ@µSÀðR‘²y°ö'w(“+óû@ð÷WÏ_d—2Šˆ§>òâ¸=G¶C(ˆP'•>:ó×þÓsQ@¿QEêÿÅþLŸ“O騞 EˆÁ‡·Âøžu?`r7¹žv[ê(Ϥ†’¶ÉòI ûýÿõا¹9{GOÔlO†¤ùÚ=ð_Úû“[:Á#DèG€/‚rh2#DŸõFê^'#z4Ú (ij¬<ì¤ÑY)ÙÜäbñ›4XM?EÕðBˆ\È[ùgõ,pdC<¿/ï¼{“ÏÛÈœKæ†u)þ×)ÄéûPÏ”‰oñËäÌ=ýµ$œ¿¬fÉ"Z… á¯ñ3ª#÷ê%ß>¥ÿXdIŠî˜ßÀž{Ùªw‡: q¶ÚU·Ó,gÈCEHP…ͨ?Aëñ=ùM›:”»F*£:q¶L–)KLª³,Ãïm¬ÜÉá¥ËÖÕqŸI¤‚/ÎHaøM†$# 7åõa»hF°þZW I6%Y†ÖÀܵeº°­ƒ+gÙ•—)LʤÆÒƶ“)£ ½ªë(N'P?­ þ±#D$¢ýƒ5*Ožm®;¼qÇÞxþЯOb{ôÚÿ!âc°=|S½e]*šB@¡Núºi÷’@À§¼ô<3Þ„ 3#g™x| IÀÏü!aòøzô;'dý)©;¼ý§^ø=ä«Ý?(ºËè¬kØ?­ö†Àj_çÒíRìõ=ïsöáê}:ˆyCYÓò|—Èž’a¸z”Ó˜D©Üö;㌘É$ZU$ þ*!lÁš® ìÂÈ„ àï.’ûbK/\ß#ú ðð<ò"œÄéòм8ôuMSO©:, ²¤àb:ù¾úÉïtÄ*Šñú ¯ÔÕAkSxÁ„Öš¾ÿô…Ïöm+-”n¶•U5.óZiÑ#©uIAžG÷âté­4ÑîDŒqŠÌÑ~^ž¯\X5QeT€?““êÙJ·z0o˹U%¸ €]&Õ%Õkùò­‡f­óÒd—{^F¸„“ÐÍC¤éÇ¢VÜ"2¯×Áèo#:D†ÅA¤éO¤Ä?O"žm˜yÜ>@€_Ù7çonØægÏ̈ÿsçèI÷¯Ù‡  æ–NߨA¡]>(Öè(²(Ë—A6VOv&&ç]›„!Hñ+ßR»çÇâ”~˜~Ò½oèã5%‡íåÇ_ν §¨|¸^$ˆ¨sAüÇW¼ÏúŸõ#T  â#:6%Á  6¡ÄêÅŸÐnvæb×·Z•ÿIÐ’ù3„ÍQß-'÷æ@„NÔ’XÃDg!‰˜×ò{ÀaïI©§1¥&pA2-¿»O›€°TÁ(ØY#DÏëÛ$b,ò!Æ”ÎOQ¯elG¼»{|µ=ªý#D\©éR¡ó}t^rÚÁ`˜Ö‹öëœ?×GùçÏ:brI}»Ñœ}óêåÆ‚¨I²^B™¿ÐΚiz‹}#h¢ëœENîÊðL3fhŘ“YÏ÷ŸU·|ã*ƒ¢½†Ôþ3ù>OC7´ñ sp¿ø‰â¢ÿ{å HuO]H‰$SHL“#à>½kßþ¿ã΀CÄ÷Õõ3`õ_tüÖ0÷§Eø'Åøõ#DÅÓ>8„tž¿°šp¯Ð¤ é|>áù˜¾‡±ï1NßÚT)ñaÝüýµaÒdŸLà ‡@|·hO05«•_V’D/ šI¥£pjaþ’Åþf’ÈnKõŸeää°‡³S0‡HêOVºÀ}08Å6Œ*š‘§À9.ä#:9c#DipĆ@Ñe–Ë`Ž>?·îü ýß³øõš„ÎŒ¡~?£ò&ªŠ¢g÷àÏ$•Ô”£ ×&Ó†¬$k$§0äÀée²’¿ÅϦ‰¢ò²ÌH0EÇó—jRŸ3•uÃËPëX7Á” #hˆ$78ü=pA_`dËà>±h÷}€uP9Ú*#:ä B"Iwv¼£žŸØ¢ñ,Tj¾íɸ’5(9CøÒQ$‘†@>¾ßžý}R迲C§éc`ŸÁ>½‰óÉ#¡õ›~ÿv}ýüËmÅwe)Åõšs¢‘#h´[ âU5×OÓöüÜ8‡’9ÒØ,αÜ4;HfEúÖú¡¿¬‰ `¼#:G­É¼%³¨‡=êÿ*~ªüðg‹P½ÐÛ¯å_“Joƒ¾œq3*‚•é0èI±çטaÒWÙ Ö>^ƒ'ë]”´'žÏ§#D¯‹þËfÁ9ûLÐQ$Ð#%ƒ'id7£DääƒÏj²BdM¿·j0#:aTD©@À¢]êm<[9ñ•)!#:ÝTïøŽÑAþsý¡Êp¯ÙÜwû1T]¹âoaúž«fI ÉÎãVQÆrO‡{!š#D\#:h€\X˜¦€6}'æã³ßýž ¿6’ó@¿ÜjkÙ#:ÖaÒxIóŸ9E+ü¦¹ÄëÜ^Øg¾{I˜~Ü8æøÃ`þ/ø?ÛSý?†­ç?Ë+]>þ5ûò+¡7û¸ c'ôÄ>°þüÊØDôJäB™P¡BêTÉ )8©4H¦ª Šð:¯×c1þ“™¶<Ð`US¡E`³ ƒ¤ÒµŒÅ•¶H©#Dq?¸&Õùßöã4RBëÿdÖí2ðbE°Î uâ R°Çþíþ¤lF´™²£Mþæ‘~üó]ð\g0þ6d`J@çdCÞªƒ›êm#DTÏ—,ÉÃKyê{gj4ñ5h¸x×fæeÓv¡ûÒKHY”À6!0€u‰‚ø2ŒEáÁ‚ò…Cô/âœ{õÿ Z¶ü>px‹#:Ðv†¹Óq­ëý›ÛàËïOßÐRíaE#:¯Vn~R÷*9cá0ö+2ˆÆVßÞ3B±=MsJ®ádþ#hA* ;nV¢½–±™yqwëÁPKì ‰ÕñíuÆ¿w»¹k0ÂȨ4(œª€Æ*”е)ÂY=< „^[59:ü¨œ±\îÂÎK !7E_'#:#h@qe(·¯)¯Ykƒõò+ÁO‹Ôe]ìÐÎk›”k7^£9†s;71#Ì)›6 _£8?Æý¡ˆ°Ú¡Œm–ÖÕ²èì)VG6l†#hÁMNëñÀ¤PÚzη§âÉéú8±LðÓƒ&|©,¯˜Å4»iÚ¢q+ ”D•Aëü |„Yo˜±Ëém÷ŒUHˆd©ñÞIžaÔ8ð.ÅgÙÞÕ£‚¥‰äðñëô}‡³YÕ›éâ;ÏÌóY¾ø‹[É®I»ÃCiª£9Ô«äO}Í£öc²Ô¯­£,CµÓJJxC26œ|¬ìwQ 9A Í/°MQ²Ï¬÷–£úºGs¶àus¼mµH"@(‰ÁC™%ŒGù>'i^CÑÊpgÏ{D7Š.ˆçÆ yÝî5 Vk#:Mz]ÝÑÅÏãÅQ…(îaþàû~­•Ó1ª-qÄå®’L–t÷“³'Ì@ò×Ç'u;Öv0•#:„ÀøGáÿÉþÖÏBá×½Ýðý˜ ¶CÉ$>Þqô p7÷~í|>Ÿ‹¤I]ü9X(PÿŸ7ìêÙ„#:’c›ó‘‹¸Ñ‹œ:#DÈNÐ ØY†ß)$â;Y‰Š¼kGÅ4€3D]&—"dS ‘hóyxgéîIk…Wn¾l®íE%à¦Ú=„€è(Q¹ª#h–]~#:íšü3AôjÕH{q+¬ƒ©?6h²éAŒF"»é k_ˆe±™N+`êr@¢G#¸úOâпY… %×ÿƒ€EÁD?YÖHŽ†ßÙ’²nœÖmRQû¸ôu|ÏN4¼lhݲ6Ç_=û^, ¶0ÖåEDFùsR ƒ{7adAÅð§þ¤VzÏ ¹ìtkÓñÅ”;àà.ÿó¾‘#ã–9Ëü1«héC}Ù™ N›ÕU3(‰²¹ÙS»lÆ;CâÕ!û7°¾×‰3g.Þˆ‹v¦ŠŽGv6aâáGv|ç9Hê¸7*µy:N?Å¿´1ìó袱}ÉGÙUŸ†jw™È)ùucyqÁƒç=£ lù"yê­â ãÖÞvSÛ/·þ,€P¦Þ“õ·»ëý~XOá¯.t”žäq#D½D-ç~øoäR!×GPº’/r«ûK²î“©‘7CνuMŒuX”7ð—ëYlåÌ»Ò%O½ýêJ&rûîY@9ðʯ{F·+ÖKÙ¤]Ðö¾ „ÂÚÍdð¼(IìïN²$Фe®ÛVØÆÕžÒþ8nÎÜâ9ñG}æý'¶ÓHNúŽ¡¯âû`v9F<:¹@Æ#h¯PEÍrhnÍñ"öøcÔÚ£Åfo)ÞÎ{Ôô“îý¯}‡‹ÍÙ>9sz`2Íñ‰D üÄ 8¢/;+ör"ßøø¨z~ßjøÓ³Áõý_xùMMBËL»Nã®JÏT$#:p>¢-­#:2 ‘àæèœ{âŠ;æ¢] Óg<#:1›4å™NgŠ‹ú$sô¯¦,H3xIä»Þõ#D„¬ ;xÔ LtYÇM‰¨Z;¶ð" jBÂ#Dé¥ÛÊaÿËÏËCø|½êð,BÃeŸžÛ,þqPÅB*§æˆE.Âù ÂÑô¼5äñAgBb‡"UHƒÉ#DãÌĹ9â$HäT¬:ÞáÌTjX*Š*^g¯A/4†¾‰‰GÁŠóšoù•XJŠf¿ÁÚ§ó¿èO׃K]”X3Ø|9Gà –YR Cû¹gþ]zÃÚþŸ×íþïÿ]s 9wŸg×·×ýµÖ`ÀìB(/ýœåB¢|ŠŠˆ€ Ô@wìõêòëòÓ!JŽPt #:û˜*`u@#:ú@øHõ„¨äSüø§@àbQ OžŸf×'ðûÀu?ÖøçÑ& è>hpYõA•º:ÿ·MKúêÛ»‡ú‰õË„Á˜i¨s!Ó qÄçÐb£ƒ±ÜŽŽLǃý@dàmø¹ÈÛƒ·:ô]9ý'™£")†H#X$€ÊÙ9Ãü—'`õnô‡Ÿ<^‘Ûf„®×³¿¼8>çƒÄÂL0pKGcbõÝ:Îé$ü²ËƒYï°K€í°Á(9o Sz«"þ¡®\%Ðóž’1ÏKôýÈØz °ÇGÍ¿á<šCØú#:}½¼/zÀ¹m=ƒÙŠÏÌz‡Ì×<ʪizÉ!‚kð¥TU+§oãs2 ø0åƒ`´ ¥ù¶cŸùæ@Áá1SãknËcr#DË#±@aîKÏ {O¿âZ[Z7A8#:÷ÓxƒÝœÓ„1Ahä¿yIçY+$¢¼”ú?¶Ô(Ja?V#h!€Ã_³6b`ªh0–Xá~^|§ŒùèíÖ{ÅŠ‚‡¯ØÀÈÔ70w´ð”7éG°1XÍ–Ìäìô2!È:å^§°¾ÕMnY>i oÊ#D6Í`‚ÏǶI%ãyù&9Ch<Ùo§cqª¢´†Xñ¯.{äË#:…¢á,áS:#D&4˜`ÉrL=4°Ó†h«ÒÀĪ±†Æi†Ãpr}¦Î¤á•®;±¥Š d¹“0[èÛî#¢ª¢·ÇH›WXžˆc«½åIS¨˜ýÝg1ÔI'—CœXxþ®eˆ„^=TXý_edÊ¡áÎVŠ…‘<€¥#:Å.ÊY#D踿Î|IÌ9€ö*³e ìs^ÁÁœC*…!øþ¯D~¿O…Ñü´Ø8¯ãɃ§âŤ¯*ÿ7ë$l7slÛhè•þ9ʹ¶­o~#:êd‡³ÞÑi?YDá-Rrd UP#D\1E¬la`\)XÆ›S-¤LÂÂJ`ƒøþPJdìU%°µ üw%ˆˆ¥‡õÿ»þÆ@à–Fi&¦†êy1h{TZÎ’4®èÝg¯W'µ’zý>ïìIñ'yrO°ùQ‡ÁTU .í»ÿüaòU>Áð[aؾUQUU®O/Ð.Ù˜2„!’Ç#:á“GVæÿ6°#hh$î*µÈœ~†¶÷Ÿð}gp½ÝÆÊs*¢*ÄDyßGu?ŠkÞcÀÏ| ªC6’€Ø³r~9f‡Á¶ÇÍ ÐHªY±þ½RŒhÔMÇ•#hT"øñKjØ–’Ø¢Sàc@1ôeõ&MN•@§_ôÌßøüo]CÑ9H›—‡8€Íâ!I$~"X{[~ó„”;#:fÊÏàˆtbBz°†½Bá“«À˜-b¢“ï—ân'ª‹Bnã>ÝÝ“ó Qì–JdŠ"ðk0ðg Ô°ÔDÆí3>ðo.ʇ&Cä‚*š!×­VÞðù†Œ¶Žp¸¦œO˨‹,0„„XÍêSÜ‘Fð8Ûœ[m¸ Ÿq¾ü „µÔ L/AW´~âÃÏU ô2»Ä±ç‚yx¸/ÉÂ|LJ$ÁŽ¾%5ßÂî;t%O²Óìa .aêÐ/^ÄÆ(í 9šÜŠ “3è.ä;´Ös8‡cXß4žy( Áèi…m¿#hŒ˜ÐØÝ÷¹™=¼:x7La0óTP“[µDS_`ØË–u§™aÁ\`×YÈçᵟ Ì™GŽÌøܹG[¶Ëéfg¥·5ˆª’CÅÂY]^£YËYà é5ËŒ9±ºÕã§kol˜`Þm¸Z©…’Cl謑îe Ô#D­#h ¤“äë5þÈúŒ°Å –Uª‡ƒv¿#DgS‘vfn㜡!2í¾ ÎKX5×ÕºvG˜l¼Ãpl‘vŽðÿ]ˆQJL±«¹”OzM 0'q’ö9C!³#:ìüÍ«ïz vìðåê”ÔÏ£ÅvŸ|ÿ)Ž!w]}ëúõ!ŸWtñ,¬%ÏCHK¯MÓΆfaÞÐŒ7«±ëåM«wCúøªÖŸéôë¯rBíSvDD™=¬ðª>"##:>“´ÆCõéQ4ô|78ÎnU°`Ë°ÁµP©Ý²Žón¼;¿¹J‹ê#:×T&ïQ{! ãÖüa49¼Á‰E|nR &Š‘t²“Žô!]¾5²I:@á<°ÆC¤ ÐÐHw')¥ðïžÉ:'‚=ŸWzû „Ä#Âè¸ç¿V¤£"Z÷…¦r¡æÄ‹§]!¾”ŠUIÚyxÂlÀÜé42#hò˜ÉìÊ7óš:CÊø£OQ¦«›*ðÈ›l d¦ÂÓ[k¶Åôr68*Öî±jYw1°#h“\‡>åù=ÐǵôxĹC½Ö"u=¢ä¡JÑIT•Óh˜7“3ICÉ:³T¤}D«Bꢱ†Nî’r8J®Ñï£0ÓƒxY¤ÐÞ`STSТy1<°sœÌÃs¦"UQ­Î®¦±“£i rsIVªF…¡Š=f{r“–‹©Òs£ 5Ô5îøøÖ`ê“*-·%¹™eÌyrÆÚ,Y™na™BL¹äŸ|é‹ØlÓîÙ™x•"t=5°ÀÑá˼À“{Vi)°@C©E’ò¸ÌÁÝÚµ}W®k-ÝciÌœ5!“[ÛÍòÒ»G™u©ãZuÞdDK©ÞrÖ¥e® ÒгjS&ìã·gyVävã:bQ­TQå=]ÜñB"&7uéÌÖ¸•1¸§~ë™Q¶羚Ôdðæn®Ap5ì:g³ZÓ::!TfXV¥P4gªB"åøgŸC±àX°ÖØÙYIÇ>*Pe°ýTÁ¾«”–#hg>(ÏÛÜÀ»IÄkm;ã[ «êŠ‚™'C5Îö8klÐU4²FÂãµÉ#D#D7à§^ {%ižPž SÒuPÔÂCVÁ’§^RÂêyhêš)ADjÐw¡6ÿªI!Ps9"ˆø;-ú =ž“”$qSt˜«ù{|4=¼Qùx4ÐÅ™hä-kß9$˜<Âç&#:÷•IÄÒÍpâhLœDÁ{/(bcÏdQ*´X‚¥#:fß}*g©ngExç=±2„b#¤<#hVRYïWWî¼b—ؾò¨‚Ä?Æ„Ëv$“VbG}Òï±âU9ºîá¼b!ª G(tËin¯¿`%-¶î¶-ˆwÖ°LŒßMm*úHhä&Ó#D{¨šx}O1¨èûÓÅÖ”Øwz,„B4@4rõÚ`²Øæ1æ)‘`z‡¦6›Òo‰¦0púÎÆ‚—ºH²Š_'¼@Աɓ#ƪëÔ{#´3¦Þ´U6'@Ç 0 €óYÑ,¯^;üQ¨&L “ªì²YLäjP£õ~¹3åêõÀëZêpMc8äè‡ÇcšË!I#hÖ«ó™0w³³4ÌÝ»2lÀ‘χÃáÉ£ŸYL ,Üa¨5!‡¼G@TZKÜ—T¯&Ÿ€ÞTÞJ#Dt÷]¤Èc'7ÙJÒÕ-P§+A¯c; Ùp=”ïË@¦#D©j#Ü¿ž¡Æˆ¬{ÆB(?1ŸóÉ!6}"àÓLSÓ#hÀ‚j0»†˜;ÍäçÐùYv#:áëtñô! åÁ*^€žî«x©CÎ"¢áÚü Ü9àû½àn1åÒÓ¡›RÚNS¿ï*<ŽÆo3´3ON‹£"ç [YF­9óS $´á^Ä/'C5ÎèîÓÂÉA†Äˆe›ìÆE’ #:kD…ͧAUV+rŸ aÓ»¬»Jc@BEÄO‚¡¥fš Ðƈ1I‘½:-®@|áÉ•lãÑÕ*ÉŸU¬-«¹:è>·kB#/µ§Ì9ÔÒ0„h‡¡/C\r¤N±ª6 ÷N#Cî80‡oATî5ëàÙØÎà¨Fj`†!€ƒÔðÁ„‡¡öm#hõqÈÝœVð_Xi!„:çù—¼èJ©ê<ïÈ ¯g‰ÏmO3¼€zõkEz¾42pdà²Èªs“Ðø8®¶q ðÈ0O{è>oD=Nh€‚£hçìAÅQ‡¯À׸'Z¯¾‰fÈVkáMMìÁë¼™s#DÆ#h·/ËHh­Wo¢ðhùîhó2V|ú£)6?žq2ðlU‚Ž¼|KÅÖ’HLæ‘X{b¢*‡w°¦wŸ·¥Œ&%üxµ3Jn˜Bff^ù:wÔ½#D¸*HèÕ§ÞƒO‰‡øS .X9²¬’lRìÌ›'Eô–0Í`XQüÖnȳîäÏ—cˆøãÛ§Šr¼ðóÊʼӒ" èG’ _rDŸmœ‘)0„+±·îˆ1Î ›æáÛåÝñØ}E&ÊÍB¿:Qœø)h³€^#hàÆc÷5\üT©Îå=”“q#‹]§3/wYaÅ~¡DÀÈO³K )tú19¶“µ5UèÞ“\ ( 2$ ›©Ç׶()ó5ƒ²"c0ð‡EDÙ M“([nn€]ZauŒ#DbÎ$ʇ»1ÔœkgÚí×^= ¾OäB=gÓ”òÞÐ;Kã >Ws'H@0•çú1ÆÊÀô¹ëœë†@±¡`»¡ÿ>·þob+AÖåMs'yž2å§óAÝ×Ó?óZ0gmà>Uc¬A‰êý^‡!¸H(Að0fÃÈ|×סù6™!þø‡þdŒÈªòä€LŒNL$ƒzfÛy¦Jöó[Ò{®ó¬^VhÁ¬d=ÃÔC€ Ù(x„¦ž #heûŽG©ÿBÓñöÃ"•ES™±ßËõip#h”àgÀ$<.E!-ô(#ho(ž•ö@âˆ?Ôá÷õ+¤ƒïÿ„ÂV05ƒþçý§ íöÏâX¾v[AJÉww£L¥èjôK½ºí­ÕÙ{¿GÚHU$~ETÜ:Ö ]¾yö›ÆC`Žìà é³ 2Qˆ§ûp!Z^ù|xŸßõ!ô‘NùÏñt:ƒã‚ÈŸ…ÍHsC´~¤¤!BDÄê\àvPãL™û½sö ™LF‚ßÆk†Í4b­ ÀJE3L£¼Åÿ B!‚eOrâz„¶õÿ<ƒ*#:Œõöݦ~ñ®Û Htcz›÷|™È˲4èÐR¥EUTý½tjª‹Q¶}©§á׋û®’U] ³?• eM*ÉæÕ²M’|‡áâºõ#: ï„5+Ç®)ŠŸŽ”÷ïó*¥Ó·9RÄPÍ}ZD~ûÚÿš?ÙZßñP•²•e)T” E‡ @0å“ÉïM«XŠª|3ÞÍ^ò?&Ãk•Ü¸Ÿ$Ì]ß.‡ìã`ÄŒ?ñL+¹Àzlðtw‡ &‹ç Bü r ´Èœ#ø ?ÃêTt,>§úuªØÐ9ùè+HDÌÅ›ôǃ×xrvHá)Ô:sóˆrŠÑšãÁPðOä¢#Dþ@@ûtÿÿ¬}~ÄO3ïOT˜1ê OÁM.JËC{»ŽÛpþýØj¦Ûe™‰8ìÃA¥ü°ÒÅÄjA5´rh€Ñê#DJ´òBhDÂ#:zö$O3Þóp“Ì WA?½#hþÊ#h½X8A{lËPýnˆaŒ’€ ÁBH\*©CŽžsW-7ãú¾VÛmîÇ…ši¾ PŠ,D†Ázm`ÒÌdÉÒÉ-… ’©w LLÞ&\ÎÓS ˆ³^>TW“Ånjppé¦>þ9¦cγo8ÓHÀæÉ4oxPõàÌY¤Á™™'Â16GÍÐÒ,:À«ÑÆçdÜWfª‰Êì.f°(¤^"AÆE›ÎÂ\ d“DZ:±1`CXv93ý R‡Ê]„&€#òü—Ãó|?/ÙN½wÃûqÁþ í‚yB-|<búÅÉÂ…ºæø÷óæVAº j2‘ŠX!`žã%&ç_Çèºïß÷kÉ£lst“ ›²jûSßêizõýfßò ˆ%p,ˆ{ÃÞtt*þ¿ñiÉ虇ñ3«àX/M€˜\#D'#:lð{ïÅ“å8‘B#)ö@Ž"@`H8Üm¶Ãä x”Y¨ŠägšÔ0–fl ïgd±Dæ™Óv¶ªh?—iíøEõ|á¡¡æk’ Ži¢e·Nd†¢‰ ±Ó+ϟ¡Fûvïé÷$’ôhM’J uÕ Ç_Ó/Ÿdé…V„B€…Ú>^¬øûN¯¼B€…¥Ü(¢¥ÈJT„˜ebUûþÄwÜ>çXÝPßèÄW!샭E#:DÕ ·Å&D“WPJ™¹¬IZ)5WÆÕÍjÚZCA ÅŠQt@è‘• WÚ`r‡KäŸ÷à!(´’Kçæ „Î%kC«0ŸFY«­ÜŸA4™² "z7’1[Q¥0LÜi*b€:oDià3& ã‡o4dÓ4„ìž5õfµ‡‘ª>`L!ãÂiIDKE,kq¡f—Yf”Ê`1²i’õM3Ð †©˜‚L런ÎD)#h!"¼è˜ ³¨žÞMDÑÄ#DP-è„ø–¶E¥ $t¬oà÷6FÆ÷v®\­Êæ™EcV¹µØ•´ÓZå…#:$Hä}r¨d4#¨JPÆRÀh!JQ¢‘öwa`+ß(C©‡?Ùˆ|ƒ¬@a<¥öÑ×sþˆ‰&¢¤ªˆjSÏHsÆ(8AèƒÑHQûà{ú£Àõ›³ßEÂ$Û@5aU¹“SñÚNZtQÜ$ ñ"Šª&)½=³èH{Ìã \úÛˆýø‘ú=_RÎ~ñ¿NF¦ #h]ì¼k·MÓj#:uëñLBŒ ÐälƒCË ¶~ÛÝ¢†ŸTÝ#D†fz¡ŽƒÌW‹oÎÚãä¤`ÚopÞ‚ ÇQ@1?CÔ•îv¸b;+cÉg”~¯EèN|kÓ¯´ØÖÃWü=ç™%°øòêf'*Î# ~#:þöìTd#D4£ž¢·ÄÚÏwJ•oñT®¤€]Úd°˜ îÀL"Ž¡Q¡Zi#g''ά€É«ï/Ï×ò÷9A3H—p€¦ ü¶¡ê9%\Y9 ¡ò‰>³ù}X]s±k¾±T½ƒ}ÓEF¹ mS›?Xå6°ák#h„>'_¢Aé˜y¨rͬ3#¤°8>5‡XËy3Û¢IÁ þƒËŽå‡ο7å¸0K‹F vhÐÔaï¹¼²aŠ â(|º.ʦ íZw:h“.u‚¯_3|^Lqck 4p)ÑæbB˜U#:ý¯_y¤\¤ÒöO)WZÄý ½¿€°#*öЀ¾Ýé;»7ÉÙïúG-ô…·Ña³ð‹`ü>špO㊈B®‚ Ä”“I¸Ãoäún‰)ú>QÖýÐû¡íCæe *ŠCñýdÇ·ä#DLUndlŒ>Ô$>P/ä“5‚ø#DG×’n7`NdèmWF:0kI·Ù½UÌ!=#J¨k#>Àã´üÏùŸú·YQ|qûÓõîUß©0¨Ò¡C0¿poþoÕk· ÞGœ’8Ÿ+îvIÀü+¾dÃÊ^)\5l›ÒÞ|#oønþ©ÑÁÔ°Ë;\ÌœDís#DÓ»“‹ˆ€Î1*$Sû`Óçf&wÝ·î#DìHL J#æ?œ?‰ØŠæa#ºu˧6îÕ'¯ÌΈW×èÉ4ýde'–³òùÂËŠ)®ñöI'8~\ŸÆÉÝ€>Nï ª;€ú‹9¸†Ä¶÷¢/Ó!@!€@y§ï€¡V’ˆ!h‚€šì‰.¼#:Ä #hB¦Áàd#:êQø|Kå’p±(Ðä ÿ{U(#D#DF‰ ÆœcQ"c˜E1©’#DÈéP¨ˆ„¤» ¨ôÓ¡&‚ ©øS{&nQKçE×È33õÙÛ B#DáŽ"jÚ@=m¸ˆdn:໧“ÐB3ZJ8€F(ã$Œí,J5i’Y–JÔãØLÖí—u º"ƒ€ÿ8j¯,ª›Àle Ú3z‹ª8]* ÷IyoÓ~r‡ÇãúßÌ:×ýx±¦ ÄVó*Ër"@Ë0k:ÓI±ÁŽ&Ç% ˆ1$#DxÌ~ü´Äh,ºª~øÃä¦<Î⛠Ȫ5&,;tN©û{ #D0PH©BkIb",kbÕ)ª,jŠÉ£QiM!12ÚU&Ú‹f@ŒÙ¢Ú6I5*f•ŠÍ&̦Õhšm“i%¢Ù+FÒ‘VË%¥E6ÌÊÙ‘T%#hA)¡ih(Y‚0 (C¯Ô~®óø|¯Ã]I3­][Ù·-áþ{†ãÙ•ÑãÄ#D¾‚'Ú¾ÙRª%Y” #:†•…F‘((¨¥~ Ô>Á?d®\#:n4㉭.Â÷Ÿ|›p˯Ç(˜dHß2Ó/½Á¾2O³ydþÞm¶É§-òwÃáž3EÃpY½-¶1°#[`‘¯ßuÉ<)¼2Y—™Ãxa Mu€0Ib"Š 'Ìxã}8(S¢þZPÖ@¡€óc=LqkHëˆÂpáàÕÇIÀ~ÅDÿ R>ˆ <G©7û#hD&C…@„pç~W€4p®#HØôÑàa›ÇX?6¨õ¯·­×#hñæäš¼8S—Î8€›ÐäÑlE&8ÊÉðŒ‡ztéÁTmîCò´F9Ç6gGrsm&sÛe×aŒ· [ipì‰ppLx)¯o> ã5˜WîÅë¼ pdË’2o#:Úï-38ÅŦÎLÀÆÚÍn˜Å·G¥×1b3ƃ;Ó™™ ” TÈ*ù#hö€}Kãáëˆô{mÐá›\%|»ÓÂóêȉÁ‡³·šgï}öFÄöB @Ñ@ÌÔ›M ªüi«–6(“TkW6æ×-\¦j¨J-™‚ÔT•Q¬ÇuÓ%#DÝÁþš#·ŠIügP-ÑCÛç@RÞdÄÔÅJPj–4Û&"š£T˜©EˆQ9P¨P¹Ô®úöµßê?þ"óÞ0û¿‘Ä!ÁF ×‰@ª]ïë@ÿ ÒJ)ÉŠ}‚>È)KÓû`ÖœÉ#h¨pÔ/=ãþÿÞtéø[É2QúHh"ˆˆP‰šŠÀé‹šrc1ÔkÙˆ} óÌVÀÀá‚ú>Íþ>ÕóÑ9”úØ%S×ó…ÜÕôU#7—M~—’ÐÛ4Ã5ô¯„*ëÓ2ÊeTÔ,±Ö‘ŽgX;ä?ûáSÚË\À86ÌËÙ5PSl^0#h1Úè°Vw3/Ë®…Å€a.‰Dñí­"xx©˜”I#:dç"Ÿ‰çLþVÓª†I`Ëã#?Óãw4tÏ÷ U_‰*½ }~~üþÐ|/#¸µ¢7gÃN‰üÞr:ŒŸå"/éoóCÜœ\¾˜šœ—/“$À’Wæ¨x‡³Ê½©\Ïï Ïjù*¿ÁD#:ìÄ“³Éˆ1Rã ç(SꪟxÞ­ÿ¤÷“Š<¸'Ñe±T> ˆA%ãá%—Üÿh‡ ýÝÀECª4«A ïìݯN5Æ·Lº2üµñŸ¢iÎÜüÍàúÒŒûåFֳΩåPg€¦cT4—õÿ!®4»´q7ÄàtIc8JšæÁÁQ#h|Òá")ý1„A)´ñLö=L'Ûò_³úo{è|ÿØÌ#Ñ>ÕÙ÷£¼ <á=^Ãëë=òs(©F¡_ô Ö°^¨!ôèÖߪ]ÊÐû“iáÐñéÍ#þ0H×Mu~yø°Š{…ÀOÝy¡óš@¡p.1^_X}?ª¯x}=ŒÅ8X/Uü1{.e?×¥€~š ê1M!ŠÅáì†û×qÊp)|ýêûfÂœ„‘61£ª'qa£òà‘Å3ó>èà`ÇÔüÝ_»Pîv9ßé,y&È…†L8¾ŽwY¥Ûì¯Lf#<¦ðPhk‰Ù;Š½N¹otŸìbt; }§oÈ*ŸîJ¨xüÞ%ù{ oKzr¡ ×iOÍ®8ÁÚLD4K%^ÔƒDŸ«w¼A5(D¦TP…¤-¸gFåýɳ çEGdÓ:c9EC#hµõ#D ‘Úª›|æ£a½—t½®¥<ñóbAY»FÈȘr20O©G†rîŽkç²¼{ò^2Ø Z Qʾ~*2:ü¿@åLÁ4åz€–P¢ÁŸ^q ’t#hR;#DÛÖˆÄëDEC^D»-˜|Xηsï#:ûÁ{ô´ðP¹-Ô”~Ž¤rˆTÜ®I0½Ý ™«öZò¬mQnlJwFv…)e {H§©BM*à:ü˜a©·üç?ž,e]¿MêäÁ¹Õo¬®–ÿï•ÌjàEÌñçK–_¯s« ÷<ð1د‡’9ÜûÒG³"Eå¾–F9ZöqŸÁylÙÝCñƒ"u—ûÇ#eì#»1²cºçž]0ÜÆ UDÍÊio¶¼®ž-S-#íc)§ÜÑöñl=y$’œöOcü¦>½œ9éß<6ƒÊ¨]‚¡†¤0výx>¶d°‘yÀly9jû²hiì°fÛÚèͦ±²aÔÈ·\W‡øsòÞ›tœySpî\Æ¥\)’ª DÈiJ°×#D§H¼F›5RÏ–°øº!ÎsW#jÆrf¼¬°Ñªë¡&Î'žuYåȻ¥ÜL Š­¿›ã…ÝØORb.pȈ H)¨³Å¯Ša†O•–ùú±}ïk«9.ãŒìÃ5™ï¶%#D‰1©óžØrœÞ9˜ß®5Ó µ‡<ºØ€ÒnOµ›7&ÈÌX·œ²#žýyTï<ð|ë!Ì›,¹ÌtöÑÉí-“{íž‹éro¤4Ú¸v€Î£,Ž¥‡½kã{·íšeðÅdTo2òA½˜¹ÙÚ£îëîf‹5vØ Z¡Îc¾Éq³ÌüùTmˆ» ‚A$¦¸îRçø@tðі¶ÍøüB:p†Ñ­ rƳ|Œœ,r2˜s1Ä/c{Ÿ2u#ÐÝÌñ3ÿ7áÊeÈ2/¹‘ó†ûܵG™)¼°}´ÎÇ, 7 :ï¿ÜÜùzãáu¬€RVvÒç»ù#h!v<ö",nûw²(~?•È?„cÉõŒI+Gº0$ØAêêë´d§gP—ž†8-ñÏ.C‘¿ Í{„ÛÅ»pݲF¥ª¡ƒ¹\Š¿hQÅû#Dò°­¨CŽø±ñuhØjšÂ õ‡ÏúvšÎÁ"fCijJŠffvãÍ›½RAé^½ñȖɯéçÏü<êIñÑÇr2=¼ûÀ׺1Cˆy‘ë ¦¦ÿ‰v¾S'·#hJªc—c}Žß/&ðÌÛŒe£‰>à¨7%‰:Y¨–@àܦþj.Æf’»wV’hqÅ@îv®tŒbtc‘Ñ=žc¥ìv8"nQÓ‘p¹ÖkÇ>ŇEó•í(0oÃÍbv¸)UR‹ ‡/_}7‡·x_~Ú&‚µ·;&ÙðY‚!u&W]G/ÐÝÌCýJ —)²`ªQ嫹ó|÷¼î[Óèãb»zÂC0ùÃ7ìõQ+$Rv±O zï™T{¾#DP9]s:C¥3»65EÉÌ‚¬;OB<À|¬K¹$CÎ5¥ÜRU#hRB¥)uƒ]þ½"Â÷¢1Xÿ‹Ÿ- =Z{ ù<¹Ñ#DeËŸ$dÈ}’ŽöjPÃ!}¾jZÌÌÊ© ¦AE'|ùrf,QW¬úºÁõn–™Ôæ7ù¸=‰Mëíè[H¶ÒÆrÈ5ó$j<Š‘"•/Á­_Ñšb¹±DÍ7ç†D9†„;Æó¤‘Nûå¿DÈ’åšsÍ~ŠfªaŽóËÀš³Ãû¹üž¯AY¤‚)#h#hiçá³ÁH_¨(,\öâ{÷:Àðé¡aÒ÷:0ï!ŸUÀ íAlôd)§¿!8ãn4«&DæTKAÂÀ ÁÝÊëÝ'º»Î›3iìÝ­æ®Én]‰ WÛ+•yÈùõ]ÀfJ;pŒ`ÊÞTFôXìM¨j±µ.Õs²43d¶º\ÙGuu6k…ÄØ$4ć¤u¥d"…’ÅJµÕÆ+åbÛãsy¥Ë£tKl›2ÛR‹ Ä¢"Â¥÷/–.€Ò\%Æ$*ÞɇA =ý mnäÚr¼È*“*!#hHÐC§Q.±QÔèñlÜUa˜Žf8L˜œÆí|êï·è-£b™EEb¦jM‹55ŠfDÚ¢E;#hÊä HÂ(íðáŽÞÍ÷í¡IHbÑ#D¦Qn§°³ƒó±u_‡åú…´5›v©ò³ÛãÌòCÉû}ƒ+5^>”Ù#DM(Æ€ž1C"4æbÔXê¡¥"U¶0yŠ°ËJ¬p­ªäÓÍhºJQ¨ó#òqó˽½Ùó×<óÍ>n]h!hÜCU¼ vh:¨3#DæIxµ SŠˆpuÉÖHCÛ#h&Ì„ìþóA?EÀ>™í›§ã›òB”ï—Åð€¦U˜#:x<á!± מ#j2´‘¼y.cк3O:ÉÙFÖR¦ë‹e1Õ•’¤¡RÚQTEЫ‰‚ج]*˜±TQÑà²V¾_uáUGD9t£¯.¬¢u€~ÌŠ À‚ZŽ7&ûõž‘Pé†äAŒ­#:àtìüÎýlØã@8é}ì0|õÕÜ=°•C¯®v>¼{8ôã°|¼á>ð'î‡âvAt"üP¿NÝöå#:ðø÷OY¹R–ùwëÕÔµè[•Ž]tâ+‘J]+Òó/GMÜ H8I‰¬Œ3F9"f.S%¤ÒŽ$(Ê -* *àT…FùYQùM5Ë…·œ¶í_i#FÐÌKïYÂG¸¹PH4P¥ø/wg Ù²ð—æÖ å òtäø#D1aÛ@ƒDê­ x<Úîíš½Ú½íÂh©ƒdÜj L,A¼¤eM ­`ËÁÀ°e›ÒR‘ól6ÑFDÅŒd#DX[)P¨fþD˜qÎc9ã7®RÐ÷#©sŒ××wŒ[­«;²&q‡Ü¨šaÐ9;ig´Y>Q³YðìŠT»¡››„2°i‡íg‚dŽÛaJþÝn¶é×N¸·½·sñ;èR©J¤¥‰#:¦†#h&!÷q£È5âï¥Pp>O)#‡ã©-˜ekDëp¨¨ŠUË|»R%ù¢]IÈ6JrÛ.Šˆ0¤¨ÑI,t‘ÉYnfm& qšÑáÑî×»Þ«g$'hÑufé„ ÒðÿOõmëÐ(‹¤WlPù}ÿYåèO\ü9“y,!1Yìg›ð-õˆþ Ô¿HÏA÷Ž”}àq§¿Â©ŒPИ@v¢õ–†é°€"M“ç®…¼ëçE1ÒÝ°Â! à<&gN+ÊÊúgza"&á ÔçT]´þê’åì ¡ñ?‰ÏŠ2XìÖ$€#:œŒ8 ,^ ññÊ’"hú`Â?iìê#¨2Ag‚¡`k«;ƒo§6”¼Š„;Ý£Ž·a³¶jÈ„R#hÁM*™Íà±CVˆÙÐŽŒÐõù¸.Hf¥%Ò$¡}2Š1Pc`»óÉ>¹NL#D0çÝVÄoˆMmMŠ‰³ýD2bÛíA Úª°;à÷âNfú\‡ªDØrK!?.\Äy5#hãÎÈ„¤&’½¯OC²‰Tp+4ÆP!³E«Ž’'´hã\)ÂtË€}8i‘‚-ˆ:›ˆGóþœD61ØÉŽðÄÀNCa„$ül` ›‚n mЩŒCÀY>Fñ${UhC²5Œ%tGPÁ‡pàÄŒ˜$Ÿ^+NTe;™9Dsä_·qwhˆÈØi v^eB†iaÿütã2<¶• œ0,{Œ÷¹4‰B3(,Ðú~=Ù~Þ‰K`9…öÈžY«Ò¨Ù^]–ÀÚ’“óVù E'¨Ö°ëw!j:ª+T):dOYò½]¾.ìºßäù~ê Öø%{fœaZk‡ÍC,‘¸ë$—sFÂ4ÇLi¬ˆÐPÀ³BÆasɆ¸1%\a¤”î«ÊlÅXü.®•#DBÑ­lÁ1Âs´¼ÝÝì  9Q:N=ÞZ œÙâ½È5…CV‘ð𯩙Ô&XÕ…¢—#:ÔÞ>°“*4ùpÝqÓ¤ S£åØè<Ê®JD±uÛ‹¼Ç] Ø¿À°=ðÒ4pEŠ}j!‹ÓG'|AÃÙÜ„¿§cÃøÜÁÙ £˜AÍ HzHþ,¨hî3®"ë¦0:”ãÆ\óÃgÞü “óÊ›7:WQväwNL=‰?ï!žT@ýã,+¡÷‡'¬*íh­#h/Îú"ºýF‡$;=;Ð?ùô°ž#:,ý)+¹ê=­nʽÔëæ~LM4ZØè2–i5©¤z<Ãéä|GÞõøn@Ô…ù1¹¡ûÇ°„Û r;Üïóõ²úýº(&ÀДšÑ’\‡àC¦"~në,6êßḟ×#Ž#h$¨¦Tcð©}!< ü#R…ÿ,Cp7z|ÒÂPÂHYñõ‚'¼dIª!ÔãÃô|ÎgéñÏI©6™ñà«Æê¢M[0|é¬?#D0YÓÇ›Ç]a#‡ð.¥%4Î@ü†1æÞʆ7hÑ™h£`h`Î#h/À[ Ó’²ÖöŠqòzonˆQé¬r‡3,«‰Ì¾ÓN{#DëC©JcXç& Pï1)$"¼®DXµÈ"æÔk~¦×eˆ‚"Z#:™XÖ;Óq‰šÌÌbF—øf'àÎFESJÓ;ÄlL#DÁ³0ÔÛ $ˆ-œèxNxb ÂÄ@£€5†$E ëÕ\ºAÜ¡(ù|tw™~.TQPkíÛv’H¬ICSW\ÖÒ»t¯›M«@µVUÄ4méf5eCRmkd„0€c*rÞ#hnB‘3X€P¹0 !uPA$L¸9^ï)Å÷ê] hÉ5ã”]=v“«†œ0´‰#hsäi(í#:eÌG#:‰ìÊ–ÆM#:ÍLv%—Ì‘²Ó€¡˜#½¢ÝJÎÝyU#Dfr”!BJ™äÀ~C˽8vp=T_o×ÙÜQ}¼ ¤†“¾Gy®2=Û¸{¾Í˜ú–q´2ÃÿNÆ›ñ±Î‚œC$+iyC½†LÅßÁ±•·¹uf‘ð02ÖèTNÂÒņ—û®×#:€vú~ŸˆŠú|¢3‰Pø#h…#DiB2ÄMhÖÄt´ýÚjè™ ÑW¥ÚÙË{Ÿ+•bj`P¬J™¸q¨Òƒ Ë# ÚáÊà<)Za„ü^°{f²eëÆX-u‹6ÚìÛ'÷ðxjnÉ°AbY´Pîª&½à·çßi4P$Ì›íiEX¬!m Ù2Ù„¡2ŠÈÔy"¬i¦ÞÑ#:Šš…oééUhÆ£+Ë•ðë¶÷]VôQšåomVjÌ(’±OÜLI‡¹ÃB†VæFd‘¤ÇŒ2³–®KJbϛ؋ÏI¼ÅjÊM_b$ä}ì̈DŒÕ&êÿ&Äc£‘ÅHÔ#æITvó²) dq£¼êR#D#D­É•š&cU•¹48ÌMøÔZ{a¶£ÄWZuk&šsPf”ƒVæ´jƒR>¯¥y}û¯5¸Í@®¶ítÆëŒ ªã !¡d£¸‘rPË?äª+6ä’8G²å·«£ÄóˆŠÆ×—y°z4t3WHÝaróNhÈDÓüncP2A%;³–\ÞèÀchÁÈ)àÈd ¸i7¡¡¡æÏÃRV5ô\Œ¶2 üþjž¹8‰žX¦¡¹ÇºSW‡³RÍ)!”Š!šC’Û ]Êd`ïaÃlŽ5#"űŸD´Re…+’ÄR(dCTy¢˜f©£Z ]òY%ÁT‰ªØÕëk®j9’jÝ™¹*;⥛T¡¨)õ%Ž­(ì‹#D4Ä5ef²Š¨çT£)#hÂTÛ 6hi­*Rhêé½MnEXI#h¢¶bósdŒÄTÔ›®îÎRW¤ä#DSDXë4’EÒŒc¢[#:Ô•ƒjK¥(×Ù«žîæã(úX/‹çÏ‚Q¦5Èq˜¼@qªÃ#h„ÈJi0žs$æp:fDt#%æhßN,)ˆÜ@7¯u Ã!™R=4Pms"Þô"±#D¡”zliÏ\¨˜dˆõÓù/Mô(¯<1zVtÐÝSª¯Wk鼯»F0úí*]&Š›BÙéÖ ¿:†ç.÷0è&u­¬HmŒÂàIùý,ó¸†ç$põ@îj y®T»…CÃæ/2xÏœ°±µ˜äÈ©u/+ªÀJ~µJ”ü˜Jb@¢efDïB…ï ñ NŠ: @ ³3 \2Ȩ`ÅXÁ’Ž™™Ž*qƒ ì^rù\ªp#ŒŒ³%X†È…D'2éP¨š—“…)“«wP–#:D6Adà@5I¸Y̽ šŒáЭ¬2³)”µhØh4¹êZ(ƒ´nJTÔXS™êH-#D.^==áâŠo¾95Á¶—,yºF‘× Ç£†JÒppCR&ìDÑ1Á ¶ …0-€šF–ÒnÀâéfD`1‚…î–^£¥É ¶C2¼Ä#iü>’u.Æu|sΪêLqÅ–‡ãy”‹”ƒ/‡Bg­))SÔ}»Olj¶Î/l9y«$FûÔŽ!–#h^²„õów }÷›²pfˆB¶çŒ¤#hŒ:.×tšé ë†Úà ,¨XÈ]︽{¸‘xM`S#D#|±•¦Äas8‹ pj+ ™yÎ?O4­éÔ—qžµãÁ ÆÅßYYƒÎ^ömdï-Hå­›C ¤>³¥„ÖÛDMã{ö:7#:6 Õ[¥²Å(µ$6K1ò]k†bÍÑ âú×BÈC ~/«NR€#D>co‚ëî*ÐÈQ'$“„!Ž#hÊ’ü'Hý¹’LìEÙu …Œì5ÜyàØF2ÀþÒzî[î Ðù¸Íÿg‡ÚÍÉ gˆ}!“׭D²&»©Æ,„ü0j{ª L)QZ7ìo+ªhë‡O1ìÍcœ­ð%L}Ù‡AAnR¡ñ¬c#hÝü×< #„NÖH @šs¤û·ÌË Á5‰¤;<œm‰àc£¥âž33T~?·¹òßÖuâû;CçìÃ#DŒF’a@"° I9ýþe}ji]ðžþ#h˜#DÇÂp\!XF…Ú°¶†Ä ñM¦caàð¡1ŸZPz ‚ „ta¥„„@—$o¦ª%‘†$ƒXi ¡¨‰R äú± fVtJšÂHD4 o5ÞSa£•Àb€Âh4NÁ5©ÐBI"LCÕ<HF’@´’Ð0ò^:h[í:œd+²9Ö¸b?ˆM-®8Ô'=LI¨A9>¨—¦_ž#hñÞ}Î’‚¨NXg8ÃI˜Xƽ›¯Àú'×)E#:™3LÛ(z@ò”¼µŸÓÑC®²‡¯¶V£#Î!°‰wý[7…”[f¼¿vþÑ1B‚‡âžÍbˆ­ÑKñ¢KÅJfh,OŠrpú Y¨3-{ðÙÁî_ßAš EoÝm´›Q« løX¶à•÷„®¢€ µ PG‡ÑˆiB©"½ãÌ?ª˜#:Ë -NnÅ(·~Úö«ÊÕÍl[mur®’¢øŠí/‰Šky½êí&L«-]s®‘·$Ô]¨†ÜÝÝŽ;%uë»S×hÑF”Xm£B9)Hä‰CBˆî#:È#DÀ®o#:2•)¥tÃlY®»W*’Ût²Vë‰p% C%@÷’gõþ³GÑõ|YÆ(Éòº”G¸`#Oåó'*b¼ða5thk—Sf'Y<¤4«É…†)0ù`|<{~¯±»Ò7v¨{r×m±)¼•AíÓWY„Š3cU†²W©§0crKÔãŸ|ÆQijîšA=B1‚ÒIüž×e™2S,´L“ª_†2D’RDI“1Q«I‰2«+h²FCJ”)iMMA@4ÏiÉ;vÓ¢b©‰X²M‰M•³XÆ,ÌIHšfÒFLRDQE JlM/Ϻˆ¤²(Pi˜RMüN–f# Å©­0™š5JŠ(‘L¥0Š3IF!5¦)¬T#:Ê%&Je6û®¥I˜‚3A¦m&˜ÒR64‰/Ïò~sï×·÷àw=ô§hÔ#õ%üÒ¾BÀ›„3ì#D6:’òkш×ØzmP]_íÈlΗNÙÏ;{CÚ䈃üÑKW8 êï`ïÖ1ðÖ£P¬Ïp¶L«ZXÀ0·]’ÑñÑ¥å&“¶f]ĽÄú¶ö.]ì'CEX®¦m#Dþ™4…^œžç¹>ï—¬Ì30©É—õ9ø4;Á°Üp0ô»”vð»X¾_0‘Žà ©—ïJ¨Š%Ž½#:è ç„掕ñ„¡dhÅ‹»u6‡[îäæ³?c²r;h½o±ÖÓ3P{8¬`ŽöLdkøª\˜”ÄâCç^Ý®ˆØ(XÓöÄÿ9÷¹#’ÒƆåµ×Yî´¨Â5]¢‘«V`±°jÆ4ÆÓc¥•·†#D+.(ò¬²7´ hǬ5Œ`Û3 •#DIqÔ=‡çEz=ÌÈS`[íÝCR‹5Ôùr#D,IK1-#DE#D×FnœQÝÚsuN×(Ô£I#3fÄm2ÊXT¥%²‘ÃÓÒòóùi=Ü`b'˜1Ôú1£6ƒ4ø` >ºÞ%ˆDþ§Ðdþtþ6"aªgJ—ν–vøsÐÎãRƶÖCUýµ¸Š0]C¼å?…ðß´´õƒæ¨‡É<^ÿ ýÂÈÉþ¾&oñ#ñûtŽ°{#:}¹ú¼ç‡÷ž÷ªÊ1z#_–/nÞc‰FœÁ=±‘ !ÆdÅOpf`-P§Û‰ƒYNÖIº@ö¼šPŒ] ‡)ùá¸ø9ÂþwßÙ»þN k´xÛhh'ɘëam¥¶ˆ&¥¤cù1Ô]ìÑÛš1£|Aƒç âWph4¤&ˆà)5’M™ÁÒ»¡ÕG?×S²nêú{?–Ýê¸ÑÔ5éçãߌb{¶G*ûÿ¼ù¶™…Ø­f·á/f†lÒÜÇ%Z#håÍÓ&©ORûÞv§-á$Hµ¨!.šaÎîÿ8¥\”Ôç£h¥¯yñ]βʶѨ!×hx¸W'lù0Ò‚zÁ²â-i| µtIm³l…iÃù˜PCÉœ’e”2ºÔófÝ߸Ѯ˜+O<÷àäÉ}ßË8áNæx9Ú`ž|±Ž±[¦6í-½ç2q­¨Á¶¢3çѱ¯Ý[ñ‘놷ÑZ<·¾å,t@¶÷+Û¢Ù´ÏW4ÔAr åj”2›ó˜ÍäAaçä¶ÎZ· _ƒÕÆb„“q—Ï3ßåÈêÀ ªZö Cíf¬†Dâ>ü¿Ü‰šˆ<69Që€Ê2Ãk‘Ä,!I˜q0Píµl, Gêür}Ïs²±‡‡ËÅôÓí.SÊN`2#:ûdNpï5.ØuuŠVÃÜ#D'<áêbˆýÓ€êCý/Ç(HFŸZ‡ÕðÅ3ã¾üõwâ>O_.Ÿ¯™ððfcÓÇáÚ¡éR!(ǬõÇçÇêÞMÈ?S÷iïCÎoè˜.WéözOaíA N gÕ*RŸ¢u{>ïážíïï3áE|S «§çÑYx¾2Lk™ϧ~|˜Øô$šãGð1ºíaXòè0†õ°v¿ðˆfþÞTRoÀ#:²q&È#:> +â·\fZ³÷1aDiè^âWZÈÆûÙ<ÇQnÑ{”v¼ d`H0`VÞ¹)Ro†Ex¼:÷¼Þ–UXIšcÍ«NÖj`bÞînh0Çã++Ý€U“míDÇ¡¶[ÎäcKSˆ€Ø#hJE5Ú¤¶c¦ŽGõ§S¼;üJ ðÏ´î<ùéÎ7pŸ¿ÅО(UÌÁX¤’0ƒZǘ‡¯¥Ë×Äâˆld“ > Ä;‚bh0Fs®„–q½r¾«.sÈzO÷Û/Ÿ8¦À€ˆÍñæ#hQ@ ²È'ÛZÿœžºûzõ4ûu‡>€`>ϳã>Mµ’ÅøÛXëÿqã¸Z¾ßÓ>æsQ¶¥âãû/Û>ñ×!¼‹£lèàã{ˆrª²mœc>"Š–zg$ŽS©qj Ü‹¥4ïÏ°ºü`Þ·ÅbwÜŒòc1‚\̽•C†¬Ói>¦1Cãb¨0[¸VÆEÖØÇZkîàæ÷o0ŸN“©iRŸ7+y«s´ßmq9˜q+r˜Íäo¡sY18ÖŒÚ$†¥¥©ë›ñDÄ›m6gaA‰ËêeÈlš#oå§M3Õ•Fè¥ioRÓ³ÆqWÀûàÍå¨xÓ#hu±8E>ÏdÞ7vtÙþ{щêÜž_w¸ås´kJ5:t¥:y3lú¢.T»GNô©F3xÚ#hÃb»½ëâŽlÖ#húˆnsK­•Y[C„!¡#h&0¢b/{æ³ÜÓð¥kË–n¥AÔ¦Þˆls»Ýy_j¼¿ó*3»ÎfM#h*]×TÖ'¡êò¥ÖñV¤H…ÛŠÍ™™˜EÂy Á]Š×Pü;¡ß;¥„j4q3o‡½±QŒq›ï˜·tµ«Ið6Hj«»–~ëZÞñ†½Í´¤Aåžj2õ*u‹àÁÝvh’é:Š®Ôì#D!ÂMT#e/Âd£©ÙÃ’S‡]ç®7ßK²š§an,nö \:Nô÷/h}§+$Þv+iŠ)#;ÌÃ-T¸áÚ]-ŒJÖÓœqi97ž+NýÓ¢0Ÿ‰u­¡ $­4#h\gu+aδ¡Ê¦¨^6ÓV*CmX,8zQw5ÛCY)Sºù0Ù8Q&‰Ã/§sÐàˆá#hœÍ¹wÍ'ĸôű‚#AÃ&4Á´æu0PñtöÖŒ²såAj0Úi OŒËÜe²Ú~TãØtíÊk#Ds DaE-G¬¶o¾Ñ¡¹Û8¾v1‰ì)ióÆÜ’Õ¾$Ä09°Î P@ÛÁnû*“³¦µRؘ§ulâ!ÌDPfîÇmŸ{~)üÔ°Ô<¡¸#&e¼á4-95œ> ˆ‚êàˆ0# ‰Ÿ D ‘”wÞî‰*cüSÃY HâèÊÐðbX%pî]ÞØE{åpæÆ4ÙX¹ØP ¢gΡª‘FSaòÄÙ{¬V=O'ã„#;΂c+^©§¬[øvÛ#0²éºjÍI›»âdo#D±<º$¢ï0­³;#h£±Û]³\GXÆ!å“6¶zc.V'©f´mxŽ³òî„C»j1 5œ'XG~.kgˆqNTªqñ±‚æçpýmI4w†ÎLV”¾{‚áöÕtŒ¬6§0qlüG-‘6Ý[bðûào³Å!Wì6‘J¸G‡cVéWÐ8Øš"1Þ)Ž²FÓ¥%u4Âß‘N;îòV&:Ð÷Å 3“SœDº$waZTк[‡‚¡Å{ÃyXû긓‚B¦¯LKw¾~—g4už/Y@ôjàžl¦iѨtd#DY “L¦¦ CØ—d#D„•»b¼¹ˆÙÇJŽŸF0F3{Q¿ƒ®6ÂèÙEÌU]c}°6/F´‡Tù\¼¾SÑ®ÎwRŽ…×íõÏ|ßW§™.[‘2@éÓxã»—9çŽ9:†ÞÍ©ÖÎÝÝÍ#ˆi4Ò#iÚz5¤ca­F5·gÙ°?È8xÑz°3A f¨I©9@8RbW;;÷˜ág§˜@éq¸ë}ž0ã‹Æb¼ž˜IO#:Þ“Ù5!ƒª8 ìqf4ìz8º@08@w“å ß#:nS˜ &…„ˆ©4ŒsˆbÝ*Xv0è2l·ÃØÖ\+q8Í?'”Ýuß·ÓÁÒyS‡”ƒ4ñ*q ÝK58YTŸgxÖÔÙ¶­‰(Œ#hË*P®ˆ‚ž^Wƒ4eÖÚž8¬V²nê¶6ÙŠ†«6ý‡Ì’ˆq%byyyZbœºNÍhµÝ£H}Ìuª‚{¾púÌãTØÚÌd‡ -ܺÙ<_ÊR‹ìTìéÙJTÙáDœN0Ý0—Ô3ÆtS:0Ò†Î$׉[O«#Nå½›¶V¦}Y“EÉ%Fsˆ¼©-éÓJ¾½x”ì‰ìàJ;°­ Ϋg³w.ª8w¯*ÈÓ‡L«4ùE}|°Ï' 3ˆ°ìd5“DÎ2‡ê!#Dm•Ò¢äjd¡)ØâFj"Ê—iÆ“Á.ÍtÔÑ“8˜¹6ÓaË*LÖ#h®¨2­¤¸¶ Ö`ç‡#hµW$Dn”ìV“®ñ½ç¸Ò²Í‰ìn¦0Û˜-·â‘ß±Æ9-¯Ú& ̺qÇ®Ôâw»=|ËÚïHÇ^UÑWÚÈá«0;¢ó„_ctØ$@„#±cñ\ÚÀøÚ–sFY9D&ÍàQjj‹Ä`›Þ·Ö•í’¼VÜfg$aÉ8™Ð×ð¢]9«߂‰9s|W§g¸ÐSiÇG[\CcuFBŠ—2ÀKØÁn WTYF*Í I“åÞ»Ç2ø\FÕPy‰ÂìÙŒ-8ì’gŒùæ›Ó›ç“¾ÜKpúMØUJFGh•fK¨ÃZH#¬u§ÏÌ7 ê÷0ou™Ï×Q#h‡mÎpe·*bV8…#DzôKa]Û罹E¡0ãé+…|ª ’-êß#Dµ’¦‚pô©-åN½ÍžÚŠ–!0šuR #Dš4,HR=DàÁ´#ºä¶ˆY:Ì3›iO&ɉ18àÝ×|ofÖ„Éécn0o¦i :».ÆÌMÅŽ´u¬ƒ0a˜®GßL…CÜ6ôÇšh¤–Mžåü¯*œÅeÅLÎú¬Ôážõ%W u·g×´ÎÄZ‡7;S&.³†›8¼Ã6&›L!e>œ«#DWG âX÷ž1’¤ñ§åáÇbÉ¿)w •õO,º•óª4:߈mu×-Á¦ …¬¢BðÄ•8é¼{é—K<èdºJDpðV±Ñ#:L’ìí¾Ñ1¶Ó‚šÙ&}Ë7¹/am[•®f%"7L:àã´F'[áÉbÐ䦽Ù×e9ÍœÀ%#ha®.¸©l/n©HëÔfY´Ç-pÏ;Œ¡Í#Dæí +DLzY§§†ºÃF2=Àüè’sLœARä2Ú#·9À…%eáôüãˆ2¯#ꨙQ¼išô`¡i“¦„,5è{ÅÌrÑÛ‡+O§lÀ¯kSqe Äç˜RçS…Ze ¡ˆUT)dÈEê“Eq†¢ 8­¢Zó”Ÿj6j#›tRÄéŽXVÈô;m§}‹b©W#“+X“2g/–f‚ñ$é– €°yÐû¨ããz7ÔÖq!ÆæˆLLºdã%š4³:”Ç*0N’L8“¸“Ž–©Ú)ø“õQ&ÜiäË7`sš{2ë¸9¹2j(;.^Ö¸=IHØ[;hßÇ[2ptxÓÌVNYšs\BpîuÃZ˜;õ–qu]y×\×3Þ`®£)”»6s6HêC,ÚŠ¥"TBÍ”¡“9t÷4ž¦Z´ñ¬6²ùY¸;ÅfÒkìÈJ³rÙ3¶¸ÔöxÔ9š±±—PµE<ÓÌÐ'Új(„ªáå ¥î%¾&U|A©ÀYj_tg.pù'slcr*¡:ù¥§Ú…Ö´Õo_ŒfÙÒé¨È#:ÃHÈX'RLÏ4 €Ex2X9L]“¢ïäÏ=ïz½E#DiE¤/#D)C€ìÇܤ¡6–­«gB‘›IŒ76{‰­Ì™Ì"2rT4ÛlÑ£†E³6¹ Þ `·­Àœ2›¡Õ&Û•¨iA£‘Û ¥Ç$Ž¤g 2K/iA' šC§§nŒ \’I² áΉ!­0‚4~dŒ”wèo®(ˆ^äÞu¦Žuw/ß÷Y6ÓRjÉÉ7y%‰,N¢Í Î I/­B‘dž-2g<Ë–Ú æÀ¶KqÅ“$»òºAMPR²rd;¬’æpàR#DÓ©>îJ)P'‡bo¨#DO‰[ÜÁÉÌLβjx†24ÄÚˆ[ ½Ñxfx­.³BJŠ#D•I©{ÙL´0ôp‘lÀÂФ‰«¦*À½Z#hJQWJ´äÉiÓQ¶iG·z—šP5däÍG’rf2ÞÞìVšFØxck;ÃÃ]:ĉ¸}µ¡×· 9cž•AçÖû‰¤dÑ`°5éd¶Ryc¶¤"«3ȘÇm»”¦ïNÓœQ¨¶tó/ e]gE>aŠ„`0°ñMÃK™Õ…† µr¥£Š©˜ÜrSuF,Ë,qâ]ÒjHƒ.ŽVQcQQNóµ[éeb¤ëÎl™;ôÀõmÅ ÕP„ Ÿ0zJu…5qâÂ|óÅ9N9u¹[Ìsýu#hZï”é%ßr>RY#hЫÛ+#D5LpÄÅÃÖ:ã6hÜ9 ©iˆ„MÈ#D ’®‰Hp3Õ°4€È¬ á È#:dÎñ##:–’…㞈‡@”å¼À %d#:˜ Q²a2f#:`û© ]_ßãbìûíã³çÀÏ\Ý•…³êã= ØÌÜÓ¹§.(ŽŠ%ù¡ãëc#h–iùù¹š|-àìmÉ#D°'wÂl%³–©çRØ¥†(PLÜÃn¡Ý­$„p‡Îlœ9yCÉÕóYBÖ "7rª‹Îøf,®Ú9~n t|÷!·ÁÝÏñ¶ÃF+ºáH`8°˜âf.ï…ÖÕ|e0ðîØÜž?Ž-.sx“Qó‚YÃq»ÄéÑÁ—#h4&T•z¹Pm\Ý;Ì3UÊkJÙ«EëŒË"H³™8«Ñè{ˆˆ ùÆ¢ÆS™!u™Å.ZmLåðô<ÝŽâRá‚INJ1ºãÊC4´ìÇ~Û–/f‰sx–6ʙ͒­ÃyÞ˜Çù)±•¤Õ#:ÓŒ8ôªÓäó‡8܃Nœãw”œÛÆÜY{c—ÎÛµ·a6†Oc¶‡|ɇˆÍõTƦ»Èð`¾œïz<”&ŒÜÄ9åD$É¢¡2ÊÓºŒOjåPm>o$®†O¬mžóç84‰K'eÅU>(tÜ]·íçm¢5¨÷=¹8¡)¥9zºq)’æ¡pþE9)åÆØM-§"³—ñm„ìä4”`ïˆQ­³«»Íuéï…… RãQKÏ¿9),ùBHY§‡tA1²%ß²‰”ê.ß(ê^Ì',™êè¡H‡Ô{æ!y$ +ÙËTïA—™·æKNšÍÂp“ ™AŽG¨®â61tã2ß—’Ê]Èð!™R)9§ò¨â‹²ñB&¡·h2yN_%TíâúAËáÒÄñÖx>&/Ä*g˜Jkªî]Ôî2nÔQ“c˜ ñãŠÙþ¦—Òh^9inG­jÐfFfš£JÉ0:“ÌX˜“çïàô'6˜#:Š$8áe$fH|dÒhÀO¢")e¤¥˜¤‚'‘=a9{€Àìy›4'2 3SM#h‘ yézž*¹B¡É‹À!î>ä‡tÀÀ:€ ½*¿ÛÎC!™€0’Üؽ~^Ô=jpõS3DåÌ!pÄ<Ž#"Œ?pœ¹™Í!eÂåRíÓ"‡b‰ºÞ#D¥ H£¨õa£dKé}#³f8ƒ•‰Vbþëºø=RI"u¼< ;”¤½£åÉï.Éî‡×Däû†U@ÿ "‡žÿÄù¿Ýüïã‚IVH'çFä,,‰LXªnsuÓuA¨æÉ_O`¸±Dsˆ=æÿ¾ùáÃÞ'Õ4B¾âÜG«òýì~“àU4¼âß&ôù`z‰Ð"ûTh'µsþ>O7LÇàÿŒø!éòö{‰“îË#DÁŸ d>Ž“£ß%)×®Bj£çbÈRt,‡Y‚W…Áƒ#Dí 5"ý!8cÎB}:þ¿ÔžÙrÉ}¿×ôxP:½ÿ|yvòjFZ#: Y5?5Y“öI’~£^"‰HTr žgWûgÚE ü^ßÓ°ÂDý~Uûw>™ÏȤOÏþÙÛÓS%Ó\«gЕ}9GèÄCœä|Ë9z°s˜Q…"ú‹+>dl+jM:ÛÈáŒËy"6ú‰-ÕµšÛØÔèš"Mk.–\ÁfCÚ¶añ9¶‚ nmƒ RuË‘gk a©éU"9]{5Ò^ …ÜôÒó7]™CëNƃoc¬—DšSºÄë å=¸ ÒtïÅ㯠ÓzCu\Br bzãØ1ñ•;të-æâˆPez~½Îáaìºý*¦4Uˆ-Ïp§ùˆ}ÿÂÞ0y†Ï­ýXF ¼ÈHES‚Nv‡Ø}w` §î±ÔH‰)DDJÒ!ª-H[hÚĘÔX™£`ÓLͦV ØÙ]Ê%!€ƒžÖ@#ãùUáò(/Ò;?3›毫0NÙpöýX04 J>P d¨¤@‡Ô|ïÙ;ëô£òßÑä¼}€t¢ø7y‘̵U‡×øyfV»ºê#àŸ@ØCCdD€ þ”¤ƒQ¥ÀüªÑ†0óÔy±T«èÈXˆ,yT\5Æ5ZÂå\¥_c6ÌÈC—`ÌäÆ“Œx%0Øs£K DóœÇIb#D±˜å…•;!,v;© ƒ ­\ÇWu m#:Gî5¸y#…*á‚äT4ñg¼:š¼…blg’byIªˆÍbêH 2ªÊDž8Ø#DÏtíE¬äàB¤5cC>e&³%I¢hv×9eYvH¢É¿Û‘$̇êákôe‰Ãt£PnC#îžë¼ÝOÜë­•ñ~,B¾bŠyã¨ñž&`}i‚Q£Ïò\šÖÆBÑÑÖñŠ™ŠžŒ¯Ë¢Gdw††@ÄR:— Þd*¼Þy«FµáUÏÅ«à½ó™X"Ûs³jóWÔòø²ã«»ª5fUÍSºÜØõÝŠ(£…#Ó‘3Tƒ£%¶¡6!´*ÄF‰#DÞØF1~ÚZ0#Ü™#bm †¨i#DgíÐ汊G°BkO·F´rmnŠ£[-mä¿ÅG†sÓÂjš5д„ú¶¼w¡—("H¹…ˆz0‡“Y®4£ÂÃyÆ#fMÂ%ÕipÚ{±-dïaº³1&x}Ó ;@0§I€u&'²#h`WûeJCx@÷Ñy£óÅ™û§°î÷BˆCÝìd_ˆ×i›_<Øp(Ã#dE1!8àÉH"ÈØUV# #`Šº}aÄà¶Ã##hŸÌÈPR CFI¬#:ýPPªLÛXÄf*œÂƒöÛ=² õAûqBT{“Ø“êØb=;á@Ûï”ã¼/«ïîô·O[ùL™ÞF\äœç83~ÙzaÍns]*a륪ÀØü¼Ò‘–ŽVö­ÐD^ÃÕ1îã±ÍCbÊrpî“Ep§U½¹<>?¬ÌEK:¥d“AÅìéPÛþ4F–1y'a˜¸wlI-Ù¦…½×(0©°Šœm»Û7ü¼1ZaÖÄrë6-híVQ˜31š* œEVZ’â04>Ȇ´èI™\<ž²oFtääéC´æ»g aRxÃíndÁgzÆoŒ#:øÌ>æm=æIw h™ìOÂ@d~lFlƒxu,C;d­ƒ8–É£iÏç¾°ƒÛxŒq1ÅíÆ$³‘Î2œ©áëê¬+½[j[?2 *-;¬N꺢iˆÜ‹RÒ5ªi7ÚØŒ¤5¶Ä˜ºÆe“3-®’æ*6“8ØÛåoˆc#D¸4i­ôÀéÐˆá …|r°4šµk ìEu°<“„:ËlŠB¨TÓ;lÒÉ„cáñ²)ÔæÇ[nœæ3\MpàG™ E@¦ „©¤\8uUA/RD‚Übu‘.Ó€N4ÆY»sX÷Ämµ<È“pn‡Ó±™òšÃ\ä¶P1¨vŽÛ¦ëÄgèÙå¬dz#„t–é.´!LJhÒàÈF1¸ÆÛµz1ÈläD ÂT]à#h*c,…õ*g”I½ƒt´›Ž4îÐoc06¸DŽÂKDEÈ !¹k]R¹¬[rÛ&ѹF6ÖÑ@̃“AÂ+û»…=$?‰#“TÁJAjsS«B± ,„:_ î„Ÿã˜rd†2hêZÇH¸ÈÀœsÔSκ€øÔƒ©^UþДF…r$By'¨N~PÀ_`? FªFyð”#hÚými‚©ýj[HA©UB&$¡QSï?ɤâ.áí#:.þ;/ëê'[øIÛy\øb˜6ÿ±àò#h5šåm9ŽQÝ‘¥XÄa,»r–b*rCsq¾®d|w‡­ß'zjD¥=³;”ìÉu1Ê'\Ãh@Jpüê~XÂÄ‘Óó®j¸™½öw¾w5þ Å¡™õ]—¹vª[cáÝM®,“D7Rç4í©´CéúÞ+˜ÚòKÚ!s7x1 œNS|c5½ŽíZ|dZã.nt²~1VÐÏXtÌ+Tüß0ØY·xÌÌt¡£ã¼·lÏ;ð_.ˆyîDB ’M#hw:ÃŒR`¤¬:ºnÙµ€Î|£#h¥ÚÓ¡vÖ¢¸»Çñ®9£qN,‡q—­Å£¦‚ÁŠ&s^&#Dá=Z®r]q^f\u¶¢X˜”®æˆ£'5œÂjg{´-–`ʹ‚fn#hlPö´°„Ò®‰Ì©’#NbÞ¸«£ ¹Öàs–¶L)ÞÂ4ÆÆ6Î$$ø;k®‘Æì†h¸ë]!Ól›qPÎ$ã ÊÈï¦\reT*yR[ÓDªeM¹I )ç–ÌFÕ+K(‰¨§Át‡Õ#êç}q‚D犃rî¥Ê’Œ¹’©J±.­à®„Hð›ñ*4»®%>Gåp>dµå3JyßÕ˜â·Ón&ëg9Û¨…¶9’R!ó7nÝÝ€ÐKÁphÓ]-Ðt†Œo“W# #D!£bâÕÖ#h5ÛVdîîÇZƒRã°ç†ZqIÛÝî;¬rÃËrëmxžVÚ‡8qj©1k®70ehf2Ðm"ËÀ™Ñ<»6òæèÒÒ5:fBfÛf͹E)Èi^]f,@„Ï–JCF¡Ò‘Їœa]>M5’r"€¶¡Í !¯C¨‹™ŽgGÔMûCä¹ñt!© ƒ?â=Ú<á™CJŒJ4¾¿% ñÑÉ*zÏm@x’Ž¥– ³< —.,€Ôj‡ƒ«sX‹›Ze›RÜ«´³#:#:?­!qUÔ€R“dã ($àÜ~ÖnøÄó0V,3ƒðRÜ0Üæ/S“š5Òa½=I:ð`´–DhQVŸ¼yDòF¤P$4Y)hri‡°q#:ïaÈ’hb8Pcñ?¹€*,8mÅßÙ€2 :°HÐ ôOˆ†½9õ±@ñp¹\#hâ$#h46‘ŒÊ´ 4=ã©N7õ‘#D´æ#:Ð@åJc!JRDiÇ ¢ç[rÚgC Ñ2C5—I‡Ïf1âGSŒl#å&ÛtÄÅ–œ©Àá(ÝV¾**Sm™ZI#DJRKJ¬[A«6Z53n¦ƒFfD†PId.>`}=ÿ9£Jõòìv#h•#h)¢Å©”[U„¨µM-“@G%ûƒïÝUüûCÀ’Cš±ˆCVfÑfÚõù5ùÜÝ~pБS•ýý::O°Ê•1#:<¥B€û>„Îð»Ã®æÐå"}˜¹†q:Ì\ÐARcOÎQÔ/Ñä=ÞþH·]òœ΃ H*ÚÙ$I€¥oÞmµl˜ÕVh ELMb’Hƒi­¾úß¿oì#:Ø{*§óȘ„G·Ç0 ƒàÊ‚M5Cð?M#h£ÆDÒôFû?¶®1‡mbéy” ÏæúÇ_0öODÙÊEñ Øz½ª“ß3÷Ǻ#hBÖKb˜#hWÐÊÀï<Ⱦ>°¬ÏDqÖ7 0úÚ¹UV˜ñ¦%ý°F4«ä;6Á«Œy´.ºiL“WF½çM› Tœ¦%8ÀÈüöˆ™¦OØ\ò½üdÕRlúIöóˆyÈ^ìý°5å(mè üÝmþ—ø?ס@ìÏlÀ „³p„  €Ú’5pˆ°m¦@“(ax*]…É8x†z`ýüô7d ¨msoJ;L8A‰¬Œþ-x5¦ŒýBQ™9T;ÄéÚš.qC#:“M8Ó™k¬yR$#±qAÔ)Þ%¶fJÉ ˆ 'tpH…#:~¿iñB/tLB^aèê’J’»;ˆñèz½>aå‘ö÷½á'tOR(y:ÈÉâоôê9ò3aaVX28(|îçdõs•#:î9v,ñjT#TgñÛO=ÄO­Àmþ÷GÂb#Ô Á¡Œ ÌPæÌÈð5F rãÑâuE¼k#hvf4*À1£5„tb!4ˆLÖÓ!²(dÓ¿p¢‘e{7ë/‘±ú½*× 9(Q'xZ)Ï ÐÒPЀ _ñw§u­1CZ MkæYªL‰ºˆ›%@6AŽä­n Ô°Ù§¾ÉN•g_‚‡ñA•©J©ŸðÙˆŒ*JA½öþ®ZžÅøàú~a¹¼3OÇçùÉ¥Ë+5Ÿ×ùøýØ–¸i#h²gëˆH–aÔ†ŸW'ãÚð°Hóa—Ðwò› ´œè1œ,&@K»Àê#¤üýo²’]œÒ&’@û¬À”™ê·ªÒ#DDd†A’¦7Ѷ4Þ¾.»Ÿ]z’duùß[¦¡#hZ@Ý`fo?êç Ã%H‹ÊÌ0Äœ² bzè´:G&åÎMçI7fÕ¹=éê½Bî{µ{R{È;]*ö“™9­£4)‚o72lÀl#:É‚Œê7Q±©wvç)fí.Úù ¥½u.¸•×I¶3§»Ì-îlBn•Ú×@el¾·]cQŽNF„†(˜ÜMMÓÛÌâFØKØÑ”#:µ­MrQY=Õk©åß…F.¥ŒeØ&F:#:àÀÝ"Þ+TTÄPdËU¾6º‹Çm]Mj!Þ]QìˆÕ€¶@7­‘Z,3)2Ç6Nß]{]<ôÒ‹mÔÖæ1Îb%%P (à $Öjš¹µS7*®¤]kŽÓ )dI1AÆ;GV#D°XŒƒ‚¢—w;.íõÞ¯±¥¦Óoæñ3"¤jÃE¶æ·[+ÂàÖÁ2Fæ4¥N²2,X s#hÍ°a¢Er0èÑ hW ™i ÆÀû£#D&8¸„-⸙IÝt)Bœ¸­péRd»®£ e%×qAní®Š€$Ð2b˜PD¶`â'[»Ýëz˜—Mmq/Ϻג#h§e3¦(®-ïy­hCRa…TÄsa\/Ùîî½zèe—Î{xõ™É´Ð¶úFnuþb¼låç=WSϧBRô8®ÀáÍ\ò"U½s}ø‘­Á;Û! ùÞÍ( oÑ‘ôxp{›x¦R7¢#h1ŒÀ,,#/®1£y…D#Dp·9•Çv:VA¬ÑT­R–…K_k·#h,h¥Öi6ȉ¶jf¡bB!šI î×(šSÊ8„ŠŠ)IHGra´ÅŒŒ‘¦Ä¤„r^¹­!–!†8Šä¦KHÒ"R´g=t*›RÐúŃv#²A b^Q!rÛ .M1+¥²,²¦4f1•M‹•ߺ·Qd4ZR[e+U›eK,[I«,¦*So¦Üa€X¬Ò²”ÖI%d£JEe¤’PŠCdY–R‰Ii0TŒ›#D3e3e™eŠTÒÙ6X³bÒfIdhXÒdÔ¥‹‹jSX¢Æ£(c*Q4Ò–K3XСITš’+J#-“cVm0 (P’“™”˜Jdšd¤¥›Q¦¨Q%¶DÆÔibfÔ™$Y¶¶–UŠL–)I¬¥€†JQ€DÚªf"¤ &•±³m!¤¯Mm®&™ˆ•B˜DfQ(#h)^$4±Jƒ¦iA:À9))Ih/iZ¹ZÒ%µ¦ˆQ(x—!Ø{ñáí7ݬ䈛Ѻ×g÷¸ûA?ü» #Du†úw×0øåßdze>èoPÁ?ÓŽ#:›ŸL—ØïNSë8;çágHèâ¦yLv®óZšôIÊD4Ä©*Šò'Ƨiµ¯se(ݧa9R²ºtZKäƒ=ô8 èªS~"]÷rÚÅ¡´~À'ì65 J6ÅÍûn4uâÌ5Žˆp³ãbD.è)ÇåwFÙM Á#D.#D(ŒŒ„È «¬¦gÐ>Á¯Qõ˜…$#D`{U}‡a󾯮8h³¯÷7Óªsº€$ã{5Jpèÿ|C´}§>«Õt9ϯÁWÆF0Ò ú"!<Ì~'¯y'Ø_·@ãõ»ï†!1²1ÂL®ƒ*îI€#:Òk$ž&h#:àd“àžÆ>w95àðoÓCI³ór>WÛl¦7ŽØÜp`a†cåÝ»ÚXÏשºÖÞÅÐøå×€ž³¥Õ2„'LÁ#D%#hÌ–ßTF‘'™'d/x6˦Ðê~Ÿ–LnD †µK<Ö~6ÈåiŒ$3ú~á#Düܹ_#hÐÄè.MáhŽw¿ÇHlže\…2HsÈÌ&ŽLM|™n8€²&¥}ÿ,R/&ìaõFµˆ$öHÐÆ3ñŠŸŒvë¶Õ#VÊ7%ʉò8ÒÄšã¤QŒ¨îa1x43ƒGEð1~Ðõâ:ZTó%üŠ•¯ê¹yí·}iÀ¤»€k%ª5ª/êœ-_œ«ºí¹©Ãq‰‹>* Ð+pèh´  ~øJP fJT‚QÎK/–tîú¦™¶4’æß?æž?AÂߤäžmúOÑí6ú(ªŸ€v¦Çï~~¾+zêücz`_¬6pk0ðCÚj)*¢IOÅ;d_@§¤JŸáµí4¡la°’AN©Ãõjc•5Á€>êÔ¨\ÿš©áÔ{È8](£·ÛÓzVY#:ò?½&AI´¾v2³#hTG³`iMÆ– h ( ¿.qÜc¡”¿Ÿ ¹…ÆR(ý¦EllÀÂ$ˆ$È1ŠY:KŒëœ7ldš]ΛÃJºÓ#DFV¥e±F6ÐÊÊHiŒÔT]ÉŠM$o#:# Ê”’EƒJPV²ÎçA‡o,1h"A*„ÉWwmÖæSiH®5Ój‰„ƒÈꞘñ¨Ó§n®ZåŸJ¹ ^—¬·¿vò ÉAÝr˜“wWk•vtØÑËﻞî’_;°•ô®Â“Q±/ÜîZ¾®»~]wåܪ¨išK:ü½­ò#:*8¨5Ýœƒ@äíïL>Eõ*.1Ù¿RiŠ#:¨ž¨ÐP¬!X$#D #h®È"#DI¢"ò’™†¡¡ôPÈ#hò†@´õŠ?f¨#h<¬„b=§‘#h¶§s‰‚Õ-N`S©Ôõ+ÊŸ(:ü§Ýã,øp‡Â@öÈIR2Iõu*ÍóçÏdâ‘ÓÄ&U22H{ª^ãç×b48˜|—Í@ðÑØÔRò<ÆÔŽN¢Ý‹T”:ƒ$ÀrY€Ö¬À@$’´Sꌑ¡ „Ø#:£w×¹Fá¤ÛÓý<µ#:Õ& ÷üÝß“,¡zÌ÷#h‰£#:5×Áí‡ÕÉJÀi]ïnŸJ?”35šSâ•8–¾V»#h+4~½­T±×~1#5¢6¾O[Ö›Vë(ݘǿø1põ½íbœê…« ÄCé×#Dá¼d $À¨lÞ´"O‚#DX~ ¸n< ‚`}0.@#DûpsÓ§~Ô x¬–˜«lIÓ¡\3‡$õ»LGØ›à©Õ.ï½â>éà3}PëÈ>äT¨“#DXÛ†þLæíÖçéžhR17±Ž(÷•SðiÙ¦}ΚÁúòb@Ý&éB§t®\KØä2ƒVCÉK§ÂtKJ¥Šˆt÷‡Õ5BWÙÇÍb煮3‰g¯Lk!€›š&#D»M9Å•”Í5ÈCº9î”Ü C¤†¬“U#:îD£D~èÛ)¨iL‘’wy[õÌ#hgLÈ((2ƒ”Œ¦’zÀ€¹ŠP»”L퇄è‘|킃V-Æ`t C]pÉQL!ˆñ€#hJ 9„æ%ÃæేL4°¬Û½a1½oÚ®Œš—š•Ç<㊱jVî„•\4DŽv"h“m Ž0i©IIè4{fª ÈhnQ"¢Ç QŒ— ±jà6àû:ù8mŸ*Á¼"š€ë½Å’c¾‰”A¶ #D!T4Eª&Çá< š,k[ÖJLÌQPk´#iÔüZô žySÉÌŠ¸Y_l-²ÔÌ|>Mqw¥–êã˜ø¦ý',mp#DDj:7°½wмÛË‘BQ¥ú4¸ˆ*#¢ž>¢ùœ 3AÞî eô¬\’O9«FJ0ñŠ¾Ü«LQ¼Þ*‰˜_`„éÔs7ÌõZ8Ù†Æ. #:É!ºnVå’»Íļç1a‚ÌTÉJIJ¨cx9Š¦˜Ö³B5%4šp ŒPc­5¡hbÎjØ^ôù¼£·ÕŽ„£_vOy€±«#h‚,q!á±B36LÔn ™Ãõ%:ni‡2Òˆm°mŒ U:²WÛhÜ6‰GZ´IX GV`ö#:*”:Éž®í(vQEòïÅk¸rQShèTv[[*_#hºt‹lLšèD·=µ`OS*ùäÒpg7| %3¢I#Dhó»<;ut²“$.yè°~®=¹Z9@Œòª˜ªš«½QÙñžƒ³ˆÈ'¨¿ œYß­(íóTôb4ë?¿áñóõ<ÖÄöÔÜ9öÎ<狸’¢UË·[®â×3XZæÆ—Sº•Ýu•s]#D¯Ã¯U¥ë&Ð@ˆÏzû({=‘¬ò«²¡žâ™FÌHʼnƒ+ŠCó=±W§ƒý[Í q=¹Ô·5LJˆÝDºPÅìA§™´:gt„ÆI2D Ö#:õÂe· pjqÃ`‰$TÈ…q:_ÂD8÷r=¦»Ãõ}1*PR4£ì‹×BPIIôØY;à#hÌ 2 j–%¡‹Ó©DØ@#hd÷ã”…·Dš…͹bÖ1”-!F–õS„Wd@Ph#ÐÖ4Ìdœ¸eÛÕrË('Ö×p¥õÜFs¤Í"|§!îæÎîù›_=r—ÇÞ-}YøoµrdA1Aiõ.¿w®[Sß®¡öígÝe2¤ó PIBšdZ0*æ­ËQ6ÆÒš.k­»¹ßJèh¯f«é^ˆ‘m;NÝ ¾Ã'`ú¤WíʽJyž{j#DÓ”šp”{¸¬'a´ÈnHnÒ#ö“-,•'Ã^íNºöÛ¬›O3Í‚÷$)˜#héûwåÙ1ÝëëçŠí[íÈ„½Ñ¾²¹#D²#:Ãv˜Ì‹%&¡LèÍÍ}™¿U-U4œƒ:k)–œ×#:áTÅ8.ë;àC)=˜>Ĩlè;*‘f‡òýüyù‚·¹$ê}^R/.žF•'ðŒ SÀz¡å¹ñÁÁõÆŠ©À T<Ö—ý«,øþXtû òŸ$¾ÔÁFЄ_\ŒÊ¯«WÇXÎ[¤ñ±ŒOˆ7³I#:î:u×DQ‰ªjÑÏ¡Ap“Åp_$C«C@E  .ý2ÎÙ£Yæšß*R»¹Û¶á«»\2häýŸ»KF&4H§%'áêÏ€wUAØ¢lÑlŽÂ5ˆ(Ÿ¨ÈÅ&Q9ËW.F_·IÞšo§(køÌLHÈÆ7Ã#hÌ™cIµÍ„äóIFŠPc1S‹#FYç&×Ýɾw"Æ&mòÉ­!¤5a&£ †&t¨¹‰‰ˆ ÜÓhÅ•]ê#SNUºi%•1 fé¶i¾w¬]K£FŒTÄ%šR×=—±å—O U“Ø"´ƒÊýöó~îóö}x¯Eq{¿æòãÚPب+(ëæ‰ø÷œò/·Òg>=b4ôóõIßg„²)Á#hM’ÐVÖ…h¡U@¶Š)êËBŃ0Ò©M?¿øWk!”ÙA„Ù‡7äÍÅÂpðpYœ”%  ,©„ªË†Šµ÷Ω¢¸ÐäDf#nüûî¯kaÜsco®ä¦kçëëÓºäÃ__Éêòô®¤,¢¦Y˜'«FDk1Z.¶p#h!úµ]s¥šs$MQŸ6Y`ÛMŸ.äE"÷WD§š¾½\îëmsdµf/6˜·†AL_ºÍzD7?Ï@í“cD€â!¶—Á„CjŒäokmæ£jû‹JE6$Ô%mk•TÙ¤Ò±²ºîW5!ÌÎ륑hµhÖ¿…~êÉ+ÝŠ¢*5E†@0#päš#P„0WP‚‡ZÅ#hG<1Ó'“#hŽ#:BÄFñ°°¬* Õâi=V zÎ6&@´#h0†'tÌìÃYž™…2@²ÚIÃïÞÃg#:‘³$¥È?ƒešI!]‹— ŒÑ,™FÀ¥Ö;S>—Ãè]ÆtL4½#D!¼_LA4Ì–Hd'¸øò4œ×è·ÌU•uH1 6Zˆ’*!3¼Å1SºVŠ"^â¤CÀKµÚ£(1S,?†TQ)zñ1¤šã)^óòÊ!@Ð#B¤r yÀ;n#D`JtÙ®†"wŸI*ï‡Áž§Ñ÷ç‡Bb¤)ÿF1–„œÌ'# ̨µZêÙ[¥lm+rÛsl”E¹\´h“Tk£ZÑlcAZÒZ&š¶Æ¨ÌÛFÑke#s,j‹Q±±QÅW1µŠ®h¬j5œí¦†Šºm®r.•Q²FÕ9Ú‹XšWN•\­’Ö6´Tl"14bƱ±£›”Dm ÖJ±lQT–-ÝÖ’±‹cQZ’ØÔk £šåQ´h65I«F4mclTHU¬’j*J“X´hÕÓrÅj5 ¥4Qb+,³#hÄ#:0U#»´ù~\~£ËYC¡õìP8 j-Z¿‘žm#I„ÍôƒrW ½Ûv%n Ïòë\}Áž?J(&ª"%iR•m¬QŒP‹÷,0¦«ù÷ÞÖ½äÀQ™#:?GN©ö.téS Äú(üScÜŸXj콜:ÁM4¾ØL "Ž˜¾bZŠ¾û¡_ÐnæÜ‚fîí™É(ç.”Er+‚X©37ဂ<EÖȱpù¦[•¦Ûu¤JG Âh0yõ‘1UQ‡,@ë#Dü9C÷òò’êÉ8¸rÚ`>S‚I çþ½ˆ¦Ô€ûµ•PÃØĨb5Ô×ä£F0TFJÄF#hŒÒ`ÄšQ¢bJT̆R-£+,mŠh‚š#h¥ðé< tI]Ç"ˆÞBýZ7¯ø1cöî!ƒt #:$<6©ÃþY¢’ך=¬Øô: `#D›Œ7g³SߦLÑ!«È{\FZF"e÷ú#Dõ¶£2ĉIìÿžn=!ˆQþ?¸@gŽ >qèÁ`ÔÞ=÷Gq Îí|‡¤×?ß‹Sx~…Óµ|ÿW­=EJ¡¯ïøT…Ãïþ“‰ïŸ[B˜y¼cÐ|/„B PH‘;È¢ §Êé:¼Ý·?µ¿\g«ˆôS#«n¹ÅÁÎØ¡á8Æ5›}!RÚþðÛE5äàA†Â€Ÿp[„“'"×ÿ{àüº‡œ'¶ÊFIûÁóE³aï ·Hå˲ƒ"LŸh‡$÷@ž(IíóŸg²2¦3)_RDÅŒËw×½uõýØÇíO €‰S[§¨¾[@²@‹ç „"´€µ@Ò „ µ&@*†ä‚òjQV” 1„ïWÙ!†  Ì†j MH”)H„J#h¢ˆŒò»\°)€ Eê4i‡ÞÊÖ€ZmIÓúo/}DØæ¸æ®.ƒA\Co‡ŒDˆ×QDV-´´¹Ø¯A˜6B뇰Î%ÓHhÂÑ);íê“‘LclX¬T%a觡ü!êäyr—,‰8eíÑøù"¾!˜è‡»QêP#D«ÜÇIú‰ÞDž}§|>ãæƒ(ל<¦‡#:Üמá­sè¬0}^ñOWÔ&¼À÷T±(Ð’W! zõhá*7D#:÷IñŒ:Y‹|ÿfpñ?f¢“Dd´Åkk…ź.¯ÂªŠ’ç¢RšM€ÇöÚuêþIPê@éupAHw&Cø8°,üK*Ý<_¬H,è͉‰ýП †Ì&–€;±ÛtÐu`C fušˆ4¿¾Ëzú~Køo€u—Óâz=Ô-ùÌEßÊ8DϘ`¢Þiô±K÷ï#å”d{49él“Ëú× “ƒà¡rÂQ‘×c&ðĨ‹ÝkCW±“8˜ª(A—VÚÄd`®Z‚©Ë¨¦ÑFiIwNÛìÜB,.Œ\,‡@Í“Ð|‰ü}ÝXIÆþ­È_Öý{W9‹ÓQ¤µs$ÖœÖ5XŽsüY¡6Na‚‘FœZBતYl5f0)2w¦ë­kaHÖäl7d`V«cm…q#h3}my qQs†â•Ün\™’dŽC±ãBÍnÖ Ó#Dµw#h2?œQ*#DPK5ª.µ²ÿÕF3ý–Sn¢"ý`ÒPÚ!«IÆh0óΗ$¤SF:hÃJ#DGŽXÚë`6ÛM’¹™ÎmèXÀnX$ilM‚ÊAFL‚+"G“Y‘Ó"4£Ñ•¬È(À0áà3M6‚Å#ÔV(8A±‚œeÍáÄSÌdE—ŒÜa9“‚ä–ß‘·W“†ò"°ªêõ`%ƒHO"Ò`µ)’9#°Ô©3;´¨J#:¢šD ŠãŸî²ãŒ#DɶZë:!w×Ô¡ÒdÔ£BåM,]#RÄ¡©)zHš€ÔddqƬÖYDGLw%q(—™ÙF¢FéU¤‚á¶E“³¹27¬lQ¢´r¸0ÖEÙ`K¤ÞªÎQÕ°“,ši¤; ‘Ý|óµÍ®\µ•%oš0wu–&©5øɈ¢‰Pé&B¼à혜ÆçU:ÑmèÔM¡¹¨"•’4Ü‚pèÔ;ÿŸZçuŒåë"cÛ hfƒÊ‹b”Þ±{KÅÆ`lµYd5¢q0ÖCY†%å¿LÈÀó5ñÄß›¥^ ?<'À#:×¼ëµÍ˜ÅK{Mcˆ€!‹ZÎÝ¿©þKö‡ìÒç™}µp!³²h¤'%'7¥·A'l%àë.Q'Ø|¨‰µ¢Ìœ@?ÃøY$ÁDÈ ïXá¢eÄ%“p[ž@4»O½ ü@Eûã?ÖÒšN”AÑ4u¤!ü‰®kÄøÑ+ç*n´`$KB4»‡.!Lo#:0” ­X’-Þu;bZ‹ÃvµŽÓI·2ÿ`ÀÜšN#0$¥Ä„~û€ DØÈ“ij]Ê&¥Ò—W†ïŽ¿+|nϱ~ÇÇ„#hyS¡k¼=•+Bµ5+ ›6ν‡?‚—ŽUÖ¼oÄnŽé¤îÁpdÁ%iÊÁ~À>Õº(`NAÄ$2sï,d¶$騡ÞUWá»Fç†a;`àªT"KmbƒÓÚŒ§~!„¥%šÝ˜ðn×Ë7Åøž*XŠñö~ÄãMDþP8 t¬ËrRÆ$CW¡ò]‰¨¥œ"oÛ5šˆ¢ÔM‹ÀTwˆx44#DXTuÑáòS{ýúÛþž3§?q¸ÃñüÜ]?夡ÜädS2å“øóˆ'^CD<:¯Õœ~ ûÌ?†Ãnè=Ž_Pª``~˜7ôú”±üÔ$ÔÄTþƒÛ[0ÈãýA¸oÄ̉$’¨ªgŒÛqdÙµ}/7­ÅHÝ,†)iJVѢث_±øè0t'˜±$#:O耠RÕåz{¹)‹Ôßi»~é]®±‘Fa%€„@jT3Û±â'¸Ìñ÷y¯³QÙëð9#D4ƒ„)1‡ÔÖ^8\©j@R†/LÂuÏá¿gë@0‡’’B}ïË¥#: §7»c;ó9»«||ÜÆ÷¸èãmµŸ´½¾Ë+s`†Awb?ˆžnUÒe$dÜ'‰@ÝK›,Í[Ä]G¹÷›ºëÃâf]䤿dzXÛÔQÙßkŽ›ã!s¿½PLè¢'F°Éê(ÍTLé¾Ú:S†»Û|¦#‡6#YÀùÖMÓSlÉ¸ë¾™Õ Ôâ÷iboª 3—uؤiÔÌè÷ 6}ÔGÅ;J¤=\èœÅhÇsºÖ*³‘SÖÆXÃ1 áp©ð…ã’‰Gºí3vBÇÚé‹#Df7s…#Do¥‡ÆSÈ•4ÆBaÂZ–1 >0Ì c2°"ŽBQ³LBHñ½¢iæP›‡ˆ£î1$ð[H.ªG}Ï-—N·b¦™ de»Í¤¸„{¼A!ü¬œþ0øÊ‹'ÏŸÈãÍQ00ªªH¿š\ž#:~—GŠ‡®ï`L©AÆDR ‘AŒ`!¯èùM!Ì9Ÿaô”V'nï=ŒgÜ^ßÁAþ€ó­U8‘›°“ç@:ûèžÇ—yU@j M.´´!®q#"Í̹Rÿ=bCÒpÉB¦Hr·ú¿¿À8ôêQìrRLš%%¹¦ê$æ”öÌ„žù¯ƒIß I :€ûý41d§[%²7©ÅFˆ¿Àj‰B#_¾ãÙÒœŽØCžÜ[là5Úh;¶‹YûnÛ·ž¦SʽÇM=ÐdBrF;#h"šàLB‡ {¶ñÓK@ǯ9yó;övvŒ‡õôïµ@ú£1_È-e"êÌ##:+,òIö²jÍý#DÛéšØšÙŸã ÁÞ:÷Mvë´ž®@v*º0ˆ0l"b?»„OOQŸä6@ÀÅå"g#:s`’CÖIØÓÏÁÀyÜùIÌž Ûsï,ÒÌžÆÐ~Jõc¨yÆ99²A@?ÃÕÞKU2SïPLüé?H»'¶0“3Àº#:Áùÿjð)F^É•RÉ®üëù_m¦I†££`²ŒÓa¦ÐÑJÌF¤Ö2Xª”d#°ll’mY4cX´ŒÑ&I5’ˆ--–1š5‹lZ6$ +QQF6M¦³BQ)B-fÖÍ©+#DDÚŒ“S_½+¢1d5+5#KKJšR¡ý[vAfV’Ò[-b#hY³5¤¤Í"± i5”i4¶‰DÐÒ#hD°2ÈJ$‹i[-lÕ¡L‰Ål´&X,"3¬ÄÌѦ˜Ù"Õ™0R¥m$EÀÛY¥2Šj”Ój6Ú¢š©#hY÷t¢{ûZSDLCûóúó®caZ˜‘s„¢Šÿš\¨†CóLñÁ˜q¨§0MùBŸ< ì '@²ƒé¥(eügâSºŽRWÏšé*®jÅ­® !#:ÂU †”Bœed·Ršf¨#hˆûÞ_Ї¡DÕé ØŠ©-RWëŒÕüfÚÀ$1 ˆ{/õüwÔGÏÏÜ ómý#D$Z¹µÝÔm®kFº•÷«ïs™iôzQ%ÔH J}#Dt©(·.T£RÏô½ÍÕtÚ51š”BÆÔÚj ÛË›DÖ¡·ê›¶ZÒ¦m¥›m&‘)“?J2BWxd LÙ9E+IHà¤Ä5Ž­!4°žÿ^MK¿OÝÉOú˜ÕërÌ×ÝÛÝ\²w$Ñ…Ÿ[wI¨‰H'y@E`®¾Ü‡3^ÆÓS“R¤O*ýÐ0AÝÐH?.Š—Ëéý·žäýÐR!ì(@¡_Ï"|—¬åì|NÁ#Dû8#hEô_é`^û/ïwZÄÂÖ5üÌÚ4I™X©*J#’‹$£kQU%rØ¿~0«Ò¯6Ú‡ Ôä†àM&ÍÒz°;—ÖmWŸ’þDNœí,ñúÓ4†r(k#h¸°(j@2p‡˜ïb¢F~MSé®;ñ'5å›ígqò³&ÌÃ'“WkœC}¬4dÔ=aNNÜñ'΄˜å‰Ô€ÿ$x!lðï}8#:íÉŠìžåT¥Þž9h]¤ ¢f6¾·sTÓ8cã‰R*²3@ÖºÎã€e´™‚L|‰¸t‚ôèk¦Cûo!CTI†Ö$ غà€ '°úPèn|§äÕ#DS³#h@„Iô `õm#:^Iöõ(‡>#:Ãá«<¨Øãî#fRíèíuKWǹbðN_4@Ä#Dÿ}ŠoÛð©‚G¸<Î\(ÌÏQ&¢5ˆ†‹"’,5#:ôÕàÆ™/sÙç(šC Ê¾'Úo/œS§¬ÉO½Ù.R…,F#%@¢’€hÛ¶O0#D$88Vé‡À»ž…3oR$¡¬+V«0#MÄÓ¶+¥«§¨$ùµ_f«äo02=‡áÏ­|arˆ2‹þˆI~Œ]ìÂuœwÒ¥± …¢)¨•b0ÆF§C!èž‘K'xjdiÿ}B´Ív”¥*0I­x4hcÄæÉL.¿L§«ó‡õŸä?vžZÖÇëIpÀˆým`ª#h¤p~0*Ã’(‚šKÚϯ:Ýå—6’`#D´Š•Š}eDûj¾ÏÑ5ƒD¥! VãÊÊÍX8@n™å‚ìüÕ è d[¾³9a‰@©zâE/º×)Ïîú¼Ðò©Š#h(™¨¦JßéâÚ2”×5ÀT,›A¥¨°B„m´²·íz÷”„BÍ)¨´Ì¨À€Á`*2†æ¼çñqŽƒð¨ïÒŒm×KÃSº¦ºASºEûA|^5vàtÝ”b÷ÂýAJ>øoÔž½??pÉ^Ï®!W8¢QüYRlFFÀ®¤46]æ¢öœnâM=’ªÛ,U”e`ÐÒŽf%¦Îu‚X¯ÝÛEÂñõÃl-'À[&.$¤æYÓ£77¹)ë½R£[©2 žŸµÆt*dº´ÔªÑ&X}Œ¶Ò Á…Lᢦ‚mX‚4Y‰:•Àz'År(¦ÍIˆeKæíÍ•ôÚßH§wEšzívTQ}ð’p´È¸Í m #NH—°êAïZ1¦ÄßëŽÝj*j%ƒ$Ç_°½åçBDª7ÿ'üWnc&¦–Mšƒ½ ÓŠf©%’že!@ó†Aº´ºJc¹hQBU+>}ÖùÑ•?Üãz1‹ƒå§D•s‘6ì#h—5‚uÌÆYA–.YSÅ‘Ä!¸Ì€(#DlH£jÎô#DÐ]ÅÆ ­–ŽU¬¯©îöÝqG\1Áb,®-p½‰-Ú :J"}¯]¦ŠM’j4Ò’†tÁr˜ÈÈ¥½˜P Ÿfaâp $ÌSòÈ:"Û/^¢IÎöHìNõ+IŒÌ wœ÷ïfÞ“¢J)pâ1‘ËÞJ †YºX®Ã;¶žÍ5ΓM$þYs,.ßððGC"ÑHÉäbœ³â"‘²‚ˆ— ÄÀј„˜ÊšRB4¦H“(†h4ðaðá#DÀqfðiÈÄDäú}È>çØI%Š+ðswlÈ¥$¬&"#h‹1G@yäÙ5×M´úSÎrÏÈ€8„*0H"á8‘€{o:88†¤Ø‡ÉÓG×^»†`œU8AÍ”ÒÑLÓ“K¨GÔí#_£”pÔçI"ššGH+ÀOíX‡Ï5WÌP½zùÁ%ðI)­¥Ë7ÁçíBõ.•Ñ"P…½öw«fd0i¼÷šQáGèÁÐ#݆?§³Ì† 2`Ë1è÷ï#QK¢MBk¾r¢a‹s´"Á¸Mæä0ž #cmxj®ùëßKàú¬&RR›‘!¯ÇÒëyèO§iÞgåõx·«Ú¯Û[%Ê.\,[†Û®îÍlmQ®®ÅRdŒ¿–%ÉxŒÔÒÌ@x#h£„M_‰ò¦‚"!ªÊ†Puw×xfzí®”¾-{3õÉ£ô‘•²L`Ù‰‘PÁ8Ùü»!³¼CÚ}3ˆeJú½›Û—‰Lƒ÷ZïçrÖmœ8/±n…cc̱„Ú÷Üf'& àN3&œR14óZÌiòõe“³µpí…B d,£ánØ@ü§ggÆ$€oIÞðd?üq¨€˜ØN_=ª¯Ÿ‡¼÷¤˜þåîœäÍÎ{"ÖµŠœ=TÆK6iHB&9jT¸O N¹áÆRv5n#DŠjRÕƒ¢Þ鉌…Ý°D¤¥¹§[ÇÔœ‘ä†ì¬#âÆ8#W1AǪ ð …RÁe‘ÉÙŒ#hÂ4*yš‘Zmĸ–š²@΢»xŽN ÀY¬E\x¶$‘=#DZ‘ó„‡LÜcYª•-U_k,¦7~÷ CJ¨LC˜S°òñÃLÁd_H¿ºI&-nv’‹¡ÞÙsJ „+¦Üb‡ªè¸¤ÐÙò5-+ýð–6ä8u¸i‹T=ÑÃÝ“–$(a˦lÜq±A¨¼>å5Æ1 ˜Ø%‘2å² Œ ËÅTÓÓËÞc#D ˜c<1è[6{ëh7TJ#DÝ¡šeÒbÕ ƒ€šhãÚAƒ­'4i(yô¶x¤øí`¨èkÕ38Ä—+iDóÌ‚LC"ÿuõ#:éŠ-a7*ˆLú¨Wضiy¼&uASUjà~/ñ#h#:wàæà V äŒ!ÒQ“oüÚ@E ã/Ÿý?Þ|o»Ùƒ&7’Èr(- Lç@Áàd‡»˜4kJE"$8DPLbh%Ð‹æ› AÓD²A™ƒ"D!€uï׎`Ö!ßP|È›x+ù‰ö©Š>*£¯/L¾Ïâ({eU¥B(P=¬êô?+µg·ª‰X¢“¶u4êg “Q”À0ƒzéGÕÚŒ‚ˆk:IÈõxèxŒ‘g 2k' õÄ{¼v|=]s_r@îU”%ZZRa%):'™Øƒ×­h÷ƒïõ#h’w¼ØwHæBž§b-EÞþ¦{òÒ×{"rÊ:²ÌÏòþV¥-‹jS%l¦ª5”ÀÁ"rì}¤Ñâá㬅(Ô¦S˜jÔÖtTÈGå!Ýà?¢\üÿ-ßð陂VPíÒoÏDŽŽà7È¡»Ÿ×bú æ $ú'ÑòÎ{»Ð5‡8DX°gÁ¡1nšýs{É+¹ØÚ‹½zÙX,ä›D­@JD&åÉxöµËxñUÞcmÓ…{×ìÞ¢¾š„?_u­)!­&•Vú›¶­Ýºeº\¢‹VÅMwu£•»ZÒ¢¶4šÚÊ–´VÅTD¢D­*Ô3LI2M(&@Á#:c`@Ç+„ ý]~ž‡·WèqÓ Ó¾§LÙþ#DªðJ§«:#D¸Cqâ€î¦…2Rò÷lûni¦dj4Àž±±Â7îdÈò¶ƒ¬‰5e±´£nH/ä`$W)¨œaYJе.DH#V”¶”ÛîÐó!X•¤ƒ“å†TR%¶Fà  ã_6í¯N^¡BšâѶ6ÚŽg&ð+6Ça áÕ=+Š¶F#D¦Yºâ0ACOѵØ|q²­'8-¸9Ö¤5×ÄpìËA4©I†b–4"˜ÄÚ„âSo-±Á¹×™¡ãÂm‘ëÌJp#h&›×5’Õ±¶ZI¼Â Îõ‹IŒËvÙÌõÐãÆ%Dæ30õàѹC²ämH¢Œ ÃÄús ¶K½¶êízBÓÙd.a‚ƱƕiŒC—tóÌC ˆ8cTƤ)@Èu¨Ä0—¬l32Ir2 Ô¹O0Ú˜RLxÎ0lXÍ45+ÔÄëh"€ÐòÚ]—ò·s¶Ü!Ø°%¦3Œ%#:ÊG¶G™Ç#LL8ë1éBpn˜°+#DÀbâ„b¼¨­Dt—X7hݵã2eh).nbÒKœR?éôu ,áÃIZÕSf„³‡6ÛÄ0‰€Ñ.#:ØBœ=“#:ïé2= ¦W…Y#D;1Ž1Íftø$žBW\ÀÁ%aGòDb‡E=TL¨ü80j|神ÞîÓÛ;]û=y ¦ñÞî½Î’Á45¥"Ô#:×Ä“¾ZlûÓÕÎr 9—ô`w¢YóóN8œšb‡ìùK,7+Hô! „:=?_ÊëÐòA¥öƒAz õÂ= h#h%°¹!Î2#:Ô(qŒ#D%U¤¶Ñ)jó\¶¶J†˜•ôùf]s¡¢b&R–úíEÎc_¶ÜµÒJû7LUZ·<ÎäÝ‘CÅ„êNµŽf.¡<’ÆÖ(ÔQämÒæë»”œ¸îæŸnÛs%çs·Ùg]­å\­’κ¶}½în÷lQ¹M#D)Jæ99S†2skSi Z×ë6!³_$Ë#:÷HJGD…t¬K‡ªˆ1@gð÷‘gñѯ¸(ô¢-›ª¡OW¢r®”8=Ç»ó£ßëêý#:#D$À~€¢½ZÓ†Èûï[æ]&’TéÓÄçõzü´ãÀ±RPbŒömåŒH³Ž ë¡nsˆm»½ÏŸŠÛÌÀ”‘Ù¯.ÿ=ögw|el1â‡èU¦±™-HT¸ˆ§)—~®0­7 ±oð¬¾`–Þ«#:Ó_/î¿U¨³„²çKÝ>œ-ìë·ÍgÌM7Ü›³#,aCØ”wmƒ8*J9=õþœ†½#93×Øwií{~-\ïìÁÿHÙ°Xp˜¶èÜ™€¹ ¢z³¡´|³#:'`YSp(<μ¶V»kPï6ãlä#hÒYÛ´=œ“@ä÷#DD‹†,dYˆÒx=S§.+Ü= èéœL›‚‰SéÅë<ÈÜüÕV™ ¸ï0 O;“ß\F—Ý.öõºgñTGÚÆÆÿÁ\V‚}#DÎý=£àt£)‡ˆÊaÑ´Äê%á-  ¨Cª¤æÉb„™HDK²“¸ÍmÍ*€]ëñùu?†0~æ·F)8“ È9B1ýØ‹[ÅÈwÇÇñýc7Ú}j?”$Ä<ü¿^ƒœW?3"½OÃÁóý0>#DXA¦Æ–_iŠ°èx+NÑ‹üc¤gBë›Ùa&CC ÍÒv ƒ|)LQRÐãÆ´lÔhÈ´c©#hæi.Ï°ÝÄ| ƒ§L¯÷³©s*ˆpû rdÅ*,áÅCz´×ûṸâV”"ãøõX'‹÷6¤µ…~n" 9waÑñ¦¦P綸ÝÎ<þÇ^ZL¦7jj™~\T8d1 Ûò×ÊÂtÔóö˜ß´êøô0PTÚ:ã©õ”ÁÔb<¡bæd*¦¤án©QÂË0 PðKgÎ)88%ûðòÞÎ`’—‹lÀÕqYipëæ\?#Döp`©˜”÷^øC©D#:wNCøKĉê„D’u#DDIöÙ³˜% âà:ã#hÅ£LÞyhÞi_wL¨Î]oïk*í¢Ê‹ù½]$`>Ivyóû( ¢6Ô¤Bµ=M1t<¤ø”C}ßO”ÝóÒ“Ì TH.–y«£@Iâ‡ä#:Æ@„W ¤ADC¦—3€¸x{oNwu9ÎJJ&´ì~ï|&€ÄÉÜW™îˆi¨ìÐÓ |™&›Õ7¶u²$ƒÏJ©†³o®wjún±ÌȃYpÅFâº±é® …ÑZ6XÓX—1Aä$$ö¥á£Ê®…Go7>D#hû’Srì'¯'àÌgàÖtPOéÞÒ<\jCY˜®!Qœ<ü»;$rp¶ e]·¤xf#D˜9ÓÊPär\‡‘ІRhªÈ5½=gW÷ûƒ>ÁSàM#@D@¿î’+Ì)ÓÏÁó×ì¹¼pÃǪ: ø¡à9ÚSaýgÐù<¿,…Iý<_OÙùP.b{—?ÓOWøyÌ$Úžqö‡®PS>óûÚõ= Nú½B!O^!TÑÖ£ðì{šë;}Ÿ ÉÉ©'ã#3­sqhÑ¿<µ£óO*÷£2Pr› ɨñÃ9ƒe|ÝeF-VÂQLréË„%ÑÆ^pµ­K\7!ŒÞ™„åÆ 8†Z¹0Ì G9Ô¼²pqçæbiE;k))¿WËV¶†øf–ºÁa~—$#3?‡4Þ=îž]é¤ö?ø¹Ã†ÔDŽØ)aWPù‚‚Ö×öbõÔú´UJ¶l4/\NvEå¿#hË( 2b…w`ðV86Z&Šƒ"ÌrR“7Ô¦”ò°c!E¢ Վ¤#:1¤PjrˆÝö`z‰8d#D„´A‘pb¬À„ٳƜÀÊBHëœ.¹J£1ƒ1›R¢0)#Dá2+¸Š%$•5j†u#¶S5“®‡«{!6G:‚ŠÃ0b 95¤9ýüi¡¤‡ˆF`h#:áÍaK«R5'R`T&µ’1)í´’ ãMΉ—=².ùqñöß²qN›âÖUÖ­ó¤aýï÷¿Ñþ_ò‹~üù ¯ðÞè%èŒQé’£å °dR4t\‡1¹¥>2Ÿ·0`šS²/LŽ.½¶¯ 9(ª"†l F&Ú§A:3é.¬hXM²¬FÙ™šâ]è«Z¦õ#DR6Úɰ؉¸6,UÈš ÑLXÍÂ8&¸#DN8•×=Ç4Í­j‰ðΦ `ë6>"¸™ æÈ1aÅÇ?Š«‹ãvÝ^åd¶œ›±Èš)ÚÖÀZšÔa?ƒáù¸NÈñ¾°ñêVXN£IÌÍc¸õürbaÒQrj#:pÇrÀÐÀeK- È!r4Ð&C˜b÷ñî{1tá9+K&ØN³‰>éR©ˆÀ'wc—q(ö^¶ìòîîËÓÔ¬dࡲfC%ñèiVBÖ<€¸†ª£³pÏùêJl6ÓI7˜§ˆšø¦³¾Q?`Ÿ–ÂÍpC„m¹šb*"#:ðïКÃHæ2õ<¡àS!ìy!!Gb븬õ&G%h ˜îñü“X§JŠ'ùú¸O,ôyÄ<^>|öx2÷ÞqþÍéÛ›—'ð¸õó¨N©ÒC}¢\Cü0øJ‡= ®{B±ýÁäN°Ü #h„8ÔêW}•W¤Qùºœ£ y¥yLFX‘$7Ã$² >=ù+ØTÇwB˜O¯^­)½Tå)Ý»#h@¡ó£É$@02#hµû9’°òNÿg·œ÷óãè‡æd’}Èš`‰‰@÷Èc( [œŠŠGÙ´Ž{®ÿAz¢/'_w†Òà=’ÐÅQbÚ‹hbµ¹j®mg~¯^æé{)“®æ©+»¶µïw›¹­Í¦I£Æ÷TkõÛ”2#D@}$™ ”AJ¥)mb­ÕsmThª÷s"‘7qŽT‘Ò°KJ´ˆšþsSE(j@“ÈŽÚAšf¡@Ú-ˆxÉ.u›†§A%dºn4F”ïî3¼[ºŒfœdfÓwN½h‘Ô‚3viä[†17†,m¤CWq0`‚Ø'{˜â†Š¢å˜ãºUX£Q¹É,(†4<$ƒªjb0U‚CãååÔëkzÕâ9=k¬‡tá!Ý"h€Í`è¢G|IGr tåÓ'R6©Èï‚„~PÚ¨…$@ô—% "@¶jϘ?f—!©ÎTåʆPû ð¡„adFÙH˜¡Ù„iy!£ïÜæÓÔpû÷‡aõ6ªö‡Åšhivz…HN*3{²ŠÖÔû§ïšc`÷bP˜faP<ÙGì³Y¸]ºžš"ÇÔž3‡iû6IÖAÛÆ…†á©Â#:|3”Ép0pŸRý‡‚þzˆÀzÈÄ!†AT#h(CY‘>,PæÀ)!ñ¯;—ƃpÄ®þ»›gBCa³Š#hI»(Qï©99u‚$¹,¢Bpç€ÂpdrµP*=ÂdÓ4“M–„ Pˆþœ9°pÐJÒ Ò ‹"К\0ð4x&Ž^ðÛÒ!ŒKü#D%.ÑßDá8nĔڱ°£ll#D’H{™½AkP…’µƒgªŽ×¶IìëÏk×rvog«ºI¸oDgêZBÕ˜6ÐÂ"+Q#h„Jø„h‡ó!M^d‹Ü9[x)#µ×*·À4º#h…]>úúÇñ‰¾“ºé†Ñ–4ÜÏ%Þ½$r }oÞbʤՖbP"‘#DEEØ( ’˜‘zÀ˜É´GŽµ£_ŒâRK³¤q)e‡t¨N#'FdZ1“/ÜÂȘbÌK‰Ý¡r&æöt£Ãàb'qò2æâ!ø#D‘©!°žð ð¦wJaL€j¸‹Ù™‡)¨ÑÏ>Ü80gg¤0C½) Êgw÷WðÁ»7×÷õk'Ý÷†sç4ælÃÙ€Óå>fcä§ßêu'—“*¨F¾xCê’‰Íg»ŽAÈÚ¬~ÛWË:Ð#]äϹÓêÈ{gÀ>òhô~cSöC1ŸŽòÈcŒ­äXn-Ü+a¡¥w3á~·œÕ ©Í#D®‘Û5{ԛ⡣‹” b…Œ·t¸Dhy.6#D´µ‹#D™º#hƒž¸‡î#h#hyÑ-™ÍÍRBCðoJÁM3VÕDn|…Ȫ}P:H%¡ðÔ`ø4Xf,¸5[æK®êlO©{1^ÔZĆŠÆ']ÈÄ»·m¬çÏzzSwnÝžï^¹E×½w™.X¼¡ªa©ƒaßtðöRQv±$D‘"c"˜&—-taAêÈè6s÷By[©L¥wË‚¤3„ÙMDcP2‘ŠÒ]X©Bý‹Y™£Ëz: ›»FøDݽóÇéú³´óÚ9:c‚J÷5ŠÎcÞkcdº7IpµˆËJŒ»,4pÚIð?G:8‹„‚4kZP˜˜[xRP4š?Vñ#D» Œ˜30ˆ2ˆ.¶rÓf5V¢aN=)F<Õ%ÖVZ­R®µ‚ªZzÈõš>‹sÉÁ8Æ™@’驈NÙ¡äÕd±($—ÌÇ°‰´–Øo$PÖõLÞÊAV½´0ÊÄ­„!$†ʨƚž@–9Y dà Ù^›€ÆJ#hqc6m#DMŽ¦4ÆÑdDŒ˜¬1Á¼¡iÝêéfŽåYbÒ;´PE"†@­ oj„›…‰QȈV Œ­ F ¼ø»Âåñ'ÆùÝ]¤½¸ø-![I²Ž#8°1¥Ñê‹R7¹`ÀÉÚÊ õ¾¹FÐ6Î-¦éB¾#h¡HŸ‡½ £È—Aðhšk<²³3¯¶\ñMLkìÙ!Á³¨Ôn™(©<Ôx54 iט²‘ˆY™*ç00~d ØȃlI"Eªzd,€ÓÏI6XWCx:% MG^°±á•Å+šË˜Éë+4Ì Œ¾YXÈ”D ÆÊ;¡u`Ä‚­ÕŒ©u )È­ƒ$ãu¥§¶6¶Í消ƄÓPˆÈ ¦¬ÀÄV‘k#:l¥#PºÅ0£Ax)3cm$ÍK†X°t¡²@˜4dOÍT1Á±º`Ôˆ;€±ŽÃ—Š‰ÒcŽ6Š”m´ÝvñhÒBQq*q2ÔEhÞ–ÁÃ4¥ÛJqÆ$á' ð“L…&²™\ŠRßiÓ»W¹íîë{×PÒYPŠ»Þ½ço­|–®Tòvî¾}IÝn½Ü’±AŸ>t²¨5kËJæÐØ«t¨j2¹(édÙ7x@ÔÒeqÂ5%M«hÄ5vÅ¿uÚÃ#h&*k0ØùݱPÈÂåÕ#¢µN=*ñŒÖ7iņš-Ù:B&švNë‡N4Û4s¢Ê³pTm¢‘·ù5·½#Êþ¬yoJe€bg^Ü®aäÀÓ;6ÁœýeZ®‚j7®') Œdpš9¢4ö1=£C¥Ü.eTÌö0¬Y¸‰tP#hQ Æ“’¹†dë6š^8ÄÕFàà6ØMQˆ*lñ…b² #:"¡ø&™ŸJY4g\€K"ż4$àŸIÐé­$‹ùˆªd|¶Í´e‹l5l˜Ç/e÷åؘùÇhÉúGy­ÙäÄ9áÆ×ÑqJÙ€…8ȸ~SÒV_{<Éñ{wÇ¿#DÚX³:2É„)0ªììËlÅ›â ,`DÕSZÁµC9·ôã4âqɎͼéFŽtÄœVô:O‘D€`˜6:º¾ 7lUEºœ+¨SC¤1ÓT§çÞ;`ÿ¤\yaܼÊd¡o#:«E‰V´}{4We±rçášo«‹}c†!3„©£BhB%ZR `0îQ#h `p p”†.ŒM$<&;MPQ¢XÀ‰Ñ†„Ò2æ“ ‘O‘,Fòœ&Ç+Ç|ÕfmÖõªŠ e°<*|SÍV bCÔrßÇ¿Ú-WL|üçXËJHPÓXG´j%Ñ¢"˜²Æ hYíã§æ³&ë¤Ò6ŽlfðˆªƒP¹anÖñå#ðj˜{~úöÿV‰°²tä†ÒQžXÚ_è4,aòš…eö™Ç¦tÓÞÆöÏÅ2Etí­‡éeZlòȳX«˜fÀ&ÒÞ }x@aä5«ªŸT"ÈÞUÝ°#Dg~tĤš" ó×ÕkWŠ;Û¯³2S¡%z¬¨=)¤¿?ÀÕ¼2 a;ij 5#MH–NÛ‡›zÅsÝ4c™¼6é“zLɆW— ”v&ÍF{3CÖÞÝ%ƒÉÞõ™nÍVåÓiïZMÁêZº¸S8¸ÄÖg5¾#hpùÆÔä…$Q¶Èd5”Y«¯;äªÆÓíÏ\9êÚ9‚‚½UtäÛ62{.°&ö6¢ÀÖ"†Š2]6äQmŽ ñ¥8h0ÚPšBÆ+±r=² ó\ÖLoMè-œ)jëÛÁIÃ0«ó„ž6pÓrkÖôxùyB'6¶±jbÁó-ƒMï®%H™#D’ºiB¦ý~ì3¸1£Ô:²rMJè–Ë&[iÊäÎ !DçæØïIƒ~uKFÛîÄ6þ¾öÈíK Œ¯;8Cz öàŸÇîç¸é²ZM¨…^V¡|Ú90ÃQ‰ Êý`?@}¡êÄÆ”"††„oæÈçä´%´ry ì#:£hŒR% %N`šðp#DÙ}Ú# 8Ù"j1±·HRX[ýX`ŒL (ܬ¥eǘ<´u6›#:Œ%ƒ"p 8šÌ)Xd¡‚?\sRÈ·]ˆ¼¥?®)(#h@„PiïM(ß—¡Uz;@¬„;àæS`#:w d#:Þ)K‚ê,ª´R”¡ ²Ë#„Ý‘‘촉Ǟ¸§îÙœò9˜©3c’BÍOa-’ÔbŒ@F*êj±Ú0¬& ˜@Ü)©#h¤®àÜ+„ ›ª¬ÍAb®÷a©Ô;ÌÖ#:(9HÒ…$dbF]ut{·(TÓ<é2×e{±ÖÚs‹r¼Ô=·œõyO©64YåÙ|t„T©Wœ6wQ¶‹Ye©TiB¦¥#Dƒ’¦HHa­hH‰ #h#DJ”±#ㆬ!Û£(MžÍ•+0s"11á¨Ü§‚Bs`(«;D`U9¥%Ö«²ÕKq4ôÛK,ŽŠÈ²¤f06‘0°˜ »<$–`$äô@9Å­žÍÛª]š—<ËJ*ðC¤LÎ ¢R&)…š·Š†D$ÍP>¸ðï㊃¡4péñ˶髹E¼+r*5÷y¯ZÊí|˜´Y†Ôl¥5¶Q˜´Jþc iF2ŠÄD¦Ê¿[¶¢H­}¥ÊiA "–%±R3M),[FÐQ°hÖBÆÑ¢£V5ªeFÅLÕ$l™,¥4RM$Úš2‘£5ÅEZš¾»mtá#D",‚µ}ê¥Ñ~%DÃH,tÞjB à,#´šŠd‰¢áB¡£©}·±±>ÎbKIB, ›,6")˜ocWцMXÒj1Y-¬Z„Û&«æÅïãícíÕ5ä]þnØÑb¬’ze•<$iù™Gû*„äÊè3Ò)¸dp‚…r¯¦ËºaÞa“ëU#:ùÂÉ#h"P"!0ŠPÑ£#:ààÈ¡© R ™§¿Óêƒ@nHñ~Ž›hU)L„Á–„¥¤ÈÈ2A#hÈ0”Lc(„âPC&]H¨”¢a1 æÀÀª’#D6A@9#D™6b¹Æ“ Fj«ËEcY6¶±%¨¬ÀÀ§Q“ˆ2R %RÔo5¹¶®ö·¶Þ¢pýZøIØæ¯{äGM'B .^›WãIŸfV Òþ²&É>¸«+#$ŽGkÖÙ[ÃŒ« HU'Hvaõ1Ü) î9MHh“Ip€FR…‚ä\ˆN@_nÚ§j&¬9œ‘ƒ3HLN³M&-žKÌ%È ¼={`ØNåD@'Ï8K Îj<ʵÞM$—ÿ+GQ餠Ô)LŒadb¡£Y\hé^æC(R(í–‘å&¢:¿0|éµBçÕ“}¾Å¹þG¡F2aQÆHgØ×Äýª‰‹÷52G·¬™iËOä¿+3¼sò³l¬m¤ØcD¬ýÁž˜i”Xãmø¿½öÙ¢’«~J5Bž¸NyÀvrH¾l‡&Ã5Q§FÖ6R²•¾—kÙ­»Åt»Œc8zÔ†®¤¹éhnë!Hzää\éF‹&›À¶.#DG^ënCXåÀ¹.L²ÂR<ýt™§éìô ³q.AÖ±4`™å®lØ7#oÙ"ß~ØpØ=HŸ ¤`ÎŒƒŸÐ!ÐkNV]¨N"bi¦( Ó¹#h4³¦¨É^VÈeg«vZF!ø€‡õ/cq Ô7š¿â#:ìÚ`V1z¢‰hiyö Ür€ßÉløµm,óiŽ’ªŸÚ•çT½õ+ר°1!º!>ðλ;C˜Ün¸^Ò/÷ú—âê š¦“<,3Äöœ¡øé;ê¸}Gl0ÞþÌ”Iü#¢zx”%\/è‘•"1Ú¨6¤ÕTV)ieð„L€pf'Ñ*ê7)„ŠÅÄž¹Sr®ã&(¾ÊäV5Immåk–Ö-Vä¢îN“ò÷@¸e‡Ñ¨¦Ñ‹ _Oœž@}Cæ|·éö¦•H€¥3öyi:° uèbk“‹í!×¾ÅvÌ1¼Ô´%]â. ™2ZÁJ‚ÌÔ5FÒû؉lbÊDWVØ«f±ˆ¥9Õ[Û<`šcyhj°[i±6!Z¶)bZ#[‹£–ùÞÞ®j¤¯CUÙµ&©“¸Öœ–…Âä¼Æ”5¹fb¹!’!UÔú£¤¼”%š¡Up] 6~r.(,bÐÚdqHXE CŸkÍ—XŸ¤„Ì*Œê'nqP$¢ÃMh*«@D"TyN’¬QD¨!2ZÆ-£*Æ5µ¢Ñ­í ÉÅN™ŽÈà0Ö­&K[i%»¬[¢‘Dsk•÷íÊcÉ÷Nám­ÖÆDš;ªüšúVÞZ"ÆrÅËGá×yW*‹3+®Û•ºÝ)õø’`†Ü꟩é­ùÝÞà>ÈÃê®#:ñ±Ý8ÇXBrˆ±SOí6oqFt#hÍ45í#&¸½g{ÐÝ“PˆQ§Ü›±Œv2\u–Ü!®‘:GÌOÙ/¢½#:UÌ€E‰P}º¢†fª‰¢bÔÏ*šQàTKv 7ôñ `ô¬Œ~Ÿé!}?sŠýà3.ÒâõÎÜ òñË\6“#:òX-Lˆ§Ï¼K‰‹"Aе%Çñ£déÞYþ¤,sŒ&ŸóOŒ†Aá.¼0<4óᛣºuwæI©)7KåHÿ~plšDöÔ+¸ü¬<0jR’¬ÑKh>¯ÀüƵ?,å-Ï*’Õœ’ !Ü×J¤{½á݃)'©àÔ DRoã^ õ˜ 5Nˆ[¨ÂŠª)íeÓÉ÷#h¤»Ã ¢—î;=N½åÁxfà;@ÐŽà¹%7q¼Bw™’ˆm#:ú冭Æt›é€µ¶T£:E†ðJ±kbªWáÜÀùòùÃ@f8 – N0Î…äѬX_ÏVD9’Ú›0¶Ô˜¦¸µiFîfxTBâù©J«s;¦uîŽJÏ9©úq¿ÇîAQ¢ö(Ù§ŸÒº1Tdwà¾o ·8hÃü®¥PTMeGÀ¼º $y"Ꮉç©+ö„D3d·ãÛ«®aÆËqlw@– /0ÌkõA²—Læ ²xa6ùìkªUÒ°Kl4"´ŽiFe„H‡ä:Õƒë¾i’9Ž~ð¨\ml­¤“úhb³ë4??#h¤°i‡SeÌ]‘J¨`zÈ5¦ª€@Ä ‡×êÙµkªA¢z'Eèè94Î'¨?Û†‰ˆ‘W¥’É'ñ WúaÌeS¹ðt_»ñ^<çËí¾ç¼ùƒ¤¿¶rjBæ¯0Ùú)Å_WWõïh®9¿¶#D¦ñ¡1¬â¦<0#hr”í:R̦Q2×`}fß0yHL}(=eê*wa¢äJˆª*ÇÆ s‡0)”ö¦¼ø!TSÑxä—æÉT´d˜&Hÿ‘”:ü؃¿×]{_öiú·úÖªlóÁÀ‡éƒr\•döé•w[ºª’JVv[]€QùÓ0Lë?«Í>ãq6,˜.RÄB$©$*c¥•Ë$Pg®Þò¹#ii™Î«¶\êj66ÔZTén둽uÔÞ®ìL£\µs^W]×½sº½´™1wmÕë¹êç.›úOeå|ˆTÊ2öܲe2¥Sfa4£a*T­m®²Ô‘cEévl›çt¨®nn66ó^ñÝ{™y®:ë¨ÝÅ«ˆæâ¼ÖâhŠ#hu½Ž‡Fò`§##:ÈYXÔ:ï{sÝ׶ջU’¦œæÕsUÓwu2É9ºÖJÔ–Ô¥ŠÎ·.Nò¹QzM½Ý·M©Äï-ååwN®º¶ëk”mºjÚõTj &@‡ùñQb#:ÂHÀ¬HªUm‰¡ IàTó©øÊ¿Àô ñ”`ñ@ N°…"P P+îòü£ýïX²`Ý œÂv”ÈA(JU }Oá C¢>p<¢}H»ý£ˆ_P?dœä ƒy"˜•Bú¥8PúBP>g*îjeúÇO‡½ZAÐ}êˆû@úîAPô>ïñôSE£#GÐ.ÇùçBŸ¶êYd!@#hüƒ¯Q1‡õïG’D÷CÖr#:!f©÷ç'ß{ M~€ÏíÙâ~½êäB•?„£JŸ£±à#ùyÑõ¥#:çöàœ,ÿgíßúFßwè¨=¯ì€kÍø?[ö¡´W‰ˆA<š"‰™#:)šR”D¡Nqð>ML@BHB=COkœªPÇ•t‹¶‹jص·¹^I¥5Íö€6Õêu(£q“Nxk{}ÌpÅéÝÆP¡T¦€d$@(Š€™ï ìæ}?£ÙÎÑjJ$H¤µh¶ÛÔm¢ÆÆƵI­EZÉT¥mchÖŠR²±‘R#hA¤ál†”‚“XŸ„ŸšrE܇u¾sù®é^XqêWtˆ³kâ׺îòíÂU2íe›a…Pa(tdxf ]³ î#hG`Dã Ð0`4’Û–2 ŠÈX=6RåŸ^º¼7NívØÑ9˜nG’#DÀ™.Ê$¤>ÝdN 2@Ðc€ñ8°"L±!’5:)lÈmÂá¬Éi"Jj ™xªЙR/“tɨy­Z'Zœ "QŒíŒ‚\X6’ƒGÒ´ø™!,)rS‰þ°ý^FÔ’œÅ) Å%=Õ¼¿ ŒÙΉ“%ÅE6C"C¹#:îº#:ÂD‚H…ú› rõÁû¶wÈ êð,)±¦¨¡‚ÊbàÆÐD”;À(ɤp œÇ;¥vÉ@`,€bO¦"’#ÝâM)¾ZoV#•?àäC}ˆIíH¥<Á:@R­#:;š¼"ýÑ“Ëpaüü¾»#D1´ÛàiXNÕ¬ïEÚ8 ¶úìÄ^`F—P¢ó2*ˆJQ¡" Ä“$­¯úøµØÈjy…IG¹;Π㢓ÌQ=&ÛMUš´™˜£*SM‰¦*6Ñ)¶mjT–RÔ–ÓZšª›h¥°HKÇ¡îÎSÃሺJ#DJ}îÎC¯‰Õðƒz~2ŒB-*‚Ò¢”¢!är0‰*È2JzmµÍM5¥šÅ5Œ‘ØiY’šØ¶f­­@¢wf¥ ,|6¯Ëîbf“ù±;ÈDŠÌ?ˆ~½ð^G°I Ô*˜b½–ýbT;£Ó( €À•0ªzÕÑ“™6’¿e¶ºb¢Ú QÚÀä@0 4¢“„L)çÍÐ:=bŠŸð¤º×ŸgóúÎt2eöq‹á'¾tyËè/28Ð¥Sçß³H¼OzÇîþðw>V#÷(Q)U¹Ü ¿±Ë¼Ð…²Â¢0Çœ;Ï¢á€ÉÛòY`Oö|¡Ë¹é˜H`‡ðF`VÄÀh*!"þù†ãÜGõÀõ“™L@¸…»@<‰ÇáqÞ-n'‡•¸4v&ÛC À 7-¤+Ô$àƃ0¢Tº4m‰ª""f›“5þ[y±â¹è^)³‰¡ØþVƒùj‚¨/âzÀ'­#:ô"P¡£ê$Ìïùl·ŽM¡ÖFlF52n#:¥ÇäÛkŸ¸½•óäë‹^pŠ}c˜ý…"+0…#:´ÉT £±ß+êl²‰¶‚VfÅšPU„Ú1#hIñmA¬ÖºÌ=T‚Ù"Eu¨ÆÒ³õHì!Q˜a³2˜MVbõÄ R€†¸Á5Rq¼Y¥•ÔAT T!Š(øhkÂã+"²@Æ ÆÊœƒ® ÐùkóÆô%1:éúsk¸††BB‘†!€•K÷r)kwÊwE¬SP‡FˆãTWpA"îРÆ4Ò¨C0p8‰sE«£Ú#GPñ†ÈÙda-Sч*Ôä¿ &³ö;¨Ò”¢Úi|UÚÆ‹Qlkªï·®Uø­ËF’Ö#D(¤ª6¥#hâ¹—XÈn1ˆ‹ (œ [ö¹|T+$Ø,YvÑÃQX€F•’ B_£;ãtG;ŠîÅå·€à6æb–(i¡R¬V¨åŸïÂ0°‚mþí6è%&"vËd´ð¡YݨØ1!*1/J…É¡¡T`á¦ô5š#€à?ÚË1FÎÀÖSy£ |XØÁ xñƒ!º%K#DˆqQAH ž’<˜¡H$AüSL>ì0’`—Æ™FP* |F}sI¢y±øs£À.ì4¼µ³T‰91`ˆˆAK#D:6›M-E­B1ÆÆ<¢ôâ(¦™*+NIç:Ö«Z*€&€ G Úž¨' 3…·’)1;ÿ€Úºú>ì|˜äîçô?0]y‚ºAÞħèÁöÈ BˆDC4Q@„ žà;½te£*©­S—t±r‹‘4'.Ø1Ã3"S…HPYsΞï‡OfhD @;‘OÝ*:“V´b2º)ܺí»u7͹º¸T0®HºaÒ9 Z¤4Z¿¦÷¯Q¾~Âd±’JISTÁ)B.#hDÞ2‰B#h*“ŠúÏT%À{_ùµLL?{03ÊT¾Ö¶ÛU~ÿk$o~=[sQA¶¥”¡‡ºì ÝïrA2ž³³W6IYU˜e‰™ƒþyŽþÕS8pÝ2Ee«#hnì²Üü&^ľZy‹î^„£“„á„ÙÐbJ}¡Ž%‰çy!•d9a~å`ÈŒØ2Cù¿ÁÜ‹øÂè*ä?A¨ðŽ¥¸?†´JžMýY½DŠ‘êôŸÝàŠ§®¨ª$)û±E1ˆI„|0S'ùá`„˜ÈD5#h#D"¹’‰JxÈ! ]1ó Õ2ªpH‚R `sdáä-†þ# €ø˜4©pí?JBfõ†©åSYl5¬0'LƒêY5¹ø¡ß•$QU4‰)Ó¯vx‡«eþÿ?ð#:#hjI.š`tÁOµ†âP¨””Pà$”\á²#håvhÒêòüËJœ“„˜É’ž2ÐÙ˜"d¦ŠÏA}¢Â‡UQÏæ7~a!ëI-·ô‚íB4Ñü¬_@isb3ÜÆßÔÿkç9ÈL´Ë¬ú¾±¬f5Ž$›e½\äJ>^ø9ÑöJT·ªiåýX˜iXÕfÓëMs® ”®*U~¸…DU–-~…‡OJÍšc±§¢áL†˜Áh"Œ…ʔ飯'¶(¡/·#™¥Ðj1;º„0î?Ú´p»#D'<êä>Ô#DRŒèQÈeˆ·JŽ%Ö*¦* .ðÀâŨúY£&Æ~X0¸ëR†PÏÁ —pÍÍÛ‘ˆ± âɪò“’W¡ìÀTÇ :ì19Æ20ýœ£üþ@z}?ZC¨}¤Å’eƒ=_Q¼H<0Âð0~úhžeL~9ªÖ$X¶?#:ÓQËüA§±¤#ô#öALç³qñhË,ÄV¡grY³ ua6Ú2ÇîÔùûôÞ»±£‡ðD¤s´4[áßñï5 òß ÔL! ùÓßlL\’g”g@Ǫ!„Ö'á÷F\d‚[”\í$ïÏcÈÞ:RfV·å9fEÆظôÕsù2Ùôÿ•8ËkúêÇ×Öx$—(л{yÍLÍiMY#:D:¸•Òï*) [ª¯,¶Æ®TIßaòÌì–ÙŒíéâs-É›Sáñ4R–ËÕúq»¢E°°áÃ'/§Lþ˜p”Ì‘—ƒ2#D”)¡°hûðÇì“=íÓ(‰H(æ_-’tÖEöµ½îQ£¤9àh$ ’Äã˜Ùb`Á”cXÓå‘­F«$¢¶#h#DŒ²yÛV’JÙq†Ãœä81æ.8Úm´´Ç¢M)È5#D/#áì…‰¬æuž|ž<"iÏï¢T%ˆøÁµĶqFÑtV7ÁH´Ê¬Q6ó¾HËåé¢ÎºlÑ¿m4V_×ü¯©u8 ÅMÝ0x2HÿV~Ë*S„Ó2iÏÐeÑ›ìaxî#è¡þ–G-¿¥H‡#:_Hj(!u¥àÖð?/üo‰#:N2Æ`$LµdÙ_ ÈóNÂíïXÂwMÞ^PMŽŠø8¾¯_+Kô#:ccc©ÆŽÌ‘d†ƒ n}+\‘¢MMVîÔáï„´„ͪ*××£986†„¶)b†ÐvË!¦¯[ŸdÐÐ`6ß–øˆ‰#DøN9q³U;qEÛ‚Îøë×ÆÒnà{Ì¡#óùÐ}Ì°ž÷0æœì~u_Þÿ[”m‡÷ëÔô` ”†:É'pöfÈü~îGÖGc îÑ@"P€9Ø¿°‡#:1ûÍ#:zH^©8C4›'dý„€Ÿ7À1º§Övd)"„õ`Sb`˜M#vÅ$¸s“nù°ïâ¡QЩ#hcHê Oüœö,\0Õ´©'l`àPëÒ?.Ô@1üú¯æù¾ÕðE˜d½Q‰QT«<‡Ý*ú=û8›ñáÖ¡?mÖ=\øÙ‘”+–8‰!÷5]$Ïöå¬qGÑ}«Ž7øµI8vF}ËxSãeMÞ7r0z”aç?^GÏ×µŸD¿É Æ|_&›Î®…NC²ô˜u?TùÃHê‚™ çˆÆñߌ‡ôO<ìªVÉ$ÖõªÏ«Ò`÷”ÈÑ+Ž#DvÇ%óÌMÊhÌAédo›P412¹)î‰^$7qªøroXDÐXÛL7%lrJó|^3kzãf7Yï~ƒöÉ/<­•“Q>)ŽÈ4¦R©¤3ÞEÎå›[N{,ìÈ`ÛdêïAê®j[ºc\²1ê°Z+å­•2Lu±Ñ®0nº}ô½ i ©’=#h,Ìß0¦‡°Ðg‚m=¤n÷¢ \rU¤’ A§%#hH£òº4ÉÇvª¦(§J¶ ½öàã:ë+UXŽ!鯳¶DþŒžþ“M|8B[`Ífx‰s¾¯£º]Òqìé\xü‡\0w3Db CU&I1Þ˜S´¸ŠdÔAÛ㢧7°¹„*/ž5úŒÁ`«.Y5ƒèܤôö‚*‹×Õ¯Ÿ[1eÖ­²–ÊÚ¡YÎS°ãé™î©'ö²#: ©¾ÆåBƒ_Kb÷›¶²6c`ÐPvS›Ä£àñ@Žårõ½C#: ê`}j‹úˆTaôŒ‚ò’Bf°bF˜¢e ~ ô/Ó²UúH•‹@ïñÙ‡`Ùë2ŽN#höÁû6CJ"ËQE±Fª,«[÷š¹%!¬¸“8ò.-nÊÆœPR…}DîgÉ=üï¸N3Fv 4“>.dØèü!=òäÐz¡£ÖÌûƒ¨º:*¨ŠSwô·?ôà|^ÀÇç>Âw¨”=Ð:ðMˆ[dñE¯ ©ÈôÓN$ÆäPGÞ|}ê¼m÷£‡¬ýß“ƒÙòý‡«ûSâlŽ_ø}šµõt‡¹O_Ú<É󓤊”5ãI!jŽ›éDðæA‘ˆcÔ<ôàg/aƒÈAÕ˪OæÎkXs‰§ù‰Ob¡ÒRà”À0$ŠEÀ¿VÃhSQHüõJ"*ª©*…‘VtÔø‚ÿÉ^}Þ†M7Ç’>$ûR>R(ðØîîqÈÌòbÏÎÁ¯ðØp]Ï#D/ßÄ"Gçê!¥1h™<_©ðõáÇ!ˆx šó à-‡Â@(P2A‘#h£Û"™#:4!“&–°2ÌD%  4í¬3h32‚F4–RUÙâ¼ðzÖŇºÏ˜q9I¹*°ˆö¿—6T»0õ'ÇÈà?nÁ͆Ö»Y*ÙEÔA‚£/èÞË  7ÞQ‰­vïå@²Ÿò7êÀ¢Ý¥µRý”Ðî#D,µ@&e¢PƧÕy½æ”à¨AƒÈœ»§M¡òŠ¢1ˆ¢÷j%I‚Ò&¥&5R ` v=H=o™ÁÏ&Gã‚t<0~“Í¢ q°òÿiØ×0:‚Ø9o碓µ™æ81Œd2—N˜u ŸâÑ£¦ 5td÷V€[BqrO`¹ÅUˆ8ˆãÔÇøÜ5tŒ(°ˆÆ1Æë5דAl>ˆáØm#(‚#: APñ÷rÂZO–L ŸRjwåDH؆à8b¿¦ sÔ¾Þ¢¶hòëlDNM™Ê|qFÇ9(7ÅXƒ)• €ôï ÖƒO¿êxÉ>ï+ ‡ ÍÀ¸>JUD¢aŠBB GNä"ÜL)7çï'Üîú}¥a‡:¨Fà·ß*Œç ýX×|{8ó5`BD:ίP¸äŸc#DI"´ˆ„8B f8Û@ºPÌ1„b"(BFö~í#:†â«f¸Ë,Ü´Ú¡*1£Ææ¤ö pãAçJŸŸ×ñ™3“Ü|dý,;Br_«Š-]7p–Ó\× W;•¶æž³(Îk•/•5hMb5\»çnkÝÚ¤ª’°¤Ê’ÊÊ!@0#D¯×´Ýƪ–ë'²MP’;—`¿>¯À^击ۻ٭y rÇÅêáFbFi”D®\šE™2Rnæ—I¤¢¿ÁëÇZP’H„ ^ÿú·w¶™éd%Gô»ˆ×ò»¨s”a06ŒÊ& f˜b¥(2›D¢Z"“Œ£C(Ó°É¢Xh$‚ ¨Šf¥?z÷ó[÷=â©â•ˆY)[ø.¹F¿¹#h/Ù—'÷´}Õü $ £¯¬ ¾ãëHC+vÀê®Öm”£+[‘cˆºZzZƒ[· û ‘R@-5¶’=ŒãƒñLÜ0Ý]ïêJä1õëpéö·ç+g`¤íÈãƒÏ+,‡¼úѶˆ“SórÎwGÇ01áT8Å"_¶´Ñß‹ã=F‚x!F ãOŒPA#DdA¯gmv®7¼‡åušÒ¦ƒÓy†0Ž+•c/sÌŸpA‰°I=ÖLÜ—¥4T<ùÈšµ<C)°OHJD‚äŸÑ ý§qëxÎÆ›16@ÐÖvþ%ÓW×Wg§T} öþÛÓ™‹ÿ+"a§7”ƒZa5+ŒjCÿ•imã+ÕX:Ò6Έäd˃‰5J0l?SÝ7T›…1Çõ¾]9é(Žu1k\ÇÚ7¦ÜæFˆ—üyþÞiš" 6íÛ‚I#:h#hY*R@ýHa RÖ[S%XÚiΈˆEbAAŠuvîêÚátwIÒßV_“^vô.é4°Œ¥JÐËcFc†Á¶C:H†pôèÅ°ØÓi6°²K¼PÆÛÒ#hDxq”iä[¤#DŽ0üè•dÙ¾3Púˆ(1A &è·CÉ,UÀ‚"ÕÇÄÊ+#D<ÆàÖé1õ4Q|E9‘®$øM·Ï™†ê9A²äÈ;|Ô&I3@ìZ5!…©È(f¶s>wAg'«#hMuÛÔ2rFff©†_¸V“Q¤†[bÆšdŠ6ø䚣_¥s.{Ý|ïï[îÍaoAˆ8Õ¹v`n1‰¯×‹‘¢³#hPÁÒ#Dl¦‰Ì^ѽfÉ¡(H)zLTkC½)J1 ©)R–Àn´ñ–¤˜ì€Ó¾J¶å°•Í»ævç®sÝã®rmîÝcIvî˶k‘×ræ·0Þ¹ÞË‚°Ë€²I¦(šff 0•hëdO9ÁÀfúRûÝ»/¥}<úo·£)J6Â3Q¨“m#VË'#:bJÒ"9!J—.µ™q#hèÖeBæ·4XŠÞýŽÝ¥¯HÕ"fØ ¥Qm¶ÅHê†A‘Äâ•]zaÈï'ã'hëtàšÔ„d¼×³S JDl]#h9V°Þ-$ñ˜~Ì3ŒL¨@æ2H!å»l[I&”×›j:òì/1綦Rÿ{Úê”IoÙIDTI!dKgw5ÃSHˆ®íÓRÃ!úf+E”Ôi‘ÖC*öÍ»dv¦1»h‘ŽW…ŒRrèÞ5¬¶ŠÐ$–Ù 8*„fuÖÃ^Ù®“wèxĦ)ˆÝr³N×ËrMèC‘8“(¾´åÜRBFÆÜŒ|èšvLõÃ1L10Êþz˜Ú!éV‡fYô ò1CÊ“p# Á¦3p(Pê†&Š0Á…Æ„4é9¿:5P4­r V"lãU†é¨ø–Q£È}¢¨0ÚÐÐyµ†Ì4:ÖtËzÔ¦’›‘5#A#hÚ4Þú^MõÝ_K§‡f îöÙ^Š2LÍ×ukÍÊý¤>Ý´jq+gŸEÍ91BïF“J²Ñ† =ÏyNJi‰TX( †ÓLqÈe†+¸Ö³æ1›MEa!háõäMõzód¥î·½®YõÚ Ç3,u:4æó{jµªÊ¶"Œm„U8´#DJ6TâŒr!*²۱¥‚ÀRZ#hYa¡p4¼‰aX+±¦IJªTtihh$É€Þr𱓭ǩ4öÔ–í›ÐB9s3öBŒ‰­I€ÚÛr(HÖ™04®óàÆ:Û0z–k+ÕÓx3PÓ³š$eêf(tOwRÊ¢0U5HB0]XáÃi¸ì#Då*­"µ\h¬M×3.Pt¦2d2ÂåÁFìm¦«Ý1#žF…z¨™PTÜÁ±¬œ ˜Å7am¤MZ¨ PsE0rT4r™Îf*;0“8™,•’ͬVx¸5¦E×1a€FC\&Zv„¯OwÊ1ÈfãŸ=#¦9¡±Qœ0àÆ“"”Žñ$‘#D†dFÊF1ybMÁ Q­KÉF%#¸JŽ #hÀÄvE#0’ršƒKZLU²±”LLx,T+y¢( ˆ» Š¡B!†Æ‚dŒÖ™±ù&oYHÃi¢C†*4DÍ_£œÔ’8„À²#:j­q†È ×SF±4Ž¡UdÜ­k½1o“tò×âåyʹ¹±O19—ˆˆx©h8´2sÎq”E¾-\Û–f¾{«^k'*^¡F#h¦‘ ÁcF÷d]æ(¦üµlL‘4Fm«Tm›7­<éws øüw2UÓ„!parrfEZ¢10 d2#:Ñ9$àh‚› Žtso5B©T1˜N±p¤LlhQŠ”Ñ¢”¯~´Žò!¹r–ÕÕŽb¦1½h0#DñQwÔÐèÂ1Øwr`Ž‰yÓŠ† bE14ΦÑ,ÒL6(@ ŒK¤#h#hTpÐr… @Ú¶ ¦ˆÇYMl•äp`7¦t Á ÉJC²pÌLÂ&X“—81}$;Ý?sü“`JºTþ±øÑñ‹î\±]ã¸nØû½tŨÊS7„?à ¢Ù‘Y™î`2×ìv)x#:ö_ (B& ¤mb*6Ú*Ñ°•&²ÙV”£j*¢+cU”³2#D) °#B!J(@@ERÉÈD@ùx¡¥Hù„m4„+!Àªÿ!:ƒ£D'õ0©æOèÿ$©#Dä<¨µ"·!üÞížB¤JcÊÂ* 2”L .:›_]@”Œz1h0Æ@øYçûùê"„(¥ È&€0kUË»³ºåeWK_’¶–©’¢˜T©„‰MÔIÚi}šöþ&&{7?ºè$¼m«þ8ënRƳ"ŒDfH•/Ó„Z}Û°‹¯ò7'#:hd‰ ¥ E Ó13Z’ÚÐDmª-&¬ØÈÄ@¸Q#:7NO;ì7µ©7Rљη1¤Äkz»j¯¶/t±ÝÐè1$†p¤(ÐÅB#hæ±;Z‰1Ì¥ÞˆÐÛŽ ´ŽdNÂTÚ bÈþëít8dì×é®ôeÊÓ#hz¤@hp_‚ZOÔÅ%›†b”På4¸b½ç‡‚q³ðéýr<¦µ3ka~3ha›Za&-#hÖð‹ÝLæ­Gé#€#hM‘‘ÈCVÝÊù 4nÒQ"ÝžE­ç]7Gêmƒ¨˜&’BS¢ŽbÐÐÈÙ™†(*µâ{¦9f*ÑF”(*Äb“K~ã\`jFZõžgßÆoçÐ~ðTÒv¾Ÿ´Pûq`O¡‰&¦h‚Û[ÍʲD6̤ h ¤Ðz{úy&×ß”E0'‹/"‘/×L«#h4F Ó—ÞB½'Î"Ý£¹×îÞÎæ)ö÷?EPÕ"ž(dŠ:ÕVäö±´¼—+é‘OÓibd,ÔÌd˜Â4älHï‘ãhth[¨Ñ¡´\dD¹+I£5L—Vä4fˆ‚$ÀïèÃ`5NäéÒƒÖkOŽ|´ô?³·ß~ÿÀ™¹x€é(Û§L#¿¬ñÆß0?± ~FÁŸ—5yX*>Ÿv½ÆߧâýÇý’#:{‹N¬Öü•ñ}ÍÀ¹Ñ•'´+NSÊäÑ€cwfË<%—'·î»!é:d·zòJaÛä÷=ø Õ:î#:Tê’~Á…æ¾zÓaƒÝ´È¼?1÷„36}#hüànDh\¹:îäìw5Ênîå¢ÎÒP Y„ a.&}©=ƒõ@ݯjrï²#j!弳ϒ#hjZb©ÙPì«#:C&u„íÎ)ïíÛ5©MÊä ~T™)ïÛ&½ZÌÔažý˜« ´n–Mf¥ ÜÃ"›$ ‘¯l#ÇÍP=-D:çÕë70Î(ý^ßÔhßO‡C7oìmø¢G"UB’ŽEÉc²·¶C©î2¨}î!IsÎ#h¾þáùpMÀîÞfAÒÕµýY209Ô8 R*#9ŸuÎGO\ˇp',ìµw²õTÛ‹½˺Sr¹!BÒq.@$JQc£¢¹ÏÁg»XÕ¹¾ìúþÇÕöz;„Ÿ]µÒ™Ë\ªQFbåÿMBŒž‡3'n[©·J®krÞUéIjö)u­hrUä”ÕJnV…¥¬“%0”ÉPÈL X©\”ˆS%bŒ²ÿ±à…·™ šé¡È ÛüÀð~>¼Ø;”N4úñÔ“„»PC⨤¹'½ã †‡—6´ðJlô3⾆#Çã>ãíÑ@0î%ÍÚ„‚dJH¡„ˆ ~è<n", ÐûCñ!Tv=pGí#:?_AâSºJà—'¡b™!¤ÁÒé1†NHžÜ±÷“ÏÝüýºeøŽ!$—«á…¶!p°9h­ ­"±/&LQ#:1(ú”CæS)ITŒQÒ ë==üúo÷ÿ#È%þD•@!-¿U·ô|%ý[B¯õÌ0lÑûç`ü#6¥*1Qõ~Ò¢ÏÕÀKŒa™y¢jYDL­UhÉ×ßYr`æI‘"ã$'žjOËX £3+(i /¤ØgîÀQÔŽÁ•Ha&=ô¡0C9À)‰ V…a P5¦B´…4¨¢ ‚¹’Ô#:‘$/ÃÄCth6#˜‚Oµ÷µózø¢ÛE$U¦™‰òµzC©†5©!ô!?lT–±!£[Zlck¢ËHD)BÒª1¬›b±©•X±Y&kŒ£#:4(ÿ9ñ=Ÿ'÷¨ vT_㶑Cþðª~qTöý>_ËX„kY÷dñùa§ŒøÛv!ŸKÖÊnÎð5ô&¸#ŸÂd`=Y§ƒ,pØöÌ#D¼ÛÖ¸§åìP>¥%!à`Ž€ö#h ½@MŒ…/ÓÕ¹öœJ:Cô(ÿ´‚´%!$# 'Ñó=<Ó;Ž%òª‘ )¡î#>]‡˜¡þé÷žÓ¼üÞóø`ïÅ'ì#:ÀÉäR@P@ñ‰÷-ÄVÜPM"{mWöTÔÍ›SIfL*L’P ±‹K&–&ÆÍ#D©(ÚJRd”‚©&)µXZ*تÑU¥MS`Z›KRJÆÄÂJ4¨P´¤H¤6±w;>׆BU µÉ“n9µ´¨Ò ¡Yœ¥nUT6’H1Þ»owysw{[ÝÚ)1 çD(áZ!XLqc14V¦Z#D€Ù^;o/MtÅêõ®Þë‘åfë›Ie{í›0ÜM™oNôaeÌ…h„#…#”cƱ…Õ¦ ƒ»ÎîóÁDøº¯Ÿ#×¼Ùw‰#h#†iqt™š!­;ÄÕhË»'tÜ»|f—9¹]Ú½^Ý^QM:1fµ‘ÆÃ#:¦Š8gˆÃC-t­×Z†Y›¦¶U×SNîÖØÆ´›:ZÕm[R#:)†|NPMãÃ’Ô[J!Ì°s12A2D" €ÅË`#hþ_àé£a$‚P*_F ¡-Iåe‹0•f9˜˜Ó•cKíÞh˜ÕËàÏv¶{µ]J©ªšP±Ž×;Þ‚„¡,±ÂZÒ‚@¨AÈIÍÔÀ#:ƃg Â0¼,ŠÌUz}1ãrd#Dû@ØHwøøK@_#h÷.6ñΗÝÍq­«ÌGñ@–’™˜ H EžÒ^š}[«ËÁ“„G‘±–©Ö°ËY*}”.b‹NÃ%!ô‚pÐhÀpR…Û#D/5Õ$ÄN\µšÖ©{óÓ\Ea5j1³V¹#hN!OË¡*'¥6>v@»3#DÒ,?7–‹…uƒ-Œ_ý8¦Æ@Iü¸'I‡m­ö4*…ü‘iQwAÐý óö'ÌSèQ€÷åk È2ÃJCD»ºƒwX^ñ¨ÜC$ŠYØÉùZÀƒ3äë‘Üݦw=ÕÉ9ÚÓ˜þÍfÝ囆q“'E†X9^äk¬f“|²êZL¦LdôéõÝLŒ‰˜™^ÛW-UF«j¦æFE!1°tº%œÄ8s_c ?ßþü Ð-¿ì_üè4¾@}…u4Rw‡y{‚e~é¯ËÔåÓ Á‚eÈÌwÞÊ7Uoãð1¦³h+ÙìŽ{™•cÝFBJ½n{»|±…ì­O¨³,°¨©oc£u´t¯/i­'ÊO2j¦¥æ¬S$†„›©â§hÔ> t¯:œ™?äg&PèÛ#2øÃÁ~jæ8—ZSäåÓ”˜9·ž¹`÷OÛ=^òs© šé5Ø2û2i§²öÞ£Ñí›ÛÙAûBæ£Ò˜¸šf˜;pPŨŽEDÊ®c®_é³Ýk3þ—Š½®I‡õAÝa&]³>ÛúÖê–ë‘ü£^—˜“u8qu|3_›4Îs a¤¡ï´FSáÙêÃ}ݪ]›ã×~v泉Ëg¤Òɼ½=ncžñ-Ôõqº­ñ/¿Cª!ŸX½N&_)KÝFïñ¬Bnrò³—}\fÜÙ+Ö\¸¨W¼‡ Ð¹Ê»àÛô…7o£†ÃHLY,qˆøt¹®•Œ’2ô!å¾Z¹“2óŠXMg®ê-Öõ×~¶98X¹‚¸Þ&£÷ç¦î’<3J6æ³y2ƒÁÛV„#~Ĭ¿;™tãÆ·1òS“ÁÛÝÚ®Pm¢Qì‹—QjR®tÙÍŽQ!;L#h…,!y=¤”'I#hÏl1‘V§‰©Ú|ºŽ]·œ.hk€ªõó]Öäy';òvøV]ùí±Yu"['2÷×ë©•V½Ñ¿køx~òÏ»»l³cì)7s™Ä)qÎÕ´˜®÷ã#hÈñvŒøÚZqIÖ4aryS§Ëïݯ*I˾Ãâ•YeW&"ÌõçfúgÒÜ„úwmÍÆ,̦»-ñN) "øïFÍG!î’ùô³Qž^œèçIÑ9>ŽT­>êmÜZ©¬½2wÂmeö\h|¦l«ç­‡À‹e8Ïü·k"¨‡±ñ#Dòћۨâîñ¯¹jîÝ¡X|ûWŠáXÞzã~iÇD´™ì³çåRcm»†Ë/âÔoÛzc•½ÁÁi±uÅñò9‹eɻ룞.±ô:G¥òÝŒ™oMÜñc¶ÒþNðïÅ÷TÀÞnæ¸%fñë­^‘“eœÈö?¡¶|â§Ò.¹&O¥„—œƒ–úQW~Uë,ç¨zsÜ•ïÝæw}2~Bw«jX!²kçÛkÕlf]ÅÁº8Z ñ6£IJdœÅ;÷êkdBÒbDñ¦Ô%"¯ŽeÍÜd®èvî°0c‘6ìÞt%B³nwœq6øŽÕræ¾¼Æ0 Þäö¢É¦³-§1ç3Ré±r¡svB¡’¤Æk¥E»ÀàÅîV'­PàcXàî))aãAš|!½oŒQTµ©Úœåõx‡Ê7kÓÓp©’ÙÁ´ I•>.sN¶CÖ°¯|ªêc oÁ†£d¹sᜠ»<7n\ÞKŒKïC‹W|áT£„yÄ£ʘØ[sq&n:ìóÔyA„׈Pim#×hdb`ÆÉv?"‰½ßgsGKx”\»ÜbÜwCÑ.ðü<ˆ—ÒšºÅe¥Ö5-êž<õÕ¼›Ù²¤lÄ?`¿*áâMyHÆHÖ_ÝcL¶ûù—:*2¬[âšÅŽ‹1ˆÚcŠv1$­¸>ªj‚‘懜D? ^ÍÍA“ŒL¥œø¹&ÛiÅdò¸Úky0C §’†ôøw :Ãêæ%|é¤y¼O^ xAšEõ–s¥ªÅH%‰TD6;®HK‹2eÀ›„AÚ®¦žl9ƒšë{ u)–kP:”í¦)fŒ³2fæiLÒ ázÒžÒ=P8œÆä¦7~Ö3Bj&3FÝ·Ç ýæÊï§8á_u"RcžÆˆ;k?JLîEòæóâ¥!Ö›gÙ<è!+S¬€¡Ì˜•aYrTÍ»Æ ž]ÝF½¤ZNú¼ÜÝÉW¡ ™}„1Ä™A¡A9ˆ=“¢s´N*Æ!k½Â#ˆr‡tòÌâËéL*¶ÂîãéjnñŒº)Ó¹¯hIXuÒ„ÑpÑF&ú\åѾ<0 á• ‹8ðcÝ< lͧ/½ôYõ¼ RqM\McÓ­ºÖï¶n‰@»^%¦#ñ$'M²KuŽ–Ý`FnaÕè§jJ¹|Uô­’f¶ðÍ…*ËîTp›dBI¼³uáêÖ‘H#D 1ô™wv›Þ€;UM$ŠnÝ9‡2CS&üåŒÃ±JPQéBNpVŠÖÎ-mu{™ƒiÞÒnw¨gÒ™!,ºCNî—gÒ¤xÆË « ¶N»¬Ø•áû=F¬é4£¸º§vÓ4äì©õoRµîƒ(Ò‘¨Dn)é“ÓÒšf0à‰AŒñøó]yhSŽíìÔéEEEJ—Vî„ìK¡w—BGa4ˆBI·øÛ¹“ÖéjΣPã¬ÌÀw—7F=s“²o*NØE஽©¤Ú†Îz®ÙCƨÁ¾Ø9¾ÁͲ„ñ.Ñ”:­£¶#D¼°¹4ãÙ4iÙÃN·™P÷î úr¼oZïMÛ±˜òs·HŸŠ˜ZŒE¢!1.–Ðü<õ¶ûg2µjΚR Â³F¢³JÕ±#DÞº[>lÄb´efeÙLJd·ðžy‡íÓ×l´O*%ÎTh¢ÅEÍÍUTã´5a5c—ÝÕ¶wÞg&(]²³cån‹Ÿòy¦9öÍs`—aú}ææ£ÁÁ”ñ<¡#NU:g%ñäãp®3´˜éò™[ËiæPB1Tä¦<øjiâšÝÓzvt̺”ÙÚp˾åßY5#fd«‹ÆjêY]e4ûRQáü®kBPB‚œréá§-EÍ]ÒW³Ø—Œ$î:ytÈÖ´®Þžáj®iøqCUÃ;ň§u2GXzV…™ÙmO튊ã|ÔoIÊbÐä©2·³Æµè6[\ÝRnˆØŠÎZÞgSH‘%JQ¸ŸNÌ„1I¢6¿h»+|A‹t@îΧÏz.²Ã¾ÝmùÛÓé¾è[2nÈÑu£»õÙ½ñ¾Ç­ñM§*©Tò²°6²Ó:4Bî¸N,ÇîPÔªâZkÊf÷mõµ¦o¿–L ç³~ú#h»B …˜†¼¥ B‹3gÈÈ“”¸¾)üéÈò¡âSñr'¶R˜ÁC†³“¦64³£¹ÜâÒ- @ö2N0­g¦·ÄiBBÙyí‹—Æóh1"qÐêu»DW?+,Â9¿ûÈÂ(@IßQóÚ–à|{mٰ٬Þb)| W¹œ*÷@%Bä@ÇI€$S²ÕëD±ç£Ê1ºÙˆqc…¶\í®*@:• #h=!:‚¥/.!'QFƒÄ˜W/Ðì¼.`M˜]UáT=½nÈYÚh È8Î$EfÃyPp8¿‹b-…],!ÑÇ ˜z¦Nž(àò 3Ið©NÊ $ˆ TeÕ£ï‹Ú!´¹YnÆÛ·ÍhÕo oëmåZ†rЂâÎGÔHG!ÃôvrÔÒ]ËvUG…k/ÌδBl¦—v±²U¬éX»çM¾±œ?;˜·í.WÓ»ÂÏ ·i­½1,lC·/i øzSd¸E<ê"³m#:ïtOÚG80mSáuÍWG’FxFÏ­±‘u¬PqòÚ]¶–i$“3ÍzNÙ}ÑÕ°Ep«eº…#DÆ׵r^ï=¨×ß½Ú ¢<õ©,u‰UkKQžl£È`Üë+m¶™$fçõ~±šß:¼;X“Ƽ±¶âÇA75³M,$Án I›aykmKCíe pîo¹¼šÁ„¢%«”I¶c#h§ZÚ#D*ÑÄâV\³49FÍå–a±€Yvq3>L³<Þ¼qM¼kO²SRµ;Ój;™(ÚÑt-Ÿ9¨jEU÷(Æ&dÇ<ýËŸlÕk”¯¹TòI©æäÁ6ðš#ÇSÔÙÂ}–bˆI]ñãXòÖ–dÇTH„ŸŠ%#šcÖT)ç¢ÑÏ#¶¼ù¦ºñÅðàÉ|MnÓ]±30ä óîž²«Ã¬«ñò%„Ú›#hÞüsQ¢ ìÂ¥K¯[=€bI‹ni2˜‹¢b¹Â®ÜöÜðÛèeCjËG:ÑÑ *ÑÁC€Ææƒå·’ýã9˜Êøb*ŠôaÆ8 û·‘½j§·»y×í®£Æë/\¶õÎÕù"RÓn>¶|l‹Û¼ÌJ>I¤Tâiö˜-ßÑ“^ VééÙœQ#h»âù)Êù´‘ɱ¹{jU¼^Ûß5[š3ÕË<íÔæ¯õtÆÚUñò—BuðÛÀ‡{Ÿ6¼(£Í…= È{ž£ã•è>Y¢˜žüß­#Dﵜx4¶-LJ DHŒÑxéo3‹²‘„!!,i[A©N\Zm‘—B•&bµ®1j>þU ;UmOÀŸ“mÈßÈbvòÖXëKߦls–ʺ^x²ˆk£(¢²&>ýùÒˆ³]ývŠoEéÓ™ÓÞt‡ÓO§þaÉ;e@;˜RY.|UIæÆ{Ìù}1ÕwÏ2 ¢XØ¢#DœÞ#:ˆ­œ'9d¶Úªk¶¯Æ£c­ÂfÉÃíZ^ŽÛÅð@<Ãsï«Hú”¸f.±Óº7ßjy†m!dVo§ü~íl¹útn$ÛûýÊ¥½ùñ JdŒÈát…nWáÆñêá;'ÆϬXñœDYXf”š%ƒO9óIJte8,탼]:&8ÌÃn°Çn]¶³dFÌ39 ‡½“ŽÂ;q°88 QZ|íšCf·vÙ†œìè÷ÝdÝéOtÍÝŽ»_rLFoÖ'àyâÍãaªÈâBH§lqƒÂÅXÅ#¹SK(7âÛ#h(#ÝÚ« ׊Vñ”¼wôHhÄ>‡¡Å^Æ#êáÆÖ—-‚ —㎥=ü¨>ÍӴ㣿 î#: 3¼7ÜCré`ÀqÁã˜xn¥û*O/†ä>[ȸYbýYǧWÝáÓ­›;yúlfQÅç<}:wƒgªåÊq¥–#ˆ6#D;7I rI D42¤p½” bš¨e<–ÌûX¿†œT ;XÈ›@ ‘D‚5žÚkóÓïë϶1Æ“A—`9†öŽí»I\@$õ+ÓÍãŸ=,½½Sרîª ×SÀP¼#D¨#:`”8=-ì㦼¡ã”=à!´‹Êß8„´pm$”È—c3‚œ+Ù?}Õãc×Tz2Pª‘C˜í—á îi±*Êcd’N’uõ×·J£¨$ö—ØEˆÈ›Á Íùµ·Ë¿MÜ݇ùy Ç~Ý÷#:¸•9Ádá(Èi‹¶˜ª º'("Ï{Ýw›"ÙgµØèºù™MÞ#h)bT¬óV´‹U¨>^ÛqèÐãîhÀÝ—”àã¿;îÛê#njòeïåÄQ®ñÔöDp‡ñÌÇJ–”<»2·hæ¡èrŠÁUˆw™»ª5ç\sœÍ†LŠßÆÝfií¡26ÓFÓâ/YÄÇ9wÛ»ÖôÍ¢!Ó¹ íú¬çF—­ý[ÂP†bÐVSU=Ó>ó#D)úÍ8…Îä·¡ÝÑvdáßd‘¨gt:´Ù•CÄ•r8i¯´ûü4Y¸alÜÄ­?~ ß» ½#ñí½ùìùc)L½NT ž®äªÝ::göLj@31°ë/ìvçi‘ïxÑBÔ Õˆ(vD#c«XkZÖEÔQdz«V±&D;S¥ÂtÌ1*–uÐÎ#hãx׋Ë*ÓžßÙå{džŸè y-ø>W©Ûʽ£j•Z7ó2"ãÙçò8=ÞÛM8úŒ¨Æ–樅¶ƒV ‚‡Mƒ÷u¦‰+/}áíu½œ¨É|x*Öé³ ¸?£8}^ÁK¾`É¥ÄÞ”1PЙX9vÀ»–VM¹Ã¼QR|*§ÉŒ¯TXx17Üs¨j áŽNaŽ$ãµ#hqëÇöt¸d\#ÝØù$*ÝÀbÌ ëŒ7éw1‰¾K–`Ä쇸§%° äd8eÞ‘-»n@ÍçF²ƒ& ö>yÞ_#:#à; tÐOyÒh#(uõž^r jVé#2£¿A·¢úLs±­£ÏKòHƤlç¡ðSÍ@pFn*G’*žS@Òð‘EgmµÂAßÞÄD»$îè\J‰ÐêŽøž!ÜxäÐ[0x0Èá¿Ÿã [ö{Ö;=Ç›ŒD<¹P-ž3ãÎ-NϬDvU®»¿eVæÈór·ð„1B>›>¿ÑùÏThìÿ¬Ïrf"a& bOÁ'åa>qüßogOWkçO ‰$q•WJj<$ò#Dx†Žÿç|ú) ×<¦HÄ/±‘ Äs#DÅ°PŸC°ˆ)[ Tg>­qqZáiñº‡õÉÏ;¡.Æc¨ùè_×(v68Ì E]:Yï@íè¥øA†ïÑ+rœŠÃ÷d£Õ¸õýèËëé{ÇŒ#hÃóÅ IÑ´«´ØÏ‘"J9=ºÄIÉBì•hÚ Ä‹é{ö@0ôóò? Â}_‘=|>ÉŠ‰§íSàúcìз×}ô÷Ýæ»úN°úïF$>wè «“ìjб´™ÅL%e£#h>‰4ÊQL²Ò1dÓEŽ »Ê{)VDÎì´‹˜V˜!ÕÐBF4ñˆu‹2åv-;¤öty¦°Lâì ÆG‚2ã¸è:@ÑÂÍê6`¡‘qxpã¦RV†Q«Î×¹ÝìØËk$á^]Ófåsv‡,vQ…j#D@a,‹F3Z»6@mè‚miYZJV@emXÔ†Q‰D¦Ha¼ Óopî@Њ×/+ÓG–êºY6Ÿ~NÃ.÷‡d:@Û Â\Ø<À‰©S%ÊâGP¹!U)É\h9yÂÑ“#:pI×zN×@‡Š¢6Ž iKŽ…ÐÆ“DiÒLá`¢®!Ïv ˆE0št£[|#DçD ™¥·c•àaDd9/‡iâA±,`÷•yŒ[Í89#D¢\°œg#!é’`ç&& ÖÎ:¯fnøÂü3©#Dž$é? ÑÙF™î@öà!ÏÕˆõ)¨š¢ƒíœ€¦ª#h% YESúåJAJ$ #DNJe‘™‘'ò :ÿ^¸ccY9Y†Y #D…­âê,¸ít»·/NVé1úèÂ"¦Ý–¬lZ.å~%RÌH4àÁŒ$‰;Ò§Rzd`q˜ã^­å¯Ç×WÓk¢t¤Ý7á:ÖbAÆ8’´\Ù5[Ö^fXa_Úªº‰2PÓ‡:ʘl²!ih¥ÉÃ1ɳzKE" Î¥sÅAYn4n\5Á‹¬´:…¯™ MvfWlz…4ÎÍ;íÚM+šúZ½ZR·¿'­-fd%™•% Ž8Q²2çzö¹_‚½‘îä¤Ë$Úã.n$ŒÄÊÌË0Ì©àÞ%&äÁ7(ÅàœÜonƒ0 À ¤ã ªému‘X£lƒÀ&á1ëXòð·bd¢è£5¹J‹¡P™AB#­'¥íÚ»MI‰“^t•3" °Þ´£kd`4²†+TéJëlcÓhJ4«¸-ÒÉÂÜ׎íGÏ]¯Œë·ÉÊnF'#D6IÍØã©rôì#:¢£’‰ßt'3’§õWø›TÞÛ¸ŒsP8)e ”EM1 -cDi‘DE­U¦¢¥J‰±ïá‘4ÁŒ˜šSíx1¹ ;ZÁ–.%Ô7qÓ#hHM`5‘YA<…Ù>NÍ5ׯ5íŕ݇+Øì!|ªTà„;A} ÇùÍŠ‰±†˜;´ý÷)°Â*JqÀÊŠ$©N]}a˸åWwpμÞ{«»ô~ç$¡yW(&¡TÅEFP¢PÄ”…!Ø‘Èmaaj;릱³’ùás'Õ2îáPF5iétÒIœìÌëÝíw¯ºù|œŒY†.9——\w·#hC!¬‰æ0Kí·Ut_©«‡Òú½w8:FÒŠ-ô‚Å‘±‰º#D&Ô)aX¶Øž8,É)%±PxÈÐÕla²¾ëË/]×wD).uÔᘛ5ŒÑA.ªUÎ Ü•h†.î•K6‚HY2ÁWŒÒ2ên¨áı‘•'”Š¶ ¤ŽAHy»RïÞBAŽ¦”Ê£M±”PsÅ`:5:‡” Ûã‘ãZªS#DbzóŸ“£íûQ?íûlÌŠ‚’ˆ’»‰Æ¨’-šu¬„?•©ÑO*ìõ]Ú‘„Û§ÉAGÎ]…°-֔ɕ6¤ÓW¦ôóå]îì ‘‚ ÌT¾äÊS´Bî¶:4DìmÈO¸™«¬Í0x ‘¢:1ƒQ4;âƒn²We„d²Z0‡Q‘\#hf%/Ú½{Õþ#:}æyᇰ»ðä'ªN$û 5.áCÚ }Q%1Bw˜ u6†ëa#D”(›—h ·×Í¿?¯Îd·ò½¥*ü¬)ìnP2Õ1OÅPPºÊð‹wZç'ÊbôЕ߉B¯ÔÉéÊOP_«fH~>åø#¯Ì"9UCË÷ø}®,Ä¢¨’˜ôCÌ;%ñr&}BpCàZM¯ÔóÇ2pÂŒz…ž5fÆò3 ×3åE·Í„0»¹#:tgcI42øùÃñüôDm„sAÑœ…Ÿðô›Ôš=•–K)dm‰¶eT®0—+Óß9½ÎÜpâ#D}‘_©À1…FK9™× :kî½OÊbµôÊú ¥ú£âO•ê`=Å'Ûcõ!ìg¸d½“ïÁ²n©ÅQ#DΆDc˜Í˜YWv”t3¸1$4tëû7Єî<û±Öº9°:=/Ðf¯’ûjRª†XE¿o¼ž¯á÷E{º•#hf¾¢MõWͺ¡Ç>¢ŠVˆ¸nbØÄj¯K[š/-Æ6(4¤ˆ-µ’I*0M;-ºmu©Rš¬¦Y1ÝÖôÖ¹¨«¥q<Õ®VÜéZÉk^íºe^ì\ÉÒ#DÝt•Ó«ªºîܱQjnv·tbúÔúš6E'"}[ø—kœ¯ÛÌØœ’YÝÃÒ§óøŸ|´¿ª­ûüáÞÁa¯ÆAˆ‹W*é˜U ,Â%(Ij¬´•-±kY›XµXQ‹@MD£5-0›lm³#D6[Ái ZÓ)›Q&²XÙ¥4Ù©MEÃ(Œ"›ÓIŠ4£R4¤*1¡›DlD¤bj2¤ŠeJY%6Í&(È!ÌP\jAÿXþw“Ù'‰ô:Þ?~¾2È"ü‡©ùSâ‡æŸ3>³Øx!Â~o’sØ’dÛÙpNLyú{ÊcötíÚ¡ú_¨’G§!Œ\æ9ž­c5»#:‘ ƒsF#D/<·¨–r1z¡úÒAG=S¥*T8¥õ¦qG„"#hw¬Cýó½žÛ‚™øãš¡ÚpUK #hÉ Èw‹áúµ®üð‚ÌÜ%~ž…V÷É°ê&ÓfñÆf€¥yéÈÖÕ¡Ù–2Üã§DÛ˜KÎ}gC¿JW £S tDÏvd²IMM5¤uÝ©0ÁÓЯßá°r¢¶ÂêŠ(e5WgðLŽ¥j&x×6©÷C‘{áÚË¥êÌ0Yò2²Å›/‡ÌèÙ8a™(¥1dÓÐ(/+ìeìû ‚)ˆˆ Ô ô›0ùaà‚kƒ¶3Á#:‰ˆÓØ1ú‚€(¦’aK*b$²Òk&Öý®ëeÚî%b2ɱ•¤ÕL”’ÒÓuÖWwM³»] \Õq,ZM´T ÖDÚŒL”b,E¦b›šFMFŠ3»š.§ ­ˆÏãØà#h0¤hÀTÉD°(Ëey¨Á'­Ûù}µ\¢YFLPM@£F´“[í¿©×ßm¾oŸh.åÜf24)Bªd–Ä~x£­ï#:þЩ4öà}›5ZPCŒÜR#D6*Å\¶:Á•ÂHsí“ÍRý£â¼ßVø†ÒÛ>kÌk|VìÔ™‰®VâböÄšÁÿ&#:›?Æçw÷ØßN$b•Eø¤gƒ«g#ÈÒzœìB²Ù¹qéLµèÐÖdoR,È–œpÌmC0–æ1¯,7¦I¦SIâNˆ#hÄ!A%x® ÃÝ­¶2š°ªï.06<(Ý¥Pã[7±ê4B`ñáp•Æn+þzeZ(Æ߃¬£4˜±é¨µU­ZAÁ’µ5²¶›p9£dÆXK­ÆœcÖQÔ@Ž¤#hæ5°À#DÊê­j÷îýµât¾ËÝÛ vwuºbÅq¬5%¶0ùO%¸$ˆÜd$KNU†f?o<ëŽ,2{t;³˜.L¡`X#AÌÚ„Ñqk0Ü®y"34 dc»"a(<žö£ÃaÄsϪ9','óNIHv“•¾‘v÷­}›ËvMÛ¹ÒRÌÅÈqÚ&€Ñ(:*©^¯qÛA±Ä¸D:B×A¼Ã¢Nj“ÁN¸™Ø…Üq™Y<É™.žTÄ9x\XRØr¯'NÙÒÉý=ÇÛÏo¾*¶ã–IŽrÁµ@_$ðp@]'ã{óιGÓ•õŠ§zów¦8um*WÞ°c‘ÚÉ4‚#DŸŒ^ê ðTÛç#DP#ø>ƒ?wï``z|/#>ïg l¤ØŒækú踒ÜWã›ÒjæýÛ,=0MÒêÌ‚}ÙñúO˜&&¨NPñ)N«^°—QA\3>Uf@Wásƒ„WÛP©#h)BPši¥M’Ž0é–Ç‹¸KG,ÊqÌœè Çá0 £ ÃŒ ?2"~¨È}ùOs²h‚é)‚õWzצ“hjâL‰`r©ç,ãõ†Ýߘ¶> g·¬/5¦î¯Jת{¼ñóý³å„FÆäà']y´u>29y>µt#heý~½w6./¥)sµ€x¡D(sÔû‘ÛÀeT{Á!í˜%æ:¤ÕKöû°t€³ì#'D¸Çé•wð¹"Mšw_Ox‰õ^DA$?å…L‘KöH{ö­z½øÉ?U4a Ç,#'ÐxYç#:Óáš@:„Šÿ;”¯å}éîM{¶†”0ç±QäßÊ}uU÷×Yã¿ÄB¥wê[öµûì3"lÙ RfebU¦"&CÐœ_y±ÿ/C^7+ÝáïóáíÜ6ì<dùþåæ@è;s!jDŸ”@õl1<~ð|Ã…#“‘Â2Jú²°b`½ÜxT+‡Þ6dè—bûf#D‡îB˜ˆw†Ì樎 FÐÁÄ>d£ó”õ#:]N½áÆ+ùºüU)Ó½I@ˆ4h!ÈåÒ¬N1*˜då–(ÕÏö8ƒÌiãª`a+Á}<.&Nùüj<ˆ~#hó'†×Î0Ðò4?“žÿÅöÊHÔ4+‘%Tu:÷þAÏC}zÂa{#DºË/2²@û‰f9ã˜~íO–ôTšqÃ=wέ¹2Ãùê[hS”³¥pÊJÀœáèuNO}kú…úZ}óOë=Â’ F5â‹01›;è—§ãÏèO£ãªhoÍ¿®L%*æ„’&·~ üïKÙùø$‘2˜4çnñv¥Mæq0ì\T4%I°²“&LõS`ŠT’wLÇ3”©µ™¯ÖípØmm9©‡{Û–ÏXXr^&ã&ÓmdغÃ×ë³zu8‚.$G3 ¦u±#D>#:Ê뤜HÞdê÷šéÆ°w½éámIÛ0Œ;í‹´Ò”½áÚxæbª8™]5&¯{½ä"ÍŽáuÍ>ì« Üowšafêê‘xÄ–wlEâã)§ÍÛB¼>› 8±³nVôïÀ¿†wËꌎ%'S̽­ÈqðØ&#9â F-È:ˆ79X×™áT6 †f#h@1Y76â˜7uŠÙ÷‚hÜqs©iß80áÔÀ+]­ö8}÷Ó[6¤ÀɪFÅ9[Ž¡¯üœuËŽÛ<çDo¶à¬3Ͳæ_8i§QB:\NqÇ-‚u;KNb­Ëq–¦5ñæƒ,«rH˜Ðý&¥#:È#DÂk ²LL1G!âS§Ž‡3æc®,ê‚ÈmØ ±1ër`XŠaÛÄD5”ÉiÃóyÑt™•‘ˬ츉ƒèÂhºy“0:r:Dsp+påØ|´6aã™îý÷ÀI–‰–Ô³°â ë;„ÌbN°Îî=*íC½Û²\SÔ‹¼Ô…YjÓ”Âd馪¢Loev£KËÌdí3#hŠ˜äs#:“¤žH¦íÓ…#D ‡qד‡h´ªNø{‡.#™‚u ¶ŠŽ_vkØvª°;4¹©ñØ©Vù¢#:н;oiRõK°§Ž;9×u»^Î!".3Š¨ÚšâŒ*ES¾%—oMñTß#:åÛ´ÏJ TŽI“˜m#ò—ë-ƒ<8êÆpQ›™CmS¾íÞï!°c $ëq£C£oqPKC’*®u:O8ÁÃK°ô ’C‘¬uyß>&ºmšò̲t·)ˆZËÍ Ýßpø½\ïNfžõ%+;­­Ô (DŒ:aLkkstdŽí°¬XU‹i—vÁÓ0œ’òHkgrg¥œwâôYØÚÎO+PËc\8¸ëˆÄÆaÿ`ÑØ»q®g^Œ×4w‡œ]ZF‘ Óc¦CGipšÝöѶâ$w6êçƒ}É™ŒbÕ¼çalÈyv’#:Ê$ÜòóV5¶wªJv6LLL@-6tÇÂۈƇѬ#:Eá¹tú&5¬±é†f…¶›ZÌ㶓RÑ´D#hsÇTœ,„‡72Èvb´u^’Mh£ÌÊ>&ÝÑ(S™“m žáèµ1[U³ZeAf‰*vì£=oÁÞ$áܽ‘¿Ì°ôf&©9a…x’±Ç2–5v¾KHS¤ûç>Cã¸ÕÞÐí²¤N(!‹æ ª¸Øj2F‹vîîÄ$¥“xÚgFÖ̱®0vl`ÂkLVB–“‰'UH(ÄV+€äd­ÂyA¾ÚM°+‹[i2àu‹’-4Cr¶Æñ…‚NµM±‘¨1:Ì…µL¦@§-‚¦™Æfc/ÊìÕ35¢Ê>×-û¨ñ;Í©K\È·3 ÊϹ¡\œÐF .Þ#h\ÞšaöO%å4`ÙŽT˜È÷‰iMÊhd P^W#hv#D-äæ¨4VdZº’ŠJ!r ÉBB¥º¯æøÔ(Û=”iµÏ5UNíÛ=)™Û§Yl1´¾Ú‹Lû8ÑOräÖ´ë»p_¬oÈ8‹åCÁÏ}q/ƒ‘‰x#hf–7¿?r>Û8Ù&ËÈIå9‹œãYA €¥XÔŽ´Îà9"l ä€#D!Å ÃÁÊu×9š5@¬«IæR¶'íºÃ5çLk:riP\Å,=»œÉ(Å‚OS äªÞ±´K§F)ëzÐj¬GiŒøYã-\$Ò"#hÎ:f"逘9(x¢n1#´…8ëÝí`Ê‚¬\çK æcfA>Q„6P­AÓOîíVsøm.õòz2o¬d Uñ~âµ[oRØRX¢ä‘îû15›„E4'Îó,y+‹™6BM#ÒËÌ&µ,aüN ,Ϋ«6wb…Ä2‘×a„BÌé®:k„p7 z<2L®Û§Pq¥ƒŽôĉS“‹7ªfæu/6yÏÃËaC u~ÛF’4Dõ`x›ÚúÎösÀ·"¡Áºïƒƒ#D™g|…D„9è´A+×:Šß­^©ÇÄ\; ‰†8luX,IŽø‚R¼vÃ…!ߊ¼SÝÿ=(ÆEc —±ø^ú`6a™0, 0ÈË €dÃcEaƆ5cŽŸn@a»æê=e"˜ 6AŒ¡TPíàáå+˜ÁôFDC|T2lÅ(tï›KÎ~ˆz›è–e˜¸¡ÝBE<·Â¯*q8I˜šG±,ØlUhUèµàÁ¤Ï.²PÈI1dïCcYRË%-;1çû8ô.ðÆ\ÙÉá˜ÙƒpóÉ=®ºÖbžÒƒÓ'ˆC7,ÌöH#%Üóla$Ë HkµÌ³ša>\Ñ™Gápȱ<åd96xüÅц(ÌH”¢šÉf»T¡ŠÐµOvÙ-#:A‡#:ËÉ:ä`K¹0Þ¢^æ«È5œô¨ªrÔâñŒØ¢ÒTªÈÓq™P(mÉŠo1b*:dær#D˜àÁ¤66Ø öJÝtuR†"‡H\#hled)‚8QŠKÏ©ª®:!’´)dA±A4×d:ÔvaØC!À¹#bè•Aq½™‚œ3\˜†–S²tátx7‡lÃQ–XS¬žÄGc€h;z¦ ·c¸i8n–ZÀà7”Èr#h6™›ƒžÄ˽….ÕÔ˜³ôØÚ€@SD#háyïÏi#g £Ô2°W8ÂâLa€ŽðSºO&ƒD­ÞIq£ƒ$8äHPbá‹}l#h6áYÚµW5Jû²dÊsÛG6^½’å ƒ@#Dé9‘ ‘% ²#$3ñ#h’iÄ¿~<©.(ðC¦È‡l\5ƒ­|Ž>VMê–9 5(%zÍêQØX'‚v‡N û½ªƒÑ^;vª‹5­X4Ä$#:5G‡ihkÞ‡|ÏMbŠÄ±JF³8Q :B#:“#:ă÷p~%ŸßÛTI#Ö·Þá öÌÌaè3ß&¿c›f㥡ãvXf[4í(_áô1˜tÐÄÅÿˆgZBi88f9ùÈ¥ ¡¸TR‘r˜’OÛÒð`œÚ€Él%0à,•¬ûPQ6­neï XÂ% ®üª#:Úmš~ˆ\¯‡¯Äõ Hœ ÀÒÓ(ùj…/ê‘5ß©ÒV‘^ÜLdÉדñ»c¨â,Ž¡B«Ä/Ÿžzä €8•öÉå#:ô€w!“’Ý#h~ý~:tf ÕáV?hX5FÁ|ºAØc£¬G _mJš”Œ=•qïdo„,mÜt©ŠnvÅæ=*¢ÓRÆk Ì,+m·b‡ë¢Xcro7×=æ{‡4vD™MŒqXÑA¦#hÂ#D@ç_n#DIoךäS%Ü õÚÒêì;Ž’|Q'CB4­_‡#Qöâ±p™ ø™dQM¨5£ðò"~hOÈ~ø³è€F@ªŠÐgõÁøÈW~YEQUê×'n¬#ä*yE¦ç‘Ü¡Ümý§9³×ë.zÈz*þ*'H¨ºWvtݹӻ«º»¯Ö¼ö™Û­ÃGOa{…ÈÙñ“-…Ó‚4ã#:i&á ôK5hÖ$ÆY1_;]wÎôãº|ùÝ|ï‹›WKžë¶F§®íî÷ð]¾†«Í·/¯¯y>uÃåNŸ:çb‰Ý›·q»·Q³\îîÝ_2é—–ù-ê`ôm¼¯{æ‘.w]îôUu™I1(É|³@ÕŒo0‡NbTS² 307l$7m¦I´U¦Œaƒ„7#h­•2ÈBrA·ƒ¡R¡CSD¤Ã¡Â4ÂL*àòÁA)ÑqÂ&€÷þDržS¦‰Žì"`Þ/jMIbņy¶²päVì›/xŠ¼k ¶«äqÊÊ܃»ÊŸX¨F²|ñ5†s’¸>š%›“vFøÃ*ÈÖ²L’/³[w—ׄäøaÍÛép¬1ó+36òZ V”`Þo3]h¦dç…u»F¡Ò#h h”‘9IƒR3„fÄÖÚ#:C#{¶tœ‚ŠPé˜P´¿˜ˆ 6z¯ªûÿ,b‡Ðå.ðf‚ø)Ñ{$ÄÏ!êE}=›4¢ºv¤ž2I’\ZFym{WªõÔ8ð(­³5°m¦†g&Šðå0<š7Bºg;ÒÙ©‘¾X#Dhޜиâ­È¸8µSš9”j¬h‰Ò5#D20pƒCg» ^ž°ÍÌ5Í7ˆ‹z+#:äm´Öƒ ]…@ÓX€M0ƒfùXC,Œ"E9µSÌòôPÀÀ©~CÔ;$g’´„M.áD¢IO+¸f.ëÔ™Sc/”©–ŽïdÃà|†Á¯HœóöÁ²Ms½e$fcNl°âܺ#hHß›O;s;¡“¯½îù/—éÔúC¹&ÌŠª–{©Z¥VmnBúŇÖð³ä=‹Ä‰¡µ$ülÐ) ÑUñ¸r2œì[CsIµÔÆ1mÂÃ#hÎ'92N63pÕn ¤•˺áá;-ìŒz­l ûf×]j)s,®ùM&‚5M}+Y4i¯>ÐZÓóµ9@ômmDÓÄöz%„¡¤vBØ"{âÞ€_†+÷ ê=‚€÷kŸgÐ÷’’_FúŽžS_{ÝQ?Û&ã[мïj ÊÃ÷êÖfecUg'ÙE¨Šë£)±¢.aÂÖï黇#h*3‘´¨"Ó§°ŽNC*i(`¦$jþ“pfƒ2Œ…Ì]xöì` z eBô@~®ä3ó`¥CI"í@aNƒ‘(Æ §’cKCEÌ´¾Ÿ-2®ê»¥]¶$µ¼ª»mD”j¦Õ¬¦0•'Œ¦6JD ƒ@$JÁ(Ä.ÙTزˆ¦B brÊ­ÒJמ­©Uéhµt¤—I 2J°$ŸRÃØð„T}ž8¨†H&@ R*fb‚R€ÒŠe+@.@#:™ ºƒ „JQ PJ?\*º€QÔq¨_AT€^$~=áç>5x9¬#Q«,ƒs‘™ÜMºÁËÒ§÷@hõgä4~C®a‘™a™”pˆ÷Hõ%Æ#:É5Jê<ç!?ævì ÔSí8ÓèD#CÅT¡ÖP/EÊ>ïŠî"~ù]G†"ª¨QãIFŸÏ>V…ö@ü=4¨R²HD#:Q£éÎß^ßÉéø|Ù†r„oõð /ëÅC‰]â¸=¨Í r>9×”!ÉP‡u„žþyë1ô¦Ó2e”@ #:¥ ëÓ¹ÄòM6bT®‡Xΰ ÃS@ÙEJ8¡GQ&Ã3q™Ø÷æƒwyÒc nìx˸l ‹ºî;O &…æ2'Ñ¢|Nª)Ï[ÅÕ¹j•ÓI•OkQìäL‰4g$SêÄÄ˵ ¦Þ4ìÌD«­æµ¢k[«•s_ÍØ ÚbÒNP‘£”rÍÔ|ZDés7š.‚6ì’¤VÁ¦Gk‰À´mÊ™-)Vž1ã†8#h;(Û/ÊòÞTOÖ×=ò_kLzLTz®8òÁ²²²”Ô%Ë bSØ—“Ýo¦½î5;vÚæI\FN81+–`µ-¬0Š1…a¬†dË(Ìf4œ±!-–hÄÍÆfÓ1Œ#`˜&ºÅm™†22d+„²8ð1ÈìÉ,ÉXñ$.#Dâ!p´"l©²PÑ4l´i10—}šê½j½^k–Ÿº¹FŒ 6í\|éÔÞÝ×¹Ü^WgCQ¡´aIq8£É%F9ÝLï—{ÙDmrv÷¹b*²BÍè­N­A˜b‰¨P#DÆX,Q¢D©'#@£¸Gxq#:Áº.Ò¥V2ŒSo ;¡w`Y<¾'QzŽ»*‡­:Ë M¢yt:šdÜÙžvQ²˜ÐT]óŒVaxù0ú~¿<}…H%µüí2¸öAï_1Q1±ÿ Ž›ß<ÃNl(2å‚©hdnÂëÀ‘†>Aîõø{Ö‘ÂôC9Êæý…“TmêÙsÈSIL0Iø¶ìÚ„]Ê¥#DC$¦B(R¢}²™ 4snˆ%M@4*SlVÜÕcV4˜ç5ˆÐR¥Ð h>f#D@÷×°Ù(Š#:Y®ó#:vL€QØNMµ7UfGø]Èn6f€@QÅB” ™F'Û˜”'>½Y‡Ü®oyý7yÝa^© 2"@Èî“£Qü¿ÁïîáxtY+ÙÑ:€{èõÿF°âŽìä!äÉêFæ’öãÒóçÙ­.ûS_·NAp²ǽ|n¾Ç¡Gªm#DÌ’‰Ê¸æV^l¤'ægëøœö99 §ÅÊ@ÏÕ¶ ¨Ôf$¢‡Õ²!ëúr·å"MF§ë/-ú—Ì.SaëJi6BkŠ8ÐÙôŸ1¤Ó¸”„ɦH éó "ZAh5“b¹õí…¥4$KBcµ‹\«™#S)´Òk–P#h²$#h#D6vöÒøD´L'Rð¬Ï¯Û¿RGÖŸÉà©ø€²"‡?-¸†D)Ÿ ÏÜHh’´XK•l–™Y5%-,Ê–)Œmi ªÛÙ‰m"jY"Š•M±Wš‹[zÑ€hÑA…¤F ²¤a”Á<ûÏKÑ)½Z4òò‘!Ûå¥Mˆªùž»¨?AaøAs¶Ç1„)|#ã~„/màqÇ·ÝîQ;†URïƒé€Ã³îïo†×£ÑȨ("@ R”ij„M'Ï;]cæ¼wºýwÖ\{øG½#ñ6ÈS>¿/˜G¯xÈÔœP½=ú ü‚?PŸ‘ü»#:„#h§ÀCŽp6”>¼ï⊉¶{+X¶tn»cŸ.kÝîÆÅæ·…JæWÁ’§©äx™›wsŒXj}¬…½Ñs$cî6%3[¿ÜoZ­YKB*žÐ©Ú„>«(Ft¡‰ÕºÒf¤“Ê‚#D“¹¢†(ÆX¶N°à6Hž#Dºù}* <'µ†Ò"¦%âLGAŒl’Ða2Š¢éJŠ‰r\Î`‚nñ+Å<#Õ*‰L½¥ÂŠPM‘´q¾²Í³±‡•¤”Oå³ô’9Á¦ÞØÞ\¹Ä+Áóuºnur®^¥*èßöé¼FóE7ÄcÅ –ˆ"NnZ1šx6ÐùÑK,ô뉪*!¾"g‹ÔÅЙÙèål˜³6âcÀ’#D·e2o3S‰ôÃt¬ÐI#(‘JÌŽ#hø‹aõš|,X'ÓÌ0ü<©Æ2d¬Õ^içˆvXá™ ¥‚3 @`R‚dˆ­)Ñ¥Œ-~‡}¹–6LÚ@#`pu²iTšRTöå>š° ‚.:ïÏF¸7ø#é3F ± ­#:A‡†W„‰<”D¤xìp”f%N—±åÔÇóx¸Sày/¼ya&b‡¨>H&C+;#Dk&cC²5ò'Rn)bÛÇæuýl§<¿[»ƒ©ÐL‚6CkÐ~81aSÚ”‚n% ƒ—¯ã:M*¿#:÷/”ñì_nþÉÐ4®±’V€‚^Ñ©ŽBýH ªË+0A2‚Ñ+2qT;'tþRzþ2¥A#ZH¿{ô×¾õÏe÷U6¶N›Ö@¼H˜Aß㔬–2¿'6Å­E@Kë#DkrT¦LÂ2$J ”ÖϤÇFœHòüåî#:üßS€ªf¾¸=‡ÉZ*߯kˆ(ÓQlHÍ¿/Óú]uª¢®»`µµiH!Ù£ÔD®ÔS!KÀYsÒ L"Ì–®–¸’ÁBã˜üÞáæ‡'Q ]C÷´yÑï^AW™ 1U""GEÃ[¨3;AA·Œ|CLêHbc3GOŠ/Õ(R¡M#hs·S`£”ýÇðžÝ:?g×ø{y?6‡ß& %Ö¨íø§G¡s³é…HóNŽèô[Ê÷²#‡úTðÈã€ó=?Ž7‘ðÛ¶@GÌ 3Wß›>'ý#KžÄè„ÿ› ?%~ÀÚr‚|M3ªb­fÑ8Ph#:üë- T‡°ÿX t*$#: i¼)hë”OR)uICóÈQ“…fݬU ðñð#:#:„óoÏddÁ’N](Æ~]rQ £×JîDî®™%ŒÙÓ{¯ží¹_6žêå’v§º¿#D{¤=T¬ÈìF!æ FÒ4iJ1¸ö¼øß'—áýÞ|î‹i\#H›!(¦ÔQFľÓw5À¯cRQ#f¬[ÎQ®k‰“E]Ýy«ÞÛ“LjˆDÐH‚d¿áì#:ÐW¨þ›bpÄDÑ|Ô_?È’=AH²¤†ò¤?®º÷ýÇDéðø¶x¥ÂÖ¿³éïÄ>CèSDª?®À—ˆ$Ì? ­ÿ5iÛ#:ñ4:ë¼÷|GÜÂ#:¤*™j5¨´Z5²kTZ’ÖÌ„Œ"($¢”-#Dkj©çŠçÔÀ‘gä(h©opQ_i4çЈ½ÿ% h)ˆ¤£E¬©¬d6+#Q´Ò[)5¤Ñˆ€ †˜t‰GĈ”¯º}ȼîŠ#(“E6FQ¶*"Ƥ±¢¬Qd‚ÓfÒU3ZÑ[ñÖ»?£ïi*–4‡D60R1!©…Pñ;Šl_÷‰ZGú#çT¡*âìHBO^'ý¬®Å} G¡q$¦#DM–Ë_{î_Ìõ½›–ŃW³äÓI(Ócl-f¨ÖˆØز’fVJ(mhKæ÷¼ÖÙ-´T‚f©/ŸÏJ¦áA€ôøA&!‰$“Lž`@9ŒŸ·ú~öó?;ù'Óþ-~NK÷’æ‹=bpB÷žÇæ}‰³«¿©{Ì3_¤Ô»Üº#hdQ#:?YÊA,# jû .BNÚp¾#h²|òT©õÃŒBñ¨i-º¥#h‡ˆÚ}ƒ)“I©Åú|%‡—³kJö'ª…Þ}'åP~Ó œù{ c1sqTdä¦Kùþë0¤R#hŒhb+HQ‘’HÑëjOYδA4·Œq¥÷qÏ1;‰ H»¢a0HÝàEè÷wýû 7¬2*%8êϬW¼ïD#D; ìê h~ê*!J@:¢>åûà ‚‚¦A()¡p  Òª§Ç¿Ê(6•u±¤ƒL˜0O挓sˆ…²Mœ5¤´bp‚îݰ׺ìÓZ•Vf†–ZÍ´®ÅJöõê4Z$È#DB¸Á:u%+£0Ã=<«H*þÄ#:=€:ûs@P H'—ŽTH…šUú]rW‰/œiú‰$ÃzÊ$ô‡2#D„#:P|6·!S¤=ÃÂl‡ÔÉê}{ÞÞ#D}tÓYËOHZV šü1Èò²ˆ õ`Úœ¸"#h©\cŠúIbÑÆkw ,@˜g-1âWj8‚¹nA#´µÖ@óXC¸É¥#h ˜—¤åß °‡ù­Z%wv'^ËšÚá!´x“ØöÉç8Çx¿‡ÒXÅßGûÔà°þöO•ù%(s¡Iƒy¥ó#D o¢ïnÔ#ØCr$7 ›2Iæž­l.¤%­üßéTÚÉ‚þ²ëŠËþ?W$ÓE÷X‡ÈU?Žlü? £»Gx{üSÖÌÑ%µÆ Øþ—i`77k©vËW+·.Æí#DsZë.w]g'rK®înÌ3¾þèžs´—\š6Æ’i´”ŠŠ[#AcF¶4Ff­A)ITÇ«Àûñ"B#hÿLǯoÑÛäg|aðyEé°Ü:C#DTÄcà«·Zü©´ñR€/9ÅŽH£ü°á,3&ð•w†ˆ7)¦ƒM2,ï0¨ÌHÄÆ`EÓn „‰41“,‹‡Ð˘T˜€™º•snrèr4N„TóÒž€@ðö~ÛøAǘ*yG†VÇæ†60²Ù &FHA#DEÅÀ…tj;ónÎ9xCü,b#h{#h„!dhDM@’Íck­Z¹ZŠ-‚¬¡TMÒ …2J2>_Xèóú)ëÚÐyÈžAž'O6GæåW´¨2áWN‡¡×T~h?TäÐ¥1Ëžøù‚¬iÐm±#:ÀQ¯Ã,æŸá»Dz3T#h³ò¡–#U._1 i®ºäš2ÅMPGú‘ ïÌßh2xÄ@xsW9ãKXZÑŽ½¸ãªG»¦å¬ RS#hj¥%)öÕáË’Yì?¥æ#hM6Ä6-Œco`ÈÉ#™W¦éÏîUé_•2jßKÕ+¡¹]®[ªP¥K¶T·SÈM]O&Í-ÝNëJâN*`4¤@P&N)N8a™»3*º¹»j5wjá%Y&2Ûwm(Iµwuwv´›J”¨šM*JÝ×jºÌ®îº´ºú½¹é—RëtŠæ¥†¤…8 Õ5,qƵA.ÃÆP:€”s ÉhH)‚eYX$31s°Hi‘c 0‰2CÊ7‡XÑlkI€dŠ’hF 0¸Ò#D€Ÿ?¨é¢.¥†ER/P”|Ò£r`H=,32ÿyÉêƒMºI&}(ù#:wH‡åý®i@$úª.þãé©–È8pϨ.œšC§™¥\Ím µîÅ#`ð•j©`Á!É#Dó^ãµÃQ¤™·q ªóÞOÊÆ5çöS„4nnüXpÚb¿\Uõ¦½àf}:#hXªñW¿#9X¹”o_ó¼Y^Æ1ÏÜï©æèo$ Z1à Uð4¥PLI8i Â(¤iGêIýC6fä´œùjcÚX™~a’Àµ»)±~0Ò¯¨4›´NRˆ;ˆ :‚{Š,R*]Tºƒu öF³û ‘=Íý»30ŸÚbœÃŠe¿jI×¼±pU~4IÕõÉëú™8½:Š˜­ 'ÃSdxhþ#WLÎ#`ýðɶ’£#:Î7.eäêùŽð6243~’'& ç6[+B<[C´!Ê~E>#hw*£ÕêHIçò±áP o㋃ØF†-ŸæúÄ^çÓâ8FA1’twê^mA—¤.Ý]uÜî뻞°°ÚKÝ#hÔ´­¶d–F“¹F†¾ó¹Ý.ò®´&¨³Ih#DA… K&†bÓ³.5–øÁã@mQÅ !–eëD½ÈŠ@hª™ÿhyZ¿ŽÁb`#häÖX­Ö/¾ÇƒuaÊ S¿£·>³ +¦Ü³Ž¦\qêj¦)S¤‡Xrp¨%ÁÒDÌÖdCœ˜ó~ÊPÄ5>i׉™3EBîp#×ùT±ûÃTƒmNP)dßýàߧU¾ô(‰Òè[Vh9ÑR Èú¬ŒòÁæw[ÁN°ÒkD"Ô¥ª¤hÉüÖT gjŸ^j¶®Ÿ^e×g:ïž Ñaá+©(€ã¬‡ÚHlg¿ÈŽ°ÛœìÆÊiùèšI 4Ï/¹¤O=krJË(C˜¹˜ØØ&D Á< `ˆb ðõíñ#DGä£â–û(ä{ü:G™·£Kx]†á “#:0T–™ŠÆ*V\©€:¤#:¹ë‰C©úÈ{}¾áT¾Ö•/9ü""4yσðƒV#hPÊï÷˜9Je'P ewÃÓÀå ê*,`XÐ"#hCM!¯×ÉiC4}dº]$QNçÌp£•³ LÆDèã±Èì#ú´CC#D-cj’ss’)Üü¬ì$ü½ßäøüÁÿ~‘4ñ†DàDù±Á‰7”eëBmnNÁ'XT6GÂØÚ #:ɤT¯çæST0U;ïéÖ'nÓ¥îÀë¬ûqgN#ÑL„ƒ^ ýÅííš(Âq~ìÄ>rh´î"±#D¥<þ „#>g×Ò™Ì1$‘^G§y&²ké]’>šfô:Žp0Ùˆÿ¢1å”ð#:?~ERĬ¨Iü1G@ÆÞu”^d)Á.¡OæÑ‚=ö#hÈu‡Ï@']&$D&Y¤ÑLÃMRiJœ@ÿñÿÈóèðÿÙÿ÷üúÿÏÿf½ýÿú¿û¿ûgÿéôÓþ?ü½?ïÿÏÿ›þ—Õ—þoäÿÿ7þ¿¤Óý?øWý½þŸøÿÿ·ûíÿoû¿ë»ÿNöð¿þ¿úÿõÏÿåÿGŸýÿöÿÕëú}!¿òÿòÿëÿ«ýÿþ?áü¿û8Óÿ_ƒÿûÿgüßåþÿòüÂz¿%b_ùÀƨ̂fŠ§sþv8ûQпðÿëŸò#:ÑßDZúžŠ@N®h?âø¾D¦@ØC’ÂyÊý'Å<¾Òh_ê[lˆbŸH]ø ¸`Vö•ûö]JÑŠÚL‚C˜L€æA5þ-ƒJêÑTEAф݈Èë¹¥›!z™Ó?â}Ñ3ÿs»´I0È&~ªß?s®/ØÚ"ÑXgtã®ÀÖÖšY ØàdjB «M¢`jO`€j8ß5“‘žú˜ø÷÷‘±æ{¡{„ š)Gh)ȸn{’9÷Q=‚ÞÀïó ó7/ü0¡ïÄp‰¶ÎÔ4¼{g@YÜ!ÐéP;Ó°÷¤ž  %•L…"¨Îð×µ˜nŠCºŠ, 9adëžJ´É­à'°ïB¨öpUÿ#:«{ÿƺԘŽ æÏG×þ%Ô¡‡úˆœ#ÁžõJË«4¤R³ÿDm½]68ðY¯úÞO[1Š©’;[6” …̲æ‘G©dk¡áOÅ5GßÄÏÖ°nƶ1¥w6;aÒgÐÆÓ ël}´\ìû!S[&kV&œ¦ü“£RVž8:p ó"ñ#:‰@2D6sÚó¯&_û®-u…­nLê[O”£ÿ««uý—†…†£2ÄC!cdÈJFÝmYÿ_wëò×8ʘŠè’¦¤€ÛckèÝÅZ·J7‰ÖÝýZ ˜]ÒÃF CƒþjbˆÑ¦ª§ŠU½¼t×3|ÐL ¨±q® ^#hCz¢ÙÐÁ±@zadÛZ¤Íã“.«ÀÆž3#hs" ²`bdP5“ÈÕ¬ƒ2*h ŒMÀl…mͬ&Ù´Ú)0õ–ņ<òŒ’“:CZˆÎš’¶q/'G¨ólÜéÐ¥\¤nÖÍÓ¼äØuz=ëÌÌúdB†¹÷˜«ÈÈŠ;F¡*J#:€O¬ö Žýštµ_CUe5¢Æ²`Iš#hË0 ®âç¡öû‡8Š}O‚j‡Ó¸Nš4þC¦ÐW¾){t~/#DÜFfr‡¿»gîÚÐÿúDÿoýÈÚøwŸ±Þœ…ú)÷(jƒ/¶£F¾Ý&~oQ^ï,Ï4@RƒL/(¾¤Ãßxÿ#h§ª0\Þ\Žòûø!ïB(1€±DX”XPŠ•×›•©4©`¨ªÒK¾7™Y*‚ÔZ¬Ò©¤‘iX„( ò1CDéÓd Ñó€ ”§îŸC –h›&Têçâ‡5©5“7E²ÎHRBè£f#h1Ah íëØ Ê¹}É&„îÄ®ÙùïÐXØõQŒ6ô62žçl9_Éç´•#Œ„Jvn먜Ӣl‘œߦ±×¶t9ˆm/ ƒj#‡CŒ™kÑ© â#h4GŠéïk¶ÍÑšnF£Vœ+¬X%ÿ¦Ä†ÒcKš¡ÅímñaËêÎYœMŠÞñÜG.xºÆvð]šÓ²1ºó¬Îu#D’OÊÿ´òúCõ{Xçžh+ÄBvž(芦„§¼ÜRܸÊÂ#DŒj+`Ôé½·¦ÁÍ9©D¿fOþ$|ýr6+–"ÇJTJˆ#±f‰M&ã]Ó]mF9èμ¥Si ªE#DÊöƒ)5 ÄÕ+ç&M-#:V¤ …h)Ô+¨)#DÊÉ("V©#:á‘2;ì#ž˜eÀÁìÄÔ6ó,ÆÀ#D@p:zE6È$£b£GíÔG8 ÿ4aåå¡€ì•e¶Aá™ÙσrÞG öÞ† ¡530.%±~=W©¤ÈgÐKY.3žSË':£˜m@±>_’=±&ÖÃÜ!6>~_ùÂO—0êå€ôñ‹â`ª‚¨ r¤Ófr#D?WŸ¸ÿ¾~ʼ{ߌæVÅÔ¾D•Þç°>Nb'ÓQîŽTÿÙe0`'´CúäÕà'ƒã€ ¿ú!J‚CÁ (Pž!§hçU=£¾•ÌB2ŠB²…2XDfA& 1‡D1Íé ö˜¤ŠSKLH^Þ¡ùÈ_ÃS{щ~ðkkøPÍ%Ë cPš‹ ¨ÿÃÕÛchW®®Š—R Ç•äƒé÷þ þÓ‘ú?N?ÍêNƒHò×ÄAð‘ˆ#:ûaÙõb£é×1Ä[¹Sæ=:ÃJJv³Ø3 Ó!ÕazßÐyÃJwÉM"^©ÿÕËèÃÉÃãËè{Ã`aE‘¯ß§ðГù³À§óQÿÆ#:HŒÅKÏËùiðÌ¥ìÅ)¹½*jHí@ì"×þ¡Rs¦zjý¤ŸÔc ÿÁ§þÊ‚#ñ(¥eäñþ¹ÝÕÿ?Kµ~pº“êü4ð  îÈÿäóü»Oý“ÿ©Ü>…—¶yv]¡åÞ­©Ùní]D;λîù|sî„õ)ò{ÜŸXO „{'ÈY/ù|˜¸aÜ;psù¿Bs¸;±•¯¨ÇëØcñ©®¿¢ö–›:/þfò“«¯ñJýÜð]©Ô!•’˜…³ÒaA¨×ó¡Oüêâ^¡ Lµ:lîµI4ÀWö{½B;>!•P=þKµýž_Fœ+?Â'#Dþ—&ŽÚ”ºiWs’)œ~ýW Œ³˜50ï‘Ä>?†Ó§m3 Ïë} ?Ç­¬e/!i–8åá{­ŠÍĶ#D-ݸ”ªèŸ¬îQÐ3˦#’«É}û¦•Sæá1ø¤¾Ðö´ØÎÐéò·ç¢ƒ­ÎÒò•,¢t=ÏgÏMz¨*$(kÏÉ?Ç¿d±èÖÎ%ÿÓþÏx\Á‰Á<,>#:…Júp †‡Aé¹yÓTÇŒ2_Ó#hU`Á7øý<Ã_?/‚=Õôõ{bª±êyžNäK[Dõ¥áí„óª^îåíÿ± ÿz*µ!þ1ØäÿúÊa#:Ì?øLÈ=ÇÙ?á#Dáÿ7ØìøQŠsKÿö/þ(jùR?ðNùÒ ”¼ $ÿ‹¹"œ(H>Àñ#: +#BZh91AY&SY&4 ’Xÿÿÿÿ?ùÿÿÿÿÿÿÿÿÿÿÿÕè&W$m~VÐ#-ø0écÂ>zä؃@P#-#-#-#-#-#-#-#-#-#-#-#-#-(#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-ó××ÕUo»}òú¹ƒÞ·ÐzïZ’½öwס“ »N€ú÷ÝwÒn/»_7Ûç_{Þ^뜊MÜäqK±§k1ÝÖöêªn:ëÖ¯¶û|sÜ7ºîó}ÝŽÃvæÎm»k›µÎ×gÓÎW·mu,ww4Ú.¸íµšj9ÙQ¬®ï½ºsiõÞã«Ï–©öbï½ï6ôwwØ·¥(÷MîçdÍV«kuº&îž#C°Ù½ÝÑо–õÛÛÍÀ;²€$»·[±vJ UUJ‚ìÉT;e:h;µæ¾s× Ý]w>íÏqõï·wÏ€#-ªØ#-#`¾vëYÁÛ«c¹µ·8äDP*leÝÎ[cSjUìvA»]¸ªP´Ð6ÆÙ­ºë”íÜf|ðïRõˆ#`wg!-˜Ø5"ƒ­@»ÎâvÖìåB¥ç°{ï>SìÈÛFA£+êökk¼W´F‚Píñ÷œ(}e-nŽNJ–÷¸x^c)Lâïgnã¯<—`ÚÉž^ïH›m·M)@P Š#-*B%J_v8•Øï^ê¥D:Ê"N½ó>Ý0˜pÛæÞ÷“Fo]Ú&}öá}}wnŸT÷gÀú×% º;³Ü#-#-#-Ð#-#->ÃT#-øèx÷o½|Ǧ#`Õ¸#-8URYLP¶Ösæºzti¥h#-§ ç[mݳCIÛR¦ìWÑÍ”#`du  Ði§@¡ÀJ#` ª½œBB”(}ŠP#-úE¼Î%tj‚#-¯³Ýòyww^ùöìûµÑå$Ÿ:]P½fvä:ÐR®µSFTa2Û{Žß aÚm¾·Ô[*#CQÝyõ¦Kk¤]ötöãyîîïy¶õ¯«¾žöõ}÷ÞÞm}Ökïs™¾çw¼¾}ͯ½öøâûîÞãÝë“í—«O{›»ÓVž.•ÐÜöõæž÷לùñvz÷¸ûãÕP[Z5Fßpš®í½ÚØ¥#`'6½fÛní*:ÔnÝwwnØn*Ží’‡®«×ÃßZá×zzß|à:@Mï•÷ÞÍsíðê)|¼Ԧ´Ÿvt}¹}îÞñê®Øí÷±ÖÛÞßw×{ÞowMÚ÷¹·=§qõÒ»7>Í×o¯·×wtW}뛹×ØÉ-š»¶¾÷wnéØúÝîkîjí3®Öíë¾¼ûw[zç»»èû½Zg¼y;Ûµw޹ݶ2}ï}÷¶ZïªGl¤÷3bÜRùæf›Úé÷œ½o<¶ÛŒ»ìoŸO{¦…õÝêåƒ@:#CŸNé²ÒŠµi©T÷>ží÷glÁŸ5ö}Të¹µ+mÝŽçfÛÍG½ç½½½oiuµ­îí÷=zûµ7kn…Ïk´¬Ð[žÏ¸øûzùóW¶Zõ¢Ø»9.ï–ðókºÜt‹ew0ï{}ž«Þû»Ö\æ ïonç½îÀéÝw]—5ˆQºê‹ÃÛ×7´çNµ× —Ͻ#C((wÝÜíâ>Ë{ÛËÇ¡;&¦3wZw{ /-5ëÛ1´×WC;wq±=èšë0ç½ï'nîúÇ!î:q7¾ûçÎÎï‰:»nY«¸SÎõ‡€^Ó°è¡0TçS#µ¶îûãßnYÞ¯v{w½|s€#-å’•mè|OJÚ€V‚š ÒŠ#-#C²@»¸ÄI#-T€€ÛE»®¦À½œq:w}½ÝwÅèi\îÔRU{ëê!͉°÷ƒ½êí¹YYváVÙÌ&ò€QÛÜs{G+{{Õp€@¯lóËÞçf¦š¡¨R´wwsnvë«®Ý;€tP#-¾ûÞ>JP#-#-{ß}ógÕÃÞy¼j{T÷-VÝ-HåÓsiÍa{z^ó^Øô­Îq½õóɈ#-|ÏYç@8í;)@jÓ #-­ÚÂ"î7Ôlx½­Õ\îæ×:m¶«††ÍrÛ¡€#`ŒÏ·t^™Ï»îúøð7wšPè&qõõ÷Ÿ{ÖçiÀ([dTQhbg=g½{žïn«[pÍÏ{ÐïnŽÙ­ÙSׯ^4#CÏs=½¤óß=iévð·šs"rºewky]{ÚNܽU9ÆØw+WtšíÍÚÃÖÞéA}÷u}ôÊ®³Î´bÌånbÇ3·.h®žóy²§»r$J¡)UTuÜÎÛ–º5Ú]]$ ØËÏi‹@#-dM#`€ª­.ƒM¸vîA@#`vœ÷Þ÷¾µ[ëßw×|Þ΋#-#-vb@R¶ÄèÇfDŒ»®îiÓlPÝfôîôâÅŠ±«¬–:½l•âÃëîù×WרVG]ÝÏ{Ÿo£¤Ÿ.vúㆶ޺ 5oow›·vÍØÈ ¹TÈzz­  #-#-xÊ`ÒÀ[#`RmPzPžÚU%ÄÛÜǯ[p¶œã®@»µtnºè’m#- ”#-ÝN[€nð¡ô#-#-S#`#-#-4À AŸ{“¶ÛÕY€åGwÜO½»;³¤Ü¥#Cws«{»Ùw–:’ú¦î8èP(¥Wl¹ÊÝÍt—6k\ €­¾uìµÀKUPiPöÝç6íj®³®ì6·ºz#-Û{¸y#`E»šíµ#-9š¤‚@uÝXq¬;Š[Íyî–íÛkhÚ‹iÛÞÓ´—BãÜSe¸nÛ€ÑEôñðú• ^½;¡»‰^Þ]a½åTlïs#-m[»¾ûïD}|Ì’¨ˆÕgcPœÔΆ#`£»rê%#-låî}ßwÏ·½èîõ§Umé³s[wSc£v]ã3Ò®bN½¶{½qÂ×µyf„›h­w†÷ž£ÊY ¾C\xi¢#-@ #-F€4d#-#-52ž)?IzM#-õIêM5z#ÔÚƒÔÓÔõIäÒ M!#-€MS)OÍTý1¤ †#ÁO41ê#-Ð#-#-#-#-#-#-‚D@„ÐL€Ó@jTÿjiÒžh*zÕ=G©úM'ꞣÔ=MÔd=@#C#- €#-#-#-‰4Úf‰¦M14iOOz£O$&4õ!å=Li¦€#-#-#- £F#-#- ¦¥@#-@ÓM=MCÑ4ÕÚ˜S'‰#Ô¦G‰£Ô#-#-#-#-#Cê 5M#-šhÈ#-$ôLš š¤Æ©ì¨õIéÕ4Ä#-õ#-#-#-#-#-þ£¤ÿÚK&*ÿ¢I€É ‘GþЪhÐ*ŸœU0Ú’™‚š¨˜¨*B© D àªl!B#-I#-QÂT_ÃÑ}#`#CýóÿoP?÷'ùDDŠîEÛÝÔÝÙróQð]Óÿ)‚ì·yÄFÄ:¦«•V÷!aðÌpPN<ÿ°ê#-8D¥p5ÌOõ¯þµôŠ§°:€°!@8 Þ¦(&¢©ý!TßЊ߆OpöÇIt:t.2‹Âj%ˆ)¡¤)¢™_ƒb8*˜S#CÜÙ"8*žn*t%\#`«ú•PP ¢Pˆ¥*#C#-%#`- " #€¥`”PHb"ˆb&ˆ¢ˆb©Š(‚Š˜Š#`b "¨ˆ&*I"hª’HŠ"Š&Š¨’&"Š¢#`š‰f#`J*Šjfb$Šš¢™ªŠ*šj‚f"*h‰#`¡)ªš`(I…d(šb‰h¢Š™‰–"Š š„$(DB¨)ˆªR’P©P¥fX€R”‰B™¢¤’I`š¨’Š(’"©ˆXa‚EJUŠd˜Aªˆ„"†šj‚©¨ ¤‚‘…bX(#`&* ª#`"‰Y€†‰¢#`¤ªªˆª)˜‚)#`b&B–†‚‚¨¢*ˆªª†H† ¨ˆ„ªZ¨)j"Š‰i‰bH‚J˜Š¨*†…Šš–Šš‚‚‚†¦ªfš)I(IŠ–a)H–*h* ¦”ª!€¡’©šh¢¦ˆ†’†jYŠšª†š`¦ š•`R šBVŠ’ˆ ) ¤™R©*"J¤‰j"™¨#`#`bRh¦ª Š ©Ja ©¦ª( ”¡$€ˆb¤`E”D™JX¨˜¨‚‚¢I¨¢!¦ ¢˜Š †I(†R¨‚  *b‰ ™B™f" ™¢$ªbª)¢& ˆe$("Rfb¢J€ *‚ˆ¢)ib‰")JŠ©’H¤ (ª¢J"J˜*‚¡¢’˜©"YŠ)’Š*‚hªJZ*ˆ*¢"ŠJŠª†jj¥*""& ¢)"V&*(˜hª¨ˆfBˆ ”©ˆŠ* ‚ª©©˜¨¢jX‰#`aš™š"Š©)¢!Š‚…‰šJ#`˜¢!ŠŠ˜†ŠŠJH!¢ ‰Š &#`b ˜¨*I˜š"–†ªš*b†ŠA¦‚"†ªŠ ‚”)(ˆ¡¢‚‰˜‰”ˆ¨ˆ&jˆ#`#-ˆ¨%¢ˆš ™j‰%ª*"i¨˜€¨i¢"J”¢š"!˜*‚"Š"ŠˆŠ ¤¤ˆ‰"‚‚¨ ¢‚šš¢J*#`b"Š""‚h˜¢J¢Jj˜(€‰¢ˆ(¨¢¢ ¦Š‰ªˆ†š¡h¢""’(š(f"#`%‚b†b)h¨¦*Š „$’ˆŠˆ‰""™bØÌARSICTÒDÕC4ÐSDE4Q4Pı4% Q5QD$QE),IT²KDAM)C±QQQ)S0U4¤PI5E+30ÒT %H0TK4ÅTT‘K1A$ÔI2‘%RD”TTL$DT…RÕÑ0EQTQT‘0QTIQÐÔDTÀTMQK0Q,ÒA0Ò¥4DµTQ!$ÉMSPEM0SA!!EUQ,’MQPEKMLTK%A@DU%4D”•MTKDM-4Ä„E‘--DË3UMS)L¤ÔBÑE5E4ÄÌÃ1Q-,R@KERÅ1#C1A LTÓ#C0DTÔ4PPÄ…!AQA)IE- Q$ÒÅUÐTDÈRÉ1D¥RPÁTÁQ1ÑKIRÔµDAÒE4L„°IKAM5QTDEMA4ÄJ T°ILQPTÄTTÒ3DQEDL±!Ð2’0ÅQ%4RDH@ITQA"MIQPDÅE@Ñ50ÄL!ÑRLTÃUMSIUÕEJCEPÕH)#`)2PÓTLÅ$ÄMA%DLDAS5%RBÁ ”2ÐEÁDK3C,-%JL#C0²SDUAPQD´Å$S1EC+ýè0³ bdUPd #-šjV&¢aš ŠbšY˜¨"J"Š¢"Š¦”&#`Š™Y¨j"H ’¦ ™ˆb*š)"bB"’š)¨˜ˆŠŠ¤”† ¤ ‰Š”¡#`J)bJJ¥ªh*‚š™j¢ˆ¥ª¦‰Š("*ˆ’*¦††’#`¤ˆ‰‰‚ˆŠZh#`šŠJ(š˜ ""h ™"¢¥¢*i)Y$hš‘ŸÝ‚af‚f’IXf&ª‰¨"Zjb¢#üœâj%¨%j •˜f‰”¤™©F&J‚RfªŠ#`’JI¦*fHš#`$™ f‰"ª@¢ ˆHba ªˆ¨ˆ¢Z¢¨(#`I™ª#`*J#`a¦Š†š"Rh¨ ‚Ib#`H¦š ª‰¦fj¦!Š(*( ˆ& %"¦¨ˆ†eŠ*†@!!‚ (†š ™(‚*( fF#`"¢ &a¢Š¨"ªR‰"BX`‚XŠ¨#`¤™ˆ‚Š”€€¦R†¢¨hHH¢”¥’&*†’R&IBüχœŠ)¦šZB˜Š¦‚ˆª#`Hˆš*†‚bŠ” ¥bª#`˜¦˜¦f¨¤¦† * ¢M(L+$S5%TÍUSAA-T‘P TU#C-UDTM1LÅ1A´ILÓ3)AC@R,CJD@MMSÅQÍ)TTÐQ4RLD$EMÌLRDÑQ3,UL•@S$2ÓIE4Ì5-,ÄÑLMEEE5L@D4•D2EJDLEÔ”I4• 0MQD‘R”ÔRE3PM¬TRÑMUQÍ2ÌÕ,M4CLSÂĤ• HA!E%M1R´!B´C#C4KTÍ(P”´”Í LI4THJSK2CADÅ-UP’´Ã11QD…P„EBÅA%Q5 JÉHÁP¤P @KÁ ,D…3 A@#C%P,I%Lã$ %Q0Á#CD‘M#CSE-(RÒS ”L1¡@ÐT SDUHD£ „ HÐD´A$©#-DQ@Ñ -(Ä,Q”P#C T…AHÐÐQ%4P„E+T”4¥4´‰JÐÕL%!HKUBÄSS#C4R©UJLÕPRÅAIIQ1!LTLSU@”U#IJM4!K@*¢ÀÒ$SDTQ0DQ,!(Ä4D„ÁBU* SI, ’,3@„Å*1DÁ!²”PŒJHÍ4´Ä“#CD°0SI AK(IÐUATA#M3UIE#CIUMDQT´D%D´S-T„@ÌSDDPÄ°EPQ1P1IAAPPMT°ÐE#CÃ,E$À0ÌÁ!SADJUTÅL1DQ BÊUTLSR’ M%MÉ%C3U0CQ4U5TÄŒTQA$Í4EAESIT5-IDD1 ÑIDTÐLÕ411#CÅ$ÒD0ÍUCBT@MUSÆM$M,ÔA±%1QUTÐL!E-,ÁLE44PA4ÒA#-DQA5#C)QHD5%!-2IK#C+ÔCTEDEALBPPPTHUIA3DIIC0@L$ÂÁSQ#AC#C E EM!R#-RÈ”ÉE)0D¡T•QÅA1MPTPRÍѱAU!PÄELQ2µL#CP± PD„‘ KL±-#C5EÉS“"4K0UST3RAIM#C4 ‘%L‘3-QHDTMRR#QKBPÓB4Ì-ITT±ÌTI$³ ÐÑMCTŒ4@PÕPLU%SHPRÕDÄÒPÄ0ËE±P”QAB©AHÑ”ÕÈ‘QEQ•B-%TÌÑUERD‘ ÍTDMMHS2TC#D‘EAAIIM!QQ M)EÒ³PP©J’UCJÒÌ…QCUPÌÁP$IMUADUP4ÑS-QDà ‚ÒR#CQ3JLPÙ¡ˆJj  $„)ˆ"•¦#`‘")Z‰ibh()#`•‚’#-ª‚Š*¤¢""šh‚B""¢*‰ªZb#`  f"#`)¢•ˆ"‰©‰™ŠŠ&*¤ª*B’ ”š($%b(‚)" ’ ¢šª#`)ˆRª˜ˆ@ª‚‚–a"JiRˆT¥‚JZbP""Š!Jˆ(h"ˆ‰j&(&"f)šI¢š © – ˜¡†”bF$J‰*ª@ )ª#`` ¤B’ed„J€¥B¥&h© ¨ª !J”’‘(¦Y– š#`#`Š ¦H˜š†ˆJ©i(jŠ&šJX"#-¥˜ˆˆd"@ª@Š Š&@*†#`ªš¢H &•¦†) ¢iiE#-FšªŠ Š& (š"ä覚*†Š#`ZI$ˆJV‚¦h(*"Iˆ€hZU¤&AJi„ˆ *abT$%d"(š` ˆ¦˜#`J©)i#`!¤$&#-Šbfbe†ªZ)** (¦Š""`ˆ˜¢&)¢˜¨ ˆ¢”&€$¢½Ì'ûn8u×(ÿ\#C3Ñdƒ´û}~¬,12JÌ@¦·Ù^ÆÜéZ^Ž’Ãwt¯èÿ¸ù{˜@ÿ¢¼/þÖßœ]hŠtvL*a÷?ôáQ¨HTñ·)¶ö»ß‚¸Ï#`‹ÎeŒ(?¬Ú±HÁ‡F³_ôJ)sþâÙù#`Â+ÖÔûáÿí&ôFÀù§4Œ×žóù?ä‹m§5âûŸ£ $Uˆ)¥P?ªŠÓkŽVS2 ?ão±ÉC þÓªÌyN¡#`Ö ˆˆ³÷¦üWõøÑoT?Üê ÿeŸ0!Í“,#`IÍÊPC…tìÁø©‡S‰Ú@¤•pD e÷=£OÑþÚ§ïÿ´UçþÇÙÖóíU—B'ƒ}B³ERB<ÓÒcþˆrÓàÂφüÈkL±ÃÒþúp6õ²€´×ÕÌ#`ƒŠ}«.^ —Ï×½‡ƒµââÉóå#Ç*×nÝ¢p’ÄD#ž’ˆ#Còý¦„g :w¶b#`ÇêÿÆðÁíUÀð²ñR»Ç«#ùL±†2–s¼.B#C‘¼qæTyt¢ý‰¦‰ ðÞ.ìEDÓÙWßÙñ8LaØHÿ­ãÑOÙF³˜t2?éNaù/qfÄf”¢¢Š*¾š˜Q?›•£s"U´{R†rMÚt„7IìrÌÛÕ§º@Ó#C0=Í3?–öÏÅøâDüm ‰ “J¥>ôšuðo3«@yÕ#QüUÒÿ£å¾ž;êê´Ÿ p$嘕4γ„‹ùФ;?&ÄQM´ó°Âb¨î¹qÊïߌý àá‡Ã2˜‘)ÍQl8Ib nÉ-F 9ƒ(JH/j¢Z‰ÙRfµB~jõ_íåXÑíu9'¥è˜›£Õ˜Z£d:ˆ²ÝÒÑd÷vЫ1æÑUÂ\üx»öžº·”ÞÞRc)N¬-”tëXú{­@ËgësëÖóŽ/Ô’R¾dìÝÎð1¢3/T–nW© ®°ÀX¦Â›È€:e“ µ±g#`RD<Õ ½!©Açÿm¤$»‹1&ÒèåÇÊù¦Y‡=Xˆ›÷@gïÛl乶µDþÎ×ÆuÞg‰ZѾýΨ1 ¦;°,J`S ”úÀé·‘¢%¤(¿x±øvOßžÊRSú3åh…ÐÞœ?G®Š ƒ¥ž”I¤“NFE]ß×ïìÔóßÎl½öß,òLëGFi¹S×F’‘ª_<þ¼¡ ™]+c•ŒwÒÑZÞnƒ?wu›¦®G#Ca¦ R(ü«ÞÈmöial?ŸÂ­áÝyÔ’Sÿ*våºPðŒ³Zòºœ4Š =•#CÞX­±fqì¸RZâÎi7f×F‰HD¥r£#`)¯Ûu`ä\³–NøP¬ss63êÖè®#gß· 4j@E8I#`ß[šž`s“1Š)éÍ­ÂB6‡Ös:YÆê¬9„>§Í^³ñ%3‚.éh0ÌÏÃèðùÈ,j¤´æ?vÇäæ`þx×—Ð`¾lHI˜"­Åê<åg8ˆ¸iˆ¹ý­—‰ “Àûs€Ü|Ä5ëÏþžñäj’ÅEó+ç°*E>ÇQ hg—·¸<]GÒÌD†ÌÔd-YšJ`¯¶‚£6þÑsß®~ÌvÙ8üµïtÑÝ#­ÿÙÇô]s˼¯šü´‡dË«YªUåRlÂõ\˜«®ôÙËú§x5ƒJ™/òn¯~Žv_õ!I÷«ìWó²®ägÙYË=»€Óï¿hZ(¼>ùù͘;#-Ä*’ {(8ª×­¬íÀÊz29뇃ç¹É"&¿¯^Ë×:fj«¨vj”qÙמ‡åJpù%;8SÍÕ#C“FNBl†‰‡ãU»2“K-çèâë‚sé¿r¬'‡W/Û_T†ÙòoçóßíÉžó[5êQ×w{æÊ#%2"ÆÐ… ,çè?T÷°Í†C#3pòe³‡M{ð~Öc1¢€;%Ê®ãgµ¢2Ù«0žt&µçÁN2Œ<¬Æ0†XÛG±Ä›¡Ücf`JÛßœÿúñPÕˬNÛùmIÇ1šjˆü1·û;D·„䟚˜éùEã¤ñ¥;>¬¼ÛF‡¢( áÉÅ[ÔŠÏÐLwŽ×0r@oò¤U±úÙQºèÖô|̽ýµ¨˜tV éŸ,Áì&„žûŽ†>2do{ÝÜ`ÞPÈN°O¿òÖMj„Pôv©6C }Í!úꘈ¿¯–¾¿Ãå—c„‚%ÀìÒšcþŠL4Âlø²Ò_v.ãÜ~¢°‹AÒìQù’†/Z(0ÎË묳>-Ù!ÍPÀ¾TuK:tŇ B"{¾TKOUVÎ×(NZ}zYðÒ…TAJIL<¡Î=ÎÚ›dPvæL$Bs!}z8Ð\pÂ鶧žd1¢Â«á™>QúÈMÙ€|R‡£)"Ÿó2^(;]hòmèæËîÁŒ4qZhïe2,#C*€FM߃- ®rŸ±×Ÿ„?Ÿ8Ãò³ÝÛ·L5–¥ñf'å© d¦™›×&M“ߥCoé(?~ù±Ö¥Dáâè>—ô¤–‚ ÞÜþü}ô¯EÃäp”!˜‚§}I)Džÿ;³B¨=HÙ){ûé°ZHú|sƒh—^L²,Š,§sîIFŽ¶µ»ô¹#`Ei)Ž$»gÞˆù÷ôÙî‹¿xžDsÏšƒ©øÃØøæ1Ÿ¶{錄ŠL?gi:WöLÊf˜ýó)àüØ–—ÚÉS«ªO |uÁaÈjCИlXÉl):(ÞÜ6J0,'eZoL/Æ&[4?çí „Ý‘h:Cˆ#C&åó×Ñ7·ÿ‹„øf>‡?é>;úîþXn üŽœ´®’‡wÏ<â[„¡Ú¨Íõ;fÛ*1‡-Ü›ñoS]"«K…#`òä©XØżT šÔWž«›ü5;u¿]O{›ÌýS|éÛ)‹t#.Îýf™E#œ˜aò3 gt–â”$[6:ž½>Ç0?5Žz¿yÃFßèy=Ý·²åJ`¿›ŒÖµNS™PW¯¶.4F5¸¹±Nmh¬ylñ‘¤Ö>h§]LÀùX€¹#C·ÇY²ƒéGôa)íˆq”:ÿÆn?~Ïé‚ùMÏ}øÜÚÛ"Q0ÍÙLX@™7e®${º•Ê9ãŽeÖ/±O\â#CѬücÞ"vÞÌòíÊcBåö M’ÓebŒÀúˆ˜ê}ôx#`#CÉsî‹ðû¡±ÿY·Å¦F~«ô|U/3³ùŒôAŸ=cÍcˆT÷Û”·ÓJ1»kï æbì$í_…úµþƒ#`A`£û85í¥{<RJA` aŠþì?ºŸâ%>Q¡í¯ºGq¢ÓâÒbŠ1Õ¡e¤Õ’Z4k)…×=z|ú¨ãË[%V5¢“hñxn(¥"ô yB§ t†¦4‡öãÓîï/—ßÏgCNÙÖÅf«övy8Ŭ>ùìž &þ‡ph?ÀëýlÁìWl[b0Ѓ£9tÖ3”²ß#-ïnÔünâ /èàô¹áâ7¿LÚø€=¥ÛâÆE(P|µo¹Òr—‘¦"m(3`¼²s#C…ŠŒr¤ž+o_³ÞÚ{µE+‡%ÎVޱö –óË„›Tn˜Ÿ¢ô‡#`ëS‹²áß×ÐýÐqïzòuýZ5èGª7ye89ƒf%Û–Øåüý Oùó=X¿7Çx´@13hÓLþþ^ ¤Ä¦" ;K?÷¼ßñôsŸÅÌ™>pÂë‘ì>—œÅ8Ú=g‘yÞ¡5#-ØÜ‘ëQ»iþ˜+Þg¾¶#Cùu‚Ûë?ŽþÏ“&òï2IË_‹ÌÜóuòc#`:Aýý µcÂ5,ÿ˜§ù¯ñ»‰–L.þrfœÉÃÀ©ðùó~´°B‰'{„7fYLEú°gŠu¢0kDQ™©]Ü>Æ,m¶ÛD³ï°üîX‡3, ÄRLƒ;,Ü¿1‘W·„Ì„k¹.T DP§´È ‡µV~¶.¢ŽES$ƒ)¹W>LÃé°Ž÷šjWœ@‹ÃÁýˆt’>hÿ7hƒ÷wÒÁÍrŠ6MÅ2 3ܲrŸl,û„b̲t±tŽ–‡êö寖 ¼-'¦tþÌt…Ÿª@:Dž¶¾;gÊãš!”>!öT¼ç#C‚†$÷ŠKå‘Œñ#7O¢ÃçË×q´j/VCXqµŸ¢>Ž=aB¤“Ëílñ©1oI' ë?Ÿe@JÇÇÊuuu—†u< å"SÉ ø ÉDÒ$lß—=N/ÁŽOÌ®WÒÇ~ù¶Yµ†6à-Fb=– Ãͬ*1÷øüÎENU¦#`ð8Bë¯3êT§Ð•tS’‡É;û¡7ÉÉÔ4eÎæP·l`¾UÖ;ýÏ‹ÕB™^ÎËx bؘ+FS÷ãøwŸõ.™"-ñ“<êS‡ƒ—qU¢ÉÇØA}Ç*Ý4šˆ!3´¤Ô:òIW·7¿7ŒgäÈ©ÕòŠÓÍÜëw kOür0/~fçú&̜ȟÓ'ŸëÍb5Ì<†q‚ÆêæX›ßYªH-=̨M Ä¿‡º§”b!ÇIöã(D'cËwÍ5Cò-«ò{Áœýé/¿\m–¹¾U;Å:I–„ñZïLJ0éñC›Î}èì‹l ¿ˆomã+ï5šÖE}zôÅîüªslë„iõ2¦50Òð“œ`¨Uÿ_ïz­CÛ>uãGÛê_5¦Ù,Ü5$ „\ˆÞñ™ˆz½3eðûñ Õν”Þøqñ#°úåV5Ÿ¦ÓÉïtË-hÇZl#`ã£þ–8eœég³JëÓáô"4H`Îc2APª½ëÚ7ùõã„£µ|$ªó¼2Aí/ÛWw,m1NÇÓó87Ò#`À°;Å&%B›`XU‹‚QAXŠEééÊD%¾ÊR¬:M‡xGdBc_ôÀÜÃÈ„ÙÌ4ªkùm'„e{ëÝD®ïï;r¿f£ñ…qåT…©…ù)ìô(¡È#Ñ&C¸ëì—4Éy½O/“ǕţŠ:ëÖÄî|í¨Mn}žU­×=uß}s…a‰Ý:}ÔžÇj0›¡²KL·Ò°1êκ˜B‘n#C¶G~ú®m‰¦º4·©§§+™\YõÓ¦L“ωôdnM_ìÔÁý¯É•½Ä¥Š0åsÙ^Þ¿÷µ¬á7žÏ\TÔTDyÝe°‡rÇ7.!Eü–#C8F!ÈÅyÙçU(I‘F“Òèªp}|Öûë±åŒý¶¬“ÄÁ§öþ¬õ$?nÅýŒþÛ¦Ýßl,÷eÀ²Õñȵü|æñž­¾ÜgËã¹yp“µ"?[ ËÊUõQºUdÝe9BÒw)¥ÙÌ °¾ $Ñ¥BLØø¹–Ÿ›Ÿ"Mµú£›zÍ­Õ½Êk«xl] ˜”új!(áÊM+é±-­_éô¨9Gà3÷ýÞutìø,pŽ:ùë¬óÞáeÃÖ,ΔýL$`àû9r:±èþÜ[ŽùÚ08˜õ)ÑÂ¥îÛ}Ït[fé< Ù0J4CöBXFä÷êñ¯¯·ìãøÎŒ®z·c‹'£7#`ˆš¢§¾¯/'=3\ùRƒÊoïÞ`¾XâþOÑeA(ƒ˜r8‚>/¤Ýíœ®Ï ·Ô»‚'äíßäj0_ñÏjô±°š]ÒW·Ugã¼Õià‡Ô|£Ú¥\…ú|kjuå»SZáJÛdÓE½$®2Íæôfh×–¿'Žé:s¶ra7$ZH‡H9W¥1Q(N‡-3)Ùe¼Wï6òß ZÚw_æùØîæ.GØ®üT5yõM&à×ôɞجÈ7>ÑE\ ‡¿&³#-Þù8†1òš´óœãÂɨËKZç#-Þ“ AG£1Ž×Äx칚Ǣj°µö48ˆù¸CvþW2\CŽ‡>A5´;Ëèôâ'jx0¥2^Õp\*=1¼ÕùAú1ƒ*mðä(˜XaùµŒ£o>»C~–+:¸ï†±Ù½ÐÃì{­Ãõ[Ä„x6VjúæBAoN2Âýΰ–7 j4þÂ|]ökÛ÷\ãƒííÚøÑו–=¨ÿw*>m*š#C°fW?L—Ýñ¨‰í~Rr#`_=¶¯%¢—¤T)Iqo8HäåJߥ_ïa©U/yîŠPC‡ÝÓ_>ߊ¸Ú³“²BCVBä3x¢™ò†Ð$yvýZ#ßðñãÐí«§—f½¹¤7vœl˜Ç"€Ù|æǶ)QÀ }„[ÿu£ä©ayÙ÷|ë¹Û¢Ž·r/¿õbʤanÙz—(Iu›UVöœ5­}kDz­¶Qm2³pf€V1g\Ûí'Ó¬F5ö9úŵ#C/åfu3o<áó6ðw5þ…óú'o‡+U ‘|ÛÜ&C…K2‚Z ÍÅÐê‚ÁÚ0yŵºôµ­:utdûðMQë#C­×ùîÐQð 5Tøîu.í~÷Yæ -ƒö¤s`Ä‚B¢¼Ùì¬E žjfÑ•‰[flý·2'rû‡Í~(fÿWùg‰aèvb>ƒ‰1?ɱå ÁgþBñÝþ~ÝÖ{êÿ#Ä÷‹©¦k&´NCNì·õÈo<#¹‚ÎLÂr@ÝJø´ŠéPוµšéxe¥3çKëFξ³!{ˆì¿+‡ßýøÐuó+¿~ïÏs £®aï‚HwøédPU¾;§#(ø—pª³yXhåì-vÆ>5lJ‹±Xò9ï‡Üà\}DѳÐÅÑ™ChÙ¶ëDµSíZåjòík²¨Ô:T|œ!$&ïߨ$6¨Ý”‰©+›z9H`AqE"¹je›w¼;”7ÌY-*m³®þ|:è„qN·|M1‹Ò4¥ñ£Œ1“ë­·Ò(kU©…GÓ³Ãì,çkÃtZÁú5ªgXÕÄUùuúžLÒ‰rJÙÂ}©å}|}úë}“¾’–ìíPé°õ+I-1î(©þ9íت~#©2^‹Ëº5FBúûÕ{htsO| Ù¯® ‰³~ á|wÊ^¾%±ÊaŒ©O—‰Ýøäç;bQ!Öþ]òë^\¼hØqÓsÇÑ<Ûö¤¶ÉBÿ‹êB¬½xïzºöîîÛshmòù+#`„Ñ”U#`QT%ôFüIÞ *Ñc—íXqã­Æ7õ>9 ‚|’mìÖöêPÔa0 „þ.²‰–% ù£쟞Àvé¬â]Ë’pûˆ+SïfDA~þÛ.4øŸ[“‰ô¸h#-ö³)™€lɵò°¬ùT¶ÎIWP~š_¿s¥ïßsŽQ„Ñ‘6FAçAê“Ìl‘¾#Ce2F"HØ»ABkÞHb=ñgŠL†Us#CòbANî;½ÆWUôùèî·(À!•"‚L×p‚/)žNb¥y¬iƒ‘È =Ý,ì—Ðî+T8iÂ`l¼—Òjæ½.gƒŸ+(ϽG™Ãwìªt£-#`ð,›#>“—×öãMJ§Pòªdb1’دÀheŸW EBýg¤!ì÷öðN’ÓmÍFyÿ€Òa‡-Ž¸GÑ>gs|"ƒÀˆF’:37u#`4ì_3øYòáÅî¡žÝkå¿ŠkRºþV¶†Xy*ß°“IÉÛD·ÖO(‘Ó^c¬]Re#-Ñ^·FDA]R*7š½=8ÛTØ…÷Øœ3¤(9%´¡Ël“Éß<'éæS“B4'%;Ió´p„å/ã6á¼ î“£ä‘,o¬ÊZŸ\´h43«X3–L ,-!üÚWvÝöMS,?O¯ÑdÈœiRŠ—'#-ª*ýìzçõú½ÙÃ#`/”#` $™>Ϲè¤~û§É ð—ô-¹Ÿ«Æ( O^—‹„ã/Þà¤KZ0â³³ºùå[âXáôÝŽvâ|ñÍ,ºÖòZö<Õó_˜¢Ð¬ù¢„$°„¸˜ä`n–™\}ÝolÓÉ:!æ}ûýžg8”BS"9è#CǽCÁ#-1D·¹PÓ­uDqs û`Ô pŸÕlçƟؘó€3½½ŽÒïaó^È„¨ÝmçU³š[|ퟺÿÇz7ƒŠß{ŠQ‡X™šÚïÝT\ÁÃÉ*~nIþZ1%+Ò¨ÙÌÇø‘ÝpŽ7##Ú¤¿½Í(O!Íüò}™hû;û>˜-b;üpGÈ‹¹î6#`_fsŽÉ9Ü›¨ ¿³$çãWMÍH¾^µ©ô -jÛç * ‹öø±¥b#-¬†LœÀ<c^{3¾fýÎwˆ uVoGQ‹«® îÖ#C›†aœ{*ÍØ_qu™yJ’Lø0cÄ0ƒ± QRáÝ/ÆaŠAø×õ&ê_—Áá÷™‹›h‘!ÿzxñéKòªÆLÞ¬3‹n0¾+L"g|BÌ¢®J)ƒÝµŸ#-u·–SVœýǃQør¶×IZ+¼¼ã½¿—¢¯óT¦w îŒêŸ;‘VSoä³ÎÜUžÊ«t;[å}+šÇ'’øª8s¬*÷ ÌÏT‰aÒõb\×’ÔÞ åÞ p¨@ß Ññt…#-¦S­5Ä:©[ã“iï]¨ÔT#Öê#`¦?·-¿è–€ù½âWJT9"†G ¿+ý4c»º]“³¯lŸ¿.Z®z´Qu¶'öÔ¢ú4±3J/ÃçÒÞã3s_R«¹0Ut}ÏÂ×ðYT‚2-¹LŸ?;Ý5µŸ8þßÙÝ6‘¨O3Ô:Æø8ý¼9_áªøpæ© ™aNšR2#`ñïðUÄ™À9Š3 Cœç<þ˜9ê;Ô9-úµïýj!-%½â»¯hç$¤Ù²¿‹Ôeÿ«ò>ce²V8aDG=šO§Ç$‘u#`™* öÀÚáÈá;CþŒ”ù@QÑ} ÁÒ <|ýqŽ™ýu…×εný8`ÕšÞTü­šÊáŠn˜gn(:ë|¿w§‹Õ=”kÁÌN8!‡vI2Š3yÁoš¥$ÒÀ¦b´VÞÜLûø…ûá]~s73‰ åc/ •šÇåk¸ÇâÛ^çCØ…D6<%5,([åa›üùŒm3Nb®ѪùÑîðäà$-­˜Æ€´.`_áØž©ÛàåæVå*åEBõlg[•]¥V@8¦¥Æ×-SæÙÉ®TºÆC$vÇí~Ü»^%ÛîËlò&A÷+ƒø÷¢¢ÞêB¹x·’ö:›‘ijàتz>úÿ«zÎ=lc(Iô´ÆÏ„—ÚééŠWtä ûS•>%ü<;[±Ø©Jˆ;*Õ5¹¦ý«p‹žV&ßêktÝjØšŽ-DÒ·O¢ÿJj&-á(¢‡ÒlÃ'K8¬Ç4Cß2zóóüv^î}XaÙÔ5ÜLâ,™#-Ѻ±@€˜I=gÎ¥WLÛ•ªÈÕ+p  lE<´cmP½ˆuëIfsÁ;Bôqçˆ1îéƺ¯ù9v¸f:jƒ'#`+zèÉÒ¤’¥I$½ˆä÷?&Z“ŸЉù°ÞuÏ¥žÞê…¤Y‡¹´Ã) %ô¯£Ju­F)ûR³V—élKðåÆThÝq)öHYÒ/²þ¥³_Ö°H¸“Yš!œúãLòiófEh骆dårÓÙ,Jž&¢`úÁQB!E¯ûn±EªM¼Ä…òg8*ßäs;ÅÈ EgÚá‘Ç·EdÞRØt0 p7Ûj>ÏO+Ý­ýpçåqm¢³†(Ïai$ÐŒþP‹pvë.Ñö*û—.#`æ÷×7#¼ÃÈ é²w=jPïT‘˜^û‚Ý#CE \„ç?. .`WÚOèÅ~3†P²îíÞ]#wg®&T§sUÆîùWµ#C\ëLi¡vC¾ç+#`èå¥E÷<í5EkQúW·a²çÞ‰ŒSŽÉ0$ÄüµB€ªïìöÜÂ{{PvB¾~ÿ·¢O.Žÿ=[#C:û¯š{îu}[Ñ3µCÝ$éDÇ.Ò£¡üeír»öõ†»~ßM»[”3”ç ¥â :#`|âëBwtR]wFÁòP‚Ñí`CŠ%H?#C2’0*jdu]^8Îf‹*-Æ‹%Ŷ³:ð²" 屃)qQÅ”½GÚxÂŒ7p[ÈRÆQN)Û‰ÌØÛ)’¼dɤý-S¦áÑ©„}j“:Ô¬ÑæýL4a wKnUR#Cq¾1ù×}Ÿ3!2öCæÜHkFQÀ£éPéʾÑNŠJËÖù”B^“P¤m)Î`6Üæd¦0jèªR9¨¼àýŸò˜ J¤8çØçò®É8d@ðä§ð³¬ø³ü#C‰ëϺØy}ã{à‡¿Sø$èîç𭪥/›N*õE3“ÞO|QÅ‚÷(ž+}œÏ7Þ©0I™Ï~Kžqq¬‘…îób2=ðKBòà¨ì^b´Þ±¾Í›– s½:Ò¨¯_d£ÙYÞCϾÑKöA2öêâ#`º³ò¥=É¢ØRjÑÅ´Ä·é~C¥RŠ¹â—·2¸z#`œwâr$¨¿Å9;f5øŸ«M&GþNÍÒèÝü,n3¿ý<žUúì´gø©†ÝVGèô\rçÇ/²n&#Qx—@ÝÌ9+Õ5éÌðúR¶§ž+µI¥Ï-hOÊÉÈN©ª/µèZ¤ì害‰ÈÑ=`ztâUãyB7íœ?ïdßiNH1”îÎÙ»ÀÈz8­¸©·é¿lÖ€p›]DMUÕVòjÅ CÞéÑfO£a …{ÈbHÆ`£ÊÍJ©>e‘‘Ö³– ¿ÛÊäí2š¥ˆ™é6()mÍ´¦u‹ êTr¸¤üª)q B¯X“Ø· ¸;\bXvú*Ë·"n%ýÑY~!ç&ªG,çðÉ®&ûk}eù;¶vÍöL0êù¸p{*Zª»Ù„H Ï#`ñt b  –LoPÅú|Ï7ýîY¹„vM:ú<*C:(Ê3•E'úÉB&B¿AKôkY)#CLE/ø7Is–%Hè߉†™èKyû6öµV§÷Þww®Îpxã#Cb·aÐLnQi4g&L&¬%‘úÃý µœWžºC‹ïÝ̉KãN$†xÞ}A×¼9kaþD=ó0ÈYš5¦²,X¨ÅóÕÙA#`‡âï®N+Ýä`þÿ鯧N—±b‚‡3äÚªC%ÊHÀçŠù6¢UÌ¢oùWðgúy!]+=ôRCšOòF÷zh ½Mkvð;†¾÷€Ýs©¬¿„φ؇r¸$$¡`ª9=Œê_0Ì·XZpb>#Ê°)w÷äÀPÔê(ÚÖ$HNö#CÑ£â`Ûíi—)(,ÜQÄ#`¯ ²X*‡ÅÎtuƈ¶ü¶FžwŒ99µ~vÚõ±s7YÝ Ð~æ¿ŸNoqmôòˆŒTÈúqkÓååË_HçwÍ\jKþ$ü«ÏÛFèå|•rgÉÛ¤ŒQÅ5— µšqæwÁüüâ)s¥¢›=Á&±“#-æõ4Š4ÚùÇ=ÛŒ½Ž[ª¢²ù`öÓëãE7hÚ»¸}ñÛQ‘yYFÞTò:õsLYyò'µþ‡+‚+{8¹—í.بóóÓ7)§ç‹ªwq2—Ñ nLbÎ2ç4|c>s’š+úµËÆ“‡!<¼Têdu¥ªd|ÜÙá#NçÅsÃV#Cý? ¬pH1(}¡Ìþ—è—læ`?ƒ¹Ò»ž#]^4qþmìÊÕ’XT®£#`þ¶ðZ½PVŠ¡ ÷,‹ËJDÞ©²#`/U°·òÂÎá¢~÷¸8åžØöÝläóc²Œáቔ9\=…)jÐ`÷ J76ÅU)eAvÅ:Óû*Ó îJ[«î®NègÂúQ†Ao¨,\ÁBKYzA8&$Lq$’ø©lî£#\Îw§Ô›7†;Ч#C ¤ÃI2X’„iȸÄábøŸCÞ t”ä®-g6V…J;6W962ú¨­E#`7.Ãõ¹EÁLãÑ—#CB31Á¡ùB°Ð¨é#`ðwÉRòrédßYvûìÔÿv¾ÀØ_ˆiìu‘^D¢!Ëgü¾›RÖ@|5>9·…ŸqfÜ™Êé¤]G…Ä©!ÁwYDKÇúWl÷ôúãb.2Š…#CôÝøè‹ìmŸ®ÀŒ#Ìù“‚)iÜ¥b#`BUöy@çóðÿ£æ×e³.:Œëß¾fåU&%ü‘hÍM;~¾Š8ÇìÁ¹ÎÍØ…%9ý«{?&<‰é Içû´2ÜX7õÿWÏ™0N{KÀžýv¬|>Z#Cóz%U8·ì ˆòx»Š‰”à@ "ò £ ÉçñÁTÐ4ÏgpáÞ ¦´‡o|>YüæÌþø±~‡á‚r@?!ˆëïÕL„¿`÷‚\ž£B;¡DÞè®ÂŠF¥`R,+g³è¹}oöþn#-ÿ3; |}tÒa‘A@„ÙDZŒnÖü™GV©iµÎîwÿ7ŸÛð_ãᯡÊ;â£Øuw/.ùû9÷1×õþ¡&#-ø+÷9Ò#`—œ ìDÔG˜¶ÜOi B”‘ ÃD #IAHL¢}Ò}Xÿµ§åß;£f'Å%ªà£ùÓÍÊ F/ÎrIá¹ÍM5c0ð)ÆzUC÷5þº(s¥8OXïçPÊ“?Íñùg]«ÚŸÐ}[ÿ.½Ôé`¯ UÉ@ë#-]Øtæ×¥£ô–!ÿ##$X\CHŽÙ B0¢¡#`i,T(ô²r*ÚÉ£‘Èåôxs–Ð!3Ò=´ÉS­IÈ iç]¥Ý–YQE)DU÷2B™µV ­Óli#`ÎÎc%Ë—šF“6¥7-±l¦‘L{³ïüÌƶ ¤Ü«Å¶¨lÕ}L$ïSàå˜xluëbôKáö¸í°hª€Të!‹<êr)r÷t³9:• ÷ ED’dÂyeÃâ׶Ó+nqÈ‘ð‚õF¦©CÞØÔ–¹ZEÖœŠF“åYmd;AAB„˜rA²/ç’R´¦¯¯9_$k—qì*ûÜ"éTNI!LÍ)ƒšüöRz{ô¼^]ðQŠú(¢·9(ÃfÊàþ¾#`6Ïù0³+Íš *GE׋Ið9cüž5¾„ô§ƒEÆõZâÌUygŽѼkËc±U»ò­‹Ž,ü«K†½¸ƒ^éšû?ðÇ uèÛH&ò.¶Œ<$ ˆç:Ì:Ù®mž «,óþ‹Gèß:U£vèûf˜æþãöà_24̤’ `“¿Ùñôyß Æ<þw×®Úbû›Û]4ÖêBÈõ@õÙúaÏæþÝ8,ý½»í!X'ù†)ÿPÖuáš*|U#ó¯ðHïm2Œuû¶d«#-Ê4ÿŒÛHóF7&D  aÕu²h©aa‹wc¢Òd#`h“Áj™òxš Å¢íixH…JÔäùY‰‘ˆqô{ìÛÑЗÌñ4£.ÍF†çP5” è›é˜ôùKir[6Œ0 ñ¸‚‹ÔÐJÒ^·†¼»lú¾x•ÃÖv†çðäÃgSo4Å~”þ^Ãè|)vЇ˜{ÞÐôßé#`)Oã´>Œ¦€9¸j#`"½]<ã @\‰r¡^×ïçÈù'œBýEžÄ0‡æùCÛ7}¼Mì©ó56“†¥%·ø~Ž¸: d`ªÅƒð~ñ=ǺTŒù:'ó”}_ Mëß‚pŸÅ&ƒùËǵY>±ëª¹<($)KÊÅÌ=û½Œ£yP†÷øÌÏçÕ^‹ r¾ß¢)¾©'fããR:dá%ŠÊÔxš¸¾X—dâ‚C½F¾mC¯ä‚ä„Ùê绫¥ñó?~#-\qôK¡J—Hd¡Ã@<¸4h,™QÓ§MûÏyâÜ,®.’ ɘÙ°`W"¨Ê‰A'ˆÂ T@Ò!F\ýÝ/Žöé#ûh²E!­ÖçÑ5»<å™ÊâXEuÍÐÀ?HƒÍ\ùg{ñ‹Ä=üªùùš3]Tþñ!W¿Si¤I× gœ¯{/–3áÅd®¾H³[ïp×ÏÊÝ%^ÌÉá2f0ˆ_Ö¢j¥Ó ÿ5`hËXó_ÙËÝãX¹éBO¬_?ì×½Eú³qå´a&+*||cú¼ª›b´Hû~©™¬ª,ÞUÑŠÑ/»ó(øè?ÞM·¨çw?É¿¯R¾s÷ï±è×ß΋“(¨LA ƒ‡á‚ëíøžÝGüøzztÑS¶z¤!úK²hìe£‚\  ©flY ˆN u=Ë‘#-Þ}ïRûØ]#C=pÒ4•E#@Á+T${ó"¸ÏÏ·Ÿ»ïºÂÈr¥¤D¨ä#C'+¨ZùOç€Þ #CxF1%}Ýqt‡”O²ÐÒ-¤NšZX‡ôÆ”ïìþfÊ㻫Y+:Æ~r9æ”~Œ67€P6«#-ÇKfúHF÷÷vÉûn,ÕO3Völ=Ÿ}­výÔtÖ2£–˜^‚Q8N'3m‹"Y×´_üA¡ÎÇ[Å¥Z¹~ K _€'Zˆà©0"ãØœBÜË¢é.©Û×Ë`—xõ}¸j¤ìeÓJ—jóß5éQ/î"ùÁH†4x .j%°^Ñ7­\{Ãïv%¬¸í6#-—jú¡Œ^2ã~¹—ò³£ øDl@E Óê¾8évÇ„×ƱtÔÞ´’|ûnßåFG>ݼH›tƒ‰Œå(^åf§†~8œEÁš «sÆPß«\®#-7IC«ÌPtm‡Ÿßá^NÎÍI³ÛÝÛÈ5ä(á°¦Á¹ÇÞŠe#Cï^ˆCB“JïE¶=JÔˆµB…?ÍϱðÛ&ïJd»ì'd÷·ŠõBöM-QJW¨2RÄk¹ÝÚ$ˆï0Ž&jMµ!=ÏcÊAìS@¦ù¡—tšÕõ4-Úˆ:’ÑS¬\aÚè4ç‡;̶·Èþ,qª/ŒØ´BD{õ‡~é1#-L©@É <Ÿ'\bóœ\£šŒU µ#-V »4}]ÐËš©4êñy¶)0᛽N“±~÷QÀÓöÅ#`¡Ò±¥àñv¥ A¬o¯ËìÆ8â,%{€×îw‰Æ¢ˆ¥’ ´ÔM0Œb* "úV.Ñã°k Å4 «ÎJiÙ¾mix¢Àmpá°Š4І•I*44ö×-biùv¨¼DÆ1³0*+'ÖÃlÁ*JÞŽ¸<.¢Œ¶J4vÓÈdÖÄr _¶~Rû<ç¯gžÂi(–—l“y—4üäÌÒy`5Ÿ\0ò€=â–’ åô*íM#CÒjO^`Îåh72ÇÕ­ôš\E!ù龜¯|F`x43?;ÂÄ¢aÍ©pß”ãðáV ±}±¢‘î0´Ñ¼}þÍÂ,)» Íë79^ì±EŠ9×AÄÎIƒ¹fBGËYø6|ÀoEÒ^ô‚avzY£"•ƒ£«hȳ®Æ."câ7ŸLn#` ‰-/Ï¡“zØ”9ª½EÛP„&Ò¸´×2òÎ^­+èê6#HP“ÓlÃ$b#`«6RjA61 #CîqQ‹©UpÖ0ÄñÎ& ˆ0âø±„è¥hâk/)#-¶ßsjba§7-I ˆ8‡ ’BB&=Y+Ôøhä¦4§há1ĹÕ+äåÃÇU˜×fTŽ^0ºÞÂöó«jÉ@e”&ÅpÚ¨lÉÑ•ˆ:õ-43¹Ô:4ˆÖØ,¨Ñ†:q¢X£?í0êôе) GxFsº•Êùeé0æÛ¿ÓY™Æ}¾YÁŠye?Ù[Aiï&{s×ðùFÚávÓRÞ’-‹Çàw>¹€§Ï*ÝhŒ8^¥Pl.³§9±‚pdíæǘEé]P]\*öê¢Ì¸¿•æHŽË\ÇSUõ‡.pÇEŽyÁØ ¼…CÑB¢ÞÁ‰)Ø@Ycjg#CNêúU·eʶ,²KPDr(M!#ä×AUŸ ×Ó#`XwF(ð±2GŠPQ„ö#-ðÙÍFÞ=TŽ*¿Ýîí­ü?žcÒGÉóKô@níãÉO0Õåìôÿ_?“ÚFÓÍJMmÞå½NÜŒL‚t¬O #WÎõ$˜‹5¸=LÜ L‰´÷>< åi\Š›¤©TÍ>,ˆì8ìü¨àÕxwÞy\•M”}«HÝI­Â#ÓËn¶§;ŠìŒ;“Âê¯8½O…‚·Ôòü+·ý*ÓwFц·cpž0JÓ&ùã©ï窑Óì×Â×™õ§Å…?ìWÕ´{KèóúÁòÒ#Cô”ùcfâGð{†'æ("Eç*ëñj·Þ«ÖŸkâØŽü¬/Ÿ­“ñì—GBÊF#C¼˜êÔxG]HÏTw]·‘Ýá»Í¡oNÞ¶–¥™N áþí<™|/¡ü©³|=áðM$ìäuU¦i\aÁ[q.µ ë¾Æ¥EL|'±–õ³÷_~ò£aÛ<‘p.Ù*šŸ€q¡€o¨™òH é¯'ÃÃw·EL9å#-÷tjPÌ ûE°£(:·Ü—Ø”û-Sü¿[êßIH Á…H…Eêï†Ñö’]ȃu;G_n;Ïž_ÏÛw°ùþl“¡Ü˜õÏ.Ÿ²ïx 9Ÿ]A#-ÿ¾A#C`ùŒáܹmñjJ+ÄAÔåDgQÖpz0åxpq#©#a½âkƒùfF~ñ¨41¾#Cû¶iù§ß:uÚ{èÛƒ‚Ž¼“£È/ˆ»ÎÓs‰6±Œ€ú“t!Àëubö”ÅДås;Õ@´!ÚÖë@€j%o©÷Áå,E„ûžÁ7ª­ù¾J{$šÉ„Š9#C5DÖªûÙ?â e½û\)º™â»-A¬Fi´¤ÍÀÕôöö#`‘¢  #-?rMS—Ú[/"}i Њöͳ÷?Ž6ÖÞ‹¨L‘^R>ï××Zk=Ž.fÛÞ¡01 umïi©Ë›ª~Ë×!ÐÔÍÍVr†\ÈÍì_;Ýe/ ùæ¢g†‡Ÿ+£ˆöŒ¼„˜Æ_ ¼£1”($Í Tžòn€ÄÉ~—Ï]JH¢‘êJ?ÆÜ´ߟ¦M½ï-¥#`øG‡‹æµˆ•S¸ â&öt&Ž«£¦»¦è¯[ÞîÏ=tþÙ‰X,i*ü225#qt[#`ŽõÇæ§]yêeDÃÒãTãnL$ƒº‹|Þ2Ô׿hYn¹¾Nø|â`óäœríßÊ[uVø}!¦ß4nžš”#‡tv·l!ÐOXFqxÅÒ™¥r+eéƱ»<½zhéøú:까èmqQr¦Ä°¸¤@–Nq¼u5p–^°¨H¸bkØd\É’½&Lä€F¥Aß’h0OÊ› EkÇåÕ£¨#C?ö¯ ÃâÑÐwùÝ® ‡¯1é— Ô]Ïùj ñ»rQ:¥#`ˆÅzb#…ôiôŒÉ?%Yì##-Ò‚ †Æý[ÆÅÿ=üý#ÇôÍ¿ÁzäË£g¼5t;¨9Úö75Â-J£jÛˆzÎ+ÀàxUü—j`ÑÇñ›ånZ¨‚A‰ãu²ÝvK–©yõîÿYʽßn›ÊAdYy:bÄ £Õœe+Ì0et<3Xj3òXkM÷ä1BE3#-Z$‡ä¼{7&×óÔP[ܲۯè[£Æl#`“´jP›lÙ¢P<Ù'£hððϤX“IB!™v‹m­æ³šñ$CKü#`ÜÚWÖµÎç׎gŽÎ$$ixuÜýjÈJF‚Y•`ÞU6˜Àvê¨Ú³A†Ëº¨±’¦Àçdˆñ¯·[÷(hò¦\”k€èØ FA³Küܺ·f3ãKàT3Ÿ#CUψÖùŠƒæ2ÞôÆ×]~JÚ_{Žšðòò᎑9CZÖ:\Ê8é£jš™~Ž¿V"ZÍ(®Ò¿¯úåM¼¿H†áý¿ÁÒ/íã•ÓR9ºÀšf²åAÒ@¡R9ös¨±—CœEèB[‡£žAãÄ¥X?'j#`ãرgn4"õ¯M¶±y¤¡ç." 8m7‰B†x;8TCûU{n»|“‡õÒó]Aé »kûõS¹ÓäÞʹGh#COsÖýц÷ëŠæ6BZc&Z¤íRövmR,6}«„AÇÍu¼Á3² ýEßc'Ž¹½ñ¶3Þ.`´¯ËÛV~cx&xÇ·)¹~$¹óÓžˆ4vü|Œ§1®†yÝÔa‚á‹ #`mÒù'7idMÑ„[ç’µz›VñmnÏ^p#-¥%¡Šdp°šóbÀ›þ#C\<ˆüêÅ]…F§™o²•vùÖÙ#V¨û[BtñgµÛƒ4×?–ýW`ìŽÝ‚•«wmóêI—‡Òž™©÷Ñøζ\óøƧê’fÔ³+’D¨Gƒ–ý“ùá‚Ž¿ƒÆw?Oðj ¿,æÃãUb5‚‚ë—4žØ(í<å#²ˆRE‡çjU‘rñåϪ™ûŸ3*¥þ×À®_…øítÍôAhêê£tPÅæÓ¨ÔMè~7ºc“@s½,ãSð‡ŽvÕàò›}ófeü-² f¥±¤K.òå~2ÆÝë3ñúy¿lbBW„êÅꢜKZõ_y™É€ù¢áð»mºíš5—)38ÿ.ˆ¦æaãæ½wüK!íõ‡ŠRÓ±tÏîæézb¦ö–ù¾1SJ§Š½U!ˆcMù>Å^öקÑy %`‚5Dù<ñCŒàÔmíOEoZ0’S8j°€«0±0—ŽC»ûŸó£ìÍòûyn<~Á4#`ƒ3Å‘cú|ÞBGŠFgnþýgóóÉøzƒZßµjâ‚©¿‚S¨ml’“â¾#C—A$¢é¼©Ùéf„kQ¯QGSså'Ÿ¯»Ü)aµJYìQ‚x'‹˜ CYl}›L‘â…ô¼sCþM„“$’Xž›—#C$ˆ‘ãpìšwt÷nß„³C-湑ÕúÈ@HßFñ=û±´±Ò3"cˆw£õzHöM Ôo2\¦xTDQGS0 5DïøFzÄÅÌ-Ú\– /`žBÙQm[ônë°ˆš,¯Íøé{5<#>c¨#`3¨½6åtúNE‡~[ôØÀEÔGÇÀ^&‰ÓvðàíûÅ8éÃg& 7ãsXx|f= ‚´3‚9CkêÞÑ$Ls–ê5ÝU±”Ÿ‹Ü:QHŠJàðçf¡Fção°Üñ¶åŽOåâ版ižN¿ ˆ8ÐuVZ΢9l¸J½˜a¨ŽïŒÁ]Ú5ù„úÏ· Æ=­…×8èØÀdíŒWÙχl/Ì{[ToÏRÇdFâ$æ°|V*ùøÆŽù;WƒGfáÏ{s)Ì8tŽs¨¸×h׈‰d95¹<ã›×¹VëïÇPgMÎ#C¦×<¸ŠÀ,œ„ È]X¨ŽäÁFvXë×ò5î{Å.}°S¸/¨9¬†¿» 7|×V4ÖF¡µè/MäÒ)Ò&°4§LYº:n™lƒƒ—beo’šü=c¶õà âmÐzE˜'>*á9•<¦œÏ ‘ÇM #`ž¦äâúÇÒ\Óù Ž\›á~“T]*9n"ž¹bæ®!¯Í{{NæuœFÂ\Ɖ]ÎÕ ñ3}c7‘Z¡ü¶7ä决ÏŸM.¢°"™*(\×pÜìrˆ„Ž£µƒ¡¨îKMþïÛ'Ü€¼ôëÌ ÊüÇýÌ"qõ|·º#Ct¯ê¿0]ÔÜÂéG.ÄV!š³pÛA=á§h$#`š¬zk?gL6]È·`HÄ?ÄMÊú(X"Šàõ>Áw\¦%ÃÜ_¦7YØ% Š`—º®ˆ”ïg £”–è+i¸oꬑﺋg Ì“„]z8TÜ -BØ5Qîz«U8c ®C© ê¹oµ°›ÊIÃëßSGBHÉ6޼Ѵ⑛Tškå©©áh™Bpyu:¥Â£éß‘ÎØ€B¬œ·G¥Üž,^ ;¢Õ"×-º€2 #-ÔdGG[춽–”EfŠ{4µ½¬£ai„CÜ\ Ë“ +(¹«©xdżÏXIb»ÖB¶"#C>ß‚±Ýù—s~Y†U¿K喦Ƅ>wÌJðê_¦üŽØ2Iß!PÓ$l™Ä9ëÎ{ÞÓ.Lâñ `1Õ²’³€Ûš¢s”ï[€5Ž{™ï®:†›òÓ)è)B‹šÞ@ra5Õb=˜(ãá6ØØÖÂÛ9éÖ<Ð]>ÐLßmQß»µÞ‡&it@íoë‘-A0ðÅysðÇY£lu(~ï„GX"^§?€†I•Ò0^±EwYøè1cjÚÍ‹„0‹ZõiUvØ,±UdY‚¶ë -#`1Œ{÷,àwdÂð¸¢LlŽ¹âEä­,@Ê–Hç©x€á@·à0ÝAjÐx/}îºò#·íyë»9kio_Pâ‹õoR5=6ò{Š2y–¼ð6þÅ2Æù‚ùGtž1@z¼H¾OCºùBx Ò¹#Ccˆ~püçIõ)Ð}<_°HÈóù}Ÿ#`›¾‰‡;$Ëp?NÅò¢Q5ffž®çååf Ž\)t *;—Æî{Rq럡œ5ä;äñ‘H;g85I!6ðuv¾5ä£më#Àç'ë.Q“3…ê‹rõ²£ÈÇ Ü\¼íÚÎg˜xûÙYâeˆ   ­#„^Äù”D‰ìM jßòÙù–ë1mÖxËH~JqGBßY'²BÖOl—µÓ{@/«×¿â›Œ˜³m¨á 0Á.âãTêGl—´ÒIxQ¦É*ÏÁuñ|szAºçlqÕÓLeË…X#Cx`FpKª•ŽêiÚá6&¬0ô8`‚ƒ.¨¼bí¿³¸ëé[¥NeÖ>P>Ô?܇Àóý_ˆRÀ#`D`Õ/×T,Š9ªˆ!ïñ#`îóâÝ}[âOw»¤=¾bX cÈ|¤´ò´ÂBPÞxøV_Mßhvò¿—={¿&ä;ûÑ%GЪ„I$ˆDwSïó¸]Ãßyäï{7O«áðó¢B˜A)±êB€s'¤éù#-3Xdñ¿¿Pt†”ë}?¬,¨#`L~r]Ç·z{¼NÓzî÷ž;œ]&Í‚…Š€L*äP]•£e[Oun.™awˆM5ÀÆ8|ÎgÛî#Cà9íïÁÖ5ôàwÏ—ÿŒe4qÙ"Œ•m4™b‡­B)²pQÑm¼½8ÿJ‘ (˜l/ÛÏsA•Cô:‡Œ{¬0æaâ&á®}VÆíc 1{‘<6ÛÛÅç\]©ÎT°ÈŒcþHŽraôÔ0ºýº*:²¼¸`ñw;um¯÷~7ÓNw®ß..}é#-äÊ”ŸÔÆÅÓ‘û¨:ù5ÇNýx<{ƒ¯ ­¼ðt2ðE#EU5 H©–IÄÑ#šâ<¼oâû??#œäú!ììñý+ëÛ—éùº¤…¢‡ÀÊ’f  (g˜Ñ@%”D–»ù^Vá—ÎTC}‚šg·AÛÓ~èƒ$#CëþÖAìxçÚ-¶ƒ‡që7¹Ï#`Bö!?ß ù}Ÿ¦·L$‚wõßlë#òäjD:ûm¥ü´IlÜ¡ø®Ö¡ÈH{5ûg ¸ E;_[„X0³öD(i]ùd’ôÿF˜¢#`“oë72Ê?5žçÚhP›ôÓñÕ0ª’H¢s}ëI¢"‡5^£wËä>çËÆ[,9†’Ö®‚¯=G^»2Ú87—Íò¡#Ÿç MË5{½9Ç|¾e ‹#-B€¦š.¾8§"86ä@ Bðì=mMw\OëÖè]Â7cÍŒžF@@ä»ÉrˆüoéÉI@]J¸TêÈ,Z³C5UP¥À‚CÇà]âÕú6›8LÕ#`€-`‚c²äů#¸²›Ó;±û²JB_ŸâTë3^b HS¹58ShÃ]Ö†¸™´ad½íêtULTDEIÑùæûÃС¥b øKÇ/‘L"”%¸$ €ßÍK̽®ïaPc|b¼ŠõrÜs  #- ›ì]“¾¤ â,NÔ ¬®dDº×áë3&II½V\%¢‚©„¯W ýrW°×ósþíÜ×@k˜–oÖËÁ‘#Î:ÿvuÂâ<•ðƒŸÀï¯.£ÛãÞõ¬ ÍŒF8¿;B€å^> Ñ_§’V ›®3vXƱò˜…dÑúýŸ©Þ"p_.]…™ùñcA±uS°ÊŠ¨4¬B5ÞÑw„`Ï»é°}p0ÞWû°ñýÖm1ùoÇNသéE 8’¬ê[ο‚>²óA¶#-„t¢,?I^îX#‹;hMaXÃ&RŒwj€ Bno¨ö¿æ­kmKùnμ7÷ø&Ì}‚àØ~ïÍ]C²Ï)]¨ï{z i4@DDï“h&Æf#`´²bOç¯íI¯ƒøsïÔÂfÊ(¥ÙfÚ”Œã—µVR‡A Tç6:¶¥¦ŽAÉt𿳚¹pÀ9@Ã?97gdñh)~ü~Ã]lÂDGI3$_¶ÑE#CóúÀvs«²„óRAP=âIãê²"ÖWñ!ƒÀßN´i ÓËŸA4D‡|XŠ(¢ŠÄŠq^Qç\_#C##-ïe±b^tï-cUÍA ‹´ÇË/x—ã̉–$hh4æ*(Žl‘N"½HëÓ;#C¡éß­ÕÍ@ð_]~“苃þ,…Ô#ã ˆˆ::Q0ŽƒD'q:*d4Ì]©îÎ-º‘Ù½8À€í³›M†{ç<âªÕ gô Éé¡Úñ•ê ¸Çd;‡oÞko ¯!(2ŒòC`ͱ†^AÉ š"f.ÈÔRàõ¸”§È—¤ ÎŽÇ%ì©òŒ]&¡’%ËXkѧ—?ì6Íq·0ç'|ižá<”–‘¹Ÿœ#Éó^#€P¦´;5 Žßü|òfj!ÔchèË‘È4¬µ§ ÚXH“‘bØiÒQÞe4-¶Ù¾ãp¾}¯on¼øêeûЧ^ÄöŸ±0u| [?xȯ4£^Êbõ-L‚)àŒiË.ðR#=ÖùÝÌÐ Ô°Šb¢Šª­6„þÀÑHZ5¦ß¹·o‹re8²Ùm&h,vi^#C±GL¹%òT¬^þ ŸÛYÔå[iL++³ËlêÑ”*¯î®•çr^¯Öa 2~bëew—®|ʧ³jW˜—Ƕ–úš#CG&+V&fd1G¥poÆÄJ2iB4¡AûKlþ6BùÁ¾Û]Ðò÷áà9Ÿ+Ä#¨â ð{ÎhØGõ(:»ë‚[¤Ø˜r®|¿Eñ°¥”uŽEÿT"«¨[N'œS°•t®†Ö¨À¿ªÙ¦XAHSPR¨Læ—4+ïÿVÝú8÷Õ M<2Û?›gȆÄÅo0P}ë^Ü“ž•=ÌøÊÿÃÜïc?E†éïaûc­Q¥X»5F«# Ì©®¯Éö²yCÃ&ø M«gjâí@½«9÷Ù««LYØàœî ¬o(ô—þe0{bY‰ þ©'á×¹öÚ¼/,QóÁ¨#'FO/cxYáLè=­Å2®z…ä@¬™™¼vhG_t:(æŸ>4ð)5æ R#sm²÷Sü±çÈ×¥P¬œ%,DU\Þ ;ëçÆŸÉË¥§¾º¥Óʬ/Örð®¿bÁý|ߟ D ¿yžOûxDíŒ0£ a,Ñâaå+¸MÛ—yºÏ#iʲå#`’¡=ј{Ô)¢E‹œÏ,©¥ +šÉp´Š-3ßȪÁ\ó¸ â'yá»Ä}D{oäÇ~ØaÑÊfl!õ&e/ÞÊxœ@¬âß¹:Úk‡æoÓE4d ù|biÝwìllÓ¦êó|âO$$ÃÇúùÿ'ŸîþÕ÷þ»4Ïã©#-`Êñ8¨xò¶£„vW£é½¶§¸¡²ÝÏÅFÙù~š÷¦' Þô{tá™6Îo!p‡‚…=CgÏåߟ¤™ì´¸¬ºš—ZeŒq§^ž¡•²g«õwp6öÅ… wÊæFÓmåmë#™ÙNasE4ì˜ÐÚdxhy@zÓ…ƒ„¶¬#)„„ŒqÀÕZÓ3W­j¬5cÅšTÎ3›»q³ 1n+}f³c?¥†UE9ᮑKÇÜäQ´ç“ïƲí…~þžs Þˆ”ÚBFn„E÷G3äÇ  œÔjpIߣ0ñð.BÃõ(Ä励m’zD¡}oÿ7×?·Ç¶P*GRW»üþ¯˜npË%)YäîXpuü£>è(¦±ô‚Ž¨‘Èé >·¼€T#C‚xÑLäÍ’õ˜.ËÆS”qIÖž+¹«àÐœF›ÔÄ›¯ë;Iê31Ù7‚!—_^f_Ó®ï±ö"ݱöïZ5ÊóYão9ËžFb{s™“€¿Åœ·uuF¬ÆùYëßšŽ×ÑAªÐºŒa²‚Ui›ƒ¦¥ŒC`¸9F&t@ɨЌ#`ú'$Þ¬ˆ”…ê‰#å»v·ZTÓ!X¥B#-#C'Tÿ™û)SðÊn}t2IëÝßÞk­‹ÂÄb#C£G[{y‹Ã_?tž!yTÚ‰®ìÖWæÅ¿+>ŸøtÖ™»ù°Æ= Ûgb"Š:ËÏNØ4(@DЄqü’› õÙ‘“ö,ÀG‹W5’ÏyŒÇ\Vnéé—ÄÝ?à|BÁ†dõýýzyë}µQzÈë|_Í1É;0™úµ°YºC5ÁÅeÃÞzõ ìk^~ö¾øqù | ¨×ƒèÕŒë3Ï#CNÝdòŒÎq†X «]ó…ß1#`ü°@š?Y,7It߬Ó,ƒ°¬9 ·» ©Ðx~9hàôûWÆue£m¢j|«ñæb­¹Ço0'Rç>ÂR#`P¡ß!"ùÛ*ëÞàø/í\}$E;_f “*À`”#` \‘Q*ÊEr‹ò2 …Å8Ù@JoÊ¢—Ó÷¿?‹…nú¨5«ÚÖ7D~i…îæMc/tó;IüWÐo®#ÎïúçÄÙ5Ž}åâ¨NßR$”ço´óÇÊMo5ñ,Q¼q\Ù×Á•8K÷èOP£*Ù<°Å/I¦#ÉDG‰9‚Èݘ૽Ú_a VUέA•Âæñºª#O…ž6ŒQàŽÓq¸"ì+KV§ªÛ¨€-]®¿¦ZÔRþ‰!Lf.'|ŒšÜ›pôC¨ìŤÃ6þ[O[ŒöÆ¥ߥœA˜Ýó´µÕLi®É]¾ôÅz›ª®Fûeà’¥½…QÚûL¨#ud‡)Ç àÐy#`n-gÁÃyu¿–¼ÏÒ ‰R3Ì'ùtU¸ôã‰Òvíýê©‘È»ÐL³Šr¦ê…‹žf}ÝßÏ_'1á —#`C3“¶–À„ŒAv@êË%¨YÃ^¬þÌq_·l&÷¹Ç¤{ê\vŠÇLJíÕ—îweÂ]«uK°’G$¦<`)mNÁUíî³`oV¾s;wõå $i´Î#C~ót±Q]ð?Íß@´ˆ[Žrv‹Çø¿†1Ä>TÐè và<|òyɤò>®ÕeEH—mö‘³ùE/X‘îX9k¼çnoœ÷’Vs¹üù›À…ì¦S¦¿•!¶ñDb‰ÈÇ@¡áÂÙdDXïÜrú—ì]AØEîh !}΄ùô@þ;6;·1:¨Öí½š<©JX¹;¶öw¶x! zÙôzÕçõ¾™¯ðEÌ¥Ô®¶Šºäô"¸ÁF¼ËˆÖó¶€^ªC†— ÞnmcýÂf÷X-å4?ò† ×WÖñpˆªÅæŽ\ô̳päüMä½n%?äêF(FERQÊ?•Â…òMÞVQħâò¥”,ó«ΔÙØTDN=\ùj"Õì;f¼—¨!ÊjþQþD¦;ÆéyH1ƒËxA#-Ç-ÓâvÎÂù.ÐãZ[ÁûáÊ¥^3“Y^7•Âš×¼e¨–˜ú¬®˜,fBmüø1#C²`¹…Wƒ%#ÍWDœô›ã³zÕÌvD¦6Zñ³HРݔŎøDóÆ,²j9üqoyzo ¤,_5GUSó¨Ù<§ÃÿH¼#(ä.Èž†—0çès¡3#-Ép5˜Û>µéqßà(3ÎDÙ[&TýE6—¾k C»ÝŸžþVÅE\Ìs¬ƒÝQx")Trïº_=*øÓ­öm„Ñ8ªÖ®ù¬C;ß2’]'–pÏvo>>Ñ·ÖÏ'´¹±°àFÖ1\K€%ÐxhW”l8‰Šh€j» :ž‰}Ê7”!LWDgW9÷#õ¨®g{ß>g×ßÝx’ix""8ä‰È œ‰ÕqÛ%“”‚è¶÷-ak†E‡—ÃKÄ0¨«äÓ»‹§GtǼòÛZ–*1Æ )Enóí¬°Ç)ÉUQ°&+Õµ\æ˜tb9 :Ý1/é1ƒƒ9|ùÊj_ ÐتÄÒ;M aasAÂ#`f·Ó9 sOk<’#CÞµäHï/8Ͳß*ÙóàÀ78íüÌjöÁF»DIÌ…9PL©4Þ$p†&I5 ¦€•†cI¢]ÜëI%¿MñÖç\ ìæc)ͦæ çÈçÊŠܲØä&w¾Ë°˜ŠìŽÇP”‚¨J6öLs#`FèºNg9uA#…FÁ8]°^¶¡í¸qCzCvrDt_Ýôü´w>‹^$K£íß³# ±{±&yJ\] Ë!!{{Ù®ÞÒ¸ #-͉öÕ÷O'ñ¦ì&,üÇÉø+xvK—méþÝõK\©¼@G•hzõ 솥ã«7vƒáªâë°ÄN\ù@ášØ½Ê¡&Í' ÉqÏK2g½(ŽB…BªW‡‘F_.Pu”½b¯~¶|¸Üt"#Cfä”·×A)*l2D’i³f±b(ä¨EMI\ÁsɆfUTR Bù¼^ô‰‰OP¬`ú/sÆ"Óʾ>51T³©Øx¨ .´å¾â+¤ITl€F Uê53v‡âùó;nçÏÛ¢Ès«ï¼1H±…^@à„waø®<;íÞ>}±FoÏÒSÞ˜¥›Y¦éŠa,ûö}|½‹m½uätZ —zßoê¿hôG·Ÿ!À ÎQÜÅs7ËÌ#`"LB‚·%øªdû);Ó;mÖê³ñU î»ͺqÉ5à6sm¢fúÀWHëÎçŸSN€<%“ŒuCs¹ú—³+ÆâÁ‹Õß•žöÙ[cÛ Í¸DK…â¸-V¨¤C`úà^A 1Y*Þ¨¾î¸SkñÃ&ÚAG†d/Ú—8ªƒéÑ0¼ûx=æv$ØIïî꜔8»'§ºP‰R –РIÅ7Wsèt­×'¨ŒðvVësÜôK*‘ÏçH5 –€^Rã ñ7ÍÃøø¸ÒÕœç9᫪#--#`èx¹XÔüfÙê_t®ÔyFèmØ¢ÕçlÈÞJ+|®»IQÍGs i#-4ôaGæ0»©óDQ#`In`z½NmÏO#`Iv\IK~ýçór39ØËèáÒtw]¢NTÛõ›"—ÞD°u­b;lùA IgPaLN&!üê)vŒ y e²Õ”(UŠ‹J°ä ߦõôÀ øáýktk¸ÌR±W8©I³Ð»1:zlz°œ;…Ó×è!Üb‰8Ä)Ö6¶ÛQ"ˆ^àV0½¦êÂë#`¾Ìå—(Åò¹²(fµÚx'wGÄì9®9}0`˜ò®²äýüÉÊd¹grFÞvãœCi39×Ãr™’~©*&Cõ %SËãÁò'YVM&Ü™é%@Œ¼Fˆ¥Äµý—öÉK”aRàâÛ,þHÂ!WjĶd.戣"ÚöD 'ÕéõQ-ËÁKÂ\»V µ÷ÊgeÌ›cq éíÖ÷^2϶´Œ¨éJmÐÛî`´BEUqé“Ø9Dãî1kQì'Ú–˜Ó爹TmŽÍXYÈMS #-‚¨X(ÀÞ`‹Ó±àPÂ6üÐÖ]ÖKs”§Œ#®h3íUV[xUÅzæ#`BÚºø|¦A^xÛßjc]ÓãÄl¡ß„“¦õÝË—SûìOk%ï\tÍZW…Mb¼9l…þÞ;³áXVdV©Ø-;š^xݘ„Ú7vXeÑÖýͲbv87+¦L00k(ò¼2 ß<áæ ZÂÆzDpþ~­ô÷öžxC|…Ìbà…·¦ÎÓTU/?ãô ÖD’J땳ålºá9”$!’n²ÞÛ¶UñkøÌ-ˆH=#-mɈ®ó„Û uÜŒ†ŠUF8ìçÑLky#C6“œYRóɪ=°t„©!Æ ­â®»¢)CŒÔMë–ê×pxÕ¦ªÆ´ÒP²â ÖÃ"RÀ‘H.KqA¯à¹JRÑÏ‘Pýá^·wø•*~ ºÝIˆP¦ÜØœæÖù›N+Ú"QùN¼)s… æËMJçêÇ.S ¬UV7ÎÚí^õ¿8ÉÂx¢çOsÌ R sän!Ï%m½7_fM­ãx¦¢+ײöÍ«MômP#C| uŸ5ÍHÈdœ`á¦Á¸IÛÕv/ rP\Ñ'jõRÛ øª‘q¬3ÓdpÈ;jRèÚYÊvÛ1äPÒpæÛEÐŽ{çJÕ„ê™ãÅÙ^_>Fψnœ†®÷öãwÉA†4\`%}ÏÅL[»R[}ËWètºªnQÈyqŽÆàÒâ‹rÀ^{?ŒAlm…òæL3£¿o„0“{zû#Cc›‡¡PŽ#C†_7”&ýŠ¸Ó-µV* PÒ|5= 0„ t²b Bø™0JHJü\­€,zøe8bÛcv‡²˜ušbŒ=GkgÙ vƒD¸£¼8s¶½C<ÀÜyäîƒu(ü2U½®tØC“À#^©É5Ø?ºº3±¤´#ù½a¾>Ûƒ°²!Ðydø1Gä3#C;*vήJù®nßµ&“pÜ­:ù÷|ù”ŸuÐÅÂðªAŠ© £6pÇFÛ2l.rž³€š‰R¥/hSL-„à#` R€å[JÍQ ÎX_”õ]ý•¢½¯Á<ÈvRãh‚ÐBûZºèŸ‡nuZÌ=7 :ûMïƒ-¦Y?­–(ø4š5ðd±Pfçwì¿×è‹ÇŽs¸øõ;®òæ­Þê䈼WAËo”Ÿ±Fw¹øÐAνzï•g)EÅ嶻’-jfi÷büœÑà¯-Ù:Ä_®ºbˆ2¿G¨r% }sdÔ0¸;#C”ÑçKŒŽq$¼DF÷¬#I/îDjå÷çÓF¼q•ðàÝ(÷â#-éŽÖ(šÍÉTZrŸËìß™1Ÿ§»NþŽ¨=âìUﺣgr¤§Ÿš#CŸÏ…ý¼vó+²Ó©ÌÚÉ{µ)p[– –ÕABISõÀð$ÉK"½S­8Òj0€îWËRÿ&3Fíž3ÕpB*é\#`_4œ–Œ9Ì ?ÓYJ<%™>’B÷^ôÒAÀãºè5#`O¶½Ûá1–úœŸˆOèåÊóãÎtq;ù×a[§®mA™¸-U7ì32qn”óy¹Åœ[32Œg"”ú%2`×.ZGŽéUe¾Švò¾¶R€¸+‚ya‚#CMAtÔƒëõQÍ7UAÎfkÛT½=S÷º‰_,½ø«D¶Éó‡òç GlqëY3¤úO¾ga䮣æI´#`‘Æ9ò·‹ìz#49é„í><¼îÙó~ôèVÍ¡ÐJxø²sç³àn#-¨ƒi3uë‘Â)Hµ†xàÂà/ŠÊHQ¡º0ÂßMÔ@…=N›:2TM#CåoŒœU–hº›œ€õ da:U @ÙRm“§'è(çøë“Ìé·„ð€wŒ5@µb˜l1X•P¤…^:ÌüÞè;b«ï$=0cóǜƲûžèŸsÃm>œoq‰î}kºâ¨öñ´pÌ[‘•Åu(i‘Æ£zÕ a1ºJ%ÄÐÕ†–— XÞãÝ9ÜY§(Û®nV¬€ØÈ2ѱ”F`È5îü÷‡Ñî^sƒLç0Û€¿S¹q¿D&²0—#`eL#`1Zñ¹6*¬¹‚º”,KãùŸkô?ñ;fÏDñÓ|Ü|ˆnc¹pkë{#CTt¹]z ê «ÿ¿³k®aEq eb¼üÐ,bŒµ%/R?nÃÖ|®~±Sv©fð¡Q¿›mÞ³’®yó¯mñÃé4³0W)Й!$&[˜}ÛGc>\18EýŽœß lœUã‘`œ+C8â||zÌ Æ1#`-#`TPI» 7œ¹\7¡¸¢¹Gq¥–©ÁÂËù§³L´Ql´”Ž.ÿ™ñ"ÄVê\5Ä…ÉŸ(óÚ(¶o¯·­ãÅaL‘¯Ë¹zg|Ñ[ùJEþÈí2*xøO:¼Hµ$âì_ú¶Ç‚ªDÈC05üµBd[(\tÐÁ:Þç|qv¶Fg„Åq^zŽ¸D†§¹/EÓ…ÞguoþЈˆ-qAq°ØA(#`|™„@ÿ#CÃSˆ(>ØÌUìwpa3±7¨æëŒr´ª«)9B¨rþÆo†¹…)Àë@nGHä.#`vá6Àéëä¿ÙCžšñÈJS5{j®‚ç1ð:ÖXˆ7S¿Qà$0c·JHa–_wÒ2psÉAù¶FPŠ®ª\Ú@C#-Êd´Þ0|!Õî{Ç„N-–ª‡«yª}zJJü³jŽ8ouyÞÁ·õÅùr×p>"ã9‚5ØÁ#CdñÄÔÿ6™æ¤4¦(=ô`2l¬ù³­[Œ#`¹ÓÖŠ™I…þ ÅA.‰ÔŒ²’¬ÛS 8?­ÜÑí¤Â2{#-Ã’ £uœ^ÏlðÀ/#-pÜþ”ÀšúN(š#-†jC}ƒq/h_nüæ6HÉÔø„m•Ùª2¹m͸ã}èhs+!"ì–æ…w+…uë#×uÃÚuH Tøø㯷ӹã—Äï=õVkc1¾CÌýߌÆo§—ód/$v\ÍC˜xL¡Î!ýfåú~³\aÏ*ãp¦ÅÄJæK•T ûFosê¡åˆpÏødçg«šdë‹”ªžãMª?bÃ)#-Ó}ø[|Þ.µi¥ u@úõKðÅȽKKÜ(®#C —kØ7ôÚ[DÜ NÙÁ°#h)²ü!‡64æDë‚€×díAÕÜ1~ŽYØ[t{™´GÄ9üpbÐSüýSó?möù£á'½V>i†p¨ãµvMckDb™DtMqö¼³KçAufîz9ÃÂÒjÊÅ22é ')’‹Óp[‰Mv;Õá›)¢Ìa o>£uå]Ǣƅæ;×—·'õ»JK|:Û^¤´‰‘ì¦q^¹ÌyZeé@Wcåw黿<å9!Ê!2EϸUœyÅ'˜ç#`¦ñ‰&ÂèÕÄíz,vª¹o‹´QÛrµ4±"®äðq»YL©ŠMÉQr¡@ä, ;´Sȶ“¼TR:äMTÉBÈÙ烶D5©µ5ìø›ùMó¢q}OqWžšLE#C#+0½˜ØCT”áÏÒ`Ì»>Û`/ÊÈP°·2tz“áÓGƒÈÔ\éꦺZ G$S±g:ùܬáŽ2Dö~vüªÖ¸Ýù"$f÷AÎ~ýLçˆ_„%´ÔBx‡´¤wO9}”Øœröþ¬æ=ˆ?ë­XÏææ'£ªyt™$¸Ì™qå}õxÈŽ™¬UME˳ÄÃN¹íòç‡#õ1ði‹RAñ’*ý{÷ÅñìçÙëñâZÑøµœ}ÅvÑ(µBãxÁ …ä„;Ü0†ÎûÍ|[ښʉãÓè×YìD89îá ÑQ²ÞþeÉø&I˜É‘[~l]V$6“%Ý.hÁ¾7,gËÓH‡ë¤.ŸÆoôÛÓ4tƒV~¼íMfþñ:í ª£m³õ6¦÷a?ˆ‡:.ü<6œª‘õÚœáQ½¥­W¸H_ ·ÉPïª>3#-ZñòîÙ@4 ðµ “KéÁ#CÂp§ìÍ÷wñ8G>?ÑÓŸ8Ä^þ“¤Ì˜Ràî_éí"^¶Q0•|u5ñ’²Ã÷Ó~c[$Û;Ù”<Û›(©¥4‹ëø?íðl€ì$›ËAÄþóÎÆ÷fÿÏ~r ý¨Þq³;Oð–ØjHócãǧ¼ºÜš šå/¤Ì¹BW·1Uóðt.k¢¯¤›j=H6õâ¥2ÓóÄé­¶4‡Žå&„î¯Y6Y¶ôúþ½b3²œÑùÞT=MàÀÑ'#-¨íÑx(êké݀贙‹ŽÈFÒz‘›}§ú3sŠ—ñ´RETPî&tþXóø³¼ž#Có:sh:G¿‡ïù#°kQ{‚`ϬRçi‹„p!ãYß“A{ÑÁ&5ip\b# ªûÖ bþU« î„—ø,s{6uR50|( ¬WÏ&‰Àüª#-Õ/xgos{j_`óqƒ‹È]Ž¼£m•áãWõts_#CDÙfz0Ûtãq×)’€ÙÏøBXÀ LÙúÜ­{(y©ê)ƒª¬ñúò[_¡p8î›Ñו« Œr,K\­nk¡Ò^+U¹öJ1趷‘Ç­$;§¯¶gûwŒ£¿°7wüÂáŸrûi.Êá“#C—÷Ffƒñã–˜OW PQQD|l›wŽql%Ë50QUtÆ’‹™ÄÐit$B@.[~ÕmPeOŽ¡²Ã—š´Éà\è.ò÷[\È¢¢š‰ÿE‘9RØ$~ݵˆÎ_Ÿ[=!ŸùÂÚ;SÙbY,gÿ2þ ànljÎ5#Cê»Dåþ’l§<‰²IG&~|±£:öç·Sþ¾4uá±MÎÐ;9¶#Cût´Á)•,õ×^4YAdòBQà¢6“ŠT#CÆõAqŽéXÚ{xŽ·tœó"q÷íË¿¨jøÔ¡!Î`9¥jµ0Îx¤"Ôw¸8`‹nãCË5z$•Ò€löÅûlã »R=‰Ú]+L@š(£ÙȺe•(cÜòæÞ:–¼ñíÖñÖO•ec²­ÚÃxžÂ줙ÚI3{#CÚ‘'Æ=ÅjæÆÒ‹ëŒqÑö20Úc9?^ˆìù [iÓ?^Mïmn„-ÓûÜ÷·â_'.2Få–í!áa¥P†!ŽËî݃EIÛ„º‚f„*W¦á«,m-X¹2¹ôÁ{×Á]½cƾ2ÎP®#CK8Â:·G7IAX2”@ÛT^8ì[Ù|rl’bŒÚÜâÄ>ô4qØdòvØÕ5äι∡£²‚0Ër1™jèÃ"ÔÛY* ®¼à£H F½w³%ƒD&£W—¥ÙÐS®ë¶/%a¡ö÷üÞYѶtZ’Lî‡ÙÙœLµD²*[“Ψ1¬Õ®1¶7éÁ È1Ë·s&­ÆF1`vžÜ&=/IiµØÝ~Û)`¬Œm(1F˜ãs#`Ó.M å Ü4™ÁÜb¶E®3ÆTÈ.´ßo/‡s0»ÑÇËÃTÈsÛPä1Ù Y×ddî}Ø׎8}kýÝAÙ6ðŸ¥Ò÷½@vTrˆéE6lœçc‹³ ¼L6ÁI[·”ß—ë:wõçX¾ÝãÐÆùí1ÃÊßh¸™ÚºøŸÍ#CÐÉPâfòÞ{?hc¬à>n‚­˜Nü—½ÞwãwCÊ“HÕ–°;|Ëe‡(7âVr#HÎì*XØú 5«HÊõÇçAÝ+rNd9Àn)«c4´R#-gSô†Òà#`(D's†Ë³§#-ÃY‘ÄÇ¡´Âl,ŽrWE ³B^Q3r¹5·,Y+¨·E+6b»¼Ç8dbRy©1:*¦ËÍ™IZ2‚å˜ãxÎaj¢õH"cˆö‘ga·l;ÄjÎhÎ7ÓªC•ÚEuÝGºœß™Ð®¢øh®s fƒ9¸lÔ­0’(2ÝTKí&ߧLÈ9m´{ ÝÏÒ|J\6Ǹãv%»¿Þàî*™ü$Á<¨÷‹£¦§òu,E„Qí4ëȳë »ÝyÃÔ%ç&dgB‹öÉù*¯Ë`-5Š·Ã>‘Êü„ÎÇgŸÓÁ ¿Åš2*'wcN!Õ†#CºDõäÎb™ˆ$]›˜¦þè0bfî¶A­Ê7ÍîìÔî;;K‰«œX3›©¬ï„6((næÇ–ÞaJìÀ@agÍ#-pXW‚!þ»ÿ¿_¾ -ØÂ;?Õw¸‡o²$(^”7õcl3Ô8²›ÐVP{ìs]Y?f~ÀÀݶ¾ku÷ %ÁL΂0\3w£>·ù¾91xQuˬâÊãI_<Äc!Ú%Òákå¾Ê¢òM#`Ñ^Fþæ¢'$õ€ò«ÝŽè¿ p_ßpZÛ®{†G,ØP’W;üzý>yúún_ÂcÕ…‚ 6äèP3 dçoSÛÆøøF‹o\5Ÿz¼‡|^‘ð­kýP`?b (‰UƒÇ~wãr­òØ|TH²Ól×O¡D£3ŠÑ´éôŽõ²^©3ñžÌywµà<ˆQš÷(¦D7I‘ó¥(X Ù¾ÇÕÒ-¿:ÚÅìà?7²›â·WVÜ–Ò€ÞÝV?Í\þº^ùrvi¬»NalÙG5K÷r¾˜ÙÎùÆùólÔîdHÐGNýq#C¸©4p2(z­Ò yV¸M¿,cÈ8‚<É0Ê`_’fx<`bÚ#CË;øîâ"{<+‡6\¤y,F¨¸Ìt¸àÞ8`Þµ8¯>£›Æ¹]¾ßZóèäÚrGj?uµÿÚ“ ‹N…ïÛ8#C¼#å‚¢ž‰|é 3ÛýKôßxÔZYŸ¾_š”¡–ìÍÂä|­¹釿,òÑcì“.¢¢›,+}Ÿl,GdV2vU¼9ªFL:VT·ç¼:çÂ-;“”?ã9ËŸE¤ÈN˜JÐ.ÔQ2ô«1+ó<#`Q娾ßjšÞ};÷ -xdâ`°AI¹uúºV9Wã7³'dÈÅY¸b¡l–gF(*Pp÷Ïi[V¥áˆ4ßmaÇðäo–aI{â^;&»Ø7¦ñEÑåuâšhþÇÜ郣‚ø!Ð i¤y&ð•èÉÂr_HÔ{þîqÖÿ“Ãøûõå%G^y ¸›™³‡ÅÆqljˆÊÁÃì½÷âðîG×õ3Ô+¸ö*›'%ób«±¢oìÅ°Jõ¤}#C£\å‡vPÖl¸`B#`ˆ’k–Ûüçíøÿ9·«8û4þõÅžFˆzá»öxÕÔv«Oíß#`H(e$ñºJxlO`Ä'“Éý‘žö#,|h[Å”¬€°b)éiÔÚ¹8Þ”RÂ#Uíu´m<>ìHÆiñ#CR$ukä”´8ÒœæPvì<æ·‡Öo¶ñµé>;Lì⇡4¤N;L_E†/ƒª#¿ #`£ @HQ6®&UpðÞæs̳iż8ˆ€˜¨x«,e´ÓÊ«ª`<¶²”ÓÆaçxJ¢q­æ -ÕŸ„§®u#C\±Rª5‰)8³k†#­ÖÚVR,4(¡Eš —Dìï®vÐÚ´Û:Ä€u²‰c%3;µ²¦C£Œ%r¬o÷ÍÑ«í|C}éÖÆ#‰â\â &äT̆¨"6¢ö‘eÖ"&SÂ(}E!SÚÛ13[;鹧6ABŒ0ÄaÍCµ&áabZ½×’š¿x:£p¿uî M–=ç¾ })qîSLžä€&ÿDœ¿KªóÏQGg!ó¯N{U,ËÆýpÑ<ÑX‡hghÓÅk¯_߬~ÌDcS²”´î#`~¸“‰øÃE|sˆH¥‡¾ÁzüÛGµ…ößý ®·Ç.ÿ‚5‰iÄá¡û|_)Ìž±÷+³{·ƒêO8ÃÓD¦{-}ÿu4[cï~9‚íö˜‘Ê·íêíRèùb »¡`_5ÍÑ`¡™ø¹ÖáGâúÌ\¦»ôÇåÛˆfcƒxl»yk¨/Ða«ü’zý‡‰ì¿_xFJIéúïÐIôUûëÒNg¤ú//m@ÔßrŸ>Dªð ‚`Y³ÍVQ„‰BI³3W4ˆW#CÌw_qì=ˆüÄMèä:²Ráóú=-é€]B;\—\•Ý_;$]'S©57þ_yü†xün¸ÿ OàîA–ð`§Í#Cqý,£d#-º…}\~”‚@sÿ]I'6 ½¾W£¼C×­Üï[ ¯'3¡¶·O£RA&:6Óå>é O€îîïèØÄmÒð]G¾>gAµRF#PMkh#-ò ¼Œ™Ia¤]Ö+KždAƒÜþKñÅÊKÚ„9¾4t¿’0<ýhÒ=7W­Õùc²p»YÎÈñPëÖ3sÈë;’œd€ˆØ©š9³$ˆ#`4)­Ñ7I×RýÍ^‡ˆ7?nª#‡i[~­ý ú%Ý«ÀÛÈô†¨#-¨ÏeFf_‡ëÓ8ï››žu.9ˆ”ÑúŒ¿ÓLFs÷óü€äða#/CA€zG¸(BB+±Kþ{Ç?äº9'ÇcÖi{qêl]Ú´™îÑîd®m­o¿Ä}G«DT—“š‰–ÿ G#ëAö_/;Ê„›:î kðã¨t½=gå2ùµ#CNýÇfÿ¬»‰•¥c?f´´ŸBb™è]﮲/Ua$JŠÅ$ëʪ Ó £‘; =ó¯’œ6tÚLå#-õ´ÛïÙöÍoC©þ’:'æb¨Ï$t²Û9#œA¨wˆÒ#C#`à zBÜñÎyÓf-Ûøऌ:foKž#`r™Òìì~®MŠ®<§UëÆ°…þmé4ž¤‡?âõ«Ç“ÿßîë.3×ßÈÍDìp\xݾåL¬/}ÆÚ…þ2"4tYV›r™¨r¾À¸Ð.©ã1êÇpv„ÉKã¹ûOsÒ‹Ó›áÒ¹Ÿ.þ]ñÜRFu=öþ6<¢[ŒO*Sv‰7ò ô,ãjg¦^·Øe“)à[ì䟓Ú#w6žðµÆ™¼u¦Û=œgB…µó¥Û»[=ìvÉ„rîÔoj%½k”mZ•;NJNZxÁ¢IÌ…aРp ÑŠsÊÙp‰îàò…B¥¤&À*Qdà eT[Æ0ŸMjÙÁR˜¡&¬ðÛñÖzÎÅ\ñÙÑjF7êsi†ËÅåè˜[#C¯Õ¤Ñés÷v›úà÷[$>ïV!7çW»ibi–°ù+ö¿…)*3f%˜kC\h!;Ã*£Œ#-Ø}ä`Dž”U¯Ð3©·![£=‹s#`ãR/üa×#C±“ƒü±î@æ<¾RŒ(ùÍûø®ì_—µX<ƪÎK sUíåFã²±¯'¥ßnéd1Î;ÝÎs–žóƒGXì?,ZTËÖC%ÆŽ¹ï˜V¯pxøÕ­„–Áê:ß©ªX¾vÔ 5®®Ø6œ7$‘ý×í¦1™<½zÙ4xcu¡ß³áë÷øç®±Ç~=2”/#-1SUðÐ&Jc¿‹^è9ÅܲƒCî2ÀÜ·ÄZfðýÅ9ˆ…êçÉʼ»†™MI#<Ä@‘lY0ÂÚc ÛéÁò5šT0%Ç‹XÀd2&ÉÞ¶‡5VøsƒÇ'ÓŠõzö¼c–ǯíÑNÉÝÒ·ùÊ?ämé#`L> (ä{ôfµú†’rÆ«³uaÆ2¯E¯ŒJ/× ½Ó/c¯|S;•s™à•~²(r˜UAˆP,S'/á:Õ8NjX„KFÐ%]ŽÉ™»Ðä϶1»ïñmÙÙÛÄꎙüÄ;1î#CW\ðvï1h%Ç0±gÇ—wpªBŠ´áŸ¯ÀéÝ!!;ñƒi¬ÇÝÉ §øÔ&ÛåRP)‘tsÇGbĪ‘F½Bòx1#Cû½Ô›ø„A˜”ø[@þðN6öïàÌís Y¥´Œ)eïsUhZ½ù $8ë¾ûÈ!ì6±ÙÔ37:UuðÁ>[÷<`q!~až!úÚ—mJ_ Ûµ}‘\±‹kÑDI‚3iêZXù6szí7¡²„Ãy#`õÍoQ/Ññ•ÝoDp§‹b¥GØ>7ߊäh (‚U" ±@Þ^X×M‡î¤$îãŽb8“Ü`ŠžlG9Í<Ž%ª½ï'8åUJpRg—w“¿·c¶äɧ6âÌÆÓŸIiÜ—4‰Œã?éœ×Ñ£Z‚¡Ä°†Š_KÆ}ÓÐâõvôÞGÕu†ü‹³Ða´wV—dšÙK­F³çöa磥˜º™Íéç0°wŽ‘pè€h=F쎆êªo/ŽŒ–$õ²ª®µ¨„£PüÂ1D0¿éÕò„#ˆ!£\¾“#CoQÙ¢§ŸÆEõaÒ«UÅPX¡|ÐB^JE“¢¨ºÀ*‰½ƒÔ à¦pÅcpmË€#Æ#`¯EnÒòKR;Æïì1臉ƒƒoÈóïÉHʼ¡±ÑB #pôgîÓÑf°²Õ>jªWtZ}:K°`þšás´˜®¥RU!lqWÚIQÁ©.-Ìp#5X½´#`ñª1€Ä¿;¥­â¾<4™°Šªœ"·MDv9´a|K‡ÙUBå^5Ҩ⨨U=Ÿ¡ÍzC]™ô-S¡u¼vˆ¸56’lÈ5¶öO±ø2ŽõAW«ƒð{±a Ü!W4dPF #-1®(ð¹òÁnÐe|ÒS¬c;ÞûV·}å>gñÑ&çfq6Þ[Ò뛵x£Ó¬ìª¹­=øôú$œk—(†@Ü| ž.“‡l¡æ ÇîQµe»j;ÊGê·ñë·ûS2¡i¯œÐ]½d.9TÍÈù§™U¡Ñ,€Éb{üÏ}¼£¦wY‹ôÛKøÆq£#c p›dFÓÞÙ,68PÏž×Sx'&›í¿†¤ÆüöŠ½6v¢²cÓz~Ý%¹œÝ ªqdïÙº0÷Ç‚6möRœÜ&>®ýSZÖvþG傺yhÓ‚ÛH`jñR/zðîéŽP8Ab„Z`R°<‡„„‘ê#Cv ÔÒE@U#CUQ¥-mg†’‹C‰£”8tG4i)×JÓå“g''¦¹ÄˆÒX›ÉMM¸F³Üwƒd ,òò á1ÂL®_«<Œ?Ü÷–¸[—õ“ jÂT¦m……6e¹ïºP¦·;K,­4%AØ‚b{Ûš ,Žß=ø¯Š:ªõvìzí£¹è`ÜD˜Ìyø"¼aØžž¹wiýš’S?.Þ#C>tû>/à^Àˆ‹§WV™¡ŠD«ÊA ¾éÉεk¾8;0ÿ¸TîÉÛ…¯ÖSöQTd 뜛=.Ô冪¿csä!Ñ/BP#C¢kRŒ8òpƱ &bqS~}¥L‰Éß'#µ¨Ø/!’\0sÛã=“‹([Íu#`‚yénH5§”u–õ!ü–êYý9ó`ÚƒwÃúàIÓ{àëŸí™¿?(47Gw\CüñRÞÅ`¢ð> ] ¢.qÎÎ o`0¤…’ènÝ£ªTíær¦–^"”']ÎzãÙèÂÎxÓ~XešÁÛâˆ@Iós>Ø{gls×ÓëÖÁ‹C®1½‚H$-`§ÿžA-÷õç"9@èiMOïRt_m£H=Î\6ÖÛchÏË%ŠÖ±ZµÅŒùy>htÛáþvÕõ%³ñ3Œ1\]æàˆUŠª÷k&\¨xéñµ#í‡CèÅÖûc\Üm²„ H|•–]eg >„ÄZT.Þ\%…++èªÛÜðèÃ+l)ÿ% —× 2À—Ê9¾PºùÖ’]z>þ+cNw·;#C#L2u³Ï–ª×DïθSeÎð>«Î:³tiS90í„'×Þ³Eo‰¯9䇾#••HÞ“ÎÎUì?3#C+0=:ŽÄtY¶ÒŠ}m5·7ß\AÆž®x­-js‡ªyPnœÙÌ£¶v¬%¿~'¼Íï™êÜóë÷O&qÇ:WN$Ÿ³†ücs´ÖÝÝ\÷<³šë ³ªæ¹‡Äœ#.t§a¡Ñnù¢ ;>k7™#‰Î(ã}zÆ#º}ÔãÄmœío­DÛj#ÁáP^5zb|øòß;Æ«…™(âl>®·4eéÞ[ 9#«ÝjˆòÌj®vg}M+'2…ÝZO‚°þ17‡¼R›)’I¸s¢nêXÎúk*ȬðªJPƒ#`HNò²­R´…K#CØó^¶ mßZSbzá\óy»qJðŸ¼m:¨áNywã-ÎU›ÖUn«P‘×[N|qÆÝ>Ó{V1{¢…ë|Ññ!‘L³[{åuNpSX-’ZÖw\±¥¬û5ùK¾U¤k\F|‹ÆÌLå'Ó,çÍ¥Ìû¥œ³"1‹ðj¾åÊû]$ÀZ['_ªå-f¥ž×Bxhc!ra7b'ÏŽ4…™èZ×»]”ae…£¯‰‘-2¥}_0h H43¦jÒtë&ÝJ‚ôcË*qzËÆÔ§Ñ<íÇ\w¦ÀJ *M҃ϋÊq× u,´CìÑ;CšÑß¼ó­õ…ëIÙxç‘ßã§1Ué<+ºßŠ'ÀøƒV†qÄÊ2‰{ßÇ+Ùï+Z0Â¥ðÃ(¬¬fÔo21¼ð¶0îèìŒ+æö©®ü?|F,“žý¶2°´Ú^#C–¶œIfzZ“YÒ)^0҆ΪëÊ3zÙ·sZžøÚ«}®ñŽÓ³5‹Ž“bèÖ=`’Ìg|q‚«cÆЩò%‘*ƒñç[Hèu”h«ªíâK1×j7ä#CïnÚ\[ã0*˜œùOuµÒí3G [ηçù¹—Á÷ÂE$ƒ8q“9B QEŠ]³Šé@f#áóIßVh•.äoNËKXµa·yý¸©l‰_/²ã!“¤±£gÚGd"I^P=‘ë,éaë/ø)§O‰üü›|ÃqóƒË%"TKv† ,Þ;óØUšnûÂŽæל|-ø¯lTm#-ı6e/)èB6«ë[›ÇÛ.§{‹ä#`„¤²OÕöOâ6Mö{×ð©ìcs·­Á‡à˱—zv‚!Ýö¨^³¨^foDá¡:F6ŒªVšy‡Ðûc¥–ºgö‰SAâìÛŸYl,Iqõ3¾7ÖqÌZùMþíqéMYÊr®zïÁ圙²éõÝÚSÓᨤ_ÒŽê>#`uŸ¯'$vLì„^c1²!3*×D®•&…Õî»þuÑíªRæa¦”Z½X»7î ¾R§àÊãV%ª ˆ“¢ç°‹®Cöø›WP.Öªu¨ÂàõŠ÷D^½—Âëiq~[Re/›ñÍ1úac­\p±öU­¥4'‡XþÍ©EÚ,…ò®Qb#`HÃÔZÜL®F&#-,ê!Fr˜(nÜð«Ý—mr½BWÅ_Œ©2SÐ!©$pýOŒuöîlÍ”ÆßU׊m8ÂJ#A •˜_Y±>.j?R$ƒ6Ç#‚ˆEÀŒM1p‚¿P€ãé†ú1€Ö™KÒý·ØŠ!ŒY8±Êj¯O›·Àùð“£ÉñGî߈­x8#-¤Õ4C%Èb=•…CÐ}±s‚€1¹Š#CÉpŒ“fC µ-½F´¿b™Ÿ°Å+Ý"k×ÏyÐwìb¤–u9ãÊ[Jï'ô5×kÓ÷’{Aã%ÑX´HLƒC·T|Mp*QO[¦C‘¨yÀnfžùEßÁÜì€Í°êCâغ\3)ÒïØÓ+fo˜W>ëòn.PôB ä3-öò6.„†÷ÿ8x™#Ñ·‹·”Åw2#Cú½xN5ÇbÓ%tb°×¢W‰#CâKC®ðp™N¾díŠ-/0ãC²õÔ•ó8ãá^\Ì3‡ 0Ë›ì» ÎR=î* 6`<‘ÇŠ»<’Ó7Ãy‚:¿J>ó¥P7ŠÌ÷7 {¾+–ÃÙ àÚo‡Um¼äÔðôÕü7üh;7ÅF;L³1RÒ†âÿ™„—J°Ý/æ6m°—£r¢Á!·9Ü°p·nŠi:Ž † X#CmÌû"Ö-È=ðÁsYiÝK:·îÉ4 -(A$1UxÛsÀCÓní!Ç@Ï’Þ%åÜ}2~¿¬È÷êm‘ÃÕéf,5-O Wî\÷nèZ¦ƒƒ[ ô )Ò‘SÈê®B¹‚@SöGÄ­Ù·FïVè`»ëp¯KGÀ_K©U"SÛƒÑÒè|j MÓ<©à©4jÌ/~ÔuÌP¾›»ßäùÆÚê–k.‹ÎÑåN\mŒÖ3†Y=ë£M+2ü±–¿!ð‚‹Ø«ÉÄ"øL)d!Uך€ünLCö¾76ÁqUôÚêx†Æ÷¶{ÏSýJÇex£µ—Ùyc‰Ž«²&…[Ÿ‚<+Ü›þÎó–`ßoh9Ç,g4þ<È]Q}_>…¬ÙgŽ/›0KÚ¥}’Ôü£Þ8Boh3ÄŠ=Ê®ùþÖq7/ù~•Äƒ£ ¬8¹íÈÃw­Ù_Ômiq#CÎî7r<†DGwZ»Z_Íà¨1:© ÂZ>Œp“U±—;Üz3ÄîJx™“׳áÕü–Êg‹‰Ë½óè½@ó?tùà½#`àÃ/N`!C\ˆ(seˆJÅáo ·¤›Ã#`ÔQv`‘3¯Æ9õ°1«“’ËÒ붉 A×Á©ÄøELZù*‘â]zšùÈ÷t)péuè¿ÀåøvåÛ9õø_ONNŒïô„DA¬JËåR¥•XVA³Ùìï4(#Cdß‚ê¢@rÌ¡JZÞ¦¦^B&%")M JWJ¸—dõëâï7={›ÞÕ#CõÙ –#-ðµÄ6˜Ç|]ôŽ0é£J2äY-4óBó[SèÃÂ> ÅnûቀZ´‹&´±ã3%B± X‚ÌÌìñí»‡P]yÖ ÓÞŒS@ÈZu]Ym€#-d,h¡B¡FläE=ÒÛÓÀÊË—lÏŒDµˆ*ú‘°éÓÎö¡Šñ®>•ôYžÈøŽÐü.TJoIažS8ó“Ça³«Ý#-”%Š}z¦þO[/VÿùIð|;Ò¿n#ˆJx»Çœ»ÄöbáÑJŽáÀxÐsóÈÂ.ÿkBBÿ"I#tI#G¹—ûÿçÚÏâOÖoCÞÐå\ƒËÇËš#-€< ";Æ¡°rûw3~*ºÕÌyÙðÚ®ÞßäÈ4Ñ9Ê%KW‹•ms­éTÄ÷Ïòö>ã'òÈÂSë€>`§í”Þ5ýøöÊyÊ›@»H´ ¤>2;B€¸BÿX‘OÙ"ï ÿñ“”#`¾Dò„û#鶀x¯ GðÂÀ 2O\¸O§Û÷ééØص…é(0 臤é#-¯´¿)’é,Êq—ÊQÿ4ë(ëIºÿ»@O»~Ãëñ|Ö믧óòßÄ_'q~Cã®#-8À¾†FHßãGøC%Û©—××kŒUÊIL‹ù»½‡ÇOÍóF•©—‘ƒ¤^#`:écTü‰ö_œÃå«áo´%âaÞ4£?Šþ*ÓæÔ+¶ÜI¡LÕ L";]1IùM>Htý;Ô'íCÞ¼ïîú;ßoŒ„ºSÜ‹Jxw*50x±V”Öê6öÃZqÐF„Æ]Š’î vEÖI#fqWýä"÷zlTç£èüÞM1ÝúC®­GqÄèà ^P¡#`å%£ÕÅKÏïÚ´äàfìI½m@ǦQ±½*ƒHš˜ÇH‰i˜ˆÆñÉ?ÐtKþ‚ác(¿Ž” 0hí®i 1¢Ò12 ÓN„yŒ‘þ\â†ÁÉ€¤ƒùàùyŽŒ6«mÙOúP ÙÌ抑"Õ Ó£7#`ºÈ¨Â5ÇzÇ£`P”„BÎô¿I!h&®¸iVAÆÝÈ'Æ^®0o¯p[d®ŠªÝ”–²5¢.CHt_8Çk* #08é¤ì¢¤¿òòÇz#Žá÷²u˜¨ð»Ï‹Ÿ­Ow9Tó»…Úe´Âdn©PÎ)fjèÉ.¢‘ǺJà6Ü„®8È60°ƒÌµŒßw{GKsLQ›^­ÖòMrLZ¤S´À¤#Õ­Q‡þ#CÝ_øm*ØÐÞ¤9nÚ vÅd ¥[9j\¥8¡nò‚(FQ3MB´c­°ŒƒJh£^­ûCr¢ 9>¶#Cr)>Ç#C‡Ö4Vß“ÁõŠ¤"zô_Ÿü72€îˆlŸÔ—„(ÛÓü?·Õ¨vA‚ÁŒÐú~›#CâòúÉ$*Äן^¾»hJëäØ›¸‰cVÑNíZ±ÊZõ i§ý1û£sè@ž*ZE†~Y#-#ò¼¨xùuðŸwä#C>_¿Ü»,çø,ö‚“ù™÷_ùÝ 2¦€QcPBÏ£o U(D¤IDMTÊ@G4AGë-I1þ£#`IhíG¯Œn.#-¦‘‡bÆq¡÷'•lóõ{öH9õÀ?¶¡s0–¿ü‚ÃcKÛûÇv—»E;uƒŒ;îÿ}ôÁÀ•@ô0Š}¨ÖÀ!„’Gþ ÿ)Æ1öþ³»¶ŸOûðMæÜk/JäQ׶¯êãû÷üœíppýêa Xü~{ÿâæ}Ë„rŒ(ÛvQB,#C“F@ëÿ^ƒÈ)J¤z@bŠ•ìDP˜ýÄ¿¢4!PÙØŒŒÃ CÂ$ìÏŒøÑö;<é+Ñ,j‚ªÛlF'Ì!Bœ´C†#v)"õôÙÄú4ô8J´ØÜü»øÌÊGVØžªÙ+¡‹D.?ò͉µ:5£'ü'ö~yf”~Cþx‡À.Lpò¦ë6ܼ’¯,H;ËÃÎük Û3|ùÚð;ÀGúÎø¯…˜zÑÓßò¯™ºÁ™ƒìøW‹â†bÚ‰š*"S)‘÷#a oלÏê‡6ÙÇ\<–LƒÃŽ]`+ÉüÅbc’%ô#C˜P‹ ?A>ô¥AU…ÁªÕÎý²¹<Ÿ’>A(ñ¹1%t¿Ÿ‹ëå»EEï ŽpÁ:µë… -~S2¤¿¸8Ö4Úòš'æˆËèñRb¡aÐQé¥dnŠŒ?1#CÿÉÏûs"Š=j:Ö…’Ãñ¸wí™—û4Ô0‡ëOfïöx)ó|‡Þß©Jÿ'Ç ^W±[û|_:c§°õ¼y%°í|ÄjØ´§&znUÖßÐc>Œè?îgOÞG$%Œ“Ñáò²Ê{½R—ø#`‡çoÙ[} |K}Çùª§%½,3>#-õGÿ.û~Xä^·Mª*ã«—»ûvó™#-éƒ9å:J'r zf$9^ÝNÚE?F¾‘ßù©m’QÆ{6ݼU—äØ–Ü?¸ŽMn„ÿ½ žùô¿÷d<]9О&eúC‡ äáÙéÇX÷×}™£ií<¾ìNKþvq™çðÓM2?~®=*C+ƒ€Uàû‘v3 ÛriÏËswám¥…âüáO<6‡Í¥3sÓ?J¯ŠÌ5bW[r+wñ¬¶záá轉(vIw0o®wxO9kL@¸#-µdFE(SÃU#-û(!‹ø·|À]Kúe¶nìs–SÑ!«Š¦e§¥F#`B«änƒQŸ0¸U¹Ç¸§lÔÝv·#ôTÕwGwV_п¯Í÷Õ¿¡šñ´'1OÇüÌðPïóévïïŠ ÿW_ ~Rð-hsuWûîŸßù?nƒþ¿ÉþNŽDéDK¢~Ò\÷£z•ODX•Oc½í"ðB’¤¨öÒ´ „b¬§ùda‰þN¶¬ƒÿ^ÿç3YÖÈúËKþV@Öªú΀þþuú©0òGó#C¡Ø! 7ëOž~ÅŒ‚ƒ Wêë5cä”ûååý·-ÿ¢ÿ§s¦µ‰É'ù;õ£8÷ÜòMxÑe˜w¯ÍGoÅêúÜ …Þ#`&L#CªŸ“«[¸·E›ã×ñÔç”ê^R>B7š” ÅFõöó0Ú "œÌŠQŠ’³V'%äÒeëŠÛlüúPfñeÑüÇŸhA’{“Á9t­¶xñwŽ†Hª¿31dxX,~øˆ~YÅÌVcçÇðü›p#-)Eƒ«ár åÖ6ü8§˜ƒ4õ#CðËo u=>Ï5ßOn`Ê~(IþC†ßoÙø|»~ï7£÷rùüCëý]ÿ™Õ#`ºóæÙà1hoõÃæóú¿Ï×™§n6¨?O+Äzâ‰ìvx¾hÐq±$ÎÝkïþ>fJLˆŽ&0Å(ˆãl^}f?¹‚°JOa¡GÜQ–Ê¢³oíü!ïãéCîÂ7»“v{%$ôtm̉ú?0'ï=#¯ÓM96önô|pçè٦ߋ ³ü3ž#Ë—²ÿ¦îN#<9×ý}\÷ß·òy$Ýråìê~ÞÐþôòGÌ¿£§„þžËf¸ãö­îúÐœÜÃÕ$¨ø¾$d,M?>Òì kÙW]®&¶B(E‡êÉ©‰l›D^Ióâ&¤‘²;'2c##-~pnЃzˆ2'øä8ÙbuØMórj<Þy_#Cç½F¦ª(J¥(¢$$¢ªª $f €Û¢@%#< Âø‹øg&©b‘E#kýÞ™'ö0Ñ$A;™)Ž[÷ºãÏZ-R’”Í,{8“ål_›&„"ÈP=w 'óùGç;ºÌ:Y$w0MµÄÞ…#C0IÀ §¯k˜=ýÎP\ÿØoÙáöý»÷ñ³þOu¼‡œúN¿£›ž½CòÏ­áè;~¢.#C6÷ý¯Ö™Øëú|Õ•÷ç“ßN‘õ~ß_(z¢>Ïϯóû~}Ò^?/ƒWÓ£ì«S‡`ãɈç)ËÁ¸PéÅz>˽¯T>á=9šŸ?F¡×/Pðÿ.þŸ¾¹lÎ+½Ý¤Xnÿzœß=ÜþØÝî³_ããíæ“ dîéó6Càò¾ˆ7Ã-Í°¨éä³ëØÏ#C;}mbá‰øù•Â wí웎N_§Ôƒnï<έê§d0glùy:û|Qãì+Q§¾ü¿Ç¿§¯ÇÉÐXtÝî]rñùÏî»ewNÿÐ~n®5söcðÒ[½Ù#CìîÑ$?J}«ç€8#!CÔ”ÞG›“ãþ.‹ös“{ÜèuZ^;¼C³*'5~¯ªשãÜ=¸²8z©+Pú!t§ô¿ß©ô >Y;„¼¡þý¹ô»ÖoJjUÁyX_8öá‹ìÚ³¸·gÙzm¾Mbèúqüåð­K¹²ã#CtÅÑ!ÙíËæ_!œïèæ•ÌßKùŸÕ†S€óÇÙ;øÆ>,|9trz~YuÇÁãË7Oû9±Ö£É÷Õ©ž2ƒ½½ø7%5üyuëÈË–øÇòd5ÆÜ™ü—_Ó–W>Jê6+Úëºø?œ¹=ýa»2û¡ûþ{›Ø¾;oô/Ó÷áËÇɦ/þ‘ÁÜÿNË­? €ó/.¸sŽLõí\½1çjôî9Wõ+ÌOÚÃ×»MsñîƒÄõÀnåÃzôC_ÅÒåÿyü"L˜`ÞìyçX{¼\®êöëӎܾm›qÒmo.±äÔ9£Š¢¾¾ï׬ô7†»K§ˆ_›¿™¹o=œm³m gž»½‚¨´—m \Á`æÜ#CÁðy‘f½Y§ù~ç=ê-ôš<Ù}råÕè¶3µöhMC LXîsÇglGWŠPÛ¯¶Ž)"FåOSÕŠHñÚ¨XaëtÍ5ä®OÀ¾yñýƒÙ¨÷½WîÆd.‹H¡Ã1Ù¶=];©Ãíþ?Âîì>߇V|ujÕ’L8:‡û=[tü*JÏøþ~\ýˆE—/Ãåô†½ît6ì—tiu|}~~m´ä‰~náÑa¸'¨xľŸjÿ/’ÿÇf±ø;ÃÒFOßîõû­ZaŸ¸ó ·xDgòû£¹üQ]G:“ü›=;nÔg>'c;úcòž—‰¾â÷ã+›V“}ï_%~íê]z¾*#‚ƒ¹˜²‘ìXæˆã§¸dq  ­5¼1Ë«¿ ÛÏë¾,¡Î°vzò±§ÃÑXCôsåuÊ!×ña2“ºmÿÇôüþ,‡fÏÛo>÷Žyl#CÄåîïOGöý¾¸×7apݱ¸p/þ™sW“Ùý]!ànWsO/ž:\.Þ<6ýæ[GË'ÖÂAëáà8Ó.Öú—>…ùë¾6´ZÙ Pµò4þÛ¿ºØoÙE÷­ãã·WËñu”—&Ó­—[Þ~ƒ#`º9«ó¿æŸ)•À)å¹í/$kþ÷½¡¿õçwå:0C˵eƒîúû|¿~†«ºë?Ë祆ŸêéÏ^Ÿ×Ãü#`Æ}éb\b,Sì(JJý¾}ÙûAà}#¯¸N­ç£áù¦ëñ÷Kmô^¾ Ô!/’Í[š’½`ž³ô§Lû=é§\&<È<=ûþ=túõc£òÃó_óGÙ õ¬ÍnàÌ#-Ä'ŽÜx Æco§“äú~Ó³LG^ZžT‚@*X©'!Ó«R´²ß7“#ClO4i/gíýžsýî¾IÊZ1ƒ¹;Ý÷v|1çýÞŠõpVTmÜþµ‡ãÔ´¸uÇŒx¹Dÿœ#-#`z&}…{Ôí#¸ˆ–ön0ýaB#-Ý÷D~á›øûô ëN‰z,‰pI¢t‡cû|\6côòŸçDAàò/PÇ—»õDòó ÀïðG‹êw)"áì|9•EýGgd“éц×KÅ«ŽÄŸž¦Ju¨A.ÂY¼•\ÈÅJ†)ó¸&¼±Ê"’ö¨Á‡ºþ°î*†)zÉ1&d‚u:ÕUÌ € bNÖú!¼+ëÀ^+QKIñŽ^ð%ôNýyqšöjÀmMPçù°0zeæ)v†›2qs,Œ°mŽ× *D‡S0_蜨tïüË4¿eS`ÕLçcz¯#ò#d¼ä{NüâÍœ8 ãâ™7ç?¾Af£Üm‘Ö*Ëü­lo¹H#-žjP´iÎIdÝŠœƒf”»L#:6n£ÞËã?Ç#`A×ìA–¾ìý[Ý€C?QM»†æNÄLþÚÁ¸ÎO_;Ó/[ë³}í¶C—Þ.xꢆ#`Wö9}n\òSþS³˜22ò¤2‡OR§S½WI}WÔ ús𭢎òóÚ4Q¼|ìÖKoWΣîeÜb@öÞÃÊx;ÇÃË°9Ôý^î˜ ÇÓ¢pk‡?ž½Pyì±NäÑ7î}=:ú1ë¤ìÈRwÍ‹àk ] ê<Ä6r¨.YÛ— 2(>t ü­þÝ4«ðñMb'Ù Í(FŠ ŠUQB‚ ࡧ¹N‚>07Œr\·öEÚ},I*Ø#CE0UùÍËÂÈ)€þÏ‹…é~˜#CDxº7åpuD©“£.’ò|ƒÒ:ƒxë-hŸW„í“Tp/Žð4Vú°>ŸGêDqDÖ;1Aǯíú¸sé9lt§¶W¦ò³‚Ž(…²à¾>Äïú òDGø’l®ÜÛq?Ì:s£1¶ª6ÄÓhØœw<â–6 ÐD\Ù–(ŽbIÈT¸Ñ”Ñþ¤“pr…m„#D>aÁƒ°5k¡ Ô`BG\W—+NMiLTLÄÚ¤œ,1T&4è5@ž#C:ÓÏ8ðïO:Háf‚H“‡ r4jä†å˜ØK`Ôj"Á |¿òuÐÔa+½B™¨Ù(nE€ZP3£á%ŒUÒ Qjí»Ví¥©¿¯¿ ÀVBžã¯ÞàÈÄ܉B×ù´êG÷oÑOþùu.“DÚ'ýD=µã@M;™qùäöi­ïk·r#ÙHX ØÁŒ"…2…Eýž¿—Ñxú>òw7‰ÅPI$% I$@ÃÒ]#G—Ž‚k'eŒ¸%ƒAˆ$$4lfm dÑþ«I5Œ ™ˆ){¿?/äù³ÏûþO‹†~_eù/Ñ—+“V¶»ô|ž±m_S£è9dÉýÚô¦OIÎ#§ÑxÛà¾ÿ„´x”¥‚·e½Ï<¾ý|þ;‡'Ëwu{„<õß¿oæåøµ9Mnü7ôÒã¸~î½_˜xåÛ¿¶)¼Kð÷êþ8ºWòeý4þ.ô‡š?ˆÚÇ1®ÎÓ+ñÕÝ´ÅñsÚ•öø>H¾PÄïµrùqáF ?´›#CBÎBT0£VœsX{†C`¼§‰ö}ºÝ÷—|““#C{¶Ùc²@Ùÿ»ú>7°Uéû¼¿¦ÿ|ú8þKsþËÌÿ*$¿Ñúsý‘Ô²Â÷~ûÄðÌ4¬ ¡éóèlžïå—!32³ð7|sÕµý½ºx+Óñk7 `A4J”ƒýÍÉ#þP´‡ùª(?í#CJ~ÙWöLM½ô©ØÃbieUIN•¨DA⦿¹fbÿuäE\BY»—B¶ŽF6… Ù ˜ã#‰mͼÏexÁj&œFK4E'{#4BxÇjTз1ÆõnuâDÅ]ó'˜Ìj=)“êZ5üœ-ékRRÂË!l±ƒn.^¥ÝÚMZ`–Û!c¥Žãf9þf­2…61¢5šŒÎ(›ÁVn*Q·±É™Ö晌†Ë0à-–cqq.`%b¸p(ˆ³oö'ÈúyÓÇy ¤ú¹›¡^Üœh³ž'4'œçluØ k¹¹žUôK!ׯàZfäï;ŽI,’È3Š˜‡§¥Ž7ÄÊæ6F݈3ͧ¬ö:ܦ íïÞhƒOò¢b˜t8ÚbE“Óu,E€Ë®îPv‡&óp+ì´lhˆSü·à 1ˆß"ÖðRª&ØR6YŠ#C6ÙËÝÓ)DxHŸ_á´@ŽA¿¯›÷~n…Ô—ê4Gö/oê†5æìívyßÞXà£ÖÂø¹u ¾g¯óÂ>7Öžãíä=ºgåû.ŸÑF窑ÈD½³½ì=W-üߤ~3‚ _`Áß8ê)°¢=S‡;¾|{Þ÷<=ž1Ù±Û¸oýàì â<6ðr`>õJQ҇ϱ!üU>¦Éšk<†üVí7¿ÁÈÎ0Øa(r:0½¾éñëWãòÝÇÅ쿦{°Ö}Xôv(æ#xê^sÇöoJ>Þdà( ”#`øýÌ9ƒr“f]ú Á»6F!ÃgÒɼ¾ÄkaagøkáBîÛ–@w&ÓxUƒKâ›ÔÇšÁÉ!j#C5k©*ÜéïÓ{¼DXm#`5S|<¿§úo¥)ÕÆDCÂÓ=Nr»‘ªràø‚?ÀÒŠ¸J Çu#CÜuWÌ"sw0Û6ibθ´~2¥+Æ>ƒŠôjgìãø/ç¯}¢‰Š˜¿î‰bà(TàÑþ9iJØØÕ#c$#´ PÕmWZ+,ƒQ³éøÎŒ0‰%9†nÆ+›ÙÍØLèÜ#ˆµÝؤ"Ji"åŠÿ4½w/CŠ.gNÓ@óy±G^1$‡ô&áÌ^ëpâøW6Ü8ŠJÛ†q5)ÃbÔ‰c•H…lŒ Ñcë–¥ŒŸGvb4˜$^àòóaò?çÊXšpF[Ó1ÓþÿþRãÆD1‰†Ýèu0ϦÏÆÑÜ—Ïvèš5o»CÖjàK ä꬧ùñp5Zï¤Ð= *æ§LÆnE<€‰¡ðîêK݃ç áÃÞFøî#FÒ¡§)˜‚*6t³†,f¬¬Ìdc±Ž–DÂ*EaØƬD}û™x”zùûЊjH,†O³Q…‘8J®8© &ΖÄ[”%"Á¶†¨PJ&–MÓ!.ȕ˃’)òòôê½F¸IƤ9%5ÇŒsTínK‹×qÔE}pv9iÅ4ÉTÎ0}¹$ëÖ#C)b!?ï©6Jæ§nû*Õi6 l$±DÃ8¡oVØ.Dh­›µPvk‘£WU¸ãP!-2ȱ¡F,XZVê×lÃA44ãDsᦽÁÎùˆ~–*:cÁq˜áFÞCIºØÖ“£ŸŸ1=”§ÂF0RTÑFh­»-mKb`º¹©Á¥°§]àÖ6v|=cÒmY-1¡U@4½ zî`¸3E`É­¨4‡³Hð<ÁÑk]ƒIÌxf»#$¶ ¶Åq»“Qwmªª6Œ`)Éï[ß4[ª"ùàÄzm!11z(ºRjq“‚ªJ lt’À «<àðææ0KÎcŒ%-AT× Å£l"d“m1‹E¬ÎˆÒ_vÜÔjœâ iÖÇÈ»Ó`ÅÕµÓóÿ…fjF1±£¹HÓ³vÈpÅFÃÚh˜lZh¢wSáí1ÏÃyËj96­#CTíª–ƒŸD·nε1QøC¾ãÕödÛQ‰¬Ž"3e(ÕDs†‹¬!CØBv{Zˆ5Š.ˆ“Ù2ŸaúPPFUŽtJÝ=&Lìã@&JÝ»ŸA3˜š'±Ê¸RQ3nQݳªØ;;¬†ß=ŽF&"“âWÂçâoùc²ÄyŠ¥63vƒñù”QÓ©vùHz;ŽÚPã 妢yIÌöÁìéÖØ*"“üÏGN§vLAˆ(¤TÒì4( x©LäÐÍXWAžç~ Î^”nŽ]éß²0ç‰ ‰w"ùyC¸˜#-"Š7ßQÀüœ¯„1x¯­‚xåÄÐ)¨(˜Uñ´ö4åmxÛZšÛ0‘û*M#CÆ e¶ŒlaI?䓃ýÌßCe`FDÏôpÄ3ïË·û®OÊ™ÄXæƒ!äYEz34F¹"±sgrx¥ŵNÃë‰`¶) ÿ[Ûy¶¯W0a1c1ËœGÇÛß°M|¤ª¥>¾mÌ…vÑKÇÚjÇ›‹—nê'Cbj‹T)M5:H´™¦vÈbwÎ1¤[˺Á0îîД6½d™)“r6¢L!8À¹¸odM±ìD¨Nã›nnwlæPºtѼLѯ_ í;¾²Âõâs}õ®3äj v1DÐD­·W)7´#`¸^tÎ×"È,X §ÅäI…tﶴš£\ã*-þ.ò†ÿ žMÏî˜-¡6¬!óQFãá1gÕÇ8lYû›l]9‹j#C¬:M³±¸Œ-µÉé¹}V1´mÃ8Ì指ŠÜò´£ÔJ¸;7’ Á}GÄÄRy[y@oÏ;úmPHI;ϱZƼc8ágÑ/^4I‘¥e˜ÙgIÀ Sˆº»´"â0UF»â•[kƒI_õÜdIÄçQÉ"ëüyÚS?ce±l<úz~Gšwþ› ‰®6˜Ò§13<»«ñ]p¼:…YD('gI×zžâgÁ“Y4)ÓFªüV–)a,ƪ37e¢~Çc o’à{|˜ù§L®ß/³ÊÍmr”Äô¾¨LÐÈd™…åÍÑEŸñõÐêÅsè×Ë6ŒÅ §ȾÖe„í\tí-,x¦oP¸ûo8µ:›Yˆ›{ylÃyg!“%íþyɯto|eo2±¨ßDs¦ú¢:re µÝ6ví#„„ÓIM aÎü‹–K¼EHŒŸsLZ\ë%6LEK>"á9ïŒA5‰ÚjÏTbÜ›Äò¯¬z_YrÖu½Ë^Õ¥Ùôböƒ>Ubôi¶ÏùhòÁMŒ£IWVç~®Ï=¼FÝçŒT+º6waìÄùpbļ†BŸŠÀ¹MÃúDòM·…¿M ßM›áQݾŠëUò±¡±X‚·µ‡A™#CÉàiâ¥òg·2¨8ôJÊK’ôdêòïD\³†žæ2¾¹»¦Vy8µ#`ôìÓRÌËò#`»M]n|8Äô3JfcsW,7"â†ô÷_åˆÛÔhp_+O#X)Áè®»"[¤R%S¼Š-é#CìLR;U3¨õ›5tºŠ"0TX«:uöYäÃǦyÿŠÌ›Ãò³Ëà}}‹Ö¸Æ/d#`ÕǧíëÛ|."¬Q43ÆmÄÿá!5¿övÖÊv÷ŠŸ?Ñ·O ˃.w<çrª#`‚ƒÎvyG™”söya=~ùª¤ñ PSñ˜.E´3ž'ƒ•{JcÊ)3%2hý¬î+y¥#oÂǸíäªÂóÆ'3âf)·Þ£õëþ÷ñvî2{#€‡‰f\{\9ÔÁßÔ:_¤Óó ¤HQUÖ(-˜Õ«R&ð ¹#-n#‡ØÆWý€É¨XkdŒøRX×ÑXø½ÿ†Æ9 K•<Á ~*¸Ybcו^#C\rPñ‚/#ôå&L]DÀc@“Hyr_ß@ Õòx‹OhŠ^ºÛ^«Ã½à‘ߨ+>Ùf¯6¿vª]ç¨ÅØÀN0ÍŠE*+l„—ïQKÛ$@HŠx=¾óóVíðàßoÒçtS†:fžáHÓ&b¹2œ@9 RT<¥;z:Ï•„ú¥aâÕý«åÓJøÁº¹ªíÉ«BÚœ¡hÜ#-dG#-.ò]]4ÓM¾0ËþÖõþSæ½PfJŽþžb©j?Üõ&ê¿À†Á4|–-|Àµ£#`þ»äƒH‡ù Í"eÿ:llC[m°Ö~–›½ëŒ’# ºÄúµƒáf’ýM Ì”ñJ pEËÛ‹’CâH1 ¦_Pñ(Ìâ&\@$ø@ᣭ9ø•ª¹÷ðô}`4zÎbxrGr8&Ùî“3ÑX¨«¡#-¨Û4?âø £ˆ·Ñ¬aq%Bˆ!ëöãžqkÖ ¶„âú8í³ÃcUŠÒd)J i#_M©oïþßáÕ)ثží’pì<ÉbàJ*"dBYD0n¶ûÓ:to:)°5ÜZšÈ‘'jäœÓâ#C´;4nìa»ÞÉL`Zssû£ð0g,˜†2zfZhê³vÁx±•íèxɶDdzÜÉßæùðI¨a†ÓÌý϶ãû[Ÿ‚j²ž0ê”›“5Þ›ündä|VË1Góĉ"{¾ï#\qiß3 9eîÕwåE L+Yàá¬2¦±âq™>WY’“£¨õ¾W·x°ÇêɺòÏKe™ŸûˆxÑ _ I›.OJ5SÖ–^l…k6¿"ÏqTí懒üj«b:ž[áÏu¢ •êb(PhÊ°Mÿ:È6ö™È=0Öµ}kÞ—S²E_>Ô1“§,Ê-hzhjF\t–b7%0~Bøç(™É“/P[w¡Ð@Šnâ,) Á+Åê‡gYQî˜2Ë@eiÙ|»ª$ä{#jìäÌû„8– Q•ì‚219ĉâøŽî æÂô¨R,ìBóy4>§Òºâs³ö³FöÏE39M?2;»#dãmëR^³Ó!MƒõN¿HiÞ<£Ù*ש‘$iනçuŽ4ÄÂÙ‡öf»ç#h»OßKL!Ü÷\çTµÉó ?[å¿8šñ‡â"ÖeÒL¶Ã;#`ÃüæÏŸÔñæÆŠäqáø_#C1 †ù»pK'96$ׯc¶~Y| ×i:þ¯/›ñìA‹ý;7-#-ïèD  èv:EàŒ¦æñÓÜÑP#&[åÄ@âáŸÖ¾nîÃæò&nƒ¤ã%5K-<œ6õµj^ÉÖçÒ‡–jB±­Bgä¯%¿&pdG9ççÀhùSöëG+òLåÊP‚™?UÃjØçþÙ™Ù°h¶—´Ò÷ PCÄt¼ ÷r‡ üx÷­Éð•N/¯ÉÓ3ºx!i1¨€ÁHyE©qWo/.ñãA´MV°jî[ÈÉ`ÂG†s´“>Ç'Ù<ù_mC Ùy´)›¤lm²è齚i4Dês§c6‚2K¼´(I›âìý.Z70N›yr·¯ƒõ—û¯kÙ@’)Wèâ1éÁÁË÷¾ÒØÛ ýewds:éLa¯E’Å1xñ0ÐëdÿhEEV8¥±t‰!Ô˜œLÝCµ\§vìúÃfšé¨÷–õM„ŒxÄHäFV™_+£¿ƒëO'ä?Å›þS–ã¡Öà?%Ò`ßX¤aKzÌ:¦y?½#`‹ôxd`šÅ}¾Æ±a”Ê"#…²¨qŽÇŽ(c4Ve± €Oíþ“Žú"^„i‘7E›Ä#C…^˜|xÑîj«-·ØÛ(h¬)ÜD;±rÂf®DK´ÝA-ŠrÙ¦yT}1Z—ùù}ý9|úFÕe™þ;¾Òîªåp#C[[œ\œiE„H§ -äàæy#`_PßADX‡wK i#CJ¹+¢½lR.sÁ1ÞË99þ)²²AÕÐ…#ƒì.Û²`ÿg~û9‰ÜÒâ…ßÖ<#`~7¶ú¹ÅL·(¥n~P~±›Ûmâ–8âcœ¸¬ÜÞéOËbº¿m>Ñ]ì}UÜî<3DÌ_Ò^Ö JèÍOâV+‡1™Ãó‹âêdzq£ŒæšÞìÙ­Bù¼QOºÚšSRs†î´¥*OŒÆNûÏÍj5nx<Û óƒ««Uª¹Û#Íñ¶p‹‚HÄŠ‘W*C$ú’)¶P¨9"3]§øÂЂD X˜oFlÕ õåû@ýn¢ »ƒÊ‹—|ñÝÝvÏ:9 oTÛò†æ°°ð!ƒ ÙŸur“í·ˆ3°ïçÙÎQÅMJ5òí¹7e­à:—5pž¢f¶î²á#¦´\Af2{&¸Æ9QA©Óè¢×¼F9£LÝ,/³Ã•FlÚŽ«m¼Ö¬Nô#déy&.åzjgÌøø'Æa…éÜÏ.æ I&Ýk×a0e‹A©£‡ˆÓtQí1Œ‹HÖŸËnh´;—ŒÖ÷DM§†¶“^Çk-¶~4h#tí…ÒÂ>0/Fû‘eØî<§²Íi½šdZ>¦ 6Á¶ÉGc(µ#Ý`ùÝÎúÝÔ·Ksw7}+w ’+¦x:³y°ÓÕ"âKÃH[Nî-jè‹ñE]1Ïg>lÀS`s­™ãÚ<üŸû9Gw)Ò—ÏÃk{÷(Õä½4䤰§gþ‰bÒíç/ˆ‡úï£}½[iÓÓr$Nè~-Î~÷pSΞ*‘[¼“Œ…"„Ž‚Ùý¹›C֥ߠOIÂ0âg"•k÷fªTÇ—3h\)M‹í¼8DïVA¦$ÇrEÓ5Ð`’ªƒzJ!)Ö —íÉò®ÙvîòGæŒc•I»¸…’%È„‡—’O0ÆhKíFúÎ}a`Î4¹é×:éíIð| ôåUvS¼TTÆäG|aÆÕ§ªœZcÑNK-áü§K3qéyvË9ºiÓlã} Q‰6#û¯öŠÊî:飮.êGFÞÊ uÀ´ÛÝ#w½fè˸òMªaEܽkÝ1)(•åçèkaRÎù™;9Õ4ïR:„…Ôñå‹°Ù²Úxga7z†*L¢ ¼ÐM2æäÔCä¥-Bà·!¤©u,ÈÒÚÍ[áj ¼MíNŽ=¯ôqÓ7(wýJ&uÇoÍ[rÁ¡DQ0šÜ™©¾e±³ìÜü#CÉuá©Žbb#C¹ü½OäN­ØY2Ê>ªQ Ï,Èþä“2ï>çÃúŸE”ÄEŒéåf¹ª3h˜ÐªË)_Tz"]ÒjTøŠ`S'Ó—²wÅâþ¢ÎÛ¼ÅäÛ:0¤i–Â~;1´0{À{^ëâö#òPwÞL{'S9®ÿ'¨ñ¼ˆI¡ JZ°Ø;ꘜИ¯ijiýC}¤®6N&ýM»x ¤¥Îýá®ýM#CyÝŸ›Uñï"ˆ8è5‹,»¢:¥üů’üNZSøÊê*Þdû2)ÃЭgõÜ+XÀõ4Lœ¢Z»..Œ®ÑÔŸM³†zܸÇÌV‚„\Œ¹—8ÉÛñ²…—#Æ4~+{;§PÛ…mÊΩ{šÕw´p1°-l'£àãÔÒvý2ÓÐ\5Ù­Q–Eáîůw@çâ'9‹ìê8bÚ¿¬šF'S„Å¢ÇÞôtp}š,®~K9y+œÃB’Úµþ*gkGg®îÍ\ÑÀ0sP‡g'ÊéÙl#CñÑgÍXré³ptæb+wNŒž)®ìnƒäÜÄG¥å…Ïb¯gß´RŠ›øluúFwMÊ)(ß‹¤ë²É£:¿6侬B]óQ/#`!V­&†…öQÖíícŽÉ4)4|¢¢$fF3@#C’p–$B*¡FÖ¤`¾JåÎ<®•ÉòLÐñ¥Ô}òÄ|Ï™äçmÐÏDµÜ QüN¿"Bñ#-òĽоnåð¯bc³#-öaGcpêŽ:BHã‹ù‚Ü.l/‘•ô Z\¼\×:È·ëF:;àQ>ƽ{córF¸™——QÖlåíXW?e%°[êÀ—«‰,¹¸, îQ(áµßÍñ徜šPOÖ³„¤Ü8ÁŒ†¨æéÍ:ÝB9õ¾#-¡pãf88V+¬Yþy:YëßÝ$ŸH=XÑ;óe={Œ:´¯>ºÍ:Á€­Ï{ÑôsW~Ù÷®á=nî¸[F¨%¢,#`ñ…Ì#`ºê¼Dy0–«éúU»1æ®ØcI\4ða¦ÅÃ?:°ŽÙvj¨¿l›%†U/¹ÛI¬ƒ‚¬H†ï¦lÔÕØáÒ8º·½:±¯µnŽ#C}³÷åX®­³ðÂà6ñ =¨¢ì™+£“ýNÞ–˜SÞQ‹p« ~ýWëJŒô²Îñßô¼d·q!B~&é#ó[¿¯®˜Îtþ®úÛnp•–*D û‹Â›¡ãË‘ÈæX ¶åb^ ÷ÑØÇ8IÒ"ÔdX*#CZÉ#Cqê—If#`¨£Ì•W³^¸¶|“Á¿fŽùïÂ2Ø,*7¼oã34¬QÑës(Ù÷£è=:S §òîzûæ¢òn¶*å ¨U‘XÐdr fÆ5d»¶ýؼYÂEÀ½,Êmµ¨û¦d5Ÿô_,¨B#-PŒ/L²°C%ÓÀã,üAoÓÏåÐm<¦‚Ao¢Ùé—Í”_ת®•qF­YwO$á δ{"ŠEœv³J,‰‰(5RIÝVä#np?²×å®ÓÚÃ…Ëî÷ú䈿TÐëhÁõ½&—j!.F¹ò &ª>Xß BîwÈê~Þµ q”ÕÊ,·:‹‰‘µ7ÁpŽ¯Âo—Êu6.g~j—éÞ‰\.ÚÀ¾é‡SO+£%¸0(wäîãhäž+¤Zê—@C†ã~õ–aL#C)G:‹7ƒÛTÄvõ´f^ÄàÃ#`¬ù¬ÅäÇÀn½öM\õŒ.ywØKºødçÏ¥y}WGö]›ë A_'°Ûóx’ƒòo×îõ°•TtcDÇ,#CáÑ)`FèœøÃç„È•bÕ, ø[ øwt%V#`¥ê—²²“k{ÖJ6³ÓÁÿlãsÚyÝ:«ußp½ð¦—f‚¹u£ ›õÅx:Í\¢0ÁD ü)5zK®‘ð‹žÖ¹7ÛDÈg–Y1ËÞÎhµ©úÞÞnUç/:¨Ÿò|p|ËJEwÕ_,Ö#-ûdÛ\!'É×…7ˆeØçt Ö-òå!§ÍÃ>ÈXEÂ*¸{º³œÞ!'©$ÏÖáXçw›Éœ:[£®¦;”_¯V¿DðôkÚ0UM€ì:ßòk£Ò>¦v™RT熪óF|6‰½Þ™S”Û =âÞ;ù®—÷¯¬ý)³J2GïŽ+K éë¬ÍTyãa€vL°çúSøg§Hh);6þçÇDâöp‹M}¬)íÇ/–žÅûýÔ¡ìƒü>ì i|}Nü`„@ï‹$SçÌÿØÁâOá=¼£A»æ,„œûfMûâMX÷ç­÷ñC‹Yh ðÓOüYŠìFÄÞ@äÔ–®TV€¾oæ{Ý[Hs?©^t#`|7ú‹Mì"ÃOÅe#-Éps¥9d3é‚CÈ¢~Ev¥»ËÙötX3*…Øæcp;D¾ª³Ü†Œ¤X(Ël+|M|Q’[E¹çÛ¦t-$(O_-§”^$‰óÀû&^ûkye:‡Å¢aËû;<ßéà“­D“<#`-q9Û¨Ÿaa%ì5íQáëJS†ß§™jÎ q)Bi›’¦©ØÇØö„‰ùΧ²k·Ì8 µÛžÙDZìQ¯ônÚˆ›á6Ï%pŠÓÖNª›\RRÖ/¼MÏaj¶};¢–‡g¯+<)½X‚nÒŒ„j(%%K<(B;¹4´  ,rÓJɤ$5%׊®¦Œ ߯räáè¿Z¼B´Íó¼¨|J—ì[ºb÷8L©¹#CÝ#`ág¨Ã{ªçÀüÍmÏÔ•æFCERÂŽ]†LñW°dsÖ.Å`S++‡‚}tªèå÷—Kï|Ĭ);þ¿²!Ñ¢ \KÁÆkå‹øÞq×Lå°Âáíœ|'$¸Ø¿’ØR+‹å¹Cø׶w¸W#`ÍÉf¯4ƒ§4•«££“ä#Ç'ºù>57Qa¶¢$z§ ú^íu‡ø¢üJÉSUø³åFz8³2‘ÁÙîw ŸcW›8;D;ï —ýGßøJÞMó¢ÔèØ£«âqGèTʶfxs#C•Z»qK5š/325+¶Ígx%Ý„”bvª€œüWz¢WWx=?'n´žb/óøóŒoÇ;ŠŒy݃â;1®ë>G{[²ŠÝÌ®í^{£";Äò¹J_¼Í€¿›cO^¨äGÔ&ŽûÙ‰»x^ÆV‰a£=³{>láÚý‘–zƒHx´ŒÅy„cs.ÚÏ`´ÂAñÁÐðŒx̨*̱LüAn("p+I5AÌø˪áVÎôö~ž? Ñ®L¼‡àv¯HŸ·Ï?¶î{¹R,Þ€Òy}ë¹u:¯ä8wuS…AÒ{ðÃ#`:èEë>}Y\ù»´Íœ³g(ghÍhÒèX¬C>pùµ6˜\7OmÔ­êý{›]T%Åb¦º#noì^ÞÙ÷ÔõµDï«/ÌÛå㯶“´ÄéÆQ±À×*œ6O*Æ°À(g™/¹ƒŽWàø=ÂT9QäKWÑGˆ”ÖW>ÈvŸg²û£…ûyác}¤¡®æ¼;Ž¦ƒ¨žü*á¶*,GhP¡Ž(ñ̪ìa»©ð3®¢Ü·AƒbØ:+w+Vél„màPÐ<Ç=÷ìlÿªñÆácìzðµ™Qï]UVÎøꪪœ7¡Y½í s ¯Mˆì|¨Õ“#Ùy ¦å¡{»V¯›œ—è÷»9‰«ÂÅiI5µ(ëÕo¶èñ!Jëƒ{mÞZ¾ Û)‡±Cšè5ïG¸J’›‘䵂 ûÑ‚‡£“E°’ÄS3–…Ï|å¾\•“,YzxSw#Ä7Êëß{źl¹_#CkX#`½ÙeÊK ÁW3ÊçÍÀeèérN‰iæÃY»(âçö•!Ô×þwNÅz8¬g¬‘Øî[ïa#`cæîaÏÚÛµ/#Ô{ôQˤ°A@²!F ¡¨³Ð_20¤=Û1QÞgN\Ür€–!aÓÛßáÕ¾]¸ü2Är^œiÇã°›>^oóêìjÁë<›úæ/ôQ?£4ÌêÆ佤xÏXëæ“w99^4ð”M¹”Ô²è4OfÅ—5ëôðý{€½/â¹AËÚ½4ÀðÀ&ü‘åúðP6Q„q‡U4p®‘bG;ã‚tóWNgvz8¥r‹ì,JÊlQ‰‹š«.ç Û_–bd‹—Âi92@X½Ä€SÇ`y„®.:ß³Ÿl®ð¦u›T±_dR ó0P6*Ù+&¸†è8‚÷܃¯Éߥ3Ûv¾×õŽÉáÒ©áú6ëçQ)®@ sGMîYrR Ü€€=4á7dn«™"uÚ/o¬RûûJK»ÞÄFíòÓj]Mþñb=ù/¨òwª[k¿¶/ˆG‚6¥œ¦+:9žç@g²Ž_|’yäÐùSç@Í·‰GÆX;43w#®æôczyÀü–¿Ö.Wú(!Îg¸àºx8ðÅ´ÛÒ”Ò·I ôþ±ë>öàõGÃé3Ód<ží–Æ–Ë&ÝO‘º©ˆ£«u2ùKë^ÿ2ÊÂg¹>xÀDx5…~Îk»×oú°ŒU/.<#`)(C_ØX^QåpPY@¾û ¼?ׄR\”J7Û騑²ÕágÓö:¤×¸*«Âø6De½ã1ëZÈìˆÔ£’ ×Pìí~¾r£ßáPXE{9~¦#Å5&ÇR£ «¼®©à¸d »Tl¹v#-ªê1!CÞ-w?l¸èk1O#C¬»„î:¹>{wöïºîFÖ-¨68kJ§7Þ¼9nÓ›šœî~pÓNµû›‰ gwä¢8oÛÈ€0ÉŠ»¥ÜÏ܇¢X¿GÁÁ:—L—8G9§8äÃÓ–-T¸¢`”ñ#-`Ã9ܱbukI?œÕ.ÄÄzÔÚÇjñ>JQN~—æ5ÝÁ\*š yÙdª6*ÂŒõC'sŒp»Ÿš•˜¢ P÷ÜØN Š.ús <à)žµ^W÷J$´"'“âç][±€ «Ù5Yg„Xe:U‘ï¨$[Ç–o¥Ç>»m‡¤c¿äO^'ÁˆHÛRFy ç@`w=‚l„båá8™±Bë}(¦ªßbí»^×6¥m(صÓm°QñjE¤ñ6WACÜê\ƒ÷w9ÓøF#CLanº:Û‚áñ·9o)ˆ;®1±´Ìs[ûýhãjòÌqü·ãJ]Dcvj‚ÔiÀåù­ÀbŒ~¹Ûô99fÃ#Tûˆ'ÃHŸºäíµú~£†¦¨Ðz(Í°&Ðv×Þ l¬Ùŧ!‡ôß^•æ…7RgU\óíuŽÞeiaˆç}¹ö_Ã%Fç < óD"[ÚŽ`–’qýà¤ë´Ež›|›îŒ’Þ×ÆuêhEžù|ES¶!èÅV~~­”.‘מó[MZàÈD#C<ÍW>÷†rtŸð\)ø=²Ÿng\pY9Gx×2‡Hßwžª"4í”FӻɄuò”mˆ¦øÅÍÔ·²'Õ' ñ^#ì ÄëRa0â÷FÓ$¨AŠt˜ûÐøpu礪{#`ÙÂ19i¾pÛ²x—(ÅPˆ¥3ÃX«Æ«Qöm¢3Žb­îJn¯;ä<ò'²¯X³¶j˜(Ò­ ¿\Èí{l¶ö^¦såŒjE¥pÁʵ<ŽcÃÚt1”ͬJ¤ìnI¤žƒü'¡ ÍÛV‹òÖÜoñÚ{/VudÏ€ˆµ„£*Å " ɳhu©›aJN-W˵!wm¥µûEúì×`óƒØŠc_¹Ì1Œi'Baä>Ý!pt£$yzðÆ”¿L„éfÜÏ÷Vä@“áU~W]Ù|qJ©÷Ôf|¼œR<¸ì› Žg}Šo[©Æa‡˜í;ù8qs#C²|HCÏüŸ¢£™¦û1t»_$›PƒÈÀ4¬ÁÖ‹Üå#-ßÉN`©TlÞÝÝ·6ù÷ö>šèØÌ%tŸyø.Z3‘1¬–8ãùn`{~J°]ü8Ó;¯y ÛkMº2{ÁMÈWÇ!Px†±t¶Øt:99Þ‘=Ö‹šƒg+d*^Ɉ¨lýë7%§Àé;;ŸdÛQÚ®šƒz=|8Ü[¶ÍkH"aý¤öž-oâQ‡À-PÐq@¾xÑP¾œ…°QÍ)&¹H#`å#nÕ?]ã9ËaH•¸“žzoÃÆ•ê½2–«a¨kQ{ ÑE.!l̨ RÆŪVl"SwÃÁÃÜ’ {{§Ù ¶.OBð¤§t˜ëi¢pmåO˜·™ÍJ¶ô€ô@´„™$¢)X¶ór[ʉÏÄß#`$¸ÔÊç°^vLa2Ï-Ò!¼;#Cã[rï–‹g»Á3¸=,J{t'6ÌW‹ˆô©|‹ˆGPÙ—±#-BWmºðÝu¼‰KÇp Ç–0gœ»ü9¾Í›+×ù˜#-ü[òúhñÃ×êÙëaçƒú¹„|\‡èv§ÇŸÏòÇâ2縌™k¥Ù‡‡¡HÆõðsý™<€Â©ó{å }ÞŠôÿH*9ZÁéÛv4ìçÖìµ#CëgEÁËégv÷‹SWö†øÀFe[''Þ/‹†“ ç17‚V«‰}½jÀ’t)/Ö¿g‘#-öŸºšKþÎø?€ð̹ÖúögPëN·ð÷s~ØBk…Gr¤¢'8ð¢©#-£ÜŽa“­»«Ì~‚w¼\}N¼Æ—U ÍýVó“%4¹2eà€€$.‚¨#`IæÍXs¢íÐ"³Õ›înö߆¼¹;óÓÂ6\:|6nñéŒ<,¿Âæ¼ aî‹€|\N8òZ%#CÍ Í(Ìo¶sßúuܸ¯ÙWßë¼êÐf£ºG9„HVmOLÞ)ÇZ'Ý/›2D"ƒüïdÆ Ç]¤Š"AdX} Ÿ i(˜X‘ˆ†’•D#-#`öý;ÇÊÿOípѱþ¬ñQHùöëìâŸ_ÈúrúàY+øÁ»~7¦Þ(@TúT1!Ü¥Ä*Ýš°÷¥(Íɉʴ˜Åóµ$†0ëþxâÿWèÏoÓí+ËòÙª,c¸J ò•CRžÚç—ÃoUÏ·Çšôª‹éÃZN&HiQ±ÚUBÄÀ­#-‚#- ¢O#é* ¡éMî½#`u‚­$âê,š°˜HwI`‰[ãÊX()"‘!(>P€1ƒÀ@¤I¨)Q‚g»Âô%§ñµO°®¹’m LÍö ô솄ºü«“æù¯Ø÷-7-Y§cü´¾èi¦€ŠŠÈš%¾Üó’ÑO sÌS°[&#Cƒoô§óEÓõ}w òr{ÆŒäí÷(‚#-\$Mwlà6ß¹ Š8Ñ'¦=K×vLA,y#pÒ÷¢‰ˆ‘§žkø?ƒá¯dý>3íý³lÇ·aÍÛUaù‹ì*Z¬J¨T&* …úðÁæ»Ê© K×tñ,ú‰2ò_† ·¤¥ˆoićߡ]úb½ÏuÙ¹»¦Úcù®ÿKêyNUÜöO¬Å*#¼Ôáž4s#Cp–ó'|OR,\p’‚K'›m#n $&Å-ŸÍdÃù&,ßßö°ädp˜f$LÈbŠ Š çè3¯oý¸Í~{’#C„#ó½öèG®‚¿“Õgü¿«M±àø&=î3Bž¶'ìßöó¹ç„’Ùñ?£¢BŒĹæ%æÓß=##Ñž6’GºÚSh?ß…I®!á(dˆü£„ ~øð—vƒñ03Ù_Èc?›÷½{÷á r¸ƒ“îA7Òƒ”'^â@Ó]Ë…Më½õˆqŽ!¢¿×/_Ÿ÷¿[_—‡>œ„bÿŠŸ8NãåŸäÍO ߤ1ˆ9²ð>l½ÑôT¼ü§ 0I‡V×`ÿ)|þWÏæ›__£>1'VÀ"éG3qXwÍf°ÌôÖ#uxtð/©Ý@{&ÜʧÈ{ºõ-`ebð.±T2óSjl8°©£-$}øW6Ö$h?ÍûvÞR3‚ôã_ìĦÉ2a%åðnŒΘ)Ž›DBÿ.w–ùQ¶Bþ/üh1Ãì.˜Šœ~­ëêÄ=\MM0v~¦OÉŽ—:—5HfȾ¿cú©”{ABIëïø}|NuæúPÆ‘WuiîŸæG¤yÈ9ÃõÔüPÝ„!¶~„Q×oòÞ»JãZ_#C³ÔäG×U¸RÃß.RȺ#`©¹~æç‹Ê3v`ô"žmîÀ¢yøóŸYĶ|̺ÈI†ûP]°?f\¢E|ÑáK$ ¼ê!4¦÷FLù˜††ÕÌŽ½vür­Ìƒ¦—lU‹êEÍB† ª­@ÉøCCž¿Žá»$Ôh¬ò¶ëä)ÃÔ)aOa.o÷á­xÓo0#ºkCÚ®ù~I+{H­4À¼AÔ¼@(š:ù2‘B1ç Ön7uu@1ý'Ô•@~o®hM´|Hs#Câ0Ô (ò®®G:…ŸªÆ1v§,×Sº‹éëC}߸=קw49ÑÂD^G! #tè‚`§Ôi›ÊÇ/¾ ð}µµ”)ÈT€«f ÊÓ×r#-œ&â>uÆ#-ÿù#r#-/Rª®ý1„À¸ü‚Z£»yÏGv^÷u°Qœh™½]éúª(<$õ_¶M¤í»ýubëyÝ-»;72Çh< Þ»=ß….‡ÎNKí#`Ÿ#C5WÏJR.«¶oÅ…¤µ¡’ªß+ÁëÁ…̺Y¸‹WB€n¹%ÉDBçÖ~ëq uE:¨æºC`zQï> ?)¬Ç…tóõðvj]tì®Q\]­› d[àú߬:üÎic˜ÿt¾h¥ÅûÝò Î ^ßí {'»5߭ʤ”Œ+Ó!èÓ8üÛ•Ïðî1®òø©½^Û®€þæÍûx|F(ŸUBñ·#C`#i«õ7Žç£Ì´âëÈæêKxä#`󌡚(¢#-5ª3*{…@bRê›Õñå•ñ’ÝDy¼ïèëL5,fhè†èõPQF¡¬F¤“ª( >,¢P~ ±.<=â,?»™nêÖ#C­@7p˜@(ñ(ì•H0ñe+÷h¡‹#`ïU>zát0gR&ìT¥3“j¼w $˜…X'º¢šggèW÷Ç"òÁyœ/ëåC Â#`úå)èÆž™¿›«Ås×Çy¾*œñ™Û#C!Mù€(N¹ˆ/Qz#`=`uÜô`îÓ‹A†‰KÎwë略4Z1Äiã»8Øtâžÿ¾ÜçòZÖåúœÃ‚=D9<1›fË·âÊNþl~¯ÊÄÓ¬ç>aåÝÒo TŒt.Á±žÖn¸4;ö‡TÊ;gH¾4„ɱšôübêŽë¤äèe²¥[Zhøp ‰ÿEø"Ô{X+„G—ÎýpR…%Ž¹U…ï [ÄDuáâäz1dëòÇ2Õº7Ô–Xáëß¹í°éxÖɱ¥_Ëò?“Yëõý¤Ï~±Ñž+©Ã`Œ¼ør¯@ˆ"ýn(H—Ì-ã¾H_ƒ&¿#C±¬~;/í½åÈ3°„!  mš*¢ØÄJâ“}f6µo#ÏZ ¨sû+PÖ/åÈ(š?LeË=ä#`ļƒl:i¯`üˆä³ÇÆà1B÷+—4)†öxëäîÞÒæ’) ¨M‚²¢®7(uïmkؼšÃß±t¼Ë™¯~ùdP>} „@¯¼öÞ—M(F¦„ Ù¹`€|»Ÿ<ºƒ¼Ø0æ6ºàŠ ­M#-*”ŸõŽß/‚Ø•¾6$ƒ8Ÿë^vYÍ>,ŽPm&ò½\“|ìåÛ€çç)Ä#`tæˆJÃk¶Æü2:‰'Öx´zEÎ#-K«Z#CjS”ªh´…Ë¥Cø²ÏÁË õþÖ«¶Å˜Mïá'gÑL*u(”"PÊ#`ñf]åÇ.@¥{.{¥w#CVÚK—ûõºœ¿)ªÄÜg}I¤Zf·¨Ål’©ñ¢kã¦l­'±å½jd˜ L8ILĆg7²o\HÆèB”RïŸt`»Ç¯óùþFÓQÈ-i ¶åɇœW¨‰ ÝUtOå~®fb²ôð=úTaý[ƒÏð½õr‰!§ë>È^louvBŒŸ.i‡"£A­‡C…Ôó!P‰Lî¢ ©~Ž1S#`ÂOâÖš–8é¯76–tlßß?:b ¿%ËvVH*áÅØbñG3F;cÊ4)Ò!nÈÎÈ$À$%q[‡˜4b¾+swˆÆ’sñBÜ¢,¢Þÿ<üÚ|< >%o–¤òô{P3Ñg«•ÃÁ”©Óµgm Ð<¥öÞ:ZØVä‚ò$Ñ€S®óKÛšO¿“µãÙ*‹ä›thË­—¥ã7èƒêp¥ë¦X^yóV âÍŸn6`\DwÝJ5ÏVƒ€2á`2lAlx/_9Èh‡FCc.­|Êr>hjó×Â<}v˲zÌy÷ÃÛî1'Ÿº8¾°H=(ÝîX>X…p/UEå\Ðw‚9Æ r¾m>7›ø½U"°ñú&ÌvöÙ¾.íD-†3"I…ý>aDÜ`"yïqv-Æ#`ó悃ÀdhBÒšì×q4͈ëD‡rºÜØEÔ¼då´ÄÐ[d F:Â>#*¦’G*#CŠ»¢P…ÐјK3$C@3\îÒƒƒÖ&M ]{ù¥IÁxIŒ†:¥íjÿÊ*ŒÁŽµGe`JÆŒºµ4¼_¬6ðL&,üëÈ5£2#`B$˜a>ú(Tâ«Ìõ›N®šMÃwlž6hør¤"ÎèÆGG¨:H<7ܘs:øýÇŸb¼‘(£{Ó½Ìñ„UÌ‚ô>rw¥×ß=ÎZm±ºŽþK0sâç»%#Ì–@Ä0ôÖ¬yk–Û:A]ãQJ>Œ7‡”l#`£2aw~VPPü#-KüÞýP°Ê·ã½°¬“£åœ‘Ÿ°€6R¶ç»o¨0p[Ú¤ÁDØRAvó_¡ÜçBc"ÆRWJ=XÝ=NÑêþ6ëm¡†ùCýOï"‚äH5´AŽî¹¯~¿¤ÆÇØ9©õ¯Ä?»Ô¶M»ä~È5ü¿Ï¢¸ú³d”ù}^ü®Mcë°_øwFXÃ>N²>þaÞ9¶•[Ûä_ßöÛMò ‚ñÞ 7UA#CÓG€GºFŽ‚Šê î樹0 1ˆÅ¬t;Á$“#-þÏÖž„ß½1r„¿ä/ Í*˜‡7ûRZÙAC+Zÿ*V-<3É?OSxÿ¯Ïk§ofÆïófƒCÀÇÖ\^l»Dwoðs‡ (*ª$ª(b«ƒµ÷Έ" žíM Sþ h¼ÁØ.ã³ú¢Ï‡#CE³®÷ˆ ¨(úN9ëri¶ÐTÖTVíþÞÁþŸwÃB«\Ç/ɯ‡í¿ìa>¾\ÕTQaPA/è?®¿ï|~?¬¿âkò~gú™ú?I_‘™•s°G ÅÒ¸nòà {»âïþö~Œ~ïCû¤D*N]<¢—àG%~2úcêƒí”>j“LüŸ†‡ÖUUA°Àëõ`p!ùA©¶¤¡Lÿi(a+Á¿Š4û!C{N(ÄaþÿÇæ&©ß$æ Ô€ ãíæõ}ÍÞD‚0P=úÀöqÅ4ûg‡÷Ôsð*ê~=;T‡$ ÚJNLž©Iò¢ls¸*³’üþà „}cÌÈ;pØýd™2ô(ôˆüƒK÷#CœN#xûDRƒ`“G»rx¯Ù&:ëÉ8u7?¶#-00sϪÃ2h….Ò¶h'뢃Ü>ø{Êø QHêATG‚P2tlÁ*„!½4’$BSqO=dÈð[ÈøÜoôŒAoÜHÞäù•‡Ð¤ ] iÁ=žËvtd½\Ü@#-:Ö µB@n”NT¼H›Š #Cµà0KHÈbvQ€ “»æɶ=pn‚'=KüØ¢­øBCqñ;·2I°è(•wêV'Ƥ¡Jæ—ÜšÃo†Œ4ÏhÙío8Gpü ÎÍþ27Ìû ÜÁH·pG™£µ¯/`ýX†ïí]‘ß¡¾ì$“1BkM´5·Õ»RDÆÃx¦7³§4Ùo±1‘Ý­­Šp”"·Ç\Ɔ“NA|´¢JÀv¨XY½#È®ãÓceeòÆYIb¨ÏÜXóÉ_i-//ás՚㛵«¥'¥ãÑù Àž9„âÈþ®Æì ÚàpC hw%BdÕ9¦‡9w Þ/WŸ]Ž!Ì×— æ¨Â,PP¥|YT“ B>Ó#-H7Ï#-š©þùЀPà`@á!'¡ÿ>õT6c;í¦‡òDY££ÿe÷·ù"¿é¿óäÿÝû¯ÙözŸù­v×ð«þVŒh÷žÆÑ8rùf3ï’Ì®q3Ã/g¯hÿÎ÷g(Ê f#CŒÕ2iR‹NK²w{rË\k4~9'³™‡^×ûÝí»ßó¹ÉbËë2-‚Pyä?R_;nk#£s},ù´I”¬áÝhÑØ–õJxg¹”ßœW“™’‡ðv;/8Ùï|í‚JÇ#›Qåe³«¢[Bmî§J—qäËhk|EË]¬‡¤~ÈC0èó]îqÉ¿ñõöèšòÈ°\’²²¯ú²UÊöbIêœ?‚„/R}¢Di:]Ѳi#CKiQ¶²ÉF†Ëh¾¹{ S¯YNX¸d9Þ¬"aŒŒ’.4{¨Ú²#³ÝóÂ,ÄÚ óÏúm½y–ÖM'Âì“^٨̻ô9â\ɦÄ4+OŽÊˆéwËâuu²k³Fø®zÒØú £Kœ¾tõ'“>gu”ZÒW¸ÇSÿqçÉ ígÊfqí'†¼þù’z>˜;Œä;÷å,Ýžä=±ˆúNø_«æÍ`Íß^^¿@èF&ƉûóßòÆB8¸øŽ\!(`62À1É 3$C~Sîz¶€Ša ¨“=pßÙ…Í%Ѩ»Ÿò¾WؾN³Ý‡Ôúô|8©Qˆ{X2j׎»V#CvöCŒ\¹ XïÀåäuÔ+œ¼$9t6Ð]Txº~Æ„9×â¤4>B=ž$P£Q¥Ñ ­Àñ×vÂçùùS$-^ž@Â"JáÔîÿž8Ûጣíqë0ëçEJ”™F DQvC½ä<èyrømêÿ¨Æu‘VDDVN¾¯—ShÒNÝÄÜ=ÁðDääAçbVxì1 ê­ì6‡a$Ó±2—ņ“&9äØÃ3uíϧG·Ä¹Lù¬«žõœ‚s#Þ’o›ºq¥ä#C÷ wÙ¼&F!ï0À÷àêZJz #` w…zG±ß’Y6HÎPFaätõlvýìâk{4C·]äƒÛ¸ç¦ô ÔˆGµ>Íçô¾²›ô uמŸÜò=GO)¨N¤”ÂwÂûRë'´új¸æ-7èIM‰è‘ œF­Ö@È:ÓZäDs+s¶ yñBÐ8aô&º«>‡|PY‹œ†Ñ¸Ú´ XD°t±êà5³Ð‚@éPàÑŽºE0ÎǶ;ãÝÇ öYÂXéèÀÔ„öAãÊ|¡m·zsú]5عϊÕæzô¹Š§vÆÝGѦ&‘£ ›ø#CÏoÀù•žx†¼¨ÆÔœY‰Ú\“Ÿ=P#C"»(Û=2zjyÄŒû\weiÒp!C¶NH`’=|ÿ6#“˜lA¼a8d…#CÝXÈ"Ž§§Ë‡]•SßÎ'—[Û¾¹°ha2n`ü¥í{ÂÀÏ‚ USé<ùïPÚÍòý,úî¿n+•"wì}šÙ×>ßóšøO•{MJÉ]‰Þ?£úpQÙ#C¿hïN%6²¤ïœj¹ážV3#`mˆ" *Œ*rh¨#C"“¡-ŸñxiÈöÍýÁ|ׯØøjðz™3R³ÃPnÁË•õM:(¸=ûõC5w/fÉ{OFœC]‰öMdêäi™åÅN}®‹ö …j¶I~«Î=Œ)$Ð?|:ç}<–={ó­USTÍ2•EÖÆC¼ðÀðPô½‡q÷5á#hì ½°î\êMi7diˆ’ç÷b8Â:3†ö’ߌØn4™ø×Gؾ©aƒ°¶—cžƒ¯ŒÛsØáûÁÓ&- æ#`„Ù‡”è“wY1–ÊFNÀqÙOkmŒcK¢àÚPü].xÏ=гó#ÐczQ&7 &¨ ;ä.Fè½@yÜ¥Ê\¢ÉáßþÓÜðÄ^ž'‡ûøî„GN“^>&¤s&M%)&ÓÚv*L‡®ƒ”ôòÌðöla;º[êžj¶J0}e ”¦j OȘiÙ}ß«‚ŸeŒ;šŸÑWÉœ\!Α*gõyÕÕºÚ LÝt\APöšž¤í®ðaxSu5¡g#`è¨aè:öŽÒmµT4£ äpk´ ?rXb ц¼±7îÂxæpΤõ%èÀ–±ž5,:ôVzaÉ&eø3ŠN2p!ò‡ÇNnä\Æ«—¥7$lƒn9>£å×j÷ò=Æšá±2´„S ¸˜ˆyͧ•Plš½X˜¨å„™G ƒI¥e7©øðmÕÑWô“æ‰òÙí¾šâ>§84Úæî¸#¡@ïÝþ°:•³ã#-P‚!ËG–ù¹Âuyùž§k©xe )›Ñ¤÷­û÷ÃÆø$‰¯|’Õí\phn@ÓˆÑpuŽ&Á#{F䎘F#C‰ï×*7'Ø E«l* „46Û Ütãèºmýß´œ-'H~œß›:C½„=ÞÏ`Ÿ5\1¤ éÆ=e»³òäÑ"sÌdC¹%ìë\åAZi¿Ì95/ ù#CËrRXÿ›Búü‡³möÃ餜óDxì!@Ú lx‡”›zí4:àXè¹Ã=Åt¢ŽRÁOŽ‡z\=ò¨i~™Ú$I’ %H.ÄÀjFÅÀÛ]ëCf¢æq¿s•«‹£8ŪäÎÕw$UÜûYÂP7eÊÜτܵ4Óé„&CClŒ³v…¡tt7B#2±ÕND¼I¹îlØ;Ÿ|+H[¶oi±Ž ÇÌùUy5óü‡¢:üù°ù`È̼Æ aG(ÏN¿÷RŽ¯ý\yQN¥‚ðižC#`ìñ3Îù#'·Œ?°ü1`ŽÉšÎeWw[Sæ¨;O¿Þ)Ái±T?g»qŒˆÜf!0ÍZ±a’ÿÂüoTÈè/rL§#-EÄ×É&D#bÕØ*–æ0ßz'¶8ƒò=55‘/²k!6’vØê†5#C< TÜó ¡ŒHŠˆj4QZ†“©ß͹ÌMˆžK(ª”1Ýi_¨»³ €äôN•N¶IF,oÅ yäíŒÑØ#-\/ÆaÎ)SƒmTHäÿ'Ÿ‘…æ'ÑÊ|ÉÓ©Ú#-~/ ÁÜÕ:M|*Vg~öZDAŒ=µæiP;'ÕÖd؆vÆ”)UG}E¨4!ƒ¸nû½‡tàˆ·–*#-µ? Iy¯‘ ø#ˆÓ{"©þ ÄŸáiö<Í?Í4/ËåF8juŠ”@'|^.䛬ÚÓ¤¢H ]RÄUiC[º‡–£xÙˆc#-âÁôpr|Ö‡[2aQ#äZ¦¨Ø¢¬êaz3‡ñxT)^#-Ù÷@ã#C÷¶9C÷âaéÄýú©8üûE®'àúlŽÎöëÐc&°~ N/űkm#`¶»–½šˆƒ¦Ogbpº ”{<àêü÷¸ìàñ݈NÈìë♾MØoå›>uÖ<“¸`;ï9Ç»Ž P’wÌã—ç(îÐÜa§PÄžp”ÈwžUèd$StáYßT¯iì@@ÁÀdKÄ¡ó‘3™áÒx¦™Ãˆ½¤u -èÀuž !äm:ÎÇ¡£Ô”‡‰™Øh¦’±LAHEt##-¤‰¦#`*G4Æ–ÚÉ#-ï¸/p¤®HÚ“Ãe*“Ýå)E(iG~Q‡Ê:FÂsÊùzÝÁ̾0Ïl5‹Ê r·êó:›l(*’’™±ã |¡¸#îëøWÂê,©k35>)´|­÷Iü\ˆTÏ}•~âßC7ŸÜ'w@ž½ŠäC“Ô_ŸÂå†ý” 3Óé¹æ#`°Àø>úS©ã#UTöIPKÊ®;Ar©3Þ]ÍÞùód¢ä“OŽë){X_ëaê`að|¸ý]w]ù?r`$%Ó¿—r½7 A€DI…Q¦B»î¨îq0‘EFX#-vO¯ÖÈbcsë[¶Õ×—Òß%…ŒÔ#`Qhˆ¾É¸kp熌.,ÐF#`îу€ÐIïˆ/D§ÏŤ@ƒðJ}?ù9CÈ6š)#Ž åÍ]=n©Ù'Â#`D÷Vb•é‘æ^#Cç¡Ö?gVß.»p6€ñ”쓯fvgË?öŽ©²ùÊpÙùïýe~h@¿ýïïŸËù~³×éî<‡ò`ƒ?¤ôŠ¢#˜uë¡O³u¨?àk¾H‡¤#-ˆ™áC–E›·sÖä°Õ†ÍÃú¾DáÜq‡(Ê>Y¢"¿z¤4²€Ý…–Rîugð?;)›ô­1‘Tów•ÞØ~@YHe½‹âläuv÷D'cà *¬C=J‚/s ?Š¼@›þ/”ØGæjaö}<¼Õ烓ãÛó™˜û‹úŽ€fá»/‘¢ÕÌD 'ëù¿úÿêÿÊúÿå{Ä¿ïþp¢3ü÷eÿš+ù•MAºÂÑ™j9Îê2huá</¼É­5|''F£ß/ëiB´ßpíéϧ‰íûOÍê#à$(I'¤U D,ñ°%ÃÂ}A’6^ÜɤIìúà˜/r)¡ùH˜$/$e‰#-pÑø­vèk\YÈ’DÔ,IÞ°þïö»6`Æ’å¢#eGÞÌSˆª¨Ù†åVj3oŽÃ­³;鄘?qÿŽøÚãº=·t/Pß#-9òôHžŽ}ž °?Ãý?«§þÿ¼|þýy‚§8@Oק÷°ÖM¶Ñd.L%³ôA|¹ëíO:¾Ò ú+ÉÕ3wœq‰#-=ìðd’)[TŸÚêïšI2­u «immŠßœ#C?ßù#-¨Â\#-üQ˜0ÞÊE­ª«;Õ GØ>€Âaîo¦=®«Ç1!aò¼ýR•QÈåd±ý†L‚èŽ8(֚ƻDŽ‰”Pšë€{qW´ Ãi3ê¹ÿuÈ( ‹çÚ¬Ù¶ÓÙ‹½¿ŸãÏó#]hüZQò5y2ÌxðOœî#z…læ~T#Cè6¬‘ª×xﶇÇãéDæÔN€Šw…úè¯rdá‚k#±BL™3¾?#`DíU}G.Àº€oƒ‡üŸ¸Ä""òSÊ?ãÿó{ËÇ™ØÍ?8À b(Ô~YƒÇ‹§MCR8zFºd´@hAÁÊ3‹°žÛ†€Yqa·¤(D}3{·O#C>'óËÂÚÈúY§,'ÐôáÇëëðúz~`à;s˜?„5D,DÁ-L}v#` ÌIŸAÿåªa؈1ãâ#-jÿÑî9 ¤m<îà¯ÀÍ#-\ÅîÙDX;kŒµû <Ôô÷/ôÕQW#CSá >3Ì.Sí#C5=ßüb—`‰ÔT0L™t*È-¢ÿ俨EÑ#-f'âÇ£UœÆR¾L´&³~÷vaàÛ³èi°¤† ¨j:ǖׇz?J’ŽCÄ£¯]ÀáÌ»³Æt¡CùßBzt„üéÜËu18•HøˆE#-±Mª~Áëßç„W¹ÊÄùËûuZéBÀrþ•ðGè…à‚-Žª¼=èÄ]ÇfyȨêxÌñBŒ´M{¿üæúfC2ÅFrâÉßÌ <¥îâðÝØjóûG,l”„yô#ªJ}2pPÔ‘’-ô½ÂÿYAç­ÇáO²r’濈æ ÇïøíøÐdQÿ*äS ð¨ƒ/ñOkÇÌUžŒw_K:b‹h¢!¢éù²ú.é.w0Ðå†Ûšïéúÿ3þlÿWRÍçPâ‡È?¾Nü‡ÄQßÞ½‚{Ú+ÝÝà§Æ—ûÕlßá3x鬛0­·Ù¦‡Qþé]±½$žè?È@؃FýÑäAÔŽgþ@è¢%AðB»PpùúQˆ_þˆØ¥á/Uõ”żL’)•š Ы@IÙáoo1d*dŒ<ꌈEï iª"ŠKK) ±”¬hOÈšÍÛ¿’ý§m‰%rõ Ýé1%ýÕÓÉ9’a$µÀm}OAôs&‡ öñù@ • ò¸õùzû|Ü£Éâ`$¤Õ^öc“FdÑ­o1Ñvq€#`Q BP•böÅ «ywwgËhôÄ=cµ]‹¡ð•ÇÜR.¦`ˆ. }ꉬ;ï[ÔŸ§dØ9%ñüxqßö·#Cþµè”¥üM¬Ž1½îϘ0w‹Á,ž–Ïó½xã2óf@µ—ëÐzgíxC›ãâIö„Û× ]#`àrÕIvÕØÍÞ¡l#-L)»º6b2£ÆFfiìºìÆKü­mªS,[µêâ\Ü€ÒìwÛ+9¡¤ŽºÙÎò¬ßÊ¿“þL¯A½+@&r#-Å2Qbüj)=4T;Ķ »Šâ‹à¡ëh> Ò‚“£L£é¬4+ÞÌuª8ž2ÊCTo0qALÁK³ã«C«»\Ä50ä<Øç¤:ÔuÂúrOyqpÅÄ#`VX–µ°ÿ}¾ ™›™œ"Š”LêC3Š”µøbZ2n}ó¢:ÏcÖ.>æMòûwã=D’‹²Ê>4°š;ܳ35õŠÝ[»4̱Œ^Úœ³37ÄúŒì{ÂIסPƒHh憅 ÏðÛa2…Ï]»'x®33¡†SOxê.—c{›ù»¹HkO¾Ñ)”É¥KƱ}Ïש¨ª0A!#`$6È â€"YB6òÆ/×K„Ó-®]ÙÚôâ Æûeç«ó»ªÛŒ<¯‡<´Tk¨ÞÓo ø@D@´\*9¹þCä÷Ÿ~ô‘î—x¤"""ô}"•îyš¹½L~àÄ&­eµû]vZ*#CY¨Jg†Ô¶íXAì:9G!Ù^pZo‘÷=/Asê#d–¾éd1JW.ë% :Š:í ¯&‚œfÆ/¸ ŽŠšÖ'2ß_ég£ l&öf5þ]Ÿø5hÆ£ûœÁ»Þç®»Ñ$nÌÌÚ½Þ3BE‘B¦kh¹õáËiÂ#C18*³¶q”¤M˜m{|Z#-–¦¢ŠT?ê`ñ"1ó9<†ƒE@^>žïâ÷üü{8þ@ (e„+v»‡Ãל#Ct×ïЗ)1³äŸ÷õÓñŸ"„öCþñOļKf9?ùˆ¦1#-¡^»ú^ˆ^ÿ6®±^ÃÒ%‡ÉÔÎLÇŸMaÝ“b¡šÀ“©cÜœU森»¥/Ÿ#ãàœD^›ïßË“Ë5ù¸ü€ã¼üç’ôËŸhª¶ªÀ7Ò8>hó¬H›úW+ Æ.D†v΃yUHߺ·áS„pB¬‚žˆ—®c¬"ƒU.@ùïÁÓ…g ˜#-@ëæ%©Ï¹šCäT ‡ù#Ö£ßî^¦ö|K“3òÙÃØ|äJ" öP¿»ó?S½cP£Œ­þl¨ >KÝP wÄÖƒë)3îz#`J÷‹‡Æ­)" (ü„<€óÙ/À̸j¢ˆÆLžÃnÊ1CCØ"Ûº?—*Ì ùÎäÁt'u¼M?¼âk%^K†²nÒr,Á°ÁÊHådƒr«2ëH+f}µt\¶#ëáG¾/ödÏ3Pxxó‘Ó#ÍðÞ”#ao­T­ë¼Ì!þO­}»ùâ´¸æ â"pf&±ß5UF—ø6®LÔ]ô0…¨÷8ç&ˆ->#‘Ó;󎪪<’ ‚×+JTç¢;^/Y—WÇÅíÓªñ{VKÌ?XxÖe,ì`#©j#C@þÏÝl[ûCVÑ=ëÞ5ü¤=Ìæ7vØùy‡ÔÕÑv9VóOIˆ¯'æÜI”ÈŠR§P¿)5&’Ú¨Ž2Ú×µ¼›lC®ÿ_¥•CˆÞ#'ª"ÕlÆ)û_Ö£ã¾~úù P†6VbAú•C+JÔ\:ãàÔ»R)KÎëœúß9ÉöiùFÚס{Õ×"Ù9äFøSþ„íÝL8dÊûTÿ•=S¥ÝaË3†÷šÎ~Šñ’û·Ù|$äÞ‡5/1ÝÏggúûÿ>óìýrÕ”»^¬s¾+ë×¹ƒÑ„Þ¢Q⢫f€’Ò¹0Ü®`£:3Míóö'†ºy'ÚNû˜1’?ʺ›?E #`ß®ÓõŒ§“±l^%­žÑú“¦VéeoòzJ²æQŠáâ„Ò«ªý[b7²:ø|`›ÞÜN½tC®½¡ŒKü^ñ²Í΢«Šž2?ŽpñÈ¿Æÿ<ñvÈ5Õ}^o5“%úÜt9ñ¯Lzœ³ð54¥åÖé-Ȫ"®eG…!T^íÊÖeÈtŒ_µÜ*øqeÅ0I8þrUB¾kJéÏN‡wÆ÷±ˆÁ+á\õƒ7ë‘ÉÆŸH«ôrbv|fbÿ+ÔoEÃŽø—ô“·\kÅs¯$lº;‰ú¬Ûæ¿Z¯ô¿jÒâÇóraï›™Yh*ä¯^màÈ(Œ•"ÉgQÅÏ0ÚÃÈ×<À0±„·E·o“`õ Ø·9ÁƆ{ÖsóÑÄÒeÙ‘oÚÍX„J#`d#`îIÊôäÆtUZy7}3W»k^8°Ç1¯Ô…D1‡×ÐSǹù ûYôú—~šò†;CžUb_[ÕM¾K&†³^Ž>ó…J⭌פRT>*ó­Xômôô}øüÂxÓjÒ?w7ÑyMbÀ9n'V»¾½ÚÑôL¨îõq›xÜ„¯ó³¾j¸„žå9Q‰|Çäê¦ß~6W KÀ‰syyí¨âž“Ÿýz’eæ¢YBKò<k½\>+Æä]F¢gßc}`½¯¶ Ù‘Žß$ìGõs8ç[6èŠÃËJ:áé„ÑÍéêaÖà僞ûV¸(€*U³3Cš“÷^Ïë„׎œÄaäY[m±‹¯Ó½¡/Šcªu¿]uY® b:’sp/-К‘ŒüÔ,a¢ßM;¸‘N|!Ñù, ‹E÷gÔ ÂRmAQ"Å#`‚ØUX´æ~<ÝN¤ Äsc`<ãë`›½*„¦1ä!ªû7#õEF§,laÇcØçؼ³Qe+3¬!å½ýýU…Âj«ÕžoÓÖLšLFQ2âR‡¿¸©#9ü>qöëÉ×nJ¿}W:À¤Ù¤]"ÅúÇЙÇçJ·øç(T˜u9jçCNÉIæ2hjÔæK,£ÕôKiä|2æc~l¨ÓæûÝEÏ°wÍß'ÁØ!7ðª£ì«Kn}‹Oâå.S÷XÁ<–äñR0à…í&%J-Y ªõboÉÌ¿–pÇ™Éÿ6ÑcñÌ—Wí ÍxÆN3_¤&ðŽLm^‰­çÜ¢®¡¿RÚ-B¿„G§·Û6ƒÔ†yý®Pd`ÈÀ9A}," #CÄá¶zv¾¼uè½\ò7úLWlCwåÏÉnÀ%îÖðJg_£ìã…8Eà Bæ,ºóëty¥@3gh€á9˜’¬n›sºæÈ ™¨s+Ç1A¶ÖÙi=‚°"Š¼åˆARò³-¤áŒ€3ò nQãþ,ýñ$î6NÇ—ô•ƒ>¶ïÚ<±=hdLW¯‘HqU6_E‚ƒ²-¸¡ž*î#CYw¤À€hl=Y^A#²[ûæ|=ú¼‚…Ì6Óš–áW îa` ¾#7•1ªí6<}•s‚”›õ4ñQ#-n(FßU+ñw;ø®ûÙ·r‚<³©Ÿn3,ÿÀ´ÝvŠýÇÛÆ0 Ò“öúÖ¥,8âE¼Dì±Úc‡ëÝ™©ñ´t8ãüøŒâ#X‹[Nß“¥ý>ò»&æÆþ‹þ•ýZ××T1W ›˶=ìÏÇái‹y¹õkf!&J¼?éùL~þ@Þ ”kQ]}{ݘç¼JÉ?" ø½[ËzN^SF-u%qRƒ­@X®½ì‘ÆÅó kÅ®q ypBµÕs;ÇQ?G`Â+.™¹™¸}Œ‡åOVjM¶çô`ËߧKüi©êÖïzb´»œUÎÉëCEžCŽþ 2œãî_}G‚#Ín—ut{z$™`373Õ‘3#%x`‰"¥¿S(žïq!’%¢O×]ïî0Ìj“#qk…ØTÜ|¿69á©)¯ôÌ£3ßÕã4Û=MâU`,¶ê˜¤ÛãÚïô¼k_uúžØ÷ûaòeaþÇ4yî ù¨cyÕs>Cx0O™èÙfjh²$„’üÿLñu¤ñßÍ®›.»| w„ÿ(u‘ ^dlÒ,jÀŽC¾-"I´i÷º¦™óLù`¸È ô2»û?zâô¨ôÜt'³*C:zÚÃúˆÈ²áWúª§»}?{ªÍª€L)8xmÏ[÷,Ж£.¶ç³Ùo$-Y‚%z‡YÏ›ÎcXpùýØíîuîj|3tú7ÚOð_JŒgðfºè8òJsíßèÛaÞø|†e!}àØÊ“`¥"ÎòF­¯oá·‘bL µÿ†«ú?ÛFþ·þúúQÿ®T”ÌÖ#Cä3&Ìšøg†¼Õ žôsÁ`@T!€zU&‘2ª>÷âýiìfƒË†¸¨mÁ`o/áù^«w|&Þ ;oè>›Ç(Ïf"˜vè-—u«h?ìñ_~Ùå8S†|c_Ô(ß_8Ç–“8~ C*PÒé#`Qè.×l÷#C¼xß,ÒWfžä.ʼn~2HÚ’Ë+Ù/ Ʊû-5“r7§5­¢Q'@›e(h*"óµÓ´zîyî‹#ChC'Õ´Øà‹j=Æ¿7ì±útQ§„ ­‹Ò€ˆVORŒ)º)„”J¢¯öÎr9íÑ«=ûóü¤iíäYÜã“YÞw1-tÌ?Þ$lÜ4Iý¸8¶t™Ž4¾×’·ƒ×@¥Í#`¹t¥ZJ®&±j‰­ UI6¦ \f‘„Ç•è[ñ³•¨€à­ˆ6¦e³#C˜zÊŽ”àé᳘˺ßP1®7žæxÙßYÛÐÿŒõëËûâdó&½½öO£¬í®ÇBÌ1f¤Òù¢¡ý=:f”¦VWŒ—cå1­ àj+¬ D@Ñ.GGs_ªuAѧ*ƒieÃ|!È®H@AêÚ?¸zGß÷Cne§ðŸÓ´…€*I‚§Íôâ&}¿M$ÝWÜ»óíßþ=À!ýpè~Ÿôä5#- Ç(¢=»ø<—R¦€OÍ9*ÿt#-é(ä¤V•#`(¡NC’$ƒÂÁ~Ò5|MA6…P(ˆ6ŽkŸ›Ø?ÍdßÞƒN¬7!7ÚœÌwÖ{ @‰6oß5#-¤Æ€?ö]Ãüp~SIª.„a9Jó#-àï˜þð˜þ$tÐ "5oç@Íò§íñ'?¨WËù®Þ>q¿Ðåûpèa-|S½;Š!ª‚P"nnÿ ñü ›µ#CŸÁ1£ï©ô1çE¹B˜zh0…†|¤Niï*º8õ÷îý†zf™“íaVp~|/‚èMõÊœ éÊÎ;Ó‡‡çt’´¾£h‰,×zxtÒ"­S˜¿ÎI ì×ï>}¥ª%4„Éý³¡,X‚ETôäAE£9À Ÿü¤ø4# ‚ÿíÿðÑ@ÿQÏ]¿Øì.ÒÝ#-(Ò˜µGÒ8É0W0ú9ùPÚàj¤û'ŒÐ6±+NFhš.8C´êÓ¯þN-›ÕšµœŽ9íÔô€L[lÎàÈaŠ*6çYr lÖÊúÑlˆŠEˆÂ˜¯•©¬ÑSf[ŸC‡>[7¬8!¦#C³P…­_dòLz³ÐÆ~úùaàI]´³>ìÐ’û°kìâb*"#Öû<ÉЦ֘‚jۣ߮ìjÆÛmƒ9>—j’Dd¡û?øgGq´b¢#AF¾ º9{ínÔQwb~Âw$·Ùq"ó&"•ü*45Q #-66'´°bl‘Bý{­þNÉ­w˜å“Q£dÑ£^\(ˆ'lpÛ“HÀi½`âÏ7nr kAZ1XÚ3mû<ç†4R´¡@ûåGŠÄÈ,°JÈ&2šr#C¶ØKQ´âª¦ 2ˆýÐ(a!ÀÉ‘9¨¬‚ÏÄ=„òïªÊ\/»xü±™×'§ÓÙÌí[GÎ|$÷#`ûy‘ª%…Tðù>Ï'äãÏWƒ¹ {”õtî|¹­°"}ò€ýIT”w~Úí#-9H¡Ä;à/%òA>l€4W`ü,€v^ÈâG1(MIJ¤Q()¹¬F¡ˆ ˜Eäžàç,­(òd„S°Ûˆt”ä#-È”‡Á?d*{I¯V”óß¿9ß,HRè4R¾N‹¸@‘J§`] ¡Hê@^H y*ºÀ.Ä(½+¿#-wØÞÁá#-:À)„,)Ô‡Xå(5Ç\Tú=ŸWàŸ·ýYüÁüú#C]è†*ù>Aó}ÏýJ—ØãÚK;ïAº¨Ã‡Hy ÄbÓaÁ!_g¼žÇá—®‡#-ðÃø#`Bã’Ì~Ä°û͉)épÃ"§Û(3¡ø/éó^}YqE÷`+÷lwçVj¼Ð•Sîš>IÁø’dR‡‡aâ|IÁæŸìïÕÆñï;#C]”OÜ9Ž¡äôy†¯óèV¬B?q@Ä~ #Cü¬ö‹“`wðätåyáÄ:®|Ûz5GOÆ©ƒŠÎ¤?9„eŽ1œ¼r³–?uoŒ¦žA˸¢>U?O„…¤\2½±8£ŒR ‚8F#<ŠÇ]tÓþšçÜž¾¾O¹ƒVBvh¾7#`ß=1 äªjìÍDyâEf#- @Dž<\äP]a´o«’³~Óø¼#œàr&í IÀF?Áüœw¶þ`´[·ìx~­>‡Ã·/øf;r´y~þ?¤}øõ÷Û¹qÀ;ý .¶ìf5ªñý!poJÒZ±‹É¹ÒGãý‡‘öCüÍ¿½öÿ¬ðÒIçÌ:t.` ð„giëݳéöæ#`“&ÿ7øÇî#-eâˆQ®ÿ›Á#CÑ7ñËþ\ÁÒÔû½#`©ð0ÀÚ„¡ÂPÑ%#CSùÏÃ]¦=}9Ÿ°ìÿ6÷A6iòzÖn8¹Wâ]1ŒXŠèeb÷WÕýß܇íþ/âýÜÎNÜ{z¯ú_í áúèÿfWüJÿ 1ˆÈÛˆ5#-ú¿Í[C]¾Ò]-#LÇ\ÏüײõW:¼†ŸÅTÕ6oû'3ûJ3 ˜Sçÿ‡7#-ì?‚ž~g“¡E§]<<Ðh† ä@22Žg#-¹$BÏìµT½$³œà›É±G› Þi:õÖKb(ó"\”ªkÖɘ÷Õ¾QH  »^hŸëX0bº9àÿÎP—E÷ƒiŸaÊ ¤Ø‰¯)äH˜îóó9÷€<ЈS#-ïP”{×¼ó.ÌMg^Ã+[úýó¼åOç·ª|hrç3ÄóaœÊQa^}¤ ã<#C S¿Š‡¾HY5;ÍqëD«£¶à*êŸìò¦À#ëâøxj¬jìîþ„]Ðéç÷’Èû1û?¤:òHîÉ#\«CÄ$¹Sr½ÊC”*„RŒ ¼žï‘\èªM'Eúé|™øþÕÏÜ@ ÚtŒy—ÙAýÑŸvlJøÂï&ÿëú4ñiü¼#CDO¹azhˆg¹=ħGì*ÊÏ!×æò©å|þÜq&°#Câqƒ£ús~ÎBxJJgÍìûþÒ¿'¿ûEÛ ÝÐÁ°ÚÈhW,9p#`|@<˜ö©öoZº˜å¿XUÀ2šÎáH£°6ŒŒ Ñ¦/ÖƒƒaûXŸsóV!ÌR˜në/‡>G#`ú#`ÏÎZ)UìÍIþù1=`fKž ÃïÚ¿¸Ÿê0º¯uo~f¨#L”b0¸Â, 2øç‰Ï»ÏXR½a#CßJ~àõ«¢Ž£„|:¸s‡V6ÐwÅ Ôã¸(é’]‰Ýè;g¡Œ´uD}ý3oŸDì7âåhŸÔâî¢ Û¹W.™É×Ú|-cUûÒMæè/X ˆ¿rýÛ{J=`~!xê=…à¾ËG>q„ØöQóo`c– /Î c·à2˜&.¦žãÉ’½¥@ç3%ôÄgã ò4:³')øVOf²S¸?Åéßq5§#CÍ0Ba(ôM%(PƶhÚþ:õÉô‚úÉqL6PÑ û>sC„¾ ç©¸žH¾#ª’®Ý î)69N©£ cAÂ2¨.žNÞ_|D–Œ-’äõ˜†?@tácÅ;(ÐóÎ\ŒG½ÛöIïl/’Oò~”$$í´µzD™§ú¸ñfÚÔ´õuÇø?8rä TßAJ#ãþ‹#CðBî„E zX÷Ï‚\…*#ý'¥Úoh/8VÍp&Û›³ ’“0Çý{™ÜÖª\ƒ&X`L!hqÛý&͘+ÏL7°å‰ã?è<#`ø}ƒt—Ƕ¿²#N#;ÿ’gýžÇÛ^ºœ>1½jrñ³ˆI1)¡¼A«XùÑpÃIm~I5ä#C#àÒXY#1„ö¼Ñ“9Ćcíà½âÜÄA}?é·Ô%ÑºÞ oÙìµfß<ÅÊf‹ #`P¨˜§Mˇ ù³†<Ù/ésú$ßb•ªCæž>â¹vxzÂ:Bº.¯ëßÉšˆ‰&j¿»“gáXÉúoêÈÒÇ dø€êÏò ÿDûô]ãòÝ/ÅýÙ§åÃ'ä÷Ï{/%¼®Ó)º>=k#C'$GµkŒl=­ Zé+^«éÃpŒ¤} uÚ=v–û:€û~<¿Ll@Ħ Iv­ŠžŸ?µ9Ýoù¿ùÿêŸúß$O¿?2IÓpúC»8ÕsO÷¿Ò”ÝŒ8¼^g?Ëvø˜°q·Á¯Ñ†#@8èùº,Küã'~žÓÚó;A!8ú?°4H´ÓDï_±þwÀJ…ÇÀ^±yˆñå4c¶•Z­F&ÖÂcÅsè3ºiõ:ÍIJO"¦aðÿž²ÙTœ\’G=1– :žÛDH0åeÒž‘‹PúhŒqd¸qÒ MVqÆ1e¸]¨–ªJU2“ÈÚÓ8J• ÿ¼Ôé5§—ö—fØd’ƈòÔÓ:U*ª»Ubî¨Ò=¼<çô°;B‡ãè¡ìîáû¼ß8ê~3°ÂÑÖn½]KÇ!3ÑÛÝÿâ.˜Ú3Ô‚º2/8¦àŠ=(PïTR„¡:¨&ƒþïÓ¶L£Ï±ÏÀìnÁųݯóìÛ1#CM N …G£Ä@s#¿«r(ñ¦¿OÃõÏ?p÷b†8ÉhþW…(ÿ0wÌ#`'ìÚE>¡šˆº›¤t³7§BÙrµ”Ô”åüà»]<Î&¸$ƒÿXôo©›ÿû»liOuü*±½I$iA¯åÓü½”¹:ÓÜŸ¼ü£ u…Q»\¡‡Š‹Ÿ)Dò4¨Ù­«[c÷†r‚³Súà %±û6ÿ€q)×Têûs2RˆñÁMFvþ:]oxo”¹u¯ÿ{ŸÞRaSi“Ÿço áÁ©[Ä.ü¢LJ½ýBÁ¢(y+"ø35Õg-6X»4‰,â ¤ *–¸;DŽIÈ#䘿óý_Ãô}À_p¦Ùñ¤F?å=Ô¬3^kÔjƒt”·h×øjI“‰Ú˜ ¿ÎÖ x_#C5n\‹{¾£láUT‰‘ i¶7ß#C”^ë­ H7ÍödÐ,2À—8’€Y IW¼.ç@äÿ'z¯oh¾I¸w<°áýÏ°-Œ˜qŒŒˆòã@Ç#-êá0ÇÙ÷~ÿøöïÈñãêíJëˆah`åôâ½þ {^'.äéù>°óÓÓØ«ës©éG´ÇmåVìJ抙zt”ÛÚ[|mÃ;G±/ò®¡À‘¤$&Ï»¾wcH@N{ÜèÊ=ÓS ÎRRu=;y^G¿ßFž9±ƒ„¥K(¨Õêà5Á@ ÀT‡ˆÉpèeÝ¡RY@>Eì&%ò4ž¾FqëBt:Ôì±Ü“Ñ´:I'vÁ´Bƒo=­x·ùpÍ}‡ýßöÿï÷~PãØõbî„ô~f›o¨7`ÒpEž”;XuþÅ׵ߘo9ÓËß$žÁpŸ#`õëEžÂÕ‘”“2zË*!®“ƒYí Üß`“Sº:²j`rbÓnÁ¨œM#`ŽJC×ÜSüñÚ³Õ~iúmåüTë»PNeÇÈ¿Ù£ø´µøö­§ý`çÌÜyµxzïÙà7ùÏåïi;PÿF«Rbïr=¦ ‰Žç,E±Š#`ò9sçù4Éfó?¯õæŒCðίŒœ:3IÝÇ–*õeÓŽ@Ì5¢¼cZÕl+{†ŸWªN#CÌYN*ÇX\®ºì)²¢#&Éz’I$ÍÔi¬OS.µšÇq·5&±U­U-oY1WÃ&á•ê0r“6SZÀËÐÃŒ ŒÕ.ðÈ÷r¬Éˆ†A´±PJL Ïêe+™À‚xé¨]ꇌQ@!á ì¼=?î·ê§žÅ¢:‘É××ÈßnÓÀæDJFè>ñðX¿4ÉJîÉ)`CßÑÙÑÛëìãÚ;¯„Q’éSòë¸ïâ…UŽpUÅYE·ªýÙ† ë ÿ‡÷¼{þð<Ñóþ%uÇÇÄ8§§ú}»4ÊÛ™u0=žQkfp!.åƒÙCESJi#`ñèGÂþOÕÓß¾]Ö¡Až¤•w·£'Ê’é¢Ë AjŒ,²|`ðèA"èŽÍp¹ÅÜ.[Ъ§Ñh]* 1¬rt»L#`®õešÁ‚H‚!&NbŒÜÃB6³+&ñuë»Ç#C¶ÚÂJ“p~ O¦cmÐâ娩’bàᢢ öêcˆâI1jò(T'\¾7åØë>jo¿;Ä«È€î;†!¥ò’|ꥅ#-n|“s]§¿}W/üÌò, ›è.J?%tû~¿pe†äc¸p…)ì> ëIpzâ;úùT-¥òôô\Ú>ø°EŠgÃÕ!ƒPbˆ )ªR|âW_—ƒzþloÄù#`A!D³©ûL«.ï²Aküa»«4dŒ“ñäÐËŸ3‚Ú¤R»~YvÃCð¹gj¦*Õ]ýåM3§#ñý*hOK8tHS®?wð{š;kËßp‡ÑïøWÔYIóQ_ŒÅ-°@TWôÿ:s8· AnÂof10„K¥ú?_ŽçáCñÍïº&HR#-”}áÌû{i휦üµ>:ý%•”¡l #`ÆÆ6q³Á;¡tþØ…ãšž‘:j!§ ?žUôB@‡ÂqT잎Øþdãò°€úêø»'Ãôü½C‡˜w¸¡ÛÑpþ»ù¾¯ìÿ›'§'”Ÿƒ ž“$¥ø~%#CLªÔ\~/¸å÷>±ü+´˜¹Œ%¯>^ÜúÏÔ‡¿áBÁd) ð€Ä È( ü'Œ‹û"GjOýƒêéøˆÚ[ ¼þyñEŽØ#C+ú6 à9džÏÇoøƒ]“¥EA‰Šð>A}}[ kàóõê¨n‡4˜ža_ØB&RζKï ù±øœþÜÉÑØ$7¤n¾ÿ¤>_í¿ijý›[kôV¯—/8?”U4ò:}ª7¡†£#-W°81ï$+¦ HANºjÇ¥ƒ?Ú'ZÈw1CCuu +ñCêþù׶%É!>g¤²J$‡>B#-ìT!¥—}»ÏAàl}ŸŸh“Xlú’¤íûfoëý!¼:‡%çN½!:ž<Zœ<Cë5SE‚<¿Iª“JJ’$·ò©‰ùcáâñ;4ŽÇÞv‡îIáìölgäí‰R,©¸×G_ç˜ý˜P&žwÍÀ~cs‹H ·‘¥Ïäã9g”?yØ…{üGܤO|ÂÐ$“7@ôüÏ>'.9×=Š©„iœÓPÅùƒŸÄÔ% DV|´/ÆQër©M~’~ªšfxjÀÃ1úÊåˆzœ¬QjŠ0yö`ÂþÓ•¯ˆy¯Øß^ľ§l›°a0¥±•#`údx¨¬ä½tŸ"ÃtêÐÎQ:§Íú>¯ÔGô ÑB'°üÉÞc6†Ô30~°jÑ”åFÿtTUAûPÓð~ðôôH%<^A.bJІüÿoÞ’þ¤2 ´$>ß]OQìùì:Ãç£N‡O9…³ð+¢ý®‘þrþ|#? ¡å25©è–|¹ª/)5ÕÅ.×+Op“ôZ¶.8\ýH þ^¿bü@9ý½Bèú~ž|!x› }vr»O®–´&IÎÞ_PoÈî&³¨‹ ‘9Ìyg'Úf<Úô=‘’ýÑ”&^€ÉïCÏÝ°Úz@öylw°4%óm&kõž#C#`½f?¤ó4C²öOkë;65w\•€É°-zýˆŽ‹‹G$Cöç*Ú{-!­‡SSp>xXQ•TÔC#- ué.Nå‡íUžÎÆ¥B°U4B‘ywºŒ‡O¾ˆˆˆ"šª#³ž¬Ç=ËiŠ‘’©f:.l6ž b+>m“ÔÄ÷=:@Žÿ‡Í›·†ÁúݘÈy̾­Ÿ¿ë-÷Ø(V¯³—˜h%°N¤+3ð;ˆö¯€3ä0SX¢£x0‚7‰ÖçÓ=$j¹T¹`¢Ø–—|#OGRžù}à;$§?AÚ„=æ¡ú¿!¼òžaæ$/ôúü<"óì!ˆTøˆÎÓ§Uõ¦>îô­ç„ê&àa´°Å&p_£ùßÖ}>»ù™=¯Û–z÷}äÍ붡P€UèGyupx zt°#-°-A³‡X‹ëÞÅ¡¨Ñ¨ÓÇ N'°*J-A#Oò`,ýAÿÿk#-îÂ4DÄäŸäœnj.UX[ý­Y¦›Ý¢ Ë©¡«j Î#C¨V#C¦ós»R¤-’ðDãr ‡{ŸÛ4‡ÍÝú>%>ÓÌWçňýf12{O¾Í×Á\nx™óx tM5ÕÁü#`/‚™ÏD•{0lœ£’ o'ïØò¯ùÏí^€Ãhåí$ëùcOʆ/ŸÉÜ`èc¯vvþ/Êv÷ŸlYÜ!ÔF}X!˜~‹F¨‹BBŠ IÚþøv@÷ÃÄý`xI§ìÞI8`˜éSÀĹóz˜ çÜÅcÄ>,Ó?õ¸ô®<Êåx½Å#-Ö#1WÝÖô¯‰†éˆü¤ðIdˆˆ #-•As§<N|óO^ïÃÃ6Ê?-IÁgÜ!q(B ;If‡ÿ3¥ð#_Á%ƒÚ4Jê#-;!¢˜ÜD˜½+ÑNÐ#þ#`pö½Ðþ5ÙÚÛÿõ{X3LIÉUþòµÚ·WÒ\'äÎÙ?ÇfÊ~|ïç˜{FE"Ác¥Iñùýt}×æ>%‰KÝ(d€So&LÈ7ô=°Ç¤“8rê-ÿ&T¶pÓœIWém#`‚ªrPõEÉ8¦jAHu ™y±´©­Cy+×÷&Ðçb4Å`ÂÝ>™`âó}Íq ÈsË`c›ÒÂIh¾KÞŽ²ñŽFŠ¢K"Q‘ºÆ-!èë‹„;¤{Òç­X€¬ÂƒÊJÇ}?«™¢=Wš#-èðûCc‡^8!é„€Kyeæ„náÝûÉ$ž#`¤öb0·U°a:¤#-æõh&=aQK4>nYaöâŒhq©Z‹H\„ð‚ë@Jl!-Áld£ÝÔèæ)ݯUO‡ë!ýàvœâñ9¦ƒöõÃ¥ÔÁ“„é1ø«ƒ½pÄFzëp€yúG­|®¿?R~mO¬ÑWRWä?¦‰Ç?_M‰ —ØYöÄšP¦`ÎÝ¥Aý vEÖÊ2 #`~u4Èßbøè›ÂŽÖû˜õÐ?L… /VG¤”<”*‚•R€ ªFR$–h-1€ ,`>™¿#3óä¨c”oâzO·æû1êJB²þÞ–W³æäcoÅñ¢lCÓÜ€°ÌíÔ;tû#CNÁ×°~)õœ¤"À#Cñ ˾93¡'Ý"Ìd»»¿ªšÒø2vôüÏO5qÐ;]DŸ¸8‡P5G‰Ã´êÑüsçØÎÎ,»õ#ª£ò~ãÔô„Öe*„ùÇ($¹žÔ#CÆl(#URÄ‚f!*´¶°ÙÁã…3!¬yë,ëP”$ã{3!åèWauâ °<Îíñ#`ƒæ°ðÞ³Åíï#CÙ\3IyG#-ˆ“Ô£àiÃÔfÓŒÑþ\#-Åð^ל:ëÑöG^âgp›KÚÁûìÃðͧ±{ý©ÅÞAfÀGW{>#-oÓ¡Ôë¨T{ ìWs¢øw£zmáF %²ãÜ®ÓÞû$@‡6°#`A@Š˜”,á¤;äØ”W»èX‘QUîvjâóá!EŸø~ðG:8U Üð¡Ce‰üÃ#`@û m8FØÛOÓg?ÜC¶„ÿ\½ôˆõÞÏçü½ß•þ}Ù6ƒü”rpã¡ÉÛÌËÇÑäÖâH/)ù‘pS´WVÔMh€ð«úÌ îR²Žiš?°a3Õ³û‘)ø‹B­-9˜Þ™.–¡ÈÛ0a$%¶ “GÛ1`}zpð›²KD`zädÀ!z{2âî¡€ÅÌ8 ‚EJ(4ØOÕ?aÿ_A÷ý±âOÏ#J÷döoˆT§M³9Œ<Ž%%S®{_/ìé#`z¦‚ 4U:T„Ülìx€„¡AÃ4 äÕ= TM‘ö`ÇhPý6 <°E“Ç:"ñ{ÿãš=ÑÓÚ[þ£ˆü8Ï&3½«ù_dÓSŒÝäu…Ø@N@`J…!ƒÍ;Ó#`É–RPbË…Ô*Ã3Uaçe$†ŠÉH®Îç#¿œÙ¶Ð·üÖ`~ÔÁ4v#C/ÊÿY…6¿åKr#Cúþoã¸ùÑÝÊœKæ†u)þÇ)ÈêûÓ˜‰pòKÞç‡mcI'/ì†2H–¡D(¸ký ü8?Ž¢]ôj_ô`6D˜®áyñ| áW½š§xs ‘’çr»ãQ®© ì0ÛlDUÌç>>ÓåœÐùgR—`¨ÅTgN6É‚²À¥)•±ª­­é·VoR}xºôú¾QW›ú~æ’¾òC®l1!I¾ÏFÚ¡Çöø®%$è•lulƒ—-vä°elû2²å)™P4˜ÚXÖØRe4dµ#-(G¬¡8œ=B<þÔƒûD4’‹ñŒÔ©>SmqÝãˆv>óÇö…z}iîÓkü‡ŠAxö'E•tU4B@¡M…øp#C¢Ñ‹üI `SÞz‡oB ‹ ¡ä@Þøpóÿp,>>­ÉÙ?JjNï/a×ÐBUç>ȸËÞ¬iâŸÈù  ª‡ÑÇøt]U!Ù#-nOCÍæüØzþ"IÁìüŸ-:šžäaä½ïP3û¸ô/2c$‘iT‚ú(…³j¸ƒ³#`“#-aÔö»Â¾x•.?,Ó M?dáÔêô")ÌNÍ#-<Ãn÷Ljjš} Á–Ât1yì€ëŸ?ÀÁ>Á×ýë}S0ϤüJý#CT±57ŒMi«ïÿH(ûþ¯iYl£u°…yTÔ½ÜãO KÈ4”í?¿üçâk®´ÓGµ1Æ+0?=ú=>~¨> j¢Ê©#-ÉÉõì‰@¥[½7á¹U%¸ €]&Õ%Ý­ýVóÖùé2Kž8¦Þöÿ+¼Æ—§tµÃ‘ÚI“ò¦þ£ý^N ßqsAÆ©rM§›M±U5² µ‚±EÁc÷/¡”<Ðò&v#-1@4ˆ‘žmØ7H#CF+ÅïåÒðpä‡Gµ¤m&\q¦q퉌c#CkÐK*Ä•ÖbC#ªC6›{D™Ê/‹ªŠ}·|r!õ<»±#CZÚ¬BC•J‰:“tsU¼éÄHxÚq#-Š3cÎ ‚! °dƒ‰'Fb|üÏM‡«ŸR·6P•öÑÒA-qðYê¹ôêû½Ÿój ¿42Uó;=»jy{ô¼öcïš• ý=aJ4v#C!܇ÏÕVtÚokãôÏHXàå0<<6^||ã¿(íU2#- z66ìRÂ7O›ÒA?¦€²ÿE£!÷½€âmýù:í9À.ôƒc¨ïuÛ‹‚†sêø ÁPþŽZë$^Šzn’îO#;ÝWE¢ÄŽ‚©ã#-h3MaFÆ9¢>ï^©ãàxxñ×_½‘_HüBfrÁ`@y-ÁüÏ@øÛtV°Î;¾`6#Cš‡IÓJVÜ"2¯ÕÁé7€"C‰`!؇õGÔjŸ§¡—[Ù}:'ÈP?³íyuGW—,s3ÛÜ´—ÛÞIô/Ë#CŒ9ô,½ãP=¡]>Öè(²(Ë—@ñ©}Úº¼Žgn÷g Dȹ¹­øcž‡ãñŒ?J~Ã<¿lÒKË»ŽŸ±xAOöl»I¨üì#`Øp錣ýË*€ÅƒP#-ГR¤  6¡Äê¿?œÜíÌůn,µ+þÁÐJök»KWÓ•‡öäQŒÎºm¾8V*3#_Å&¤~|Æ”™ÁȶþþŸ7`©‚P3°²ŸÝÛ$b,ò!Æ”ÎOQ¯el_ž*z~gØžÅ~†®TôÔ¨|ÿ]œ¶°X&5¢ÀýšçáõÑþyò19$„>ÝèÎ?ú¹q ªX¹eä!é+ü™ÓM/B‘o¡@á]sˆÐi-Ù^ †lͳk9þùYõ[wÎ3Åžüå§þ®c‘mÖÑVs8 · >ç…±·ù5QÄq~U¶ÝàVø€Ã£®¥¶o ÙµiÜëN“cNy!¦õ{T¸‚ýB½´.í/oçà¤Å䈊h%H~„ÂLr#`ÜC‰¨nK!`GzÕB¼MTýSôûkªv„‡¥¶Ïú„ìQƒ× HqO#`‘H¦™&G¨øi§Ÿûßfú#-‡aé‡Á›#-Lj*úgÚ±‡šp_Rzß_Ö.îÑÄ#€ì9  ºÂ¾å!Eëó´Åï<^†0ßÔÍ¥BŸßö¬:L“éœpãÐÆí êåWÃÕ¤‘ Âf’G©hܘ@Žrh?¾Id7%ú‡2òrÖCש˜C¤Hu'ž0À6ú`qŠmU4!#O€r\È<#-rÆÒá‰2‹ °ž[qñü}?‡ÊÏÃñ~^³P™Ñ”/Ãà~4 ÕQTLÿsŸH*•ä”£À磈qŠC–$kINÀ£»]! [üIò^¡2ݸ_ºKëA€Ò!$Ûƒ’bÙe£ÖŠPÞ‚3„c0FE>æ˜ëNÿ°;ð}¹O ;LïC·ê•‡YÅ%*¢,i»® ¸3ÌräDÔDœÚÌÑxtï;Ë69i#šÛFŠÕë®™ì©Ó+MÉÆBÀ„¥­#dqÖ‹µCAS.´U#C#CÛrï#CHDýøÇ;!À¿‚õá³g;Eié{Òž¾ÿ]8ýèWõ=¬Øó:÷ Ä’OЈ:¢ë#-íñæ†; ôû“ÎfÒT\±íˆj#`"ÛEÁIfM#7‡“¦03åàÇ6©œ°'ìü#-¦O®Ô¿n@?S³÷DD&¾j>àXM€ÕE?þ#-5Y'çãß…’÷ÚöËÌükü!ØŸ ñFÅNØ÷õAF6w€˜Ü1׌Q1Ê*¤0~‘#-ä¢HµyXd×t¯}–¬nPT“µ_·¼S¢JLŠwPÔèGÌ…Né, 5„êFP4éýXò?÷t<ÂUúSðýÓXqîªEqÇdcÿ|€;É ú·R©¤‘øŒ#C0Ü€î¿zUC†K©ÑË x«×ôãÀ˸ò½“Ù#Ù€òJ¾¤€,ÜZ%FLÁÏÜz?çöæÍ«¡ì$6ðð©'õ»U¨ù#ž~Ïn #CÿI:¡~{`¿·ºžBfd6u1€^!ÄÆ#C™þè~Û¯»>ó 0+²ñàg$ÈË$9ó:½T uƒ±BAˆù™fƒ$"ãù˵)ϙʺáå¨ õ¬åe¢ #CÎ?/®+ì 1'/”}béw}€uTj¾X†©HÇ#`µügÖz½çŸÝ ¿ç”?·Ð§SÛã¿Å|[`æ¥(:J$’0ÈÍÛñ¿U&Ÿb¨Ÿ„‚PïNÙéDD0¢_7ñÊÁ‹.?ísÜM£$6¾ŽpR!V‹PÍ¥SN5~Ö_wΡ¦<‘ΖÃøLëþC´†d_­aÉFŸ¤à=nMá-D9ïWùô×æƒ8ÀZš!S¯æÆ~™”YêªÄé¯p¡êüX^h½àÂ*1›Ø~§ªÙ’H(á…¡<mT7×kø1N=Ø¢qQˆ±@0{O°<_ËÎLv‹ŽIÙþq©¯dXU‡Iá'ÈùR¿Êkü½Áèö@ò=³ØLÃöaÇ0÷FÃû@#ú*8·ã‰`>AúŠOª/õ¹QžŸDB€âåÉ|ñþ‡w”#-~©]¡MP¡BòTÓ@RyRp‘NTAõ=’þ·Lÿ*ÕºcÖU:V 0 È:M+XÌY[dŠ‘P×üm_•ÿr3E ä!N¿øF°×ûÊ<äÄX#-ç&:ñ)Xcÿ›±­&l¨Ó@»¤@_¿=ŒŸøxŸ÷ÿêJ(zªTJv @ç’!õÅPl! }§Šµ¥9†ÎO5‹á©î#Cœ¨ÓÏY‚åé®ì+Ì˦ìGö1*ÄpoFàhX„Âßò3-;ŒEþR£ýÚþÙðü=_êïjÛô}@"ñ íF¹Óq¯v#CýÎÄø|Yþdþ}€Yºôq!~”Î ÿ?œ’#ýURí/ÚJ¿äñcX×»ù‡ÌЬOc\Ò«¸Ù?¢Jˆ#-ãNû•¨¯eŸðf^l]û‡ôTûÂâu@}=θ¯Ï0ƱG >¢HBiʨ b ‰küõÉšWm»ó!–k‘µ³S“ oʉËÎì,äºtUòp#-¤R‹zòšñU–¸?_"¼ú^£*ïf†s\Ü£YºõÌ3™Ù¹ˆqLÜ i±ù#-ÕÁþGí E†Õ cl¶¶­—G`)J°¢9° d0P#`jpíƒÂP.‘ß7º\çºæ÷3öÐidÎÂ@;þ±÷— ° ÊI'ûwSS5F-3ñê¿»ôm§V5üÛ6>ÿ;ØÍ¥E}ž>°¤PÚzŽ·§àÉéý\X¢¶?ìI0"Pßr7­ݤóçä‹#C7ærq+ ”D•Aîý#`#-œ“ˆ1¹fhÎé5¢é–¦õ8ôÃ3Å}KFÚýÇ+B „Ÿ=/ëð#CñKÊN0©aeî#-÷2Â$žÂ›É®I»ÃCiª£9à”ØϦ®S@þD3ÍjWÖÑ–!ÚŽiˆ%%‹;ÔhPH3D©ó–)uð<‹¸~¨Ý#¹Ûp:¹Þ6Ú¤4pÀìÒ_<2£Ñ×ÁþWÄí+Èz9N ùïcH†ñEÑøÁÁb;½Æ¤#`Í`à  ï@‹»º8¹üxª0¢ŒJòáåŠ-ÏB#`Dд¢V0D#->!á…À/¸0ä™>’–¾9;©Þ³±„¨&ôËÿÏþÆÏBá*£Žß*¢#-ä(è#`Ñ@>`\@Cù<í·“r2•ßו‚…ùó~Ρ½‘ˆô¢€rÚ#áýiÝoãÅéÌ9ÑçøÕ{‡k1 ‘GbcHø¦h‹¢ÒäLJt-o/ ý#UÉ-Ъí:ù²»µ˜›hö  QF¿‘ì ©v¦×àv¿#C}Æ•RÜJì ëOÕ¢,‚:Pcˆ®ÐúBAÛâlfSŠØ:œ(‘Èï>“ûÚãÄ+#¤ºÿõà àpQàu‡Ò#¡·öDDd l›‡g5›T”>=?§^64nÙluóßµâÀËc#CnTT@äo›1µ6Ø(:[±øYq|)ã©•ž³è.Aü]ôüqeù\Ç„; §¬|UÉÌ k„¬Œ68WÀñGVUîþù%lðù5Ðæe¾É?_íÇÜSñáôr>çFp+ÊÏ6Dú­=‚÷·Òt˜tÔlŒ9t#`*)Cc´h¨öJ ¶–öGéutF#-¢­°Ÿêºî"q?jtN!â\T:À¢à©CX3Ý}üÝœ`;!—èxâû=̶/)yz¨NZI‚<–ÕÏ‘þ+‚ÚŠƒ3!>s#-ÃHGS óß ¨Šü±–—#Cc͵ÕBuBù²\•(ò!½a˜Ð½Û˱FƒF”dª¨ÏÄÃ{š%£;¨±³ªqÏ8ív*žŸàŠfH=íÃç'x>G/Êñ]¼KC+Ë8Œ8UKE÷ˆóôÛj„ÕÜtí/š2­¾H6J1$Ä’Ä/¬bZ(™—®Š}¨êû³®—ù¾Qé›ûµ«/Q=fäÍ9<žO >}ç".&Í •¹Þ9–WÒ>1ÖÙ»I¤#¾8Eö|âôM󉹚âÕšÇ 95ðƒBIß¿kk¯fJTU‰ÞךWlØM+¯nëýþÕÈʸÞjA²¥Y@åÑÀþ Á#-Ž£ÄíyΚËÕ˜)X”#`A#È¥Â,œ´ã©vÖüêÕªZ%c·_h¶áý{`Ó9n| ˜úç§,Þ`a WÖÃÚÛ#-ùÿŽ·L¯Ht+'ÓýíoÉâÆ·ˆ\øíŒg“Ê`©àÉ4¾uŒwñõùîv/…îV#tW·Æ¸èÌKË·¢"Ý©¢ c‘ÝùŒ¶¢ÊR‘"÷ØL*ÄÔÁ)÷π߮âD“%óCŸ¡Þ¾´íà¦bOË«ËŽ 9í[gÉÏUhØxç³q[*¨÷Ïøìx` LFÛxOàÞï¯ñ#òÂ.¼¹ÒR{‘Ä\6õx·ûá¿‘H‡]E7Ygüîõù”ø+óævð…å7gÞº¦Æ:¬J÷Ëõ¬¶ræ]é§Þþõ%¬ÿ—ȱô··ê£òôù{Ç•?uA·W •ñê·“Âð¡$ ³½8VÄ8L3¤cà–½qK•.‹]“Æ5t5;#`M!;ê:†¿‹íØåðë—öáÇNÃeLrÞfìß/aï†1Mª¯o æÉÝã[ëÚç¦jT l9«€¨Pf¨2…¼¢Võpÿ¹²--ÑÄyïÚøD±ŠIì(eágÜ3iœãÚ–™{µ#C]ßÌø˜ûy{¿•¿ ¬{ÕT€H<Þ9üÎgËÑ!2G*¥¡Ö÷r£ZÁTQS=š„¼Ò:f%9žÿ‘é@„EBC×ë½Sê<Ï€OÕƒK;(°g¨÷ˆ#` r¼,²¢„ÑWÿ‰F] o>¯fÿìþÕ°•Èx;^×ÿzÞŽÁ„"‚ÿ×ÁwýhTOÛR B„1ýŸ‹ÑøyòÓ!JŽPt #-û˜*Xe@#-ú@÷Hõ¨äSüا@àbQ OžŸf×'õûÀí³ó'Z#‚ ûJ¡Í?:1NŒ|·|ñ•ÛQþð¿P°Ò?Ô` s@m³qÈ™$8;ÈèäÀlx?Ó'oÀÝÎFܹÐXf#`gþóYØЄ˜d‚1ÁÅ‚H’'/ô½SüZ/`÷Òå]™ Ï#fYÊq+µìïï¹àñ0† Á‘ØؽwN³ºI?ì²Ç Öyð¸¸}L!Òx­sÜvüX©üc5’j—CÎzHHÇ=/Ñ÷#aè0‹„yBÿiê°>Òôöð½ü#Cè#-å´öŒ½}總LW»­_j©Ñ÷ý-U#C¶­gù½w½¸~)@û4[2ý³sÒtÿI°ì3^wjÝ]ÒÕ WN¡ÙÁ‡£7m™ïëâZ[Z7A8#-÷ÓxƒÝœÓ„1Ahä¿vtz½C8þ‡Ü~?ê±þP£`£BýøAçðÕ1a#Œ#C9c…÷ÐiyôOò£·Yî*#`¯XzÃ!þ³Dž­âg>!/5&ªßb„‡T’èø‘ÙÝ>ˆïk#CnÂvÙ¬ð00YøöÉ&KÛ>v ]Øir¾Ÿ3°ŒUQZC,x×—=Îòe€BÑp– p©‚“L0d¹&š#CXiÃ4U€é`bU@ØÃc4Ãa¸#`¹>ÓgR pÎøâæ1¥ŠŸ d¹“0[èÛî#¢ª¢·ÇH›WY”a¹‡=Òm²YÁöõœÇQ$žŽ‡8°ý<½#Cˆ„^<è±ú¾ÊÉ–Œ˜Å^”VÈxv¯`¥#-Å.ÊY#C踿Î|IÌ9€ö*³e ìs^ÁÁœC*…!øþŸD~¿O…Ñü”Ø8¯ãɃ§âŤ¯*ÿ7ë$l7slÛhè•þ9ʹ¶­o~#-Ä< ßÈT1 ì#`ç¨~’àœ;`Éé÷|îÅjçNˆšwyÁx‰±bJ`ƒû_Ô :AþèÏH ´rNQ#-¿ã¹,DE,?³ýëÈD,&¦†ŸóÏ£-q„lDRA˜S‡ó¦‘hŒ>¿?¿ûÑø ÑgØ|Q‡½TU .í»\Ïé\’gœç#€Ü}j¨Šª«z~áæIdD‰c€pÉ£«s›X4#`Ù»Žø¡#`#C¿ØÐÖÞà³þ/¬î»¸ÙNeTEXˆ;èî§ïš÷˜ðÀ3ß*Ä#C¤ 6,ÜŸœNA™¡Á°m±óÃ$4+&4!ŸçÍ–##CAÄÜyY#-¥B! ðO¶­Šf@ü%I^“ Òü#`ÄÔéT#`uÿDÇþŸÃá|mðvI#`t`j'ÄII$~"Xx«äk$£‘¸ ¡iù*#CžxC^¡pÉÕà#C̱QIá×Ñåxœƒ#AYÍèŒîçßš&ǘnk4lEÕ¬ÂC«82Ð.‹DbYüÅWL›Ë²¡Ä‰ù† BI@ƒ®œô…cí>f2Ú9Àvà#`AÏ˳`šØÍ#CoR˜äŠ7ÀÜâÛmÀ\ûð'îC[à.&‚¯h0ý¥‡–«Aé2»Ä±ç‚zy©#Ñ7qyåº$ž¼ÇOã 1è6m@4ý÷gÌ…WŒ Ò¾#v„œÍnE E7âC†;–ÃplÇc á­½hqÂO`Ø)Ó6¾P¾ÇŽÌ˜ÐØÝ÷¹™=»é«90Õ…bÑÉ‘f•*"šøÃÆ\³¦ìÞ¤ÃìtÂ~™ƒÐãË0{LËËÔ¨y÷‰Œ£­Ûeô³3ÒÛšÄW¦Ù.ë|¿-ô½9ål³º ÃÉ­ä§gÒñÓµ·¶L0o F¶Ü- ÔÂÉ!¶tVa7JK@ºtI&#CÉÖkü±õaŠ,6,«Uí~ÎÝx—r½æ‘DÛ¾µÃWpwÛàöã!ÕûC°.K½†3Òω‘¦ÈÍ4å«Lg›Í–ÀÝÊ#&á³YA#¦ùÌÞ̓ˆ›ï3)ïd-f2Î’m?þ³ BîºúWìû8 ·«½ñ4 Ö™ºk—— ÷øì*™›Â0ޮǯ•6­Ýëâ«Z§Ó­¸Ç/xd¡Ý‡fCù£–†>tñ 3w–CõéQ4ô|78ÎnU°`Ë°ÁµP©Ýd#`3#ƒ\/½J‹ê#-×T&ïQ{!#CfO+íBÙÝ÷’Šø\¤M"è!fXe;²#¦•®ü*˜)Bƒ!#Cƒª#C@°`r†ÑzôžI:±?‹ã*B'cÉáǯ9IFDµï LåBxܯ…¢“eÅ”œm×QŠjÛyF¶D{+N#Ìê(ºp6ýâÙbc´ÞÇ7 #`þmŠ&°âdõù§¢€âáÐâøHèªzŽ`t‹mB§¼CsBdxþNÙ—.B=#C#`M±60`uè(Œá±†Rçªa9gvòÕ±ËÔËœ ùcVèHwfìÞžl6P#`–²%»M&¾/Fœê95æŒpó–ôqû#5@ Réç7›$çŽq†C_¡ÑàéV·u‹R˹Ž#-Ñgˆqò}ýã$[¾ÞÌKt::b'È\”)Z)*’ Ó$ë©Évwhy'Vj”™*к¨¬@¡“¸à1)sd;öeà^nå1{b k4Ò$a Ôǖ§W©²r;5h³3rÀÀ‘¦&Š‚”Š‰Š­Xw*†Å8›°çÀ®Û³oi`å¸sp@&Ø+Óßš™3̨¶Ü–æe—1åËh±fe¹†e &'¦9t~©o”¸H™ŒL1„vƒ!ƒ‡×ÓÝ‚NõY¤¤H?&Ô,—•ÆfîÕ«çzæ²ÝÖ6œÉÁSPô ˜Œú¯K~̸!ðy½õ¦¡EÔï9k#`Ò²×Pnî;^MðÆv¢§j¬:Q}¸Î˜”kUEyO>îx¡F“ ºîÚ³jâ-JçÎeá‹A*;Ô}I~SÍÃdo§ÅPnó ÎˆU–©`T#Cꈹ~çÐìx´•hH„9ð|g˜fq6ÃõSú®RX3^š=i·ÍÜj=°s½øÞ«]ø§z(" ¿ w˜(\M6#C¶,"id…Çmª¤1zq©‡C§óÒ‚õÙ#`äÄÕœ9Z‘ªÇ—!tðFôk1¼+ÚG*µÃºz<-«5Íe§73!rÔ¨ ¶àõ,.§˜öŽ©¢”AknåÒ çþTÄ{vz%yÕ§©#³ÙæôE{uvÒ#-tÉ%ûû}lùlçïØ»&#`,èBkÝ9$˜<‚ç&#-÷•I–,æT,LœôÁy.èbcòêá¿·{3žp,·ßJ™ê[™ÑGÞ9ÏlL¡ˆé#-E•”–{ÖCzøtwn¸UÒvc„-«¸L-Ø’>MY‰÷K¾Ät;‰Tæ뻆ñˆ†¨-¡Ó-¥º¾üI€”¶Ø;ºØ¶B!Ü7‡‡‹ç½m*úHhä&Ó#C{¨Š´½‹l¬à ì#`ÁÔŒ¾‰4 b 9zí0YlsóÈ°=C€ÓGθiüJ™¬Ü°Bi8`(²uœˆ¥ŽL™œêrYó$ùF·ÏŶr.  Š€FpB΄²»véÕÝ‚ ’#CNÝy¾MšP„:cïþÓ‡>¾ÏV#`ÛQ¤'MfpxÚŸDCñéx·‚yìzAZÂõ~FLÆììÍ37F=£51NôÃãàèx÷ãLõ#`1†ÎƒR{ÄnÉ’2z$âKÔ¢²ô#C2¦òPk§¶í&C2¥Ýü£n7#pDzZ#C{ÙËì§~Z0mHëQä}üõ4@ÝcÞ2Å÷ÿmŒ[Ñú‡ÙäÃ>ñX šŒ.c̉ÞísÜøq#-ëK¥—é+È#CéûÍˬdÎ.®ìZdî/\=ÍêÐ{BOÛ–BòÕ䛧O ¨ûO‘»·Ê§ßµáƒEënj9p¡Ÿ:D Ðêƒ ý¢’‰¤jw°ôÞ^¬5’ƒ#C‰Ë7ÙŒ‹$}̘Ѥ5­l{†¢œ7ÄrGÌXÁБqà¨iY¦ˆ41¢ RdoC΋k8reD„8ôuFŠ†òB'Õk jîNº­ÚЈËíióu4Œ!‰¡¿qÓ¿‡ƒN»äà³Í¡7+‘A©ÜI!Á~0nWPŒÔÁ C#-‰Ô9=çËT+Ày›ÉÅn«à¨ŸìÏó× åíîzÇ àþI&ó8̓oSÀÀ{{áÊó÷ÐÉÁ“‚Ë"©ÎOIç*Ix2’#<ÀècÙ»…7fÆx#-`qÓÆrˆ8ª0õø÷ëU÷Ñ,؉©õGJañº2ç:Œn_£Hh­Vïºt2zô¢†Œ•Ÿ.¨ÊMåœL¼`ïwBKò7ÆõѶơњâmµ³Tl’l4¤Ï–×»b0—y®}ú7œc.4ÌB©ô°×’²pb‚IŒá6êø@«ÁÄøê07ˆV\®ÜævE)Ä¥—–?´l‡c=l?D;ÃÇÀy7ߟÊsyKÁïNwr”éöü2D¿f4µˆÉìóÛ˜ì´dJ쟴—ô_>ó*/ÖɇB¼âP>K BF¯åŒsxDÄ–’Wlk×éo¤±5çnõ1½ž^Oþ›o‹¼ÎR=`ìÍ}^“IƒüÒ Q=¿w«ý7Á¿ñsùäï\F®ÃwH—jȯR—ÿ«•´Ã²ž2òúW-Fï7 |a¤ž6m—3yä¾Ù-¡õé8khÄNQãa¿“T:ý·"LkÓ²»V`ׯ¡á‰>,Ÿ­Š°‚Q?¡ ¤ ñá%#C#BÄÐ#CúATÌ’€ÑR0¼»ž~.ק¶R‡YûÆ`ÒÍòÝ£{ü1wu‹Îšªô·¤×#`̉Øìgሡ‹¢`DbUÒX©6HdʧÖó˜#`÷ië»ÒÝBõ’eCólòM´ÁcLÚy\¸à‹Â„¯Ñ'ë!3ߺvëª){$¶æÉ %wþEìí£Xü`î6ddMàz¥üªÿÏáˆL8æYë.ü<ó16ŸÍýÿw_Lÿ²Êу;oðê«b OPêô9#`f#È’ =ê—ÂÏÝ–¡öµ³ð‘ä‚·¯†#CXƒ.Ã4z ¶1ÆäKîÿPMȃÂÂWËÜxt/Ö§ vöô÷p;U=í½†–BØ…À}½´-ôë7k¿¿ºÑ#$ÈžXîü<7¾çƒëõ}Ù«fds12ÿƒçÄ×Tq-ŠÏÄt0VH@ ó TÆ8ðcQëðÆÛj“Ø›câ¤Íë…Œ)!r-°ÌÃG‡ow@ì6Ý6Ä!Âe^I1Hð‹˜Ü°ÑÈ7 5¤Ì”ôIC€ÄúÔÑrVZÓÌØþkÇû=±Ê›­¨­²hôÇ‹üÒÅärA9ÔtÐ#-#CÃð#C% ÈðBh"Õ#-=½Ä«“Þó/¤Yó3ƒ «øÿ«G'‚d? ‘—+¸yA.0cx8]@åõ#-½÷âÈIñ8‘B#)ï ¦¡TE«UP~ ¿fà÷Ìœ–5dìpñø÷3²X¢sLé»[U°W*ñôÔ÷úèÈÁÆk’ Ži¢e·Nd†¢‰ ±ÓÇ»ÏDOný:u×®‡Îª÷ÈÑ$ª=»qê:2÷r@=ðªÐˆB°BPº£ÛèÏ_‘Åó `!D)A5…#-4 A‡ÊQ M J“ ¬J¿G»ì}y¥ÓŠ‚šýø*ä£üÐq¢¨¢ #`‰ª`‰Š"$5Q3¤hˆ‚$²†‘¤„PÌ Ð#-åŠQx@ð‘• WÈÀݱ?æ?P‹ŽÊ¯³¸<õÂgµ¡‡Õ˜OŸ,Õ€ÖÞÏ œTF7ƒ D*û:wHÅuF”ÂnǦ(ßxAWcɈ<ñëá#C¦ÓM kµ~©l<‹GÌ „#qØÁK¶~I¹ž'iù)õÍão5L6M2^©¦`‚ šBd á0C·#C¾Ð¹È(„ˆDN‡À]N"yniFÐd5@´?Å ø—:EÅ HòŸÝ椥)#`Zy²4ƒI0P4‚iLÑ’Hšb€$t~Ü#-šä%(fRŽÀp!JQ¢‘ñ0ç…€¯I@Õ&ÿ6!ì0 bq„í”N¼µGNo’j*J¨†¥;´C}±AÀõöw‚'#`BÑÓŠ;”ûðÑÄxî *žî£Àü|W¦ý€”sõ‘EUÝþSÞHy™¶& ¦¿ÄýZ©÷x-ˆ|þ]ò&y$EÙ­õp[8Û{¼£49 ßÎód#`mO>ÛÛ4,òTI¬§É!ÔïN8*±½È~vˆÇ8æÌèîNm¤Îclºì1±–àA‹m.‘. "CUíâÇÁœf Àr#Cë²Á.Yµ°AÁ­¦D4$º 9·N5¬Ë˜[93hS5ºc"ØæqŒG&÷ HÆZI­R èhEhp#Cˆç{ƆC ·Zœa£ØÒ³V#CL†=áӢNJ=aÓì¼û×Q}‚ý°3"D‚D”¢ƒSD+ #-½€w*b#-J' #-|µúö€uðZïNi„£g4ÄðOðG–«VeÈÑ „;àd%ɧ¬‚k«‹M*ꬢgKÈÀ¼å.Jš6ëaªã*ÃÞŒX—ß=rÔ¸¡a |÷Ý˺{sƒ#`q·.¡ªD@j4´‡§›'A0Ù†÷•Bðñ¡ê%Œ` Ù%æƪ »Ï9u7#TÁ¹›™CöŽ|÷æšHd!}Þ8ÏŽr#C¡9Îfd‚RS «Ú+Êô/g_Ž÷–­&%PØ3åÙߟ -ˆFˆ{zy¢~çÎÈÔOAˆ($"RHª#-ù††–‚ˆ)ÒéMh&B €¨‚…™ª "#-)ªZØÓ%#CÏcûQ]ª5öNíÁC˺€¥ºÉ“S)@#C$¬MUHRM¢Ä(›¨ÀPš/¡]xò´éè>ÊõvúÁóýNµ‘,:q?fàpOןÈýù¥”SsùøÁJ]øÏš9’2ÃH]ø·ýï à/¼Öp7lS$Ï U1GàC@wDB„LÔPþc˜”ª4É›¬¾èƒíZIŒ]:El /£íßæôþ¸¢ˆ‰Ì§ØÀ i*˜¾ÏžsWÑTŒÞ]5ú^KClÓ ÔKÐuüZ8mü{ÚÔÊ©È*X.g˜Ú3·ÄI·®L*y2×ÃÇ2ñŠ,Ä£|^0#`1Þè°VlÌ¿>ºC%ŒDì妈z„©˜”IhœŠ4r~sΙýÆÓª„hŒ ËÙ#?¶²æÑÃ?Ñ#`ªúÈ#-8`½|þü ¼~;1Ú3ùª£AÚdØ«2\ˆ¿‰—ZÛ=ØšNK—®WRûÿ—À=~9ìŒèxX=xçÇ3ú0@;¦$äîb T¸ÂwJ’#`{ꧮüùø7aø÷£Çr|ö[CÚ˜„^=²Y|ïv`"!ÕU ÐwõnW¿¯ÅXh'ø'Ò=¹m<ú•ýŽáù™#C}#`FֳΩåPg€¦cT4—øy®4»´pºbê=±Qœ ¥Ms`ਅ>Ip‘ý‰B ÄÕ;<^&åì_ìºù½çµŽäø¨~#§Ù‡Ì°þ!ÆÐzdÞQR!_áƒM0^(!í<Í¿ƒM_|ºÊÐù&©×ƒÇm û@Æ‚x¼¯”üɈ^ŽîÙ- R¸nØ®ï€{uÑò`aA?Ï+3🎧}äï³ý™˜å¤ÐO3Ò¬^Èo½w§GËܯ²lJØ*ª(Œ8…s& ŸN #ClÇÔó†¢÷Ÿe:yÌ•Aƒ±Ó—»ðfÂ:Æ>ê;²öøgÑ®Ö#`mÀzo†¸“°8«Ôë–÷L²ì`ÐÈûŽ_@ª–#-³Ô>a1Áí_iÇš#C÷Ø;ÇT!¹âSø9炸z“#CRÉW’A¡'á­±æ šJ)JŠÒÜÁ’ŠÖ:O¶élÂùQQÙ4ΘÎQP­}CH$vª¥Æ•i€ÆÏçw«ˆ-ëišš{°÷ž#C¹SQ§ o¬yþgt”eŽ)cÿ4)Ëô§7ˆ©\œ]CEÏ®ƒ†ÑbÑ‚Lñ*Œ Q5Ñ„|âD*Óf-AÙì.@ŠD­`¤ì$ŽE?²ˆ(¢"Aú¡G ¡&b  (JV•Š$RQ=w¾OÄ%PñCœâ{“ÁÁçH‚ìÇRT>¡ù“ãÉp#CŸTÑÖ`æ#`à§1|Ð~ì‘"‡y¸÷Ûªãåý¸GšÁãþ_NÍ3[Ý–Ò ##uýw¬E1£¾²}ÑÔòð#`jŠˆbb¤¥ˆZüÙÃ`¢ /ÃãýE·#C±P\Ôᦀ‚ý+óòINǃɟCßÁxµë­²(nÛ–GÁNì{˜V@®Q©#-oE%H{d&bBJH|é“JùY^=ù/lP-(Àå_.*2:ü~Ê™‚iÊõ,¡Eƒ>¼â<$è𘠽H]‰PÙDEH½FL#`=Qí ÕÖ|À>€^š-'H(\–ƒUeÏ(òGB‘#`•ÓDÔsb¢(ûdäJ€ëœÈ£9 ˆB”²†<¤_уÉé¨uù0ÃSn1øœþ1c*íù©7¨“çU¾²ºUý÷4àÀˆ9ÖÒrú‹à‚AF‘0Ý™TtàÛ/-ô²0áÈ„„z׳Œø¶ ËfÎêŒ#C;WÎQ|>‘;¤8aÜôéÒ˜N—­ŽrX›\¦–ûaÛÊéâÕ2Ò>Ö2š}ÍßoÖÃјגA‰)Á/dö?ÊcëÙÞðãÃh<ª…Ø*jCoÕ‡óëa¶K#-ùœÇ“–¯„="â-+m"Ù4¤A³›íp£åö}9oo,ßѶvÕÈh«z2UA€™#C)A–¡°çˆÓ^”³óèU‡¾ ™ü.7ƳÒ|öóç›ÌÎ'žuYåȻ¥ÜL Š­¿›ã…ÝÆOºîs'„ % ¤f¢Ï¾)†>V[çéÅ÷½®¬äl¢Ö•"YžûbWîÑØ“Ÿ9á­‡)Íã™úãX7@‘ XsË­ˆ#C&äû³rlŒÅ‹q¹Ë"9ïוNóÏʲɲ˜ÇOmžÒÙ7¾Ùè¾—&ùÚCM«‡h ê2ÈêXpKÖ¾7»q®Ù¦_ VEFó/$Ù‹ª1Nî±þ Ñf®ÛkT#^Z?O#CÊâ”üÙTmˆ» ‚A$¦¸îRçø#-tðі¶Î»ÿHÓ«É~ç«[ã¾ú¬¬ÉÀÚÊΉf8…àlbÏsæN¤z¹ž&×ør™~]£BûÙH`î;°íÓTz›ÏÝLìt‚·øŸîçÏÛ­dxûÛKžïä(…Øô؈±·Nqs"|ƒëúœƒëõF;ŸƒÞJÑéŒ 5=«Ñ‘‡F°ä[àŠ 7ãAÙòô-ñÌïÛÿƒpݲFŪ¡ƒ¹œŠ¿hQÅûÉ”Pæ9–•‡3QAPTŽ3ö‡Çû»Mg`‘®º‡=]«ce\züW¨ùÔ[y÷ðè:KÃôõë¯_›8ù]?g´µ¿rÑôf”‘";t;¿/^MᙶËG}áPnKt³A0pnNÓ%c3I]»«I´8Ò@8˜†æ$­R¢2Ê”šÃDZ.MÍ̉7(éȸ\ë5ãŸbâùJˆv”7áæˆ1;\§vrÌPoçÇèðê%vðã6‹Ož l×’R#Bê$:L®ºŽ_¥»™ ‡ùª”.SdÁT¢'ÏWsåô^ó¸=H 6{5㶨}í© ‘©ìÃY7Áôx,IRªNÖ)ãdUó*ƒ¯w¾áª+®b§H`¢ô¢Æwfƨ¹!Ù‚0U‡fj'`'SöÀ8½òD>èä<—±IT)I#`”¥Æ#C:xhl‹ Ìü#oêëÓq}[û’òºuÁ<8::?&]ƒîpôIé’€=Xbö>å9­¶ÕSAL„HÉ;çÇ&bÅzýIõö;Ãëá-3±Ð_³SÂI½}Ý iÚXÎY¾rD«PÑækÍ2¿Z¿«4Åsb‰š4oÏ ˆs#Cw5çJŠf«–ÓëÆzIºdêfLæäuæ1ƒæëüûý^ŽñY¤‚)#`#`isë‡Çª”¿ ±rò“Õ®éׂ!ë&/s£òõQ<#-ÎÔÏK!M=ø£ jÕiy¹·x›™ XæU¶ƒÅmˆ¹ƒrij)qœ$3èÍQéƒ@rÑ]ó—:’ŽÜ#2·•ƒ½ A–"lj 4£Q4ÐC•2Ä)ˆÒÁ[ dÕ! RaK>mr^lgÛ™áÇÌšƒBötòHÑŠqD*Ð-µ!¨±R J",*Q/rðybàh#C%ÂP\bB­á˜tÃßÚÑÆ؃f&뼂©0 2¤ˆÜ8qøŠƒÛåÓ±U‘Û8™-4äî#CëëB”´@Ð!ÐÈHÐLÅB)ÈVW!FÉGW®ÌrêûŸ=aNÕþ!‹D6™EºžÂÎÉ‹ªüßÖ–Øp¹óÌzÏoSÊ+ïó‰'6ÌñóËA8á®á  Æ(dFœÂÌZ‹T4¤J¶Æ1ViUŽµ\šy­IJ5duŽ7œÇ™»Ã\¹rK®œPÉU G`Ä5]Áž†É8ÈÁÈ6;´‘÷`ÀPÊd"¢>ü>Ôôy@žÔBˆã(÷_¸ÜÑ¢Öû^Iö潨R%ìE:À?¶U˜#-v;¡JšñóYéÒ16£+IÇ’æ=»£Qõ#Ù|0ÀÊ6²•0÷Xt[)Ž¬¬•TrÔU^‚þ›Ó“S^¶šiE %kèüW…UåÒŽ¼º²Šâ€û¿PÈ Ðäq¹7߬ôŠ‡’€:»;@žm@ÏU׉¯=À¢’#`0ûØ`ùWWpöBU¾©ØúñëãÓÇ`øó„ù#-ÏXmÉÐEõ¡~-]yn€uõóž2dôÚª1ÀÁ ¡Ð5£µ( ŒAÂ91±;$ÉÍÜ3¢C‘6^&¦K‚)Ä!£$Y—Ž#-¨®HPD`Q¯•œÓ\¸[xËnÕó[I6€îb_{úÎ{‹”DƒböoMÕ–9|³ ßž¤FSJ žÂhOPhJ :c$ß7œÝ«’›d¤$9çQ%F(Á«N!ƒ™#C»ÃGC“Ò9ìö=#›Ö®úTÊ>»êRpŒÅ3(t8h¿bŠ¶Â£w±çuš×]TÆ?ÅL¬>†> ãY©øÛ™Z6É2£Q3Œ>åDÓÉÛK=¢Éñˆ=šÏ‡dR¥Ý ØüØ4!•ƒL?’Až ’;m…+ûn7ßyåÞõ¾oâ}(R©J¤¥‰#-X¤AF"@“Ë6u é1ªˆPp>‡”‘Ãð¿Ò–Ì2µ¢u¸TTE*徎ÕÏÍÌ¿@øyryŒÁ&ŒÔR4™ÃB¨ñ¦šuCÔ´ïÂëuµ™kƒA!ºXíëC E“3÷&Ú…tŠíŠGâ÷úG¤ž©÷ó&òXBb³ØÏ6ƒè.<ßóÇ0Î.Aáçñ4€÷ „j/Yhn›$Ù>UзK¨M,Xˆ ‘dw¢„H¡æÄCë^VWÓ;Ó 7 §:¢í§÷T—/‘ƒCà~Ÿd±Ù¬H)#-9p#C5&ÐvveI4{àÂ?)ãÄGH2BŸBÀ×VwßNm)yw»F#CnÃgl>÷„©"—Ö׫>yÎœ(9pNp`uCùÀÔ·!˜V–yb€´"O¦QF* l~qây"‡×)ɦâ;ªØêâ“[Sb¢lÁ?Q†Œ[}¨!›UVcÚìIÌßKã5I9¥ŸŸ.b<š‰q‡gdB#`RI^ק¡ÙDª8„c(Ù΢ՊÒ'”(ÛM”Ù8×€{¸ lÓ#Zˆ8šÄ#÷~ˆj1ÈÉŽ‰€›†¡„$úì` yn mЩŒCÀY.Fñ${Uh”A²5Œ%tGPÁ‡pàÄŒ˜&/Ç&œ¨Êw2rˆçÐ_·qwhˆÈØi v^eB†iaÿ.ütã2<¶• œ0,œ+æäÒ%Ì  ¸0öùóKøï! 4#-õÖ $ñªèxT²#-¥ƒ‘˜Z©B ‹éÐŒYyšÖnä-AB‚•Ej…ÄÅUP\¡ÀÏcf0öLßìU·Á+Û4ã#`øqGÍC,‘¸ìm¹ët¸Pi‹Sï9Ÿ2zn ‹¿f9á„0䫘D‚*‚ ¶”Š˜%š¯‘ƒJ‡apçxa3¿;ç/«ßµ»('NTN“o£As›T@ï@ü'æÒÂx#-³ô$®ç¨öµ»*÷S¯‘øñ4Ñk`h ÈjY¤Ö¦‘èò>ÿ§‘ðs×ÞI¹RäÆæ‡ïÀ:lÈìs¿ÏÖËëöh ›BRkFIry˜ ‰ù{¬°Û¨:¾:™„ŒB=˜Np(`’¢™Qª¥ï„íƒê%#`:{1#C`n‰íHN²†ŠBÈ'`‚ ByŒ‰5D:œx~Ìý9é5&Ó><xÀ]TI«f’k¿LtñæñÇ×XHáÀûÆ‹©IM3?¤(c>®ô¨c·\1QÐ8#C°gà-#CéÉYk{E8ø½··D(‡úÕ#CC¶µY¯ÖqÁ”ï8<”¦9€~é9#-‡»%$„ ÑE#C ¢¨£Jß)•ˆ‚"Z#-™Xæ{Æ™“sm˜‘ ?Ÿdò3£ESJÓ=ÈÙ0v›‚ëbI" §®ƒ)âŒD²@£"#-æ1"(ˆO#C*ÝÑYBQíõès˜ùZ‚€ªOYrDDP4D!4DÒ)qT”¬#`ÛmHPàoŸ…3†¢85º$#eCîïp§d)s .¤È]EF îG…W·Ñ8¾ýKÜ#CNE9ˆJ/’âgªãŽ!ixBÑŒ˜ ÄÈ뮈f"N¨B°Î<#Õ‚ùÀö$Œãjßë^U$—FqEŒ#ÝRîŠÎCDgRà¦è: "d Ö†Íúô4†ÆàI˜v b"ûLdúõ#- zLÀ9èÍêVv2 ÅPÖ'„(IS;@€vp¤ìæ›:›Ò‹æøòuŠ/›atHi:Hë˜ú##Ó­³Ïå©™8gC,?ôl`i±è)Ä2B¶—”=èi˜¾ž[½—–ÁÄ~¦#Cs´*'a èbˆÃKýŽ×#-€vú>€Šú|ÑĨ{ÅB†´¡b&´kb:Z~Ý5tL†h«Òílå½Ï‹•bj`P¬J™¸q¨Òƒ Ë# ÚáÊà<)Za„üÏX=³Y2õã,ºÅ›mvm“ý˜¼57dØ ±,ˆÚ (wU^Îð[óï´š(fMö´¢¬V¶†l™lÂP‚™EvƒZîÏ"f¯I€Ï1Êý_.<“Œ\¯.WìÛÜmu[ÑFk•½µY«0¢JÅ?q1&>ç#C#`[˜Q’F[0ÊÎZ¹-)‹>lOb/M&ó«)5}ˆ“‘÷³2!3T›«ü›Œ"ŽDWM#PŒO˜a$duQÛÎÊt¤D$i‘ÆŽó©H46·&Vh˜aVVäÐã0i7ãQií†Ú]iÔA¬˜niÍAšR#C[šÑª#CHø\<ƒ‘ïc’:™#`¨0¹14ã‚«Ìb=–”{¤](kÇTVmÉ$peËoWG‰ç¯.ó`ôhé:f®ºÃåæœÑ,‰§ù®cP2A%;³Ž\ÞèÀchÁÈ)àÈd ¸i7¡¡¡æÏÃRV5óÜŒ¶2 þßênSñ ÄNDßnSÞ³ó”åäãòû8z€ÐžCDh>ä¡Î:Y.} £ZvŒõÁD~œ%<%Û¼#CSðŠ=ÂóO6±­®ù,ŒàªDÕljŠõ5×5É5nÌÜ•ñRͪPÔú’ÇV„vE†šb²³YETsªQ”…a*m‡‹·¬èõ{½5sÙï˜(ªa!hfŽN–"™¢€‰×>NRW„ä#CSDXéšÛðN>Z{5Fua¬QHâΓ£A›‘i؆£i³#̦µÕ¾¸dÔ:Ò>èz´F^­qQŒlm ¤‹¯JayZI4”rRðÝëe#®´Žk"Mj™‡FqauFÊì Æú[™lˆ}69ºa‰¤‘B4£àh–À5%`ÚÄRz×6Ó©‚¼†¨ìw½Ž1Èõ EæË䜬b¡4%4˜Ÿ[$Iêp{Ú#Ù\{Ü7Å“‹#`b7#CÄkÝh0ÈFæTGÔ\È·½¬Che›E³×*&C†™"=tþ[Ó}#`+Ë ^•‡]47Tê«ÕÚúo+Ǯь>»J—I¢§fßжzu‡H/Ρ¹Ë½Ì: kkc0¸~;<î!¹É=A;š‚c^k•.áPðù‹Ìž3Æg,,mf92*]`Kà ™Æj°‰ß©R¥>ü%1 Q2¶ŠtB…è!Ø@œt @ ¶ØËh¨`ʱ†J=í³•5ƒ ì^rù\ªp#ŒŒ³%X‡DÂ& ¹clÉÌÑž¿6 ìêÝÔ%€#CY8#CRns/H&£8dÕ#CKÚÃ@€è̤bR•£a¹d³Ä´(ƒ”k%*i'ÞxÐÒåèÓÞÞ)Ø¡Xöûã‘Ó\irÇ›¤ip¼pj0èd­'´7`øõK— =iHG¦¥´›°8ºY‘ d `¡{¥—¨®H-̯1Ú¤K±‡_󪺓qe¡øÞe"å ËÄ!ЙëJJTõnÓÆÛ­³‹Û^jɾõ#ˆe‚—¬„a=|ÝÈ~D&윢­¹ã©£§KµÝ&ºBczᶟ邅–T,a.÷Ü^½ÜH¼&°0á¤`@o–2´Ø‚ .g`¢!Ž#CEd1Ùñú³%J›µW<ŽF,n*k0aËØvͬãe©µ³ha4‡ÖjšÛh‰£\o~ÇFàÔ«t¶C8¥£)R%˜ù.‚Š5ŒÃ1ŒfŒxBýôç=†„1/âþDðe(#-¿¼ÏÔ‘ºùŽÀrP8ͺ¶@Äì=0HÜ—á8GíÈôBgb.Ë©,g!ÀσºÎ×hßIþ¬vH^åÈ1$òaìI6×þãSE*î·J µŸ,Þ"c=Î8ãeo纠´Â•£~Æòº¦Ž¸tó“DÅÆg+| SnGÆ!ÐP[”¨|k·5Ïáµ…&œé>íò2ÄŒÔváSC ˜ê9•,ÕéŒbde˜üoÂþ¦Ø¹ñð5„–!Šf@K ,A$ç÷yõ)¥wÂ{¸*`7 Áp(JÄp^¡ºPè?bu6lwZ#-Иϻ­(H½PA…Â: 0Ò€ K’7ÓUÈÃA¬4„ЄT#-Ä©Pr}X…³#-«:%Mà ˆh@Þk¼¦ÃG+€Å cÂz ÈåOIbQÜ4„i$ I-%㦅¾Ó©ÆB»#k†#÷ ¥µÇc:„穉"õ#-¨''ÿ[í‡A]º;¢PU » æØh™…Œiã­}'º|%( ##-†`*–ÔBIW³1û·”qœXxxÕ²’¬Ä6.ã_«fð²‹`LØB—ìßÚ&(Pd®yx$UŠ"·E/‰/)™ °¢:¬S“¸Åšƒ2ÓÏ#CMJþh˜#`ªdªj‰()_tªD¡H(ž«ò¶%|ÂWH -$#`HëîÄ4PªH¯1Þ˜#-Ö‚„-9 ¦ ¡ßSˆ#C"Ъ`Ј#`;#”ÍA#Éç$IHL±cXÄQ@º"BŒ….³Z³c†È\2RPRACR”*4#¥)"DÐТ=€4`Ww#-hD#`T¤R•I©Z1ÐB¸† pÂ\ BÉP#^„™û¿O£êø³ŒQ“ãu(pÀFŸÇäNTÅyàÂkžëàé“âO¶CŠ¾ŒXÊL#-ÿÁCÇ·êû½#p÷j‡·-vÛ›ÉTÝ5u˜H£1Æ5Xaqk%z‘šs-Pä±#CN9ñWÌeK>Ù¤ÌE’‚"/Ó“QE0ÌLA0Â$Ī—ï˜Ì‘$”‘ R13@RÁDL#-À¥ E1S@Q$$QDÔLòœ“—- Š¦$Iš‰h‚XFšhfhˆ"‰&R"˜šŠ((¢h‚¦ˆ%¢HúlQJD1APU$ÍA?¢Âš)©š‘&¢fJB”€  ¢(&&¢‚™"#`h¨‘&‚F€ªª˜(‚& ˜%÷ ‚&hª)’©&R$šH‚)i"ˆŽœî…Ã/ÏÍéJr!‚_¢W´XXB¾`ÊÉ:³¹ ϘîÒXNÇŠ41½å»ªºç!¿ë²nè"!ü(²+¥¢2Þöýc#C`*5#`Ìö‹dÁzµ¥Œnx$;§K.M ņõTð2p3¿t¹)å–)6Eج@°¢$*$u7“D(˜#`ïÜô¼ÓÕÑØ’ð3 Ì”ÇöÑò—PÇ3‚sæB²÷ ;w½´g´0'xé2ý UD±Ç€ƒ¡Ö@îGE{!(Gy#-ìbç­M Á‰_w'5˜~ÇdävÑzßc­¦f öqXÁì˜È×óT¹1(ˆ)Å †7ν»]Ð8þñ0?¯2ª ûÖÚÛïæ’·9]užëJŒ#UÚ)µf ¬cL¦‹‡7*éÒ^nu×x÷›V#`B$탡=9˜ÃtØÒ¡ÀÒ^|‰÷"¼³1PKUùRPÈPq=›ƒKRųƒŒKCADãÎ-A[d´à,š#`B#`HŠ’)™e¢”˜`†*R’ŠYH€ˆa÷Üõíë¸yfƒ=`ÇSèÆŒXÚ Óß‚€úó“°àþ»Ì—ú#ù'ù`i8FÛãqœë×goyg= áî5)ÜkmaD5QÛ[‰@D‡ˆfºS¤¾,¼v#R>ÈGðù¹ãÛÄ:§é½Zù=ªˆ{±éî?0²2n&köö|Ú#†j ›?S×÷CùÏ7ŠœÔ>äÅà}qy` b1(ÑÌÉ‘”(ÖŒ‹è#C,#`ÛþOŸ$ðŽ å Ý'sEÁÐ\7OºoS•Á'Û=;Ç—­¡Êœ*Á^¤Ãjwe¶ˆ&¥¤cø±Ô]ìÑÛš1”ž½â Ö#C’½ƒÅ!1H€ë#-àm/^2ŒyÞšþ¬:þ¼{£‘‰Ù¿èc5ZAK$&®ß£`š’#` :Ê(ÂìV³[ð—³C6inc’­ræéˆT§©}Žï;S–ð’$ZÔ—M0çwœR®Êjsу4R‰W¼ø®çYeNÛhÔ ë´<\@+ÃÏŸT,a`Ùq–´¾0Zº#`¤¶Ù¶QlhýˆX!èg$™e ®µ<™·G7í4k¤~µâMÓ5÷,ã…;˜aàçi‚xAòÆ:Ån˜¦JÂRp´æ0xˆ¤Ù¥¥â‚~GÖÒ#`û$aó}•¨yˆšÀ¡mîV+¶#CE³iž&®i¨‚äÊÕ(³ªòÑå äAaçä¶ÎZ· _ƒÕÆb„¢Î{VÝ1cßèäu`U-#-ûP!ö³ #`VC#B|ûcâFJ2wÍz7å¨l×~qˆ]<cá„f‘ÕaÏ05BƒÁýÿ³cï»î52u0öv=ú>Eºvɼ@4‰Ã#C,•¥‰Š¾XÚýxtÙÁQ½ÃØ&(¢?4à#-úþ¿³(HFOŠ‡ÃÕˆ®ç< UÊ®@sî­UúÇ.e†í9pT¹Q€”cÔz£òÇéÞMÈ?S÷iîCÊoéL+ôzý/°öÁ;Ï¢T¥>ùÒñùÿNzu×è3ÕEzÓ «†Ùéì©ið2¼U׆Ê~}ýyHdↄÙGï1ºíaXòè0†õ°v¿ñÄ3k÷†L³ä#-#BË(°Õì#-GNyäó“‘òèA (= ÒJëYß{'˜ê-Ú ÏrŽ×”UFǺ·®JT›á‘^/½ï7¥•Vf‡˜óDFªÓµš˜·»›š 1Â8ÊÊ÷`dÛ{Q1èm–ó yÒÖídÁ@Pè£ßÆ^O{¨õx]àGnsãÌwÛÐØíx·e°Ã'þ<´ wÂ@Ä«¶hU ‘-í¾¤У=øœÓa{Cá ØÓ¸è®ä‘=s°]|³¥ð²ß;G„ÿ#eíßÔ{žÍà¥(!)LèÝœ?Þ^}ݽ¦ÿ—ïà Ñö|Óåô[Y,QŸšÚÇ_úÞ;…«íý03îg5j^.?±¢ý³áÁýrȱ˜ú$'¥qUVM³ŒbÇÄQC’ÏLä‘Êu"Î-A#û‘t¦â9ã_ŒÂÖø¬Nû‘žLf0K™—²¨xPÕšm'ÔÆ ˆ|lU w#`ØÈ£ZÛà«M}ÜÞâ#CæéÒu-*Sáæåo5nn÷½aòòaá'"Qréh‰©yd騛a;’;RÒÔõƒ#CM‡x¢bM¶‚›3° Äåõ2‚d6M·ñÓ¦™êÊ£tR´·©iÙã8«à}ðfòÔ<é…:Øœ"Ÿg„2o»:lÿjôbz·'—Ýçn9\íÒN)EžLÛ>‡¨‡‹•.ÑÓ½*QŒÞ6‚°Ø®Ç/zÆ'x£›5‚†¾¢œÆRëeVVÐáhB‡I‡L(˜‹Þù¬÷4ü)ZÄr囩Pu)·¢î÷^WÚ¯/Å<ÊŒÀîó™“gBŠ—uÄU5‰èz¼©u¼U©!v—ØÓ33¨O$^nÅk¨}ÝÐï‡ÒÂ57™·ÃÞبÆ7Í÷Ì[ºZÕ¤ø$5UÝË?wFÚ‰Òu­oxÃ^æÚ @Ò ò‡Oµz•:ÎÅð`ì»4ItEWjvá&ƪ²—á2QÔìáÉ)îó×屢 YMSˆ0·7{P.'Hz{—ƒ4>Ó•’o;´Å ‚æa–ª\pí.–Æ%kiÎ8´œ›Ï§~éÑOĺÖЄ’ VšÇJ§iC•MP½m¦*&¬T†Ú°Xpô¢îk¶†²R§uòa²p¢KFÉ£žë“aØà âÌÍ›åGfe‡’ª`Ql`ˆÐuifðñÑ£P­ψi¥ß´#`ØÑÊÜ$Q>0g/q–ËhuøÓaÓ¶Y4•—SDaE-G¬¶o¾Ñ¡¹Û8¾v1‰ì)ióÆÜ’Õ¾$Ä09°Î P@»R—´êt kN(Öß4ú™u#Úƒ,8°qA›»¶}íøz5 5(nHÉ™o8M N`Mgˆ"D º¸" ÂG"gƃl£Låž›ÝÑ%LaߊxbƒK$ ¶‘ €QƒÁ+‡rîöÂ,{ß+‡61¦Êàrc®š vy΃02‡dDQ”ØDˆ‡)ˆ¥ó˜#`-×W¦°†9ÖäD¶tN"ÒƒÉÚÅ>¶ÈÌlºn†@Ú§E†_%û÷¾ô 5#CÒ=pW?]Ó•ú 1¹½o5‡P†1.™µ³Ór±=K5£kÄp(ÝŸ—t"ÛQˆY¬á:Â;ñs[ö\t‘ELÒº5*@!1½3U)#C"KLœI”ãu;Ó.Æ:%c—´vv(ÓÃÙF»9ÉY"lÝ;ÄòÈÛ#`ÖpÓÑXÌ´˜àÎij¸ÔÓðÛ6ŠÀcsšÏ7¥/¸&7pÒcl–1&c ¸ÔqÚìT-3´oŠ–’TÈ#C€LháÇ} GÑê®Êç$á6d·„ç}ûoË¿ZÊñAl…$^vÑBiÔ¦’‘Òlö“&xÂm[Ý.ó%]$§•ÒoW|&Î4„í­’w¡²sQ”“ÁÌA³3òö‡‡#- ¾çkÚpvðoÐñ]ˆË3G1 †Ö1ƒ‹Ã+“_|‘×°,–³ƒ[>J\u¤Û³°Ø#`{œSêbñ9ÖÝ·À–Ô9´»¬P韙ã€@J#`XÝÍ­Çx†ás/ÒD9Þ;Á¤e C`5ÌJgãP7mÎü:l.öËpšÇQ#`¤wL76ÃÐmœ¹xÜvÚ]\áP<¾7ÙJìpÁ#4Ã…˜³†,“>Ü({öWŸlŽ‚£ÜÌÝ̽#`çƒRw²é°a³J Îy­V»¾Ò·Š§ÃÀîÖcÆÚâN»1krÝ ».5Ø‚íÒu|’žtCãŒK±š¡“‹FnSdPsÆZÃv6ÇHƒ×“] ï{0SŒ³¬L.PgTH–Ê´ŒÍ·99TŒ¢0ÍD«æ±=@a‘ß<ìšøvOSá…0í€Ù:.›m8¥Û6¸†¤“¹CLjÍÊ1’b7ç„Þ!©3 ˜Ï[`R2Öq#:–C‡¯Qw®$º&·“y#uM~¦‡Î¹'~d6§a±ÃŒBcq7dú˜`Ù }Ë6)ÓÃì‰`FÊÑ6­¶qG[_(d2ÂsÖ¡hb›4“HNtjVl™çÚG †ždíNÔ#Cèu¹¸›¡»&åõ+N‘ß¾FSF#CÝ÷ Þúss˜UH‹†¶J@tcfâíž…Õ=í&}Áј¢’!#C¶ÅxMhº”B7|sºÆÜYŒmµRãnŽ¸}LZ3ÛÆETind„´ŠìÏ%u}(¼›[“Ô¸FDefîÃai«„q¨v5n%}‰¢#Იë$m:Tf?*U•öàRÒ)Ãgc½ÞCÊÄÇZâ–dd§Î"];Ž°­*h]-ÃŒAPâ½á¼¬}õ\IÜs,š5,×mÛ¼Ylþ/Y@ôjàž©߇á¤4Š0Kzf %Š–P *9vÅys³Ž•>;>Œ<`Œfö£X´E6]¡¢ŽÂ[È)Hné‹L†q§Êååòœîvsº”t.¸ïo®{ðbú  æ:ÕA=ß8}fqªlmf2Cî]lž/€å)Eö*vtì¥*lð¢N#C§NîÎÁyã­Ê½·2›¡™F†­•YéV±á)å½›¶V¦}Y“EÉ%Fsˆ¼©,kTжÛìOgQÝ…hfu[=›¹uQýyVFœ8:e¥–!mÔ“ÒqÞpí&ä¹S4Láó!Ø„Ç-bÚàOzz7‚Òn·Nø%¢ì©vœi<ì×MM3‰‹‘ÂÓ$D3"»8ÑÉ{‹;ð¸Ë÷ŽË·O´b{Mo¿teç:à­']ã{Î;q¥e›ØÝ:L`·0[oÅ"¿cŒr[_´L™tãŽ']©Äïvzù—µÞ‘Ž¼«¢¯µ‘ÃV`0wD-ç¾Æé°2HGbÇâ¹µñµ,挲rˆM›À¢&ÔÕˆÁ7½o­+Û$6/x­¸ÌÎHÂbn#CFr>çjîFaìš×|ŸŒ(¬_ªÊ%¨h)´ã£­®!±º£ !EK™`%ì`¼”BLCÞ#:“&˽wŽe𸪠ó…Ù³ZqÙ$ Î'óÍ7§7Ï'}¸–áô›°.ª”ŒŽÑ*.Ì—Q†´&GXëOž1˜nÕî`Þë3Ÿ®¢ÛœàËnTĬq#` õè–»·ßOL‹BaÇÒW#`8ùUA$[Õ¾k%L%áéR[Ë:{›=µ,Ba4ê¤lKÜuÀfCq„9Í€š ëE‘ 3¬Ã06ñ#C­diÑœÖa‰6"5Æ(˜g hà‰`úDc1œ¸ëX5`Ã1\¾™a[–©#`i"’Y6{—ò¼ªs—3;ê³S†{Ô•\-ÖÛ­·yÜv§3¼òÛ>$b0eÉÉ’L!e>œ«#CWG âX÷ž8äÖΓ݈²oÊ]Ã%}SË.„e|ê·â]uËpG©‚ák(¼1%N:oúeÒÏ:.†iàÎòšâk"˜dQvëiÌG-´à¦¶IŸrÍîKØ[A–Â%k™‰H‡#CÓ¸8í‘…ÉÖørX´9)¯E6EµÙAãisÍ0ƒf1®2αV‹Ï“#CX#CµÊÎÜcžwCšÍÚVˆ˜ô²•)+g(”;ÜΉ'4ÀÄ.C-¢;sœRV^OÎ8ƒ*ò>ª‰‘•Æ“ÆÖÒZ(©ÉqhÄE)D;ô›-pñ½;c3lCé¯F#`™:hBÃ^‡¼\Ç-¸r´úvÌ#`ö±¥7Z Nq!±‰… Žu8U¡Pš…UB–L„^©4P÷èj “ŠÚ%¯8©Iö£f¢9·E N˜å…lˆßC¶Úwظ*…r92µ‰3&rùfh/@ø™b ºŽ>0%EìT‰ðîbÞ‡YH|L³F–gXRœxw€ÍFÛDn¾šŠ)ø“ôQ&Üiä˜{‡ Í+&+€Onòôìâ²åá½kˆÓÔ”…³¶üÚÙ“ƒ£Æžb²rÌÓšâÓ„os®ÔÁØÄhדּ‹ª d2Sµ` Ms0-æ#`ê2™K³g3dŽ©Ô2ͨªR%D,ÙJ3—OsIêe«OÃk/•›ƒ¼Vm$ßl¸;¼4ÛÄ;ÎõŠy3iÓ™z‡HDS–¨§šyšûME•\<¢Qá4¢ÝÄ·ÄÇŠœËŽï/›š—H^®bÍëžÎÁ„ÅYÌvîsžØÖý"5O‚)(ÆUML'Šµ'œ¼M#`M·‘ú“ÚEÒ$Ú¡#`çsTï:N+"ã”jó—x¶ÝÁÚ6šŽ2ÈÃi²¼bÆ‚)”n[keÂ(Û#ÇŒ(à솺…ÞG²M²¼ó5B¬8É8Ù8Ýã#`Î0’4iŠh±c3‡§§Ö¡«rcfrk°z®B9K ‰ÍÚX=ê,zõú88š¾Št¢½¹]_fø™z‹Ç³y9eÞ’V\±‰ÆuLœµeJᕉZ¡œNÐT±w Å:ÃÓ6'n@JBöv¦Ô9Es1ëFð\"kæJŒWÌ‘rCž<8‘Z½¤¦œKU*ô‚°³q#Hã¼ÌvÔåÕˆµft^že“Ø°\qtHŒ¢r„´Qw‹ƒ.“åÆÂò@[6\lhyTWLD:ºÃ«MAD“S\)±XtánaÐñ… TW-F‰h’Vj‘s„‡&Ýä’5LC5&¥¤IO–ç%NDeCŠê]©fÜHñ;6GÅ1”ë}ßb™í¶ÌcV4öÚ³!!¥`Æ¢Zß^÷¬ÿ­—vçü²D1;H?I%;K7…™{´’æo4Ì‚„+mõiuÉ>WÆÈF_¨ŽSåQ§Äœ–¥÷D¤¢Ë&ÍTR1¨g½Šž$ÍhðoœÑòYŸÖÒ‚*õ™²#-k$ d,©&g’@"¾¦Š#-×e–QöÙ<é—+‘ µ!&ÀyÜ‚’‚ Fnï¤ÏŽ 7a†æÏq"u¹‚Þrã±/’‰#C()$ š¥M mr¼@Áo[8d-7C¤6t>Ü*9°Ê\rHêFpÃ$,½¥œ.hžœqº2rI&6È3‡:0«m /Cò(Q¡¾Éyt`î/,‚a{“yÖš9ÕÜ¿wÝdØON¤Õ“’nòK2XD '’œ6’_Z…"ɧy+[ó¢Fà÷|qºß rÂn·®ª“$ø0`´ëŒeú‚wda”¤éÁ„":¢‘ºÔ¾ä±z“xI;;UJ óÆu‰¬Z#-ä…|E5t„U2±l5Ñt(dÞY‘™˜yÖ7ãPÔ4ã\Kü«× ã‹dnÓ…‡{QÌgf¥³#`¤Ú$ÎÃnPí žtÒsˆ+]Ûäz ‰äÆlàè°dt&wì1pò¡Ä!aØb”!Û„äÒDÓ‰¢ƒ„‡K&i—hf+ƒ´PQÿngŒ1&##-ëwSAÇpKË,â1™keÝ4‚ÿ##C‘›sGª:Ô<@ڨť#`¬ª¤Y¤œúÞ¿‹<ÂÒ\¦XÍdt€Úö4°g÷“Ž ˆég}’C™ÝÄâ)tÀ¹vg'¶–L%$ Ãp;ƒ=¥Ž†æND¯BmÆÒOyçB'Ò{_hÐ\N1hÄqˆ+!¡ËiºPd–X‰U Íq2j;MÉd~9#CBœ#C ÔM‚¡u†C"‡`é(½ÙF)p«¸h>S¡°d( ®N…ƒêm66 òä•##`*8x‘ìüÌ{^>äõ×"ŽR#ÐðZaŠôdr7.·cÁ#-)A$M!þ8ªb3 ”¢Ì ˆšÈRçöA‡j•¨õÀ©´Þ9ˆÒòXPÔîyœøk€ñïw?Èwþ¡$:AŒ9¯sÌ«“¬¡‰‰ÛD{·r§` #-Tÿ7äð¯%ߣ~ÑÜ_Q ÄŒ½Sº@øZã‘TÁ#‘étC¦&%#`#-eB}.â«äýOêñ˜rGš‹{„!G²ùN0¦s;Obئé¶ãNšÊÝßãP¥¯¤§¹(&üeÑú$æÇ$y#`¶Vj˜á‰‹1†)bº˜S£’Ò(J¼$rC€ vü:+± #¸>#-È5âF-% Ç<AÀ#C„äºîÁ@JÈ {ðùÂ$ÀlÀ u4 «ûül]Ÿ}¼vyö'ÊnD?×Ü›6ô„„ôž%(—懌|­Œ(VY§çææi𷃱·$6ÀÜK °–ÎZ§K`J–¡A3s#Cº‡v´’Â9²påå'WÌee X€ˆÝÈnª#7„I2¦D ÝBìœBgËX8O˾6ØhÅwA) gLÀ…ÝðºÚ ÃŒ¦Û“ÇðÅ¥Îoj!þpK!n7x;IÛh½'Iéܨ6®næªå5¥lÕ¢õÆe*ª:Jâè²D:=q2Ô@øÂë9³ۮs>è­6¦røznÇq)pÁ$µÌLÀ„¡:ªC6´ìÇ~Û–/f‰sx–5ËÌçf=Àí;ÓâR%62´š b‘‡•Z|žp⇻aÉÓœnò“›xÛ‹/lrùÛv¶ì&ÂðÉìvÐï™0ñ¾ª˜Ô×y ¬Üˆî¤åϵϳ¬.§°²Ç'£#Ck¶g]J4úPš3sç•“$rŠ„Ë+Nê1=«•A´ù¼’º>±¶{ÏœàÒ%,—T,ÒÄ­Û{öóŠ¶ÑÔ{žÜœP”ÒŠÍoPo0Þj²nÏSPÇ2 ¨ÖH]sÌömq*ÃG¦!F¶Î®ï5Ôo§¼1KE/>ü䤰[å#C±ó©de2õfIæîcŽïs–ydÙÆ̞¯þ,T!ö‚1Æ¢š@’±oœµOTyÍÏ,6ã[;†pÚ:D˜rعXÆj1pÛ2×»¹eî Úug:/TáÞUQv^(DÔ6îƒ'”åì’HoÒw!»×…ˆLêö”¾À©–Š(¨šˆ$Àmµ19 0ê4À˜æ+Þ;vb¶‡EïšmÚCÓ‘àµp6°IÊ8¬“É>à? Ü$ÇûO§‡ÞNê`ÐaD‡8µ$m!ú$âpÀŸµ2ˆRRÌÒ A‰Hž9z@Àäwš ¼¨ ÔÓB¤@CÚ /±W (T71v='Ñ Þ ù:â#-IµÙ‰Óþªl0uíQ×E`—?/rT.R6¢íÜQˆ÷SI±£ú@àíÜÖ£#`TU)v鉗3šåt¥Æáä/9F_Õ}MSss[âêºF¯çêõ|Ä=I"q u$tu”¤¼‡·s̹'¦N ¸!ô ¨ý0ŠÚÿSÜþøÜýx$•iþ(ävBÅ¢S3@‹q‰ÀU!ZXƒËˆ^OX¢?{ˆ=#C•íªW˜Ÿ  ôþr=_¸|_y‹l*š.ø·¯µ{ý˜‚t_%ÈÉKý;z„î©þùêC»×ãä1‡ÏMB¼Ü\ûlÕ–zX,†Ûcœ¨ý6Y#`Oe¡æÂW›†o"IÜà¡ ³}Ä'¸'Aäüiå.C™/—ò{º8½>ˆíîò±gH€368ÇÖ©ØÔ>ÕØYÛ"P€Åˆ'qÅþ‰ò"„ýnÃçÛˆ”kïƒò¯Ù¹ôÎ~E"~ó»zjdºk•lú…¬±†4Ê%Á§9ß rÎ^¬æaH¾¢ÊÀ™#`Ú“N¶Áò8c2ÞH¾¢KumE¦¶ö5:&ˆ“ZË¥—0YÆ6­…Eüîm ‚Û†›c`ÈräYÁšÂØjA:UHŽW^ÍtTØŠ†² !8F€(æɾW8Ò.¥ÄCaKjBØÌÑ×ÎiCŠˆv Æñðø`Á‹À\‚QÌ^rw'Ú}šh÷þk 2DIJ" ˜‚V‘)„¥"¡R”h‰¤(h™)j’I™IªE¥ 2 š‰H` ç]tÈ{>µv{J ðOµÙMw¤+á˜',ǘó{¾ ÀÐP1(öÀ’¢‘Ú~S¯z>Í}Ý«·È#-âp^§`Mnâ7–ª°øýB¹¢#`×> zõ'¸#Cì!¡ƒ#0/¡) Çip?*´a† <õlU*ú2" U×#CF1V°¹cW)WØͳ2ÆD`eØ391¤ã†‘ #CÓ‹ DúޣܱXÙ×Nn7673±ÝHødðejæ8"»©hX9ëO¬w"xžÜ§p.‡Ì‡²Iš —tgrV껨òÓpç¿g6‡³æƒpð`>3ïӅТ*!ˆòŒíLŸlœ¨€Ì¼’*§D¾:ñ‘î{°ÒžûàÅ8M„“zLŽˆy lÙçÆ´Ô¥¢M¤¿Û‘$̇êákòÊ-†éF Ü†G ì%jêä Ö8•PÑP{EîÇHè̱ˆOQ,íô:t‹±±ÖñŠ™ŠžŒ¯Ë¢Gc: ƒîc@ÄR<—@#CÝŠ€9<¹ ”‰Ê€5òíG;¦¨¡]fP䇅Èì1«Ø#`A˜#C [–¸lÐPPÝ¢¼¶YªAÑ’ÛRMˆm#`±¢Cw¶‘ü‡Çp€×­»µ#C$CT4…ŸÉE,BcBè E«ÝKNαƒ°”ke­¢œ—øèðÎzbÂØMRËÔn;ð¹ï]AEê!ù@>wJ³\iG…†óŒFÌ›„K« Òá´÷bZÈÞ"ÃufbLðú5¦6:0 S„ƒ@:I‰ãð-ý®9$C߆›a¬]uÐÛï}‡¹0D÷{%_XÆÑ›Ÿ§t8(ÆŽ‘Ä…k¸a9µ=N£;0'—í„]Âë Œ*e ¤†'0ñÁB©0s¬f ÙTõ#`ëµ<¥Aâ ƒòEÅ Qæž)>Cà)Ò#C_9M•S _£Ÿ~†šÅ=oç2gys’sœàÍûeé‡5¹Ít­\z”Z«còðSJEþZ9[Ú·AfÃÕ1îã±ÍCbÊrpî“Ep§U½¹<>?¤ÌEJ64Ѧ‚²KvçgIáù£5ÉÅGVÂs¡Ç‹ˆv±î5/¯çv¤YÆYÚ·¦4$Á¶`·e°¸ºÚ¨r¤xFÀî’W$-àÒ¦­´vŠ—tøqCoøÑXÅäž4†bàqݱ$·fš÷\ Â¦Â*q´bïlßñðÅi‡[ˬØ8µ£µYF`ÌÄJhx¨‚paYjKˆÀÐû"Ó¡&epðlNzɽÓ““¤mÓšíœ$,9…I㵸=“ë¾0ã0û™´@÷™%Ü-¢g±2-q—7:YG‹¿«hg¬:f«ž7â®;’ó™|WÂL^|çŽÝÍøŒ³H[Xw3 ««´<¸‚4ÐRV]7lÚÀg>Q…RíiлëQ\]ã‡Îø×Ѹ§Cˆ11´KQªq ì™BzåäJGòM òF¸¯3.:ÛQ,LJWsDQ“šƒNa5F³½ÚË0e\Á376({NÚXBiWDæTɤ塮±#C¥#-Ù«pÌÄ$<”ïaccg |µ×HãvC#C´\u®é¶M¸Ç¨gqeä@÷Ó.G¹ 2Žê½Lxni\zNÃ7#C6<§œ>[1T­,¢&¢Ÿ@iÒTŒñ[[¶]öê*tÚÆ饌”û«õÆ!ž* e˺—*J2æHf¥*ĺ·‚¸N#Âl;ĨÒù•Àù’×”Í)ç~wVcŠßM¸›­œçn¢ØæIH‡ÌlÛï‡Àñ‚$LRmDPiÊ%,•éÁÌ#C!£bâÕÖ#`5ÛVdîîŽÄ})Ó! ´â“¶;»ÜwX8凖å«MËõU³™Ã‹UI‹]q¹ƒ+C1–ƒijÆRfg¥ÉË4rMÝS|o"$$㉶”dËÊB¬&L›±ƒœ¢a¦+ÒÆðŒcÎ0®Ÿ&šÉ9@[Pæ†סÔEÈLÇ3£ê&ýòíÜŒ`fp—`¯ë=Ü=)²#`•”"i|;Tƒ³CrTð<ª°”t”8X$:fu€2]yhG ír«¤h£J$Ã(C 3,Ààò$.Uy ¤Ù8§‚À`/3‘}Òvé§=_I¨aX°Î½G :UI±¡¢—ªxò‰æÓ³ƒaO J\´Ãˆ°äèâY!=¸‹ìßÑÐOeÓ|zú50à sM€Snñ õ =çœúØ x¸\®q@ŒHÆeZñÔ¦³ë‰â#C°#` R™¥)"8ç2‹{¯x‰Á Ñ2C5—I‡ËgN1÷µÆEƒûfõW°÷˜Ñîìs¾p–-R¹±¦bÆÜ·>ëN/ ÞÂÁ‰Ð¨¥¥€¢ ˆdø ÖIƒl”)’£K‚œÃŠ†`+l­nÛ“™iU$#ŒcdmXQÌÀ­c!õbätËtÃ'2‹üNeì/ÆூёÉ€Îýp«£Äv÷Óg3DÙ#`¼qœ¼€×Íöž”’xx¦0c™Î¾mƒÚê#-NÀPJ³DEHAD$#-Ð¥R °”„Ë‚@Ä# 3"C ($²Êï÷ôöÍ+Ç·‘È( bT(¦¨hB`¡@j (@’%¶4÷àH~gôqOSšu©‚˜A™JSCœ œ:%DQNÈ+ùøptO‘•*b#-vÊ…#-)ò÷&lô  qÁût¤C¯å˱¼žl»IšL£È_t óóÜŒàq×tØÍô % ¨U²H“@}ò,M %”µPPQ4HÐDDQT¤‹Á¯ÈPñªŸºDÄ"<°dR#-0Œ> ¨)ÓT?ôСê<ŒeïMDù&#`<<\cZb軃ºçèøŽž¼àñž'Žèq°1ý_#ÑÞqTž“?Dz`¤-0Â[ÀÈR½ÌªŽGaÕà#C€YéGcpí«•Q*´†ý½1Ïêá;/!-¥'‡›Gð×M)’jÒ¬]Ž0ÀzHj6˜ÔÛ#î´"f™?!oºôÛ&ª“SÞO–ø‡t…éÏË]²†¯#-gÔµ«út—ô¿Ç  r#C3Êm0!,Ä\!(@uI?ÐdR'àFdÊ€Œ/BV…Iu}Q¯¾Î,A5#C®m飴ÈšÈÏ߯´ÑŸ¨Bj3'*‡xšî±GJ(×7‚à¦Zø·†Q=”>øß\¾£4jVHdAXÐаÙî>("ôÄÄ!ªïÿ’T•ÔæGgÑßíÛû.@“½TPyö¬ºÿ枈ê9ô°°«,N¾Ws²yó•#-î9v)íqD“‘-/綞zˆ$Ÿ[€Ú#Cýî Û ¯1ˆ$ˆ‰ñœ|swºè ÕËŽ7G‰Õñ¬)٘Ы#-ÖMvÅDåŽìFÙÅØ8ÅÆþŽAÆÔùtòW±±ú½*× 9(Q'xZ)ÏØUpØ@ _êæbœí451Bì º!£$Õ&DÝÄM‹ !7!ÎbX¡¨jhô²S…YdzԡúejRªgûöÈET”  Ÿšz>ïƒ]óéÀïõ‹Z`Æ%ǧÙëªÌÙ•ˆ?½gøÿG»×#C!VLà]q òÌ:ËÀªúy^³2 –Š}§-!JîÃK#`Œ¢ÌÝ ÌkÖ$ùßäú2ß´éá¼ásâ›vuÂ’&x­è´A¨ŒÂh4©›õGµ¤žŒmyŽDÅUÌ}MRÒlÖ#CŒwûï;*D^­Œdh‰øápxŽ'F´O,DNeEÎ#-àTms!Ä"ç"«& 8‘i‹H±¸)„îìÉÓ`#C0B‘°S‚–³­3’2*1 DÄJÍ8;+M±X"Ø„Ý+µ®"€Ë à‡öî\‰Ñc0c'Û`8H¤9ŠÌï\?/ÆòFèKƒÇ`&ˆ”  „BJ6Šµ€"¬˜âº–1—`™èˆ\3ë¯8”ÑATÅPL =”ÁG+(`‘#CŒE[‘€®6ÆšHŽXúðØà¿ŠáaÆÔškK|Ç.AB¸$tÒzÙ’Â((Æ1'8s)!¥#-™Ð‚(Âk%dƒ§ ò2°âÑs…Ç¥@õ;‹PFÚÌlù¸ †’R^îO(™Š)¤¡]#…ˆÅáÌs(  „iîÍ)SÍ,±@ì¹1[¬á"ºb;Ä1Á\HDËHNl»âkQ¨¨¨JZ{¢`‹c£P&«1)©¦ŒmE#CD•C²˜ ( 8 LPD·aÈCI¹¸< ¢1"šˆúl'#`§¥3Æ(¯.÷ºîÈI ‘XaU1\ØW ö{{¯^º(ÖNÜñ$€b×!I‹þº0®´ØvÞ5Ý¥ÁžMŸ°)BJžȉVôaÎ5÷âF·ïl„ƒèßUŒ0_Ÿ"çðà÷6ñL¤oDdG@æ9é¹æhîÅD#Cx·­«Ïžx¬‚ ”• èΨ(i( ¨¤d‰X¢‰Y ’$"¤žzn‰¢°!´$TQH²H²B=‹@Y‘’8Ù)!/ÆçÖCÈ®”ÒÒ4(”­¿SU!ÀP3ÂQð$GÂA b_hºñM1*±C ÒS42±-#C Hû¡À¡Š’„‚!X @e`!†…"A ¨`’’ |—U55UQÐ2@ÁŒDD AMAQ%$ÂDDAQKA,PÌ0AD TSÔ“4´“ËRLUÃA$,K#C ´$LÄC•#C$AE45IB„44…0TÓI1 ÈÒT@!@SD#C- ÊMUCAQDÐÔA3MD$Ä2…$E Q¬Q(RCDÊ1C*) A”A0B2RˆD'USdT„HšVTŠ’ á"¦¢Ib!D„¦”JBŠWÉ,R… ñPO”¥ ˆJ£‰H¢IQ(|—B$FƒîQ¨9PÑ–#-‚#`jKç‚ívç#-«À€N?›T±¦¿”ŒÉ5õL@=|Ð0ø[‹–COKï?ËŠÎ#`sÚ9—X>"±è¢H‰£_E_Ú\azvƒõq¾ôGúVÅ}Ä¿ 0Æ6 >ëh{rƒè²;òŸL?—ˆ`ŸîFÂ#-M¿b#`¿„nÃÈ蛧Á;!Éžh¥jšÊ‘3ÊcµwšÔ×¢NR!¦(”„÷ ë‹Ís6û›)Fí;¸Ê••Ó¢Ò_çù ×±|ÝáÈgURùD¼o;dëÌà’“ù@ßÊz% J:§·ü¯TçNBÅF(<ø؃‘ º#`qø»£¡ÏGéKìñˆÑ¡4Õñ)„FG#ËÞ>C^ƒä #C…$#C0<•|NCÝ|~Q³Eœy©¯)¾µ#-H¶ºšR›ø¤„C”|æþ‹Ñp7Ï—U^É ]}]Á¢ ÷¢"‚Æ>³ÁÙ™¬Mæ/á#-sö´î†!1²1ÂL®ƒ-ˆiT`Ô#-7fƒ”“ÄÍ#- ’{ÓÖÀ'ÐàG&¼#Cút4˜>í Õø¸$S10<5ŒÑ‘yuˆÝí,gÍuênµ·±t>lºðÖtº¦P„àòAPU†Ž£«ÚbßTF‘'™'d/x3JòÇGóþ) i=U'½©üÔÿ/#-âÞRES‘7ëÚ¢§ø˜ÐvV†'z;ˆƒ„x“Þÿé>¥]#`i!ßi±4lŒô¦ƒ³DxÄ‘¦#-¹hEi/‡Êd_GlÃøG9Jôm$D~þx~þvë¶Õ#VÊ7%ʉò8ÒÄšã¤QŒ¨îa1x43E40ƒ@‰|Ááˆè´©ÜJ;/¬ëÈÒÑ\Ù-UT…)UEEUÃjªª¢ª¡„#`B”®Ê„>„Æ]xZ§Ì™`'ìQ[`Àó@NP&€ý”¡@Ì”©£›–^Ìàvb&!Ú9ã6 ¤ºó*0#`žÝ:øæe©*âL‰ó}W$w{iœ ¨Z´ÆÖfdó—^‡D$’`ЩZ“ÇC8x2Ÿ|¦] ²… è ¢Î&hùùæ̦ˆ5*ñÓNïáÎϼàï߃œAîÍ_yøy½âª§Ô†Æ$ùOo¦ýR'kÚz“(P>¯€`ÉuG8ƒH¤ª‰%>Ää<‘{Â^HŠDgéIwè,%¤–0ØI §TáúµoÌÈâã êxÏÏÁWüR1«à~Áµ¾w}ÝšêvfÒÚ~É2#`IU¥û¬ÊÌ*AQ— qNÇB– h ( ¾¼Ú4[c Ê_Æ[Âã)H}ð¦~Û8ØÙ„II ÌRÉî\Ï=c·FI¥ìâfB¤~þmLM„doR²Ø£hee6„ÌAsÌðI{&Ri#¸0h5JI ÂÃJPW5¾omuŒ´ ÐDÄÙJØtÁ)PcÌi§‚‰±‹¹=yŽ·‘vtÜ3ƒBho 4JE-RÒÈ’öB¥¢Š¢ µ± š"v š6%¤­Ý®lDGvj ò Ô!KL~z¤’ŠŠ¬âŠâ©UPÓ4–uøö·Ð#-T qPk»8ɽršÑæ”}òBJˆŒÞ?|."€'¼~Ë”À°´(´$€0p#Cº!‰)Àwˆ€Ó¡(Ù8çÓ€rP§pÌ”2 !>kPêÒA!Ü÷u(°è P›jw8‘H-RÔæ99=‰-¤|˜sòkÞÏ"šóÌ6è`0Y$úº•fyòÏ\â‘ÓÄ&U22U‹“Ðlxm’ĆK´Àö>׶N¦ø&‰R;ÆÒGO"í–#`©(yS¥˜c+0 $­þHÒ4$`‚C#-UL€Î^冓o?ôòÔXu–’>ð7£òm!!(^&yŠ‰¡€·9½#C’ \!}ïnŸR?35šSâ•8–¾V»#`+˜~Lܘm¿UHcF´F×ÉësºÓjÝe³÷ÿ.·½¬SBµtˆ}:á¼7š°#CDrïˆ~0hœN~9Ñ9Ãx Xžö F#-ŸòAON;b@GÖh¬É]¼x'VpäžgúÓ}ÊRÞO¤„ò`db0÷¤¾¡æ΢²B F]+F#`¿v6½`B{iÛkÍREÐG{Š©ø4ìÓ?¦°~̘†7IºP©Ã$§,›š([¶3M*»µF“i&ë.­Žo‰ÓKU½öqó`X;ê§ âYÛRš\”ÃàÞ‡‘[©¾JÃfؘ´³d R@á4BpÄì(÷ hC–“•(öP’Äù&#!iB˜Eª¬<ºÙÄב †Cd:!I³03dÐ µT‚‹ÙDß,}g„‹÷] 9,·›4ã†JˆvBa Gd#-PîL`mˆÛšPÑ£çX,aÓ#C,+6ïXLoGÛö«¬š—š•Ç<㊱jQ¾x+ÇÜ9Oºmä¡­Îá¸o¼höÍTAÐÜ&¢EEŽ@£.!©Ê°´âÝBÜìåz<ša’0Æ¡”¯ >D$>qç£Äx’gΗá< š,k[ÖJLÌQPk´#iÔüZô žySÉÌŠ¸Y_l-²ÔÌ|>Mqw¥–êã˜ø¦ý',æHÎC¨âc‰ðïA¿.4/6òØ¡(Òý-í‘F1)ãåô_3²¶>–¡¦¯‰…˜§œƒU£%xÅ_nU¦(^oDÌ/± jªÍ±±?œïlÎ#`,ÂT”D#-€É!ºnVåö]kI´aL‹DV%•@ Áî¼f'Îø'šêéó5/`è-®¦µoQ´¡Ô;˜•Hàk@—h³•QM0>:a³4‹É#ÑÒ‚Ø?I*bªj¬xñGS×< Œ„Ä âz3G¹†QP*†]*›@ä*bŸ³—›^Ðk² ÁRa×»mn~—ÆäCŒ˜ÌF88fP™25 ¥¤ŒÁÆ#`Zt“%"|c€$pK4† …ú=yöPö{#YåWeB=Å2˜‘‹W† æ{b¯Oú·šâ]7ZOmëU‹LíD¼PËò ãêCõ‡½ó˜ÒL‘ü@’]}‡‡'8Â0D’*dB¸œ/Ó"zwS]ñ{‚b#`T ¤#`iGÆ/#`¢€’JOqå 'Hî ¨¥‰hbûðdªCïN„#-¦Ÿ¦u!uÁ•Dš…Ü8hhFš`¨H¨)!ઊðÚ (8÷œÍ3ÜIéÆ3À40ÁT^)µAmE6±$Qt-4óim·YNðÐGoeû‘àß Ñ1TÕCDÐU xF?V4-Ñï*õ‘4|JhOsê ’…8È´`ƒH:¢ªeiH$£I…ÛE ä©(8ÈAŠ"…ž§ËÙ¯Ü0gÉÔ#-?Šã®®UèS¸îãÀ,"ɾaÜ;ÀäaîçšÇqi!NA#C"?12ÑÈRz°Eç¨ðãûjDñ}'¦Jî†3#C üÿ-oÙßá׬¿gÖwhÒtϸ¨å#-æ´ÀîdY)5#`gFnkëÍü*©j©¤äÓX™L´æ¸#`¦)ÁwYÜ#-Ý6„KºKÙœC®uPÓ¹üÿ‡?H˜3–Š½§×ä¦Æš˜¢#äo‹û‰ù0÷t!õáÔüS„‹¨Ø oKhü·Viž¯¯_@`ýg­~f&Óe|$fU{íõ]FQ× D¨z‚>:HqÓ®º"ŒMSVŽ}#` „ž(³‚ù$#C¢‘@F*%^ýúß-ÚMLÈô ÛY˪CdÕ,ŸòcÇìI"œ”Ÿ‡«>ÝUb‰°cE²;Ô$Ûò¥Û¼dÔÂ4S”"Ü$Ÿ-H×aìu‚îÑ¢+µŽÈp5»»Í-?ÏÖtv %)A•<´pÖå¢SÝ¢)*)îÑCM/E9Ä8‡,IÈÄÃ\ÐF6Ĉ~­W\éfœÍ™ágÖ Ñͦ#`šn›EG0bˆ.HyÀÖØH:»ÉqÈ–ÝLyÁ´Çü®_FCsýúl›& d©f ”ðŠž"¼$)CÝBA5Q-!Q‰ )€%’$¥ƒA¤"­3lb¡(B•)úqú¡ˆƒ™ #`(#`@ ˆÆ@0GaÒr<ŽBÁ_9”)õÈ>Ö ËCBШ=ý ‡{€AÅÖpB‘ACì1"|æg¦9 ãïb™ Æâ 3=8À`ÈA0gД¢YáPt¦ÒHžeUÛ(´Äà62Å’æ”ùÐMG‚µ…&¤A#-D‡HoÓˆ&™’É „öŸF“ƒyYûnûÒá£Ìø‚hz&*h™ÐÅ1Sœ­D¼ÈB‘¢\®PEA•5çãÊŠ%/É“ {“žjW¡õÊ!@Ð#B¤n w@:¶Æ˜œ54àb'CÞJºìõg‰îú3¯b¤)ÿA–„±:4€¡#`Q0, ZR’Ю•ˆ(¡ÐhJJ"@¤i‘…ªV’¨"‰$"¡Z@¦eJR„X"5ª@¡#`ZZŠF€4Ò…!@J¤me$©( Jš¥ÑF –"” Y#`¢H1j#-h!Q(#`ZŠ)¢JhiZJÓ ¢ŠTªF ‚€"ƒl$@ÓBÒD-!HÔIZM#-QKKH%%RÒH%4”£JÐE@Ð1!@DHД”†'C@…"UPP‘ZÀ6Ä’ª#-H'³¯ÕSÞu¼Xn|4%¡†¢Õ«úæÒ48LßAè c@ãzK1ÄÁÂJ#íõ]ä•íL•ÓÌ`¢ƒ*¢ ‚V•( ¦š#`Š=CSPHÞ8 ÃÐ:†(èéÕ>ÅÎ*tŸE‚l{Së#CfÇ%› ±š_(L "n½Â#-B€àáP‹:¤Òèª&vË6ˆ‚µ£QA¢ƒUÑI³ŽÁƒ°‚@IW#-é\~†9ÌíÈ¢š«ˆnv¶ Çë_‰ˆ šªŒ7dành™=Ñ?wO%N!Ê¥yè8º‡Ìsn)m‹ÿ%ˆ¦ª°|úeA0ò10ª&ŠL 9ÁIM5@QL@ÑE5@S$MSDI%DA30E#`S#C+DÑ4KׄìÁ%uÅ»KíätÛû¸n þcõì*Ì9#Cˆ#-'€0<0’3?ÂÅ#Cö„Ôçð0MM`#-Ãfã#CÙëÔ÷i“4HjÁrÇ‘–‘‡‡‘}þ‘¾¶ÔfCXp ‰ |ßç#`0èDbW¬Aæ ?“³H »$¤"&‡&Qˆ@˜Æ:¹"ÿÚæ$M¤˜`½]»ˆÚ±8~ï†)¢~¶éjûߎºXOwåüGçû¶ß€¡A"D8ï jŠ‚ü+¤<êótvÜþæý‘Ÿz®#ÑLŽ­ºç8S`‡„ãÖmô…KrìüÛ…vfàA†Ò€Ÿq[Ä©“™#`?Ïøóobãe#$þà>”Pë6ð›t‰Þ\»(2$ÉñMÀðéô?·Òý¾Ôú*&™‚#CˆŠ&†™‡6ÓC#C¿Â0š€úþt÷*i­<@•íÕ#->i/l.ŠÒÕH&%­iEPÄ#-ºA}œ”U¥„ú+ùd1àlŒÔ@œ…(R!”6G#·MÞ0ÀP"ƒõ4Ãðek@-6¤éüo/}Mè·žþ8ûçàrÉW»V‚;9M>Ü¢ˆ¬[iis±^ƒ0l…ã˜×UcŒ7“HŠŒŽã—Rô2fÒi©(¨%Aé#CÓÒ~ãóäz¹K–Deú}¨¯`_â8!éÃÐztUyÍ»ú‡Ð)<»Nø}'ÏQ¯8z&‡#-äkË©w§t‘$G˜§£à&é©bQ W(‹ï(³0TÂÀ2“RQ«YoÓü¸óÇÉýÌvBŠNªÓVøâq®b"Ç7?&ØÊŸû1 †Z/×Êuêþ)Pê@éupAHwƒúzÐ)û#C @ï|o¦’"å†Úc#CóáŸq††#`ÌÈ4ñ¼°8@£r Ìê%5i¹e½}?›Ó~óíð²ú|KÝBß®°F#`üc„Lù#`-æŸ@;¿q~ñŸ( i#C™:wÚ’vÿäbìz‚4,%ù pM†ØÇïd-¼š5‹$!órå1,•ydª4` ” ¤®$_S¨¨¤æ;Äá!›'¤|‰ü}ÝXIÄþJEOÑ›CZhá!I†˜‰òÞwûœzŸÿD{ï#Á£|c£³¦Ø9)]6ÑO$pÖ4°lZbHìòœPŒkr6²0-–¢¨[P”šqvâš(2¨éF•ìvž¤Ò8B—d{ç®rÓ#Cµw#`2?”Q*#CPM®$Ë8¥ÿ¿F3þ )·Q#-þø4”6„DjÆuÔ }õº’ŒlÛOd6ÑÆ¥£Ç,YÅÐX¥[XÆØÑÉ0€µtHeØ›”‚Œ™VDŽ"3&³#¦0M.ùG'½Ã  ãî„y4†5¯3ÌâÆ(‘ÞûÁôwE>£DZ󱻧 ¤ºþ‚}õqûlzîNAÇžsç#-½%¹ñä¦ÐR;åŽH“oŸ9 @SH’¹Í„˄ᤋœÐa†#"ìË=øÈòP÷©9(к¦–/qÉbP䔾äN@r4hóÎ[šÔD{Ïd¯#Ýúž‘Ùär$®žJ³º`¨ÏwÒçwuyÚ'$ø‚¸0ÖEÙ`K¤ÞªÎQÕ°“,Þ2—1Îä;v)‰£]9´Û“AÈy<—ܤ`æ(i#`D Ðèõ"ˆ›Ö;Ë&žzuÍRWsÓ†ßÄÕ'"!ï˜òb(Q’¬) .+&õPÑ0ËlâÉ£“16†æ ŠVHHÓr ãPïþþµÎê9ËÖDÆ#¶ÐÍ•±´Œ±?)|¼Ø:\­hk„äÇ45±’ûsÒF@ó/Æ#<ÕI-!ŒɈù€ ð9Äæ¦1AÞF˜â`b֙˗ñ?³°Ñ~@aù4\î/š­„59'óŠBt¡ëviXìóÆ\<•PKôkÎ`¢mF¢#-üÌÇóÚ I‚‰”!Aï3Ž.!,˜3€ÂÛî¢êŸBôE#-Š3ó%F¿²ä.¨†°³eäHvÏñ?wžž™šs\wà ô*Šù—0Ì ò‡Ú>„µõ‡Ý2zcH¬ƒ#£¨a@À¾À§}R‘#`¥ +J‰4¥3#(ž¹Jª#`Ê%*ÑÉФšB€£T¥#¤B”‰ "èI‚$•UþÑ¡yôJhn~|@¤JT4‰Kì¸Üš? :}œØØé D±1ª'a´%Š`ZWPOæ=%Úwšxr¿CÓOOÔ^;¨þù°. &`àd<‚uu%Þíö~{¯º¨PKÜŸ:)pÂùÍ)>Ï $C2 #C`Ÿ+©#-›}”÷¢8ÈvAä‡5¾äþœ=–D?©‘ã± D¿oúJbîŒÁd3¼I†ñ¼¦jÀgöñ ^ŸèQh?X ÷>"ƒƒçgÑ_+£÷`ÂbrvC½!†#©¼šŒÕp…_²yHD ø@üs#-üB†’¢Ei"ªî²ád+÷ÆHï*.C@3´ BÒ‰¥^Œé(é#- HÓ¬#`z4Ê:OœhJ÷JšÖ†D´#KØuä)›ë#-%(®!¡'Øäéë|vň¨Å#ÿtÙ8,ži°jRä„zðQ:2dê\—²‰ˆR„iKáñ¾‘ÇÙk¶¶|5ñ{6AëNâŠp-:JÁ P­MJÁf¦¬éâuøGû¼ù†ª ¬!x߈ÝÒ'HãÁpdÁ%iÊÁ~À>Õº(`NAÄ$2sï,d»T??"‡yU_—vÏ ÂvÀ ÁT¨D–Úŧ´¨QU JQBPyÙ®Û>¹ú7|ð=×#CÄñRÄW³ð¦'zj'òÅ¥cþ[’’dÈd½šìME(ôð‰¿lÖj"‹R):£¸ÌCÁ¡ j£®Š›ßïâ¿÷ºë}þƒq‡áùxº~Ë!IB,,)¶RR32å“öoˆ'ÃD:ñ_†mõ/ç0ý8n4>­]h<\¾©øA¯¿Ðv, @|‘¶„š˜€ªŸ¼ò­L26Á?Œ;#CüÑ$’UAùËr™]CÊG'ƒ¨§ÅMDÐBA#`RP´+øð0ð že‰ #-’Š‚Cp¹´A44èœ×½ˆ£1JÑAALÔDB(#CJ†{bvßAšû15œýÁ¦p…&0úšËÇ •-H#`PÅðÒ–¿_Nëì€5ƒÊ!Gð¾mð#-¥#-6³}ôÁŽ2ù‹†¼|SÊWn N’I§÷¿ÙenlÈ.ã,GðÐ-ʺL¤€L›„âåÝaP7RæË3VñQîA½æîºðø™—y)/óì©IS³¨~-±«Ä¹Æ*6`yÓ;#Xdõfª&tßm)Ã]í¾SÛ¬à|ê^'¤¦.Ù‡q×}3¨‘©ÅîЉ¾¬6ç.ë±;HÓ©™Ñî@lû¨4ŽxÃ#CžîmÇÌ*z)™·èâØ‘â"”Y|SÄhÆ …Žd±˜0ô£­ä›YÏB#`B ÐR寵^ìX8ŒLkÇ$QÓD>#±ZMƺÓu™ãkÛ<œó¬3ôW8¥¥y¨)ÀÕÔ.ÑÌíA×y-Jl"á·©×g|'0îê¶s¦ÑJÂÙö²QJ¡ÇEL¹¬îXZ1ÏÆÊ’ÎaÓaœœ¦¤ÂL¦vÕG‘&è}Šv•Hz¹Ñ9ŠÑŽçu¬Ug"§¬)Œ±†bAÂàSá#`-Ç%Ž1uÚfì…Ž+µÓÌr£›sJÝÖµÙ¬ ™bB#®ijXÄ0ø`Ã2ŒÊÀˆv9 FÌ ¤$‡èÍO2€˜ŒÜÖCíY¿¤Ý¾™­‰­™þ: èéÇ8ÃÝv“ÁõÈ#-bÐ1¥WF#-í„LGöaÓÔgúMŸÒ01yH™ÀaÐ$ð$ähïêpàužÙ7“¬ñä~ÎÍ,Éëmæ¯ïʧH£/•Rµ èÒ&¤ˆš¦š#`Z¡‚™%¨¢R¤ š¤)#`B$iˆh#-‚"©ŠŠhFªˆ––"%$¦š()’ˆ˜ˆ‘ˆ(ª–i¤¦d¤hV„¥¢ ª B€¦’–Š#`ibZJ)$d¨‚ˆ ¨¡E”"¤(”)‰šH$÷aE41R2I $QWòNb¨b H„ˆXJ"hªe™ ‰’(J©"F#`H’¢Š¡ˆ ¡¡‰`e”I!Hd¢’&(Š‚©¨e„¨˜j†¢Š˜™ ªdf‰™)$šXŠ) ‰ª¡‚"( ¢©Q’ ‚‚@‚IB•@ Jj¤( dÍŠéÊÐM&ˆ˜‡ûü-ƒãf5ŠäÄ‹³Æÿš\¨†CòÌñÁ˜q¨§0MùAŸ²º¡È@N#-%¾JPþkõO¬,‚Ì ˆ;ÔÄ@hE0Z J¡¡¥§2²]©N3TDü¿Â‡yDªð‰…)Š#-" °™)R„À$1 XxÞ£ãë׈ww¥@sV©:ÉBSl©¤JL~ò¿³ æòÓÞð¢J%¨”÷I„AC£@AM7ðí-²”¤&™(¨iBRBŠ¥äiJ$B¥ûÉËSP‰2•HC*‘$QÄÝÐd„¯q ZfÓ (¥i#ÉÉ2Ï.!4°Ÿ³ù8òjWî÷h‚ÿ6ÄÒ5 ÉîÏ0hh!ìjH~|X¶CœšDJDuQôAè(“uöd9šö6šœšÝ"x2„W瀉‚p{‰ÙÁRö{Çdþç~iù ¤CÆ@¡…~éؼdß°Øú×Ç`¡¤^õý¬ À?+ø:#CÑ5Ò}Œ¥%3@DAE4„ACT%)J!@CI¡hùAò*„%x0:'Hvátá€êBÏY|#CUwö/Ò‰Ã}PÚÎÏŠbé¶Åæf!…ŠD»àô€ž…(¨‘ŸSTúkŽüIÍyfûYÜ|Ƭɳ0Éè`êísˆoµ†Œš‡¨)ÉÛž$ù!&9T6?¹Ð<;öœh¦È’Á®©$‡.ôñËBí ]û1µõ»š¦™ÃJ‘­r%FµÐ&w-¤Èp¤cäMà|­éþ§ÂHGPÃÀ•aµ‰Áˆ9,ê@|“Ö}(t7>'äÕ#CS³#`@I{`M£`X|vI^`'µÕNŽ0â1àtÔ½}½^RÇÂýŸ5‹ê›½È€`$ÿ&"ø¨m¯—¨r¦ ˜}ǧm¿NDs d1(Í0g@`Ø î]¢[ãêÎMpè2¯¤ ö›Ëç#-Ôéê2SîvK”¡KˆÃ‰P(¤ 6í“ÈIÛGÀ»ž…3oR$¡¬+V«0#MZ B¸D]P=HìVVjÁÂt×l-?-h:Ôï¬ÎXbP*^¸‘Ká½!§åøw!ÛR%Q!3QM>Èu(UDÃPA&“UPIC•IPÑUEDR)B$0?‡r¨¢¡’ #`fš¨ª¦ªB‘D75ç?è>úŽý(ÆÝt¼5;ªkp$‡ $ø„&mß$°^)J“’Iï ,ºêoàg‡/·¨®b¾Ž$N–Øâåê±À®¤46]æ¢öœnâM=’ªÛ,U”e`ÐÒ›3Óg:àÄkööÑp¼}pÛ IðÉ‹‰é9–tèÍÍÊa'ã¼¥F»RkhéõìôüRç›ì3áui¨'T!¢L°ûm¥Aƒ#`™¢Åš ¹d¢Ù3O%p>ÓñÈDÑSs¥ƒÉGÈ ¶ÅX’á“0Rþö0woprSÖÁñhñ#`H4Ûeü™‹zÑ6&ÿ\vëQSQ,$†:ý…ö[3_âÿ.š[2ð7Ð8Ø—,‚Ë\f©%’Óµ!@úƃ»‘Ì”Æ!rТ„,ªV$|»­ò£,?àÎ,Ì^£ *õ¢4Ý°P»˜O‡±­A¬ºÕ>Z<„;îÁ"P§?+߯#-ðA$9ë:"ÖËG*ÖWÔ÷{n¸£ˆ. ˜à±7¦ó&ãÎ,(Õ„>3lBÅ!!…ÇRPÞðº˜Ñ¢–ü¸ Ë°MŠM”þ¤€úáŸP€øúõHÎw°ŠGd#`w©ZH”dƒ»×Ó½:ûžòQKÇ#=?B¢2ÍÒÅvÝ´õé®tši'ò˘Aahf~®¨è00­Œ¦)»= b)!H(‰qˆ2`á²BfTâ’Àt‰2ˆ`ûCÇíÖk5Á§#sßéAôž%ÐP|ZhÛ,Å11TYŠ8B¾æ¤×T÷§r|:º1°09¢c!JÐN!Èk |¿£‡6vcdcíæ×ÁþÅ0Û;n ÞÊih¦iˆI¥ÒÊB~ä×Ì 4rŽœé$C“SHéx ýûùæº\qÃN€÷ùϹõ*&x/(áÄïô øId H1¼Å¸*’!p“™æh£²¼y#-<0‘ü\ä4€Èƒ[>ߧtr)xIÈNdÉôQ0ÅÙêG`ì'vÈb|-%NT…Dïy«Â»‘ª(IHJlnD†¿6!¤/ÖóПVÓ¼ÏÏêñN¥?,,F‚¡¡Õ+³"ÒE!J`Í#-DÄL¿Á ¥ÒùC“Hsd§À¡¸XÌóñ2~‚xx1džG8ÍÊ™¸e1vŒÍ5ÃDÇñ‘«¤™ƒ¦M ç6ŒþŸ‘‚<„âR¾uc¾ÙДÈ=Ö»úµ›`g ì[†¡XØó,a6½÷‰É‚ø†ŒÉ§ŒM<Ö³|½Ydí¸v¡² Qð·l ~S³³ã@Lw‰ô|#`´?ý#‘#-<<%3bqcíï¿åÍTA5û»G1kDΡ°‚ ía{X©ÃÕLd±p3f”„"c–¥K„ñð´àkž\e'cVàئ¥-X:-ÁH]ÛJJ[‘u¼xÝIÉHnÊÂ>,hÓˆò5szª#-˜U,FYM˜À¬#B¡w™©õ¦ÜK‰i¡«$ ê+·ˆà„àœšÁUÇ‹bIÑ Õ©"’雌k5R¥ª«íe”ÆïÞácÈp)@cU#-Mˆs#`v^8iƒ8"Ì‹é÷â7BI$Å­ÍÁ$¢èEw¶\Òƒ!#`é·¡Àjº.)46Fto,Éù¨4)¬ÌÙè5‡Ü¦¸Æ!s³²&\£C–Aq9xªšaúybÌa¤ g†= fÏ}mê‰A»´3LºLZ¢DpM{H0u¤æ&‡ŸKgŠO›h;GC^©™Æ$¹[J'žj ‹ý·Ô¦(µ„ܪ!3ê¡_bÙ¥æð™ÕMU«ø ¼wÄ(߃›€0-X2’0‡IFNu¿÷t€‹AÆ_?õþôè â{}ÞÌYÑ*F˜v +¯‡©¡ìÁÃœP"(ˆ¡ñ 3ˆŠ Œœ x$"ýÉÐÈaÉQ;f`)øðáC²ämH¢Œ ÃÄús ¶K½¶êízBÓÙd.a‚ƱƕiŒC¿9õÙ#CAã¦9!J‡œŒ†%øŽ†ÚIthƒ’èÄú†ótá·nÇ»°Pö<’w/7[”„@0[K²ã€`ÞVîaÖÛ„#´Æq‚ä HöÈàâÓ#xäi‰ƒgf=(N#CÓa¸¬\PŒW’¨Ž’ëí¶¼fL­ wf.!°Iå#þ£ÛÎoq­U6hK8sa=¼C˜#CbáP#C„)ãòLÓÜÈûx'_d8ôÌmŽi™ÃÔ’v‰\s#->¤”!„{ߤí#8)è¤x`|«c#-f§ºìJ‰—›%Ųo·ˆª ånlsXˆb)°±š2+!|¹xñù²Ðü#ÕÕ6äs.ç"Àï!D³åÍ8âriŠ¯âY k+HðBpxÞÈŽƒ¹‹ä#CèƒÂå@P‘(ñ…É#Cþ˜Ð!CÈÀÐA2TD*Q!É4(±J4į³2ã˜Õ#C 2”·™#`5¦“ò΄)á©ÄÐ˳ê{'m>XžCÉç3¶^Br"Q ¤(+‘KˆÓ´hÕ¶’õ•ÓËk>¡±‘ä*’!±o\æÌ´èR•ØçTãdõs4§R¹Ï§òéÏКˀüÄÒ‘Ù ™«¢z°Iˆ 'ø½³ù0áî >‹fê¨SÕ蜫¥qîý÷úú¿@I0'›! Kï»”\#`O“á;Òápbž'_¯×å¿=vÔ¦$;a1‚‚hŸg,bEœpg] sœ@ãmÝî|¸­¼ˆûÉAaøß-ywùo³;»ã+a? åZk’Ð#¹Pfâ"œ¦]ú¸Â´Ü&ſ°Vù‚[z¬M{a¥¿¾¼š§ Өᖰ×bÿ’#³N2òÉ’à›¦†¨Ñæ(zs†°ˆ"ƒCÑôÏûm¡œ™ëì;´ö½¿®wö`ÿˆÙ°Xp˜¶èÝ™™@ÑM0|ÏYØqo-¸)Ç“·¡œ¤Ÿ¶vÍPmnÌ|wE†á ¸ÕŒ`“E²4ŸWá=úr¿1÷¶äâdÜÔJŸN/YàöFç媰$È]Çy Äó¹=ðõÁäi}Òïo[¡ñqv@„}¬poû«ŠÐB¯¡¹ß§°|”`e0ñL:6˜Oä¼%¡4uTœÙ,BÒ)#-åìŸäî3[sGŠ gzü~]OᆌŸ×¹­Ób“‰: ƒ”#Û6ÄZÞ.@“¸¶>?ê¾Ðƒâ£õ„€vU@Á>§Wã o€Š×Ø•/ƒèèzþêK.Š£V/ÄÄ´7:Þ4ðþ,¿Úq½—ÆïM#-lI¡¡”ƒûGÈ4ñJbŠ–‡>s‡I K)*[tBb ˜>cSÎdbñ'úuÁVE Ž¯äƒ³K#lmuyÒVqþ´n`n8•¥¸À~V âýÍ©-aE@`ÏÊ ´­@ùl`©˜”ó¼á%Îrª]¤OD Ò { óà9'ë·L´Á(_‹MM@Д‘<¹ O$ƒÝª`)´aîçX€ÊPÀQý\F#`°ò¤»#`<¹ý”QjR$zçß ÛQí qøÊ®ˆu`"Ÿ$CæâÇg®Æ!?sd5ª"ý¹©ZÇ0€nÎœy3D[‹Ã=æiŠ`¯-X#`€ªn0¼‡„-AI´Pø$ÆŠÕºIp79$œëÔá ÂEËMݦá¸7.†ª14M̧(è À+™;#`q„Nª#-J¡"À‡&…䈈ÛÆ›ØÄО÷#-Þ”7ÜêqÝ °€oÐÛë\Jð>~ÞàyæMϘ­OU“¹<—â`šñûþ¯'•éß#Ò#-“P\,,îWC@$ìé#-¨€$ |ºCFHøøô¼CìºúºÏ›Ÿ,c’,žSéÛ×öçÁÜ&6;Ìôžæ#Cø $té+¿,Êó«ƒyÔ’D,ÅêÖ:þ—¥‰ŒöÈƈ(Ú7ðÕMq#-¸<.Œ#`ѲƘĹŠ!$ /µ/#CUt,9y8Áó PwÜ’›—aâÉÂtW'¡Nb*|‡ žÄ#CÇä†÷«##"Ó#¿La„¾žƒ˜Á#-öü¤6û„€ºÇŒ¦8zCÖ&€9̉ú ÐQV$›¿<1î4z6í1G ªxXTÙ’m[sC`Ìs-„#`å“40›K°éÎ&%%pfç ©À…K’Á$±.BQ’ d’ƒQÃÃóYª©Š$¥ˆJ¡D‡ÄÞ¸ôÀXòL=<3Ï_nŽ‡ša ’ØæƒQ-¶‘d3ñÒÈZh3IEÐ}HS f°ë‰Ñ{`Š‚î'8ÁU\Ç.#Hü­Ù3zÄP«*$ÉU·™á¹ô&èf7”G ¿K²<\jCY˜®!Qœ<üû;$rp¶ g§¬>ãë°Ð[÷”4É.‡ãrI¢¨^ÐÓ]£ù9|…OQ4#-#Cÿ˜‘]ÈS‡oWÊþ×·âÑã”*0ø î)Ѥ`Qô/ BÜõÐ#íÔ„öüßT ¨L@‡¢mòöYàý«fêwGÈ<%0Pè?ƒOCÈíÙ׿¼…xjã.G#ÐÀ>/—¦ö63U_LŒÌzµÍÅ£Dzý6ÖÆyW½ù’„k”ÜèfMGŽÌ+æè*1j¶Šc—N\!.ˆ(Æ2ó…­jZá¹ fôÌ'.0aÄ4êÕɆa9Υ哃ˆØeå4¢‰µŠ”‰”Àߣ劫[ Ã|3NË]ØX_¡ÉÌÏåÍ7„ÏG»…g—zi=þ.pÕTDŽØ)aWPù‚Ö×öbõÔú´RIQö͆…ë‰Îȼ·áYe a“+¸à .EƒE³¥)7cr9¥<¬ÈDhˆ5c°é#-#L” äê#¶€<»ÓàIã t% ò#C†@¹°BtéÌ3;r@\áuÊUˆ„)ŒÚ•áœLJö¢‰I%N\¡žHõ”ÜÓÏgáÞ#×1AEcaˆ [ÑÎ!ëûq¡¤‡ÈF`h#-ñÜÄy/.J>ªO$¤ÁPœæA‰HÑ^àrH7œmÒeÏl‹ƒ¾\|}·ìœS¦ãùšÊºÕ ¾tŒ>ŸëýŸåÿðoŸŸ!uþݽŠ=2Tb"¡– ŠFŽƒ­SxÖiO\§åÌ&ä‹Ã#k-UñïÁÁâd2@T8CSÃßL7}Õ‚M ¶UƒèÛ33C|K½kCæõæžͪžïAèf±Cז̃4S3p‡N ®$SŽ%uÏqÍ3kE5D÷çS0u›\L‹sd&¿‚Hþ#`¬r/Ûu{•’ÚrnÇ"hÌI­l¨Ù¨Fï÷¾—„èë¥hõQF“™š/Æ+qëødÄä¢Òà#-áŽå¡€Ê– ZBäh  L‡0 ÅïãÜöbéÂrV–Ly\N$û¥J¦##-DÝŽ]Ä O×xnÛOw§¦žž¥c'#C“2A('CHäe¥Ö±àÀ´5UE›€~oÍRS¡Îö—E‘õLØ "Á ù{ðü”)Æ óŠë£ïãP‰×éÁ9Œä²?d„vQæÀõcc¾e/— ö{Ì«s¡“Rz*aéE 5bÔ†ªŠL³öjJ ³HÍf•¤Ï³MæJ}/dÿ;Ý™X[w'ñh§o§íŽÅ:çÜFŒÔŸ¯«nóÌÍ´<‡µ9Ã9ð;LôhcNy ˜þo´ô#Cè•ô˜ÉCz±ÂK Ú×Ó%y#`˜ëp)„øéèÒC¬¥ã#C #-#CƒØDS¬°Ñ`ªÏàê9¨Êg·«ïëÏéOÍ*¿LÆF˜”9 e#-¡kYȨ Ü|cÁ#~wNñx¢.çO]RØ>LùvºVœ3M[c×\ïyÂs‘4 3L#`$p‡°”{"°L>ê¤ ? #ê8†ÕD(@!"|¹(41  /ºCñØuIaüüx8á ñ‘IGì5˜/ã÷–Î 0ž Èo×i ° ûC‹á2”3ñYD.¸5†ˆg†S"¥FÓ°1¨ô_A“$æºû¶ýÀ>M=G¿xvSaª¯h~xð)%z}áb Ü'•7{j+S÷g÷¦†˜è~y(LmŠBhÒŸ«¬$–,–ÍlF§|:2Ùó£]!ÙÆ…†á©Â#-{Ó3”ÈÇ 59ÇÆr=CªýÕ€ñh((#`C ƒ0( ¤NÄ|fƒ«@F#ëžú¯]Ž£öyt9ÖÄŽF‘N°"MØBuIÉˬ9%Éek€Ÿ$$y›=4U#-wÑQæ&M3I4Ð p@Ê#-ÿ‘è8àJÒ Ò ‹"Ðœ\cêpêš½W„DBÿT®ð>î¼6ÆÃ8V˜‚2›V6mƒÁ¢Is7¨-j²V Pm:#-ÜEIŒu8WQM¢Ìñ¸m´zǯ#CÖ|GÎn•$ZˆT#Ÿ-úYíOÓþ×»ùÂ/ªü†¹6x’ÿí<ÜD±ý3ðaŸÔF3¦×¥¦¨ã«]~ËxðØHäú߼ŕI«,ÄàÎÉ9àü‚Š )‰âÌ‘‰63Å´¿†qJðÈ£aN÷Éâ|HÞ†„ØêmÜÖÅ…&_¹…ÝMu‘/»ºá†”LÍ•&t:SÄ=~£=±ª¶чë‰##`xgÀ/šÝ)…2ªàxo2e“:c9öáÀÁƒ;<#C!‚éIS™ž«_CÔ¿ÉÛ¥/ßøÛz]úœd…SØ„ÿ1óο,½þ«€ùyNfgЉõ®#`˜^{azlä þU_ë@w“>çO«!íœw#-úÉ£ÑùOÙ Æ~måÇ[È°Ü[¸VÃCJîgÂýo9‘ƒlîÛùÔ½"]2#m4ÆôÀÌ‚Æ[º\"4H<— PT· žuéÓEƒÈ^Aëã@Ë&PPÓKÙœÜÕ$$>öô¬ÐS5mTFçÌ\Œ³ßA¹¨oD™YH>#C‹.#CVù„€ÒctzSTB„hŠ’¦‹E4FÎTmwœ.NÎsspá   NBÒV˜ÈÄÙ]#CS#CLû§‡²˜þ‘ÏOU2¦"œÄd3/ÄœPz²:#CœýУÞVêS)]òà© á6SQÔ ¤d"´—V*P¿bÖfQÕÑ¡‘ql^{ˆâyý_WÒkÒ,óŒ&fâ-I#CÏ´©È¼¦K7IpµˆËH:š´¡·Ž±$ÏÖ6é¦=!ƒ)mH&&îRP4œ?¹½#C`Û¢ âßGgÉÌ8ô¥óTp—YYj´}J¸fÖ©ië#Öhøj,aÏ'ãeHo¦¦!;f‡“U’Ä ’\3Â&Ò6[a¼P‘C[Õ0c{)AZöÐHÃ+¶„’S*£hyXäud1“ ƒezlf()ŌٴH54>911Iš2`“»¯0ð‘Çh7”!ZwzºY£¹VX´ŽíH¡+CÛÁÚ¡&ábTr"Õƒ#69#ÖNƒ°µ7cr£Gb.Ïv ‘uv¡"9KG 'd÷ÌÒèõHE©܃0Fàd‹meúß\À£HhgÓt¡_P¤OÃÞ‡„ÑäK‚ ø4M5žYY™ƒWÛ.x¦¦µölàÙÔj7L”TžHj<š4ëÌYHÆÄ,Ì•s˜?2lFäAêLsŽûûŽl#C>¼q£‰'K#Cðd! MG^°±á•Å+šË˜Éë+4Ì Œ¾YXÈ”D ÆÊ;¡e˜†‹(Ô=œÊ%xUÀ¶Þýq7ËÔSê=w z“DŒÎ2w8wœÝ§%4ÚÀÀ)HÔ.±cL(ÐAEž#`LôT¬y¹Ó¼ÏK‡Ý€3—]!`Þ ™0„0s,­P¤g®î^>Mƒ¡Ý°CÞ–Bf#`J©ªX •`’¢ hJh"(†IIh˜dd¢b—¦ H°ò·œëï—ÝòˆTÞŠoÓM#Cc’9³œÌƒ·XÕ’)xÌV#-Þ7ˆ•d…qÓ€ò–H3ñÇLso1ŒRÇ­Cd„°åÇp°UÑÀ]TpÒ+ ‘Â1Æ' ŒTMYq”FÌ·FŸ'Tij«sœuT˜,6ž8Þc‘™!¤ÑuŒ¬d7vc1Q¿$Ä4öSdï1ÍÏ7.„*v‰ƒÃŒ&-Syq²4XÆ2-P‡õ2Ôù‡$ó*—qÁò‹´6™£o{š#j4}mtx--ói¥@šhyÙïÇÍ.yïb}û#`ô·Š°Mƒyíy.‹—j ,"i³1óPV›0ÇØØñ×r*ÊÊÆj(X¦jǧ¼Êh#i#CF†§€×æ‚éÚÕÝS;8„±U)ÇH,”íUL‘,o0Ëóê2ŠF*Ãîb~Z¡Ž#CÓ¤@!Ì „nœK#fí¬p='U5ËœòáÄ„¢òTòe¨ŠáÞ-‡Š_.)ç™ 'xAÀŸ¬œd)9‚`ÑA>‹Ès\y°ó†#`’!€¨ 78s–yN­t„4ȳ±ß¶shˆ#`¦ïvæã‰×|û|yßICÊáÄ*ÚŽlHToX=tÁæñŽZÆqå\‰€›sŒ[öݬ0¢b¨ö3#C•Ñk Œ.YöÛ‘çÂ\ÍLèãØÃM–áÌ!M;' uçmš ¹ÑeY¸*6ÑHÛúU«¦:×*b5Ã,Ž»o#C+tÁr#CÙBƒ9úŠµ]:Ôo\VÑH”ÑY6±{žÑ¡Òf{V,{hœðà˜0ç(vXBzJìm<ÝN/ždåG`ðžU#-Lð„8›&.†Ôô•—ÞÅÏ2|Ïnø÷ãµ”mfÞڢƘÛo—ËUÓ!YÓ ²ÁÖ$@mU5¬T3›qN3J#["ÃêBi¥Äa•Å’áë`’ÍÆ߶*¢ÝNÔ)¡TQ±´Ë"ƒÖôüEϦËêSJppª*ÝŸ .ò5%:T<êãïË“17ˆÁÂpB%ç‚` úœˆð” =ÁÄ(JC/ œH| Lô:AAG cO pN!#.âbdSôÄpàO¤ñ1ÌêÈ羪¶ëÎ󕥨jz„§r¬„'¨éËÇÑí+3æ1ßÞú¿k< ‹ITÓΗ FˆŠbË1 ]g·ŽŸ‹‘§ÍEBtÛÏò÷1Pr`±w…r»w†¾ÃÎ{~úöÿV‰°²t|ŒÚJ5îÎUþcc…>&¡Y}†qÆ)4=Ìolü¦zÔß‚º»;kƒs6q+ßÄ 41¥½@úð€4ÃÈkWT9>¨E‘¼–Ö€á{5¼T“D@¡žZùÚÅâŽö×æªd5/{J‡rÆÁû|ËqE*¡I\Æej 5#MH–NÛ‡›zÅsÝ4c™¼6é“zLɆW— ”v&ÍF{3CÖÞÝ%ƒÉÞõ™nÍVåÓiïZMÁêZº¸S8¸Ìk3šß8|ãjrB’(Ûd2Ê,Õ×òUciöç®õm œÁHA^ªºrm›=—XFÇ“{Q`k#`ÃE¯*ÕW»ºhŽ´÷Y«Y»lµ1]ˆ«‘í`šæ²czoAlœ‚!¶ß’Ã0«ó„ž6pÓrkÖôxùyB'6¶±jbÁó-ƒMï®%H™#C’ºiB¦ý~ì3¸1£Ô:²rMIᕪƒÞ‚‘ðã9‚%Óq»†ÈB²"„¯Æî˜=˜Ò„PÑ"oìFYÃO[°¸é¦€y =£ò#-Ћ¦#-c… H S°œú¸ôwÃF Q"j1±·HRX[ýX`ŒL (ܱÑÎÝét¹ÎQ #-(Ä°fœOœœØ¥a’†þHõRÈ·D]Ò„Ç”#C BH#C(4‚tMo¯¡Ux: VB ÞSP#-9Œl#-rhÊÔæN<6äQ¨ÄFJxsÑnÈ‚ŒHˆvZDãÏ\SSß„ÞÅ$I1lé!f§Ë cs›sˆp‡B‡—ä'Ö@Ðr €Ð’˜@ì)É#`ܯ`ì+ˆvª¶áh"ÙW½±Éä=Øc™#-Òƒ¡Ò4¡I3E1Œ®gAPMË0™ƒ™¬*ZÔ:HUÅå® ¹L„KICš3±-RÛ*PŒ0„I'!S’‡AÒ¦Ç9Á"&€(D9*PDDZ^{ÀSEÍ•+0s"11á¨Ü§tR4 J¬õ‘TÒA„ ¢K„© (ð­û•#1€S© ;aO¬€²Àt„>ÅàòâÜg8Ì„k“ Ê¢­"a#Cá4JDÅ0³Wr¡Ž¢nP?n>¿O<¨=“Gf,ã.$6‚‡•¢€¤÷rNÁ“¤Ð”3R…-P$4«3BET}–EUCATJAM0P4UA,…ŠDP'¢4ATT1A#CP-Í0ICBÐ)TµIHÅCJRPƒHÒ0´È±1 IA%DJSQ@”ÈP´#-„†Ø©„Ç`›ÉL«ïU.‹ñ(Â&AS\U°fBÂ9I¤S$Hœ~¨½88y˜›ÜO»N'ûz@éHLÙa¨ˆ¦Çz5~¬b) i¤‰#`h…¨•‰T?èç÷²öðŽ#C4üÝÚïMR¯žÓ‚Ù|ó‡÷fÒs°Ÿ4ñŽA±k Ó>­#C4É:EÚø*€{ad•(‰˜E(õpÊ€pá‘C’@¤A3ONÿ„¬‘Øû¸jЪR™ ƒ- J I£A¤­%1¨„òCM¼‘Q)DÄÄ€DÃP”’q°¡¶›`#Cç !¥(F%hˆBƒ`Á!Nlˆ2ˆÔžA¥ š€ˆª䎕#CÄx¯#-œü¢z9ëÑí#†‰À‚Kw†«õb¬|µÒ·ê¿lÍ~Þykkœ~.•Üdc*ÃIÑL>†9„#`hÍÓB©gï—ÀÊb‰‚èh„0Ç@4îîÜ<û°xI¼äˆ”d$Š&O‰¦“rbšáÔ¹7‡«l Á|Q#`õãY0>8ŸqÇÏ®Þ*ÿõ“ߎ¼•ÄÀ2”ÁYŒZ2¡ÃÏÈW‡ë+ó”ò#C#-û%¤yI¨Ž¯Î$Ú¡sêɶ~=ò÷9%1“#`Ž2C>ƾcò 5TL„_¹©’=½ebìÙ¨9}IŒᯩ4KEdPÃ#`´ûKÀØ2–L4‰w~oÉå¡B(°UÇS%‰¡OM÷Àu@Ot(zúÊvt5"qíÖÓ¤a”»âíaû5·x®—q±ŒgZÕÔ— Ý- `ŽZÐz´lkT᲎ #C0ã [£¯uÈ7!¬rà\—&YFá)~ºLŒÓôözPY¸— ëØš0Lò×6l‘·ì‘iÏ–#CTÕGT²MÒ¨¢Ô%!¾ô¡®bÐã:3.ief00{ÔrigMQ’¼­ÊÏV(ì´ŒCññK¡Œc¡š†îWÔ»B`›FL‡Á4a“·+¦¨u#C5æU@ÐæÕ´³É¦6:JªbW•R÷Ô¯V nÀć$åržÕ.âù9<ßNú+N¸ œôÍ”ÅX°®nÁî6ƒñ¨ì¤¡õ! ϲÚ#-¨þvpCèN¡¢€Ä¡á#÷ÈE4S@T¡#- KH(}4ˆæÉú¥^Be1"±y'ä•;*ö4ÄQê#C#C D(¼4(Ї/ s‹ö?×ûиe‡Ñ¨¢Ñ‹ _Oœ#-äìzñÜšx†„-*‘J(gäíÑ8° sÁviý£ø8’ÄÓ3QV#`X{y’<<9I¸˜ ìwÌy—÷ 4/¢ðÉ˨y§A¹˜ŠSÔ‡*ï\úó Æ;³G I<õ44¸äó™ÜμðÓë8­wƒ…H”"@dÁ¼ç-#`=…ÐWviC‘:l®Ò!Uäþî_E ß8y`Ší³ôqAc†Ó#‹atÁû&lÝÆ#`y64‡«Àða;pƒŠ'=<“„‡¯$ ‰*>¤Ü«Q*L„@Ò4Ð¥4”M"‰BRQŽ°„åb¦ŸÚlÞâŒèk{8ó‘“\@Þ³‰5sA»„0á†À%B8ÖäÝŒc±’㬶á#Ct‰ÌêNÄ?½êðTâ%AòÒŠšª&ˆ¶pi)*³E- úvèä¬óšŸ§ðì~Ô(ñ¯bšyüë£å \Âmá6ç#C[©TYQð/.¤U<pÇ\óÔ•‡ûB"²[#CñíÕ×0ãe¸¶; 7Á†ù©?E:¼Cƒ”åB`»sØ×Tª¥`–ØhEiÒ"ŒË‘¤ëV®ù¤Hæ:uû¡qµ²¶’O衊ϬÐüü*’Á¦M”P1vE*¡Ôƒ<`j¨ „#-xþOçU¯„ƒBx'à蚎ÿO ?Ë#C"¯ %’NÏÔûa¸eS›ÕÐÓçúžÁÏ9ñûo¹ï>@é/휚¹«Ì6~T⯫ªý™ˆÁ4™ $Àn\„¸—YV':œÂ9q{2£h¬î ÖqùÃÉG_ªŒ¼ESéä4[‰C@AEXã"9‚Å•vV2Ÿ°˜#CpCÔ@…QOÛpMßho%RÑ’b@™#þPãíbžÇ™áüÚ?#C~òðU5;°p!÷Á9.J¤Z8˜#C‡`#-ˆˆ l™€£òDU0i€½Oãæ¸Æ4àm0’ ÊXˆD•$…Lt²¹d’#j ¤VÁ¢)HI›Z­–5‚B”¤£J… bðÆ àlÑ0RhCIÈ1±ÎØ8¤LM.E&#ChÄÿqŽAÒŠ€˜)Ž.†& €€%™¨’#`Zˆ¹*a„"(i(á–¨¨–‚ÖÄ:ªiij*äœålsLrMXÆ#`v¡#CEiÔ‘ÒpŠ#`yÞ‡tÁNM#C 0ÒcsŽ¹±ÅAÈ @IkJ1;`˜b-8F B!BhäŽ$KÍšW…¨·!är#C‹0EBáM+‰8#-RMD„!þÌTE€0’0+ê•X€ÔЈP$ì*÷)öJ§aàØ1(ÁØ€@œa#`D @ WÓÛõðx#-øÀj#-äxAR‚% J¯Òê†!ÐlèŸ](âÀ”›ä ƒBD4˜•B÷£E6TP÷„ {MÕÖjeûVßZAÐ>…D|€÷ ÍC¼ùÿ«‚š†F x\öN‚Ÿ–âYd!@ŽÊû~Óã×#CÇrDôÃÆr#-!f©óÍÏ¢0ñ 14ûÃ?ŸS°üzén!JŸ¦Q¥O¿‘ÔGëßCâ”PDŸÏ‚l³üß—_ïš¾Ÿ¼>äþH»‘Sñ~d5Ev‘H„µ¢(™‘™¥)Jߢ‡±©ˆIGˆhò±öÊ¥CZ@b(h(PZy ä'ºžÉâ #-ÀþÝ…ÂSW;Ư¥ƒ\^ãP{%#`JhBDˆ¨ N„œÏß㾨…(DEA‚PªÒ!J”4´´ˆ"1#-A)H”@ÀÓTH4›-Ò@ni‰öu“휑uçd/tý·9NÜ6ƒZ•Ö‘”Ö˜Û‘’šj ŒŒ2ãA‰CÛ#ã4…𞚘p×0fщ %_VæŒFy°5\%‚4ëÎ9S‹dÊÓ¶;#胰&—¥RØ惉ä8Àù9`8DšÈ=ÉäKm#CØ\sii"Jj ™v„# ™R.Ή¸H€«$ ç'²è¦2w q'ê “{ÝØÜÅ.”ÚO€~.ÓUÜ„ß1JEÉ2‚æ‘ðÅ2ÚÅR…2Šl†D‡rÝt„‰I„{ÃD&ü üÚ$i¸\À¸ZØÄ Ñïd*‚$¡ìvAFN#€Ó€û7ÎW ^ˆÄ “÷àÄȤ„üûDÑMwi½ŽTÿFâ â‰Á4O$ŠS¸„*ЬÕÖ!?à#5W-Á‡òSóúì4ÆÓl?‘¤qa;T^ZÊ\£`¹kÇSw]!EÞä#Csr‹b …Øssìÿ2û®&ÁÀî#`’Jt6ƒlrŠNàDï€@‰™ ¦– $¤(’h#`T¢– X "!…$B@T …ª™ž¤åZCŸ@&#`È|¦ „·CiÖ#Ct}rŒB-*‚Ò¢”¢!Ún‰*È2JhJ¦’D‚‚!h$iiˆ¢‚Z’b …˜E#-…žaJP²Ç«U}Ÿ;4Ÿ£¡#-èH¬Ãöã×bíÙS@Pð%G«¢#-(€a T™ ˆŠ`7NíîÄá਩þ¤—M;¹?w¾ˆï&_-±zÉç¦;¥ïyFœNr#CSÝÓSD]§¢£óÿ#-s{lKï>`¢R«s¸:;~c—y¡ e„'Da(wŸEÓ·ã²ÀŸð|C—sÒ30ÁÝ!?¢`4‘!†±é#øàxɼ¦ ˜°÷ÐÄkWIE‘&Æ̉áåÎtÉôj’$Ä#-ÁÓÖçv+Ô$àƃ0”©ti,Û[`Æ,t*fÿÀâ°L®šH`ÌÐì-üÙ˜|OXë€<Æ0 ÌÞõftý.çMÁæÑäÉØ—=%S_ž8ÁÞ–5 ËTPUEyÞœ;úÊ#`DV(a#`2i’©ADýÓá,0Q*Uµ2ÐÉUD@5”ÑPD]…1=ó]f ªAl‘"ºÔciYú¤vhé”50œ­—ã e"Bó Ê“ÎåšY^A$AHCÂÊ„1E>ý#Cx\eb „U‚ •„N6ÔaÌ %Áò»Íë(†Ó¼[u{ÒÈA#-R1ÐÈ`HJ¥ýßAâ—;}£)ó‹™O1”šÐ\sÏXC,úÈâ ðò\„3ˆ—4Z°J1#C¢4uÁ-–„)öêäéo³`¤‚(RHìF’„(ZLõÃ@|¡Ð”‘Õ%#-v#Cƒ|÷Ôæïò„:ÔÄR‚€‰À•¿k—ÅB²M‚Å—m5TZH% }ÙÒ0ßB7Ö3¿\)Ën'ÀmÌÄ!,PÒ!B¥Y.ÀÁŽN·7ùÐsjþÇÚ¸ ·S};ÎmªnœGÖu(¼!~þ#`餥T`Ù¦ï4Í#-‚êCûÙf(ÙØÊo4b‹#-[!#p Ø4ÁÇ@â'(¦#ë 0V#`dþÉ!#-õQBƒ@…®Œ’‰JvH! \1î Ò™U6$A)0z´Ÿ@t-"†5ü„#-ý¦Š£Í;µxG ÷EŒ)Z-† ã þ ';?ŠMRESAH2œ8óΧRCÑ©§úÿ¤š’K†úr€<"Gî“‘TFFøƒ"#`5ªX¨jƒA™)#Ûü+Jž‰Ä™“Hhþ¬kƒm„M)‡³“·^ ¾B‡»¸n™„‡†’[oéÔ4 ÐkGðbù™¥ÍˆÏsSý¯œç!2Ó.³êúƱ˜Ö8’m–ö5p‘(ù{àçDKÙ)RÞ©§—ò"ÆcŠÆ«6ŸZkp\¥qR«õD*"¬²gí˜tñ„¬Ñ!Y¦;šz.ÈaÙŒ‚(ÈQœ©NúŠ¡‚{bŠûpò9š]¡Ã°;¨Cãý«Gø °ÒsΡNCò`pÄtðC°Õݱâ³$Y!îÜ08±jþ†@èɱŸŒ\u©C(g‚@àÐK¸fæíÈÄX†qƒdÕõ)¹+ÀñÀTÇ 8ê›ã~MÑý wûþ)ùI”B H ñ}¸=ðuà ©ƒêເ„}\æˆöæUë«[¨#R4tõ4ÅD,ú¿D–tB?$~È )œöq¢Ž2"­eX›jw%›2Vm£ ü>íO—~›×v4pü“#`9Ú-ðï‰x÷š…ùo…‚j&…üéï¶&.LtM·y%Áš µÎ¹&ÔE N‰xåU;ä3ØÄr7Ž”™•­ùNY‘q¶.=5\þŒ°ò?榃 솕·“ò36X 7ßÏ3SóZSV@®%A4»ÊŠhŪ«Ë-±«•zqʹ‰DßF.o=o@&¥ÎqVrsjc\>&ŠRÙz¿N7tH¶8dåôâiOc3™’2ðfA²…3´6#C~ýb2gJ7ÖP#Q̾[$è)¬‹íjÿ‡Ü£GHo@CA!|‰Îͬ˜`Ôv{7Äh»nNžFÅæ9rQÛ$4‰Ò ó[ÐxgÔ^yÔí.˜ôI¥"y¡¥ä|!ݱ5œÒ©ž¯4çõÑ*Ä|`Ú‰â[8£hº+à¤ZeVN¨›yß$eòôÆÑGé®›4oÛM—õþãê]N#-qSCôÈL ’?ÕŸ²Ê”á´ÇŒ»çäLÒ/rHÇ(÷ÓéÎaÚ×ÍØ é#CE.´¼Þåà3¿ñ ÀFX̉–¬›+ÙycÉØ]½ëNé»ËÊ ±Ñ_Õëåi~`#CxœNÓž÷ZTÜÒÛÏ8l6 ÀàîíNñÈA{HLÚ¢­p-z3“ƒhhK’–(m`,²jõ¹öM#Ca#Cùoˆqñ(ß„ã—5S·]¸,ï½{ô~swÜne §çL}H ásiÎÉ¿Ûœÿ5{ôÂq­yõò= 0¤:#`Õ¦M‡×êõsÜ|äa#ýí2„#-1Èâ¤übJ."cäXsïa˜pÀº“ò ïö½CŠ|NA¼…$AŸ†Ù0˜šFùeIxïG^ýdôsÄÆÜÅ2f(n¯¯BµƒVÒ¤œQ°uƒC¯HüûQ#-Çóñ_·Úù(tÒ,à #Ct£¢©I5°û˜Æ’^‹¶ãJÖ#ù,öoÃ’2‚%rÇ$>櫤™þ䵎(ú/µqÆÿ3T“wdgÜ·…>k*nñ»‘ƒÔ£9úò>~½¬ùàEþ™0óâøé4Þut*r—¤Ã“ñOt4Ž”È^ÜF5Ç^Éá÷ܪVÉ$ÖõªÏ«Ò`÷”ÈÑ+‡>YÒýÛ'e8lƒîÑßV@ÐÄxÊéM`<•òC·œ¯Ïèï1“Acm0Ü•±É+ÍñxÍ­ë˜Ýg½úÛ$|¼ò¶VMDø¦; Ò™J¤dnÏy;–mm9ì³³!ƒm“«½ª¹©l~é†l”Žh#`{Ê anÌи•XmFÆqƒuÐëï¥éHeL‘èQffù…4=†STò#[ÒiD›rU¤’#CƒN”( 2EÔxq’Ží!ULQN•l{íÁ4•xÅ‘‡¦6¾Î¹ùò{úÍ5ðá m€_%™ÜhF%Îú¾ŽéwIdz¥qãð:ჹš#J©2IŽð”Â¥ÄS&¢ÿ9¸ ÔŽ°c\ÔgÌY²RU'¿§MŽ lƒ‚~ŽÐEAzùëåÖÌYu«l¥²¶¨Vs”ì8úf{ªIþ&@7Øܨ ×Òؽæí¬˜Ø4”æñ(ù^(‘Ü®^·¨`€ñŠ„"þ"#-˜@üAw I!70Ä1D¥ :{—ß²Uø+€ëöjaÈ5#ýÖQÉÁ^X?-B‚(b©DR@ ¡h)#-¡€Gß!‘D]9aYê9o#C(}k*@:KÁìa6ÓßẞÉB¾ë3Úž{ëÌM‡3C9š$ÏcÉ5#-{ßT'œ¹4áÞhx3>àœï.Y ,Òþ寤ûL4n€×ì>b»ÔJèx&»Ä-²x¢×…ÔäztÓ‰1¹"#-è}¯ÍWm_4pð?7Ó±ãìïü…üÇ¡ì5#cwý^:Z|8CÍO˜(w“Û' ((kÆ’BÕ6#`%ÒˆåÌ2ƒ#Ç©:yè3ÀÎ^Ã,ƒª3–-UŸ·7­0"*ßGôž*‡ìÏh%1'èÙý̘ýá(9€À1$R,}L& øúV…9àéå(ˆªª¤dªEYÓSßü÷øvJòîðô›ò×Ê/û¡"òSc'y ¼ú>LYúlÿ#C‡ÜñðÒýüB$~~©ÒS‰“±óô"}Bžm¸FbPM;ˆ6k!ùä…BH20!T~ÄŠh¡„™8µƒ["„‡‡ÖÜzÖ6I ÛPHÁç#-²ÊJ»Ìâ„1¢…¶xØ'¬r#-Á(»Œ\½bʧɇ16i=µC‰Œ‚Ÿ†Ì±Y,Ý#CÆpÚS'ÛDžÝêCöí?ÝTã]PÁ€Ã=¼—ÊP…?OnR¨úûX{'ù¹{,4ÈP|6¿Í‚ÒèÊkf±¾¾Îò’#­½àÌVûO¨ Jü»’‰¹)a‘SׯîÌ­¸s¢\õÉ´K#CÊ©^Y$8MX!ßÒVB÷GD|LÃmí·¤Ð10è/¶GÛÙšÒR•õ<ÁÀžˆ_wímü•ÄÆ‘­Â.#Cß'¤C,PhÀv.¬ZhÆÅ9´Z0Úd#CjÉú¾K;q±± &K¼Ð5P~Q€`#``Mùû‰÷;¾Ÿ´¬0çUÜûñ¥QœáŸ§ç]kùŒPÁ“ÔêŒÊÉwE'Z˜Ä6¶†ÊøÁm¬#Côä5ëìÇ †"h/#-ë€iV`⧷o»,Hƒ§YÂUê“ìa I$V#`!ÄGmRÈéÀ#`:v G2d¡B;Óù¸Š?¥Óžk[²ÓpCÆ`Œ´m¢vbŠþ=ÓŠ>zPc¨qŠD¿eh,£¿Æz Œ5ðB'>61A5‘¼ûkµq½ä?;¬Ö•4›Ì1„q\«{ždû„‚ M„/»AÛD.žya‰éègAácá"mAóBiH\“øa?œæx;g#ýLMB†#C#Cgoå’]5rhäI#ð’=">ÈöþÛÓ™‹{dL4æòkL1æ¥qHcßðZ[xÊõV´³†"92Á`â`@ÍRŒÔ÷MÕ&áLq}‹* éàc4òÕYu¼RÓÒIòî˜voú³þY¤hhˆ$Û·lÀ‚™%JHÿµ $#`ZË`ªc$£+M9ѬHp!Ø3¶Ù5F+b,CàÇVŠâ°»¤ÒÂ2•+C,AŒ"("©=1ß–Éï.úDÒ›XY¥Þ(cczAH2<‹tp±:ЉVM‘ká5¨‚ƒÂn‹t!œ˜l†W(¬4óƒ[¤ÇÔÐMDAñæF¸Cà!6ß>f¨åfË“‹çu¦ÚTˆÛ:`púg ê[PçêÞƒf¨ª=mò×! ¦šj©†ŽÈê"B’"¦¡¤’b(){h‰“î1°É’Ó:Ë”"oðŒÌM&]+b”'°{ƒ¤)¢v_”w›¤Ð”$¾æ*9Áï )@±°ðÜ8»˜¹€ªäÝŽqX¹°Lt€] Q’Ýl놵ÌUµcZ%æpÒDgf2É¢±²ì`®»ç®ô¼+ð-$ÓHMG m¹‰V‹DúÞî fsAåËÉî]QALF¨4v]9jS²ó›™ÆöUç ˆé#`T¼Àó›BB¼9µBZGICEϳ9!8E Eê8qÈ5T<0¸ã¸fΪø÷CÝ?ŒŸ(ø½øšäˆÒú¹ñg±ª ‡àæ8/*B‘;c¹i'ÍåÆó&¨@õH!õ9¤–…""H$ä¨V9¨ä×.(Lþ>L4ADE=Ù‚ ¢€¢"Š(¨bˆ[m!D5!$QEÎ&©‚˜«ëš¡‚B’`øÕG~[€véÇTÄLvÌ4IÛréÍß#`ìêä`õ%°$“D„œB3:ëa¯@ì×I‰»ôG¢ûXõæR0ÚhኅM3Wççõ$„Ž!°,€†ë\a‡²(5ÄTÀQ¬Lnàqäo[’!ÂhzN.B|­-§KTùï'©|¢*Z. ž½o R¨¡ì-—C2w™’1háŽ<`-Ä8cF÷d]æ(¦üµlL‘4Fm«Ti ]R‘\r›.âÇås©xma†‘;àïsÉà` Èh„èg1§ÍÓo0aT*•A“€6ä>ݘ¢E‚š#-ò=XÔoÚ÷¡ûz$áœi•È)›c"&e¡#-ÍT_JšŒ‡Ë?Fá/ N9PȆ2EC1ó:pðÉ{âÁèqƒ€_–#`#`TqÀô…À@ê¶Nžjk¤¬ !ÃÞ3Àé#C)He-8²lDËnæÆ/|‚ৠÕÑùßÖš*ñSû¿¸þ4~1b6#CåmS¶9ùþnš¦%7pbçƢ颶6dO¥€ËOÈê)uä=€½ P„MAH1(Ñ@R©@%-DHÂÀ$R…#-P- 0C2ƒJC,ÒˆR…JT²A2#-P=½ˆh©ýdjš! JÄDEHl*¿¬N èh#ýrcéßý]#䧖)t8Ùæðïlê@žjÈ «B*€ÐD¢`^7³àjür‰¾É¬#`d×`>ŸÝ¿)Ö B–ƒ@âh hÛ6Æ‚€` B|àR¥M*)Š•1"SqÀå4¾:xýF¬n7Ïæ´{7?Ók•oìÎáŽÏ{ £»/âé…vºÑ†<ÿ]fp6#-î@î$He(ˆJ)™‚dB!Dª(¥@¡"A–db@ \€‰(=vuéËmï¼NlKk‰’§ÐÂî=—#Cÿ#Cõ;.–äc)PL&öUIm‰ªh‘€JCùd4ÿ]1Š(åÇ)·æ8cP–Q¹Ñ‚X1¹”m`ö ÚÐ9RQ„Ö@°xˆÛvêxÔÄÊèFò«ÄÇ“’ž›œ¼ häòåD(bæá‡/¥#¼Á˸»Üv]wŽ5O’ºV‚cr;'HzÂy ‘™{Ìñ “¹Û=¢#çg—J8MMc”hä.ܛ͎ÀujNéhÛ×4‘4R<  š9ˆklTEÀ…R=»$‡‡é¨nrrŻÞ¼4•k#C)̉ØJ›A,QŸãj¢„k ¦•Aõ¿E.T†Œ)è‘ s™§2#CRt‡õMxÁ£u4q¥ú_ªy%#CÆ?’Jƶ"-¬#Ø”HÅ&Ú<Å¡ZÞ{©œÃ•¨ý$aÐI²29èjÛ¹_!#f’#CÚJ$[³Èµ¼ë§f'õª&‰‚i$%=¨ì´426Û‚ªß”ýO~ž&*–1ëLL M-üÇ<ÁÉkÀì|³XöjOI €q~rçÄ>5"þtJ*’ji(ªN€b(©Y‚ ¡¢‚“@ïóáÚš¯ž¢)€²}Œý§ ¿Çüzp“BZü€²KÝ…zêîœKü¸ÁÄEž#>yíU¤P06S±#1󞎎§sÆ){^+è‘GÑibd,ÔÌd˜Â4älHï‘ãhth[¨Ñ¡´\dD¹+I£5L—Vä4fˆ‚$Ð{<4r ôg>'ûuîßÀÿ?Ÿê¿O몸‚¨lÅUM. íËvÛW¸æGca†0Gך]¶#`ϧ¤Ä÷z§ÎdgÌÀÚXªu`~§Þt¯„ëín¤ÏƒQ/œ&û/uKm.”õT©}ìpT?uÙLé’ÝëД÷Íí{ð«uÜ#-©Õ$ýc Í|«LQ…‡–!Hóû$bµÁú•ý07¡F‹h³[I ¶„¡²%#`±—&ý¿Äž#ð¸Tå!Ð>Q¨‡n¹gv&äÔ Š§%C’¬ ™Æ–ø§Ÿ.Y¦’šÊä }_ 2SÎCVD1û,•Ÿ #mߪƒuŽÆÑ#šŠß&ĦT(2DN<°ŽÎå@ï´„—§†$eH'¿Ñ÷–»ž¥aÅJÜÄõ¢GMĪ$…%=}ë’Ç%o)'¤Ê Mz8„%¾ø*ù쾯¯Ö·vÐ{€yË4ý¼<$>¹>aʈšmŸ¿GqÑã™lënÎ¥¥Ñ—áS®^ðOiNÊé#`~dº#-¡"R¦š#`iƒZø†æFtö¯ðñþêá[TEæSM¡4F˨¨õý¬œ„;|"´®‡¸€4Ž‡ ˆCò79ÁÒ¯¢S•)ÙZ–´šSšT4& X©])•ˆtkBßæ|#-¡ní ¹ïƒ¢ƒ¯ùÄ°}_¬ÀXÒ¤"†~9ä%Õ€O8úÂð6:+ï¹ë¨n^\sIÔ[p¸Æ=O¶³é¯œø‰¼ €°08+C0A¤”‘C üÐ }W®DXóØB¨ê‹Ê1E§a’úA8h4`8)B톌—šê’b'.\.' ¼ƒ}÷Æ&Ú#CF6j×!IÄ)ùô%Dô¢fÇÎÀˆs¦`aºE‡ãå¢á[A|Òh! ÍR´€qUû0+zÁËGcK¡k#¢?ÁŽÎƒÎ‡Þ¾ßöŠ{”`?gW0h4*Z’©(°U4–ÃQÈ3ÊÉ6ÎæÁsFÎüyÀì!ºXÑ[NI,msˆµ’¢©;?×æëÝG&ñœÉ§…`³£š)0Ó$OF0BT10MDDÓ Ey°LSLÑ0pД ¥ (TÉ:Ñ¢Š˜è<^ Œª™TKù¥ýÃRKÊÓ_Ȇï°¯,ѧ%“&é˜ÿfkóq<³–U• à³2˜ï½”nªÞ#ÇàcMfÖõ#C51øüûAL’b©ù©ü߻ԓw9œB—í[IŠá/~0¬hÏ¥§a3A&'•:|¾ýÚò¤œ»ì>)U–UraÒ!øÜÏ^vo¦}-ÈO§vÜÜbÌÊk²ßâÂ/ŽôYNÏGxí¨zv7TTk4#`ßL dïòË=i÷SnâÕMe链k/²ãCå3e_8l>[)ÆÙv²*ˆ{Qàß-½ºŽ.ïñÛ–®íÐ:‡Ïµx®ç®7æütKIžË>~P%!f0vÛ¸l²þ-Fý·¦9[ܦ›\_#˜¶\›¹º7ùâëC¤z_-ØÈ’jªŒ (åÍU•m ZosÑTNË™€«kœá2dÙg2=èmŸ8©ô£Ë®I„ASéa%ç å¾”Uß•zË9êœð<+ôìÜ-KýâÙâlšùvÚõ[‡qFpnŽ°`€w‰µJS$æ(q߸¦³]Y_FŒ":.•¼¾\ä;A7½è‹ºÂtpÁŽDÛ³yЖ5J͹ÞqÄÛâ;T#CËšúótƒ{Ú‹&šÌ¶œf_nf²5Æñ×ã{+&ªoMë¼wrR¹XžµCcƒ²ÎðCœÐM,0•^>1ERÖ§js”cÕâ(ݯOM¦KgÒ&Tø¹Í:ÙZÀB¼!ò«©Œ%¼s’åÏ„>p&ìðݹsy.1/¼I-]ó…PnŽçŒ*camÍʸë³ÏQåo^!A¥´]¡‘‰ƒ%Ø<üŠ&÷d:OµÆo$Ýãp‘“FIgyM²äèóWX¬¢”ºÆ¥¢]SÇžº·“{6T˜‡ìå@üÉçA ZddÀ䨓#`Ë’¦mÞ0Lòîê5èõ"ÒwÕææîJ½ Ëì„!Ž$Ê#CB ÌA옕¢p¡Pö1 ]îC”; ç–g_JdyU¶wKPûwŒeÑNÍ{@Jî”&‹†ˆB17Òç.ñဠXw ¨LYǃè9à[flí9}èç¢Èç­àZ“ŠjâkmÖ·}°#tJÚñ-1‡‰!:m’[¬t¶èƒ3s¯E;RPõË⯥l“0·†l)P¦_r£„Û"ÚM囯V´Š@iˆ'¤ÈC»´ÞðäÚªi$SvéÈ´9”BgVa¯,÷Ñ=Ûm©¶D:ò·Œ>üJŽ=w©dj#C§x;I¹Þ UœT ŒRR“Ñܽ«w)߯ÀçVpÓÜí ºk]«7¾‡Va8¡Å1e3m{2i&ýì#eR5Å=2zzSLÄ5*JÃÓ´#`ëËBœwof§J***P”º·t'b]v)ì3"³·ÆÝÌž·KVu‡ff¼¹º0™ëœ“yRvÂ/uíM&Ô6sÕvËú5F#CöÁÍö®m”'‰vŒ¡Õm°må€õɧÉ£NÎp­¸Äʇ¸wpgÓ•ãz×zn݌Ǔ˜u¸ÊDüTÂÔb-é ‰t¶‡áç¬ë5LøO+`§HvN`A 5¾7zélù³ŠÑ”Y˜—g’ßÂyæ·O]²Ñb‚g:|™fy½xâ›xÖŸd¦¥jw‡&Ôw2þQµ¢è[>sPÔŠ«îQŒLÉŽyû—>Ø5ª×)_r$©ä“SÍÉ‚mà34GŽ§©³„û,Å’»ãƱå­,ÉŽ¨‘ ?3¡S¦]3°ë‹IÎ(YÇ#ÛhÙHy‹²­Ï-Ï:»í‚}ò ~Sα•.ÉåÞ÷⮾í`[aØÇ*ørWµzq†óìw÷UP°«]<>É|MnÓ]±30ä ó´«ä_’˜†œ2àCD¶¦Â·¿Ô@è‚û0©RëÖÏ`’bÛšL¦"蘮p«·=·<6úÙPÚ²ÑÎãl¡ßiïÌu¸,—ïÌÆWÃTW£X0è«‚k{Ý…„ðÑö–½²˜«ä¯¹*û¨þÂN›qõ³ãd^ØÅæbQòM"§O´Ánþˆ¼šñ·ONÌâˆUßÉNWͤŽLmËÛR­âöÞùªÜÑž®Yçn§0µ£™•ÚÞ6ûˆqygÈc¯&¼(£É…= „ö½GÇ+Ð}¢˜žìß©#Cï:ú‡_­7( ˆ‘£:ñÒÞg7!ÍÃAŒkÜÓW ³¯.ò>Ñ48ìÔÉ'ÄÌ$/ª…ª¶§àOɶäod1;y@ë, u¥ï‡Ó69Ëe]/<YD5‡Q”QYþüéÄY®þ»E7¢ôéÌéï :CéΧÓÈ#C‹0äƲ Ì#`)#C,—>*¤óc=æ|¾˜ê»ç@ Q,lQÎo#-DVÎœ²[mU5ÛWãQ±Öa3dáö­/ÇNíâø a¹÷Õ¤}J\3XéÝïµ<Ã6²”žïöùËñOp_S³—ŸÅÎ'ŸNû†¦Ø(#.­Ã#`ü8Þ=\'døÙõ‹3ˆ‹"# Ó²€óC¤°iç>x‘¶NŒ§°w‹§DÇ×G¥Á;»1ç¹Î?)¯AWH]Ô=Hîèv#`5:ë#-Ñ ‰ *×Ó´°;-Àø®¨Y#CØÖMÞ”øLÞíÞüb3x±?ÓlŠ&¨aÎÅ#`*f¦¼ðp8X«¤w)nÎk¡²{»UazñJÞ2—Žþ‰#C‡Ðô8¢KØÒ³J'jQÑw@´‚\m¸Šz·P|t‡)Æ1G_yÌ#-fÜ'a ­¡˜0pxçf©~Ê”ÉÂðGlwÊ7ïPš‰/{=âªá‡O?LM!DƒÆM xôàéN³Zñ#CAb|^ôêÎ)©:ª`Àh–Á.CYßaAˆ=¥C)ä¶gÚšÀ=ø*‰ !ÑÀkb1#-#C4$!ƒ h>G²yÒ[ íÞ¹ytôë¯=ÙfÔ,¥Ï¡0âb ’ ù&½¦tè§sO£hxZphÚÙ:¤™#l#-ˆ2Nç5ß=hé´0Y Úi¶!&MM$’V*NFf78W²4~û«ÆÇ®¨ô²Pª‘C˜ÝÕúj_ lcGŠ²˜Ù$“¤}uíÂ7«åL>:ŒØÎB™:D‚qÆôÄUñò“Žý»îq**&s‚ÉÆls>óî­nìpªp²‚,÷½×y²!ý–{]Ž‹¯™”Ý࢖%JÏ5kHµZƒåí·Ñn#`¢b!09º"ØÝZ¥fÍKžÄÅP¡Û™âxµîÁÂÇ3*ZRtòìÈ~Ý£š‡¡Ê+V!Þfî¨D×qÎs62+u™§¶„ÈÛMOˆä¢Îk¤«LUõz#¥DC§sAÛõYÎ.,)ZGr¼ã?¶DBŠc|éT÷LûÌ4§ë6@â;’Þ‡wEÙ“‡}nF¡ÐêÓfTqUÈᦾÐ[ïðÑfá…³s´߈7îÈ#-oHÆ<{o~{>XÊS/S•g«¹*·N£Î™ý±â #©3ày¤Î¹ƒ3Þñ¢„= @Pì°ã¤(j’ªªbê(ãÙÕŽ«X“"TÍ/6™a†èÄÆýt3‚¸Þ5âÀ²Åb,â÷êi<&aø#-‰ŸÔFÈÞ¸nj"·¨[Z6©U£q¯3".=žƒÝí´Ðƒ¨ÊŒinjˆ[h5`‘X(tØ1ïwZh’¡KzƶÂõFYÐñÏ$5\‹${q|§ g°Rï˜2iq$7†e5Uiì!½ ~få•“B.pïTŸ#`©òcÇFw8?Ißo¸çPÔÃœÂ!#`$HXÎ?lÀT#CDcÁ<ºµÔyp#CQã!¨¶N]ˆæŒBc:hª˜bóaô¸x 'Þ‘-»n@ÍçF²ƒ& ónú{¶à‘îLKŠ äjÅ©rŽ<+¯j¡V¶HÌ DhïÐm输ìkhóÒúdcR6sÐø)æ H8#7#ÉO©ˆ ft²]kP"¬Ö@çäÄD»$îè\Ja‹s¶.Z¾!ÞxÇ%ܹ¡èF¼+¥‚Æ$ÁpXN8,E³+”=¢´²Ñ ]EœY°/ø®ðQB4PúäA#-„&.Û¸÷Œ£C“þ6y¦b&âaˆl«øáüÒ?@þo·¯§ŸkçO„Ä’8Ì’K(´æè_@³—ìµ5Xƒ4…0î+ë¹#`AN]ÇÀõ7Ú‰üØí¨Î\áRðÇôxרr·™tvÞÃ{ÆÄ>„Š³ç •þ¦þÓÌã ñ#`§·íÂó“ø®õÊsÛK=_ã×$"âð0áÆ3L¤­ £W¯s»Ù±–ÖI¼º7¦ÍÊæíXì£N'˜¶zFˆóÎz=*ðÃO7%w#r“ÓÙØÔd¢SHc¸7½€(G²*(G á%jt8 C—¼Ÿ!—½Ç˜Ðxñ¯éƒêNJšP>ÛÉ@dqÒ©BŽ‘(Ð# ZO¬\4Å@2|w‰ò½„>UÐœñàÇàD¹à¼âpŽÌƒüÒ¬x: ¸}5bc—ÄÑAO}AâSQ5EÍ9M!TK@²Š§ñÊ”‚”*&H)B(NJ+#3"O×dãüzlÆ£ZulkCbçrò"Š(†5dÄlèî(Å q;¨ìÜáÊ›…cœwòS×bƒl²gzTêOLŒ3h «Ò5)Š,AŠ4ê1€ýùç6H<ÎIZ^­5]çyñ»ÌƒHÛÿ‡\e33z橆ֈZZ)tãgM»ÄäPÁ\ï¯bÁ´†ÝN#`Ž0ËYhu _2§é媋Ìpò>“s<ÎH’#C'•Rð|îZ#C¶„¶ÕJ9Å#^»Î&ƒâ1\Ú ‰†"SSq$lš¶ÖÆÕ>ãĤì˜NÊ1D8OWëÀØ ÀAIæ5T—s—#<Λ Â0 ¸GŒzÖ<¼7hÍB°‡z{ãª+Ñ‘ò#`$H¸G†Iš&$刀–8DAc¼âŒ!Α ˆ|•Ü¼ã¾\9r¢ ¦»¸KÈ/X\C¨tœ­®ðÉÙ±ž–‚¶†ÂFÚ{ëš×âòæ#-Áˆ"Š‹ÃáÌ3¹‘:”_Ý?Ìëlé]©ŒsP8)ew‚q˜‘Óœ‰4ÆrgÏ&sËÁ¢õÐçNæ`ˆ!†Iw_\àÛÁÈIÚÖ ±pÑ(Æ¡»Ž˜pS`¼ž„ã)Èà7pf.–d“9'CÅ:­J.cûx¼=‡h#o¡ ¸ÿ|ÚU&Ó#`˜u«ï{F"¤§85ETÅ£5h‘¸À$‘¶œ+UØwäºÐ ßiÖ¬°Új(%œQ1%!07JHÝ…#C§#l,´SbŠ{`¹“‡j‚™wp¨#Š4ôÈ41ŒjÖflsq7qÓµU¬Œ[sµöüg½qHhkDúŒ%ép(ûä5^G‡#C­E‹J\úùaëÜžsC\ iÇc…T7l=Õ¶0í¹Ìð.Æ’y±ArjX=Ç!ŽbŠ‚#1ÆÉÓ™š"ˆ%åJ»È;%\¼ç5QÂÇX1"‹@@¨m¾ºŒjên¨áı‘•'”Š¶ ¤ŽAHy»Rïóà6"ã./GSQ8ÁõîèvXÓnH({|v.|32Œ4Äï'ÚþÇCæù‘>ÉåùlÌŠ‚’ˆ’¹“Š¨Á4.]Ñ,9¤ú¢³E=Ù껵"' ·Oš‚‘vÀDJIDdL¡HhN…¥¨–w÷!+A˜©}É”§h…Ýl6n£t®ÆýÓwÎyУÈ.¸F“\" ̎Ÿ Û¬•ÄYa,•² U‘ŽØRrJ_™xôWôô݆$|ÿV!=m'ϤºÂ‡0 )Šè`càêCâÄ6 *#`'F%UTC¶«Ón„Ä?i•_4™v7(#C꘧à¨(]exE»­s“æ5Ó}Ç=¸U}rùô_Piêã*}ž•õ#§Ú"Ø#-·óõ6÷`f%D”ÇrZM`dè®!#`öšŒ¢¼´›_›ÏÉÃ#`1æ,ñ«67‘˜^¹Ÿ[|˜C ±Ýв{×r] ?¥?çÀ1Šƒ ³Vtcg!gÁü=&õ&ee’ÊYbm™U+Œ%Êô÷Îos·8ƒFCQþµ#-¨Š”Èʪڊ5¿î ž¶"ß¹’w¡KðY=·¡€ô”Ÿ5Á÷”ŽyŸ.º è{ÃŒr¨ç˜&õ®uÂb1ÌfÈ8ÛëRXÂ! §þÜàBô=þ™äŒ:Èv]ykÂò¯n9I™„CQCãV‡Ñt(]P * aacf6‘în(m¿ €¢•¢ ©É¡i¢Ž’ŽC©¨q.ÙÜœç8›…mÙx@͘BA‚JA‰­°ð…4…ˆ5$@ºÄÄ"s.&#`#-æhÓ"©ØÄA‹#-ÆΆ€¡ ÖG`ËùýG¾a¡~<ÿÉ+I3#-Mè<‰É%=¼=*Þ`Ï£Kúªß¹ßÊìÜEX¥! 13PMC5ADE€0‘#`Ј̣BPSBUUT’ÒAMST„$ÔJ´«5$°´-P‘Pˆ“ÊDŒCK$K!…4ÔÁE5DÅ´T’DÐRAHE$TL4•I)E-AÑ!TU0#-A D²DÐSÊ H Ò~ÇsÆNÃÜvi®?F‡Ñ$}ƒÄúÓÖ‡ÚýÆÂ|Oª'ÛìNªðBìqöhiÒn¾~ó&û{;»±?MõŠ·fáŒ[æ9¿f#`k¶D‚Íà%û»¦œÀâtE׉‚ ~%C$ùÉ’}Q’¡Å/¨í3ˆt#`< ™PË‹¨ÈŸE˜‚™üs¹CÔ?t*¥„…fņã7²#w8@å"_¯GÖfÉ IÔéÜæf€¥Ž}ÜtsªÐôÖe½g×°/¬Iûg³éÅ+Åá‚Sð#`‰ŸžÒÉ%3i´Û$#`…zÖˆAUÀßîƒí Î7TQC)ª»?zdu+Q3ƸùógÏFÄò£µ—KÕ˜`²/èee‹6þ?‘б¨BF QJb$È3‡xP]·É—uÇĘb""…¡q‚]àBòoQ™XÄK›…C¥À¨Aï6’Þ>Sðð÷ü~0D.ÇÏ §½=ºÓË2s(uù$~=6ï©ë³R±D„m0 þÖpçPPÒL)0E $HÄ£øl,dÚˆ)†%¦ˆ@˜‚"Ô˜ÃØ•¶LT&5Б*PS#JT…4LAM4P“4Ð’E1!IAM¶’ŒŠ Z)¿1œŒ)0*iD° ?¨0rBš£ÛƒË¨@È(†#`bh*‰#`¨)("GwêN#`ººîUFÔÉU•U5(UKCIÄ/ž$+A,€„h nÙ³V\_¦Ñëk͉¡ä<·9®A±¶#C9öÉæ©~Ññ^o« |Cim˜ŠÓhN^ÀæB&h“@ê&.SÑçKú08Ùø~kßßc}8‘ŠULoæHÏVÏG‘¤õ9Ø…e³rãÒ™kÑ¡¬ÈÞ¤Y‘-8á˜Ú6†a-Ìc^XoL“L$¦“ĈB‚Jñ]A‡»[le5aUÞ\`lxQ»J¡Æ¶ocÔh„ÁãÂá+ŒÜWýÊ´Q¿XFi1íäçÎ<äó†,Fäï0ëZºpz£¤Ʊ/8o0ÌüJ<ƒÈV€m Û.#-ÆX±& ¿FÑ\XQÍœUY¶Ã‰¡ªó˜ä”BØÓÛ>‹°IØÄ$KN«gõúõÏ<]cOËÙóóį à°}OŒGªï)/(²¸ãèˆÛ€ÆŒöÑ 0”kôj:êFûú#rrÂ~ÙÉ)Rd¦ÑMs<àž§æ';Xˆ"Û.‡=Dà%…U+ðüÏ—£’ñá#`s¶"­s }ÒoTTØy“=¢€~g›`­>¤ÛD¸xî¦!»²à2Àì‚–¡º»œ6 S…“øs>mù}U«ŽY&9¹ª€½©ÕÁtO²óÎêÝý×ÀN‹½Ñ0 ÁËh©_BÁŽG+$Ñ(ú±òÐñh-fJ€{ÿ"ó $;˜öŒŒ›×ϸIIÑÛŸÝOÕy%Ø&¯àÝâåêþn– ZP=O§=~óÚ ‰¦Ñý¥6 úìàáöT*BŠP”&šd)Sd£Œºe±âîÑË2œs'zñÄ÷ÊÆÆt`·ð¢'ñÀô@}ßMOÍé4A{”Âü+Þsïâu^I¢X/*?5ûP_âú)Z~¬§Ï䋨œŽÙw5êžï<|¿b;!PÐZ­B¸Ïf#`yÕ5HužK#CB™††ÜÛ¾”„7Õ`-\4¡ÏSîvGo•Q`—#C´ÎPÃÉRÛ^_>ˆË>DdèKŒ~}6"(ŒƒTe»QG!j" CþÔ*dŒhM/Ê@î×ÉsÕìOŠ§—Žªš0Ç 1ËÉßôy@4÷Ãç `‘_Õ5#ãìÇi¾„ô¦žPÖ#`ö*<›øŸ]BCƒÇ}õÖxïð©UÜ“¾ù©˜¢Yb¢&f•iDF0;†Tž“]{‹èé'ý=²öîn·3µ~¾ºˆz\zˆÉÀaü¤£PÄìúî#C–;MÍÇÉ+ᕃ•ßÌ3Ö"uHé ?µZ7ó¬9w©¦ ì@æÃð#ˆª8‡´”}²ž€ ‰Ç“Áýÿ2¿ÃñøªR Ǽ’ €" ± ‡#—J±8Īa“–X "ª~ÏšRð§n)„®ÅïÙp)2p»³ò£¸‡Õ ®òuÕ{£#C#- §ncã)#PЬD•QÀáÏéî5ãÆ ÀpÕÐ~¹w•’ȘðÇ û3¨Tü{Ä#`¤ãœoÜ¿M\²kÜÏ.A¥ø–c®IC8œ =©É¬Ãä¼öÍ`¿›š ˆªh¦“…Á™³‘Ž„¼:}›ýéîõéM#Cöëñ“ ÐÑËØ‹3WPý#`›ÇèÀîîÈ‚DÉñ‹Œ]£éSyœbê^nZåš»,c¬çeʵLOÇ3”©µ™¯ÔípØmm9©‡{Û–ÏXXr^&ã&ÓmdغÃ×ê³fN ‹‰—’)D\2'#-Ê뤜HÞdê÷šéÆ°w!¾Ú•íIÛ0Œ;í‹´Ò”½áÚxæbª8™Z§zŽ"î]ÚlQ$SŽŠ\"\IҸŔÂÍÕÕ"ð=‰,îØ‹ÅÆSO›¶…x}6qcfÜ­éß~üï—ÔJN§™{[ãá°L,FsÄŒ[>u>nr±®53¨lA Ì€b²n2mÅ0në³ïѸâçRÒ&¾paé€V»[ìpû;禮mI“TŠr·C_óñÔ_.;ló¾ØO‚°Ï6Ë™~tá¤^œhQD#Cép9Ƕ Ôí-8QŠ·-Ä:Z˜Ô_¯¦Chç‘‚ñ{•Õ™l"“›4!„D"BŒZiúuÁ¶¼È®œäàÙWdPX˜Š„uˆ¹0,Å0íâ"#-š˜%â .ÊtÆ4ó0‡O;¶- z"”äÌœŽ‘Ü#`Ü9v-#C˜Cøæ{¿}ðe¢eµ,ì8‚zÎÄá3$“¬3»FJ»Pïvì—ðå'bï5!VB´å0™:iª¦CN ~ì7Ók©/s²cQ¯C¨ö/”)¹pÙC H!ó>==E¥R{àî>,Ÿê@m¾ì×°íU`vir5Sã±Y¡1ÿ|ìCwÞíšÙ›qŸœ,Ùšánׂ3ˆH‹Œâ_ij‹0©NvøDl–]½7ÅPg|—nÐj4”©“'0ÚF5å/Ö[xqÔŒà£72†Ú§}Û½ÞC`ÆIÖãF‡FÞ"â¬Äº09ÏMñ»X×SœÄ/‚² Tb¢²=ÕB"‘UŽ¯;çÄ×M³^ŒË'M(Ì-ßgš»¿áñz¹ÞœÍ=êJVw[[Në,0î#R9’S[[Ë`!ÝE¶‹#`±m2îÃf“’^IlîLêb4ÓØÚÎO+PËc\8¸ëˆÄÆaÿHÑØ»q®g^Œ×4w‡œ]ZF‘ Óc¦U|òkwØFÛˆ‘ÜÛ«ž#C÷$ff1‹Vó„e³!åÚH( “sËÍXÖÙÞ¨I(ØÙ111#-´ÙÓ# n#F°†åÓÐò¥Iü=B‚x)CËn‰„òŒoX1³˜ÉCø²!f"ݤ)Ñ ÃB<·WÌé«~–x<¢Be¶ä]TËôéô`šÆ…{Ãó&¢›…„0Zfÿð2Y‚µÎ·FøÚ‘Ä°; ØâMóºÓ>r×´ÀÁ€]-Ý#`!q­o3œqÙ-knÞTlkD‹&ÎÅí¯U#¾cDÖ“µÑ»GügCÈA*ÓNîZ¡–Û&¯%×å½è²º«ß9Ùd;tÉï®ÂLbQ£SëO:;'æ¯f™Z%ÛuÖÚÞKPF³›>Ð;ªájFAEU#C2o1®,¼I¶ M`&”Ö˜Ì<1»{ž¦Ô̹Fu¤AA©)ÇUŽ$ ¬°Èͧ ჩ’D“/cO&E Ö`µ´2囿•õÎZ§—”s³:KihÀ»Ã.T3 •Æ#CYte¹ Æâ2°)‡'‡ Zñgãlé’t:psÍBQ§"(–”ª=Èé.á @Òˆ[(CKªˆwwL3eT]LGŽ+9ZÊÐåmqy9ç{~9wÜ€à0Ñ£‰D"G@Vi¤0åíÞ„§&Tø}˜SZmÁÉœL•F-ëSEK+CÂk~²ˆÂÙ«IÅ•/ ½˜J„ÈðÄÍá]œ¬o~ãYšiV¬A..gZF«þ–šÛÓ37@2‰C“qÌrÝ–u¼~)ßiŽu¼ã[ÐÉçÄ ghßg~.J56ÛffDK*G/uŠÃB!Õ1@žhkL“TÎ7ÒjZ!×BÑç7HòÓ²¶A™¼Ä̘­W¤“Z(Ƴ2‰·tCÊÇƦE¦ºÖ¦áèµ1[U³ZeAf‰*vì£=oÁÞ$áܽ‘¿Ì°ôf&©9a…x’±Ç2–5v¾KHS¤ûç>Cã¸ÕÞÐí²¤N(!‹æ ª¸Øj2F‹vîîÄ$¥“xÚgFÖ̱®0vl`ÂkLVB–“‰'UH(ÄV+€äd­ÂyA¾ÚM°+‹[i2àu‹’-4Cr¶Æñ…‚NµM±‘¨1:Ì…µL¦@§-‚¦™Æfc/ÊìÕ35‘Îÿ ¿_#C¨×K¶ã˜gæ Öx¬œÅïâbÚÀ]¼¸½4ÃìžKÊh$Á²)¨/1‘ïÒ›”ÐÈ@ ¼®ì[ÉÌPh¬Èµu%”ï—O:IÒÈèIôž¢õ¼æ à@R,rGÔÞ=Àé¡Æ#-QˆáˆXÆÄihÚ9»™£T#`Ê´že+b~Û¬3^tƱó¨÷&–ƒytø–îs$£ =L/’ªwzÆÑ.¥WT#Cˆí1‚? æcfA>Q„6P­AÓOîíVsøm.õòz2o¬d Uñ~âµ[oRØRX¢ä‘îû15›„E4'Îó,y+‹™6BM#ÒËÌ&´K¨ 3ªÆêÍØ¡aÉÕyÖD,Κ㦸GÓp·£Ã$ÊíºpeX8ïLH”tœY½PpÛ;ôÉ–yÏÃËaC u~ÛF’4Dõ`x›ÚúÎög[»@8bÜFû^ Y0üKøætÚ •ëEoÖƯTãŒâ. žF¡4ŸÄÃ6:¬$Ç|A)^;aÂïÅ^)îáßž”cTÞuÕéZ ŠÕóÞÄ$HH kh<Á“#C‡ÕŽ:|a¹„[#`îÖFÊR)Š¤”*Š¾>ëÎàCÐM5#CñPɳ¡Ó¾m%¸þ(~ûKkeÊ:(÷ö÷Å_‡{ÞÔÌM#Ø–l6*´*‚t#Cx°i©‘ä¡’bþÉÞ†,Ʋ¥–JZvcÏõñè]ጹ³“Ã1³áç’{]u¬Å=¥!¦O‡nY™ìFK¹æØÂI—@×k™g42Â|¹£2Âá6#`xÚ&m Á3QÚ_k‡;ýiïzW2Vmp~PÀmt —Ë]³ 3a4¥¹pÖêX㺎œóƒ«'¶(©’ Ia—“+§‹âïè%z.¶·Z¾{Ö:8ŽxRd%·¦Mô÷N¼éu³ª2ËÆâêË塸Øð죚,ÕòWíŒiX/wdä±L›3M¼TK' kD!2kj:)I;Òm{Ù ±Ö¯D¶È÷OµÌ3a#--”Ç+j§WaX©" ‘¤&Èm<¦*š#•½;á†.Dsô¸Üès_}ƒ!1i”& §K(œŒŽã”ÄH0T6“0k„‡œZk%š¸oX4ï‚-¥Ãè÷ç1pÎ×#-ÔŽJò*¨GôRTwrBæ½7¥·èæi1ñ´Q&-͇Þ‡‰ö¾×õxgä¤E “´* å`™¤^¬#`5œsx42‡=úÄhgaÄ#`¦&·ìdÕ(Úrã)È.mb(ùàÇ I5&´–‹f¿o”ÞùY—•å˳NaÍÉÒy)Ìriл°ß·ª¦Ò£Ç­ƒÕà±£!¹©Ôu†.†£0A"Rr=‡¿ŒâjÅm}ÿâò#-‚O—rtÜ`Kš`½¼‡F‹Ð8<ô¨ªrÔâñŒØ¢ÒTªÈÓq™P(mÉŠo1b*:dær#C˜àÁ˜,m±A앺èê¤) Ez )ÐI•¤zàÅI~=㉪®:!’´)dA±A4×d:ÔvaØC!À¹#bè•Aq½™‚F™¯FC‹)òO~/«}L|¶9Ö)ºfâ&æB(nNRB#-Hb`›l›¼Ë­º:RÓ’.ΰ`3ò&^ô)z¯$Ë01ò;ï¥$`Ñ„GxCƒž{óÚHÙÁ‚h\†@aÆ#`êN0‡A&0ÀNG@S½ÏŽN [èIyÃÃHyèHq †-õ°(Û…dwjÕ\Õ+îÉ“)ÏmÜùýâC^6Ȧ!#C–„¥‰Soˆb¿Ústð¹sòÈÑ¢æAËsÛ#C`ë_ƒÓz…åŽH#CDHc=g,O¬î#`àƒ„w'g3ow¯âf`j<ßînUCfib@Ó#-ÔA@å#Cy¡Ò#Cs¿LQX–)HæÞ(‚ÐáÈ#-I€…ÝÁùŠ-?¿¶¨$‘ëÛïp…ûff0ô#`ÉðiÏåwY¼÷p|ÁÛXÚéÇ©Bÿ?êr0顉‹þ‰i ¤àáüŘçè"”‚†áQHnEÊb>(u?oKÁ€vsj#-±ÚhêS…ú@!œ¤N*ÓEÜJÆ"P¾;öÔÔë4= |e4sáëñû 8ÉhKRÊÉÃæÌ%þ¹]ú}%iíÄÆLy?5ÛGc8…#`®Ð½ÝÙá d´¯”°Y œžp§çÓìÐÐÌ@9×Z¶<üU¡`Ôp!ñéaŒŽ±}µ(zjR0öUǽ‘¼:t±·qÒ¦)¹Ûôª‹L!H#A®ƒ0°­¶Ý©Gßd˜0‡¢ S—gŒw²¨t£¡rH>L§F<€¬?»AÆ#` ˆ+5õôÑZz˱"4±¤ œI´«èG$ø¢O:†„iZ¿F£íÅbá2!ÝŒŒ²(¦Ò#C ï}@"}(Ÿl'Ò}Züeø$ HóÂls¯²u?bDöi¨HI컜¸°h©Û"š²GYC™ª=5MóSÃÀ·ã!Þ«ö(@œ"¢áY‰Î±mªL’9q&ÎB#-héì/p¹» Â>2e°ºpFœ`µŒm/#CÍç8OV#›TQܘÝÜ-[T÷»Ý(b¹Œ±H\6y¹û¦{M rWGžs‘w®j¨‹¸Ö­MÌçjvpRÍÖÛ8:Æ&9HxU•äçR(lnn†f*ˆ‰¢#`b;nVc»ñÙ*)é6ÁÛ¡±»lm2M¢­4c !¸PÝlª9±ŒXŶ*ép8 ¸ÈIN“™s'I…\>œ ¨"E:."8BÐÿ¥‡'”é¢c¬v0oŒ5&¤±bÃ<ÛY8r#`«vM—ŒE^5„ÛUò8åenAÝåO,T# Ù>xš‹C9Éiˣ鄢srnÈßeYÖI’Eöknòúðœ€ß 9»}.†>effÞKT#`ÒŒÍæk­JfNxW[´n  ¶‰IÓ”˜5#8FlMm 3#CéÔrnk¡Ã0¡i~Ò 0Ôô]‡óúã=ÎRá^¦h/Qˆîœš˜÷Hcø‘^ÿMWGP…$ð‘’L’àz’3ѵí^w®¡ÇEm™©%ƒm4394W‡)„iäÐѺÓ9Þ–ÍLòÄhhÛFôæ…ÇnEÁŪ˜ÌÑȤ£UcDN‘¨i‘ƒ„=ØJôõ†na®i¼D[ШÐÑX#m¦´ì*šÄh‰„7ÊÄ‚da)ͪžg— Š†Ké=C²A@öy Ržƒ2ýC‚å]öóÖ;×êô§nñleò•2ÑÝì˜|ˆØ5éžrØ6FרóRFÙ‚‡Néc˲ð(‰"žÎ”¹gM±S;žstŽòÓ©ô4‡rM˜U,öÒµJ&¬ÚÜ„-õ‹­ágÄ{‰CjIù¬Ð¢0«æ¸r2œì[CsIµÔÆ1¶áa…gœ™'¸j·PRJèî¸sž—zF~¾~×7~\äRíjòSŒ†N¶Óîv×66žî˜»jq>ÙÎ([»¸´í<ƒÍMHF!(iHCÊž‘kŠ ÕŠüã:Gˆ <ôßÇcÜô%$½x"ð8vÍ}:‰þy55¼º¡ÞÔ@1•‡÷5k32±ª³“ì¢ÔE#CuÑ”ØÑ0ákw„tÝÃ…ȇZTiÃkn±¨Fæá•4”0S5~Ópfƒ2Œ…Ì]xöì` zD2¡#Cƒõ@[«Ù#Cü8’¡Ä‘z AÛËlº fîÝ—ÂNçË¢LÀlCI@Z"ä#-eBˆ‚ A&šˆû%3iH"h‰@<%…ë*QЇHh'.„¯ JÄ ¢H ’¬ 'Áaäu„D~çÙ•Ò  )6Ê H RJ)­´è#-H/ ăB%(Ð ÈJ?·#`®#`:FÐ B÷Š¤öJí#øý¯«¹ˆärÖƒ³£lÐõÉÒ˜u…⧂üà8øÓ”€=éŸ6±¶£ÄGç#ðJ'Ù#-i9JéÓŸ×Ë€O˜Ø9Oy@"!*¥²z.Q÷|WqËøJê<1UB‰¶‰FÝ=¶‚øÀú„<`#`#`iP¤)dˆ#`#-¢ G¿}_#C_§¿êö³ •×ñìà/OÇŠ†Ò;ºâºžØÐè|váÑ¥@úûúíÚêÂÓ¦(dË(@J#@!LJ7½÷>‹Þí…S^/‘áËB½ž‹ÑçG&aŒÄ’C‰»yŒÎÇ¿4»Î“pÀw`ûÆ]ÀÛa]×qÚ}4/1‘>àšuQNzÞ.­ËT®šL€¬ª{Xªg"dI£9"ŸV&&]´ÃM¼iÙ˜ˆ3W[ÍkDÖ·W*æ(¿½Ø ÚbÒNP‘£”rÍÔ|ZDés7š.‚6ì’¤VÁ¦Gk‰À´mÊ™-)Vž1ã†8#`;)QÎ[AÈy°Òp#`¢¨»°¦¹ƒ7qéƒOIŠUÇX6VVRš†çy¨†ˆ.4G"æ$ç5!g*k»rÄmb7-G7H92ÚãVºÈfL²ŒÆcIË^–à“v6êlÆ#Á0I‚®±[faŒŒ™#`Æa#lŽ< r;²K2EV<`cÉ ƒxˆ`J±pDé=S¤¡Â#`:\8™1/s&‚’hK±Ì#`Jj¡—%)«¸°OŽkj9lTé):pÜëgZ¶Å‹¡‹niÑÁ6é¹Æ#`)MyÍ#C#-Òï#-ZäòäDä(b d˜Q±Á&D—ca@;Àê€Ã”WR«F)Ç—¸ï¹àY;½‡"\ŠôI õG-*³.JšÓn«³Ll¦4|ã˜@ž>†O×å°©®‰ÆWÃç=ëä*&6?å‘Ó{ç˜iÍ…\°U- Ø@½x0ÃÇÄ=Þ¿zÒ8~ôh4ýKT1 Rð4Ò1U%E$A55D\¥ÆP¨—²ÉCÄI)¡)Q?\¦=B¢ S#C#`”‹@ºA¤i’&µ¤h¤#`T¡é1ìä¿t8…ö˜(&À#`qÑ`cÅ yŒìàqê¬Èãû¹#CÆÌÐ#-(#`8¨R‚&Q‰òÌJ#C,Ã`æ®k®~Û¡Ï8¼S@2"@Èç&!F‘úÿ£ÏžË³½.{óŽÐ~¿ßÁAÊzyRy¢‡ç`ú*j‘´ñ»“¬L¿’å æFñî_†¯­èQç6†æIDå\s+/6RæŸ×ñ:ñ:[Ññè¡·×ÇSLlgUà ղ!KÇÝ‹^µR2™_y{/}‚û¦0jÓ¼¨m l˛ٕìßymr|Æî@LR™ ƒ£Þ@D´‚Ð(rLQ•ß·Ôt-)Á"ZƒY@iˆ¤& I"F§°0ªXC~7nW‹Ba:—BÕ|<±ßøCÔu€z¤‡Ò@–#-<‘7öê± ˆS>ã?1!¡%p±+#-± 01!BC0ÐM4¥ ’U#-+H³D)HCA@@´$(EáÃÆ‚ \D`Ö©e0Nî‡}ÞÀ”Þ#CÝÒ$9{4TÐ{W´ïð¸ƒï!ä>£ˆ.rÔsõB‘£Ö=wÞ…åu6ÛËÓéQ9Œª0¥Òx„ŸOAòëªðx"v•HJR-P‰¢{s•Æ=«·GO¼çñ-¼öG¢GØj~§´S>=¾Ð#Cq'‰8¡wùèH)ôˆüú_¯Pà@ªz„6ÞƒÞ!õçTH˜õ3×Zų£uÛøê}Fr\Öð©\Êø2Tõ#¼Ï3nîqŠJ¹[Ý2F>ãiS5»ýÆõªÕ”´"©í#`¨C겉ÔgJ[­&jA)<¨ êä:09F2Ųu‡²Dôèm×Ëé 8)'µ†Ò"¦%âLGAŒl’Ða2Š¢éJŠ‰r\Î`‚nñ+Å<#Õ*‰L½¥ÂŠPM‘´q¾²Í³±‡•Y¦ÝŸÂÎtWÒHäc›{cyr篗b*W “ìž*žm%‚íArÅÍ^#(f+@·ÅxçRz#C>TÔ}ö&¨¨rðìŽaÙSËhyÝQ–°[ËLŽ6âcÀ’#C·e2o3S‰ôÃtŽA4ÐI#(‘JÌŽ#`ø‹aõš|,X'ÓÌ0ü<©Æ2d¬Õ^içˆvXá™ ¥‚3 @`R‚dˆ­)Ñ¥Œ-~‡}ºb2n€œ<3Y…rZQšÞgV”»¾]ùè×âß} F`(Á6$ 0ðÊð‘'’ˆ˜Tƒ¢ŽŒÄ°t9iÐíâcöö8SÔí_0<Ë¡3=úM ¬Dô9Ìñ6hzG?@Ëa„YÄDÏÙ/ö°ÀLÃ]4Ÿ ‡&Ƥ)Ù#C¯Aø`Å…ObR ¸”0#`ž¿Šx£¢hªúƒÒ{=“·Šùa§óN€Òºc$­#-1¼£IÂüH ²ª»+0A2‚Ñ+2mTœ9'9æE¡óÓ0DR%D‘G¿¸<ï ä¾Ê¦ÃÁ“†¢x.Ò&tì`ˆi‚ª(vJЈPÁ@Kà2iˆ¹*¾LBŽò$HŠàœÞø0GC¸Dw}²ó#-û~©š|`ñ=ˆ¼áùÔULAI!BÑ8íÍúë­UuÛ­«JAÍdJíF™ ¥ð-õƒ¸%1ÆÑ·V‚ÜI`¡qÌ~opóC“¨„®¡û€â»èy„.ૼ‰†ª‘#`Œ`$tQ<5ºƒ3´qHÇÄ4ΤÇXÃßãö"ù#CRE*Ц˜–Æ„…§í?ˆöiÑû>¿¿ÙÉùô?˜0–4*#-bªvüS£Ð¹€Ùô¤y§G@€ôz-å{ÙOùœéÈã€ó=?†7‘ðÛ¶@GÌ ‚íôf§¬ÿ„ÑsÅ8!?áà±_ än„-øœÏ)Š¹º‰àœƒ€ܲЕHxŸîƒÚ‚¡© H#-í–€0è¢zKŠJt…8Q†jê±T/«³¨#- n½Ý#MTÄE£SMóƈ)*‚¸b#C¢‹`ÄÄCL³lO1ÞeÐuK˜41f¤ ˜9Eƒª•™ˆÄ<ÔÐ:F)Â+Jrìô¹âç…'v(R#CQIKPI!AAKDz'i5Ti‚ˆ¥hyi)4š‰‰(#C±ÉqtI4…‰À6AKýðl|@#CzÛj&ÌDMµEêGøGˆ2ÒÊœœSùLíáèûÎÈìø|mÑ4ZûÆϧ¿ùŒO¡MXn¨þ«^ “0þé[þZÓz¡d½“#-r /Q<‚y  ©™#`D(J‘bD#`ˆEd$a˜  A%¡hhMUS»σ#-xJÇÒ4PÑRÞ¢*¾c8èæǹzû ¤¢‚ ¤¡ b¥ h˜¤)I"‘"Jh¢¨ †˜tD£‘)_<úP?9Î()‚ˆ’‚X¦#`V€¢†ˆi(‚†"¨Ie"#-™(H˜ßÛ¦‰RCI팔„ŸÙ$9! iþ£™M‹þ’¤t6G½芥 WQ! <16Gæeuï%8wAÕ!,,5ADžÏtŒèZ¤8Ý$’"€‚’ZV¡‘(¥¥¡‚"f  ©JI*#¯9ɈT€• „ªKÛíÑTÖÿT ªjª»ìz@@êKû?§èÿ7PçùïÈý_ßÜ÷ìtŸ —4,ðù¡Ö6û_Ôâëï“sˆÃ\×è5.÷.‚†Ù@Ôr…ƒ¾Aá'-/R¬žÜ¤ƒWÕ1 Æ¡¤¶ê”*#iö ’îð9éèë(;;¼›MäO ¡ï>µõžÄð‡íü¥‰cd=l=Š£N”ÒþÕÏôÓ†v á ÉDd’4zºÑênуM¤ü3U/~¢ùäGRBR#-çÁ0˜H$n€#CÞœôôú5 5Ó Š‰M¸‡7ÀW¡Ñ4#Cõ åÄ@Ð~z*!J@8¢>•úA !L‚PSB˜R誩ëéÛ¥\li Ñ“ ü1¤ìäBé'Ž9ƒ‰pɪ*‹lµ'1™&”B#-’¤†•"Éš<8’Ñ&€ä+ƒ˜Aƒã’Rº†ßÛZ(¨ûÐ#-ò#-àÝ4#-Ä‚vöeDˆP‰¢¬½Órz ’ç¼`ÂŒ](ù%R#-Py°·!S¤=£Âl‡ÔÉæêõ§¹¾žÿkyãO¸ZV šýüèûmäwГ؜¸"#`©\cŠúIbÑÆkwð€{Wy:ÈrÚÕ±ŸMÎ'÷ŒrÂ2CM(Pi‰}Î]!uE¥¡+¬\ðw8ò\ÓT'¬†¨í&!¹o݆E§Í©JüÐörÁN õ´¾æîÈ}Áâ#C‹ ‡Áá@rgx<.îì"ï ä'ÌœƒGŒ«ç½Ïri‡ÔÝþèŒMQùŒ2ø°2"I è±`*Ÿ«5>¿­ˆ(ç¡Ð<ûÁ!˜¦’ˆ…#`FªZú–2CUNœ˜#,! ÎŒÓ’¤Ò&ÖÆE´Dcm:­¦¦ÞùŠ.ZÉR´‘JD@PBÒÒQIT4”‹HFØ•j JJ¦? ®§2!d ¡o÷ qò÷röÒ0õ>‘}ô;F!ŽTÄgê«×œûTìR€.éÅÈ£û!ÂXfNâUî8AÙN0Äi‘g»$dÌÀ‹Æì$àƘ‚”;ÄýùãÚ‚´ÀGœÎ¹79t92ÈI×!Ý9”&fóâþ„5æ„&xa5l~hcc -‘¢#-âJ†¢ŽeÁ#`ðä}7^žzvC÷° ˆ)âT! #B"jK4Òë„2((Z *#-¡¢!Q¤¦IFI×àK;{UëÚÐyHž€ÈO§“#óò«]™âng‡Þ|rêÁü“ …*&’¹ÒcÙE„´c`ª@%3èÁDÆÖzô…93T#`³îC,F(ª\¾2bÓ]uÉ4#CXª þ4°åUФŒù‘ñÙyyúgÎ-bç óö3˜? Ÿ¼=–±~]‚‡I­œ5ÕO§;eÉ,ÆöÐãªÊ…ÝAI ˆ®ñ*6ÑïSÂ×û_Ƀ¤@˜}G˜`-*t48*i¡`!Ár*$0\‰d‡`¶#C$åL#C) §)Nq†g30#C9BÙ#CQ DÓ#`Ö6R#`ˆ”6Á¶D‰H (’$€ˆŒ†`Û1èã®#"ƒHF1ýbð'”Ô±çœäP!‡C ˜Là9È G`Á‰ÒÐSʲ°Hm—dΰÓ"Æ$ÄI¤0ZŽãå.“ã#-wg†ÁH #C0_A4HžÅŸy­ˆì4R*Â?(ýŒq @áÙ0}®±¶¿§¡¹ášº$“>Š=€äCoìü®\€ =ê—é©–Ì8pϨ.œšC§‘¥\Ém µîÅ#`ð•j©A¡ƒG,9!²kÜv¸j4“6âŽ!•^{ÃIâ™XüþÊp†Íß™‡#C¦KøT¸[Å™ò#-û3éèD(Ab«Å\f·*’d©œÕµàÊð†0™Ž~ç}Hï7Cy OÛpê’«ÔÑJ ˜’pÑ Â(¤#Õõ“Æu#¤&óæÄC‚W#ÏHd°-nÆJl_Œ t«ê#C&íS”™úP8†‡PP/`QEŠEK¢ªƒÎd˜/a?–9°¿¹#-)ù›ü>›`r)Š=Ð9Œ1êh™û!{}#-Y­Cýóýy¶/'ñ“×õ2qzu1ZO†¦ÈðÑüÆ®™œFÁûà“m%Fœn\ˇ!GÙeÔ`HC43~r'& ç6[+Dx»¦:áº}*z”æ¨=ï‰!'w­©™ 'Byâjw”Á&‡öü_3ÛÒš4aˆÛ¾øä¡TǨΠBG$$ŽØX m%î…jZVÛ2K4°^· $òÝÚm±H¤‰†HZb†%Á&'C-=6ÞmwÌ>pª9Fˆe™N_Jöê¹ô¹ƒÌðÿFÿ“NŸâó8n36˜t™+Éb·X¼\û#CÕ‡(1Oüݸé¥0üõ®.¦eÇþ®µQ©ÒC¬98r ¥¼ÌÖdCœ˜ò~ÊPÄ5>w·›²í†&š<Èxàþ°¢Š–"Ÿ0Ñ`G  ¤“÷ƒ|N4«}ÈQ¥Ð¶¬ÐkXA¤÷´•Ö‰£0ÝŸÒs„EgVÚ,x‡Ã-È íRàkÍVÖPÊøU(;`É|¨Y”8X JòJ <Áñ!úÉŒý2´‰²o¥™Œ¹gÔ\£‰¬. 8Ï/æiîç;$¬²„;.Ù±°Lˆ#C—að)ñ#CSá¢_ˆj?5·×G#ÝáÒüN=½ìNi0 gÃM#AÜ}øÖ,¹R(#-uHsׇÞQããè%Éñd¥ç?wˆˆƒð}ðjÁJ]þã"ÉL¤êl®øz|Z¡’¥2A)2“ø¸˜&Îì{N‹ÊèÙ"Šw<c…­˜J`æ2'GŽGaÏÕ¢ikT“›œ‘NççgaG¯ûÞ~°…”R1f(²Å?k(}”á‰;¨ÔË9}mó#C¾Xâs p½ ÑH8#C4Š•ýÏP9yA„Hìÿ¦ÄtÑú˜!#¬lûb…¶ñÈûSHBAϼ?˜¢þ¿Ëp£—õêë`)cs H,”5ÛÊkcÔ{÷£c9†$’+Èôï$ÖM|ãc‹ ’GÏLÞ‡¢qÔ575O÷A7e:€Ÿ ¢©bVT$þw9G˜ »âQ|n¤KºBŸ£CzX+!Æí#-OŽ&HˆF&J ˆ©©$¨ €´öÿþ–Š)¡"Š<,´7¾7àgf+à ÆÈšÐ3PÂ,µ-º\GÑ ìûÏÈæ)¯ZDgPÅ€'4£ë{ILIHÉÝ(>×±§Ó_Ù™fbQ51TMÞ >hFÏ @º+#-,î€|uØÚÓK»XQ§cÎi1ä›~ãFÊàFÚç‘–=:i¨ï#-¼áy‰A4RŽ¨)¸¸k<Ò·ôš(ž"Þ!Ó¸3¸Ö_ž<ñ"mYÃÉ#CoJ ä°ÞAØvbˆî.ô“Ä#-´ R©¤UÞö³#Cáwᆆ» 'íU¦M5ÀO¢#`§ŽE~b4¹¦Ÿ¶óš¬CR»'sÇ×8,€,?a„c#ø3Þ®>˜*€¡ ŠdiwEô&wPÿ‡eSÔÈ:Ý:ƒNû—1=ð´Ì…4E1#CAULÐ)ÉÐ!@CT#-$USCÑQÙÔTL @B 2@ 4‹JÄ!AÚe *'¿yP:B#C¦#-2PNš{Ì$:!Ù!Êö›?ÆÂWSmÙS¨,10h eùãÄ%álH˜ȃÍ>'GÀþ`ÛOÈ$‘óþþ› îsýíi³D1ÚçÛ2‘Ÿ™,n8ùdx216‰ÌŒEÂÞ–Ã}=·€aœ1F‘TbK™«0qŒÎz^†n!Bª;ÝoyÎêŽ £¥´l‚#Cu#˘€É|¬ ÅÛŒÈåñxüt³¹“CòøÐ7E}Ù#`m8C>¸™Cñøï’Ý5tí)†5†fŽ\HTÆ;æZSÅI…"·¶¢5."ÐKÃ(1ÊvdÚÔÎ…nîEéáôèìUhKj¨€B“o›:ï{F·‡M¡!:ÅÙð«²`4©‰~¿Of0Œ µëxÚðÁKQl”]JÅ*£ŽþSkýFDû²äÆ>|̤!%]‚ÊP]|Œb[vOoG2ÒªZ#CeyAŒ”šH15JýÒi¥ #`ä¡Z#`y#`ò#`C²ƒÒJ¤•ª@˜©’ ª&ˆi#`‰#`‰#`ÿV”¨8`ÑAòA<ó#ev ÷ý`?á‘÷þ1£ú<Ã@Ñõ×°Aì¡#->t˜>÷{ÃêÅ¡ø›éS÷¿OÏä^ÛQgÒ×<ÙvxNóåÖסÕÉC“‹þ=)ÿ^ì;\={½çˆXq 0ÐlýÛÿüŸÏ·2çÃý@ćãG…ÝÛúôAÂ:æÒ<Å)9®Šš*·ü°ÄVŒT@“>èÿ|"~@çýÐþ$‚#ñ(¥eäñÿ¢wuN—jüáu'Õøià;@Ý‘ý‚cú|£Ý§û'õw¡eí‡Þ]—hyA·«jv[»WQó®û¾oû¡‡}J|ž÷'Öè!ÉòKþo&.A7ÜþoÅ9Ü€ØÊ×Ô;Ù G9PtDcém¥¦Î‹þå'V<3_ÏVú6Ô½%pbÒ¬Ä&‡u!`Êgí‚€Ï÷V4EþŒnÎëT“Lg»Ô#³âUßäf'Û˜!®*\IO¸e²¥ëÑk8+·‰ÅQÎ ¿v«…ÆY̘wÈâ¿iÓ¶ˆÐgõ>…ŸáÖ‚V2Ž PÆ×+¦òƒ9"&jèÍÄ èèÕWDlýgrŽž]1•^KïØu4¨:Ÿ7 ˆ6Ô@"3Ð#C#`ª‰îfÞðÀ%j­$FU–‰Ð÷ Date: Wed, 25 May 2022 03:36:23 +0300 Subject: [PATCH 224/548] engine: client: fix array overruns --- engine/client/cl_events.c | 4 ++-- engine/client/cl_frame.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/client/cl_events.c b/engine/client/cl_events.c index bcf3f76d..0107a65e 100644 --- a/engine/client/cl_events.c +++ b/engine/client/cl_events.c @@ -158,7 +158,7 @@ CL_EventIndex */ word CL_EventIndex( const char *name ) { - int i; + word i; if( !COM_CheckString( name )) return 0; @@ -484,7 +484,7 @@ void GAME_EXPORT CL_PlaybackEvent( int flags, const edict_t *pInvoker, word even return; // first check event for out of bounds - if( eventindex < 1 || eventindex > MAX_EVENTS ) + if( eventindex < 1 || eventindex >= MAX_EVENTS ) { Con_DPrintf( S_ERROR "CL_PlaybackEvent: invalid eventindex %i\n", eventindex ); return; diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index b86debed..dd1777e6 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -222,7 +222,7 @@ void CL_UpdateLatchedVars( cl_entity_t *ent ) if( ent->curstate.sequence != ent->prevstate.sequence ) { - memcpy( ent->latched.prevseqblending, ent->prevstate.blending, sizeof( ent->prevstate.blending )); + memcpy( ent->latched.prevseqblending, ent->prevstate.blending, sizeof( ent->latched.prevseqblending )); ent->latched.prevsequence = ent->prevstate.sequence; ent->latched.sequencetime = ent->curstate.animtime; } From 924e895753098f6cb65ed27046bf5054e712e8b1 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 25 May 2022 03:38:02 +0300 Subject: [PATCH 225/548] engine: common: set Host_Error and Sys_Error as NORETURN --- engine/common/common.h | 2 +- engine/common/system.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/common/common.h b/engine/common/common.h index f96eda0d..b60dcf34 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -699,7 +699,7 @@ void Host_WriteConfig( void ); qboolean Host_IsLocalGame( void ); qboolean Host_IsLocalClient( void ); void Host_ShutdownServer( void ); -void Host_Error( const char *error, ... ) _format( 1 ); +void Host_Error( const char *error, ... ) _format( 1 ) NORETURN; void Host_PrintEngineFeatures( void ); void Host_Frame( float time ); void Host_InitDecals( void ); diff --git a/engine/common/system.h b/engine/common/system.h index 9656dbca..48fb1807 100644 --- a/engine/common/system.h +++ b/engine/common/system.h @@ -51,7 +51,7 @@ char *Sys_GetClipboardData( void ); const char *Sys_GetCurrentUser( void ); int Sys_CheckParm( const char *parm ); void Sys_Warn( const char *format, ... ) _format( 1 ); -void Sys_Error( const char *error, ... ) _format( 1 ); +void Sys_Error( const char *error, ... ) _format( 1 ) NORETURN; qboolean Sys_LoadLibrary( dll_info_t *dll ); void* Sys_GetProcAddress( dll_info_t *dll, const char* name ); qboolean Sys_FreeLibrary( dll_info_t *dll ); From 843c9abf9b0572e0368891eb7d28c2822265c7ec Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 25 May 2022 03:38:19 +0300 Subject: [PATCH 226/548] engine: common: fix array underrun --- engine/common/host.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/common/host.c b/engine/common/host.c index 07fbfd55..9b0c6285 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -1008,7 +1008,7 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha len = Q_strlen( host.rootdir ); - if( host.rootdir[len - 1] == '/' ) + if( len && host.rootdir[len - 1] == '/' ) host.rootdir[len - 1] = 0; // get readonly root. The order is: check for arg, then env. From 52d1383f14d1de3505efca3639ab0bff061709fc Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 25 May 2022 03:50:06 +0300 Subject: [PATCH 227/548] engine: client: fix various useless checks, unused variables & defines, double assignments and mistypings --- engine/client/cl_custom.c | 2 +- engine/client/cl_efrag.c | 2 -- engine/client/cl_game.c | 4 ++-- engine/client/cl_main.c | 7 +++---- engine/client/cl_parse.c | 15 ++------------- engine/client/cl_pmove.c | 1 - engine/client/cl_tent.c | 3 +-- engine/client/console.c | 33 ++++++++++++++------------------- engine/client/in_touch.c | 1 - engine/client/keys.c | 2 +- engine/client/s_dsp.c | 10 +++------- engine/client/s_main.c | 1 - 12 files changed, 27 insertions(+), 54 deletions(-) diff --git a/engine/client/cl_custom.c b/engine/client/cl_custom.c index e1baee8f..b279b361 100644 --- a/engine/client/cl_custom.c +++ b/engine/client/cl_custom.c @@ -106,7 +106,7 @@ void CL_RemoveFromResourceList( resource_t *pResource ) if( pResource->pPrev == NULL || pResource->pNext == NULL ) Host_Error( "mislinked resource in CL_RemoveFromResourceList\n" ); - if ( pResource->pNext == pResource || pResource->pPrev == pResource ) + if( pResource->pNext == pResource || pResource->pPrev == pResource ) Host_Error( "attempt to free last entry in list.\n" ); pResource->pPrev->pNext = pResource->pNext; diff --git a/engine/client/cl_efrag.c b/engine/client/cl_efrag.c index 5df1b6bd..366134a2 100644 --- a/engine/client/cl_efrag.c +++ b/engine/client/cl_efrag.c @@ -188,8 +188,6 @@ void R_StoreEfrags( efrag_t **ppefrag, int framecount ) case mod_brush: case mod_studio: case mod_sprite: - pent = pefrag->entity; - if( pent->visframe != framecount ) { if( CL_AddVisibleEntity( pent, ET_FRAGMENTED )) diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index dfd1f1d9..0737489e 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -2299,9 +2299,9 @@ static void GAME_EXPORT pfnKillEvents( int entnum, const char *eventname ) int i; event_state_t *es; event_info_t *ei; - int eventIndex = CL_EventIndex( eventname ); + word eventIndex = CL_EventIndex( eventname ); - if( eventIndex < 0 || eventIndex >= MAX_EVENTS ) + if( eventIndex >= MAX_EVENTS ) return; if( entnum < 0 || entnum > clgame.maxEntities ) diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 688c7fc0..c5807555 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -2345,11 +2345,10 @@ Replace the displayed name for some resources */ const char *CL_CleanFileName( const char *filename ) { - const char *pfilename = filename; - if( COM_CheckString( filename ) && filename[0] == '!' ) - pfilename = "customization"; - return pfilename; + return "customization"; + + return filename; } diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index daf819d9..27c1a3d4 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -484,6 +484,8 @@ void CL_BatchResourceRequest( qboolean initialize ) switch( p->type ) { case t_sound: + case t_model: + case t_eventscript: if( !CL_CheckFile( &msg, p )) break; CL_MoveToOnHandList( p ); @@ -491,11 +493,6 @@ void CL_BatchResourceRequest( qboolean initialize ) case t_skin: CL_MoveToOnHandList( p ); break; - case t_model: - if( !CL_CheckFile( &msg, p )) - break; - CL_MoveToOnHandList( p ); - break; case t_decal: if( !HPAK_GetDataPointer( CUSTOM_RES_PATH, p, NULL, NULL )) { @@ -520,11 +517,6 @@ void CL_BatchResourceRequest( qboolean initialize ) break; CL_MoveToOnHandList( p ); break; - case t_eventscript: - if( !CL_CheckFile( &msg, p )) - break; - CL_MoveToOnHandList( p ); - break; case t_world: ASSERT( 0 ); break; @@ -2389,14 +2381,11 @@ CL_ParseBaseline void CL_LegacyParseBaseline( sizebuf_t *msg ) { int i, newnum; - entity_state_t nullstate; qboolean player; cl_entity_t *ent; Delta_InitClient (); // finalize client delta's - memset( &nullstate, 0, sizeof( nullstate )); - newnum = MSG_ReadWord( msg ); player = CL_IsPlayerIndex( newnum ); diff --git a/engine/client/cl_pmove.c b/engine/client/cl_pmove.c index 897a717b..3d3a9a98 100644 --- a/engine/client/cl_pmove.c +++ b/engine/client/cl_pmove.c @@ -992,7 +992,6 @@ void CL_SetupPMove( playermove_t *pmove, local_state_t *from, usercmd_t *ucmd, q pmove->flFallVelocity = ps->flFallVelocity; pmove->flSwimTime = cd->flSwimTime; VectorCopy( cd->punchangle, pmove->punchangle ); - pmove->flSwimTime = cd->flSwimTime; pmove->flNextPrimaryAttack = 0.0f; // not used by PM_ code pmove->effects = ps->effects; pmove->flags = cd->flags; diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c index 2be8132f..1e9c19e9 100644 --- a/engine/client/cl_tent.c +++ b/engine/client/cl_tent.c @@ -1482,7 +1482,6 @@ void GAME_EXPORT R_FunnelSprite( const vec3_t org, int modelIndex, int reverse ) pTemp->entity.baseline.angles[2] = COM_RandomFloat( -100.0f, 100.0f ); pTemp->entity.curstate.framerate = COM_RandomFloat( 0.1f, 0.4f ); pTemp->flags = FTENT_ROTATE|FTENT_FADEOUT; - pTemp->entity.curstate.framerate = 10; vel = dest[2] / 8.0f; if( vel < 64.0f ) vel = 64.0f; @@ -2920,7 +2919,7 @@ void CL_PlayerDecal( int playernum, int customIndex, int entityIndex, float *pos { if( FBitSet( pCust->resource.ucFlags, RES_CUSTOM ) && pCust->resource.type == t_decal && pCust->bTranslated ) { - if( !pCust->nUserData1 && pCust->pInfo != NULL ) + if( !pCust->nUserData1 ) { const char *decalname = va( "player%dlogo%d", playernum, customIndex ); pCust->nUserData1 = GL_LoadTextureInternal( decalname, pCust->pInfo, TF_DECAL ); diff --git a/engine/client/console.c b/engine/client/console.c index 2c2d6cf5..e53aafce 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -1069,7 +1069,7 @@ int Con_DrawGenericString( int x, int y, const char *string, rgba_t setColor, qb Con_UtfProcessChar( 0 ); // draw the colored text - *(uint *)color = *(uint *)setColor; + memcpy( color, setColor, sizeof( color )); s = string; while( *s ) @@ -2174,6 +2174,9 @@ void Con_DrawSolidConsole( int lines ) int i, x, y; float fraction; int start; + int stringLen, width = 0, charH; + string curbuild; + byte color[4]; if( lines <= 0 ) return; @@ -2187,28 +2190,20 @@ void Con_DrawSolidConsole( int lines ) if( !con.curFont || !host.allow_console ) return; // nothing to draw - if( host.allow_console ) - { - // draw current version - int stringLen, width = 0, charH; - string curbuild; - byte color[4]; + // draw current version + memcpy( color, g_color_table[7], sizeof( color )); - memcpy( color, g_color_table[7], sizeof( color )); + Q_snprintf( curbuild, MAX_STRING, "%s %i/%s (%s-%s build %i)", XASH_ENGINE_NAME, PROTOCOL_VERSION, XASH_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( )); + Con_DrawStringLen( curbuild, &stringLen, &charH ); + start = refState.width - stringLen; + stringLen = Con_StringLength( curbuild ); - Q_snprintf( curbuild, MAX_STRING, "%s %i/%s (%s-%s build %i)", XASH_ENGINE_NAME, PROTOCOL_VERSION, XASH_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( )); + fraction = lines / (float)refState.height; + color[3] = Q_min( fraction * 2.0f, 1.0f ) * 255; // fadeout version number - Con_DrawStringLen( curbuild, &stringLen, &charH ); - start = refState.width - stringLen; - stringLen = Con_StringLength( curbuild ); - - fraction = lines / (float)refState.height; - color[3] = Q_min( fraction * 2.0f, 1.0f ) * 255; // fadeout version number - - for( i = 0; i < stringLen; i++ ) - width += Con_DrawCharacter( start + width, 0, curbuild[i], color ); - } + for( i = 0; i < stringLen; i++ ) + width += Con_DrawCharacter( start + width, 0, curbuild[i], color ); // draw the text if( CON_LINES_COUNT > 0 ) diff --git a/engine/client/in_touch.c b/engine/client/in_touch.c index f59c88c5..5bd454d0 100644 --- a/engine/client/in_touch.c +++ b/engine/client/in_touch.c @@ -786,7 +786,6 @@ static touch_button_t *Touch_AddButton( touchbuttonlist_t *list, button->flags = privileged ? 0 : TOUCH_FL_UNPRIVILEGED | TOUCH_FL_CLIENT; MakeRGBA( button->color, color[0], color[1], color[2], color[3] ); button->command[0] = 0; - button->flags = 0; button->fade = 1; Touch_SetCommand( button, command ); diff --git a/engine/client/keys.c b/engine/client/keys.c index 97b2e629..682f44ad 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -1044,7 +1044,7 @@ static qboolean OSK_KeyEvent( int key, int down ) else osk.curlayout++; - osk.shift = osk.curbutton.val == OSK_SHIFT; + osk.shift = true; osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x]; break; case OSK_BACKSPACE: diff --git a/engine/client/s_dsp.c b/engine/client/s_dsp.c index 4d57675c..d0bbca0b 100644 --- a/engine/client/s_dsp.c +++ b/engine/client/s_dsp.c @@ -33,7 +33,6 @@ GNU General Public License for more details. #define MAXDLY (STEREODLY + 1) #define MAXLP 10 -#define MAXPRESETS 29 typedef struct sx_preset_s { @@ -79,7 +78,7 @@ typedef struct dly_s int *lpdelayline; } dly_t; -const sx_preset_t rgsxpre[MAXPRESETS] = +const sx_preset_t rgsxpre[] = { // -------reverb-------- -------delay-------- // lp mod size refl rvblp delay feedback dlylp left @@ -116,7 +115,7 @@ const sx_preset_t rgsxpre[MAXPRESETS] = // 0x0045dca8 enginegl.exe // SHA256: 42383d32cd712e59ee2c1bd78b7ba48814e680e7026c4223e730111f34a60d66 -const sx_preset_t rgsxpre_hlalpha052[MAXPRESETS] = +const sx_preset_t rgsxpre_hlalpha052[] = { // -------reverb-------- -------delay-------- // lp mod size refl rvblp delay feedback dlylp left @@ -882,7 +881,7 @@ void CheckNewDspPresets( void ) else idsp_room = room_type->value; // don't pass invalid presets - idsp_room = bound( 0, idsp_room, MAXPRESETS - 1 ); + idsp_room = bound( 0, idsp_room, MAX_ROOM_TYPES ); if( FBitSet( hisound->flags, FCVAR_CHANGED )) { @@ -893,9 +892,6 @@ void CheckNewDspPresets( void ) if( idsp_room == room_typeprev && idsp_room == 0 ) return; - if( idsp_room > MAX_ROOM_TYPES ) - return; - if( idsp_room != room_typeprev ) { const sx_preset_t *cur; diff --git a/engine/client/s_main.c b/engine/client/s_main.c index a88546b1..6c926552 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -1155,7 +1155,6 @@ void S_RawSamples( uint samples, uint rate, word width, word channels, const byt int snd_vol = 128; if( entnum < 0 ) snd_vol = 256; // bg track or movie track - if( snd_vol < 0 ) snd_vol = 0; // fixup negative values S_RawEntSamples( entnum, samples, rate, width, channels, data, snd_vol ); } From 33d79ddb2444911e8ef1b762820f4385814568c1 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 25 May 2022 17:57:47 +0300 Subject: [PATCH 228/548] engine: server: fix possible array overruns --- engine/server/sv_custom.c | 2 +- engine/server/sv_game.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/engine/server/sv_custom.c b/engine/server/sv_custom.c index 112d74e1..ed65820e 100644 --- a/engine/server/sv_custom.c +++ b/engine/server/sv_custom.c @@ -533,7 +533,7 @@ void SV_BatchUploadRequest( sv_client_t *cl ) void SV_SendResource( resource_t *pResource, sizebuf_t *msg ) { - static byte nullrguc[36]; + static byte nullrguc[sizeof( pResource->rguc_reserved )]; MSG_WriteUBitLong( msg, pResource->type, 4 ); MSG_WriteString( msg, pResource->szFileName ); diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index 35626d83..21076253 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -108,7 +108,7 @@ entavrs table for FindEntityByString */ TYPEDESCRIPTION *SV_GetEntvarsDescirption( int number ) { - if( number < 0 && number >= ENTVARS_COUNT ) + if( number < 0 || number >= ENTVARS_COUNT ) return NULL; return &gEntvarsDescription[number]; } @@ -3984,7 +3984,7 @@ void GAME_EXPORT SV_PlaybackEventFull( int flags, const edict_t *pInvoker, word return; // someone stupid joke // first check event for out of bounds - if( eventindex < 1 || eventindex > MAX_EVENTS ) + if( eventindex < 1 || eventindex >= MAX_EVENTS ) { Con_Printf( S_ERROR "EV_Playback: invalid eventindex %i\n", eventindex ); return; @@ -4914,7 +4914,7 @@ qboolean SV_ParseEdict( char **pfile, edict_t *ent ) Mem_Free( pkvd[i].szValue ); } - if( classname && Mem_IsAllocatedExt( host.mempool, classname )) + if( Mem_IsAllocatedExt( host.mempool, classname )) Mem_Free( classname ); return true; From c713c7fc5912046ba0ff5284394938ec8134fd0a Mon Sep 17 00:00:00 2001 From: NightFox <0x4E69676874466F78@users.noreply.github.com> Date: Wed, 23 Mar 2022 02:40:35 +0300 Subject: [PATCH 229/548] correct lighting and gamma-correction improvement --- ref_vk/shaders/color_spaces.glsl | 62 ++++++++++++++++++++++++++++ ref_vk/shaders/denoiser.comp | 7 ++-- ref_vk/shaders/ray_light_direct.glsl | 8 ++-- ref_vk/shaders/ray_primary.rchit | 10 +++-- 4 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 ref_vk/shaders/color_spaces.glsl diff --git a/ref_vk/shaders/color_spaces.glsl b/ref_vk/shaders/color_spaces.glsl new file mode 100644 index 00000000..0a916f89 --- /dev/null +++ b/ref_vk/shaders/color_spaces.glsl @@ -0,0 +1,62 @@ +#define SRGB_FAST_APPROXIMATION 0 + +#ifdef SRGB_FAST_APPROXIMATION +#define LINEARtoSRGB OECF_sRGBFast +#define SRGBtoLINEAR sRGB_OECFFast +#else +#define LINEARtoSRGB OECF_sRGB +#define SRGBtoLINEAR sRGB_OECF +#endif + +// based on https://github.com/OGRECave/ogre/blob/f49bc9be79f6711a88f01892711120da717f6148/Samples/Media/PBR/filament/pbr_filament.frag.glsl#L108-L124 +float sRGB_OECF(const float sRGB) { + // IEC 61966-2-1:1999 + float linearLow = sRGB / 12.92; + float linearHigh = pow((sRGB + 0.055) / 1.055, 2.4); + return sRGB <= 0.04045 ? linearLow : linearHigh; +} +/** + * Reverse opto-electronic conversion function to the one that filament + * provides. Filament version has LDR RGB linear color -> LDR RGB non-linear + * color in sRGB space. This function will thus provide LDR RGB non-linear + * color in sRGB space -> LDR RGB linear color conversion. + */ +vec3 sRGB_OECF(const vec3 sRGB) +{ + return vec3(sRGB_OECF(sRGB.r), sRGB_OECF(sRGB.g), sRGB_OECF(sRGB.b)); +} +vec4 sRGB_OECF(const vec4 sRGB) +{ + return vec4(sRGB_OECF(sRGB.r), sRGB_OECF(sRGB.g), sRGB_OECF(sRGB.b), sRGB.w); +} +vec3 sRGB_OECFFast(const vec3 sRGB) { + return pow(sRGB, vec3(2.2)); +} +vec4 sRGB_OECFFast(const vec4 sRGB) { + return vec4(pow(sRGB.rgb, vec3(2.2)), sRGB.w); +} + +// based on https://github.com/abhirocks1211/filament/blob/3e97ac5268a47d5625c7d166eb7dda0bbba14a4d/shaders/src/conversion_functions.fs#L20-L55 +//------------------------------------------------------------------------------ +// Opto-electronic conversion functions (linear to non-linear) +//------------------------------------------------------------------------------ + +float OECF_sRGB(const float linear) { + // IEC 61966-2-1:1999 + float sRGBLow = linear * 12.92; + float sRGBHigh = (pow(linear, 1.0 / 2.4) * 1.055) - 0.055; + return linear <= 0.0031308 ? sRGBLow : sRGBHigh; +} + +vec3 OECF_sRGB(const vec3 linear) { + return vec3(OECF_sRGB(linear.r), OECF_sRGB(linear.g), OECF_sRGB(linear.b)); +} +vec4 OECF_sRGB(const vec4 linear) { + return vec4(OECF_sRGB(linear.r), OECF_sRGB(linear.g), OECF_sRGB(linear.b), linear.w); +} +vec3 OECF_sRGBFast(const vec3 linear) { + return pow(linear, vec3(1.0 / 2.2)); +} +vec4 OECF_sRGBFast(const vec4 linear) { + return vec4(pow(linear.rgb, vec3(1.0 / 2.2)), linear.w); +} diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index e35e6518..be11a3b3 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -2,6 +2,7 @@ #include "noise.glsl" #include "utils.glsl" +#include "color_spaces.glsl" layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; @@ -183,10 +184,10 @@ void main() { colour *= 8.; } #else - colour *= .25; + //colour *= .25; #endif - colour = aces_tonemap(colour); + //colour = aces_tonemap(colour); //colour = reinhard02(colour, vec3(400.)); //colour = reinhard02(colour, vec3(1.)); @@ -194,7 +195,7 @@ void main() { if (pix.x < res.x / 2) { #endif //colour *= .25; - colour = pow(colour, vec3(1. / 2.2)); + colour = LINEARtoSRGB(colour); // gamma-correction #if 0 } #endif diff --git a/ref_vk/shaders/ray_light_direct.glsl b/ref_vk/shaders/ray_light_direct.glsl index 59ac7e9b..195248c2 100644 --- a/ref_vk/shaders/ray_light_direct.glsl +++ b/ref_vk/shaders/ray_light_direct.glsl @@ -67,10 +67,10 @@ void main() { computeLighting(pos + geometry_normal * .001, shading_normal, throughput, -direction, material, diffuse, specular); #if LIGHT_POINT - imageStore(out_image_light_point_diffuse, pix, vec4(diffuse, 0.f)); - imageStore(out_image_light_point_specular, pix, vec4(specular, 0.f)); + imageStore(out_image_light_point_diffuse, pix, vec4(diffuse / 4.0, 0.f)); + imageStore(out_image_light_point_specular, pix, vec4(specular / 4.0, 0.f)); #else - imageStore(out_image_light_poly_diffuse, pix, vec4(diffuse, 0.f)); - imageStore(out_image_light_poly_specular, pix, vec4(specular, 0.f)); + imageStore(out_image_light_poly_diffuse, pix, vec4(diffuse / 25.0, 0.f)); + imageStore(out_image_light_poly_specular, pix, vec4(specular/ 25.0, 0.f)); #endif } diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index 385f4775..f43c434d 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -8,6 +8,8 @@ #include "ray_kusochki.glsl" +#include "color_spaces.glsl" + layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; layout(set = 0, binding = 7) uniform samplerCube skybox; @@ -30,10 +32,11 @@ void main() { const uint tex_base_color = kusok.tex_base_color; if ((tex_base_color & KUSOK_MATERIAL_FLAG_SKYBOX) != 0) { - payload.emissive.rgb = pow(texture(skybox, gl_WorldRayDirectionEXT).rgb, vec3(2.2)); + payload.emissive.rgb = SRGBtoLINEAR(texture(skybox, gl_WorldRayDirectionEXT).rgb); return; } else { - payload.base_color_a = sampleTexture(tex_base_color, geom.uv, geom.uv_lods) * kusok.color; + const vec4 base_color_a = sampleTexture(tex_base_color, geom.uv, geom.uv_lods) * kusok.color; + payload.base_color_a = vec4(SRGBtoLINEAR(base_color_a.rgb), base_color_a.a); payload.material_rmxx.r = (kusok.tex_roughness > 0) ? sampleTexture(kusok.tex_roughness, geom.uv, geom.uv_lods).r : kusok.roughness; payload.material_rmxx.g = (kusok.tex_metalness > 0) ? sampleTexture(kusok.tex_metalness, geom.uv, geom.uv_lods).r : kusok.metalness; @@ -53,7 +56,8 @@ void main() { #if 1 // Real correct emissive color - payload.emissive.rgb = kusok.emissive; + //payload.emissive.rgb = kusok.emissive; + payload.emissive.rgb = clamp(kusok.emissive / (1.0/3.0) / 20, 0, 1.5) * payload.base_color_a.rgb; #else // Fake texture color if (any(greaterThan(kusok.emissive, vec3(0.)))) From 4e1982815cd0350c1ea6d09ddff7eefe32b9a264 Mon Sep 17 00:00:00 2001 From: NightFox <0x4E69676874466F78@users.noreply.github.com> Date: Wed, 23 Mar 2022 03:38:15 +0300 Subject: [PATCH 230/548] correct payload.emissive.rgb --- ref_vk/shaders/ray_primary.rchit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index f43c434d..aefb4f7a 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -57,7 +57,7 @@ void main() { #if 1 // Real correct emissive color //payload.emissive.rgb = kusok.emissive; - payload.emissive.rgb = clamp(kusok.emissive / (1.0/3.0) / 20, 0, 1.5) * payload.base_color_a.rgb; + payload.emissive.rgb = clamp(kusok.emissive / (1.0/3.0) / 25, 0, 1.5) * payload.base_color_a.rgb; #else // Fake texture color if (any(greaterThan(kusok.emissive, vec3(0.)))) From d2bb972930cb45bb907fe23270381aa55688e905 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Fri, 27 May 2022 11:16:15 -0700 Subject: [PATCH 231/548] rt: assoc dyn polylights w/ render model fix #355 --- ref_vk/vk_brush.c | 29 ++++++++++------------------- ref_vk/vk_light.h | 2 +- ref_vk/vk_ray_model.c | 7 +++++++ ref_vk/vk_render.h | 3 +++ 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/ref_vk/vk_brush.c b/ref_vk/vk_brush.c index 6934aca2..d5bf3366 100644 --- a/ref_vk/vk_brush.c +++ b/ref_vk/vk_brush.c @@ -22,9 +22,6 @@ typedef struct vk_brush_model_s { vk_render_model_t render_model; int num_water_surfaces; - - rt_light_add_polygon_t *polylights; - int polylights_count; } vk_brush_model_t; static struct { @@ -345,13 +342,6 @@ void VK_BrushModelDraw( const cl_entity_t *ent, int render_mode, const matrix4x4 if (bmodel->render_model.num_geometries == 0) return; - for (int i = 0; i < bmodel->polylights_count; ++i) { - rt_light_add_polygon_t *polylight = bmodel->polylights + i; - polylight->transform_row = (const matrix3x4*)model; - polylight->dynamic = true; - RT_LightAddPolygon(polylight); - } - for (int i = 0; i < bmodel->render_model.num_geometries; ++i) { vk_render_geometry_t *geom = bmodel->render_model.geometries + i; const int surface_index = geom->surf - mod->surfaces; @@ -531,9 +521,9 @@ static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { } if (is_emissive) { - if (bmodel->polylights) { - ASSERT(bmodel->polylights_count < sizes.emissive_surfaces); - bmodel->polylights[bmodel->polylights_count++] = loadPolyLight(mod, surface_index, surf, emissive); + if (bmodel->render_model.polylights) { + ASSERT(bmodel->render_model.polylights_count < sizes.emissive_surfaces); + bmodel->render_model.polylights[bmodel->render_model.polylights_count++] = loadPolyLight(mod, surface_index, surf, emissive); } else { polylight = loadPolyLight(mod, surface_index, surf, emissive); RT_LightAddPolygon(&polylight); @@ -635,10 +625,11 @@ static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { R_GeometryBufferUnlock( &buffer ); - if (bmodel->polylights) { - gEngine.Con_Reportf("WHAT %d %d \n", sizes.emissive_surfaces, bmodel->polylights_count); - ASSERT(sizes.emissive_surfaces == bmodel->polylights_count); + if (bmodel->render_model.polylights) { + gEngine.Con_Reportf("Dynamic polylights %d %d \n", sizes.emissive_surfaces, bmodel->render_model.polylights_count); + ASSERT(sizes.emissive_surfaces == bmodel->render_model.polylights_count); } + ASSERT(sizes.num_surfaces == num_geometries); bmodel->render_model.num_geometries = num_geometries; @@ -673,7 +664,7 @@ qboolean VK_BrushModelLoad( VkCommandBuffer cmdbuf, model_t *mod, qboolean map ) bmodel->render_model.geometries = (vk_render_geometry_t*)((char*)(bmodel + 1)); if (!map && sizes.emissive_surfaces) - bmodel->polylights = Mem_Malloc(vk_core.pool, sizeof(bmodel->polylights[0]) * sizes.emissive_surfaces); + bmodel->render_model.polylights = Mem_Malloc(vk_core.pool, sizeof(bmodel->render_model.polylights[0]) * sizes.emissive_surfaces); if (!loadBrushSurfaces(sizes, mod) || !VK_RenderModelInit(cmdbuf, &bmodel->render_model)) { gEngine.Con_Printf(S_ERROR "Could not load model %s\n", mod->name); @@ -698,8 +689,8 @@ void VK_BrushModelDestroy( model_t *mod ) { return; VK_RenderModelDestroy(&bmodel->render_model); - if (bmodel->polylights) - Mem_Free(bmodel->polylights); + if (bmodel->render_model.polylights) + Mem_Free(bmodel->render_model.polylights); Mem_Free(bmodel); mod->cache.data = NULL; } diff --git a/ref_vk/vk_light.h b/ref_vk/vk_light.h index 648b3564..545f3b97 100644 --- a/ref_vk/vk_light.h +++ b/ref_vk/vk_light.h @@ -111,7 +111,7 @@ struct cl_entity_s; void R_LightAddFlashlight( const struct cl_entity_s *ent, qboolean local_player ); struct msurface_s; -typedef struct { +typedef struct rt_light_add_polygon_s { int num_vertices; vec3_t vertices[7]; diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index 4d305330..53b27454 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -416,6 +416,13 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render kusok->uv_speed[0] = kusok->uv_speed[1] = 0.f; } } + + for (int i = 0; i < render_model->polylights_count; ++i) { + rt_light_add_polygon_t *const polylight = render_model->polylights + i; + polylight->transform_row = (const matrix3x4*)model; + polylight->dynamic = true; + RT_LightAddPolygon(polylight); + } } void XVK_RayModel_ClearForNextFrame( void ) diff --git a/ref_vk/vk_render.h b/ref_vk/vk_render.h index 4a446dbb..8c0d7e81 100644 --- a/ref_vk/vk_render.h +++ b/ref_vk/vk_render.h @@ -120,6 +120,7 @@ struct vk_ray_model_s; #define MAX_MODEL_NAME_LENGTH 64 +struct rt_light_add_polygon_s; typedef struct vk_render_model_s { char debug_name[MAX_MODEL_NAME_LENGTH]; int render_mode; @@ -134,6 +135,8 @@ typedef struct vk_render_model_s { // Non-NULL only for ray tracing struct vk_ray_model_s *ray_model; + struct rt_light_add_polygon_s *polylights; + int polylights_count; } vk_render_model_t; qboolean VK_RenderModelInit( VkCommandBuffer cmdbuf, vk_render_model_t* model ); From 9ce8edcb38c4ae302648fc0de8e0521f0e40307c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 28 May 2022 17:57:43 +0300 Subject: [PATCH 232/548] public: moved compiler attributes to xash3d_types.h --- common/xash3d_types.h | 28 +++++++++++++++++----------- public/crtlib.h | 8 -------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/common/xash3d_types.h b/common/xash3d_types.h index 7ed4ab91..650c5fc8 100644 --- a/common/xash3d_types.h +++ b/common/xash3d_types.h @@ -66,19 +66,25 @@ typedef uint64_t longtime_t; #define ColorIndex( c ) ((( c ) - '0' ) & 7 ) #if defined(__GNUC__) -#ifdef __i386__ -#define EXPORT __attribute__ ((visibility ("default"),force_align_arg_pointer)) -#define GAME_EXPORT __attribute((force_align_arg_pointer)) -#else -#define EXPORT __attribute__ ((visibility ("default"))) -#define GAME_EXPORT -#endif + #ifdef __i386__ + #define EXPORT __attribute__ ((visibility ("default"),force_align_arg_pointer)) + #define GAME_EXPORT __attribute((force_align_arg_pointer)) + #else + #define EXPORT __attribute__ ((visibility ("default"))) + #define GAME_EXPORT + #endif + #define _format(x) __attribute__((format(printf, x, x+1))) + #define NORETURN __attribute__((noreturn)) #elif defined(_MSC_VER) -#define EXPORT __declspec( dllexport ) -#define GAME_EXPORT + #define EXPORT __declspec( dllexport ) + #define GAME_EXPORT + #define _format(x) + #define NORETURN #else -#define EXPORT -#define GAME_EXPORT + #define EXPORT + #define GAME_EXPORT + #define _format(x) + #define NORETURN #endif diff --git a/public/crtlib.h b/public/crtlib.h index 79bd414a..5e23423f 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -20,14 +20,6 @@ GNU General Public License for more details. #include #include "build.h" -#ifdef __GNUC__ -#define _format(x) __attribute__((format(printf, x, x+1))) -#define NORETURN __attribute__((noreturn)) -#else -#define _format(x) -#define NORETURN -#endif - // timestamp modes enum { From f25254369d3a47582430fa33ba8b78c5836eeffc Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 28 May 2022 17:58:17 +0300 Subject: [PATCH 233/548] engine: set useful format attribute for RefAPI, set Host_Error as NORETURN --- engine/ref_api.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/engine/ref_api.h b/engine/ref_api.h index bfe210a6..3c3119d3 100644 --- a/engine/ref_api.h +++ b/engine/ref_api.h @@ -277,13 +277,13 @@ typedef struct ref_api_s void (*Cbuf_Execute)( void ); // logging - void (*Con_Printf)( const char *fmt, ... ); // typical console allowed messages - void (*Con_DPrintf)( const char *fmt, ... ); // -dev 1 - void (*Con_Reportf)( const char *fmt, ... ); // -dev 2 + void (*Con_Printf)( const char *fmt, ... ) _format( 1 ); // typical console allowed messages + void (*Con_DPrintf)( const char *fmt, ... ) _format( 1 ); // -dev 1 + void (*Con_Reportf)( const char *fmt, ... ) _format( 1 ); // -dev 2 // debug print - void (*Con_NPrintf)( int pos, const char *fmt, ... ); - void (*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... ); + void (*Con_NPrintf)( int pos, const char *fmt, ... ) _format( 2 ); + void (*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... ) _format( 2 ); void (*CL_CenterPrint)( const char *s, float y ); void (*Con_DrawStringLen)( const char *pText, int *length, int *height ); int (*Con_DrawString)( int x, int y, const char *string, rgba_t setColor ); @@ -335,7 +335,7 @@ typedef struct ref_api_s // utils void (*CL_ExtraUpdate)( void ); - void (*Host_Error)( const char *fmt, ... ); + void (*Host_Error)( const char *fmt, ... ) _format( 1 ) NORETURN; void (*COM_SetRandomSeed)( int lSeed ); float (*COM_RandomFloat)( float rmin, float rmax ); int (*COM_RandomLong)( int rmin, int rmax ); From 5e4996b119aba9f9a794da08c1787dd4b1d267b6 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 28 May 2022 22:16:08 +0300 Subject: [PATCH 234/548] engine: server: fix bot count --- engine/server/sv_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index b6756d47..44b28e4d 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -179,7 +179,7 @@ int SV_GetConnectedClientsCount(int *bots) if( FBitSet( svs.clients[index].flags, FCL_FAKECLIENT )) { if( bots ) - *bots++; + (*bots)++; } else clients++; From 0d195ee6fe8c8d91c7cb251c9578ff20b7cc8df1 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 29 May 2022 01:29:51 +0300 Subject: [PATCH 235/548] engine: server: fix output of maps command --- engine/server/sv_cmds.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/server/sv_cmds.c b/engine/server/sv_cmds.c index 4d216f0e..571c4ffc 100644 --- a/engine/server/sv_cmds.c +++ b/engine/server/sv_cmds.c @@ -265,7 +265,7 @@ void SV_Maps_f( void ) Mem_Free( mapList ); - Msg( "%s\nDirectory: \"%s/maps\" - Maps listed: %d\n", separator, GI->basedir, nummaps ); + Msg( "%s\nDirectory: \"%s/maps\" - Maps listed: %d\n", separator, GI->gamefolder, nummaps ); } /* @@ -763,7 +763,7 @@ void SV_ServerInfo_f( void ) { Con_Printf( "Server info settings:\n" ); Info_Print( svs.serverinfo ); - Con_Printf( "Total %i symbols\n", Q_strlen( svs.serverinfo )); + Con_Printf( "Total %lu symbols\n", Q_strlen( svs.serverinfo )); return; } @@ -805,7 +805,7 @@ void SV_LocalInfo_f( void ) { Con_Printf( "Local info settings:\n" ); Info_Print( svs.localinfo ); - Con_Printf( "Total %i symbols\n", Q_strlen( svs.localinfo )); + Con_Printf( "Total %lu symbols\n", Q_strlen( svs.localinfo )); return; } From 86a777880f2199782aacc88a8b9bbb067106584c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 29 May 2022 01:58:56 +0300 Subject: [PATCH 236/548] engine: common: mark AbortCurrentFrame as NORETURN, fix noreturn warnings --- engine/common/common.h | 2 +- engine/common/host.c | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/engine/common/common.h b/engine/common/common.h index b60dcf34..333f31aa 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -691,7 +691,7 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa int Host_CompareFileTime( int ft1, int ft2 ); void Host_NewInstance( const char *name, const char *finalmsg ); void Host_EndGame( qboolean abort, const char *message, ... ) _format( 2 ); -void Host_AbortCurrentFrame( void ); +void Host_AbortCurrentFrame( void ) NORETURN; void Host_WriteServerConfig( const char *name ); void Host_WriteOpenGLConfig( void ); void Host_WriteVideoConfig( void ); diff --git a/engine/common/host.c b/engine/common/host.c index 9b0c6285..d3ca35b8 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -721,7 +721,6 @@ void GAME_EXPORT Host_Error( const char *error, ... ) else if( host.framecount == host.errorframe ) { Sys_Error( "Host_MultiError: %s", hosterror2 ); - return; } else { @@ -741,7 +740,6 @@ void GAME_EXPORT Host_Error( const char *error, ... ) { Con_Printf( "Host_RecursiveError: %s", hosterror2 ); Sys_Error( "%s", hosterror1 ); - return; // don't multiple executes } recursive = true; From db48d0ded744326c21b1911c32e0a93b61b65668 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 29 May 2022 01:59:15 +0300 Subject: [PATCH 237/548] engine: server: fix useless double assignment --- engine/server/sv_pmove.c | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/server/sv_pmove.c b/engine/server/sv_pmove.c index 34008241..e8e0ea29 100644 --- a/engine/server/sv_pmove.c +++ b/engine/server/sv_pmove.c @@ -686,7 +686,6 @@ static void SV_SetupPMove( playermove_t *pmove, sv_client_t *cl, usercmd_t *ucmd pmove->flFallVelocity = clent->v.flFallVelocity; pmove->flSwimTime = clent->v.flSwimTime; VectorCopy( clent->v.punchangle, pmove->punchangle ); - pmove->flSwimTime = clent->v.flSwimTime; pmove->flNextPrimaryAttack = 0.0f; // not used by PM_ code pmove->effects = clent->v.effects; pmove->flags = clent->v.flags; From 11d4cb0f1e6af9d76800e5c87a2132bd2aa9244c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 29 May 2022 02:27:02 +0300 Subject: [PATCH 238/548] engine: client: securedstub: cosmetic changes --- engine/client/cl_game.c | 4 +--- engine/client/cl_securedstub.c | 15 ++++++++------- engine/client/client.h | 5 +++++ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index 0737489e..2b64bec0 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -3923,8 +3923,6 @@ void CL_UnloadProgs( void ) Cmd_Unlink( CMD_CLIENTDLL ); } -void GetSecuredClientAPI( CL_EXPORT_FUNCS F ); - qboolean CL_LoadProgs( const char *name ) { static playermove_t gpMove; @@ -3983,7 +3981,7 @@ qboolean CL_LoadProgs( const char *name ) Con_Reportf( "CL_LoadProgs: found single callback export (secured client dlls)\n" ); // trying to fill interface now - GetSecuredClientAPI( GetClientAPI ); + CL_GetSecuredClientAPI( GetClientAPI ); } if ( GetClientAPI != NULL ) diff --git a/engine/client/cl_securedstub.c b/engine/client/cl_securedstub.c index 28034cf2..fd7746b1 100644 --- a/engine/client/cl_securedstub.c +++ b/engine/client/cl_securedstub.c @@ -109,6 +109,11 @@ typedef struct cldll_func_dst_s void (*pfnGetPlayerTeam)( int *iPlayer ); } cldll_func_dst_t; +struct cl_enginefunc_dst_s; +struct modshelpers_s; +struct modchelpers_s; +struct engdata_s; + typedef struct modfuncs_s { void (*m_pfnLoadMod)( char *pchModule ); @@ -398,14 +403,10 @@ static cldll_func_dst_t cldllFuncDst = DstGetPlayerTeam, }; -void GetSecuredClientAPI( CL_EXPORT_FUNCS F ) +void CL_GetSecuredClientAPI( CL_EXPORT_FUNCS F ) { - cldll_func_src_t cldllFuncSrc; - modfuncs_t modFuncs; - const dllfunc_t *func; - - memset( &cldllFuncSrc, 0, sizeof( cldllFuncSrc ) ); - memset( &modFuncs, 0, sizeof( modFuncs ) ); + cldll_func_src_t cldllFuncSrc = { 0 }; + modfuncs_t modFuncs = { 0 }; // secured client dlls need these *(cldll_func_dst_t **)&cldllFuncSrc.pfnVidInit = &cldllFuncDst; diff --git a/engine/client/client.h b/engine/client/client.h index 03f75131..129caca6 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -1109,6 +1109,11 @@ void pfnPIC_DrawAdditive( int x, int y, int width, int height, const wrect_t *pr qboolean Mobile_Init( void ); void Mobile_Shutdown( void ); +// +// cl_securedstub.c +// +void CL_GetSecuredClientAPI( CL_EXPORT_FUNCS F ); + // // cl_video.c // From 8445567ab49400a528fc3aa4d6d197a9b66bff5b Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 29 May 2022 02:27:39 +0300 Subject: [PATCH 239/548] engine: server: remove unused arguments passed to Con_Printf --- engine/server/sv_game.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index 21076253..c236840f 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -2729,7 +2729,7 @@ void GAME_EXPORT pfnMessageEnd( void ) SV_Multicast( svgame.msg_dest, org, svgame.msg_ent, true, false ); - if( svgame.msg_trace ) Con_Printf( "^3%s()\n", __FUNCTION__, svgame.msg_dest, svgame.msg_name ); + if( svgame.msg_trace ) Con_Printf( "^3%s()\n", __FUNCTION__ ); } /* From dd29e705f8661953040ccb67462841bad2cf3ff3 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 29 May 2022 02:33:28 +0300 Subject: [PATCH 240/548] engine: menu_int: add _format and NORETURN attributes to MenuAPI --- engine/menu_int.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/engine/menu_int.h b/engine/menu_int.h index ee48ed48..cdb918bf 100644 --- a/engine/menu_int.h +++ b/engine/menu_int.h @@ -87,10 +87,10 @@ typedef struct ui_enginefuncs_s const char* (*pfnCmd_Args)( void ); // debug messages (in-menu shows only notify) - void (*Con_Printf)( const char *fmt, ... ); - void (*Con_DPrintf)( const char *fmt, ... ); - void (*Con_NPrintf)( int pos, const char *fmt, ... ); - void (*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... ); + void (*Con_Printf)( const char *fmt, ... ) _format( 1 ); + void (*Con_DPrintf)( const char *fmt, ... ) _format( 1 ); + void (*Con_NPrintf)( int pos, const char *fmt, ... ) _format( 2 ); + void (*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... ) _format( 2 ); // sound handlers void (*pfnPlayLocalSound)( const char *szSound ); @@ -116,7 +116,7 @@ typedef struct ui_enginefuncs_s int (*CL_CreateVisibleEntity)( int type, struct cl_entity_s *ent ); // misc handlers - void (*pfnHostError)( const char *szFmt, ... ); + void (*pfnHostError)( const char *szFmt, ... ) _format( 1 ) NORETURN; int (*pfnFileExists)( const char *filename, int gamedironly ); void (*pfnGetGameDir)( char *szGetGameDir ); From 5fb4edeb364aed366a5a2e16b8249d039671db5c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 29 May 2022 04:00:19 +0300 Subject: [PATCH 241/548] ref: simplify gldebug switchcase in output function, kinda fix const qualifiers --- ref_gl/gl-wes-v2 | 2 +- ref_gl/gl_opengl.c | 20 ++++++++------------ ref_gl/gl_studio.c | 3 ++- ref_soft/r_glblit.c | 4 ---- ref_soft/r_studio.c | 2 +- 5 files changed, 12 insertions(+), 19 deletions(-) diff --git a/ref_gl/gl-wes-v2 b/ref_gl/gl-wes-v2 index 11384d5c..7f567393 160000 --- a/ref_gl/gl-wes-v2 +++ b/ref_gl/gl-wes-v2 @@ -1 +1 @@ -Subproject commit 11384d5cf6de22de3a9a47f97f2e1a4f069d4cd4 +Subproject commit 7f567393d4273d8f33924d98968c9a96991aba3c diff --git a/ref_gl/gl_opengl.c b/ref_gl/gl_opengl.c index 80c89e25..f0a33d9c 100644 --- a/ref_gl/gl_opengl.c +++ b/ref_gl/gl_opengl.c @@ -273,8 +273,6 @@ static void APIENTRY GL_DebugOutput( GLuint source, GLuint type, GLuint id, GLui gEngfuncs.Con_Printf( S_OPENGL_ERROR "%s\n", message ); break; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: - gEngfuncs.Con_Printf( S_OPENGL_WARN "%s\n", message ); - break; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: gEngfuncs.Con_Printf( S_OPENGL_WARN "%s\n", message ); break; @@ -282,8 +280,6 @@ static void APIENTRY GL_DebugOutput( GLuint source, GLuint type, GLuint id, GLui gEngfuncs.Con_Reportf( S_OPENGL_WARN "%s\n", message ); break; case GL_DEBUG_TYPE_PERFORMANCE_ARB: - gEngfuncs.Con_Printf( S_OPENGL_NOTE "%s\n", message ); - break; case GL_DEBUG_TYPE_OTHER_ARB: default: gEngfuncs.Con_Printf( S_OPENGL_NOTE "%s\n", message ); @@ -498,10 +494,10 @@ void R_RenderInfo_f( void ) if( glConfig.wrapper == GLES_WRAPPER_GL4ES ) { - const char *vendor = pglGetString( GL_VENDOR | 0x10000 ); - const char *renderer = pglGetString( GL_RENDERER | 0x10000 ); - const char *version = pglGetString( GL_VERSION | 0x10000 ); - const char *extensions = pglGetString( GL_EXTENSIONS | 0x10000 ); + const char *vendor = (const char *)pglGetString( GL_VENDOR | 0x10000 ); + const char *renderer = (const char *)pglGetString( GL_RENDERER | 0x10000 ); + const char *version = (const char *)pglGetString( GL_VERSION | 0x10000 ); + const char *extensions = (const char *)pglGetString( GL_EXTENSIONS | 0x10000 ); if( vendor ) gEngfuncs.Con_Printf( "GL4ES_VENDOR: %s\n", vendor ); @@ -644,10 +640,10 @@ void GL_InitExtensionsBigGL( void ) // gl4es may be used system-wide if( Q_stristr( glConfig.renderer_string, "gl4es" )) { - const char *vendor = pglGetString( GL_VENDOR | 0x10000 ); - const char *renderer = pglGetString( GL_RENDERER | 0x10000 ); - const char *version = pglGetString( GL_VERSION | 0x10000 ); - const char *extensions = pglGetString( GL_EXTENSIONS | 0x10000 ); + const char *vendor = (const char *)pglGetString( GL_VENDOR | 0x10000 ); + const char *renderer = (const char *)pglGetString( GL_RENDERER | 0x10000 ); + const char *version = (const char *)pglGetString( GL_VERSION | 0x10000 ); + const char *extensions = (const char *)pglGetString( GL_EXTENSIONS | 0x10000 ); glConfig.wrapper = GLES_WRAPPER_GL4ES; } diff --git a/ref_gl/gl_studio.c b/ref_gl/gl_studio.c index 5155ef00..bb52c262 100644 --- a/ref_gl/gl_studio.c +++ b/ref_gl/gl_studio.c @@ -34,7 +34,8 @@ typedef struct model_t *model; } player_model_t; -cvar_t r_shadows = { "r_shadows", "0", 0 }; +// never gonna change, just shut up const warning +cvar_t r_shadows = { (char *)"r_shadows", (char *)"0", 0 }; static vec3_t hullcolor[8] = { diff --git a/ref_soft/r_glblit.c b/ref_soft/r_glblit.c index e0f25973..a678db16 100644 --- a/ref_soft/r_glblit.c +++ b/ref_soft/r_glblit.c @@ -58,8 +58,6 @@ static void APIENTRY GL_DebugOutput( GLuint source, GLuint type, GLuint id, GLui gEngfuncs.Con_Printf( S_OPENGL_ERROR "%s\n", message ); break; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: - gEngfuncs.Con_Printf( S_OPENGL_WARN "%s\n", message ); - break; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: gEngfuncs.Con_Printf( S_OPENGL_WARN "%s\n", message ); break; @@ -67,8 +65,6 @@ static void APIENTRY GL_DebugOutput( GLuint source, GLuint type, GLuint id, GLui gEngfuncs.Con_Reportf( S_OPENGL_WARN "%s\n", message ); break; case GL_DEBUG_TYPE_PERFORMANCE_ARB: - gEngfuncs.Con_Printf( S_OPENGL_NOTE "%s\n", message ); - break; case GL_DEBUG_TYPE_OTHER_ARB: default: gEngfuncs.Con_Printf( S_OPENGL_NOTE "%s\n", message ); diff --git a/ref_soft/r_studio.c b/ref_soft/r_studio.c index fd4a3b1e..3dad15b6 100644 --- a/ref_soft/r_studio.c +++ b/ref_soft/r_studio.c @@ -34,7 +34,7 @@ typedef struct model_t *model; } player_model_t; -cvar_t r_shadows = { "r_shadows", "0", 0 }; +cvar_t r_shadows = { (char *)"r_shadows", (char *)"0", 0 }; static vec3_t hullcolor[8] = { From 6f36edfd16f5ccb6d7f4d55b2eefde6a4da131ea Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 29 May 2022 04:22:43 +0300 Subject: [PATCH 242/548] engine: various const qualifier fixes --- common/xash3d_types.h | 2 +- engine/client/s_vox.c | 18 +++++++++--------- engine/common/base_cmd.c | 2 +- engine/server/sv_game.c | 8 ++++---- engine/server/sv_log.c | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/common/xash3d_types.h b/common/xash3d_types.h index 650c5fc8..71ceca77 100644 --- a/common/xash3d_types.h +++ b/common/xash3d_types.h @@ -145,7 +145,7 @@ typedef struct dll_info_s void *link; // hinstance of loading library } dll_info_t; -typedef void (*setpair_t)( const char *key, const void *value, void *buffer, void *numpairs ); +typedef void (*setpair_t)( const char *key, const void *value, const void *buffer, void *numpairs ); // config strings are a general means of communication from // the server to all connected clients. diff --git a/engine/client/s_vox.c b/engine/client/s_vox.c index 93595683..73818943 100644 --- a/engine/client/s_vox.c +++ b/engine/client/s_vox.c @@ -21,7 +21,7 @@ GNU General Public License for more details. static int cszrawsentences = 0; static char *rgpszrawsentence[CVOXFILESENTENCEMAX]; -static char *voxperiod = "_period", *voxcomma = "_comma"; +static const char *voxperiod = "_period", *voxcomma = "_comma"; // return number of samples mixed int VOX_MixDataToDevice( channel_t *pchan, int sampleCount, int outputRate, int outputOffset ) @@ -248,8 +248,8 @@ static int VOX_ParseString( char *psz, char *rgpparseword[CVOXWORDMAX] ) psz[1] != '\n' && psz[1] != '\r' && psz[1] != '\0' ) { if( *psz == '.' ) - rgpparseword[i++] = voxperiod; - else rgpparseword[i++] = voxcomma; + rgpparseword[i++] = (char *)voxperiod; + else rgpparseword[i++] = (char *)voxcomma; if( i >= CVOXWORDMAX ) return i; @@ -457,7 +457,7 @@ static void VOX_ReadSentenceFile_( byte *buf, fs_offset_t size ) } } -static void VOX_ReadSentenceFile( char *path ) +static void VOX_ReadSentenceFile( const char *path ) { byte *buf; fs_offset_t size; @@ -529,11 +529,11 @@ static void Test_VOX_LookupString( void ) VOX_Shutdown(); - rgpszrawsentence[cszrawsentences++] = "exactmatch\000123"; - rgpszrawsentence[cszrawsentences++] = "CaseInsensitive\000456"; - rgpszrawsentence[cszrawsentences++] = "SentenceWithTabs\0\t\t\t789"; - rgpszrawsentence[cszrawsentences++] = "SentenceWithSpaces\0 SPAAACE"; - rgpszrawsentence[cszrawsentences++] = "SentenceWithTabsAndSpaces\0\t \t\t MEOW"; + rgpszrawsentence[cszrawsentences++] = (char*)"exactmatch\000123"; + rgpszrawsentence[cszrawsentences++] = (char*)"CaseInsensitive\000456"; + rgpszrawsentence[cszrawsentences++] = (char*)"SentenceWithTabs\0\t\t\t789"; + rgpszrawsentence[cszrawsentences++] = (char*)"SentenceWithSpaces\0 SPAAACE"; + rgpszrawsentence[cszrawsentences++] = (char*)"SentenceWithTabsAndSpaces\0\t \t\t MEOW"; for( i = 0; i < sizeof( data ) / sizeof( data[0] ); i += 2 ) { diff --git a/engine/common/base_cmd.c b/engine/common/base_cmd.c index b1685626..efdf718e 100644 --- a/engine/common/base_cmd.c +++ b/engine/common/base_cmd.c @@ -229,7 +229,7 @@ void BaseCmd_Stats_f( void ) Con_Printf( "Empty buckets: %d\n", empty ); } -static void BaseCmd_CheckCvars( const char *key, const char *value, void *buffer, void *ptr ) +static void BaseCmd_CheckCvars( const char *key, const char *value, const void *unused, void *ptr ) { base_command_t *v = BaseCmd_Find( HM_CVAR, key ); qboolean *invalid = ptr; diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index c236840f..588bc7d8 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -3842,7 +3842,7 @@ char *pfnGetInfoKeyBuffer( edict_t *e ) if(( cl = SV_ClientFromEdict( e, false )) != NULL ) return cl->userinfo; - return ""; // assume error + return (char*)""; // assume error } /* @@ -4807,7 +4807,7 @@ qboolean SV_ParseEdict( char **pfile, edict_t *ent ) if( !token[0] ) continue; // create keyvalue strings - pkvd[numpairs].szClassName = ""; // unknown at this moment + pkvd[numpairs].szClassName = (char*)""; // unknown at this moment pkvd[numpairs].szKeyName = copystring( keyname ); pkvd[numpairs].szValue = copystring( token ); pkvd[numpairs].fHandled = false; @@ -4845,8 +4845,8 @@ qboolean SV_ParseEdict( char **pfile, edict_t *ent ) { if( numpairs < 256 ) { - pkvd[numpairs].szClassName = "custom"; - pkvd[numpairs].szKeyName = "customclass"; + pkvd[numpairs].szClassName = (char*)"custom"; + pkvd[numpairs].szKeyName = (char*)"customclass"; pkvd[numpairs].szValue = classname; pkvd[numpairs].fHandled = false; numpairs++; diff --git a/engine/server/sv_log.c b/engine/server/sv_log.c index 4f6050c2..7c1c5d6b 100644 --- a/engine/server/sv_log.c +++ b/engine/server/sv_log.c @@ -136,7 +136,7 @@ void Log_Printf( const char *fmt, ... ) } } -static void Log_PrintServerCvar( const char *var_name, const char *var_value, void *unused2, void *unused3 ) +static void Log_PrintServerCvar( const char *var_name, const char *var_value, const void *unused2, void *unused3 ) { Log_Printf( "Server cvar \"%s\" = \"%s\"\n", var_name, var_value ); } From 113f18cfb0bcebb0633652c368b77ec11b491821 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 29 May 2022 04:23:07 +0300 Subject: [PATCH 243/548] ref_soft: use correct types for pointer arithmetics --- ref_soft/r_edge.c | 2 +- ref_soft/r_glblit.c | 2 +- ref_soft/r_main.c | 8 ++++---- ref_soft/r_rast.c | 10 +++++----- ref_soft/r_scan.c | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ref_soft/r_edge.c b/ref_soft/r_edge.c index e65797d5..e5b8e571 100644 --- a/ref_soft/r_edge.c +++ b/ref_soft/r_edge.c @@ -651,7 +651,7 @@ void R_ScanEdges (void) surf_t *s; basespan_p = (espan_t *) - ((unsigned long long)(basespans + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + ((uintptr_t)(basespans + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); max_span_p = &basespan_p[MAXSPANS - RI.vrect.width]; span_p = basespan_p; diff --git a/ref_soft/r_glblit.c b/ref_soft/r_glblit.c index a678db16..6f73b7f2 100644 --- a/ref_soft/r_glblit.c +++ b/ref_soft/r_glblit.c @@ -353,7 +353,7 @@ static qboolean R_CreateBuffer_GLES3( int width, int height, uint *stride, uint 1, 1, 0, 1, }; - int vbo, pbo, fbo, to; + GLuint vbo, pbo, fbo, to; // shitty fbo does not work without texture objects :( pglGenTextures( 1, &to ); diff --git a/ref_soft/r_main.c b/ref_soft/r_main.c index c4833e7c..44d2a383 100644 --- a/ref_soft/r_main.c +++ b/ref_soft/r_main.c @@ -1258,12 +1258,12 @@ void R_DrawBrushModel(cl_entity_t *pent) else { r_edges = (edge_t *) - (((unsigned long long)&ledges[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + (((uintptr_t)&ledges[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); } if (r_surfsonstack) { - surfaces = (surf_t *)(((unsigned long long)&lsurfs[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + surfaces = (surf_t *)(((uintptr_t)&lsurfs[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); surf_max = &surfaces[r_cnumsurfs]; // surface 0 doesn't really exist; it's just a dummy because index 0 // is used to indicate no edge attached to surface @@ -1411,12 +1411,12 @@ void R_EdgeDrawing (void) else { r_edges = (edge_t *) - (((unsigned long long)&ledges[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + (((uintptr_t)&ledges[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); } if (r_surfsonstack) { - surfaces = (surf_t *)(((unsigned long long)&lsurfs + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + surfaces = (surf_t *)(((uintptr_t)&lsurfs + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); surf_max = &surfaces[r_cnumsurfs]; // surface 0 doesn't really exist; it's just a dummy because index 0 diff --git a/ref_soft/r_rast.c b/ref_soft/r_rast.c index 193ccb41..023f326e 100644 --- a/ref_soft/r_rast.c +++ b/ref_soft/r_rast.c @@ -521,7 +521,7 @@ void R_EmitCachedEdge (void) { edge_t *pedge_t; - pedge_t = (edge_t *)((unsigned long long)r_edges + r_pedge->cachededgeoffset); + pedge_t = (edge_t *)((uintptr_t)r_edges + r_pedge->cachededgeoffset); if (!pedge_t->surfs[0]) pedge_t->surfs[0] = surface_p - surfaces; @@ -624,9 +624,9 @@ void R_RenderFace (msurface_t *fa, int clipflags) } else { - if ((((unsigned long long)edge_p - (unsigned long long)r_edges) > + if ((((uintptr_t)edge_p - (uintptr_t)r_edges) > r_pedge->cachededgeoffset) && - (((edge_t *)((unsigned long long)r_edges + + (((edge_t *)((uintptr_t)r_edges + r_pedge->cachededgeoffset))->owner == r_pedge)) { R_EmitCachedEdge (); @@ -670,9 +670,9 @@ void R_RenderFace (msurface_t *fa, int clipflags) { // it's cached if the cached edge is valid and is owned // by this medge_t - if ((((unsigned long long)edge_p - (unsigned long long)r_edges) > + if ((((uintptr_t)edge_p - (uintptr_t)r_edges) > r_pedge->cachededgeoffset) && - (((edge_t *)((unsigned long long)r_edges + + (((edge_t *)((uintptr_t)r_edges + r_pedge->cachededgeoffset))->owner == r_pedge)) { R_EmitCachedEdge (); diff --git a/ref_soft/r_scan.c b/ref_soft/r_scan.c index 2e3c9176..28ed582b 100644 --- a/ref_soft/r_scan.c +++ b/ref_soft/r_scan.c @@ -1390,7 +1390,7 @@ void D_DrawZSpans (espan_t *pspan) // we count on FP exceptions being turned off to avoid range problems izi = (int)(zi * 0x8000 * 0x10000); - if ((unsigned long long)pdest & 0x02) + if ((uintptr_t)pdest & 0x02) { *pdest++ = (short)(izi >> 16); izi += izistep; From 978ec27a57bdd44d1fbbcb91e372f2e8d64e1006 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 29 May 2022 05:19:23 +0300 Subject: [PATCH 244/548] waf: upgrade to temporary waifu branch with absolute path clang compilation database generator --- waf | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/waf b/waf index 90d59803..82ac5125 100755 --- a/waf +++ b/waf @@ -33,12 +33,12 @@ POSSIBILITY OF SUCH DAMAGE. import os, sys, inspect VERSION="2.0.24" -REVISION="af69e5d2e74a777b13b5753931cb3c85" +REVISION="99462435aaeec331fa35f40b2179472b" GIT="c140c3f538c4a21f3d88bab9403b42c696759dcb" INSTALL='' -C1='#`' -C2='#C' -C3='#-' +C1='#5' +C2='#/' +C3='#+' cwd = os.getcwd() join = os.path.join @@ -171,5 +171,5 @@ if __name__ == '__main__': Scripting.waf_entry_point(cwd, VERSION, wafdir) #==> -#BZh91AY&SY&4 ’Xÿÿÿÿ?ùÿÿÿÿÿÿÿÿÿÿÿÕè&W$m~VÐ#-ø0écÂ>zä؃@P#-#-#-#-#-#-#-#-#-#-#-#-#-(#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-ó××ÕUo»}òú¹ƒÞ·ÐzïZ’½öwס“ »N€ú÷ÝwÒn/»_7Ûç_{Þ^뜊MÜäqK±§k1ÝÖöêªn:ëÖ¯¶û|sÜ7ºîó}ÝŽÃvæÎm»k›µÎ×gÓÎW·mu,ww4Ú.¸íµšj9ÙQ¬®ï½ºsiõÞã«Ï–©öbï½ï6ôwwØ·¥(÷MîçdÍV«kuº&îž#C°Ù½ÝÑо–õÛÛÍÀ;²€$»·[±vJ UUJ‚ìÉT;e:h;µæ¾s× Ý]w>íÏqõï·wÏ€#-ªØ#-#`¾vëYÁÛ«c¹µ·8äDP*leÝÎ[cSjUìvA»]¸ªP´Ð6ÆÙ­ºë”íÜf|ðïRõˆ#`wg!-˜Ø5"ƒ­@»ÎâvÖìåB¥ç°{ï>SìÈÛFA£+êökk¼W´F‚Píñ÷œ(}e-nŽNJ–÷¸x^c)Lâïgnã¯<—`ÚÉž^ïH›m·M)@P Š#-*B%J_v8•Øï^ê¥D:Ê"N½ó>Ý0˜pÛæÞ÷“Fo]Ú&}öá}}wnŸT÷gÀú×% º;³Ü#-#-#-Ð#-#->ÃT#-øèx÷o½|Ǧ#`Õ¸#-8URYLP¶Ösæºzti¥h#-§ ç[mݳCIÛR¦ìWÑÍ”#`du  Ði§@¡ÀJ#` ª½œBB”(}ŠP#-úE¼Î%tj‚#-¯³Ýòyww^ùöìûµÑå$Ÿ:]P½fvä:ÐR®µSFTa2Û{Žß aÚm¾·Ô[*#CQÝyõ¦Kk¤]ötöãyîîïy¶õ¯«¾žöõ}÷ÞÞm}Ökïs™¾çw¼¾}ͯ½öøâûîÞãÝë“í—«O{›»ÓVž.•ÐÜöõæž÷לùñvz÷¸ûãÕP[Z5Fßpš®í½ÚØ¥#`'6½fÛní*:ÔnÝwwnØn*Ží’‡®«×ÃßZá×zzß|à:@Mï•÷ÞÍsíðê)|¼Ԧ´Ÿvt}¹}îÞñê®Øí÷±ÖÛÞßw×{ÞowMÚ÷¹·=§qõÒ»7>Í×o¯·×wtW}뛹×ØÉ-š»¶¾÷wnéØúÝîkîjí3®Öíë¾¼ûw[zç»»èû½Zg¼y;Ûµw޹ݶ2}ï}÷¶ZïªGl¤÷3bÜRùæf›Úé÷œ½o<¶ÛŒ»ìoŸO{¦…õÝêåƒ@:#CŸNé²ÒŠµi©T÷>ží÷glÁŸ5ö}Të¹µ+mÝŽçfÛÍG½ç½½½oiuµ­îí÷=zûµ7kn…Ïk´¬Ð[žÏ¸øûzùóW¶Zõ¢Ø»9.ï–ðókºÜt‹ew0ï{}ž«Þû»Ö\æ ïonç½îÀéÝw]—5ˆQºê‹ÃÛ×7´çNµ× —Ͻ#C((wÝÜíâ>Ë{ÛËÇ¡;&¦3wZw{ /-5ëÛ1´×WC;wq±=èšë0ç½ï'nîúÇ!î:q7¾ûçÎÎï‰:»nY«¸SÎõ‡€^Ó°è¡0TçS#µ¶îûãßnYÞ¯v{w½|s€#-å’•mè|OJÚ€V‚š ÒŠ#-#C²@»¸ÄI#-T€€ÛE»®¦À½œq:w}½ÝwÅèi\îÔRU{ëê!͉°÷ƒ½êí¹YYváVÙÌ&ò€QÛÜs{G+{{Õp€@¯lóËÞçf¦š¡¨R´wwsnvë«®Ý;€tP#-¾ûÞ>JP#-#-{ß}ógÕÃÞy¼j{T÷-VÝ-HåÓsiÍa{z^ó^Øô­Îq½õóɈ#-|ÏYç@8í;)@jÓ #-­ÚÂ"î7Ôlx½­Õ\îæ×:m¶«††ÍrÛ¡€#`ŒÏ·t^™Ï»îúøð7wšPè&qõõ÷Ÿ{ÖçiÀ([dTQhbg=g½{žïn«[pÍÏ{ÐïnŽÙ­ÙSׯ^4#CÏs=½¤óß=iévð·šs"rºewky]{ÚNܽU9ÆØw+WtšíÍÚÃÖÞéA}÷u}ôÊ®³Î´bÌånbÇ3·.h®žóy²§»r$J¡)UTuÜÎÛ–º5Ú]]$ ØËÏi‹@#-dM#`€ª­.ƒM¸vîA@#`vœ÷Þ÷¾µ[ëßw×|Þ΋#-#-vb@R¶ÄèÇfDŒ»®îiÓlPÝfôîôâÅŠ±«¬–:½l•âÃëîù×WרVG]ÝÏ{Ÿo£¤Ÿ.vúㆶ޺ 5oow›·vÍØÈ ¹TÈzz­  #-#-xÊ`ÒÀ[#`RmPzPžÚU%ÄÛÜǯ[p¶œã®@»µtnºè’m#- ”#-ÝN[€nð¡ô#-#-S#`#-#-4À AŸ{“¶ÛÕY€åGwÜO½»;³¤Ü¥#Cws«{»Ùw–:’ú¦î8èP(¥Wl¹ÊÝÍt—6k\ €­¾uìµÀKUPiPöÝç6íj®³®ì6·ºz#-Û{¸y#`E»šíµ#-9š¤‚@uÝXq¬;Š[Íyî–íÛkhÚ‹iÛÞÓ´—BãÜSe¸nÛ€ÑEôñðú• ^½;¡»‰^Þ]a½åTlïs#-m[»¾ûïD}|Ì’¨ˆÕgcPœÔΆ#`£»rê%#-låî}ßwÏ·½èîõ§Umé³s[wSc£v]ã3Ò®bN½¶{½qÂ×µyf„›h­w†÷ž£ÊY ¾C\xi¢#-@ #-F€4d#-#-52ž)?IzM#-õIêM5z#ÔÚƒÔÓÔõIäÒ M!#-€MS)OÍTý1¤ †#ÁO41ê#-Ð#-#-#-#-#-#-‚D@„ÐL€Ó@jTÿjiÒžh*zÕ=G©úM'ꞣÔ=MÔd=@#C#- €#-#-#-‰4Úf‰¦M14iOOz£O$&4õ!å=Li¦€#-#-#- £F#-#- ¦¥@#-@ÓM=MCÑ4ÕÚ˜S'‰#Ô¦G‰£Ô#-#-#-#-#Cê 5M#-šhÈ#-$ôLš š¤Æ©ì¨õIéÕ4Ä#-õ#-#-#-#-#-þ£¤ÿÚK&*ÿ¢I€É ‘GþЪhÐ*ŸœU0Ú’™‚š¨˜¨*B© D àªl!B#-I#-QÂT_ÃÑ}#`#CýóÿoP?÷'ùDDŠîEÛÝÔÝÙróQð]Óÿ)‚ì·yÄFÄ:¦«•V÷!aðÌpPN<ÿ°ê#-8D¥p5ÌOõ¯þµôŠ§°:€°!@8 Þ¦(&¢©ý!TßЊ߆OpöÇIt:t.2‹Âj%ˆ)¡¤)¢™_ƒb8*˜S#CÜÙ"8*žn*t%\#`«ú•PP ¢Pˆ¥*#C#-%#`- " #€¥`”PHb"ˆb&ˆ¢ˆb©Š(‚Š˜Š#`b "¨ˆ&*I"hª’HŠ"Š&Š¨’&"Š¢#`š‰f#`J*Šjfb$Šš¢™ªŠ*šj‚f"*h‰#`¡)ªš`(I…d(šb‰h¢Š™‰–"Š š„$(DB¨)ˆªR’P©P¥fX€R”‰B™¢¤’I`š¨’Š(’"©ˆXa‚EJUŠd˜Aªˆ„"†šj‚©¨ ¤‚‘…bX(#`&* ª#`"‰Y€†‰¢#`¤ªªˆª)˜‚)#`b&B–†‚‚¨¢*ˆªª†H† ¨ˆ„ªZ¨)j"Š‰i‰bH‚J˜Š¨*†…Šš–Šš‚‚‚†¦ªfš)I(IŠ–a)H–*h* ¦”ª!€¡’©šh¢¦ˆ†’†jYŠšª†š`¦ š•`R šBVŠ’ˆ ) ¤™R©*"J¤‰j"™¨#`#`bRh¦ª Š ©Ja ©¦ª( ”¡$€ˆb¤`E”D™JX¨˜¨‚‚¢I¨¢!¦ ¢˜Š †I(†R¨‚  *b‰ ™B™f" ™¢$ªbª)¢& ˆe$("Rfb¢J€ *‚ˆ¢)ib‰")JŠ©’H¤ (ª¢J"J˜*‚¡¢’˜©"YŠ)’Š*‚hªJZ*ˆ*¢"ŠJŠª†jj¥*""& ¢)"V&*(˜hª¨ˆfBˆ ”©ˆŠ* ‚ª©©˜¨¢jX‰#`aš™š"Š©)¢!Š‚…‰šJ#`˜¢!ŠŠ˜†ŠŠJH!¢ ‰Š &#`b ˜¨*I˜š"–†ªš*b†ŠA¦‚"†ªŠ ‚”)(ˆ¡¢‚‰˜‰”ˆ¨ˆ&jˆ#`#-ˆ¨%¢ˆš ™j‰%ª*"i¨˜€¨i¢"J”¢š"!˜*‚"Š"ŠˆŠ ¤¤ˆ‰"‚‚¨ ¢‚šš¢J*#`b"Š""‚h˜¢J¢Jj˜(€‰¢ˆ(¨¢¢ ¦Š‰ªˆ†š¡h¢""’(š(f"#`%‚b†b)h¨¦*Š „$’ˆŠˆ‰""™bØÌARSICTÒDÕC4ÐSDE4Q4Pı4% Q5QD$QE),IT²KDAM)C±QQQ)S0U4¤PI5E+30ÒT %H0TK4ÅTT‘K1A$ÔI2‘%RD”TTL$DT…RÕÑ0EQTQT‘0QTIQÐÔDTÀTMQK0Q,ÒA0Ò¥4DµTQ!$ÉMSPEM0SA!!EUQ,’MQPEKMLTK%A@DU%4D”•MTKDM-4Ä„E‘--DË3UMS)L¤ÔBÑE5E4ÄÌÃ1Q-,R@KERÅ1#C1A LTÓ#C0DTÔ4PPÄ…!AQA)IE- Q$ÒÅUÐTDÈRÉ1D¥RPÁTÁQ1ÑKIRÔµDAÒE4L„°IKAM5QTDEMA4ÄJ T°ILQPTÄTTÒ3DQEDL±!Ð2’0ÅQ%4RDH@ITQA"MIQPDÅE@Ñ50ÄL!ÑRLTÃUMSIUÕEJCEPÕH)#`)2PÓTLÅ$ÄMA%DLDAS5%RBÁ ”2ÐEÁDK3C,-%JL#C0²SDUAPQD´Å$S1EC+ýè0³ bdUPd #-šjV&¢aš ŠbšY˜¨"J"Š¢"Š¦”&#`Š™Y¨j"H ’¦ ™ˆb*š)"bB"’š)¨˜ˆŠŠ¤”† ¤ ‰Š”¡#`J)bJJ¥ªh*‚š™j¢ˆ¥ª¦‰Š("*ˆ’*¦††’#`¤ˆ‰‰‚ˆŠZh#`šŠJ(š˜ ""h ™"¢¥¢*i)Y$hš‘ŸÝ‚af‚f’IXf&ª‰¨"Zjb¢#üœâj%¨%j •˜f‰”¤™©F&J‚RfªŠ#`’JI¦*fHš#`$™ f‰"ª@¢ ˆHba ªˆ¨ˆ¢Z¢¨(#`I™ª#`*J#`a¦Š†š"Rh¨ ‚Ib#`H¦š ª‰¦fj¦!Š(*( ˆ& %"¦¨ˆ†eŠ*†@!!‚ (†š ™(‚*( fF#`"¢ &a¢Š¨"ªR‰"BX`‚XŠ¨#`¤™ˆ‚Š”€€¦R†¢¨hHH¢”¥’&*†’R&IBüχœŠ)¦šZB˜Š¦‚ˆª#`Hˆš*†‚bŠ” ¥bª#`˜¦˜¦f¨¤¦† * ¢M(L+$S5%TÍUSAA-T‘P TU#C-UDTM1LÅ1A´ILÓ3)AC@R,CJD@MMSÅQÍ)TTÐQ4RLD$EMÌLRDÑQ3,UL•@S$2ÓIE4Ì5-,ÄÑLMEEE5L@D4•D2EJDLEÔ”I4• 0MQD‘R”ÔRE3PM¬TRÑMUQÍ2ÌÕ,M4CLSÂĤ• HA!E%M1R´!B´C#C4KTÍ(P”´”Í LI4THJSK2CADÅ-UP’´Ã11QD…P„EBÅA%Q5 JÉHÁP¤P @KÁ ,D…3 A@#C%P,I%Lã$ %Q0Á#CD‘M#CSE-(RÒS ”L1¡@ÐT SDUHD£ „ HÐD´A$©#-DQ@Ñ -(Ä,Q”P#C T…AHÐÐQ%4P„E+T”4¥4´‰JÐÕL%!HKUBÄSS#C4R©UJLÕPRÅAIIQ1!LTLSU@”U#IJM4!K@*¢ÀÒ$SDTQ0DQ,!(Ä4D„ÁBU* SI, ’,3@„Å*1DÁ!²”PŒJHÍ4´Ä“#CD°0SI AK(IÐUATA#M3UIE#CIUMDQT´D%D´S-T„@ÌSDDPÄ°EPQ1P1IAAPPMT°ÐE#CÃ,E$À0ÌÁ!SADJUTÅL1DQ BÊUTLSR’ M%MÉ%C3U0CQ4U5TÄŒTQA$Í4EAESIT5-IDD1 ÑIDTÐLÕ411#CÅ$ÒD0ÍUCBT@MUSÆM$M,ÔA±%1QUTÐL!E-,ÁLE44PA4ÒA#-DQA5#C)QHD5%!-2IK#C+ÔCTEDEALBPPPTHUIA3DIIC0@L$ÂÁSQ#AC#C E EM!R#-RÈ”ÉE)0D¡T•QÅA1MPTPRÍѱAU!PÄELQ2µL#CP± PD„‘ KL±-#C5EÉS“"4K0UST3RAIM#C4 ‘%L‘3-QHDTMRR#QKBPÓB4Ì-ITT±ÌTI$³ ÐÑMCTŒ4@PÕPLU%SHPRÕDÄÒPÄ0ËE±P”QAB©AHÑ”ÕÈ‘QEQ•B-%TÌÑUERD‘ ÍTDMMHS2TC#D‘EAAIIM!QQ M)EÒ³PP©J’UCJÒÌ…QCUPÌÁP$IMUADUP4ÑS-QDà ‚ÒR#CQ3JLPÙ¡ˆJj  $„)ˆ"•¦#`‘")Z‰ibh()#`•‚’#-ª‚Š*¤¢""šh‚B""¢*‰ªZb#`  f"#`)¢•ˆ"‰©‰™ŠŠ&*¤ª*B’ ”š($%b(‚)" ’ ¢šª#`)ˆRª˜ˆ@ª‚‚–a"JiRˆT¥‚JZbP""Š!Jˆ(h"ˆ‰j&(&"f)šI¢š © – ˜¡†”bF$J‰*ª@ )ª#`` ¤B’ed„J€¥B¥&h© ¨ª !J”’‘(¦Y– š#`#`Š ¦H˜š†ˆJ©i(jŠ&šJX"#-¥˜ˆˆd"@ª@Š Š&@*†#`ªš¢H &•¦†) ¢iiE#-FšªŠ Š& (š"ä覚*†Š#`ZI$ˆJV‚¦h(*"Iˆ€hZU¤&AJi„ˆ *abT$%d"(š` ˆ¦˜#`J©)i#`!¤$&#-Šbfbe†ªZ)** (¦Š""`ˆ˜¢&)¢˜¨ ˆ¢”&€$¢½Ì'ûn8u×(ÿ\#C3Ñdƒ´û}~¬,12JÌ@¦·Ù^ÆÜéZ^Ž’Ãwt¯èÿ¸ù{˜@ÿ¢¼/þÖßœ]hŠtvL*a÷?ôáQ¨HTñ·)¶ö»ß‚¸Ï#`‹ÎeŒ(?¬Ú±HÁ‡F³_ôJ)sþâÙù#`Â+ÖÔûáÿí&ôFÀù§4Œ×žóù?ä‹m§5âûŸ£ $Uˆ)¥P?ªŠÓkŽVS2 ?ão±ÉC þÓªÌyN¡#`Ö ˆˆ³÷¦üWõøÑoT?Üê ÿeŸ0!Í“,#`IÍÊPC…tìÁø©‡S‰Ú@¤•pD e÷=£OÑþÚ§ïÿ´UçþÇÙÖóíU—B'ƒ}B³ERB<ÓÒcþˆrÓàÂφüÈkL±ÃÒþúp6õ²€´×ÕÌ#`ƒŠ}«.^ —Ï×½‡ƒµââÉóå#Ç*×nÝ¢p’ÄD#ž’ˆ#Còý¦„g :w¶b#`ÇêÿÆðÁíUÀð²ñR»Ç«#ùL±†2–s¼.B#C‘¼qæTyt¢ý‰¦‰ ðÞ.ìEDÓÙWßÙñ8LaØHÿ­ãÑOÙF³˜t2?éNaù/qfÄf”¢¢Š*¾š˜Q?›•£s"U´{R†rMÚt„7IìrÌÛÕ§º@Ó#C0=Í3?–öÏÅøâDüm ‰ “J¥>ôšuðo3«@yÕ#QüUÒÿ£å¾ž;êê´Ÿ p$嘕4γ„‹ùФ;?&ÄQM´ó°Âb¨î¹qÊïߌý àá‡Ã2˜‘)ÍQl8Ib nÉ-F 9ƒ(JH/j¢Z‰ÙRfµB~jõ_íåXÑíu9'¥è˜›£Õ˜Z£d:ˆ²ÝÒÑd÷vЫ1æÑUÂ\üx»öžº·”ÞÞRc)N¬-”tëXú{­@ËgësëÖóŽ/Ô’R¾dìÝÎð1¢3/T–nW© ®°ÀX¦Â›È€:e“ µ±g#`RD<Õ ½!©Açÿm¤$»‹1&ÒèåÇÊù¦Y‡=Xˆ›÷@gïÛl乶µDþÎ×ÆuÞg‰ZѾýΨ1 ¦;°,J`S ”úÀé·‘¢%¤(¿x±øvOßžÊRSú3åh…ÐÞœ?G®Š ƒ¥ž”I¤“NFE]ß×ïìÔóßÎl½öß,òLëGFi¹S×F’‘ª_<þ¼¡ ™]+c•ŒwÒÑZÞnƒ?wu›¦®G#Ca¦ R(ü«ÞÈmöial?ŸÂ­áÝyÔ’Sÿ*våºPðŒ³Zòºœ4Š =•#CÞX­±fqì¸RZâÎi7f×F‰HD¥r£#`)¯Ûu`ä\³–NøP¬ss63êÖè®#gß· 4j@E8I#`ß[šž`s“1Š)éÍ­ÂB6‡Ös:YÆê¬9„>§Í^³ñ%3‚.éh0ÌÏÃèðùÈ,j¤´æ?vÇäæ`þx×—Ð`¾lHI˜"­Åê<åg8ˆ¸iˆ¹ý­—‰ “Àûs€Ü|Ä5ëÏþžñäj’ÅEó+ç°*E>ÇQ hg—·¸<]GÒÌD†ÌÔd-YšJ`¯¶‚£6þÑsß®~ÌvÙ8üµïtÑÝ#­ÿÙÇô]s˼¯šü´‡dË«YªUåRlÂõ\˜«®ôÙËú§x5ƒJ™/òn¯~Žv_õ!I÷«ìWó²®ägÙYË=»€Óï¿hZ(¼>ùù͘;#-Ä*’ {(8ª×­¬íÀÊz29뇃ç¹É"&¿¯^Ë×:fj«¨vj”qÙמ‡åJpù%;8SÍÕ#C“FNBl†‰‡ãU»2“K-çèâë‚sé¿r¬'‡W/Û_T†ÙòoçóßíÉžó[5êQ×w{æÊ#%2"ÆÐ… ,çè?T÷°Í†C#3pòe³‡M{ð~Öc1¢€;%Ê®ãgµ¢2Ù«0žt&µçÁN2Œ<¬Æ0†XÛG±Ä›¡Ücf`JÛßœÿúñPÕˬNÛùmIÇ1šjˆü1·û;D·„䟚˜éùEã¤ñ¥;>¬¼ÛF‡¢( áÉÅ[ÔŠÏÐLwŽ×0r@oò¤U±úÙQºèÖô|̽ýµ¨˜tV éŸ,Áì&„žûŽ†>2do{ÝÜ`ÞPÈN°O¿òÖMj„Pôv©6C }Í!úꘈ¿¯–¾¿Ãå—c„‚%ÀìÒšcþŠL4Âlø²Ò_v.ãÜ~¢°‹AÒìQù’†/Z(0ÎË묳>-Ù!ÍPÀ¾TuK:tŇ B"{¾TKOUVÎ×(NZ}zYðÒ…TAJIL<¡Î=ÎÚ›dPvæL$Bs!}z8Ð\pÂ鶧žd1¢Â«á™>QúÈMÙ€|R‡£)"Ÿó2^(;]hòmèæËîÁŒ4qZhïe2,#C*€FM߃- ®rŸ±×Ÿ„?Ÿ8Ãò³ÝÛ·L5–¥ñf'å© d¦™›×&M“ߥCoé(?~ù±Ö¥Dáâè>—ô¤–‚ ÞÜþü}ô¯EÃäp”!˜‚§}I)Džÿ;³B¨=HÙ){ûé°ZHú|sƒh—^L²,Š,§sîIFŽ¶µ»ô¹#`Ei)Ž$»gÞˆù÷ôÙî‹¿xžDsÏšƒ©øÃØøæ1Ÿ¶{錄ŠL?gi:WöLÊf˜ýó)àüØ–—ÚÉS«ªO |uÁaÈjCИlXÉl):(ÞÜ6J0,'eZoL/Æ&[4?çí „Ý‘h:Cˆ#C&åó×Ñ7·ÿ‹„øf>‡?é>;úîþXn üŽœ´®’‡wÏ<â[„¡Ú¨Íõ;fÛ*1‡-Ü›ñoS]"«K…#`òä©XØżT šÔWž«›ü5;u¿]O{›ÌýS|éÛ)‹t#.Îýf™E#œ˜aò3 gt–â”$[6:ž½>Ç0?5Žz¿yÃFßèy=Ý·²åJ`¿›ŒÖµNS™PW¯¶.4F5¸¹±Nmh¬ylñ‘¤Ö>h§]LÀùX€¹#C·ÇY²ƒéGôa)íˆq”:ÿÆn?~Ïé‚ùMÏ}øÜÚÛ"Q0ÍÙLX@™7e®${º•Ê9ãŽeÖ/±O\â#CѬücÞ"vÞÌòíÊcBåö M’ÓebŒÀúˆ˜ê}ôx#`#CÉsî‹ðû¡±ÿY·Å¦F~«ô|U/3³ùŒôAŸ=cÍcˆT÷Û”·ÓJ1»kï æbì$í_…úµþƒ#`A`£û85í¥{<RJA` aŠþì?ºŸâ%>Q¡í¯ºGq¢ÓâÒbŠ1Õ¡e¤Õ’Z4k)…×=z|ú¨ãË[%V5¢“hñxn(¥"ô yB§ t†¦4‡öãÓîï/—ßÏgCNÙÖÅf«övy8Ŭ>ùìž &þ‡ph?ÀëýlÁìWl[b0Ѓ£9tÖ3”²ß#-ïnÔünâ /èàô¹áâ7¿LÚø€=¥ÛâÆE(P|µo¹Òr—‘¦"m(3`¼²s#C…ŠŒr¤ž+o_³ÞÚ{µE+‡%ÎVޱö –óË„›Tn˜Ÿ¢ô‡#`ëS‹²áß×ÐýÐqïzòuýZ5èGª7ye89ƒf%Û–Øåüý Oùó=X¿7Çx´@13hÓLþþ^ ¤Ä¦" ;K?÷¼ßñôsŸÅÌ™>pÂë‘ì>—œÅ8Ú=g‘yÞ¡5#-ØÜ‘ëQ»iþ˜+Þg¾¶#Cùu‚Ûë?ŽþÏ“&òï2IË_‹ÌÜóuòc#`:Aýý µcÂ5,ÿ˜§ù¯ñ»‰–L.þrfœÉÃÀ©ðùó~´°B‰'{„7fYLEú°gŠu¢0kDQ™©]Ü>Æ,m¶ÛD³ï°üîX‡3, ÄRLƒ;,Ü¿1‘W·„Ì„k¹.T DP§´È ‡µV~¶.¢ŽES$ƒ)¹W>LÃé°Ž÷šjWœ@‹ÃÁýˆt’>hÿ7hƒ÷wÒÁÍrŠ6MÅ2 3ܲrŸl,û„b̲t±tŽ–‡êö寖 ¼-'¦tþÌt…Ÿª@:Dž¶¾;gÊãš!”>!öT¼ç#C‚†$÷ŠKå‘Œñ#7O¢ÃçË×q´j/VCXqµŸ¢>Ž=aB¤“Ëílñ©1oI' ë?Ÿe@JÇÇÊuuu—†u< å"SÉ ø ÉDÒ$lß—=N/ÁŽOÌ®WÒÇ~ù¶Yµ†6à-Fb=– Ãͬ*1÷øüÎENU¦#`ð8Bë¯3êT§Ð•tS’‡É;û¡7ÉÉÔ4eÎæP·l`¾UÖ;ýÏ‹ÕB™^ÎËx bؘ+FS÷ãøwŸõ.™"-ñ“<êS‡ƒ—qU¢ÉÇØA}Ç*Ý4šˆ!3´¤Ô:òIW·7¿7ŒgäÈ©ÕòŠÓÍÜëw kOür0/~fçú&̜ȟÓ'ŸëÍb5Ì<†q‚ÆêæX›ßYªH-=̨M Ä¿‡º§”b!ÇIöã(D'cËwÍ5Cò-«ò{Áœýé/¿\m–¹¾U;Å:I–„ñZïLJ0éñC›Î}èì‹l ¿ˆomã+ï5šÖE}zôÅîüªslë„iõ2¦50Òð“œ`¨Uÿ_ïz­CÛ>uãGÛê_5¦Ù,Ü5$ „\ˆÞñ™ˆz½3eðûñ Õν”Þøqñ#°úåV5Ÿ¦ÓÉïtË-hÇZl#`ã£þ–8eœég³JëÓáô"4H`Îc2APª½ëÚ7ùõã„£µ|$ªó¼2Aí/ÛWw,m1NÇÓó87Ò#`À°;Å&%B›`XU‹‚QAXŠEééÊD%¾ÊR¬:M‡xGdBc_ôÀÜÃÈ„ÙÌ4ªkùm'„e{ëÝD®ïï;r¿f£ñ…qåT…©…ù)ìô(¡È#Ñ&C¸ëì—4Éy½O/“ǕţŠ:ëÖÄî|í¨Mn}žU­×=uß}s…a‰Ý:}ÔžÇj0›¡²KL·Ò°1êκ˜B‘n#C¶G~ú®m‰¦º4·©§§+™\YõÓ¦L“ωôdnM_ìÔÁý¯É•½Ä¥Š0åsÙ^Þ¿÷µ¬á7žÏ\TÔTDyÝe°‡rÇ7.!Eü–#C8F!ÈÅyÙçU(I‘F“Òèªp}|Öûë±åŒý¶¬“ÄÁ§öþ¬õ$?nÅýŒþÛ¦Ýßl,÷eÀ²Õñȵü|æñž­¾ÜgËã¹yp“µ"?[ ËÊUõQºUdÝe9BÒw)¥ÙÌ °¾ $Ñ¥BLØø¹–Ÿ›Ÿ"Mµú£›zÍ­Õ½Êk«xl] ˜”új!(áÊM+é±-­_éô¨9Gà3÷ýÞutìø,pŽ:ùë¬óÞáeÃÖ,ΔýL$`àû9r:±èþÜ[ŽùÚ08˜õ)ÑÂ¥îÛ}Ït[fé< Ù0J4CöBXFä÷êñ¯¯·ìãøÎŒ®z·c‹'£7#`ˆš¢§¾¯/'=3\ùRƒÊoïÞ`¾XâþOÑeA(ƒ˜r8‚>/¤Ýíœ®Ï ·Ô»‚'äíßäj0_ñÏjô±°š]ÒW·Ugã¼Õià‡Ô|£Ú¥\…ú|kjuå»SZáJÛdÓE½$®2Íæôfh×–¿'Žé:s¶ra7$ZH‡H9W¥1Q(N‡-3)Ùe¼Wï6òß ZÚw_æùØîæ.GØ®üT5yõM&à×ôɞجÈ7>ÑE\ ‡¿&³#-Þù8†1òš´óœãÂɨËKZç#-Þ“ AG£1Ž×Äx칚Ǣj°µö48ˆù¸CvþW2\CŽ‡>A5´;Ëèôâ'jx0¥2^Õp\*=1¼ÕùAú1ƒ*mðä(˜XaùµŒ£o>»C~–+:¸ï†±Ù½ÐÃì{­Ãõ[Ä„x6VjúæBAoN2Âýΰ–7 j4þÂ|]ökÛ÷\ãƒííÚøÑו–=¨ÿw*>m*š#C°fW?L—Ýñ¨‰í~Rr#`_=¶¯%¢—¤T)Iqo8HäåJߥ_ïa©U/yîŠPC‡ÝÓ_>ߊ¸Ú³“²BCVBä3x¢™ò†Ð$yvýZ#ßðñãÐí«§—f½¹¤7vœl˜Ç"€Ù|æǶ)QÀ }„[ÿu£ä©ayÙ÷|ë¹Û¢Ž·r/¿õbʤanÙz—(Iu›UVöœ5­}kDz­¶Qm2³pf€V1g\Ûí'Ó¬F5ö9úŵ#C/åfu3o<áó6ðw5þ…óú'o‡+U ‘|ÛÜ&C…K2‚Z ÍÅÐê‚ÁÚ0yŵºôµ­:utdûðMQë#C­×ùîÐQð 5Tøîu.í~÷Yæ -ƒö¤s`Ä‚B¢¼Ùì¬E žjfÑ•‰[flý·2'rû‡Í~(fÿWùg‰aèvb>ƒ‰1?ɱå ÁgþBñÝþ~ÝÖ{êÿ#Ä÷‹©¦k&´NCNì·õÈo<#¹‚ÎLÂr@ÝJø´ŠéPוµšéxe¥3çKëFξ³!{ˆì¿+‡ßýøÐuó+¿~ïÏs £®aï‚HwøédPU¾;§#(ø—pª³yXhåì-vÆ>5lJ‹±Xò9ï‡Üà\}DѳÐÅÑ™ChÙ¶ëDµSíZåjòík²¨Ô:T|œ!$&ïߨ$6¨Ý”‰©+›z9H`AqE"¹je›w¼;”7ÌY-*m³®þ|:è„qN·|M1‹Ò4¥ñ£Œ1“ë­·Ò(kU©…GÓ³Ãì,çkÃtZÁú5ªgXÕÄUùuúžLÒ‰rJÙÂ}©å}|}úë}“¾’–ìíPé°õ+I-1î(©þ9íت~#©2^‹Ëº5FBúûÕ{htsO| Ù¯® ‰³~ á|wÊ^¾%±ÊaŒ©O—‰Ýøäç;bQ!Öþ]òë^\¼hØqÓsÇÑ<Ûö¤¶ÉBÿ‹êB¬½xïzºöîîÛshmòù+#`„Ñ”U#`QT%ôFüIÞ *Ñc—íXqã­Æ7õ>9 ‚|’mìÖöêPÔa0 „þ.²‰–% ù£쟞Àvé¬â]Ë’pûˆ+SïfDA~þÛ.4øŸ[“‰ô¸h#-ö³)™€lɵò°¬ùT¶ÎIWP~š_¿s¥ïßsŽQ„Ñ‘6FAçAê“Ìl‘¾#Ce2F"HØ»ABkÞHb=ñgŠL†Us#CòbANî;½ÆWUôùèî·(À!•"‚L×p‚/)žNb¥y¬iƒ‘È =Ý,ì—Ðî+T8iÂ`l¼—Òjæ½.gƒŸ+(ϽG™Ãwìªt£-#`ð,›#>“—×öãMJ§Pòªdb1’دÀheŸW EBýg¤!ì÷öðN’ÓmÍFyÿ€Òa‡-Ž¸GÑ>gs|"ƒÀˆF’:37u#`4ì_3øYòáÅî¡žÝkå¿ŠkRºþV¶†Xy*ß°“IÉÛD·ÖO(‘Ó^c¬]Re#-Ñ^·FDA]R*7š½=8ÛTØ…÷Øœ3¤(9%´¡Ël“Éß<'éæS“B4'%;Ió´p„å/ã6á¼ î“£ä‘,o¬ÊZŸ\´h43«X3–L ,-!üÚWvÝöMS,?O¯ÑdÈœiRŠ—'#-ª*ýìzçõú½ÙÃ#`/”#` $™>Ϲè¤~û§É ð—ô-¹Ÿ«Æ( O^—‹„ã/Þà¤KZ0â³³ºùå[âXáôÝŽvâ|ñÍ,ºÖòZö<Õó_˜¢Ð¬ù¢„$°„¸˜ä`n–™\}ÝolÓÉ:!æ}ûýžg8”BS"9è#CǽCÁ#-1D·¹PÓ­uDqs û`Ô pŸÕlçƟؘó€3½½ŽÒïaó^È„¨ÝmçU³š[|ퟺÿÇz7ƒŠß{ŠQ‡X™šÚïÝT\ÁÃÉ*~nIþZ1%+Ò¨ÙÌÇø‘ÝpŽ7##Ú¤¿½Í(O!Íüò}™hû;û>˜-b;üpGÈ‹¹î6#`_fsŽÉ9Ü›¨ ¿³$çãWMÍH¾^µ©ô -jÛç * ‹öø±¥b#-¬†LœÀ<c^{3¾fýÎwˆ uVoGQ‹«® îÖ#C›†aœ{*ÍØ_qu™yJ’Lø0cÄ0ƒ± QRáÝ/ÆaŠAø×õ&ê_—Áá÷™‹›h‘!ÿzxñéKòªÆLÞ¬3‹n0¾+L"g|BÌ¢®J)ƒÝµŸ#-u·–SVœýǃQør¶×IZ+¼¼ã½¿—¢¯óT¦w îŒêŸ;‘VSoä³ÎÜUžÊ«t;[å}+šÇ'’øª8s¬*÷ ÌÏT‰aÒõb\×’ÔÞ åÞ p¨@ß Ññt…#-¦S­5Ä:©[ã“iï]¨ÔT#Öê#`¦?·-¿è–€ù½âWJT9"†G ¿+ý4c»º]“³¯lŸ¿.Z®z´Qu¶'öÔ¢ú4±3J/ÃçÒÞã3s_R«¹0Ut}ÏÂ×ðYT‚2-¹LŸ?;Ý5µŸ8þßÙÝ6‘¨O3Ô:Æø8ý¼9_áªøpæ© ™aNšR2#`ñïðUÄ™À9Š3 Cœç<þ˜9ê;Ô9-úµïýj!-%½â»¯hç$¤Ù²¿‹Ôeÿ«ò>ce²V8aDG=šO§Ç$‘u#`™* öÀÚáÈá;CþŒ”ù@QÑ} ÁÒ <|ýqŽ™ýu…×εný8`ÕšÞTü­šÊáŠn˜gn(:ë|¿w§‹Õ=”kÁÌN8!‡vI2Š3yÁoš¥$ÒÀ¦b´VÞÜLûø…ûá]~s73‰ åc/ •šÇåk¸ÇâÛ^çCØ…D6<%5,([åa›üùŒm3Nb®ѪùÑîðäà$-­˜Æ€´.`_áØž©ÛàåæVå*åEBõlg[•]¥V@8¦¥Æ×-SæÙÉ®TºÆC$vÇí~Ü»^%ÛîËlò&A÷+ƒø÷¢¢ÞêB¹x·’ö:›‘ijàتz>úÿ«zÎ=lc(Iô´ÆÏ„—ÚééŠWtä ûS•>%ü<;[±Ø©Jˆ;*Õ5¹¦ý«p‹žV&ßêktÝjØšŽ-DÒ·O¢ÿJj&-á(¢‡ÒlÃ'K8¬Ç4Cß2zóóüv^î}XaÙÔ5ÜLâ,™#-Ѻ±@€˜I=gÎ¥WLÛ•ªÈÕ+p  lE<´cmP½ˆuëIfsÁ;Bôqçˆ1îéƺ¯ù9v¸f:jƒ'#`+zèÉÒ¤’¥I$½ˆä÷?&Z“ŸЉù°ÞuÏ¥žÞê…¤Y‡¹´Ã) %ô¯£Ju­F)ûR³V—élKðåÆThÝq)öHYÒ/²þ¥³_Ö°H¸“Yš!œúãLòiófEh骆dårÓÙ,Jž&¢`úÁQB!E¯ûn±EªM¼Ä…òg8*ßäs;ÅÈ EgÚá‘Ç·EdÞRØt0 p7Ûj>ÏO+Ý­ýpçåqm¢³†(Ïai$ÐŒþP‹pvë.Ñö*û—.#`æ÷×7#¼ÃÈ é²w=jPïT‘˜^û‚Ý#CE \„ç?. .`WÚOèÅ~3†P²îíÞ]#wg®&T§sUÆîùWµ#C\ëLi¡vC¾ç+#`èå¥E÷<í5EkQúW·a²çÞ‰ŒSŽÉ0$ÄüµB€ªïìöÜÂ{{PvB¾~ÿ·¢O.Žÿ=[#C:û¯š{îu}[Ñ3µCÝ$éDÇ.Ò£¡üeír»öõ†»~ßM»[”3”ç ¥â :#`|âëBwtR]wFÁòP‚Ñí`CŠ%H?#C2’0*jdu]^8Îf‹*-Æ‹%Ŷ³:ð²" 屃)qQÅ”½GÚxÂŒ7p[ÈRÆQN)Û‰ÌØÛ)’¼dɤý-S¦áÑ©„}j“:Ô¬ÑæýL4a wKnUR#Cq¾1ù×}Ÿ3!2öCæÜHkFQÀ£éPéʾÑNŠJËÖù”B^“P¤m)Î`6Üæd¦0jèªR9¨¼àýŸò˜ J¤8çØçò®É8d@ðä§ð³¬ø³ü#C‰ëϺØy}ã{à‡¿Sø$èîç𭪥/›N*õE3“ÞO|QÅ‚÷(ž+}œÏ7Þ©0I™Ï~Kžqq¬‘…îób2=ðKBòà¨ì^b´Þ±¾Í›– s½:Ò¨¯_d£ÙYÞCϾÑKöA2öêâ#`º³ò¥=É¢ØRjÑÅ´Ä·é~C¥RŠ¹â—·2¸z#`œwâr$¨¿Å9;f5øŸ«M&GþNÍÒèÝü,n3¿ý<žUúì´gø©†ÝVGèô\rçÇ/²n&#Qx—@ÝÌ9+Õ5éÌðúR¶§ž+µI¥Ï-hOÊÉÈN©ª/µèZ¤ì害‰ÈÑ=`ztâUãyB7íœ?ïdßiNH1”îÎÙ»ÀÈz8­¸©·é¿lÖ€p›]DMUÕVòjÅ CÞéÑfO£a …{ÈbHÆ`£ÊÍJ©>e‘‘Ö³– ¿ÛÊäí2š¥ˆ™é6()mÍ´¦u‹ êTr¸¤üª)q B¯X“Ø· ¸;\bXvú*Ë·"n%ýÑY~!ç&ªG,çðÉ®&ûk}eù;¶vÍöL0êù¸p{*Zª»Ù„H Ï#`ñt b  –LoPÅú|Ï7ýîY¹„vM:ú<*C:(Ê3•E'úÉB&B¿AKôkY)#CLE/ø7Is–%Hè߉†™èKyû6öµV§÷Þww®Îpxã#Cb·aÐLnQi4g&L&¬%‘úÃý µœWžºC‹ïÝ̉KãN$†xÞ}A×¼9kaþD=ó0ÈYš5¦²,X¨ÅóÕÙA#`‡âï®N+Ýä`þÿ鯧N—±b‚‡3äÚªC%ÊHÀçŠù6¢UÌ¢oùWðgúy!]+=ôRCšOòF÷zh ½Mkvð;†¾÷€Ýs©¬¿„φ؇r¸$$¡`ª9=Œê_0Ì·XZpb>#Ê°)w÷äÀPÔê(ÚÖ$HNö#CÑ£â`Ûíi—)(,ÜQÄ#`¯ ²X*‡ÅÎtuƈ¶ü¶FžwŒ99µ~vÚõ±s7YÝ Ð~æ¿ŸNoqmôòˆŒTÈúqkÓååË_HçwÍ\jKþ$ü«ÏÛFèå|•rgÉÛ¤ŒQÅ5— µšqæwÁüüâ)s¥¢›=Á&±“#-æõ4Š4ÚùÇ=ÛŒ½Ž[ª¢²ù`öÓëãE7hÚ»¸}ñÛQ‘yYFÞTò:õsLYyò'µþ‡+‚+{8¹—í.بóóÓ7)§ç‹ªwq2—Ñ nLbÎ2ç4|c>s’š+úµËÆ“‡!<¼Têdu¥ªd|ÜÙá#NçÅsÃV#Cý? ¬pH1(}¡Ìþ—è—læ`?ƒ¹Ò»ž#]^4qþmìÊÕ’XT®£#`þ¶ðZ½PVŠ¡ ÷,‹ËJDÞ©²#`/U°·òÂÎá¢~÷¸8åžØöÝläóc²Œáቔ9\=…)jÐ`÷ J76ÅU)eAvÅ:Óû*Ó îJ[«î®NègÂúQ†Ao¨,\ÁBKYzA8&$Lq$’ø©lî£#\Îw§Ô›7†;Ч#C ¤ÃI2X’„iȸÄábøŸCÞ t”ä®-g6V…J;6W962ú¨­E#`7.Ãõ¹EÁLãÑ—#CB31Á¡ùB°Ð¨é#`ðwÉRòrédßYvûìÔÿv¾ÀØ_ˆiìu‘^D¢!Ëgü¾›RÖ@|5>9·…ŸqfÜ™Êé¤]G…Ä©!ÁwYDKÇúWl÷ôúãb.2Š…#CôÝøè‹ìmŸ®ÀŒ#Ìù“‚)iÜ¥b#`BUöy@çóðÿ£æ×e³.:Œëß¾fåU&%ü‘hÍM;~¾Š8ÇìÁ¹ÎÍØ…%9ý«{?&<‰é Içû´2ÜX7õÿWÏ™0N{KÀžýv¬|>Z#Cóz%U8·ì ˆòx»Š‰”à@ "ò £ ÉçñÁTÐ4ÏgpáÞ ¦´‡o|>YüæÌþø±~‡á‚r@?!ˆëïÕL„¿`÷‚\ž£B;¡DÞè®ÂŠF¥`R,+g³è¹}oöþn#-ÿ3; |}tÒa‘A@„ÙDZŒnÖü™GV©iµÎîwÿ7ŸÛð_ãᯡÊ;â£Øuw/.ùû9÷1×õþ¡&#-ø+÷9Ò#`—œ ìDÔG˜¶ÜOi B”‘ ÃD #IAHL¢}Ò}Xÿµ§åß;£f'Å%ªà£ùÓÍÊ F/ÎrIá¹ÍM5c0ð)ÆzUC÷5þº(s¥8OXïçPÊ“?Íñùg]«ÚŸÐ}[ÿ.½Ôé`¯ UÉ@ë#-]Øtæ×¥£ô–!ÿ##$X\CHŽÙ B0¢¡#`i,T(ô²r*ÚÉ£‘Èåôxs–Ð!3Ò=´ÉS­IÈ iç]¥Ý–YQE)DU÷2B™µV ­Óli#`ÎÎc%Ë—šF“6¥7-±l¦‘L{³ïüÌƶ ¤Ü«Å¶¨lÕ}L$ïSàå˜xluëbôKáö¸í°hª€Të!‹<êr)r÷t³9:• ÷ ED’dÂyeÃâ׶Ó+nqÈ‘ð‚õF¦©CÞØÔ–¹ZEÖœŠF“åYmd;AAB„˜rA²/ç’R´¦¯¯9_$k—qì*ûÜ"éTNI!LÍ)ƒšüöRz{ô¼^]ðQŠú(¢·9(ÃfÊàþ¾#`6Ïù0³+Íš *GE׋Ið9cüž5¾„ô§ƒEÆõZâÌUygŽѼkËc±U»ò­‹Ž,ü«K†½¸ƒ^éšû?ðÇ uèÛH&ò.¶Œ<$ ˆç:Ì:Ù®mž «,óþ‹Gèß:U£vèûf˜æþãöà_24̤’ `“¿Ùñôyß Æ<þw×®Úbû›Û]4ÖêBÈõ@õÙúaÏæþÝ8,ý½»í!X'ù†)ÿPÖuáš*|U#ó¯ðHïm2Œuû¶d«#-Ê4ÿŒÛHóF7&D  aÕu²h©aa‹wc¢Òd#`h“Áj™òxš Å¢íixH…JÔäùY‰‘ˆqô{ìÛÑЗÌñ4£.ÍF†çP5” è›é˜ôùKir[6Œ0 ñ¸‚‹ÔÐJÒ^·†¼»lú¾x•ÃÖv†çðäÃgSo4Å~”þ^Ãè|)vЇ˜{ÞÐôßé#`)Oã´>Œ¦€9¸j#`"½]<ã @\‰r¡^×ïçÈù'œBýEžÄ0‡æùCÛ7}¼Mì©ó56“†¥%·ø~Ž¸: d`ªÅƒð~ñ=ǺTŒù:'ó”}_ Mëß‚pŸÅ&ƒùËǵY>±ëª¹<($)KÊÅÌ=û½Œ£yP†÷øÌÏçÕ^‹ r¾ß¢)¾©'fããR:dá%ŠÊÔxš¸¾X—dâ‚C½F¾mC¯ä‚ä„Ùê绫¥ñó?~#-\qôK¡J—Hd¡Ã@<¸4h,™QÓ§MûÏyâÜ,®.’ ɘÙ°`W"¨Ê‰A'ˆÂ T@Ò!F\ýÝ/Žöé#ûh²E!­ÖçÑ5»<å™ÊâXEuÍÐÀ?HƒÍ\ùg{ñ‹Ä=üªùùš3]Tþñ!W¿Si¤I× gœ¯{/–3áÅd®¾H³[ïp×ÏÊÝ%^ÌÉá2f0ˆ_Ö¢j¥Ó ÿ5`hËXó_ÙËÝãX¹éBO¬_?ì×½Eú³qå´a&+*||cú¼ª›b´Hû~©™¬ª,ÞUÑŠÑ/»ó(øè?ÞM·¨çw?É¿¯R¾s÷ï±è×ß΋“(¨LA ƒ‡á‚ëíøžÝGüøzztÑS¶z¤!úK²hìe£‚\  ©flY ˆN u=Ë‘#-Þ}ïRûØ]#C=pÒ4•E#@Á+T${ó"¸ÏÏ·Ÿ»ïºÂÈr¥¤D¨ä#C'+¨ZùOç€Þ #CxF1%}Ýqt‡”O²ÐÒ-¤NšZX‡ôÆ”ïìþfÊ㻫Y+:Æ~r9æ”~Œ67€P6«#-ÇKfúHF÷÷vÉûn,ÕO3Völ=Ÿ}­výÔtÖ2£–˜^‚Q8N'3m‹"Y×´_üA¡ÎÇ[Å¥Z¹~ K _€'Zˆà©0"ãØœBÜË¢é.©Û×Ë`—xõ}¸j¤ìeÓJ—jóß5éQ/î"ùÁH†4x .j%°^Ñ7­\{Ãïv%¬¸í6#-—jú¡Œ^2ã~¹—ò³£ øDl@E Óê¾8évÇ„×ƱtÔÞ´’|ûnßåFG>ݼH›tƒ‰Œå(^åf§†~8œEÁš «sÆPß«\®#-7IC«ÌPtm‡Ÿßá^NÎÍI³ÛÝÛÈ5ä(á°¦Á¹ÇÞŠe#Cï^ˆCB“JïE¶=JÔˆµB…?ÍϱðÛ&ïJd»ì'd÷·ŠõBöM-QJW¨2RÄk¹ÝÚ$ˆï0Ž&jMµ!=ÏcÊAìS@¦ù¡—tšÕõ4-Úˆ:’ÑS¬\aÚè4ç‡;̶·Èþ,qª/ŒØ´BD{õ‡~é1#-L©@É <Ÿ'\bóœ\£šŒU µ#-V »4}]ÐËš©4êñy¶)0᛽N“±~÷QÀÓöÅ#`¡Ò±¥àñv¥ A¬o¯ËìÆ8â,%{€×îw‰Æ¢ˆ¥’ ´ÔM0Œb* "úV.Ñã°k Å4 «ÎJiÙ¾mix¢Àmpá°Š4І•I*44ö×-biùv¨¼DÆ1³0*+'ÖÃlÁ*JÞŽ¸<.¢Œ¶J4vÓÈdÖÄr _¶~Rû<ç¯gžÂi(–—l“y—4üäÌÒy`5Ÿ\0ò€=â–’ åô*íM#CÒjO^`Îåh72ÇÕ­ôš\E!ù龜¯|F`x43?;ÂÄ¢aÍ©pß”ãðáV ±}±¢‘î0´Ñ¼}þÍÂ,)» Íë79^ì±EŠ9×AÄÎIƒ¹fBGËYø6|ÀoEÒ^ô‚avzY£"•ƒ£«hȳ®Æ."câ7ŸLn#` ‰-/Ï¡“zØ”9ª½EÛP„&Ò¸´×2òÎ^­+èê6#HP“ÓlÃ$b#`«6RjA61 #CîqQ‹©UpÖ0ÄñÎ& ˆ0âø±„è¥hâk/)#-¶ßsjba§7-I ˆ8‡ ’BB&=Y+Ôøhä¦4§há1ĹÕ+äåÃÇU˜×fTŽ^0ºÞÂöó«jÉ@e”&ÅpÚ¨lÉÑ•ˆ:õ-43¹Ô:4ˆÖØ,¨Ñ†:q¢X£?í0êôе) GxFsº•Êùeé0æÛ¿ÓY™Æ}¾YÁŠye?Ù[Aiï&{s×ðùFÚávÓRÞ’-‹Çàw>¹€§Ï*ÝhŒ8^¥Pl.³§9±‚pdíæǘEé]P]\*öê¢Ì¸¿•æHŽË\ÇSUõ‡.pÇEŽyÁØ ¼…CÑB¢ÞÁ‰)Ø@Ycjg#CNêúU·eʶ,²KPDr(M!#ä×AUŸ ×Ó#`XwF(ð±2GŠPQ„ö#-ðÙÍFÞ=TŽ*¿Ýîí­ü?žcÒGÉóKô@níãÉO0Õåìôÿ_?“ÚFÓÍJMmÞå½NÜŒL‚t¬O #WÎõ$˜‹5¸=LÜ L‰´÷>< åi\Š›¤©TÍ>,ˆì8ìü¨àÕxwÞy\•M”}«HÝI­Â#ÓËn¶§;ŠìŒ;“Âê¯8½O…‚·Ôòü+·ý*ÓwFц·cpž0JÓ&ùã©ï窑Óì×Â×™õ§Å…?ìWÕ´{KèóúÁòÒ#Cô”ùcfâGð{†'æ("Eç*ëñj·Þ«ÖŸkâØŽü¬/Ÿ­“ñì—GBÊF#C¼˜êÔxG]HÏTw]·‘Ýá»Í¡oNÞ¶–¥™N áþí<™|/¡ü©³|=áðM$ìäuU¦i\aÁ[q.µ ë¾Æ¥EL|'±–õ³÷_~ò£aÛ<‘p.Ù*šŸ€q¡€o¨™òH é¯'ÃÃw·EL9å#-÷tjPÌ ûE°£(:·Ü—Ø”û-Sü¿[êßIH Á…H…Eêï†Ñö’]ȃu;G_n;Ïž_ÏÛw°ùþl“¡Ü˜õÏ.Ÿ²ïx 9Ÿ]A#-ÿ¾A#C`ùŒáܹmñjJ+ÄAÔåDgQÖpz0åxpq#©#a½âkƒùfF~ñ¨41¾#Cû¶iù§ß:uÚ{èÛƒ‚Ž¼“£È/ˆ»ÎÓs‰6±Œ€ú“t!Àëubö”ÅДås;Õ@´!ÚÖë@€j%o©÷Áå,E„ûžÁ7ª­ù¾J{$šÉ„Š9#C5DÖªûÙ?â e½û\)º™â»-A¬Fi´¤ÍÀÕôöö#`‘¢  #-?rMS—Ú[/"}i Њöͳ÷?Ž6ÖÞ‹¨L‘^R>ï××Zk=Ž.fÛÞ¡01 umïi©Ë›ª~Ë×!ÐÔÍÍVr†\ÈÍì_;Ýe/ ùæ¢g†‡Ÿ+£ˆöŒ¼„˜Æ_ ¼£1”($Í Tžòn€ÄÉ~—Ï]JH¢‘êJ?ÆÜ´ߟ¦M½ï-¥#`øG‡‹æµˆ•S¸ â&öt&Ž«£¦»¦è¯[ÞîÏ=tþÙ‰X,i*ü225#qt[#`ŽõÇæ§]yêeDÃÒãTãnL$ƒº‹|Þ2Ô׿hYn¹¾Nø|â`óäœríßÊ[uVø}!¦ß4nžš”#‡tv·l!ÐOXFqxÅÒ™¥r+eéƱ»<½zhéøú:까èmqQr¦Ä°¸¤@–Nq¼u5p–^°¨H¸bkØd\É’½&Lä€F¥Aß’h0OÊ› EkÇåÕ£¨#C?ö¯ ÃâÑÐwùÝ® ‡¯1é— Ô]Ïùj ñ»rQ:¥#`ˆÅzb#…ôiôŒÉ?%Yì##-Ò‚ †Æý[ÆÅÿ=üý#ÇôÍ¿ÁzäË£g¼5t;¨9Úö75Â-J£jÛˆzÎ+ÀàxUü—j`ÑÇñ›ånZ¨‚A‰ãu²ÝvK–©yõîÿYʽßn›ÊAdYy:bÄ £Õœe+Ì0et<3Xj3òXkM÷ä1BE3#-Z$‡ä¼{7&×óÔP[ܲۯè[£Æl#`“´jP›lÙ¢P<Ù'£hððϤX“IB!™v‹m­æ³šñ$CKü#`ÜÚWÖµÎç׎gŽÎ$$ixuÜýjÈJF‚Y•`ÞU6˜Àvê¨Ú³A†Ëº¨±’¦Àçdˆñ¯·[÷(hò¦\”k€èØ FA³Küܺ·f3ãKàT3Ÿ#CUψÖùŠƒæ2ÞôÆ×]~JÚ_{Žšðòò᎑9CZÖ:\Ê8é£jš™~Ž¿V"ZÍ(®Ò¿¯úåM¼¿H†áý¿ÁÒ/íã•ÓR9ºÀšf²åAÒ@¡R9ös¨±—CœEèB[‡£žAãÄ¥X?'j#`ãرgn4"õ¯M¶±y¤¡ç." 8m7‰B†x;8TCûU{n»|“‡õÒó]Aé »kûõS¹ÓäÞʹGh#COsÖýц÷ëŠæ6BZc&Z¤íRövmR,6}«„AÇÍu¼Á3² ýEßc'Ž¹½ñ¶3Þ.`´¯ËÛV~cx&xÇ·)¹~$¹óÓžˆ4vü|Œ§1®†yÝÔa‚á‹ #`mÒù'7idMÑ„[ç’µz›VñmnÏ^p#-¥%¡Šdp°šóbÀ›þ#C\<ˆüêÅ]…F§™o²•vùÖÙ#V¨û[BtñgµÛƒ4×?–ýW`ìŽÝ‚•«wmóêI—‡Òž™©÷Ñøζ\óøƧê’fÔ³+’D¨Gƒ–ý“ùá‚Ž¿ƒÆw?Oðj ¿,æÃãUb5‚‚ë—4žØ(í<å#²ˆRE‡çjU‘rñåϪ™ûŸ3*¥þ×À®_…øítÍôAhêê£tPÅæÓ¨ÔMè~7ºc“@s½,ãSð‡ŽvÕàò›}ófeü-² f¥±¤K.òå~2ÆÝë3ñúy¿lbBW„êÅꢜKZõ_y™É€ù¢áð»mºíš5—)38ÿ.ˆ¦æaãæ½wüK!íõ‡ŠRÓ±tÏîæézb¦ö–ù¾1SJ§Š½U!ˆcMù>Å^öקÑy %`‚5Dù<ñCŒàÔmíOEoZ0’S8j°€«0±0—ŽC»ûŸó£ìÍòûyn<~Á4#`ƒ3Å‘cú|ÞBGŠFgnþýgóóÉøzƒZßµjâ‚©¿‚S¨ml’“â¾#C—A$¢é¼©Ùéf„kQ¯QGSså'Ÿ¯»Ü)aµJYìQ‚x'‹˜ CYl}›L‘â…ô¼sCþM„“$’Xž›—#C$ˆ‘ãpìšwt÷nß„³C-湑ÕúÈ@HßFñ=û±´±Ò3"cˆw£õzHöM Ôo2\¦xTDQGS0 5DïøFzÄÅÌ-Ú\– /`žBÙQm[ônë°ˆš,¯Íøé{5<#>c¨#`3¨½6åtúNE‡~[ôØÀEÔGÇÀ^&‰ÓvðàíûÅ8éÃg& 7ãsXx|f= ‚´3‚9CkêÞÑ$Ls–ê5ÝU±”Ÿ‹Ü:QHŠJàðçf¡Fção°Üñ¶åŽOåâ版ižN¿ ˆ8ÐuVZ΢9l¸J½˜a¨ŽïŒÁ]Ú5ù„úÏ· Æ=­…×8èØÀdíŒWÙχl/Ì{[ToÏRÇdFâ$æ°|V*ùøÆŽù;WƒGfáÏ{s)Ì8tŽs¨¸×h׈‰d95¹<ã›×¹VëïÇPgMÎ#C¦×<¸ŠÀ,œ„ È]X¨ŽäÁFvXë×ò5î{Å.}°S¸/¨9¬†¿» 7|×V4ÖF¡µè/MäÒ)Ò&°4§LYº:n™lƒƒ—beo’šü=c¶õà âmÐzE˜'>*á9•<¦œÏ ‘ÇM #`ž¦äâúÇÒ\Óù Ž\›á~“T]*9n"ž¹bæ®!¯Í{{NæuœFÂ\Ɖ]ÎÕ ñ3}c7‘Z¡ü¶7ä决ÏŸM.¢°"™*(\×pÜìrˆ„Ž£µƒ¡¨îKMþïÛ'Ü€¼ôëÌ ÊüÇýÌ"qõ|·º#Ct¯ê¿0]ÔÜÂéG.ÄV!š³pÛA=á§h$#`š¬zk?gL6]È·`HÄ?ÄMÊú(X"Šàõ>Áw\¦%ÃÜ_¦7YØ% Š`—º®ˆ”ïg £”–è+i¸oꬑﺋg Ì“„]z8TÜ -BØ5Qîz«U8c ®C© ê¹oµ°›ÊIÃëßSGBHÉ6޼Ѵ⑛Tškå©©áh™Bpyu:¥Â£éß‘ÎØ€B¬œ·G¥Üž,^ ;¢Õ"×-º€2 #-ÔdGG[춽–”EfŠ{4µ½¬£ai„CÜ\ Ë“ +(¹«©xdżÏXIb»ÖB¶"#C>ß‚±Ýù—s~Y†U¿K喦Ƅ>wÌJðê_¦üŽØ2Iß!PÓ$l™Ä9ëÎ{ÞÓ.Lâñ `1Õ²’³€Ûš¢s”ï[€5Ž{™ï®:†›òÓ)è)B‹šÞ@ra5Õb=˜(ãá6ØØÖÂÛ9éÖ<Ð]>ÐLßmQß»µÞ‡&it@íoë‘-A0ðÅysðÇY£lu(~ï„GX"^§?€†I•Ò0^±EwYøè1cjÚÍ‹„0‹ZõiUvØ,±UdY‚¶ë -#`1Œ{÷,àwdÂð¸¢LlŽ¹âEä­,@Ê–Hç©x€á@·à0ÝAjÐx/}îºò#·íyë»9kio_Pâ‹õoR5=6ò{Š2y–¼ð6þÅ2Æù‚ùGtž1@z¼H¾OCºùBx Ò¹#Ccˆ~püçIõ)Ð}<_°HÈóù}Ÿ#`›¾‰‡;$Ëp?NÅò¢Q5ffž®çååf Ž\)t *;—Æî{Rq럡œ5ä;äñ‘H;g85I!6ðuv¾5ä£më#Àç'ë.Q“3…ê‹rõ²£ÈÇ Ü\¼íÚÎg˜xûÙYâeˆ   ­#„^Äù”D‰ìM jßòÙù–ë1mÖxËH~JqGBßY'²BÖOl—µÓ{@/«×¿â›Œ˜³m¨á 0Á.âãTêGl—´ÒIxQ¦É*ÏÁuñ|szAºçlqÕÓLeË…X#Cx`FpKª•ŽêiÚá6&¬0ô8`‚ƒ.¨¼bí¿³¸ëé[¥NeÖ>P>Ô?܇Àóý_ˆRÀ#`D`Õ/×T,Š9ªˆ!ïñ#`îóâÝ}[âOw»¤=¾bX cÈ|¤´ò´ÂBPÞxøV_Mßhvò¿—={¿&ä;ûÑ%GЪ„I$ˆDwSïó¸]Ãßyäï{7O«áðó¢B˜A)±êB€s'¤éù#-3Xdñ¿¿Pt†”ë}?¬,¨#`L~r]Ç·z{¼NÓzî÷ž;œ]&Í‚…Š€L*äP]•£e[Oun.™awˆM5ÀÆ8|ÎgÛî#Cà9íïÁÖ5ôàwÏ—ÿŒe4qÙ"Œ•m4™b‡­B)²pQÑm¼½8ÿJ‘ (˜l/ÛÏsA•Cô:‡Œ{¬0æaâ&á®}VÆíc 1{‘<6ÛÛÅç\]©ÎT°ÈŒcþHŽraôÔ0ºýº*:²¼¸`ñw;um¯÷~7ÓNw®ß..}é#-äÊ”ŸÔÆÅÓ‘û¨:ù5ÇNýx<{ƒ¯ ­¼ðt2ðE#EU5 H©–IÄÑ#šâ<¼oâû??#œäú!ììñý+ëÛ—éùº¤…¢‡ÀÊ’f  (g˜Ñ@%”D–»ù^Vá—ÎTC}‚šg·AÛÓ~èƒ$#CëþÖAìxçÚ-¶ƒ‡që7¹Ï#`Bö!?ß ù}Ÿ¦·L$‚wõßlë#òäjD:ûm¥ü´IlÜ¡ø®Ö¡ÈH{5ûg ¸ E;_[„X0³öD(i]ùd’ôÿF˜¢#`“oë72Ê?5žçÚhP›ôÓñÕ0ª’H¢s}ëI¢"‡5^£wËä>çËÆ[,9†’Ö®‚¯=G^»2Ú87—Íò¡#Ÿç MË5{½9Ç|¾e ‹#-B€¦š.¾8§"86ä@ Bðì=mMw\OëÖè]Â7cÍŒžF@@ä»ÉrˆüoéÉI@]J¸TêÈ,Z³C5UP¥À‚CÇà]âÕú6›8LÕ#`€-`‚c²äů#¸²›Ó;±û²JB_ŸâTë3^b HS¹58ShÃ]Ö†¸™´ad½íêtULTDEIÑùæûÃС¥b øKÇ/‘L"”%¸$ €ßÍK̽®ïaPc|b¼ŠõrÜs  #- ›ì]“¾¤ â,NÔ ¬®dDº×áë3&II½V\%¢‚©„¯W ýrW°×ósþíÜ×@k˜–oÖËÁ‘#Î:ÿvuÂâ<•ðƒŸÀï¯.£ÛãÞõ¬ ÍŒF8¿;B€å^> Ñ_§’V ›®3vXƱò˜…dÑúýŸ©Þ"p_.]…™ùñcA±uS°ÊŠ¨4¬B5ÞÑw„`Ï»é°}p0ÞWû°ñýÖm1ùoÇNသéE 8’¬ê[ο‚>²óA¶#-„t¢,?I^îX#‹;hMaXÃ&RŒwj€ Bno¨ö¿æ­kmKùnμ7÷ø&Ì}‚àØ~ïÍ]C²Ï)]¨ï{z i4@DDï“h&Æf#`´²bOç¯íI¯ƒøsïÔÂfÊ(¥ÙfÚ”Œã—µVR‡A Tç6:¶¥¦ŽAÉt𿳚¹pÀ9@Ã?97gdñh)~ü~Ã]lÂDGI3$_¶ÑE#CóúÀvs«²„óRAP=âIãê²"ÖWñ!ƒÀßN´i ÓËŸA4D‡|XŠ(¢ŠÄŠq^Qç\_#C##-ïe±b^tï-cUÍA ‹´ÇË/x—ã̉–$hh4æ*(Žl‘N"½HëÓ;#C¡éß­ÕÍ@ð_]~“苃þ,…Ô#ã ˆˆ::Q0ŽƒD'q:*d4Ì]©îÎ-º‘Ù½8À€í³›M†{ç<âªÕ gô Éé¡Úñ•ê ¸Çd;‡oÞko ¯!(2ŒòC`ͱ†^AÉ š"f.ÈÔRàõ¸”§È—¤ ÎŽÇ%ì©òŒ]&¡’%ËXkѧ—?ì6Íq·0ç'|ižá<”–‘¹Ÿœ#Éó^#€P¦´;5 Žßü|òfj!ÔchèË‘È4¬µ§ ÚXH“‘bØiÒQÞe4-¶Ù¾ãp¾}¯on¼øêeûЧ^ÄöŸ±0u| [?xȯ4£^Êbõ-L‚)àŒiË.ðR#=ÖùÝÌÐ Ô°Šb¢Šª­6„þÀÑHZ5¦ß¹·o‹re8²Ùm&h,vi^#C±GL¹%òT¬^þ ŸÛYÔå[iL++³ËlêÑ”*¯î®•çr^¯Öa 2~bëew—®|ʧ³jW˜—Ƕ–úš#CG&+V&fd1G¥poÆÄJ2iB4¡AûKlþ6BùÁ¾Û]Ðò÷áà9Ÿ+Ä#¨â ð{ÎhØGõ(:»ë‚[¤Ø˜r®|¿Eñ°¥”uŽEÿT"«¨[N'œS°•t®†Ö¨À¿ªÙ¦XAHSPR¨Læ—4+ïÿVÝú8÷Õ M<2Û?›gȆÄÅo0P}ë^Ü“ž•=ÌøÊÿÃÜïc?E†éïaûc­Q¥X»5F«# Ì©®¯Éö²yCÃ&ø M«gjâí@½«9÷Ù««LYØàœî ¬o(ô—þe0{bY‰ þ©'á×¹öÚ¼/,QóÁ¨#'FO/cxYáLè=­Å2®z…ä@¬™™¼vhG_t:(æŸ>4ð)5æ R#sm²÷Sü±çÈ×¥P¬œ%,DU\Þ ;ëçÆŸÉË¥§¾º¥Óʬ/Örð®¿bÁý|ߟ D ¿yžOûxDíŒ0£ a,Ñâaå+¸MÛ—yºÏ#iʲå#`’¡=ј{Ô)¢E‹œÏ,©¥ +šÉp´Š-3ßȪÁ\ó¸ â'yá»Ä}D{oäÇ~ØaÑÊfl!õ&e/ÞÊxœ@¬âß¹:Úk‡æoÓE4d ù|biÝwìllÓ¦êó|âO$$ÃÇúùÿ'ŸîþÕ÷þ»4Ïã©#-`Êñ8¨xò¶£„vW£é½¶§¸¡²ÝÏÅFÙù~š÷¦' Þô{tá™6Îo!p‡‚…=CgÏåߟ¤™ì´¸¬ºš—ZeŒq§^ž¡•²g«õwp6öÅ… wÊæFÓmåmë#™ÙNasE4ì˜ÐÚdxhy@zÓ…ƒ„¶¬#)„„ŒqÀÕZÓ3W­j¬5cÅšTÎ3›»q³ 1n+}f³c?¥†UE9ᮑKÇÜäQ´ç“ïƲí…~þžs Þˆ”ÚBFn„E÷G3äÇ  œÔjpIߣ0ñð.BÃõ(Ä励m’zD¡}oÿ7×?·Ç¶P*GRW»üþ¯˜npË%)YäîXpuü£>è(¦±ô‚Ž¨‘Èé >·¼€T#C‚xÑLäÍ’õ˜.ËÆS”qIÖž+¹«àÐœF›ÔÄ›¯ë;Iê31Ù7‚!—_^f_Ó®ï±ö"ݱöïZ5ÊóYão9ËžFb{s™“€¿Åœ·uuF¬ÆùYëßšŽ×ÑAªÐºŒa²‚Ui›ƒ¦¥ŒC`¸9F&t@ɨЌ#`ú'$Þ¬ˆ”…ê‰#å»v·ZTÓ!X¥B#-#C'Tÿ™û)SðÊn}t2IëÝßÞk­‹ÂÄb#C£G[{y‹Ã_?tž!yTÚ‰®ìÖWæÅ¿+>ŸøtÖ™»ù°Æ= Ûgb"Š:ËÏNØ4(@DЄqü’› õÙ‘“ö,ÀG‹W5’ÏyŒÇ\Vnéé—ÄÝ?à|BÁ†dõýýzyë}µQzÈë|_Í1É;0™úµ°YºC5ÁÅeÃÞzõ ìk^~ö¾øqù | ¨×ƒèÕŒë3Ï#CNÝdòŒÎq†X «]ó…ß1#`ü°@š?Y,7It߬Ó,ƒ°¬9 ·» ©Ðx~9hàôûWÆue£m¢j|«ñæb­¹Ço0'Rç>ÂR#`P¡ß!"ùÛ*ëÞàø/í\}$E;_f “*À`”#` \‘Q*ÊEr‹ò2 …Å8Ù@JoÊ¢—Ó÷¿?‹…nú¨5«ÚÖ7D~i…îæMc/tó;IüWÐo®#ÎïúçÄÙ5Ž}åâ¨NßR$”ço´óÇÊMo5ñ,Q¼q\Ù×Á•8K÷èOP£*Ù<°Å/I¦#ÉDG‰9‚Èݘ૽Ú_a VUέA•Âæñºª#O…ž6ŒQàŽÓq¸"ì+KV§ªÛ¨€-]®¿¦ZÔRþ‰!Lf.'|ŒšÜ›pôC¨ìŤÃ6þ[O[ŒöÆ¥ߥœA˜Ýó´µÕLi®É]¾ôÅz›ª®Fûeà’¥½…QÚûL¨#ud‡)Ç àÐy#`n-gÁÃyu¿–¼ÏÒ ‰R3Ì'ùtU¸ôã‰Òvíýê©‘È»ÐL³Šr¦ê…‹žf}ÝßÏ_'1á —#`C3“¶–À„ŒAv@êË%¨YÃ^¬þÌq_·l&÷¹Ç¤{ê\vŠÇLJíÕ—îweÂ]«uK°’G$¦<`)mNÁUíî³`oV¾s;wõå $i´Î#C~ót±Q]ð?Íß@´ˆ[Žrv‹Çø¿†1Ä>TÐè và<|òyɤò>®ÕeEH—mö‘³ùE/X‘îX9k¼çnoœ÷’Vs¹üù›À…ì¦S¦¿•!¶ñDb‰ÈÇ@¡áÂÙdDXïÜrú—ì]AØEîh !}΄ùô@þ;6;·1:¨Öí½š<©JX¹;¶öw¶x! zÙôzÕçõ¾™¯ðEÌ¥Ô®¶Šºäô"¸ÁF¼ËˆÖó¶€^ªC†— ÞnmcýÂf÷X-å4?ò† ×WÖñpˆªÅæŽ\ô̳päüMä½n%?äêF(FERQÊ?•Â…òMÞVQħâò¥”,ó«ΔÙØTDN=\ùj"Õì;f¼—¨!ÊjþQþD¦;ÆéyH1ƒËxA#-Ç-ÓâvÎÂù.ÐãZ[ÁûáÊ¥^3“Y^7•Âš×¼e¨–˜ú¬®˜,fBmüø1#C²`¹…Wƒ%#ÍWDœô›ã³zÕÌvD¦6Zñ³HРݔŎøDóÆ,²j9üqoyzo ¤,_5GUSó¨Ù<§ÃÿH¼#(ä.Èž†—0çès¡3#-Ép5˜Û>µéqßà(3ÎDÙ[&TýE6—¾k C»ÝŸžþVÅE\Ìs¬ƒÝQx")Trïº_=*øÓ­öm„Ñ8ªÖ®ù¬C;ß2’]'–pÏvo>>Ñ·ÖÏ'´¹±°àFÖ1\K€%ÐxhW”l8‰Šh€j» :ž‰}Ê7”!LWDgW9÷#õ¨®g{ß>g×ßÝx’ix""8ä‰È œ‰ÕqÛ%“”‚è¶÷-ak†E‡—ÃKÄ0¨«äÓ»‹§GtǼòÛZ–*1Æ )Enóí¬°Ç)ÉUQ°&+Õµ\æ˜tb9 :Ý1/é1ƒƒ9|ùÊj_ ÐتÄÒ;M aasAÂ#`f·Ó9 sOk<’#CÞµäHï/8Ͳß*ÙóàÀ78íüÌjöÁF»DIÌ…9PL©4Þ$p†&I5 ¦€•†cI¢]ÜëI%¿MñÖç\ ìæc)ͦæ çÈçÊŠܲØä&w¾Ë°˜ŠìŽÇP”‚¨J6öLs#`FèºNg9uA#…FÁ8]°^¶¡í¸qCzCvrDt_Ýôü´w>‹^$K£íß³# ±{±&yJ\] Ë!!{{Ù®ÞÒ¸ #-͉öÕ÷O'ñ¦ì&,üÇÉø+xvK—méþÝõK\©¼@G•hzõ 솥ã«7vƒáªâë°ÄN\ù@ášØ½Ê¡&Í' ÉqÏK2g½(ŽB…BªW‡‘F_.Pu”½b¯~¶|¸Üt"#Cfä”·×A)*l2D’i³f±b(ä¨EMI\ÁsɆfUTR Bù¼^ô‰‰OP¬`ú/sÆ"Óʾ>51T³©Øx¨ .´å¾â+¤ITl€F Uê53v‡âùó;nçÏÛ¢Ès«ï¼1H±…^@à„waø®<;íÞ>}±FoÏÒSÞ˜¥›Y¦éŠa,ûö}|½‹m½uätZ —zßoê¿hôG·Ÿ!À ÎQÜÅs7ËÌ#`"LB‚·%øªdû);Ó;mÖê³ñU î»ͺqÉ5à6sm¢fúÀWHëÎçŸSN€<%“ŒuCs¹ú—³+ÆâÁ‹Õß•žöÙ[cÛ Í¸DK…â¸-V¨¤C`úà^A 1Y*Þ¨¾î¸SkñÃ&ÚAG†d/Ú—8ªƒéÑ0¼ûx=æv$ØIïî꜔8»'§ºP‰R –РIÅ7Wsèt­×'¨ŒðvVësÜôK*‘ÏçH5 –€^Rã ñ7ÍÃøø¸ÒÕœç9᫪#--#`èx¹XÔüfÙê_t®ÔyFèmØ¢ÕçlÈÞJ+|®»IQÍGs i#-4ôaGæ0»©óDQ#`In`z½NmÏO#`Iv\IK~ýçór39ØËèáÒtw]¢NTÛõ›"—ÞD°u­b;lùA IgPaLN&!üê)vŒ y e²Õ”(UŠ‹J°ä ߦõôÀ øáýktk¸ÌR±W8©I³Ð»1:zlz°œ;…Ó×è!Üb‰8Ä)Ö6¶ÛQ"ˆ^àV0½¦êÂë#`¾Ìå—(Åò¹²(fµÚx'wGÄì9®9}0`˜ò®²äýüÉÊd¹grFÞvãœCi39×Ãr™’~©*&Cõ %SËãÁò'YVM&Ü™é%@Œ¼Fˆ¥Äµý—öÉK”aRàâÛ,þHÂ!WjĶd.戣"ÚöD 'ÕéõQ-ËÁKÂ\»V µ÷ÊgeÌ›cq éíÖ÷^2϶´Œ¨éJmÐÛî`´BEUqé“Ø9Dãî1kQì'Ú–˜Ó爹TmŽÍXYÈMS #-‚¨X(ÀÞ`‹Ó±àPÂ6üÐÖ]ÖKs”§Œ#®h3íUV[xUÅzæ#`BÚºø|¦A^xÛßjc]ÓãÄl¡ß„“¦õÝË—SûìOk%ï\tÍZW…Mb¼9l…þÞ;³áXVdV©Ø-;š^xݘ„Ú7vXeÑÖýͲbv87+¦L00k(ò¼2 ß<áæ ZÂÆzDpþ~­ô÷öžxC|…Ìbà…·¦ÎÓTU/?ãô ÖD’J땳ålºá9”$!’n²ÞÛ¶UñkøÌ-ˆH=#-mɈ®ó„Û uÜŒ†ŠUF8ìçÑLky#C6“œYRóɪ=°t„©!Æ ­â®»¢)CŒÔMë–ê×pxÕ¦ªÆ´ÒP²â ÖÃ"RÀ‘H.KqA¯à¹JRÑÏ‘Pýá^·wø•*~ ºÝIˆP¦ÜØœæÖù›N+Ú"QùN¼)s… æËMJçêÇ.S ¬UV7ÎÚí^õ¿8ÉÂx¢çOsÌ R sän!Ï%m½7_fM­ãx¦¢+ײöÍ«MômP#C| uŸ5ÍHÈdœ`á¦Á¸IÛÕv/ rP\Ñ'jõRÛ øª‘q¬3ÓdpÈ;jRèÚYÊvÛ1äPÒpæÛEÐŽ{çJÕ„ê™ãÅÙ^_>Fψnœ†®÷öãwÉA†4\`%}ÏÅL[»R[}ËWètºªnQÈyqŽÆàÒâ‹rÀ^{?ŒAlm…òæL3£¿o„0“{zû#Cc›‡¡PŽ#C†_7”&ýŠ¸Ó-µV* PÒ|5= 0„ t²b Bø™0JHJü\­€,zøe8bÛcv‡²˜ušbŒ=GkgÙ vƒD¸£¼8s¶½C<ÀÜyäîƒu(ü2U½®tØC“À#^©É5Ø?ºº3±¤´#ù½a¾>Ûƒ°²!Ðydø1Gä3#C;*vήJù®nßµ&“pÜ­:ù÷|ù”ŸuÐÅÂðªAŠ© £6pÇFÛ2l.rž³€š‰R¥/hSL-„à#` R€å[JÍQ ÎX_”õ]ý•¢½¯Á<ÈvRãh‚ÐBûZºèŸ‡nuZÌ=7 :ûMïƒ-¦Y?­–(ø4š5ðd±Pfçwì¿×è‹ÇŽs¸øõ;®òæ­Þê䈼WAËo”Ÿ±Fw¹øÐAνzï•g)EÅ嶻’-jfi÷büœÑà¯-Ù:Ä_®ºbˆ2¿G¨r% }sdÔ0¸;#C”ÑçKŒŽq$¼DF÷¬#I/îDjå÷çÓF¼q•ðàÝ(÷â#-éŽÖ(šÍÉTZrŸËìß™1Ÿ§»NþŽ¨=âìUﺣgr¤§Ÿš#CŸÏ…ý¼vó+²Ó©ÌÚÉ{µ)p[– –ÕABISõÀð$ÉK"½S­8Òj0€îWËRÿ&3Fíž3ÕpB*é\#`_4œ–Œ9Ì ?ÓYJ<%™>’B÷^ôÒAÀãºè5#`O¶½Ûá1–úœŸˆOèåÊóãÎtq;ù×a[§®mA™¸-U7ì32qn”óy¹Åœ[32Œg"”ú%2`×.ZGŽéUe¾Švò¾¶R€¸+‚ya‚#CMAtÔƒëõQÍ7UAÎfkÛT½=S÷º‰_,½ø«D¶Éó‡òç GlqëY3¤úO¾ga䮣æI´#`‘Æ9ò·‹ìz#49é„í><¼îÙó~ôèVÍ¡ÐJxø²sç³àn#-¨ƒi3uë‘Â)Hµ†xàÂà/ŠÊHQ¡º0ÂßMÔ@…=N›:2TM#CåoŒœU–hº›œ€õ da:U @ÙRm“§'è(çøë“Ìé·„ð€wŒ5@µb˜l1X•P¤…^:ÌüÞè;b«ï$=0cóǜƲûžèŸsÃm>œoq‰î}kºâ¨öñ´pÌ[‘•Åu(i‘Æ£zÕ a1ºJ%ÄÐÕ†–— XÞãÝ9ÜY§(Û®nV¬€ØÈ2ѱ”F`È5îü÷‡Ñî^sƒLç0Û€¿S¹q¿D&²0—#`eL#`1Zñ¹6*¬¹‚º”,KãùŸkô?ñ;fÏDñÓ|Ü|ˆnc¹pkë{#CTt¹]z ê «ÿ¿³k®aEq eb¼üÐ,bŒµ%/R?nÃÖ|®~±Sv©fð¡Q¿›mÞ³’®yó¯mñÃé4³0W)Й!$&[˜}ÛGc>\18EýŽœß lœUã‘`œ+C8â||zÌ Æ1#`-#`TPI» 7œ¹\7¡¸¢¹Gq¥–©ÁÂËù§³L´Ql´”Ž.ÿ™ñ"ÄVê\5Ä…ÉŸ(óÚ(¶o¯·­ãÅaL‘¯Ë¹zg|Ñ[ùJEþÈí2*xøO:¼Hµ$âì_ú¶Ç‚ªDÈC05üµBd[(\tÐÁ:Þç|qv¶Fg„Åq^zŽ¸D†§¹/EÓ…ÞguoþЈˆ-qAq°ØA(#`|™„@ÿ#CÃSˆ(>ØÌUìwpa3±7¨æëŒr´ª«)9B¨rþÆo†¹…)Àë@nGHä.#`vá6Àéëä¿ÙCžšñÈJS5{j®‚ç1ð:ÖXˆ7S¿Qà$0c·JHa–_wÒ2psÉAù¶FPŠ®ª\Ú@C#-Êd´Þ0|!Õî{Ç„N-–ª‡«yª}zJJü³jŽ8ouyÞÁ·õÅùr×p>"ã9‚5ØÁ#CdñÄÔÿ6™æ¤4¦(=ô`2l¬ù³­[Œ#`¹ÓÖŠ™I…þ ÅA.‰ÔŒ²’¬ÛS 8?­ÜÑí¤Â2{#-Ã’ £uœ^ÏlðÀ/#-pÜþ”ÀšúN(š#-†jC}ƒq/h_nüæ6HÉÔø„m•Ùª2¹m͸ã}èhs+!"ì–æ…w+…uë#×uÃÚuH Tøø㯷ӹã—Äï=õVkc1¾CÌýߌÆo§—ód/$v\ÍC˜xL¡Î!ýfåú~³\aÏ*ãp¦ÅÄJæK•T ûFosê¡åˆpÏødçg«šdë‹”ªžãMª?bÃ)#-Ó}ø[|Þ.µi¥ u@úõKðÅȽKKÜ(®#C —kØ7ôÚ[DÜ NÙÁ°#h)²ü!‡64æDë‚€×díAÕÜ1~ŽYØ[t{™´GÄ9üpbÐSüýSó?möù£á'½V>i†p¨ãµvMckDb™DtMqö¼³KçAufîz9ÃÂÒjÊÅ22é ')’‹Óp[‰Mv;Õá›)¢Ìa o>£uå]Ǣƅæ;×—·'õ»JK|:Û^¤´‰‘ì¦q^¹ÌyZeé@Wcåw黿<å9!Ê!2EϸUœyÅ'˜ç#`¦ñ‰&ÂèÕÄíz,vª¹o‹´QÛrµ4±"®äðq»YL©ŠMÉQr¡@ä, ;´Sȶ“¼TR:äMTÉBÈÙ烶D5©µ5ìø›ùMó¢q}OqWžšLE#C#+0½˜ØCT”áÏÒ`Ì»>Û`/ÊÈP°·2tz“áÓGƒÈÔ\éꦺZ G$S±g:ùܬáŽ2Dö~vüªÖ¸Ýù"$f÷AÎ~ýLçˆ_„%´ÔBx‡´¤wO9}”Øœröþ¬æ=ˆ?ë­XÏææ'£ªyt™$¸Ì™qå}õxÈŽ™¬UME˳ÄÃN¹íòç‡#õ1ði‹RAñ’*ý{÷ÅñìçÙëñâZÑøµœ}ÅvÑ(µBãxÁ …ä„;Ü0†ÎûÍ|[ښʉãÓè×YìD89îá ÑQ²ÞþeÉø&I˜É‘[~l]V$6“%Ý.hÁ¾7,gËÓH‡ë¤.ŸÆoôÛÓ4tƒV~¼íMfþñ:í ª£m³õ6¦÷a?ˆ‡:.ü<6œª‘õÚœáQ½¥­W¸H_ ·ÉPïª>3#-ZñòîÙ@4 ðµ “KéÁ#CÂp§ìÍ÷wñ8G>?ÑÓŸ8Ä^þ“¤Ì˜Ràî_éí"^¶Q0•|u5ñ’²Ã÷Ó~c[$Û;Ù”<Û›(©¥4‹ëø?íðl€ì$›ËAÄþóÎÆ÷fÿÏ~r ý¨Þq³;Oð–ØjHócãǧ¼ºÜš šå/¤Ì¹BW·1Uóðt.k¢¯¤›j=H6õâ¥2ÓóÄé­¶4‡Žå&„î¯Y6Y¶ôúþ½b3²œÑùÞT=MàÀÑ'#-¨íÑx(êké݀贙‹ŽÈFÒz‘›}§ú3sŠ—ñ´RETPî&tþXóø³¼ž#Có:sh:G¿‡ïù#°kQ{‚`ϬRçi‹„p!ãYß“A{ÑÁ&5ip\b# ªûÖ bþU« î„—ø,s{6uR50|( ¬WÏ&‰Àüª#-Õ/xgos{j_`óqƒ‹È]Ž¼£m•áãWõts_#CDÙfz0Ûtãq×)’€ÙÏøBXÀ LÙúÜ­{(y©ê)ƒª¬ñúò[_¡p8î›Ñו« Œr,K\­nk¡Ò^+U¹öJ1趷‘Ç­$;§¯¶gûwŒ£¿°7wüÂáŸrûi.Êá“#C—÷Ffƒñã–˜OW PQQD|l›wŽql%Ë50QUtÆ’‹™ÄÐit$B@.[~ÕmPeOŽ¡²Ã—š´Éà\è.ò÷[\È¢¢š‰ÿE‘9RØ$~ݵˆÎ_Ÿ[=!ŸùÂÚ;SÙbY,gÿ2þ ànljÎ5#Cê»Dåþ’l§<‰²IG&~|±£:öç·Sþ¾4uá±MÎÐ;9¶#Cût´Á)•,õ×^4YAdòBQà¢6“ŠT#CÆõAqŽéXÚ{xŽ·tœó"q÷íË¿¨jøÔ¡!Î`9¥jµ0Îx¤"Ôw¸8`‹nãCË5z$•Ò€löÅûlã »R=‰Ú]+L@š(£ÙȺe•(cÜòæÞ:–¼ñíÖñÖO•ec²­ÚÃxžÂ줙ÚI3{#CÚ‘'Æ=ÅjæÆÒ‹ëŒqÑö20Úc9?^ˆìù [iÓ?^Mïmn„-ÓûÜ÷·â_'.2Få–í!áa¥P†!ŽËî݃EIÛ„º‚f„*W¦á«,m-X¹2¹ôÁ{×Á]½cƾ2ÎP®#CK8Â:·G7IAX2”@ÛT^8ì[Ù|rl’bŒÚÜâÄ>ô4qØdòvØÕ5äι∡£²‚0Ër1™jèÃ"ÔÛY* ®¼à£H F½w³%ƒD&£W—¥ÙÐS®ë¶/%a¡ö÷üÞYѶtZ’Lî‡ÙÙœLµD²*[“Ψ1¬Õ®1¶7éÁ È1Ë·s&­ÆF1`vžÜ&=/IiµØÝ~Û)`¬Œm(1F˜ãs#`Ó.M å Ü4™ÁÜb¶E®3ÆTÈ.´ßo/‡s0»ÑÇËÃTÈsÛPä1Ù Y×ddî}Ø׎8}kýÝAÙ6ðŸ¥Ò÷½@vTrˆéE6lœçc‹³ ¼L6ÁI[·”ß—ë:wõçX¾ÝãÐÆùí1ÃÊßh¸™ÚºøŸÍ#CÐÉPâfòÞ{?hc¬à>n‚­˜Nü—½ÞwãwCÊ“HÕ–°;|Ëe‡(7âVr#HÎì*XØú 5«HÊõÇçAÝ+rNd9Àn)«c4´R#-gSô†Òà#`(D's†Ë³§#-ÃY‘ÄÇ¡´Âl,ŽrWE ³B^Q3r¹5·,Y+¨·E+6b»¼Ç8dbRy©1:*¦ËÍ™IZ2‚å˜ãxÎaj¢õH"cˆö‘ga·l;ÄjÎhÎ7ÓªC•ÚEuÝGºœß™Ð®¢øh®s fƒ9¸lÔ­0’(2ÝTKí&ߧLÈ9m´{ ÝÏÒ|J\6Ǹãv%»¿Þàî*™ü$Á<¨÷‹£¦§òu,E„Qí4ëȳë »ÝyÃÔ%ç&dgB‹öÉù*¯Ë`-5Š·Ã>‘Êü„ÎÇgŸÓÁ ¿Åš2*'wcN!Õ†#CºDõäÎb™ˆ$]›˜¦þè0bfî¶A­Ê7ÍîìÔî;;K‰«œX3›©¬ï„6((næÇ–ÞaJìÀ@agÍ#-pXW‚!þ»ÿ¿_¾ -ØÂ;?Õw¸‡o²$(^”7õcl3Ô8²›ÐVP{ìs]Y?f~ÀÀݶ¾ku÷ %ÁL΂0\3w£>·ù¾91xQuˬâÊãI_<Äc!Ú%Òákå¾Ê¢òM#`Ñ^Fþæ¢'$õ€ò«ÝŽè¿ p_ßpZÛ®{†G,ØP’W;üzý>yúún_ÂcÕ…‚ 6äèP3 dçoSÛÆøøF‹o\5Ÿz¼‡|^‘ð­kýP`?b (‰UƒÇ~wãr­òØ|TH²Ól×O¡D£3ŠÑ´éôŽõ²^©3ñžÌywµà<ˆQš÷(¦D7I‘ó¥(X Ù¾ÇÕÒ-¿:ÚÅìà?7²›â·WVÜ–Ò€ÞÝV?Í\þº^ùrvi¬»NalÙG5K÷r¾˜ÙÎùÆùólÔîdHÐGNýq#C¸©4p2(z­Ò yV¸M¿,cÈ8‚<É0Ê`_’fx<`bÚ#CË;øîâ"{<+‡6\¤y,F¨¸Ìt¸àÞ8`Þµ8¯>£›Æ¹]¾ßZóèäÚrGj?uµÿÚ“ ‹N…ïÛ8#C¼#å‚¢ž‰|é 3ÛýKôßxÔZYŸ¾_š”¡–ìÍÂä|­¹釿,òÑcì“.¢¢›,+}Ÿl,GdV2vU¼9ªFL:VT·ç¼:çÂ-;“”?ã9ËŸE¤ÈN˜JÐ.ÔQ2ô«1+ó<#`Q娾ßjšÞ};÷ -xdâ`°AI¹uúºV9Wã7³'dÈÅY¸b¡l–gF(*Pp÷Ïi[V¥áˆ4ßmaÇðäo–aI{â^;&»Ø7¦ñEÑåuâšhþÇÜ郣‚ø!Ð i¤y&ð•èÉÂr_HÔ{þîqÖÿ“Ãøûõå%G^y ¸›™³‡ÅÆqljˆÊÁÃì½÷âðîG×õ3Ô+¸ö*›'%ób«±¢oìÅ°Jõ¤}#C£\å‡vPÖl¸`B#`ˆ’k–Ûüçíøÿ9·«8û4þõÅžFˆzá»öxÕÔv«Oíß#`H(e$ñºJxlO`Ä'“Éý‘žö#,|h[Å”¬€°b)éiÔÚ¹8Þ”RÂ#Uíu´m<>ìHÆiñ#CR$ukä”´8ÒœæPvì<æ·‡Öo¶ñµé>;Lì⇡4¤N;L_E†/ƒª#¿ #`£ @HQ6®&UpðÞæs̳iż8ˆ€˜¨x«,e´ÓÊ«ª`<¶²”ÓÆaçxJ¢q­æ -ÕŸ„§®u#C\±Rª5‰)8³k†#­ÖÚVR,4(¡Eš —Dìï®vÐÚ´Û:Ä€u²‰c%3;µ²¦C£Œ%r¬o÷ÍÑ«í|C}éÖÆ#‰â\â &äT̆¨"6¢ö‘eÖ"&SÂ(}E!SÚÛ13[;鹧6ABŒ0ÄaÍCµ&áabZ½×’š¿x:£p¿uî M–=ç¾ })qîSLžä€&ÿDœ¿KªóÏQGg!ó¯N{U,ËÆýpÑ<ÑX‡hghÓÅk¯_߬~ÌDcS²”´î#`~¸“‰øÃE|sˆH¥‡¾ÁzüÛGµ…ößý ®·Ç.ÿ‚5‰iÄá¡û|_)Ìž±÷+³{·ƒêO8ÃÓD¦{-}ÿu4[cï~9‚íö˜‘Ê·íêíRèùb »¡`_5ÍÑ`¡™ø¹ÖáGâúÌ\¦»ôÇåÛˆfcƒxl»yk¨/Ða«ü’zý‡‰ì¿_xFJIéúïÐIôUûëÒNg¤ú//m@ÔßrŸ>Dªð ‚`Y³ÍVQ„‰BI³3W4ˆW#CÌw_qì=ˆüÄMèä:²Ráóú=-é€]B;\—\•Ý_;$]'S©57þ_yü†xün¸ÿ OàîA–ð`§Í#Cqý,£d#-º…}\~”‚@sÿ]I'6 ½¾W£¼C×­Üï[ ¯'3¡¶·O£RA&:6Óå>é O€îîïèØÄmÒð]G¾>gAµRF#PMkh#-ò ¼Œ™Ia¤]Ö+KždAƒÜþKñÅÊKÚ„9¾4t¿’0<ýhÒ=7W­Õùc²p»YÎÈñPëÖ3sÈë;’œd€ˆØ©š9³$ˆ#`4)­Ñ7I×RýÍ^‡ˆ7?nª#‡i[~­ý ú%Ý«ÀÛÈô†¨#-¨ÏeFf_‡ëÓ8ï››žu.9ˆ”ÑúŒ¿ÓLFs÷óü€äða#/CA€zG¸(BB+±Kþ{Ç?äº9'ÇcÖi{qêl]Ú´™îÑîd®m­o¿Ä}G«DT—“š‰–ÿ G#ëAö_/;Ê„›:î kðã¨t½=gå2ùµ#CNýÇfÿ¬»‰•¥c?f´´ŸBb™è]﮲/Ua$JŠÅ$ëʪ Ó £‘; =ó¯’œ6tÚLå#-õ´ÛïÙöÍoC©þ’:'æb¨Ï$t²Û9#œA¨wˆÒ#C#`à zBÜñÎyÓf-Ûøऌ:foKž#`r™Òìì~®MŠ®<§UëÆ°…þmé4ž¤‡?âõ«Ç“ÿßîë.3×ßÈÍDìp\xݾåL¬/}ÆÚ…þ2"4tYV›r™¨r¾À¸Ð.©ã1êÇpv„ÉKã¹ûOsÒ‹Ó›áÒ¹Ÿ.þ]ñÜRFu=öþ6<¢[ŒO*Sv‰7ò ô,ãjg¦^·Øe“)à[ì䟓Ú#w6žðµÆ™¼u¦Û=œgB…µó¥Û»[=ìvÉ„rîÔoj%½k”mZ•;NJNZxÁ¢IÌ…aРp ÑŠsÊÙp‰îàò…B¥¤&À*Qdà eT[Æ0ŸMjÙÁR˜¡&¬ðÛñÖzÎÅ\ñÙÑjF7êsi†ËÅåè˜[#C¯Õ¤Ñés÷v›úà÷[$>ïV!7çW»ibi–°ù+ö¿…)*3f%˜kC\h!;Ã*£Œ#-Ø}ä`Dž”U¯Ð3©·![£=‹s#`ãR/üa×#C±“ƒü±î@æ<¾RŒ(ùÍûø®ì_—µX<ƪÎK sUíåFã²±¯'¥ßnéd1Î;ÝÎs–žóƒGXì?,ZTËÖC%ÆŽ¹ï˜V¯pxøÕ­„–Áê:ß©ªX¾vÔ 5®®Ø6œ7$‘ý×í¦1™<½zÙ4xcu¡ß³áë÷øç®±Ç~=2”/#-1SUðÐ&Jc¿‹^è9ÅܲƒCî2ÀÜ·ÄZfðýÅ9ˆ…êçÉʼ»†™MI#<Ä@‘lY0ÂÚc ÛéÁò5šT0%Ç‹XÀd2&ÉÞ¶‡5VøsƒÇ'ÓŠõzö¼c–ǯíÑNÉÝÒ·ùÊ?ämé#`L> (ä{ôfµú†’rÆ«³uaÆ2¯E¯ŒJ/× ½Ó/c¯|S;•s™à•~²(r˜UAˆP,S'/á:Õ8NjX„KFÐ%]ŽÉ™»Ðä϶1»ïñmÙÙÛÄꎙüÄ;1î#CW\ðvï1h%Ç0±gÇ—wpªBŠ´áŸ¯ÀéÝ!!;ñƒi¬ÇÝÉ §øÔ&ÛåRP)‘tsÇGbĪ‘F½Bòx1#Cû½Ô›ø„A˜”ø[@þðN6öïàÌís Y¥´Œ)eïsUhZ½ù $8ë¾ûÈ!ì6±ÙÔ37:UuðÁ>[÷<`q!~až!úÚ—mJ_ Ûµ}‘\±‹kÑDI‚3iêZXù6szí7¡²„Ãy#`õÍoQ/Ññ•ÝoDp§‹b¥GØ>7ߊäh (‚U" ±@Þ^X×M‡î¤$îãŽb8“Ü`ŠžlG9Í<Ž%ª½ï'8åUJpRg—w“¿·c¶äɧ6âÌÆÓŸIiÜ—4‰Œã?éœ×Ñ£Z‚¡Ä°†Š_KÆ}ÓÐâõvôÞGÕu†ü‹³Ða´wV—dšÙK­F³çöa磥˜º™Íéç0°wŽ‘pè€h=F쎆êªo/ŽŒ–$õ²ª®µ¨„£PüÂ1D0¿éÕò„#ˆ!£\¾“#CoQÙ¢§ŸÆEõaÒ«UÅPX¡|ÐB^JE“¢¨ºÀ*‰½ƒÔ à¦pÅcpmË€#Æ#`¯EnÒòKR;Æïì1臉ƒƒoÈóïÉHʼ¡±ÑB #pôgîÓÑf°²Õ>jªWtZ}:K°`þšás´˜®¥RU!lqWÚIQÁ©.-Ìp#5X½´#`ñª1€Ä¿;¥­â¾<4™°Šªœ"·MDv9´a|K‡ÙUBå^5Ҩ⨨U=Ÿ¡ÍzC]™ô-S¡u¼vˆ¸56’lÈ5¶öO±ø2ŽõAW«ƒð{±a Ü!W4dPF #-1®(ð¹òÁnÐe|ÒS¬c;ÞûV·}å>gñÑ&çfq6Þ[Ò뛵x£Ó¬ìª¹­=øôú$œk—(†@Ü| ž.“‡l¡æ ÇîQµe»j;ÊGê·ñë·ûS2¡i¯œÐ]½d.9TÍÈù§™U¡Ñ,€Éb{üÏ}¼£¦wY‹ôÛKøÆq£#c p›dFÓÞÙ,68PÏž×Sx'&›í¿†¤ÆüöŠ½6v¢²cÓz~Ý%¹œÝ ªqdïÙº0÷Ç‚6möRœÜ&>®ýSZÖvþG傺yhÓ‚ÛH`jñR/zðîéŽP8Ab„Z`R°<‡„„‘ê#Cv ÔÒE@U#CUQ¥-mg†’‹C‰£”8tG4i)×JÓå“g''¦¹ÄˆÒX›ÉMM¸F³Üwƒd ,òò á1ÂL®_«<Œ?Ü÷–¸[—õ“ jÂT¦m……6e¹ïºP¦·;K,­4%AØ‚b{Ûš ,Žß=ø¯Š:ªõvìzí£¹è`ÜD˜Ìyø"¼aØžž¹wiýš’S?.Þ#C>tû>/à^Àˆ‹§WV™¡ŠD«ÊA ¾éÉεk¾8;0ÿ¸TîÉÛ…¯ÖSöQTd 뜛=.Ô冪¿csä!Ñ/BP#C¢kRŒ8òpƱ &bqS~}¥L‰Éß'#µ¨Ø/!’\0sÛã=“‹([Íu#`‚yénH5§”u–õ!ü–êYý9ó`ÚƒwÃúàIÓ{àëŸí™¿?(47Gw\CüñRÞÅ`¢ð> ] ¢.qÎÎ o`0¤…’ènÝ£ªTíær¦–^"”']ÎzãÙèÂÎxÓ~XešÁÛâˆ@Iós>Ø{gls×ÓëÖÁ‹C®1½‚H$-`§ÿžA-÷õç"9@èiMOïRt_m£H=Î\6ÖÛchÏË%ŠÖ±ZµÅŒùy>htÛáþvÕõ%³ñ3Œ1\]æàˆUŠª÷k&\¨xéñµ#í‡CèÅÖûc\Üm²„ H|•–]eg >„ÄZT.Þ\%…++èªÛÜðèÃ+l)ÿ% —× 2À—Ê9¾PºùÖ’]z>þ+cNw·;#C#L2u³Ï–ª×DïθSeÎð>«Î:³tiS90í„'×Þ³Eo‰¯9䇾#••HÞ“ÎÎUì?3#C+0=:ŽÄtY¶ÒŠ}m5·7ß\AÆž®x­-js‡ªyPnœÙÌ£¶v¬%¿~'¼Íï™êÜóë÷O&qÇ:WN$Ÿ³†ücs´ÖÝÝ\÷<³šë ³ªæ¹‡Äœ#.t§a¡Ñnù¢ ;>k7™#‰Î(ã}zÆ#º}ÔãÄmœío­DÛj#ÁáP^5zb|øòß;Æ«…™(âl>®·4eéÞ[ 9#«ÝjˆòÌj®vg}M+'2…ÝZO‚°þ17‡¼R›)’I¸s¢nêXÎúk*ȬðªJPƒ#`HNò²­R´…K#CØó^¶ mßZSbzá\óy»qJðŸ¼m:¨áNywã-ÎU›ÖUn«P‘×[N|qÆÝ>Ó{V1{¢…ë|Ññ!‘L³[{åuNpSX-’ZÖw\±¥¬û5ùK¾U¤k\F|‹ÆÌLå'Ó,çÍ¥Ìû¥œ³"1‹ðj¾åÊû]$ÀZ['_ªå-f¥ž×Bxhc!ra7b'ÏŽ4…™èZ×»]”ae…£¯‰‘-2¥}_0h H43¦jÒtë&ÝJ‚ôcË*qzËÆÔ§Ñ<íÇ\w¦ÀJ *M҃ϋÊq× u,´CìÑ;CšÑß¼ó­õ…ëIÙxç‘ßã§1Ué<+ºßŠ'ÀøƒV†qÄÊ2‰{ßÇ+Ùï+Z0Â¥ðÃ(¬¬fÔo21¼ð¶0îèìŒ+æö©®ü?|F,“žý¶2°´Ú^#C–¶œIfzZ“YÒ)^0҆ΪëÊ3zÙ·sZžøÚ«}®ñŽÓ³5‹Ž“bèÖ=`’Ìg|q‚«cÆЩò%‘*ƒñç[Hèu”h«ªíâK1×j7ä#CïnÚ\[ã0*˜œùOuµÒí3G [ηçù¹—Á÷ÂE$ƒ8q“9B QEŠ]³Šé@f#áóIßVh•.äoNËKXµa·yý¸©l‰_/²ã!“¤±£gÚGd"I^P=‘ë,éaë/ø)§O‰üü›|ÃqóƒË%"TKv† ,Þ;óØUšnûÂŽæל|-ø¯lTm#-ı6e/)èB6«ë[›ÇÛ.§{‹ä#`„¤²OÕöOâ6Mö{×ð©ìcs·­Á‡à˱—zv‚!Ýö¨^³¨^foDá¡:F6ŒªVšy‡Ðûc¥–ºgö‰SAâìÛŸYl,Iqõ3¾7ÖqÌZùMþíqéMYÊr®zïÁ圙²éõÝÚSÓᨤ_ÒŽê>#`uŸ¯'$vLì„^c1²!3*×D®•&…Õî»þuÑíªRæa¦”Z½X»7î ¾R§àÊãV%ª ˆ“¢ç°‹®Cöø›WP.Öªu¨ÂàõŠ÷D^½—Âëiq~[Re/›ñÍ1úac­\p±öU­¥4'‡XþÍ©EÚ,…ò®Qb#`HÃÔZÜL®F&#-,ê!Fr˜(nÜð«Ý—mr½BWÅ_Œ©2SÐ!©$pýOŒuöîlÍ”ÆßU׊m8ÂJ#A •˜_Y±>.j?R$ƒ6Ç#‚ˆEÀŒM1p‚¿P€ãé†ú1€Ö™KÒý·ØŠ!ŒY8±Êj¯O›·Àùð“£ÉñGî߈­x8#-¤Õ4C%Èb=•…CÐ}±s‚€1¹Š#CÉpŒ“fC µ-½F´¿b™Ÿ°Å+Ý"k×ÏyÐwìb¤–u9ãÊ[Jï'ô5×kÓ÷’{Aã%ÑX´HLƒC·T|Mp*QO[¦C‘¨yÀnfžùEßÁÜì€Í°êCâغ\3)ÒïØÓ+fo˜W>ëòn.PôB ä3-öò6.„†÷ÿ8x™#Ñ·‹·”Åw2#Cú½xN5ÇbÓ%tb°×¢W‰#CâKC®ðp™N¾díŠ-/0ãC²õÔ•ó8ãá^\Ì3‡ 0Ë›ì» ÎR=î* 6`<‘ÇŠ»<’Ó7Ãy‚:¿J>ó¥P7ŠÌ÷7 {¾+–ÃÙ àÚo‡Um¼äÔðôÕü7üh;7ÅF;L³1RÒ†âÿ™„—J°Ý/æ6m°—£r¢Á!·9Ü°p·nŠi:Ž † X#CmÌû"Ö-È=ðÁsYiÝK:·îÉ4 -(A$1UxÛsÀCÓní!Ç@Ï’Þ%åÜ}2~¿¬È÷êm‘ÃÕéf,5-O Wî\÷nèZ¦ƒƒ[ ô )Ò‘SÈê®B¹‚@SöGÄ­Ù·FïVè`»ëp¯KGÀ_K©U"SÛƒÑÒè|j MÓ<©à©4jÌ/~ÔuÌP¾›»ßäùÆÚê–k.‹ÎÑåN\mŒÖ3†Y=ë£M+2ü±–¿!ð‚‹Ø«ÉÄ"øL)d!Uך€ünLCö¾76ÁqUôÚêx†Æ÷¶{ÏSýJÇex£µ—Ùyc‰Ž«²&…[Ÿ‚<+Ü›þÎó–`ßoh9Ç,g4þ<È]Q}_>…¬ÙgŽ/›0KÚ¥}’Ôü£Þ8Boh3ÄŠ=Ê®ùþÖq7/ù~•Äƒ£ ¬8¹íÈÃw­Ù_Ômiq#CÎî7r<†DGwZ»Z_Íà¨1:© ÂZ>Œp“U±—;Üz3ÄîJx™“׳áÕü–Êg‹‰Ë½óè½@ó?tùà½#`àÃ/N`!C\ˆ(seˆJÅáo ·¤›Ã#`ÔQv`‘3¯Æ9õ°1«“’ËÒ붉 A×Á©ÄøELZù*‘â]zšùÈ÷t)péuè¿ÀåøvåÛ9õø_ONNŒïô„DA¬JËåR¥•XVA³Ùìï4(#Cdß‚ê¢@rÌ¡JZÞ¦¦^B&%")M JWJ¸—dõëâï7={›ÞÕ#CõÙ –#-ðµÄ6˜Ç|]ôŽ0é£J2äY-4óBó[SèÃÂ> ÅnûቀZ´‹&´±ã3%B± X‚ÌÌìñí»‡P]yÖ ÓÞŒS@ÈZu]Ym€#-d,h¡B¡FläE=ÒÛÓÀÊË—lÏŒDµˆ*ú‘°éÓÎö¡Šñ®>•ôYžÈøŽÐü.TJoIažS8ó“Ça³«Ý#-”%Š}z¦þO[/VÿùIð|;Ò¿n#ˆJx»Çœ»ÄöbáÑJŽáÀxÐsóÈÂ.ÿkBBÿ"I#tI#G¹—ûÿçÚÏâOÖoCÞÐå\ƒËÇËš#-€< ";Æ¡°rûw3~*ºÕÌyÙðÚ®ÞßäÈ4Ñ9Ê%KW‹•ms­éTÄ÷Ïòö>ã'òÈÂSë€>`§í”Þ5ýøöÊyÊ›@»H´ ¤>2;B€¸BÿX‘OÙ"ï ÿñ“”#`¾Dò„û#鶀x¯ GðÂÀ 2O\¸O§Û÷ééØص…é(0 臤é#-¯´¿)’é,Êq—ÊQÿ4ë(ëIºÿ»@O»~Ãëñ|Ö믧óòßÄ_'q~Cã®#-8À¾†FHßãGøC%Û©—××kŒUÊIL‹ù»½‡ÇOÍóF•©—‘ƒ¤^#`:écTü‰ö_œÃå«áo´%âaÞ4£?Šþ*ÓæÔ+¶ÜI¡LÕ L";]1IùM>Htý;Ô'íCÞ¼ïîú;ßoŒ„ºSÜ‹Jxw*50x±V”Öê6öÃZqÐF„Æ]Š’î vEÖI#fqWýä"÷zlTç£èüÞM1ÝúC®­GqÄèà ^P¡#`å%£ÕÅKÏïÚ´äàfìI½m@ǦQ±½*ƒHš˜ÇH‰i˜ˆÆñÉ?ÐtKþ‚ác(¿Ž” 0hí®i 1¢Ò12 ÓN„yŒ‘þ\â†ÁÉ€¤ƒùàùyŽŒ6«mÙOúP ÙÌ抑"Õ Ó£7#`ºÈ¨Â5ÇzÇ£`P”„BÎô¿I!h&®¸iVAÆÝÈ'Æ^®0o¯p[d®ŠªÝ”–²5¢.CHt_8Çk* #08é¤ì¢¤¿òòÇz#Žá÷²u˜¨ð»Ï‹Ÿ­Ow9Tó»…Úe´Âdn©PÎ)fjèÉ.¢‘ǺJà6Ü„®8È60°ƒÌµŒßw{GKsLQ›^­ÖòMrLZ¤S´À¤#Õ­Q‡þ#CÝ_øm*ØÐÞ¤9nÚ vÅd ¥[9j\¥8¡nò‚(FQ3MB´c­°ŒƒJh£^­ûCr¢ 9>¶#Cr)>Ç#C‡Ö4Vß“ÁõŠ¤"zô_Ÿü72€îˆlŸÔ—„(ÛÓü?·Õ¨vA‚ÁŒÐú~›#CâòúÉ$*Äן^¾»hJëäØ›¸‰cVÑNíZ±ÊZõ i§ý1û£sè@ž*ZE†~Y#-#ò¼¨xùuðŸwä#C>_¿Ü»,çø,ö‚“ù™÷_ùÝ 2¦€QcPBÏ£o U(D¤IDMTÊ@G4AGë-I1þ£#`IhíG¯Œn.#-¦‘‡bÆq¡÷'•lóõ{öH9õÀ?¶¡s0–¿ü‚ÃcKÛûÇv—»E;uƒŒ;îÿ}ôÁÀ•@ô0Š}¨ÖÀ!„’Gþ ÿ)Æ1öþ³»¶ŸOûðMæÜk/JäQ׶¯êãû÷üœíppýêa Xü~{ÿâæ}Ë„rŒ(ÛvQB,#C“F@ëÿ^ƒÈ)J¤z@bŠ•ìDP˜ýÄ¿¢4!PÙØŒŒÃ CÂ$ìÏŒøÑö;<é+Ñ,j‚ªÛlF'Ì!Bœ´C†#v)"õôÙÄú4ô8J´ØÜü»øÌÊGVØžªÙ+¡‹D.?ò͉µ:5£'ü'ö~yf”~Cþx‡À.Lpò¦ë6ܼ’¯,H;ËÃÎük Û3|ùÚð;ÀGúÎø¯…˜zÑÓßò¯™ºÁ™ƒìøW‹â†bÚ‰š*"S)‘÷#a oלÏê‡6ÙÇ\<–LƒÃŽ]`+ÉüÅbc’%ô#C˜P‹ ?A>ô¥AU…ÁªÕÎý²¹<Ÿ’>A(ñ¹1%t¿Ÿ‹ëå»EEï ŽpÁ:µë… -~S2¤¿¸8Ö4Úòš'æˆËèñRb¡aÐQé¥dnŠŒ?1#CÿÉÏûs"Š=j:Ö…’Ãñ¸wí™—û4Ô0‡ëOfïöx)ó|‡Þß©Jÿ'Ç ^W±[û|_:c§°õ¼y%°í|ÄjØ´§&znUÖßÐc>Œè?îgOÞG$%Œ“Ñáò²Ê{½R—ø#`‡çoÙ[} |K}Çùª§%½,3>#-õGÿ.û~Xä^·Mª*ã«—»ûvó™#-éƒ9å:J'r zf$9^ÝNÚE?F¾‘ßù©m’QÆ{6ݼU—äØ–Ü?¸ŽMn„ÿ½ žùô¿÷d<]9О&eúC‡ äáÙéÇX÷×}™£ií<¾ìNKþvq™çðÓM2?~®=*C+ƒ€Uàû‘v3 ÛriÏËswám¥…âüáO<6‡Í¥3sÓ?J¯ŠÌ5bW[r+wñ¬¶záá轉(vIw0o®wxO9kL@¸#-µdFE(SÃU#-û(!‹ø·|À]Kúe¶nìs–SÑ!«Š¦e§¥F#`B«änƒQŸ0¸U¹Ç¸§lÔÝv·#ôTÕwGwV_п¯Í÷Õ¿¡šñ´'1OÇüÌðPïóévïïŠ ÿW_ ~Rð-hsuWûîŸßù?nƒþ¿ÉþNŽDéDK¢~Ò\÷£z•ODX•Oc½í"ðB’¤¨öÒ´ „b¬§ùda‰þN¶¬ƒÿ^ÿç3YÖÈúËKþV@Öªú΀þþuú©0òGó#C¡Ø! 7ëOž~ÅŒ‚ƒ Wêë5cä”ûååý·-ÿ¢ÿ§s¦µ‰É'ù;õ£8÷ÜòMxÑe˜w¯ÍGoÅêúÜ …Þ#`&L#CªŸ“«[¸·E›ã×ñÔç”ê^R>B7š” ÅFõöó0Ú "œÌŠQŠ’³V'%äÒeëŠÛlüúPfñeÑüÇŸhA’{“Á9t­¶xñwŽ†Hª¿31dxX,~øˆ~YÅÌVcçÇðü›p#-)Eƒ«ár åÖ6ü8§˜ƒ4õ#CðËo u=>Ï5ßOn`Ê~(IþC†ßoÙø|»~ï7£÷rùüCëý]ÿ™Õ#`ºóæÙà1hoõÃæóú¿Ï×™§n6¨?O+Äzâ‰ìvx¾hÐq±$ÎÝkïþ>fJLˆŽ&0Å(ˆãl^}f?¹‚°JOa¡GÜQ–Ê¢³oíü!ïãéCîÂ7»“v{%$ôtm̉ú?0'ï=#¯ÓM96önô|pçè٦ߋ ³ü3ž#Ë—²ÿ¦îN#<9×ý}\÷ß·òy$Ýråìê~ÞÐþôòGÌ¿£§„þžËf¸ãö­îúÐœÜÃÕ$¨ø¾$d,M?>Òì kÙW]®&¶B(E‡êÉ©‰l›D^Ióâ&¤‘²;'2c##-~pnЃzˆ2'øä8ÙbuØMórj<Þy_#Cç½F¦ª(J¥(¢$$¢ªª $f €Û¢@%#< Âø‹øg&©b‘E#kýÞ™'ö0Ñ$A;™)Ž[÷ºãÏZ-R’”Í,{8“ål_›&„"ÈP=w 'óùGç;ºÌ:Y$w0MµÄÞ…#C0IÀ §¯k˜=ýÎP\ÿØoÙáöý»÷ñ³þOu¼‡œúN¿£›ž½CòÏ­áè;~¢.#C6÷ý¯Ö™Øëú|Õ•÷ç“ßN‘õ~ß_(z¢>Ïϯóû~}Ò^?/ƒWÓ£ì«S‡`ãɈç)ËÁ¸PéÅz>˽¯T>á=9šŸ?F¡×/Pðÿ.þŸ¾¹lÎ+½Ý¤Xnÿzœß=ÜþØÝî³_ããíæ“ dîéó6Càò¾ˆ7Ã-Í°¨éä³ëØÏ#C;}mbá‰øù•Â wí웎N_§Ôƒnï<έê§d0glùy:û|Qãì+Q§¾ü¿Ç¿§¯ÇÉÐXtÝî]rñùÏî»ewNÿÐ~n®5söcðÒ[½Ù#CìîÑ$?J}«ç€8#!CÔ”ÞG›“ãþ.‹ös“{ÜèuZ^;¼C³*'5~¯ªשãÜ=¸²8z©+Pú!t§ô¿ß©ô >Y;„¼¡þý¹ô»ÖoJjUÁyX_8öá‹ìÚ³¸·gÙzm¾Mbèúqüåð­K¹²ã#CtÅÑ!ÙíËæ_!œïèæ•ÌßKùŸÕ†S€óÇÙ;øÆ>,|9trz~YuÇÁãË7Oû9±Ö£É÷Õ©ž2ƒ½½ø7%5üyuëÈË–øÇòd5ÆÜ™ü—_Ó–W>Jê6+Úëºø?œ¹=ýa»2û¡ûþ{›Ø¾;oô/Ó÷áËÇɦ/þ‘ÁÜÿNË­? €ó/.¸sŽLõí\½1çjôî9Wõ+ÌOÚÃ×»MsñîƒÄõÀnåÃzôC_ÅÒåÿyü"L˜`ÞìyçX{¼\®êöëӎܾm›qÒmo.±äÔ9£Š¢¾¾ï׬ô7†»K§ˆ_›¿™¹o=œm³m gž»½‚¨´—m \Á`æÜ#CÁðy‘f½Y§ù~ç=ê-ôš<Ù}råÕè¶3µöhMC LXîsÇglGWŠPÛ¯¶Ž)"FåOSÕŠHñÚ¨XaëtÍ5ä®OÀ¾yñýƒÙ¨÷½WîÆd.‹H¡Ã1Ù¶=];©Ãíþ?Âîì>߇V|ujÕ’L8:‡û=[tü*JÏøþ~\ýˆE—/Ãåô†½ît6ì—tiu|}~~m´ä‰~náÑa¸'¨xľŸjÿ/’ÿÇf±ø;ÃÒFOßîõû­ZaŸ¸ó ·xDgòû£¹üQ]G:“ü›=;nÔg>'c;úcòž—‰¾â÷ã+›V“}ï_%~íê]z¾*#‚ƒ¹˜²‘ìXæˆã§¸dq  ­5¼1Ë«¿ ÛÏë¾,¡Î°vzò±§ÃÑXCôsåuÊ!×ña2“ºmÿÇôüþ,‡fÏÛo>÷Žyl#CÄåîïOGöý¾¸×7apݱ¸p/þ™sW“Ùý]!ànWsO/ž:\.Þ<6ýæ[GË'ÖÂAëáà8Ó.Öú—>…ùë¾6´ZÙ Pµò4þÛ¿ºØoÙE÷­ãã·WËñu”—&Ó­—[Þ~ƒ#`º9«ó¿æŸ)•À)å¹í/$kþ÷½¡¿õçwå:0C˵eƒîúû|¿~†«ºë?Ë祆ŸêéÏ^Ÿ×Ãü#`Æ}éb\b,Sì(JJý¾}ÙûAà}#¯¸N­ç£áù¦ëñ÷Kmô^¾ Ô!/’Í[š’½`ž³ô§Lû=é§\&<È<=ûþ=túõc£òÃó_óGÙ õ¬ÍnàÌ#-Ä'ŽÜx Æco§“äú~Ó³LG^ZžT‚@*X©'!Ó«R´²ß7“#ClO4i/gíýžsýî¾IÊZ1ƒ¹;Ý÷v|1çýÞŠõpVTmÜþµ‡ãÔ´¸uÇŒx¹Dÿœ#-#`z&}…{Ôí#¸ˆ–ön0ýaB#-Ý÷D~á›øûô ëN‰z,‰pI¢t‡cû|\6côòŸçDAàò/PÇ—»õDòó ÀïðG‹êw)"áì|9•EýGgd“éц×KÅ«ŽÄŸž¦Ju¨A.ÂY¼•\ÈÅJ†)ó¸&¼±Ê"’ö¨Á‡ºþ°î*†)zÉ1&d‚u:ÕUÌ € bNÖú!¼+ëÀ^+QKIñŽ^ð%ôNýyqšöjÀmMPçù°0zeæ)v†›2qs,Œ°mŽ× *D‡S0_蜨tïüË4¿eS`ÕLçcz¯#ò#d¼ä{NüâÍœ8 ãâ™7ç?¾Af£Üm‘Ö*Ëü­lo¹H#-žjP´iÎIdÝŠœƒf”»L#:6n£ÞËã?Ç#`A×ìA–¾ìý[Ý€C?QM»†æNÄLþÚÁ¸ÎO_;Ó/[ë³}í¶C—Þ.xꢆ#`Wö9}n\òSþS³˜22ò¤2‡OR§S½WI}WÔ ús𭢎òóÚ4Q¼|ìÖKoWΣîeÜb@öÞÃÊx;ÇÃË°9Ôý^î˜ ÇÓ¢pk‡?ž½Pyì±NäÑ7î}=:ú1ë¤ìÈRwÍ‹àk ] ê<Ä6r¨.YÛ— 2(>t ü­þÝ4«ðñMb'Ù Í(FŠ ŠUQB‚ ࡧ¹N‚>07Œr\·öEÚ},I*Ø#CE0UùÍËÂÈ)€þÏ‹…é~˜#CDxº7åpuD©“£.’ò|ƒÒ:ƒxë-hŸW„í“Tp/Žð4Vú°>ŸGêDqDÖ;1Aǯíú¸sé9lt§¶W¦ò³‚Ž(…²à¾>Äïú òDGø’l®ÜÛq?Ì:s£1¶ª6ÄÓhØœw<â–6 ÐD\Ù–(ŽbIÈT¸Ñ”Ñþ¤“pr…m„#D>aÁƒ°5k¡ Ô`BG\W—+NMiLTLÄÚ¤œ,1T&4è5@ž#C:ÓÏ8ðïO:Háf‚H“‡ r4jä†å˜ØK`Ôj"Á |¿òuÐÔa+½B™¨Ù(nE€ZP3£á%ŒUÒ Qjí»Ví¥©¿¯¿ ÀVBžã¯ÞàÈÄ܉B×ù´êG÷oÑOþùu.“DÚ'ýD=µã@M;™qùäöi­ïk·r#ÙHX ØÁŒ"…2…Eýž¿—Ñxú>òw7‰ÅPI$% I$@ÃÒ]#G—Ž‚k'eŒ¸%ƒAˆ$$4lfm dÑþ«I5Œ ™ˆ){¿?/äù³ÏûþO‹†~_eù/Ñ—+“V¶»ô|ž±m_S£è9dÉýÚô¦OIÎ#§ÑxÛà¾ÿ„´x”¥‚·e½Ï<¾ý|þ;‡'Ëwu{„<õß¿oæåøµ9Mnü7ôÒã¸~î½_˜xåÛ¿¶)¼Kð÷êþ8ºWòeý4þ.ô‡š?ˆÚÇ1®ÎÓ+ñÕÝ´ÅñsÚ•öø>H¾PÄïµrùqáF ?´›#CBÎBT0£VœsX{†C`¼§‰ö}ºÝ÷—|““#C{¶Ùc²@Ùÿ»ú>7°Uéû¼¿¦ÿ|ú8þKsþËÌÿ*$¿Ñúsý‘Ô²Â÷~ûÄðÌ4¬ ¡éóèlžïå—!32³ð7|sÕµý½ºx+Óñk7 `A4J”ƒýÍÉ#þP´‡ùª(?í#CJ~ÙWöLM½ô©ØÃbieUIN•¨DA⦿¹fbÿuäE\BY»—B¶ŽF6… Ù ˜ã#‰mͼÏexÁj&œFK4E'{#4BxÇjTз1ÆõnuâDÅ]ó'˜Ìj=)“êZ5üœ-ékRRÂË!l±ƒn.^¥ÝÚMZ`–Û!c¥Žãf9þf­2…61¢5šŒÎ(›ÁVn*Q·±É™Ö晌†Ë0à-–cqq.`%b¸p(ˆ³oö'ÈúyÓÇy ¤ú¹›¡^Üœh³ž'4'œçluØ k¹¹žUôK!ׯàZfäï;ŽI,’È3Š˜‡§¥Ž7ÄÊæ6F݈3ͧ¬ö:ܦ íïÞhƒOò¢b˜t8ÚbE“Óu,E€Ë®îPv‡&óp+ì´lhˆSü·à 1ˆß"ÖðRª&ØR6YŠ#C6ÙËÝÓ)DxHŸ_á´@ŽA¿¯›÷~n…Ô—ê4Gö/oê†5æìívyßÞXà£ÖÂø¹u ¾g¯óÂ>7Öžãíä=ºgåû.ŸÑF窑ÈD½³½ì=W-üߤ~3‚ _`Áß8ê)°¢=S‡;¾|{Þ÷<=ž1Ù±Û¸oýàì â<6ðr`>õJQ҇ϱ!üU>¦Éšk<†üVí7¿ÁÈÎ0Øa(r:0½¾éñëWãòÝÇÅ쿦{°Ö}Xôv(æ#xê^sÇöoJ>Þdà( ”#`øýÌ9ƒr“f]ú Á»6F!ÃgÒɼ¾ÄkaagøkáBîÛ–@w&ÓxUƒKâ›ÔÇšÁÉ!j#C5k©*ÜéïÓ{¼DXm#`5S|<¿§úo¥)ÕÆDCÂÓ=Nr»‘ªràø‚?ÀÒŠ¸J Çu#CÜuWÌ"sw0Û6ibθ´~2¥+Æ>ƒŠôjgìãø/ç¯}¢‰Š˜¿î‰bà(TàÑþ9iJØØÕ#c$#´ PÕmWZ+,ƒQ³éøÎŒ0‰%9†nÆ+›ÙÍØLèÜ#ˆµÝؤ"Ji"åŠÿ4½w/CŠ.gNÓ@óy±G^1$‡ô&áÌ^ëpâøW6Ü8ŠJÛ†q5)ÃbÔ‰c•H…lŒ Ñcë–¥ŒŸGvb4˜$^àòóaò?çÊXšpF[Ó1ÓþÿþRãÆD1‰†Ýèu0ϦÏÆÑÜ—Ïvèš5o»CÖjàK ä꬧ùñp5Zï¤Ð= *æ§LÆnE<€‰¡ðîêK݃ç áÃÞFøî#FÒ¡§)˜‚*6t³†,f¬¬Ìdc±Ž–DÂ*EaØƬD}û™x”zùûЊjH,†O³Q…‘8J®8© &ΖÄ[”%"Á¶†¨PJ&–MÓ!.ȕ˃’)òòôê½F¸IƤ9%5ÇŒsTínK‹×qÔE}pv9iÅ4ÉTÎ0}¹$ëÖ#C)b!?ï©6Jæ§nû*Õi6 l$±DÃ8¡oVØ.Dh­›µPvk‘£WU¸ãP!-2ȱ¡F,XZVê×lÃA44ãDsᦽÁÎùˆ~–*:cÁq˜áFÞCIºØÖ“£ŸŸ1=”§ÂF0RTÑFh­»-mKb`º¹©Á¥°§]àÖ6v|=cÒmY-1¡U@4½ zî`¸3E`É­¨4‡³Hð<ÁÑk]ƒIÌxf»#$¶ ¶Åq»“Qwmªª6Œ`)Éï[ß4[ª"ùàÄzm!11z(ºRjq“‚ªJ lt’À «<àðææ0KÎcŒ%-AT× Å£l"d“m1‹E¬ÎˆÒ_vÜÔjœâ iÖÇÈ»Ó`ÅÕµÓóÿ…fjF1±£¹HÓ³vÈpÅFÃÚh˜lZh¢wSáí1ÏÃyËj96­#CTíª–ƒŸD·nε1QøC¾ãÕödÛQ‰¬Ž"3e(ÕDs†‹¬!CØBv{Zˆ5Š.ˆ“Ù2ŸaúPPFUŽtJÝ=&Lìã@&JÝ»ŸA3˜š'±Ê¸RQ3nQݳªØ;;¬†ß=ŽF&"“âWÂçâoùc²ÄyŠ¥63vƒñù”QÓ©vùHz;ŽÚPã 妢yIÌöÁìéÖØ*"“üÏGN§vLAˆ(¤TÒì4( x©LäÐÍXWAžç~ Î^”nŽ]éß²0ç‰ ‰w"ùyC¸˜#-"Š7ßQÀüœ¯„1x¯­‚xåÄÐ)¨(˜Uñ´ö4åmxÛZšÛ0‘û*M#CÆ e¶ŒlaI?䓃ýÌßCe`FDÏôpÄ3ïË·û®OÊ™ÄXæƒ!äYEz34F¹"±sgrx¥ŵNÃë‰`¶) ÿ[Ûy¶¯W0a1c1ËœGÇÛß°M|¤ª¥>¾mÌ…vÑKÇÚjÇ›‹—nê'Cbj‹T)M5:H´™¦vÈbwÎ1¤[˺Á0îîД6½d™)“r6¢L!8À¹¸odM±ìD¨Nã›nnwlæPºtѼLѯ_ í;¾²Âõâs}õ®3äj v1DÐD­·W)7´#`¸^tÎ×"È,X §ÅäI…tﶴš£\ã*-þ.ò†ÿ žMÏî˜-¡6¬!óQFãá1gÕÇ8lYû›l]9‹j#C¬:M³±¸Œ-µÉé¹}V1´mÃ8Ì指ŠÜò´£ÔJ¸;7’ Á}GÄÄRy[y@oÏ;úmPHI;ϱZƼc8ágÑ/^4I‘¥e˜ÙgIÀ Sˆº»´"â0UF»â•[kƒI_õÜdIÄçQÉ"ëüyÚS?ce±l<úz~Gšwþ› ‰®6˜Ò§13<»«ñ]p¼:…YD('gI×zžâgÁ“Y4)ÓFªüV–)a,ƪ37e¢~Çc o’à{|˜ù§L®ß/³ÊÍmr”Äô¾¨LÐÈd™…åÍÑEŸñõÐêÅsè×Ë6ŒÅ §ȾÖe„í\tí-,x¦oP¸ûo8µ:›Yˆ›{ylÃyg!“%íþyɯto|eo2±¨ßDs¦ú¢:re µÝ6ví#„„ÓIM aÎü‹–K¼EHŒŸsLZ\ë%6LEK>"á9ïŒA5‰ÚjÏTbÜ›Äò¯¬z_YrÖu½Ë^Õ¥Ùôböƒ>Ubôi¶ÏùhòÁMŒ£IWVç~®Ï=¼FÝçŒT+º6waìÄùpbļ†BŸŠÀ¹MÃúDòM·…¿M ßM›áQݾŠëUò±¡±X‚·µ‡A™#CÉàiâ¥òg·2¨8ôJÊK’ôdêòïD\³†žæ2¾¹»¦Vy8µ#`ôìÓRÌËò#`»M]n|8Äô3JfcsW,7"â†ô÷_åˆÛÔhp_+O#X)Áè®»"[¤R%S¼Š-é#CìLR;U3¨õ›5tºŠ"0TX«:uöYäÃǦyÿŠÌ›Ãò³Ëà}}‹Ö¸Æ/d#`ÕǧíëÛ|."¬Q43ÆmÄÿá!5¿övÖÊv÷ŠŸ?Ñ·O ˃.w<çrª#`‚ƒÎvyG™”söya=~ùª¤ñ PSñ˜.E´3ž'ƒ•{JcÊ)3%2hý¬î+y¥#oÂǸíäªÂóÆ'3âf)·Þ£õëþ÷ñvî2{#€‡‰f\{\9ÔÁßÔ:_¤Óó ¤HQUÖ(-˜Õ«R&ð ¹#-n#‡ØÆWý€É¨XkdŒøRX×ÑXø½ÿ†Æ9 K•<Á ~*¸Ybcו^#C\rPñ‚/#ôå&L]DÀc@“Hyr_ß@ Õòx‹OhŠ^ºÛ^«Ã½à‘ߨ+>Ùf¯6¿vª]ç¨ÅØÀN0ÍŠE*+l„—ïQKÛ$@HŠx=¾óóVíðàßoÒçtS†:fžáHÓ&b¹2œ@9 RT<¥;z:Ï•„ú¥aâÕý«åÓJøÁº¹ªíÉ«BÚœ¡hÜ#-dG#-.ò]]4ÓM¾0ËþÖõþSæ½PfJŽþžb©j?Üõ&ê¿À†Á4|–-|Àµ£#`þ»äƒH‡ù Í"eÿ:llC[m°Ö~–›½ëŒ’# ºÄúµƒáf’ýM Ì”ñJ pEËÛ‹’CâH1 ¦_Pñ(Ìâ&\@$ø@ᣭ9ø•ª¹÷ðô}`4zÎbxrGr8&Ùî“3ÑX¨«¡#-¨Û4?âø £ˆ·Ñ¬aq%Bˆ!ëöãžqkÖ ¶„âú8í³ÃcUŠÒd)J i#_M©oïþßáÕ)ثží’pì<ÉbàJ*"dBYD0n¶ûÓ:to:)°5ÜZšÈ‘'jäœÓâ#C´;4nìa»ÞÉL`Zssû£ð0g,˜†2zfZhê³vÁx±•íèxɶDdzÜÉßæùðI¨a†ÓÌý϶ãû[Ÿ‚j²ž0ê”›“5Þ›ündä|VË1Góĉ"{¾ï#\qiß3 9eîÕwåE L+Yàá¬2¦±âq™>WY’“£¨õ¾W·x°ÇêɺòÏKe™ŸûˆxÑ _ I›.OJ5SÖ–^l…k6¿"ÏqTí懒üj«b:ž[áÏu¢ •êb(PhÊ°Mÿ:È6ö™È=0Öµ}kÞ—S²E_>Ô1“§,Ê-hzhjF\t–b7%0~Bøç(™É“/P[w¡Ð@Šnâ,) Á+Åê‡gYQî˜2Ë@eiÙ|»ª$ä{#jìäÌû„8– Q•ì‚219ĉâøŽî æÂô¨R,ìBóy4>§Òºâs³ö³FöÏE39M?2;»#dãmëR^³Ó!MƒõN¿HiÞ<£Ù*ש‘$iනçuŽ4ÄÂÙ‡öf»ç#h»OßKL!Ü÷\çTµÉó ?[å¿8šñ‡â"ÖeÒL¶Ã;#`ÃüæÏŸÔñæÆŠäqáø_#C1 †ù»pK'96$ׯc¶~Y| ×i:þ¯/›ñìA‹ý;7-#-ïèD  èv:EàŒ¦æñÓÜÑP#&[åÄ@âáŸÖ¾nîÃæò&nƒ¤ã%5K-<œ6õµj^ÉÖçÒ‡–jB±­Bgä¯%¿&pdG9ççÀhùSöëG+òLåÊP‚™?UÃjØçþÙ™Ù°h¶—´Ò÷ PCÄt¼ ÷r‡ üx÷­Éð•N/¯ÉÓ3ºx!i1¨€ÁHyE©qWo/.ñãA´MV°jî[ÈÉ`ÂG†s´“>Ç'Ù<ù_mC Ùy´)›¤lm²è齚i4Dês§c6‚2K¼´(I›âìý.Z70N›yr·¯ƒõ—û¯kÙ@’)Wèâ1éÁÁË÷¾ÒØÛ ýewds:éLa¯E’Å1xñ0ÐëdÿhEEV8¥±t‰!Ô˜œLÝCµ\§vìúÃfšé¨÷–õM„ŒxÄHäFV™_+£¿ƒëO'ä?Å›þS–ã¡Öà?%Ò`ßX¤aKzÌ:¦y?½#`‹ôxd`šÅ}¾Æ±a”Ê"#…²¨qŽÇŽ(c4Ve± €Oíþ“Žú"^„i‘7E›Ä#C…^˜|xÑîj«-·ØÛ(h¬)ÜD;±rÂf®DK´ÝA-ŠrÙ¦yT}1Z—ùù}ý9|úFÕe™þ;¾Òîªåp#C[[œ\œiE„H§ -äàæy#`_PßADX‡wK i#CJ¹+¢½lR.sÁ1ÞË99þ)²²AÕÐ…#ƒì.Û²`ÿg~û9‰ÜÒâ…ßÖ<#`~7¶ú¹ÅL·(¥n~P~±›Ûmâ–8âcœ¸¬ÜÞéOËbº¿m>Ñ]ì}UÜî<3DÌ_Ò^Ö JèÍOâV+‡1™Ãó‹âêdzq£ŒæšÞìÙ­Bù¼QOºÚšSRs†î´¥*OŒÆNûÏÍj5nx<Û óƒ««Uª¹Û#Íñ¶p‹‚HÄŠ‘W*C$ú’)¶P¨9"3]§øÂЂD X˜oFlÕ õåû@ýn¢ »ƒÊ‹—|ñÝÝvÏ:9 oTÛò†æ°°ð!ƒ ÙŸur“í·ˆ3°ïçÙÎQÅMJ5òí¹7e­à:—5pž¢f¶î²á#¦´\Af2{&¸Æ9QA©Óè¢×¼F9£LÝ,/³Ã•FlÚŽ«m¼Ö¬Nô#déy&.åzjgÌøø'Æa…éÜÏ.æ I&Ýk×a0e‹A©£‡ˆÓtQí1Œ‹HÖŸËnh´;—ŒÖ÷DM§†¶“^Çk-¶~4h#tí…ÒÂ>0/Fû‘eØî<§²Íi½šdZ>¦ 6Á¶ÉGc(µ#Ý`ùÝÎúÝÔ·Ksw7}+w ’+¦x:³y°ÓÕ"âKÃH[Nî-jè‹ñE]1Ïg>lÀS`s­™ãÚ<üŸû9Gw)Ò—ÏÃk{÷(Õä½4䤰§gþ‰bÒíç/ˆ‡úï£}½[iÓÓr$Nè~-Î~÷pSΞ*‘[¼“Œ…"„Ž‚Ùý¹›C֥ߠOIÂ0âg"•k÷fªTÇ—3h\)M‹í¼8DïVA¦$ÇrEÓ5Ð`’ªƒzJ!)Ö —íÉò®ÙvîòGæŒc•I»¸…’%È„‡—’O0ÆhKíFúÎ}a`Î4¹é×:éíIð| ôåUvS¼TTÆäG|aÆÕ§ªœZcÑNK-áü§K3qéyvË9ºiÓlã} Q‰6#û¯öŠÊî:飮.êGFÞÊ uÀ´ÛÝ#w½fè˸òMªaEܽkÝ1)(•åçèkaRÎù™;9Õ4ïR:„…Ôñå‹°Ù²Úxga7z†*L¢ ¼ÐM2æäÔCä¥-Bà·!¤©u,ÈÒÚÍ[áj ¼MíNŽ=¯ôqÓ7(wýJ&uÇoÍ[rÁ¡DQ0šÜ™©¾e±³ìÜü#CÉuá©Žbb#C¹ü½OäN­ØY2Ê>ªQ Ï,Èþä“2ï>çÃúŸE”ÄEŒéåf¹ª3h˜ÐªË)_Tz"]ÒjTøŠ`S'Ó—²wÅâþ¢ÎÛ¼ÅäÛ:0¤i–Â~;1´0{À{^ëâö#òPwÞL{'S9®ÿ'¨ñ¼ˆI¡ JZ°Ø;ꘜИ¯ijiýC}¤®6N&ýM»x ¤¥Îýá®ýM#CyÝŸ›Uñï"ˆ8è5‹,»¢:¥üů’üNZSøÊê*Þdû2)ÃЭgõÜ+XÀõ4Lœ¢Z»..Œ®ÑÔŸM³†zܸÇÌV‚„\Œ¹—8ÉÛñ²…—#Æ4~+{;§PÛ…mÊΩ{šÕw´p1°-l'£àãÔÒvý2ÓÐ\5Ù­Q–Eáîůw@çâ'9‹ìê8bÚ¿¬šF'S„Å¢ÇÞôtp}š,®~K9y+œÃB’Úµþ*gkGg®îÍ\ÑÀ0sP‡g'ÊéÙl#CñÑgÍXré³ptæb+wNŒž)®ìnƒäÜÄG¥å…Ïb¯gß´RŠ›øluúFwMÊ)(ß‹¤ë²É£:¿6侬B]óQ/#`!V­&†…öQÖíícŽÉ4)4|¢¢$fF3@#C’p–$B*¡FÖ¤`¾JåÎ<®•ÉòLÐñ¥Ô}òÄ|Ï™äçmÐÏDµÜ QüN¿"Bñ#-òĽоnåð¯bc³#-öaGcpêŽ:BHã‹ù‚Ü.l/‘•ô Z\¼\×:È·ëF:;àQ>ƽ{córF¸™——QÖlåíXW?e%°[êÀ—«‰,¹¸, îQ(áµßÍñ徜šPOÖ³„¤Ü8ÁŒ†¨æéÍ:ÝB9õ¾#-¡pãf88V+¬Yþy:YëßÝ$ŸH=XÑ;óe={Œ:´¯>ºÍ:Á€­Ï{ÑôsW~Ù÷®á=nî¸[F¨%¢,#`ñ…Ì#`ºê¼Dy0–«éúU»1æ®ØcI\4ða¦ÅÃ?:°ŽÙvj¨¿l›%†U/¹ÛI¬ƒ‚¬H†ï¦lÔÕØáÒ8º·½:±¯µnŽ#C}³÷åX®­³ðÂà6ñ =¨¢ì™+£“ýNÞ–˜SÞQ‹p« ~ýWëJŒô²Îñßô¼d·q!B~&é#ó[¿¯®˜Îtþ®úÛnp•–*D û‹Â›¡ãË‘ÈæX ¶åb^ ÷ÑØÇ8IÒ"ÔdX*#CZÉ#Cqê—If#`¨£Ì•W³^¸¶|“Á¿fŽùïÂ2Ø,*7¼oã34¬QÑës(Ù÷£è=:S §òîzûæ¢òn¶*å ¨U‘XÐdr fÆ5d»¶ýؼYÂEÀ½,Êmµ¨û¦d5Ÿô_,¨B#-PŒ/L²°C%ÓÀã,üAoÓÏåÐm<¦‚Ao¢Ùé—Í”_ת®•qF­YwO$á δ{"ŠEœv³J,‰‰(5RIÝVä#np?²×å®ÓÚÃ…Ëî÷ú䈿TÐëhÁõ½&—j!.F¹ò &ª>Xß BîwÈê~Þµ q”ÕÊ,·:‹‰‘µ7ÁpŽ¯Âo—Êu6.g~j—éÞ‰\.ÚÀ¾é‡SO+£%¸0(wäîãhäž+¤Zê—@C†ã~õ–aL#C)G:‹7ƒÛTÄvõ´f^ÄàÃ#`¬ù¬ÅäÇÀn½öM\õŒ.ywØKºødçÏ¥y}WGö]›ë A_'°Ûóx’ƒòo×îõ°•TtcDÇ,#CáÑ)`FèœøÃç„È•bÕ, ø[ øwt%V#`¥ê—²²“k{ÖJ6³ÓÁÿlãsÚyÝ:«ußp½ð¦—f‚¹u£ ›õÅx:Í\¢0ÁD ü)5zK®‘ð‹žÖ¹7ÛDÈg–Y1ËÞÎhµ©úÞÞnUç/:¨Ÿò|p|ËJEwÕ_,Ö#-ûdÛ\!'É×…7ˆeØçt Ö-òå!§ÍÃ>ÈXEÂ*¸{º³œÞ!'©$ÏÖáXçw›Éœ:[£®¦;”_¯V¿DðôkÚ0UM€ì:ßòk£Ò>¦v™RT熪óF|6‰½Þ™S”Û =âÞ;ù®—÷¯¬ý)³J2GïŽ+K éë¬ÍTyãa€vL°çúSøg§Hh);6þçÇDâöp‹M}¬)íÇ/–žÅûýÔ¡ìƒü>ì i|}Nü`„@ï‹$SçÌÿØÁâOá=¼£A»æ,„œûfMûâMX÷ç­÷ñC‹Yh ðÓOüYŠìFÄÞ@äÔ–®TV€¾oæ{Ý[Hs?©^t#`|7ú‹Mì"ÃOÅe#-Éps¥9d3é‚CÈ¢~Ev¥»ËÙötX3*…Øæcp;D¾ª³Ü†Œ¤X(Ël+|M|Q’[E¹çÛ¦t-$(O_-§”^$‰óÀû&^ûkye:‡Å¢aËû;<ßéà“­D“<#`-q9Û¨Ÿaa%ì5íQáëJS†ß§™jÎ q)Bi›’¦©ØÇØö„‰ùΧ²k·Ì8 µÛžÙDZìQ¯ônÚˆ›á6Ï%pŠÓÖNª›\RRÖ/¼MÏaj¶};¢–‡g¯+<)½X‚nÒŒ„j(%%K<(B;¹4´  ,rÓJɤ$5%׊®¦Œ ߯räáè¿Z¼B´Íó¼¨|J—ì[ºb÷8L©¹#CÝ#`ág¨Ã{ªçÀüÍmÏÔ•æFCERÂŽ]†LñW°dsÖ.Å`S++‡‚}tªèå÷—Kï|Ĭ);þ¿²!Ñ¢ \KÁÆkå‹øÞq×Lå°Âáíœ|'$¸Ø¿’ØR+‹å¹Cø׶w¸W#`ÍÉf¯4ƒ§4•«££“ä#Ç'ºù>57Qa¶¢$z§ ú^íu‡ø¢üJÉSUø³åFz8³2‘ÁÙîw ŸcW›8;D;ï —ýGßøJÞMó¢ÔèØ£«âqGèTʶfxs#C•Z»qK5š/325+¶Ígx%Ý„”bvª€œüWz¢WWx=?'n´žb/óøóŒoÇ;ŠŒy݃â;1®ë>G{[²ŠÝÌ®í^{£";Äò¹J_¼Í€¿›cO^¨äGÔ&ŽûÙ‰»x^ÆV‰a£=³{>láÚý‘–zƒHx´ŒÅy„cs.ÚÏ`´ÂAñÁÐðŒx̨*̱LüAn("p+I5AÌø˪áVÎôö~ž? Ñ®L¼‡àv¯HŸ·Ï?¶î{¹R,Þ€Òy}ë¹u:¯ä8wuS…AÒ{ðÃ#`:èEë>}Y\ù»´Íœ³g(ghÍhÒèX¬C>pùµ6˜\7OmÔ­êý{›]T%Åb¦º#noì^ÞÙ÷ÔõµDï«/ÌÛå㯶“´ÄéÆQ±À×*œ6O*Æ°À(g™/¹ƒŽWàø=ÂT9QäKWÑGˆ”ÖW>ÈvŸg²û£…ûyác}¤¡®æ¼;Ž¦ƒ¨žü*á¶*,GhP¡Ž(ñ̪ìa»©ð3®¢Ü·AƒbØ:+w+Vél„màPÐ<Ç=÷ìlÿªñÆácìzðµ™Qï]UVÎøꪪœ7¡Y½í s ¯Mˆì|¨Õ“#Ùy ¦å¡{»V¯›œ—è÷»9‰«ÂÅiI5µ(ëÕo¶èñ!Jëƒ{mÞZ¾ Û)‡±Cšè5ïG¸J’›‘䵂 ûÑ‚‡£“E°’ÄS3–…Ï|å¾\•“,YzxSw#Ä7Êëß{źl¹_#CkX#`½ÙeÊK ÁW3ÊçÍÀeèérN‰iæÃY»(âçö•!Ô×þwNÅz8¬g¬‘Øî[ïa#`cæîaÏÚÛµ/#Ô{ôQˤ°A@²!F ¡¨³Ð_20¤=Û1QÞgN\Ür€–!aÓÛßáÕ¾]¸ü2Är^œiÇã°›>^oóêìjÁë<›úæ/ôQ?£4ÌêÆ佤xÏXëæ“w99^4ð”M¹”Ô²è4OfÅ—5ëôðý{€½/â¹AËÚ½4ÀðÀ&ü‘åúðP6Q„q‡U4p®‘bG;ã‚tóWNgvz8¥r‹ì,JÊlQ‰‹š«.ç Û_–bd‹—Âi92@X½Ä€SÇ`y„®.:ß³Ÿl®ð¦u›T±_dR ó0P6*Ù+&¸†è8‚÷܃¯Éߥ3Ûv¾×õŽÉáÒ©áú6ëçQ)®@ sGMîYrR Ü€€=4á7dn«™"uÚ/o¬RûûJK»ÞÄFíòÓj]Mþñb=ù/¨òwª[k¿¶/ˆG‚6¥œ¦+:9žç@g²Ž_|’yäÐùSç@Í·‰GÆX;43w#®æôczyÀü–¿Ö.Wú(!Îg¸àºx8ðÅ´ÛÒ”Ò·I ôþ±ë>öàõGÃé3Ód<ží–Æ–Ë&ÝO‘º©ˆ£«u2ùKë^ÿ2ÊÂg¹>xÀDx5…~Îk»×oú°ŒU/.<#`)(C_ØX^QåpPY@¾û ¼?ׄR\”J7Û騑²ÕágÓö:¤×¸*«Âø6De½ã1ëZÈìˆÔ£’ ×Pìí~¾r£ßáPXE{9~¦#Å5&ÇR£ «¼®©à¸d »Tl¹v#-ªê1!CÞ-w?l¸èk1O#C¬»„î:¹>{wöïºîFÖ-¨68kJ§7Þ¼9nÓ›šœî~pÓNµû›‰ gwä¢8oÛÈ€0ÉŠ»¥ÜÏ܇¢X¿GÁÁ:—L—8G9§8äÃÓ–-T¸¢`”ñ#-`Ã9ܱbukI?œÕ.ÄÄzÔÚÇjñ>JQN~—æ5ÝÁ\*š yÙdª6*ÂŒõC'sŒp»Ÿš•˜¢ P÷ÜØN Š.ús <à)žµ^W÷J$´"'“âç][±€ «Ù5Yg„Xe:U‘ï¨$[Ç–o¥Ç>»m‡¤c¿äO^'ÁˆHÛRFy ç@`w=‚l„båá8™±Bë}(¦ªßbí»^×6¥m(صÓm°QñjE¤ñ6WACÜê\ƒ÷w9ÓøF#CLanº:Û‚áñ·9o)ˆ;®1±´Ìs[ûýhãjòÌqü·ãJ]Dcvj‚ÔiÀåù­ÀbŒ~¹Ûô99fÃ#Tûˆ'ÃHŸºäíµú~£†¦¨Ðz(Í°&Ðv×Þ l¬Ùŧ!‡ôß^•æ…7RgU\óíuŽÞeiaˆç}¹ö_Ã%Fç < óD"[ÚŽ`–’qýà¤ë´Ež›|›îŒ’Þ×ÆuêhEžù|ES¶!èÅV~~­”.‘מó[MZàÈD#C<ÍW>÷†rtŸð\)ø=²Ÿng\pY9Gx×2‡Hßwžª"4í”FӻɄuò”mˆ¦øÅÍÔ·²'Õ' ñ^#ì ÄëRa0â÷FÓ$¨AŠt˜ûÐøpu礪{#`ÙÂ19i¾pÛ²x—(ÅPˆ¥3ÃX«Æ«Qöm¢3Žb­îJn¯;ä<ò'²¯X³¶j˜(Ò­ ¿\Èí{l¶ö^¦såŒjE¥pÁʵ<ŽcÃÚt1”ͬJ¤ìnI¤žƒü'¡ ÍÛV‹òÖÜoñÚ{/VudÏ€ˆµ„£*Å " ɳhu©›aJN-W˵!wm¥µûEúì×`óƒØŠc_¹Ì1Œi'Baä>Ý!pt£$yzðÆ”¿L„éfÜÏ÷Vä@“áU~W]Ù|qJ©÷Ôf|¼œR<¸ì› Žg}Šo[©Æa‡˜í;ù8qs#C²|HCÏüŸ¢£™¦û1t»_$›PƒÈÀ4¬ÁÖ‹Üå#-ßÉN`©TlÞÝÝ·6ù÷ö>šèØÌ%tŸyø.Z3‘1¬–8ãùn`{~J°]ü8Ó;¯y ÛkMº2{ÁMÈWÇ!Px†±t¶Øt:99Þ‘=Ö‹šƒg+d*^Ɉ¨lýë7%§Àé;;ŸdÛQÚ®šƒz=|8Ü[¶ÍkH"aý¤öž-oâQ‡À-PÐq@¾xÑP¾œ…°QÍ)&¹H#`å#nÕ?]ã9ËaH•¸“žzoÃÆ•ê½2–«a¨kQ{ ÑE.!l̨ RÆŪVl"SwÃÁÃÜ’ {{§Ù ¶.OBð¤§t˜ëi¢pmåO˜·™ÍJ¶ô€ô@´„™$¢)X¶ór[ʉÏÄß#`$¸ÔÊç°^vLa2Ï-Ò!¼;#Cã[rï–‹g»Á3¸=,J{t'6ÌW‹ˆô©|‹ˆGPÙ—±#-BWmºðÝu¼‰KÇp Ç–0gœ»ü9¾Í›+×ù˜#-ü[òúhñÃ×êÙëaçƒú¹„|\‡èv§ÇŸÏòÇâ2縌™k¥Ù‡‡¡HÆõðsý™<€Â©ó{å }ÞŠôÿH*9ZÁéÛv4ìçÖìµ#CëgEÁËégv÷‹SWö†øÀFe[''Þ/‹†“ ç17‚V«‰}½jÀ’t)/Ö¿g‘#-öŸºšKþÎø?€ð̹ÖúögPëN·ð÷s~ØBk…Gr¤¢'8ð¢©#-£ÜŽa“­»«Ì~‚w¼\}N¼Æ—U ÍýVó“%4¹2eà€€$.‚¨#`IæÍXs¢íÐ"³Õ›înö߆¼¹;óÓÂ6\:|6nñéŒ<,¿Âæ¼ aî‹€|\N8òZ%#CÍ Í(Ìo¶sßúuܸ¯ÙWßë¼êÐf£ºG9„HVmOLÞ)ÇZ'Ý/›2D"ƒüïdÆ Ç]¤Š"AdX} Ÿ i(˜X‘ˆ†’•D#-#`öý;ÇÊÿOípѱþ¬ñQHùöëìâŸ_ÈúrúàY+øÁ»~7¦Þ(@TúT1!Ü¥Ä*Ýš°÷¥(Íɉʴ˜Åóµ$†0ëþxâÿWèÏoÓí+ËòÙª,c¸J ò•CRžÚç—ÃoUÏ·Çšôª‹éÃZN&HiQ±ÚUBÄÀ­#-‚#- ¢O#é* ¡éMî½#`u‚­$âê,š°˜HwI`‰[ãÊX()"‘!(>P€1ƒÀ@¤I¨)Q‚g»Âô%§ñµO°®¹’m LÍö ô솄ºü«“æù¯Ø÷-7-Y§cü´¾èi¦€ŠŠÈš%¾Üó’ÑO sÌS°[&#Cƒoô§óEÓõ}w òr{ÆŒäí÷(‚#-\$Mwlà6ß¹ Š8Ñ'¦=K×vLA,y#pÒ÷¢‰ˆ‘§žkø?ƒá¯dý>3íý³lÇ·aÍÛUaù‹ì*Z¬J¨T&* …úðÁæ»Ê© K×tñ,ú‰2ò_† ·¤¥ˆoićߡ]úb½ÏuÙ¹»¦Úcù®ÿKêyNUÜöO¬Å*#¼Ôáž4s#Cp–ó'|OR,\p’‚K'›m#n $&Å-ŸÍdÃù&,ßßö°ädp˜f$LÈbŠ Š çè3¯oý¸Í~{’#C„#ó½öèG®‚¿“Õgü¿«M±àø&=î3Bž¶'ìßöó¹ç„’Ùñ?£¢BŒĹæ%æÓß=##Ñž6’GºÚSh?ß…I®!á(dˆü£„ ~øð—vƒñ03Ù_Èc?›÷½{÷á r¸ƒ“îA7Òƒ”'^â@Ó]Ë…Më½õˆqŽ!¢¿×/_Ÿ÷¿[_—‡>œ„bÿŠŸ8NãåŸäÍO ߤ1ˆ9²ð>l½ÑôT¼ü§ 0I‡V×`ÿ)|þWÏæ›__£>1'VÀ"éG3qXwÍf°ÌôÖ#uxtð/©Ý@{&ÜʧÈ{ºõ-`ebð.±T2óSjl8°©£-$}øW6Ö$h?ÍûvÞR3‚ôã_ìĦÉ2a%åðnŒΘ)Ž›DBÿ.w–ùQ¶Bþ/üh1Ãì.˜Šœ~­ëêÄ=\MM0v~¦OÉŽ—:—5HfȾ¿cú©”{ABIëïø}|NuæúPÆ‘WuiîŸæG¤yÈ9ÃõÔüPÝ„!¶~„Q×oòÞ»JãZ_#C³ÔäG×U¸RÃß.RȺ#`©¹~æç‹Ê3v`ô"žmîÀ¢yøóŸYĶ|̺ÈI†ûP]°?f\¢E|ÑáK$ ¼ê!4¦÷FLù˜††ÕÌŽ½vür­Ìƒ¦—lU‹êEÍB† ª­@ÉøCCž¿Žá»$Ôh¬ò¶ëä)ÃÔ)aOa.o÷á­xÓo0#ºkCÚ®ù~I+{H­4À¼AÔ¼@(š:ù2‘B1ç Ön7uu@1ý'Ô•@~o®hM´|Hs#Câ0Ô (ò®®G:…ŸªÆ1v§,×Sº‹éëC}߸=קw49ÑÂD^G! #tè‚`§Ôi›ÊÇ/¾ ð}µµ”)ÈT€«f ÊÓ×r#-œ&â>uÆ#-ÿù#r#-/Rª®ý1„À¸ü‚Z£»yÏGv^÷u°Qœh™½]éúª(<$õ_¶M¤í»ýubëyÝ-»;72Çh< Þ»=ß….‡ÎNKí#`Ÿ#C5WÏJR.«¶oÅ…¤µ¡’ªß+ÁëÁ…̺Y¸‹WB€n¹%ÉDBçÖ~ëq uE:¨æºC`zQï> ?)¬Ç…tóõðvj]tì®Q\]­› d[àú߬:üÎic˜ÿt¾h¥ÅûÝò Î ^ßí {'»5߭ʤ”Œ+Ó!èÓ8üÛ•Ïðî1®òø©½^Û®€þæÍûx|F(ŸUBñ·#C`#i«õ7Žç£Ì´âëÈæêKxä#`󌡚(¢#-5ª3*{…@bRê›Õñå•ñ’ÝDy¼ïèëL5,fhè†èõPQF¡¬F¤“ª( >,¢P~ ±.<=â,?»™nêÖ#C­@7p˜@(ñ(ì•H0ñe+÷h¡‹#`ïU>zát0gR&ìT¥3“j¼w $˜…X'º¢šggèW÷Ç"òÁyœ/ëåC Â#`úå)èÆž™¿›«Ås×Çy¾*œñ™Û#C!Mù€(N¹ˆ/Qz#`=`uÜô`îÓ‹A†‰KÎwë略4Z1Äiã»8Øtâžÿ¾ÜçòZÖåúœÃ‚=D9<1›fË·âÊNþl~¯ÊÄÓ¬ç>aåÝÒo TŒt.Á±žÖn¸4;ö‡TÊ;gH¾4„ɱšôübêŽë¤äèe²¥[Zhøp ‰ÿEø"Ô{X+„G—ÎýpR…%Ž¹U…ï [ÄDuáâäz1dëòÇ2Õº7Ô–Xáëß¹í°éxÖɱ¥_Ëò?“Yëõý¤Ï~±Ñž+©Ã`Œ¼ør¯@ˆ"ýn(H—Ì-ã¾H_ƒ&¿#C±¬~;/í½åÈ3°„!  mš*¢ØÄJâ“}f6µo#ÏZ ¨sû+PÖ/åÈ(š?LeË=ä#`ļƒl:i¯`üˆä³ÇÆà1B÷+—4)†öxëäîÞÒæ’) ¨M‚²¢®7(uïmkؼšÃß±t¼Ë™¯~ùdP>} „@¯¼öÞ—M(F¦„ Ù¹`€|»Ÿ<ºƒ¼Ø0æ6ºàŠ ­M#-*”ŸõŽß/‚Ø•¾6$ƒ8Ÿë^vYÍ>,ŽPm&ò½\“|ìåÛ€çç)Ä#`tæˆJÃk¶Æü2:‰'Öx´zEÎ#-K«Z#CjS”ªh´…Ë¥Cø²ÏÁË õþÖ«¶Å˜Mïá'gÑL*u(”"PÊ#`ñf]åÇ.@¥{.{¥w#CVÚK—ûõºœ¿)ªÄÜg}I¤Zf·¨Ål’©ñ¢kã¦l­'±å½jd˜ L8ILĆg7²o\HÆèB”RïŸt`»Ç¯óùþFÓQÈ-i ¶åɇœW¨‰ ÝUtOå~®fb²ôð=úTaý[ƒÏð½õr‰!§ë>È^louvBŒŸ.i‡"£A­‡C…Ôó!P‰Lî¢ ©~Ž1S#`ÂOâÖš–8é¯76–tlßß?:b ¿%ËvVH*áÅØbñG3F;cÊ4)Ò!nÈÎÈ$À$%q[‡˜4b¾+swˆÆ’sñBÜ¢,¢Þÿ<üÚ|< >%o–¤òô{P3Ñg«•ÃÁ”©Óµgm Ð<¥öÞ:ZØVä‚ò$Ñ€S®óKÛšO¿“µãÙ*‹ä›thË­—¥ã7èƒêp¥ë¦X^yóV âÍŸn6`\DwÝJ5ÏVƒ€2á`2lAlx/_9Èh‡FCc.­|Êr>hjó×Â<}v˲zÌy÷ÃÛî1'Ÿº8¾°H=(ÝîX>X…p/UEå\Ðw‚9Æ r¾m>7›ø½U"°ñú&ÌvöÙ¾.íD-†3"I…ý>aDÜ`"yïqv-Æ#`ó悃ÀdhBÒšì×q4͈ëD‡rºÜØEÔ¼då´ÄÐ[d F:Â>#*¦’G*#CŠ»¢P…ÐјK3$C@3\îÒƒƒÖ&M ]{ù¥IÁxIŒ†:¥íjÿÊ*ŒÁŽµGe`JÆŒºµ4¼_¬6ðL&,üëÈ5£2#`B$˜a>ú(Tâ«Ìõ›N®šMÃwlž6hør¤"ÎèÆGG¨:H<7ܘs:øýÇŸb¼‘(£{Ó½Ìñ„UÌ‚ô>rw¥×ß=ÎZm±ºŽþK0sâç»%#Ì–@Ä0ôÖ¬yk–Û:A]ãQJ>Œ7‡”l#`£2aw~VPPü#-KüÞýP°Ê·ã½°¬“£åœ‘Ÿ°€6R¶ç»o¨0p[Ú¤ÁDØRAvó_¡ÜçBc"ÆRWJ=XÝ=NÑêþ6ëm¡†ùCýOï"‚äH5´AŽî¹¯~¿¤ÆÇØ9©õ¯Ä?»Ô¶M»ä~È5ü¿Ï¢¸ú³d”ù}^ü®Mcë°_øwFXÃ>N²>þaÞ9¶•[Ûä_ßöÛMò ‚ñÞ 7UA#CÓG€GºFŽ‚Šê î樹0 1ˆÅ¬t;Á$“#-þÏÖž„ß½1r„¿ä/ Í*˜‡7ûRZÙAC+Zÿ*V-<3É?OSxÿ¯Ïk§ofÆïófƒCÀÇÖ\^l»Dwoðs‡ (*ª$ª(b«ƒµ÷Έ" žíM Sþ h¼ÁØ.ã³ú¢Ï‡#CE³®÷ˆ ¨(úN9ëri¶ÐTÖTVíþÞÁþŸwÃB«\Ç/ɯ‡í¿ìa>¾\ÕTQaPA/è?®¿ï|~?¬¿âkò~gú™ú?I_‘™•s°G ÅÒ¸nòà {»âïþö~Œ~ïCû¤D*N]<¢—àG%~2úcêƒí”>j“LüŸ†‡ÖUUA°Àëõ`p!ùA©¶¤¡Lÿi(a+Á¿Š4û!C{N(ÄaþÿÇæ&©ß$æ Ô€ ãíæõ}ÍÞD‚0P=úÀöqÅ4ûg‡÷Ôsð*ê~=;T‡$ ÚJNLž©Iò¢ls¸*³’üþà „}cÌÈ;pØýd™2ô(ôˆüƒK÷#CœN#xûDRƒ`“G»rx¯Ù&:ëÉ8u7?¶#-00sϪÃ2h….Ò¶h'뢃Ü>ø{Êø QHêATG‚P2tlÁ*„!½4’$BSqO=dÈð[ÈøÜoôŒAoÜHÞäù•‡Ð¤ ] iÁ=žËvtd½\Ü@#-:Ö µB@n”NT¼H›Š #Cµà0KHÈbvQ€ “»æɶ=pn‚'=KüØ¢­øBCqñ;·2I°è(•wêV'Ƥ¡Jæ—ÜšÃo†Œ4ÏhÙío8Gpü ÎÍþ27Ìû ÜÁH·pG™£µ¯/`ýX†ïí]‘ß¡¾ì$“1BkM´5·Õ»RDÆÃx¦7³§4Ùo±1‘Ý­­Šp”"·Ç\Ɔ“NA|´¢JÀv¨XY½#È®ãÓceeòÆYIb¨ÏÜXóÉ_i-//ás՚㛵«¥'¥ãÑù Àž9„âÈþ®Æì ÚàpC hw%BdÕ9¦‡9w Þ/WŸ]Ž!Ì×— æ¨Â,PP¥|YT“ B>Ó#-H7Ï#-š©þùЀPà`@á!'¡ÿ>õT6c;í¦‡òDY££ÿe÷·ù"¿é¿óäÿÝû¯ÙözŸù­v×ð«þVŒh÷žÆÑ8rùf3ï’Ì®q3Ã/g¯hÿÎ÷g(Ê f#CŒÕ2iR‹NK²w{rË\k4~9'³™‡^×ûÝí»ßó¹ÉbËë2-‚Pyä?R_;nk#£s},ù´I”¬áÝhÑØ–õJxg¹”ßœW“™’‡ðv;/8Ùï|í‚JÇ#›Qåe³«¢[Bmî§J—qäËhk|EË]¬‡¤~ÈC0èó]îqÉ¿ñõöèšòÈ°\’²²¯ú²UÊöbIêœ?‚„/R}¢Di:]Ѳi#CKiQ¶²ÉF†Ëh¾¹{ S¯YNX¸d9Þ¬"aŒŒ’.4{¨Ú²#³ÝóÂ,ÄÚ óÏúm½y–ÖM'Âì“^٨̻ô9â\ɦÄ4+OŽÊˆéwËâuu²k³Fø®zÒØú £Kœ¾tõ'“>gu”ZÒW¸ÇSÿqçÉ ígÊfqí'†¼þù’z>˜;Œä;÷å,Ýžä=±ˆúNø_«æÍ`Íß^^¿@èF&ƉûóßòÆB8¸øŽ\!(`62À1É 3$C~Sîz¶€Ša ¨“=pßÙ…Í%Ѩ»Ÿò¾WؾN³Ý‡Ôúô|8©Qˆ{X2j׎»V#CvöCŒ\¹ XïÀåäuÔ+œ¼$9t6Ð]Txº~Æ„9×â¤4>B=ž$P£Q¥Ñ ­Àñ×vÂçùùS$-^ž@Â"JáÔîÿž8Ûጣíqë0ëçEJ”™F DQvC½ä<èyrømêÿ¨Æu‘VDDVN¾¯—ShÒNÝÄÜ=ÁðDääAçbVxì1 ê­ì6‡a$Ó±2—ņ“&9äØÃ3uíϧG·Ä¹Lù¬«žõœ‚s#Þ’o›ºq¥ä#C÷ wÙ¼&F!ï0À÷àêZJz #` w…zG±ß’Y6HÎPFaätõlvýìâk{4C·]äƒÛ¸ç¦ô ÔˆGµ>Íçô¾²›ô uמŸÜò=GO)¨N¤”ÂwÂûRë'´új¸æ-7èIM‰è‘ œF­Ö@È:ÓZäDs+s¶ yñBÐ8aô&º«>‡|PY‹œ†Ñ¸Ú´ XD°t±êà5³Ð‚@éPàÑŽºE0ÎǶ;ãÝÇ öYÂXéèÀÔ„öAãÊ|¡m·zsú]5عϊÕæzô¹Š§vÆÝGѦ&‘£ ›ø#CÏoÀù•žx†¼¨ÆÔœY‰Ú\“Ÿ=P#C"»(Û=2zjyÄŒû\weiÒp!C¶NH`’=|ÿ6#“˜lA¼a8d…#CÝXÈ"Ž§§Ë‡]•SßÎ'—[Û¾¹°ha2n`ü¥í{ÂÀÏ‚ USé<ùïPÚÍòý,úî¿n+•"wì}šÙ×>ßóšøO•{MJÉ]‰Þ?£úpQÙ#C¿hïN%6²¤ïœj¹ážV3#`mˆ" *Œ*rh¨#C"“¡-ŸñxiÈöÍýÁ|ׯØøjðz™3R³ÃPnÁË•õM:(¸=ûõC5w/fÉ{OFœC]‰öMdêäi™åÅN}®‹ö …j¶I~«Î=Œ)$Ð?|:ç}<–={ó­USTÍ2•EÖÆC¼ðÀðPô½‡q÷5á#hì ½°î\êMi7diˆ’ç÷b8Â:3†ö’ߌØn4™ø×Gؾ©aƒ°¶—cžƒ¯ŒÛsØáûÁÓ&- æ#`„Ù‡”è“wY1–ÊFNÀqÙOkmŒcK¢àÚPü].xÏ=гó#ÐczQ&7 &¨ ;ä.Fè½@yÜ¥Ê\¢ÉáßþÓÜðÄ^ž'‡ûøî„GN“^>&¤s&M%)&ÓÚv*L‡®ƒ”ôòÌðöla;º[êžj¶J0}e ”¦j OȘiÙ}ß«‚ŸeŒ;šŸÑWÉœ\!Α*gõyÕÕºÚ LÝt\APöšž¤í®ðaxSu5¡g#`è¨aè:öŽÒmµT4£ äpk´ ?rXb ц¼±7îÂxæpΤõ%èÀ–±ž5,:ôVzaÉ&eø3ŠN2p!ò‡ÇNnä\Æ«—¥7$lƒn9>£å×j÷ò=Æšá±2´„S ¸˜ˆyͧ•Plš½X˜¨å„™G ƒI¥e7©øðmÕÑWô“æ‰òÙí¾šâ>§84Úæî¸#¡@ïÝþ°:•³ã#-P‚!ËG–ù¹Âuyùž§k©xe )›Ñ¤÷­û÷ÃÆø$‰¯|’Õí\phn@ÓˆÑpuŽ&Á#{F䎘F#C‰ï×*7'Ø E«l* „46Û Ütãèºmýß´œ-'H~œß›:C½„=ÞÏ`Ÿ5\1¤ éÆ=e»³òäÑ"sÌdC¹%ìë\åAZi¿Ì95/ ù#CËrRXÿ›Búü‡³möÃ餜óDxì!@Ú lx‡”›zí4:àXè¹Ã=Åt¢ŽRÁOŽ‡z\=ò¨i~™Ú$I’ %H.ÄÀjFÅÀÛ]ëCf¢æq¿s•«‹£8ŪäÎÕw$UÜûYÂP7eÊÜτܵ4Óé„&CClŒ³v…¡tt7B#2±ÕND¼I¹îlØ;Ÿ|+H[¶oi±Ž ÇÌùUy5óü‡¢:üù°ù`È̼Æ aG(ÏN¿÷RŽ¯ý\yQN¥‚ðižC#`ìñ3Îù#'·Œ?°ü1`ŽÉšÎeWw[Sæ¨;O¿Þ)Ái±T?g»qŒˆÜf!0ÍZ±a’ÿÂüoTÈè/rL§#-EÄ×É&D#bÕØ*–æ0ßz'¶8ƒò=55‘/²k!6’vØê†5#C< TÜó ¡ŒHŠˆj4QZ†“©ß͹ÌMˆžK(ª”1Ýi_¨»³ €äôN•N¶IF,oÅ yäíŒÑØ#-\/ÆaÎ)SƒmTHäÿ'Ÿ‘…æ'ÑÊ|ÉÓ©Ú#-~/ ÁÜÕ:M|*Vg~öZDAŒ=µæiP;'ÕÖd؆vÆ”)UG}E¨4!ƒ¸nû½‡tàˆ·–*#-µ? Iy¯‘ ø#ˆÓ{"©þ ÄŸáiö<Í?Í4/ËåF8juŠ”@'|^.䛬ÚÓ¤¢H ]RÄUiC[º‡–£xÙˆc#-âÁôpr|Ö‡[2aQ#äZ¦¨Ø¢¬êaz3‡ñxT)^#-Ù÷@ã#C÷¶9C÷âaéÄýú©8üûE®'àúlŽÎöëÐc&°~ N/űkm#`¶»–½šˆƒ¦Ogbpº ”{<àêü÷¸ìàñ݈NÈìë♾MØoå›>uÖ<“¸`;ï9Ç»Ž P’wÌã—ç(îÐÜa§PÄžp”ÈwžUèd$StáYßT¯iì@@ÁÀdKÄ¡ó‘3™áÒx¦™Ãˆ½¤u -èÀuž !äm:ÎÇ¡£Ô”‡‰™Øh¦’±LAHEt##-¤‰¦#`*G4Æ–ÚÉ#-ï¸/p¤®HÚ“Ãe*“Ýå)E(iG~Q‡Ê:FÂsÊùzÝÁ̾0Ïl5‹Ê r·êó:›l(*’’™±ã |¡¸#îëøWÂê,©k35>)´|­÷Iü\ˆTÏ}•~âßC7ŸÜ'w@ž½ŠäC“Ô_ŸÂå†ý” 3Óé¹æ#`°Àø>úS©ã#UTöIPKÊ®;Ar©3Þ]ÍÞùód¢ä“OŽë){X_ëaê`að|¸ý]w]ù?r`$%Ó¿—r½7 A€DI…Q¦B»î¨îq0‘EFX#-vO¯ÖÈbcsë[¶Õ×—Òß%…ŒÔ#`Qhˆ¾É¸kp熌.,ÐF#`îу€ÐIïˆ/D§ÏŤ@ƒðJ}?ù9CÈ6š)#Ž åÍ]=n©Ù'Â#`D÷Vb•é‘æ^#Cç¡Ö?gVß.»p6€ñ”쓯fvgË?öŽ©²ùÊpÙùïýe~h@¿ýïïŸËù~³×éî<‡ò`ƒ?¤ôŠ¢#˜uë¡O³u¨?àk¾H‡¤#-ˆ™áC–E›·sÖä°Õ†ÍÃú¾DáÜq‡(Ê>Y¢"¿z¤4²€Ý…–Rîugð?;)›ô­1‘Tów•ÞØ~@YHe½‹âläuv÷D'cà *¬C=J‚/s ?Š¼@›þ/”ØGæjaö}<¼Õ烓ãÛó™˜û‹úŽ€fá»/‘¢ÕÌD 'ëù¿úÿêÿÊúÿå{Ä¿ïþp¢3ü÷eÿš+ù•MAºÂÑ™j9Îê2huá</¼É­5|''F£ß/ëiB´ßpíéϧ‰íûOÍê#à$(I'¤U D,ñ°%ÃÂ}A’6^ÜɤIìúà˜/r)¡ùH˜$/$e‰#-pÑø­vèk\YÈ’DÔ,IÞ°þïö»6`Æ’å¢#eGÞÌSˆª¨Ù†åVj3oŽÃ­³;鄘?qÿŽøÚãº=·t/Pß#-9òôHžŽ}ž °?Ãý?«§þÿ¼|þýy‚§8@Oק÷°ÖM¶Ñd.L%³ôA|¹ëíO:¾Ò ú+ÉÕ3wœq‰#-=ìðd’)[TŸÚêïšI2­u «immŠßœ#C?ßù#-¨Â\#-üQ˜0ÞÊE­ª«;Õ GØ>€Âaîo¦=®«Ç1!aò¼ýR•QÈåd±ý†L‚èŽ8(֚ƻDŽ‰”Pšë€{qW´ Ãi3ê¹ÿuÈ( ‹çÚ¬Ù¶ÓÙ‹½¿ŸãÏó#]hüZQò5y2ÌxðOœî#z…læ~T#Cè6¬‘ª×xﶇÇãéDæÔN€Šw…úè¯rdá‚k#±BL™3¾?#`DíU}G.Àº€oƒ‡üŸ¸Ä""òSÊ?ãÿó{ËÇ™ØÍ?8À b(Ô~YƒÇ‹§MCR8zFºd´@hAÁÊ3‹°žÛ†€Yqa·¤(D}3{·O#C>'óËÂÚÈúY§,'ÐôáÇëëðúz~`à;s˜?„5D,DÁ-L}v#` ÌIŸAÿåªa؈1ãâ#-jÿÑî9 ¤m<îà¯ÀÍ#-\ÅîÙDX;kŒµû <Ôô÷/ôÕQW#CSá >3Ì.Sí#C5=ßüb—`‰ÔT0L™t*È-¢ÿ俨EÑ#-f'âÇ£UœÆR¾L´&³~÷vaàÛ³èi°¤† ¨j:ǖׇz?J’ŽCÄ£¯]ÀáÌ»³Æt¡CùßBzt„üéÜËu18•HøˆE#-±Mª~Áëßç„W¹ÊÄùËûuZéBÀrþ•ðGè…à‚-Žª¼=èÄ]ÇfyȨêxÌñBŒ´M{¿üæúfC2ÅFrâÉßÌ <¥îâðÝØjóûG,l”„yô#ªJ}2pPÔ‘’-ô½ÂÿYAç­ÇáO²r’濈æ ÇïøíøÐdQÿ*äS ð¨ƒ/ñOkÇÌUžŒw_K:b‹h¢!¢éù²ú.é.w0Ðå†Ûšïéúÿ3þlÿWRÍçPâ‡È?¾Nü‡ÄQßÞ½‚{Ú+ÝÝà§Æ—ûÕlßá3x鬛0­·Ù¦‡Qþé]±½$žè?È@؃FýÑäAÔŽgþ@è¢%AðB»PpùúQˆ_þˆØ¥á/Uõ”żL’)•š Ы@IÙáoo1d*dŒ<ꌈEï iª"ŠKK) ±”¬hOÈšÍÛ¿’ý§m‰%rõ Ýé1%ýÕÓÉ9’a$µÀm}OAôs&‡ öñù@ • ò¸õùzû|Ü£Éâ`$¤Õ^öc“FdÑ­o1Ñvq€#`Q BP•böÅ «ywwgËhôÄ=cµ]‹¡ð•ÇÜR.¦`ˆ. }ꉬ;ï[ÔŸ§dØ9%ñüxqßö·#Cþµè”¥üM¬Ž1½îϘ0w‹Á,ž–Ïó½xã2óf@µ—ëÐzgíxC›ãâIö„Û× ]#`àrÕIvÕØÍÞ¡l#-L)»º6b2£ÆFfiìºìÆKü­mªS,[µêâ\Ü€ÒìwÛ+9¡¤ŽºÙÎò¬ßÊ¿“þL¯A½+@&r#-Å2Qbüj)=4T;Ķ »Šâ‹à¡ëh> Ò‚“£L£é¬4+ÞÌuª8ž2ÊCTo0qALÁK³ã«C«»\Ä50ä<Øç¤:ÔuÂúrOyqpÅÄ#`VX–µ°ÿ}¾ ™›™œ"Š”LêC3Š”µøbZ2n}ó¢:ÏcÖ.>æMòûwã=D’‹²Ê>4°š;ܳ35õŠÝ[»4̱Œ^Úœ³37ÄúŒì{ÂIסPƒHh憅 ÏðÛa2…Ï]»'x®33¡†SOxê.—c{›ù»¹HkO¾Ñ)”É¥KƱ}Ïש¨ª0A!#`$6È â€"YB6òÆ/×K„Ó-®]ÙÚôâ Æûeç«ó»ªÛŒ<¯‡<´Tk¨ÞÓo ø@D@´\*9¹þCä÷Ÿ~ô‘î—x¤"""ô}"•îyš¹½L~àÄ&­eµû]vZ*#CY¨Jg†Ô¶íXAì:9G!Ù^pZo‘÷=/Asê#d–¾éd1JW.ë% :Š:í ¯&‚œfÆ/¸ ŽŠšÖ'2ß_ég£ l&öf5þ]Ÿø5hÆ£ûœÁ»Þç®»Ñ$nÌÌÚ½Þ3BE‘B¦kh¹õáËiÂ#C18*³¶q”¤M˜m{|Z#-–¦¢ŠT?ê`ñ"1ó9<†ƒE@^>žïâ÷üü{8þ@ (e„+v»‡Ãל#Ct×ïЗ)1³äŸ÷õÓñŸ"„öCþñOļKf9?ùˆ¦1#-¡^»ú^ˆ^ÿ6®±^ÃÒ%‡ÉÔÎLÇŸMaÝ“b¡šÀ“©cÜœU森»¥/Ÿ#ãàœD^›ïßË“Ë5ù¸ü€ã¼üç’ôËŸhª¶ªÀ7Ò8>hó¬H›úW+ Æ.D†v΃yUHߺ·áS„pB¬‚žˆ—®c¬"ƒU.@ùïÁÓ…g ˜#-@ëæ%©Ï¹šCäT ‡ù#Ö£ßî^¦ö|K“3òÙÃØ|äJ" öP¿»ó?S½cP£Œ­þl¨ >KÝP wÄÖƒë)3îz#`J÷‹‡Æ­)" (ü„<€óÙ/À̸j¢ˆÆLžÃnÊ1CCØ"Ûº?—*Ì ùÎäÁt'u¼M?¼âk%^K†²nÒr,Á°ÁÊHådƒr«2ëH+f}µt\¶#ëáG¾/ödÏ3Pxxó‘Ó#ÍðÞ”#ao­T­ë¼Ì!þO­}»ùâ´¸æ â"pf&±ß5UF—ø6®LÔ]ô0…¨÷8ç&ˆ->#‘Ó;󎪪<’ ‚×+JTç¢;^/Y—WÇÅíÓªñ{VKÌ?XxÖe,ì`#©j#C@þÏÝl[ûCVÑ=ëÞ5ü¤=Ìæ7vØùy‡ÔÕÑv9VóOIˆ¯'æÜI”ÈŠR§P¿)5&’Ú¨Ž2Ú×µ¼›lC®ÿ_¥•CˆÞ#'ª"ÕlÆ)û_Ö£ã¾~úù P†6VbAú•C+JÔ\:ãàÔ»R)KÎëœúß9ÉöiùFÚס{Õ×"Ù9äFøSþ„íÝL8dÊûTÿ•=S¥ÝaË3†÷šÎ~Šñ’û·Ù|$äÞ‡5/1ÝÏggúûÿ>óìýrÕ”»^¬s¾+ë×¹ƒÑ„Þ¢Q⢫f€’Ò¹0Ü®`£:3Míóö'†ºy'ÚNû˜1’?ʺ›?E #`ß®ÓõŒ§“±l^%­žÑú“¦VéeoòzJ²æQŠáâ„Ò«ªý[b7²:ø|`›ÞÜN½tC®½¡ŒKü^ñ²Í΢«Šž2?ŽpñÈ¿Æÿ<ñvÈ5Õ}^o5“%úÜt9ñ¯Lzœ³ð54¥åÖé-Ȫ"®eG…!T^íÊÖeÈtŒ_µÜ*øqeÅ0I8þrUB¾kJéÏN‡wÆ÷±ˆÁ+á\õƒ7ë‘ÉÆŸH«ôrbv|fbÿ+ÔoEÃŽø—ô“·\kÅs¯$lº;‰ú¬Ûæ¿Z¯ô¿jÒâÇóraï›™Yh*ä¯^màÈ(Œ•"ÉgQÅÏ0ÚÃÈ×<À0±„·E·o“`õ Ø·9ÁƆ{ÖsóÑÄÒeÙ‘oÚÍX„J#`d#`îIÊôäÆtUZy7}3W»k^8°Ç1¯Ô…D1‡×ÐSǹù ûYôú—~šò†;CžUb_[ÕM¾K&†³^Ž>ó…J⭌פRT>*ó­Xômôô}øüÂxÓjÒ?w7ÑyMbÀ9n'V»¾½ÚÑôL¨îõq›xÜ„¯ó³¾j¸„žå9Q‰|Çäê¦ß~6W KÀ‰syyí¨âž“Ÿýz’eæ¢YBKò<k½\>+Æä]F¢gßc}`½¯¶ Ù‘Žß$ìGõs8ç[6èŠÃËJ:áé„ÑÍéêaÖà僞ûV¸(€*U³3Cš“÷^Ïë„׎œÄaäY[m±‹¯Ó½¡/Šcªu¿]uY® b:’sp/-К‘ŒüÔ,a¢ßM;¸‘N|!Ñù, ‹E÷gÔ ÂRmAQ"Å#`‚ØUX´æ~<ÝN¤ Äsc`<ãë`›½*„¦1ä!ªû7#õEF§,laÇcØçؼ³Qe+3¬!å½ýýU…Âj«ÕžoÓÖLšLFQ2âR‡¿¸©#9ü>qöëÉ×nJ¿}W:À¤Ù¤]"ÅúÇЙÇçJ·øç(T˜u9jçCNÉIæ2hjÔæK,£ÕôKiä|2æc~l¨ÓæûÝEÏ°wÍß'ÁØ!7ðª£ì«Kn}‹Oâå.S÷XÁ<–äñR0à…í&%J-Y ªõboÉÌ¿–pÇ™Éÿ6ÑcñÌ—Wí ÍxÆN3_¤&ðŽLm^‰­çÜ¢®¡¿RÚ-B¿„G§·Û6ƒÔ†yý®Pd`ÈÀ9A}," #CÄá¶zv¾¼uè½\ò7úLWlCwåÏÉnÀ%îÖðJg_£ìã…8Eà Bæ,ºóëty¥@3gh€á9˜’¬n›sºæÈ ™¨s+Ç1A¶ÖÙi=‚°"Š¼åˆARò³-¤áŒ€3ò nQãþ,ýñ$î6NÇ—ô•ƒ>¶ïÚ<±=hdLW¯‘HqU6_E‚ƒ²-¸¡ž*î#CYw¤À€hl=Y^A#²[ûæ|=ú¼‚…Ì6Óš–áW îa` ¾#7•1ªí6<}•s‚”›õ4ñQ#-n(FßU+ñw;ø®ûÙ·r‚<³©Ÿn3,ÿÀ´ÝvŠýÇÛÆ0 Ò“öúÖ¥,8âE¼Dì±Úc‡ëÝ™©ñ´t8ãüøŒâ#X‹[Nß“¥ý>ò»&æÆþ‹þ•ýZ××T1W ›˶=ìÏÇái‹y¹õkf!&J¼?éùL~þ@Þ ”kQ]}{ݘç¼JÉ?" ø½[ËzN^SF-u%qRƒ­@X®½ì‘ÆÅó kÅ®q ypBµÕs;ÇQ?G`Â+.™¹™¸}Œ‡åOVjM¶çô`ËߧKüi©êÖïzb´»œUÎÉëCEžCŽþ 2œãî_}G‚#Ín—ut{z$™`373Õ‘3#%x`‰"¥¿S(žïq!’%¢O×]ïî0Ìj“#qk…ØTÜ|¿69á©)¯ôÌ£3ßÕã4Û=MâU`,¶ê˜¤ÛãÚïô¼k_uúžØ÷ûaòeaþÇ4yî ù¨cyÕs>Cx0O™èÙfjh²$„’üÿLñu¤ñßÍ®›.»| w„ÿ(u‘ ^dlÒ,jÀŽC¾-"I´i÷º¦™óLù`¸È ô2»û?zâô¨ôÜt'³*C:zÚÃúˆÈ²áWúª§»}?{ªÍª€L)8xmÏ[÷,Ж£.¶ç³Ùo$-Y‚%z‡YÏ›ÎcXpùýØíîuîj|3tú7ÚOð_JŒgðfºè8òJsíßèÛaÞø|†e!}àØÊ“`¥"ÎòF­¯oá·‘bL µÿ†«ú?ÛFþ·þúúQÿ®T”ÌÖ#Cä3&Ìšøg†¼Õ žôsÁ`@T!€zU&‘2ª>÷âýiìfƒË†¸¨mÁ`o/áù^«w|&Þ ;oè>›Ç(Ïf"˜vè-—u«h?ìñ_~Ùå8S†|c_Ô(ß_8Ç–“8~ C*PÒé#`Qè.×l÷#C¼xß,ÒWfžä.ʼn~2HÚ’Ë+Ù/ Ʊû-5“r7§5­¢Q'@›e(h*"óµÓ´zîyî‹#ChC'Õ´Øà‹j=Æ¿7ì±útQ§„ ­‹Ò€ˆVORŒ)º)„”J¢¯öÎr9íÑ«=ûóü¤iíäYÜã“YÞw1-tÌ?Þ$lÜ4Iý¸8¶t™Ž4¾×’·ƒ×@¥Í#`¹t¥ZJ®&±j‰­ UI6¦ \f‘„Ç•è[ñ³•¨€à­ˆ6¦e³#C˜zÊŽ”àé᳘˺ßP1®7žæxÙßYÛÐÿŒõëËûâdó&½½öO£¬í®ÇBÌ1f¤Òù¢¡ý=:f”¦VWŒ—cå1­ àj+¬ D@Ñ.GGs_ªuAѧ*ƒieÃ|!È®H@AêÚ?¸zGß÷Cne§ðŸÓ´…€*I‚§Íôâ&}¿M$ÝWÜ»óíßþ=À!ýpè~Ÿôä5#- Ç(¢=»ø<—R¦€OÍ9*ÿt#-é(ä¤V•#`(¡NC’$ƒÂÁ~Ò5|MA6…P(ˆ6ŽkŸ›Ø?ÍdßÞƒN¬7!7ÚœÌwÖ{ @‰6oß5#-¤Æ€?ö]Ãüp~SIª.„a9Jó#-àï˜þð˜þ$tÐ "5oç@Íò§íñ'?¨WËù®Þ>q¿Ðåûpèa-|S½;Š!ª‚P"nnÿ ñü ›µ#CŸÁ1£ï©ô1çE¹B˜zh0…†|¤Niï*º8õ÷îý†zf™“íaVp~|/‚èMõÊœ éÊÎ;Ó‡‡çt’´¾£h‰,×zxtÒ"­S˜¿ÎI ì×ï>}¥ª%4„Éý³¡,X‚ETôäAE£9À Ÿü¤ø4# ‚ÿíÿðÑ@ÿQÏ]¿Øì.ÒÝ#-(Ò˜µGÒ8É0W0ú9ùPÚàj¤û'ŒÐ6±+NFhš.8C´êÓ¯þN-›ÕšµœŽ9íÔô€L[lÎàÈaŠ*6çYr lÖÊúÑlˆŠEˆÂ˜¯•©¬ÑSf[ŸC‡>[7¬8!¦#C³P…­_dòLz³ÐÆ~úùaàI]´³>ìÐ’û°kìâb*"#Öû<ÉЦ֘‚jۣ߮ìjÆÛmƒ9>—j’Dd¡û?øgGq´b¢#AF¾ º9{ínÔQwb~Âw$·Ùq"ó&"•ü*45Q #-66'´°bl‘Bý{­þNÉ­w˜å“Q£dÑ£^\(ˆ'lpÛ“HÀi½`âÏ7nr kAZ1XÚ3mû<ç†4R´¡@ûåGŠÄÈ,°JÈ&2šr#C¶ØKQ´âª¦ 2ˆýÐ(a!ÀÉ‘9¨¬‚ÏÄ=„òïªÊ\/»xü±™×'§ÓÙÌí[GÎ|$÷#`ûy‘ª%…Tðù>Ï'äãÏWƒ¹ {”õtî|¹­°"}ò€ýIT”w~Úí#-9H¡Ä;à/%òA>l€4W`ü,€v^ÈâG1(MIJ¤Q()¹¬F¡ˆ ˜Eäžàç,­(òd„S°Ûˆt”ä#-È”‡Á?d*{I¯V”óß¿9ß,HRè4R¾N‹¸@‘J§`] ¡Hê@^H y*ºÀ.Ä(½+¿#-wØÞÁá#-:À)„,)Ô‡Xå(5Ç\Tú=ŸWàŸ·ýYüÁüú#C]è†*ù>Aó}ÏýJ—ØãÚK;ïAº¨Ã‡Hy ÄbÓaÁ!_g¼žÇá—®‡#-ðÃø#`Bã’Ì~Ä°û͉)épÃ"§Û(3¡ø/éó^}YqE÷`+÷lwçVj¼Ð•Sîš>IÁø’dR‡‡aâ|IÁæŸìïÕÆñï;#C]”OÜ9Ž¡äôy†¯óèV¬B?q@Ä~ #Cü¬ö‹“`wðätåyáÄ:®|Ûz5GOÆ©ƒŠÎ¤?9„eŽ1œ¼r³–?uoŒ¦žA˸¢>U?O„…¤\2½±8£ŒR ‚8F#<ŠÇ]tÓþšçÜž¾¾O¹ƒVBvh¾7#`ß=1 äªjìÍDyâEf#- @Dž<\äP]a´o«’³~Óø¼#œàr&í IÀF?Áüœw¶þ`´[·ìx~­>‡Ã·/øf;r´y~þ?¤}øõ÷Û¹qÀ;ý .¶ìf5ªñý!poJÒZ±‹É¹ÒGãý‡‘öCüÍ¿½öÿ¬ðÒIçÌ:t.` ð„giëݳéöæ#`“&ÿ7øÇî#-eâˆQ®ÿ›Á#CÑ7ñËþ\ÁÒÔû½#`©ð0ÀÚ„¡ÂPÑ%#CSùÏÃ]¦=}9Ÿ°ìÿ6÷A6iòzÖn8¹Wâ]1ŒXŠèeb÷WÕýß܇íþ/âýÜÎNÜ{z¯ú_í áúèÿfWüJÿ 1ˆÈÛˆ5#-ú¿Í[C]¾Ò]-#LÇ\ÏüײõW:¼†ŸÅTÕ6oû'3ûJ3 ˜Sçÿ‡7#-ì?‚ž~g“¡E§]<<Ðh† ä@22Žg#-¹$BÏìµT½$³œà›É±G› Þi:õÖKb(ó"\”ªkÖɘ÷Õ¾QH  »^hŸëX0bº9àÿÎP—E÷ƒiŸaÊ ¤Ø‰¯)äH˜îóó9÷€<ЈS#-ïP”{×¼ó.ÌMg^Ã+[úýó¼åOç·ª|hrç3ÄóaœÊQa^}¤ ã<#C S¿Š‡¾HY5;ÍqëD«£¶à*êŸìò¦À#ëâøxj¬jìîþ„]Ðéç÷’Èû1û?¤:òHîÉ#\«CÄ$¹Sr½ÊC”*„RŒ ¼žï‘\èªM'Eúé|™øþÕÏÜ@ ÚtŒy—ÙAýÑŸvlJøÂï&ÿëú4ñiü¼#CDO¹azhˆg¹=ħGì*ÊÏ!×æò©å|þÜq&°#Câqƒ£ús~ÎBxJJgÍìûþÒ¿'¿ûEÛ ÝÐÁ°ÚÈhW,9p#`|@<˜ö©öoZº˜å¿XUÀ2šÎáH£°6ŒŒ Ñ¦/ÖƒƒaûXŸsóV!ÌR˜në/‡>G#`ú#`ÏÎZ)UìÍIþù1=`fKž ÃïÚ¿¸Ÿê0º¯uo~f¨#L”b0¸Â, 2øç‰Ï»ÏXR½a#CßJ~àõ«¢Ž£„|:¸s‡V6ÐwÅ Ôã¸(é’]‰Ýè;g¡Œ´uD}ý3oŸDì7âåhŸÔâî¢ Û¹W.™É×Ú|-cUûÒMæè/X ˆ¿rýÛ{J=`~!xê=…à¾ËG>q„ØöQóo`c– /Î c·à2˜&.¦žãÉ’½¥@ç3%ôÄgã ò4:³')øVOf²S¸?Åéßq5§#CÍ0Ba(ôM%(PƶhÚþ:õÉô‚úÉqL6PÑ û>sC„¾ ç©¸žH¾#ª’®Ý î)69N©£ cAÂ2¨.žNÞ_|D–Œ-’äõ˜†?@tácÅ;(ÐóÎ\ŒG½ÛöIïl/’Oò~”$$í´µzD™§ú¸ñfÚÔ´õuÇø?8rä TßAJ#ãþ‹#CðBî„E zX÷Ï‚\…*#ý'¥Úoh/8VÍp&Û›³ ’“0Çý{™ÜÖª\ƒ&X`L!hqÛý&͘+ÏL7°å‰ã?è<#`ø}ƒt—Ƕ¿²#N#;ÿ’gýžÇÛ^ºœ>1½jrñ³ˆI1)¡¼A«XùÑpÃIm~I5ä#C#àÒXY#1„ö¼Ñ“9Ćcíà½âÜÄA}?é·Ô%ÑºÞ oÙìµfß<ÅÊf‹ #`P¨˜§Mˇ ù³†<Ù/ésú$ßb•ªCæž>â¹vxzÂ:Bº.¯ëßÉšˆ‰&j¿»“gáXÉúoêÈÒÇ dø€êÏò ÿDûô]ãòÝ/ÅýÙ§åÃ'ä÷Ï{/%¼®Ó)º>=k#C'$GµkŒl=­ Zé+^«éÃpŒ¤} uÚ=v–û:€û~<¿Ll@Ħ Iv­ŠžŸ?µ9Ýoù¿ùÿêŸúß$O¿?2IÓpúC»8ÕsO÷¿Ò”ÝŒ8¼^g?Ëvø˜°q·Á¯Ñ†#@8èùº,Küã'~žÓÚó;A!8ú?°4H´ÓDï_±þwÀJ…ÇÀ^±yˆñå4c¶•Z­F&ÖÂcÅsè3ºiõ:ÍIJO"¦aðÿž²ÙTœ\’G=1– :žÛDH0åeÒž‘‹PúhŒqd¸qÒ MVqÆ1e¸]¨–ªJU2“ÈÚÓ8J• ÿ¼Ôé5§—ö—fØd’ƈòÔÓ:U*ª»Ubî¨Ò=¼<çô°;B‡ãè¡ìîáû¼ß8ê~3°ÂÑÖn½]KÇ!3ÑÛÝÿâ.˜Ú3Ô‚º2/8¦àŠ=(PïTR„¡:¨&ƒþïÓ¶L£Ï±ÏÀìnÁųݯóìÛ1#CM N …G£Ä@s#¿«r(ñ¦¿OÃõÏ?p÷b†8ÉhþW…(ÿ0wÌ#`'ìÚE>¡šˆº›¤t³7§BÙrµ”Ô”åüà»]<Î&¸$ƒÿXôo©›ÿû»liOuü*±½I$iA¯åÓü½”¹:ÓÜŸ¼ü£ u…Q»\¡‡Š‹Ÿ)Dò4¨Ù­«[c÷†r‚³Súà %±û6ÿ€q)×Têûs2RˆñÁMFvþ:]oxo”¹u¯ÿ{ŸÞRaSi“Ÿço áÁ©[Ä.ü¢LJ½ýBÁ¢(y+"ø35Õg-6X»4‰,â ¤ *–¸;DŽIÈ#䘿óý_Ãô}À_p¦Ùñ¤F?å=Ô¬3^kÔjƒt”·h×øjI“‰Ú˜ ¿ÎÖ x_#C5n\‹{¾£láUT‰‘ i¶7ß#C”^ë­ H7ÍödÐ,2À—8’€Y IW¼.ç@äÿ'z¯oh¾I¸w<°áýÏ°-Œ˜qŒŒˆòã@Ç#-êá0ÇÙ÷~ÿøöïÈñãêíJëˆah`åôâ½þ {^'.äéù>°óÓÓØ«ës©éG´ÇmåVìJ抙zt”ÛÚ[|mÃ;G±/ò®¡À‘¤$&Ï»¾wcH@N{ÜèÊ=ÓS ÎRRu=;y^G¿ßFž9±ƒ„¥K(¨Õêà5Á@ ÀT‡ˆÉpèeÝ¡RY@>Eì&%ò4ž¾FqëBt:Ôì±Ü“Ñ´:I'vÁ´Bƒo=­x·ùpÍ}‡ýßöÿï÷~PãØõbî„ô~f›o¨7`ÒpEž”;XuþÅ׵ߘo9ÓËß$žÁpŸ#`õëEžÂÕ‘”“2zË*!®“ƒYí Üß`“Sº:²j`rbÓnÁ¨œM#`ŽJC×ÜSüñÚ³Õ~iúmåüTë»PNeÇÈ¿Ù£ø´µøö­§ý`çÌÜyµxzïÙà7ùÏåïi;PÿF«Rbïr=¦ ‰Žç,E±Š#`ò9sçù4Éfó?¯õæŒCðίŒœ:3IÝÇ–*õeÓŽ@Ì5¢¼cZÕl+{†ŸWªN#CÌYN*ÇX\®ºì)²¢#&Éz’I$ÍÔi¬OS.µšÇq·5&±U­U-oY1WÃ&á•ê0r“6SZÀËÐÃŒ ŒÕ.ðÈ÷r¬Éˆ†A´±PJL Ïêe+™À‚xé¨]ꇌQ@!á ì¼=?î·ê§žÅ¢:‘É××ÈßnÓÀæDJFè>ñðX¿4ÉJîÉ)`CßÑÙÑÛëìãÚ;¯„Q’éSòë¸ïâ…UŽpUÅYE·ªýÙ† ë ÿ‡÷¼{þð<Ñóþ%uÇÇÄ8§§ú}»4ÊÛ™u0=žQkfp!.åƒÙCESJi#`ñèGÂþOÕÓß¾]Ö¡Až¤•w·£'Ê’é¢Ë AjŒ,²|`ðèA"èŽÍp¹ÅÜ.[Ъ§Ñh]* 1¬rt»L#`®õešÁ‚H‚!&NbŒÜÃB6³+&ñuë»Ç#C¶ÚÂJ“p~ O¦cmÐâ娩’bàᢢ öêcˆâI1jò(T'\¾7åØë>jo¿;Ä«È€î;†!¥ò’|ꥅ#-n|“s]§¿}W/üÌò, ›è.J?%tû~¿pe†äc¸p…)ì> ëIpzâ;úùT-¥òôô\Ú>ø°EŠgÃÕ!ƒPbˆ )ªR|âW_—ƒzþloÄù#`A!D³©ûL«.ï²Aküa»«4dŒ“ñäÐËŸ3‚Ú¤R»~YvÃCð¹gj¦*Õ]ýåM3§#ñý*hOK8tHS®?wð{š;kËßp‡ÑïøWÔYIóQ_ŒÅ-°@TWôÿ:s8· AnÂof10„K¥ú?_ŽçáCñÍïº&HR#-”}áÌû{i휦üµ>:ý%•”¡l #`ÆÆ6q³Á;¡tþØ…ãšž‘:j!§ ?žUôB@‡ÂqT잎Øþdãò°€úêø»'Ãôü½C‡˜w¸¡ÛÑpþ»ù¾¯ìÿ›'§'”Ÿƒ ž“$¥ø~%#CLªÔ\~/¸å÷>±ü+´˜¹Œ%¯>^ÜúÏÔ‡¿áBÁd) ð€Ä È( ü'Œ‹û"GjOýƒêéøˆÚ[ ¼þyñEŽØ#C+ú6 à9džÏÇoøƒ]“¥EA‰Šð>A}}[ kàóõê¨n‡4˜ža_ØB&RζKï ù±øœþÜÉÑØ$7¤n¾ÿ¤>_í¿ijý›[kôV¯—/8?”U4ò:}ª7¡†£#-W°81ï$+¦ HANºjÇ¥ƒ?Ú'ZÈw1CCuu +ñCêþù׶%É!>g¤²J$‡>B#-ìT!¥—}»ÏAàl}ŸŸh“Xlú’¤íûfoëý!¼:‡%çN½!:ž<Zœ<Cë5SE‚<¿Iª“JJ’$·ò©‰ùcáâñ;4ŽÇÞv‡îIáìölgäí‰R,©¸×G_ç˜ý˜P&žwÍÀ~cs‹H ·‘¥Ïäã9g”?yØ…{üGܤO|ÂÐ$“7@ôüÏ>'.9×=Š©„iœÓPÅùƒŸÄÔ% DV|´/ÆQër©M~’~ªšfxjÀÃ1úÊåˆzœ¬QjŠ0yö`ÂþÓ•¯ˆy¯Øß^ľ§l›°a0¥±•#`údx¨¬ä½tŸ"ÃtêÐÎQ:§Íú>¯ÔGô ÑB'°üÉÞc6†Ô30~°jÑ”åFÿtTUAûPÓð~ðôôH%<^A.bJІüÿoÞ’þ¤2 ´$>ß]OQìùì:Ãç£N‡O9…³ð+¢ý®‘þrþ|#? ¡å25©è–|¹ª/)5ÕÅ.×+Op“ôZ¶.8\ýH þ^¿bü@9ý½Bèú~ž|!x› }vr»O®–´&IÎÞ_PoÈî&³¨‹ ‘9Ìyg'Úf<Úô=‘’ýÑ”&^€ÉïCÏÝ°Úz@öylw°4%óm&kõž#C#`½f?¤ó4C²öOkë;65w\•€É°-zýˆŽ‹‹G$Cöç*Ú{-!­‡SSp>xXQ•TÔC#- ué.Nå‡íUžÎÆ¥B°U4B‘ywºŒ‡O¾ˆˆˆ"šª#³ž¬Ç=ËiŠ‘’©f:.l6ž b+>m“ÔÄ÷=:@Žÿ‡Í›·†ÁúݘÈy̾­Ÿ¿ë-÷Ø(V¯³—˜h%°N¤+3ð;ˆö¯€3ä0SX¢£x0‚7‰ÖçÓ=$j¹T¹`¢Ø–—|#OGRžù}à;$§?AÚ„=æ¡ú¿!¼òžaæ$/ôúü<"óì!ˆTøˆÎÓ§Uõ¦>îô­ç„ê&àa´°Å&p_£ùßÖ}>»ù™=¯Û–z÷}äÍ붡P€UèGyupx zt°#-°-A³‡X‹ëÞÅ¡¨Ñ¨ÓÇ N'°*J-A#Oò`,ýAÿÿk#-îÂ4DÄäŸäœnj.UX[ý­Y¦›Ý¢ Ë©¡«j Î#C¨V#C¦ós»R¤-’ðDãr ‡{ŸÛ4‡ÍÝú>%>ÓÌWçňýf12{O¾Í×Á\nx™óx tM5ÕÁü#`/‚™ÏD•{0lœ£’ o'ïØò¯ùÏí^€Ãhåí$ëùcOʆ/ŸÉÜ`èc¯vvþ/Êv÷ŸlYÜ!ÔF}X!˜~‹F¨‹BBŠ IÚþøv@÷ÃÄý`xI§ìÞI8`˜éSÀĹóz˜ çÜÅcÄ>,Ó?õ¸ô®<Êåx½Å#-Ö#1WÝÖô¯‰†éˆü¤ðIdˆˆ #-•As§<N|óO^ïÃÃ6Ê?-IÁgÜ!q(B ;If‡ÿ3¥ð#_Á%ƒÚ4Jê#-;!¢˜ÜD˜½+ÑNÐ#þ#`pö½Ðþ5ÙÚÛÿõ{X3LIÉUþòµÚ·WÒ\'äÎÙ?ÇfÊ~|ïç˜{FE"Ác¥Iñùýt}×æ>%‰KÝ(d€So&LÈ7ô=°Ç¤“8rê-ÿ&T¶pÓœIWém#`‚ªrPõEÉ8¦jAHu ™y±´©­Cy+×÷&Ðçb4Å`ÂÝ>™`âó}Íq ÈsË`c›ÒÂIh¾KÞŽ²ñŽFŠ¢K"Q‘ºÆ-!èë‹„;¤{Òç­X€¬ÂƒÊJÇ}?«™¢=Wš#-èðûCc‡^8!é„€Kyeæ„náÝûÉ$ž#`¤öb0·U°a:¤#-æõh&=aQK4>nYaöâŒhq©Z‹H\„ð‚ë@Jl!-Áld£ÝÔèæ)ݯUO‡ë!ýàvœâñ9¦ƒöõÃ¥ÔÁ“„é1ø«ƒ½pÄFzëp€yúG­|®¿?R~mO¬ÑWRWä?¦‰Ç?_M‰ —ØYöÄšP¦`ÎÝ¥Aý vEÖÊ2 #`~u4Èßbøè›ÂŽÖû˜õÐ?L… /VG¤”<”*‚•R€ ªFR$–h-1€ ,`>™¿#3óä¨c”oâzO·æû1êJB²þÞ–W³æäcoÅñ¢lCÓÜ€°ÌíÔ;tû#CNÁ×°~)õœ¤"À#Cñ ˾93¡'Ý"Ìd»»¿ªšÒø2vôüÏO5qÐ;]DŸ¸8‡P5G‰Ã´êÑüsçØÎÎ,»õ#ª£ò~ãÔô„Öe*„ùÇ($¹žÔ#CÆl(#URÄ‚f!*´¶°ÙÁã…3!¬yë,ëP”$ã{3!åèWauâ °<Îíñ#`ƒæ°ðÞ³Åíï#CÙ\3IyG#-ˆ“Ô£àiÃÔfÓŒÑþ\#-Åð^ל:ëÑöG^âgp›KÚÁûìÃðͧ±{ý©ÅÞAfÀGW{>#-oÓ¡Ôë¨T{ ìWs¢øw£zmáF %²ãÜ®ÓÞû$@‡6°#`A@Š˜”,á¤;äØ”W»èX‘QUîvjâóá!EŸø~ðG:8U Üð¡Ce‰üÃ#`@û m8FØÛOÓg?ÜC¶„ÿ\½ôˆõÞÏçü½ß•þ}Ù6ƒü”rpã¡ÉÛÌËÇÑäÖâH/)ù‘pS´WVÔMh€ð«úÌ îR²Žiš?°a3Õ³û‘)ø‹B­-9˜Þ™.–¡ÈÛ0a$%¶ “GÛ1`}zpð›²KD`zädÀ!z{2âî¡€ÅÌ8 ‚EJ(4ØOÕ?aÿ_A÷ý±âOÏ#J÷döoˆT§M³9Œ<Ž%%S®{_/ìé#`z¦‚ 4U:T„Ülìx€„¡AÃ4 äÕ= TM‘ö`ÇhPý6 <°E“Ç:"ñ{ÿãš=ÑÓÚ[þ£ˆü8Ï&3½«ù_dÓSŒÝäu…Ø@N@`J…!ƒÍ;Ó#`É–RPbË…Ô*Ã3Uaçe$†ŠÉH®Îç#¿œÙ¶Ð·üÖ`~ÔÁ4v#C/ÊÿY…6¿åKr#Cúþoã¸ùÑÝÊœKæ†u)þÇ)ÈêûÓ˜‰pòKÞç‡mcI'/ì†2H–¡D(¸ký ü8?Ž¢]ôj_ô`6D˜®áyñ| áW½š§xs ‘’çr»ãQ®© ì0ÛlDUÌç>>ÓåœÐùgR—`¨ÅTgN6É‚²À¥)•±ª­­é·VoR}xºôú¾QW›ú~æ’¾òC®l1!I¾ÏFÚ¡Çöø®%$è•lulƒ—-vä°elû2²å)™P4˜ÚXÖØRe4dµ#-(G¬¡8œ=B<þÔƒûD4’‹ñŒÔ©>SmqÝãˆv>óÇö…z}iîÓkü‡ŠAxö'E•tU4B@¡M…øp#C¢Ñ‹üI `SÞz‡oB ‹ ¡ä@Þøpóÿp,>>­ÉÙ?JjNï/a×ÐBUç>ȸËÞ¬iâŸÈù  ª‡ÑÇøt]U!Ù#-nOCÍæüØzþ"IÁìüŸ-:šžäaä½ïP3û¸ô/2c$‘iT‚ú(…³j¸ƒ³#`“#-aÔö»Â¾x•.?,Ó M?dáÔêô")ÌNÍ#-<Ãn÷Ljjš} Á–Ât1yì€ëŸ?ÀÁ>Á×ýë}S0ϤüJý#CT±57ŒMi«ïÿH(ûþ¯iYl£u°…yTÔ½ÜãO KÈ4”í?¿üçâk®´ÓGµ1Æ+0?=ú=>~¨> j¢Ê©#-ÉÉõì‰@¥[½7á¹U%¸ €]&Õ%Ý­ýVóÖùé2Kž8¦Þöÿ+¼Æ—§tµÃ‘ÚI“ò¦þ£ý^N ßqsAÆ©rM§›M±U5² µ‚±EÁc÷/¡”<Ðò&v#-1@4ˆ‘žmØ7H#CF+ÅïåÒðpä‡Gµ¤m&\q¦q퉌c#CkÐK*Ä•ÖbC#ªC6›{D™Ê/‹ªŠ}·|r!õ<»±#CZÚ¬BC•J‰:“tsU¼éÄHxÚq#-Š3cÎ ‚! °dƒ‰'Fb|üÏM‡«ŸR·6P•öÑÒA-qðYê¹ôêû½Ÿój ¿42Uó;=»jy{ô¼öcïš• ý=aJ4v#C!܇ÏÕVtÚokãôÏHXàå0<<6^||ã¿(íU2#- z66ìRÂ7O›ÒA?¦€²ÿE£!÷½€âmýù:í9À.ôƒc¨ïuÛ‹‚†sêø ÁPþŽZë$^Šzn’îO#;ÝWE¢ÄŽ‚©ã#-h3MaFÆ9¢>ï^©ãàxxñ×_½‘_HüBfrÁ`@y-ÁüÏ@øÛtV°Î;¾`6#Cš‡IÓJVÜ"2¯ÕÁé7€"C‰`!؇õGÔjŸ§¡—[Ù}:'ÈP?³íyuGW—,s3ÛÜ´—ÛÞIô/Ë#CŒ9ô,½ãP=¡]>Öè(²(Ë—@ñ©}Úº¼Žgn÷g Dȹ¹­øcž‡ãñŒ?J~Ã<¿lÒKË»ŽŸ±xAOöl»I¨üì#`Øp錣ýË*€ÅƒP#-ГR¤  6¡Äê¿?œÜíÌůn,µ+þÁÐJök»KWÓ•‡öäQŒÎºm¾8V*3#_Å&¤~|Æ”™ÁȶþþŸ7`©‚P3°²ŸÝÛ$b,ò!Æ”ÎOQ¯el_ž*z~gØžÅ~†®TôÔ¨|ÿ]œ¶°X&5¢ÀýšçáõÑþyò19$„>ÝèÎ?ú¹q ªX¹eä!é+ü™ÓM/B‘o¡@á]sˆÐi-Ù^ †lͳk9þùYõ[wÎ3Åžüå§þ®c‘mÖÑVs8 · >ç…±·ù5QÄq~U¶ÝàVø€Ã£®¥¶o ÙµiÜëN“cNy!¦õ{T¸‚ýB½´.í/oçà¤Å䈊h%H~„ÂLr#`ÜC‰¨nK!`GzÕB¼MTýSôûkªv„‡¥¶Ïú„ìQƒ× HqO#`‘H¦™&G¨øi§Ÿûßfú#-‡aé‡Á›#-Lj*úgÚ±‡šp_Rzß_Ö.îÑÄ#€ì9  ºÂ¾å!Eëó´Åï<^†0ßÔÍ¥BŸßö¬:L“éœpãÐÆí êåWÃÕ¤‘ Âf’G©hܘ@Žrh?¾Id7%ú‡2òrÖCש˜C¤Hu'ž0À6ú`qŠmU4!#O€r\È<#-rÆÒá‰2‹ °ž[qñü}?‡ÊÏÃñ~^³P™Ñ”/Ãà~4 ÕQTLÿsŸH*•ä”£À磈qŠC–$kINÀ£»]! [üIò^¡2ݸ_ºKëA€Ò!$Ûƒ’bÙe£ÖŠPÞ‚3„c0FE>æ˜ëNÿ°;ð}¹O ;LïC·ê•‡YÅ%*¢,i»® ¸3ÌräDÔDœÚÌÑxtï;Ë69i#šÛFŠÕë®™ì©Ó+MÉÆBÀ„¥­#dqÖ‹µCAS.´U#C#CÛrï#CHDýøÇ;!À¿‚õá³g;Eié{Òž¾ÿ]8ýèWõ=¬Øó:÷ Ä’OЈ:¢ë#-íñæ†; ôû“ÎfÒT\±íˆj#`"ÛEÁIfM#7‡“¦03åàÇ6©œ°'ìü#-¦O®Ô¿n@?S³÷DD&¾j>àXM€ÕE?þ#-5Y'çãß…’÷ÚöËÌükü!ØŸ ñFÅNØ÷õAF6w€˜Ü1׌Q1Ê*¤0~‘#-ä¢HµyXd×t¯}–¬nPT“µ_·¼S¢JLŠwPÔèGÌ…Né, 5„êFP4éýXò?÷t<ÂUúSðýÓXqîªEqÇdcÿ|€;É ú·R©¤‘øŒ#C0Ü€î¿zUC†K©ÑË x«×ôãÀ˸ò½“Ù#Ù€òJ¾¤€,ÜZ%FLÁÏÜz?çöæÍ«¡ì$6ðð©'õ»U¨ù#ž~Ïn #CÿI:¡~{`¿·ºžBfd6u1€^!ÄÆ#C™þè~Û¯»>ó 0+²ñàg$ÈË$9ó:½T uƒ±BAˆù™fƒ$"ãù˵)ϙʺáå¨ õ¬åe¢ #CÎ?/®+ì 1'/”}béw}€uTj¾X†©HÇ#`µügÖz½çŸÝ ¿ç”?·Ð§SÛã¿Å|[`æ¥(:J$’0ÈÍÛñ¿U&Ÿb¨Ÿ„‚PïNÙéDD0¢_7ñÊÁ‹.?ísÜM£$6¾ŽpR!V‹PÍ¥SN5~Ö_wΡ¦<‘ΖÃøLëþC´†d_­aÉFŸ¤à=nMá-D9ïWùô×æƒ8ÀZš!S¯æÆ~™”YêªÄé¯p¡êüX^h½àÂ*1›Ø~§ªÙ’H(á…¡<mT7×kø1N=Ø¢qQˆ±@0{O°<_ËÎLv‹ŽIÙþq©¯dXU‡Iá'ÈùR¿Êkü½Áèö@ò=³ØLÃöaÇ0÷FÃû@#ú*8·ã‰`>AúŠOª/õ¹QžŸDB€âåÉ|ñþ‡w”#-~©]¡MP¡BòTÓ@RyRp‘NTAõ=’þ·Lÿ*ÕºcÖU:V 0 È:M+XÌY[dŠ‘P×üm_•ÿr3E ä!N¿øF°×ûÊ<äÄX#-ç&:ñ)Xcÿ›±­&l¨Ó@»¤@_¿=ŒŸøxŸ÷ÿêJ(zªTJv @ç’!õÅPl! }§Šµ¥9†ÎO5‹á©î#Cœ¨ÓÏY‚åé®ì+Ì˦ìGö1*ÄpoFàhX„Âßò3-;ŒEþR£ýÚþÙðü=_êïjÛô}@"ñ íF¹Óq¯v#CýÎÄø|Yþdþ}€Yºôq!~”Î ÿ?œ’#ýURí/ÚJ¿äñcX×»ù‡ÌЬOc\Ò«¸Ù?¢Jˆ#-ãNû•¨¯eŸðf^l]û‡ôTûÂâu@}=θ¯Ï0ƱG >¢HBiʨ b ‰küõÉšWm»ó!–k‘µ³S“ oʉËÎì,äºtUòp#-¤R‹zòšñU–¸?_"¼ú^£*ïf†s\Ü£YºõÌ3™Ù¹ˆqLÜ i±ù#-ÕÁþGí E†Õ cl¶¶­—G`)J°¢9° d0P#`jpíƒÂP.‘ß7º\çºæ÷3öÐidÎÂ@;þ±÷— ° ÊI'ûwSS5F-3ñê¿»ôm§V5üÛ6>ÿ;ØÍ¥E}ž>°¤PÚzŽ·§àÉéý\X¢¶?ìI0"Pßr7­ݤóçä‹#C7ærq+ ”D•Aîý#`#-œ“ˆ1¹fhÎé5¢é–¦õ8ôÃ3Å}KFÚýÇ+B „Ÿ=/ëð#CñKÊN0©aeî#-÷2Â$žÂ›É®I»ÃCiª£9à”ØϦ®S@þD3ÍjWÖÑ–!ÚŽiˆ%%‹;ÔhPH3D©ó–)uð<‹¸~¨Ý#¹Ûp:¹Þ6Ú¤4pÀìÒ_<2£Ñ×ÁþWÄí+Èz9N ùïcH†ñEÑøÁÁb;½Æ¤#`Í`à  ï@‹»º8¹üxª0¢ŒJòáåŠ-ÏB#`Dд¢V0D#->!á…À/¸0ä™>’–¾9;©Þ³±„¨&ôËÿÏþÆÏBá*£Žß*¢#-ä(è#`Ñ@>`\@Cù<í·“r2•ßו‚…ùó~Ρ½‘ˆô¢€rÚ#áýiÝoãÅéÌ9ÑçøÕ{‡k1 ‘GbcHø¦h‹¢ÒäLJt-o/ ý#UÉ-Ъí:ù²»µ˜›hö  QF¿‘ì ©v¦×àv¿#C}Æ•RÜJì ëOÕ¢,‚:Pcˆ®ÐúBAÛâlfSŠØ:œ(‘Èï>“ûÚãÄ+#¤ºÿõà àpQàu‡Ò#¡·öDDd l›‡g5›T”>=?§^64nÙluóßµâÀËc#CnTT@äo›1µ6Ø(:[±øYq|)ã©•ž³è.Aü]ôüqeù\Ç„; §¬|UÉÌ k„¬Œ68WÀñGVUîþù%lðù5Ðæe¾É?_íÇÜSñáôr>çFp+ÊÏ6Dú­=‚÷·Òt˜tÔlŒ9t#`*)Cc´h¨öJ ¶–öGéutF#-¢­°Ÿêºî"q?jtN!â\T:À¢à©CX3Ý}üÝœ`;!—èxâû=̶/)yz¨NZI‚<–ÕÏ‘þ+‚ÚŠƒ3!>s#-ÃHGS óß ¨Šü±–—#Cc͵ÕBuBù²\•(ò!½a˜Ð½Û˱FƒF”dª¨ÏÄÃ{š%£;¨±³ªqÏ8ív*žŸàŠfH=íÃç'x>G/Êñ]¼KC+Ë8Œ8UKE÷ˆóôÛj„ÕÜtí/š2­¾H6J1$Ä’Ä/¬bZ(™—®Š}¨êû³®—ù¾Qé›ûµ«/Q=fäÍ9<žO >}ç".&Í •¹Þ9–WÒ>1ÖÙ»I¤#¾8Eö|âôM󉹚âÕšÇ 95ðƒBIß¿kk¯fJTU‰ÞךWlØM+¯nëýþÕÈʸÞjA²¥Y@åÑÀþ Á#-Ž£ÄíyΚËÕ˜)X”#`A#È¥Â,œ´ã©vÖüêÕªZ%c·_h¶áý{`Ó9n| ˜úç§,Þ`a WÖÃÚÛ#-ùÿŽ·L¯Ht+'ÓýíoÉâÆ·ˆ\øíŒg“Ê`©àÉ4¾uŒwñõùîv/…îV#tW·Æ¸èÌKË·¢"Ý©¢ c‘ÝùŒ¶¢ÊR‘"÷ØL*ÄÔÁ)÷π߮âD“%óCŸ¡Þ¾´íà¦bOË«ËŽ 9í[gÉÏUhØxç³q[*¨÷Ïøìx` LFÛxOàÞï¯ñ#òÂ.¼¹ÒR{‘Ä\6õx·ûá¿‘H‡]E7Ygüîõù”ø+óævð…å7gÞº¦Æ:¬J÷Ëõ¬¶ræ]é§Þþõ%¬ÿ—ȱô··ê£òôù{Ç•?uA·W •ñê·“Âð¡$ ³½8VÄ8L3¤cà–½qK•.‹]“Æ5t5;#`M!;ê:†¿‹íØåðë—öáÇNÃeLrÞfìß/aï†1Mª¯o æÉÝã[ëÚç¦jT l9«€¨Pf¨2…¼¢Võpÿ¹²--ÑÄyïÚøD±ŠIì(eágÜ3iœãÚ–™{µ#C]ßÌø˜ûy{¿•¿ ¬{ÕT€H<Þ9üÎgËÑ!2G*¥¡Ö÷r£ZÁTQS=š„¼Ò:f%9žÿ‘é@„EBC×ë½Sê<Ï€OÕƒK;(°g¨÷ˆ#` r¼,²¢„ÑWÿ‰F] o>¯fÿìþÕ°•Èx;^×ÿzÞŽÁ„"‚ÿ×ÁwýhTOÛR B„1ýŸ‹ÑøyòÓ!JŽPt #-û˜*Xe@#-ú@÷Hõ¨äSüا@àbQ OžŸf×'õûÀí³ó'Z#‚ ûJ¡Í?:1NŒ|·|ñ•ÛQþð¿P°Ò?Ô` s@m³qÈ™$8;ÈèäÀlx?Ó'oÀÝÎFܹÐXf#`gþóYØЄ˜d‚1ÁÅ‚H’'/ô½SüZ/`÷Òå]™ Ï#fYÊq+µìïï¹àñ0† Á‘ØؽwN³ºI?ì²Ç Öyð¸¸}L!Òx­sÜvüX©üc5’j—CÎzHHÇ=/Ñ÷#aè0‹„yBÿiê°>Òôöð½ü#Cè#-å´öŒ½}總LW»­_j©Ñ÷ý-U#C¶­gù½w½¸~)@û4[2ý³sÒtÿI°ì3^wjÝ]ÒÕ WN¡ÙÁ‡£7m™ïëâZ[Z7A8#-÷ÓxƒÝœÓ„1Ahä¿vtz½C8þ‡Ü~?ê±þP£`£BýøAçðÕ1a#Œ#C9c…÷ÐiyôOò£·Yî*#`¯XzÃ!þ³Dž­âg>!/5&ªßb„‡T’èø‘ÙÝ>ˆïk#CnÂvÙ¬ð00YøöÉ&KÛ>v ]Øir¾Ÿ3°ŒUQZC,x×—=Îòe€BÑp– p©‚“L0d¹&š#CXiÃ4U€é`bU@ØÃc4Ãa¸#`¹>ÓgR pÎøâæ1¥ŠŸ d¹“0[èÛî#¢ª¢·ÇH›WY”a¹‡=Òm²YÁöõœÇQ$žŽ‡8°ý<½#Cˆ„^<è±ú¾ÊÉ–Œ˜Å^”VÈxv¯`¥#-Å.ÊY#C踿Î|IÌ9€ö*³e ìs^ÁÁœC*…!øþŸD~¿O…Ñü”Ø8¯ãɃ§âŤ¯*ÿ7ë$l7slÛhè•þ9ʹ¶­o~#-Ä< ßÈT1 ì#`ç¨~’àœ;`Éé÷|îÅjçNˆšwyÁx‰±bJ`ƒû_Ô :AþèÏH ´rNQ#-¿ã¹,DE,?³ýëÈD,&¦†ŸóÏ£-q„lDRA˜S‡ó¦‘hŒ>¿?¿ûÑø ÑgØ|Q‡½TU .í»\Ïé\’gœç#€Ü}j¨Šª«z~áæIdD‰c€pÉ£«s›X4#`Ù»Žø¡#`#C¿ØÐÖÞà³þ/¬î»¸ÙNeTEXˆ;èî§ïš÷˜ðÀ3ß*Ä#C¤ 6,ÜŸœNA™¡Á°m±óÃ$4+&4!ŸçÍ–##CAÄÜyY#-¥B! ðO¶­Šf@ü%I^“ Òü#`ÄÔéT#`uÿDÇþŸÃá|mðvI#`t`j'ÄII$~"Xx«äk$£‘¸ ¡iù*#CžxC^¡pÉÕà#C̱QIá×Ñåxœƒ#AYÍèŒîçßš&ǘnk4lEÕ¬ÂC«82Ð.‹DbYüÅWL›Ë²¡Ä‰ù† BI@ƒ®œô…cí>f2Ú9Àvà#`AÏ˳`šØÍ#CoR˜äŠ7ÀÜâÛmÀ\ûð'îC[à.&‚¯h0ý¥‡–«Aé2»Ä±ç‚zy©#Ñ7qyåº$ž¼ÇOã 1è6m@4ý÷gÌ…WŒ Ò¾#v„œÍnE E7âC†;–ÃplÇc á­½hqÂO`Ø)Ó6¾P¾ÇŽÌ˜ÐØÝ÷¹™=»é«90Õ…bÑÉ‘f•*"šøÃÆ\³¦ìÞ¤ÃìtÂ~™ƒÐãË0{LËËÔ¨y÷‰Œ£­Ûeô³3ÒÛšÄW¦Ù.ë|¿-ô½9ål³º ÃÉ­ä§gÒñÓµ·¶L0o F¶Ü- ÔÂÉ!¶tVa7JK@ºtI&#CÉÖkü±õaŠ,6,«Uí~ÎÝx—r½æ‘DÛ¾µÃWpwÛàöã!ÕûC°.K½†3Òω‘¦ÈÍ4å«Lg›Í–ÀÝÊ#&á³YA#¦ùÌÞ̓ˆ›ï3)ïd-f2Î’m?þ³ BîºúWìû8 ·«½ñ4 Ö™ºk—— ÷øì*™›Â0ޮǯ•6­Ýëâ«Z§Ó­¸Ç/xd¡Ý‡fCù£–†>tñ 3w–CõéQ4ô|78ÎnU°`Ë°ÁµP©Ýd#`3#ƒ\/½J‹ê#-×T&ïQ{!#CfO+íBÙÝ÷’Šø\¤M"è!fXe;²#¦•®ü*˜)Bƒ!#Cƒª#C@°`r†ÑzôžI:±?‹ã*B'cÉáǯ9IFDµï LåBxܯ…¢“eÅ”œm×QŠjÛyF¶D{+N#Ìê(ºp6ýâÙbc´ÞÇ7 #`þmŠ&°âdõù§¢€âáÐâøHèªzŽ`t‹mB§¼CsBdxþNÙ—.B=#C#`M±60`uè(Œá±†Rçªa9gvòÕ±ËÔËœ ùcVèHwfìÞžl6P#`–²%»M&¾/Fœê95æŒpó–ôqû#5@ Réç7›$çŽq†C_¡ÑàéV·u‹R˹Ž#-Ñgˆqò}ýã$[¾ÞÌKt::b'È\”)Z)*’ Ó$ë©Évwhy'Vj”™*к¨¬@¡“¸à1)sd;öeà^nå1{b k4Ò$a Ôǖ§W©²r;5h³3rÀÀ‘¦&Š‚”Š‰Š­Xw*†Å8›°çÀ®Û³oi`å¸sp@&Ø+Óßš™3̨¶Ü–æe—1åËh±fe¹†e &'¦9t~©o”¸H™ŒL1„vƒ!ƒ‡×ÓÝ‚NõY¤¤H?&Ô,—•ÆfîÕ«çzæ²ÝÖ6œÉÁSPô ˜Œú¯K~̸!ðy½õ¦¡EÔï9k#`Ò²×Pnî;^MðÆv¢§j¬:Q}¸Î˜”kUEyO>îx¡F“ ºîÚ³jâ-JçÎeá‹A*;Ô}I~SÍÃdo§ÅPnó ÎˆU–©`T#Cꈹ~çÐìx´•hH„9ð|g˜fq6ÃõSú®RX3^š=i·ÍÜj=°s½øÞ«]ø§z(" ¿ w˜(\M6#C¶,"id…Çmª¤1zq©‡C§óÒ‚õÙ#`äÄÕœ9Z‘ªÇ—!tðFôk1¼+ÚG*µÃºz<-«5Íe§73!rÔ¨ ¶àõ,.§˜öŽ©¢”AknåÒ çþTÄ{vz%yÕ§©#³ÙæôE{uvÒ#-tÉ%ûû}lùlçïØ»&#`,èBkÝ9$˜<‚ç&#-÷•I–,æT,LœôÁy.èbcòêá¿·{3žp,·ßJ™ê[™ÑGÞ9ÏlL¡ˆé#-E•”–{ÖCzøtwn¸UÒvc„-«¸L-Ø’>MY‰÷K¾Ät;‰Tæ뻆ñˆ†¨-¡Ó-¥º¾üI€”¶Ø;ºØ¶B!Ü7‡‡‹ç½m*úHhä&Ó#C{¨Š´½‹l¬à ì#`ÁÔŒ¾‰4 b 9zí0YlsóÈ°=C€ÓGθiüJ™¬Ü°Bi8`(²uœˆ¥ŽL™œêrYó$ùF·ÏŶr.  Š€FpB΄²»véÕÝ‚ ’#CNÝy¾MšP„:cïþÓ‡>¾ÏV#`ÛQ¤'MfpxÚŸDCñéx·‚yìzAZÂõ~FLÆììÍ37F=£51NôÃãàèx÷ãLõ#`1†ÎƒR{ÄnÉ’2z$âKÔ¢²ô#C2¦òPk§¶í&C2¥Ýü£n7#pDzZ#C{ÙËì§~Z0mHëQä}üõ4@ÝcÞ2Å÷ÿmŒ[Ñú‡ÙäÃ>ñX šŒ.c̉ÞísÜøq#-ëK¥—é+È#CéûÍˬdÎ.®ìZdî/\=ÍêÐ{BOÛ–BòÕ䛧O ¨ûO‘»·Ê§ßµáƒEënj9p¡Ÿ:D Ðêƒ ý¢’‰¤jw°ôÞ^¬5’ƒ#C‰Ë7ÙŒ‹$}̘Ѥ5­l{†¢œ7ÄrGÌXÁБqà¨iY¦ˆ41¢ RdoC΋k8reD„8ôuFŠ†òB'Õk jîNº­ÚЈËíióu4Œ!‰¡¿qÓ¿‡ƒN»äà³Í¡7+‘A©ÜI!Á~0nWPŒÔÁ C#-‰Ô9=çËT+Ày›ÉÅn«à¨ŸìÏó× åíîzÇ àþI&ó8̓oSÀÀ{{áÊó÷ÐÉÁ“‚Ë"©ÎOIç*Ix2’#<ÀècÙ»…7fÆx#-`qÓÆrˆ8ª0õø÷ëU÷Ñ,؉©õGJañº2ç:Œn_£Hh­Vïºt2zô¢†Œ•Ÿ.¨ÊMåœL¼`ïwBKò7ÆõѶơњâmµ³Tl’l4¤Ï–×»b0—y®}ú7œc.4ÌB©ô°×’²pb‚IŒá6êø@«ÁÄøê07ˆV\®ÜævE)Ä¥—–?´l‡c=l?D;ÃÇÀy7ߟÊsyKÁïNwr”éöü2D¿f4µˆÉìóÛ˜ì´dJ쟴—ô_>ó*/ÖɇB¼âP>K BF¯åŒsxDÄ–’Wlk×éo¤±5çnõ1½ž^Oþ›o‹¼ÎR=`ìÍ}^“IƒüÒ Q=¿w«ý7Á¿ñsùäï\F®ÃwH—jȯR—ÿ«•´Ã²ž2òúW-Fï7 |a¤ž6m—3yä¾Ù-¡õé8khÄNQãa¿“T:ý·"LkÓ²»V`ׯ¡á‰>,Ÿ­Š°‚Q?¡ ¤ ñá%#C#BÄÐ#CúATÌ’€ÑR0¼»ž~.ק¶R‡YûÆ`ÒÍòÝ£{ü1wu‹Îšªô·¤×#`̉Øìgሡ‹¢`DbUÒX©6HdʧÖó˜#`÷ië»ÒÝBõ’eCólòM´ÁcLÚy\¸à‹Â„¯Ñ'ë!3ߺvëª){$¶æÉ %wþEìí£Xü`î6ddMàz¥üªÿÏáˆL8æYë.ü<ó16ŸÍýÿw_Lÿ²Êу;oðê«b OPêô9#`f#È’ =ê—ÂÏÝ–¡öµ³ð‘ä‚·¯†#CXƒ.Ã4z ¶1ÆäKîÿPMȃÂÂWËÜxt/Ö§ vöô÷p;U=í½†–BØ…À}½´-ôë7k¿¿ºÑ#$ÈžXîü<7¾çƒëõ}Ù«fds12ÿƒçÄ×Tq-ŠÏÄt0VH@ ó TÆ8ðcQëðÆÛj“Ø›câ¤Íë…Œ)!r-°ÌÃG‡ow@ì6Ý6Ä!Âe^I1Hð‹˜Ü°ÑÈ7 5¤Ì”ôIC€ÄúÔÑrVZÓÌØþkÇû=±Ê›­¨­²hôÇ‹üÒÅärA9ÔtÐ#-#CÃð#C% ÈðBh"Õ#-=½Ä«“Þó/¤Yó3ƒ «øÿ«G'‚d? ‘—+¸yA.0cx8]@åõ#-½÷âÈIñ8‘B#)ï ¦¡TE«UP~ ¿fà÷Ìœ–5dìpñø÷3²X¢sLé»[U°W*ñôÔ÷úèÈÁÆk’ Ži¢e·Nd†¢‰ ±ÓÇ»ÏDOný:u×®‡Îª÷ÈÑ$ª=»qê:2÷r@=ðªÐˆB°BPº£ÛèÏ_‘Åó `!D)A5…#-4 A‡ÊQ M J“ ¬J¿G»ì}y¥ÓŠ‚šýø*ä£üÐq¢¨¢ #`‰ª`‰Š"$5Q3¤hˆ‚$²†‘¤„PÌ Ð#-åŠQx@ð‘• WÈÀݱ?æ?P‹ŽÊ¯³¸<õÂgµ¡‡Õ˜OŸ,Õ€ÖÞÏ œTF7ƒ D*û:wHÅuF”ÂnǦ(ßxAWcɈ<ñëá#C¦ÓM kµ~©l<‹GÌ „#qØÁK¶~I¹ž'iù)õÍão5L6M2^©¦`‚ šBd á0C·#C¾Ð¹È(„ˆDN‡À]N"yniFÐd5@´?Å ø—:EÅ HòŸÝ椥)#`Zy²4ƒI0P4‚iLÑ’Hšb€$t~Ü#-šä%(fRŽÀp!JQ¢‘ñ0ç…€¯I@Õ&ÿ6!ì0 bq„í”N¼µGNo’j*J¨†¥;´C}±AÀõöw‚'#`BÑÓŠ;”ûðÑÄxî *žî£Àü|W¦ý€”sõ‘EUÝþSÞHy™¶& ¦¿ÄýZ©÷x-ˆ|þ]ò&y$EÙ­õp[8Û{¼£49 ßÎód#`mO>ÛÛ4,òTI¬§É!ÔïN8*±½È~vˆÇ8æÌèîNm¤Îclºì1±–àA‹m.‘. "CUíâÇÁœf Àr#Cë²Á.Yµ°AÁ­¦D4$º 9·N5¬Ë˜[93hS5ºc"ØæqŒG&÷ HÆZI­R èhEhp#Cˆç{ƆC ·Zœa£ØÒ³V#CL†=áӢNJ=aÓì¼û×Q}‚ý°3"D‚D”¢ƒSD+ #-½€w*b#-J' #-|µúö€uðZïNi„£g4ÄðOðG–«VeÈÑ „;àd%ɧ¬‚k«‹M*ꬢgKÈÀ¼å.Jš6ëaªã*ÃÞŒX—ß=rÔ¸¡a |÷Ý˺{sƒ#`q·.¡ªD@j4´‡§›'A0Ù†÷•Bðñ¡ê%Œ` Ù%æƪ »Ï9u7#TÁ¹›™CöŽ|÷æšHd!}Þ8ÏŽr#C¡9Îfd‚RS «Ú+Êô/g_Ž÷–­&%PØ3åÙߟ -ˆFˆ{zy¢~çÎÈÔOAˆ($"RHª#-ù††–‚ˆ)ÒéMh&B €¨‚…™ª "#-)ªZØÓ%#CÏcûQ]ª5öNíÁC˺€¥ºÉ“S)@#C$¬MUHRM¢Ä(›¨ÀPš/¡]xò´éè>ÊõvúÁóýNµ‘,:q?fàpOןÈýù¥”SsùøÁJ]øÏš9’2ÃH]ø·ýï à/¼Öp7lS$Ï U1GàC@wDB„LÔPþc˜”ª4É›¬¾èƒíZIŒ]:El /£íßæôþ¸¢ˆ‰Ì§ØÀ i*˜¾ÏžsWÑTŒÞ]5ú^KClÓ ÔKÐuüZ8mü{ÚÔÊ©È*X.g˜Ú3·ÄI·®L*y2×ÃÇ2ñŠ,Ä£|^0#`1Þè°VlÌ¿>ºC%ŒDì妈z„©˜”IhœŠ4r~sΙýÆÓª„hŒ ËÙ#?¶²æÑÃ?Ñ#`ªúÈ#-8`½|þü ¼~;1Ú3ùª£AÚdØ«2\ˆ¿‰—ZÛ=ØšNK—®WRûÿ—À=~9ìŒèxX=xçÇ3ú0@;¦$äîb T¸ÂwJ’#`{ꧮüùø7aø÷£Çr|ö[CÚ˜„^=²Y|ïv`"!ÕU ÐwõnW¿¯ÅXh'ø'Ò=¹m<ú•ýŽáù™#C}#`FֳΩåPg€¦cT4—øy®4»´pºbê=±Qœ ¥Ms`ਅ>Ip‘ý‰B ÄÕ;<^&åì_ìºù½çµŽäø¨~#§Ù‡Ì°þ!ÆÐzdÞQR!_áƒM0^(!í<Í¿ƒM_|ºÊÐù&©×ƒÇm û@Æ‚x¼¯”üɈ^ŽîÙ- R¸nØ®ï€{uÑò`aA?Ï+3🎧}äï³ý™˜å¤ÐO3Ò¬^Èo½w§GËܯ²lJØ*ª(Œ8…s& ŸN #ClÇÔó†¢÷Ÿe:yÌ•Aƒ±Ó—»ðfÂ:Æ>ê;²öøgÑ®Ö#`mÀzo†¸“°8«Ôë–÷L²ì`ÐÈûŽ_@ª–#-³Ô>a1Áí_iÇš#C÷Ø;ÇT!¹âSø9炸z“#CRÉW’A¡'á­±æ šJ)JŠÒÜÁ’ŠÖ:O¶élÂùQQÙ4ΘÎQP­}CH$vª¥Æ•i€ÆÏçw«ˆ-ëišš{°÷ž#C¹SQ§ o¬yþgt”eŽ)cÿ4)Ëô§7ˆ©\œ]CEÏ®ƒ†ÑbÑ‚Lñ*Œ Q5Ñ„|âD*Óf-AÙì.@ŠD­`¤ì$ŽE?²ˆ(¢"Aú¡G ¡&b  (JV•Š$RQ=w¾OÄ%PñCœâ{“ÁÁçH‚ìÇRT>¡ù“ãÉp#CŸTÑÖ`æ#`à§1|Ð~ì‘"‡y¸÷Ûªãåý¸GšÁãþ_NÍ3[Ý–Ò ##uýw¬E1£¾²}ÑÔòð#`jŠˆbb¤¥ˆZüÙÃ`¢ /ÃãýE·#C±P\Ôᦀ‚ý+óòINǃɟCßÁxµë­²(nÛ–GÁNì{˜V@®Q©#-oE%H{d&bBJH|é“JùY^=ù/lP-(Àå_.*2:ü~Ê™‚iÊõ,¡Eƒ>¼â<$è𘠽H]‰PÙDEH½FL#`=Qí ÕÖ|À>€^š-'H(\–ƒUeÏ(òGB‘#`•ÓDÔsb¢(ûdäJ€ëœÈ£9 ˆB”²†<¤_уÉé¨uù0ÃSn1øœþ1c*íù©7¨“çU¾²ºUý÷4àÀˆ9ÖÒrú‹à‚AF‘0Ý™TtàÛ/-ô²0áÈ„„z׳Œø¶ ËfÎêŒ#C;WÎQ|>‘;¤8aÜôéÒ˜N—­ŽrX›\¦–ûaÛÊéâÕ2Ò>Ö2š}ÍßoÖÃјגA‰)Á/dö?ÊcëÙÞðãÃh<ª…Ø*jCoÕ‡óëa¶K#-ùœÇ“–¯„="â-+m"Ù4¤A³›íp£åö}9oo,ßѶvÕÈh«z2UA€™#C)A–¡°çˆÓ^”³óèU‡¾ ™ü.7ƳÒ|öóç›ÌÎ'žuYåȻ¥ÜL Š­¿›ã…ÝÆOºîs'„ % ¤f¢Ï¾)†>V[çéÅ÷½®¬äl¢Ö•"YžûbWîÑØ“Ÿ9á­‡)Íã™úãX7@‘ XsË­ˆ#C&äû³rlŒÅ‹q¹Ë"9ïוNóÏʲɲ˜ÇOmžÒÙ7¾Ùè¾—&ùÚCM«‡h ê2ÈêXpKÖ¾7»q®Ù¦_ VEFó/$Ù‹ª1Nî±þ Ñf®ÛkT#^Z?O#CÊâ”üÙTmˆ» ‚A$¦¸îRçø#-tðі¶Î»ÿHÓ«É~ç«[ã¾ú¬¬ÉÀÚÊΉf8…àlbÏsæN¤z¹ž&×ør™~]£BûÙH`î;°íÓTz›ÏÝLìt‚·øŸîçÏÛ­dxûÛKžïä(…Øô؈±·Nqs"|ƒëúœƒëõF;ŸƒÞJÑéŒ 5=«Ñ‘‡F°ä[àŠ 7ãAÙòô-ñÌïÛÿƒpݲFŪ¡ƒ¹œŠ¿hQÅûÉ”Pæ9–•‡3QAPTŽ3ö‡Çû»Mg`‘®º‡=]«ce\züW¨ùÔ[y÷ðè:KÃôõë¯_›8ù]?g´µ¿rÑôf”‘";t;¿/^MᙶËG}áPnKt³A0pnNÓ%c3I]»«I´8Ò@8˜†æ$­R¢2Ê”šÃDZ.MÍ̉7(éȸ\ë5ãŸbâùJˆv”7áæˆ1;\§vrÌPoçÇèðê%vðã6‹Ož l×’R#Bê$:L®ºŽ_¥»™ ‡ùª”.SdÁT¢'ÏWsåô^ó¸=H 6{5㶨}í© ‘©ìÃY7Áôx,IRªNÖ)ãdUó*ƒ¯w¾áª+®b§H`¢ô¢Æwfƨ¹!Ù‚0U‡fj'`'SöÀ8½òD>èä<—±IT)I#`”¥Æ#C:xhl‹ Ìü#oêëÓq}[û’òºuÁ<8::?&]ƒîpôIé’€=Xbö>å9­¶ÕSAL„HÉ;çÇ&bÅzýIõö;Ãëá-3±Ð_³SÂI½}Ý iÚXÎY¾rD«PÑækÍ2¿Z¿«4Åsb‰š4oÏ ˆs#Cw5çJŠf«–ÓëÆzIºdêfLæäuæ1ƒæëüûý^ŽñY¤‚)#`#`isë‡Çª”¿ ±rò“Õ®éׂ!ë&/s£òõQ<#-ÎÔÏK!M=ø£ jÕiy¹·x›™ XæU¶ƒÅmˆ¹ƒrij)qœ$3èÍQéƒ@rÑ]ó—:’ŽÜ#2·•ƒ½ A–"lj 4£Q4ÐC•2Ä)ˆÒÁ[ dÕ! RaK>mr^lgÛ™áÇÌšƒBötòHÑŠqD*Ð-µ!¨±R J",*Q/rðybàh#C%ÂP\bB­á˜tÃßÚÑÆ؃f&뼂©0 2¤ˆÜ8qøŠƒÛåÓ±U‘Û8™-4äî#CëëB”´@Ð!ÐÈHÐLÅB)ÈVW!FÉGW®ÌrêûŸ=aNÕþ!‹D6™EºžÂÎÉ‹ªüßÖ–Øp¹óÌzÏoSÊ+ïó‰'6ÌñóËA8á®á  Æ(dFœÂÌZ‹T4¤J¶Æ1ViUŽµ\šy­IJ5duŽ7œÇ™»Ã\¹rK®œPÉU G`Ä5]Áž†É8ÈÁÈ6;´‘÷`ÀPÊd"¢>ü>Ôôy@žÔBˆã(÷_¸ÜÑ¢Öû^Iö潨R%ìE:À?¶U˜#-v;¡JšñóYéÒ16£+IÇ’æ=»£Qõ#Ù|0ÀÊ6²•0÷Xt[)Ž¬¬•TrÔU^‚þ›Ó“S^¶šiE %kèüW…UåÒŽ¼º²Šâ€û¿PÈ Ðäq¹7߬ôŠ‡’€:»;@žm@ÏU׉¯=À¢’#`0ûØ`ùWWpöBU¾©ØúñëãÓÇ`øó„ù#-ÏXmÉÐEõ¡~-]yn€uõóž2dôÚª1ÀÁ ¡Ð5£µ( ŒAÂ91±;$ÉÍÜ3¢C‘6^&¦K‚)Ä!£$Y—Ž#-¨®HPD`Q¯•œÓ\¸[xËnÕó[I6€îb_{úÎ{‹”DƒböoMÕ–9|³ ßž¤FSJ žÂhOPhJ :c$ß7œÝ«’›d¤$9çQ%F(Á«N!ƒ™#C»ÃGC“Ò9ìö=#›Ö®úTÊ>»êRpŒÅ3(t8h¿bŠ¶Â£w±çuš×]TÆ?ÅL¬>†> ãY©øÛ™Z6É2£Q3Œ>åDÓÉÛK=¢Éñˆ=šÏ‡dR¥Ý ØüØ4!•ƒL?’Až ’;m…+ûn7ßyåÞõ¾oâ}(R©J¤¥‰#-X¤AF"@“Ë6u é1ªˆPp>‡”‘Ãð¿Ò–Ì2µ¢u¸TTE*徎ÕÏÍÌ¿@øyryŒÁ&ŒÔR4™ÃB¨ñ¦šuCÔ´ïÂëuµ™kƒA!ºXíëC E“3÷&Ú…tŠíŠGâ÷úG¤ž©÷ó&òXBb³ØÏ6ƒè.<ßóÇ0Î.Aáçñ4€÷ „j/Yhn›$Ù>UзK¨M,Xˆ ‘dw¢„H¡æÄCë^VWÓ;Ó 7 §:¢í§÷T—/‘ƒCà~Ÿd±Ù¬H)#-9p#C5&ÐvveI4{àÂ?)ãÄGH2BŸBÀ×VwßNm)yw»F#CnÃgl>÷„©"—Ö׫>yÎœ(9pNp`uCùÀÔ·!˜V–yb€´"O¦QF* l~qây"‡×)ɦâ;ªØêâ“[Sb¢lÁ?Q†Œ[}¨!›UVcÚìIÌßKã5I9¥ŸŸ.b<š‰q‡gdB#`RI^ק¡ÙDª8„c(Ù΢ՊÒ'”(ÛM”Ù8×€{¸ lÓ#Zˆ8šÄ#÷~ˆj1ÈÉŽ‰€›†¡„$úì` yn mЩŒCÀY.Fñ${Uh”A²5Œ%tGPÁ‡pàÄŒ˜&/Ç&œ¨Êw2rˆçÐ_·qwhˆÈØi v^eB†iaÿ.ütã2<¶• œ0,œ+æäÒ%Ì  ¸0öùóKøï! 4#-õÖ $ñªèxT²#-¥ƒ‘˜Z©B ‹éÐŒYyšÖnä-AB‚•Ej…ÄÅUP\¡ÀÏcf0öLßìU·Á+Û4ã#`øqGÍC,‘¸ìm¹ët¸Pi‹Sï9Ÿ2zn ‹¿f9á„0䫘D‚*‚ ¶”Š˜%š¯‘ƒJ‡apçxa3¿;ç/«ßµ»('NTN“o£As›T@ï@ü'æÒÂx#-³ô$®ç¨öµ»*÷S¯‘øñ4Ñk`h ÈjY¤Ö¦‘èò>ÿ§‘ðs×ÞI¹RäÆæ‡ïÀ:lÈìs¿ÏÖËëöh ›BRkFIry˜ ‰ù{¬°Û¨:¾:™„ŒB=˜Np(`’¢™Qª¥ï„íƒê%#`:{1#C`n‰íHN²†ŠBÈ'`‚ ByŒ‰5D:œx~Ìý9é5&Ó><xÀ]TI«f’k¿LtñæñÇ×XHáÀûÆ‹©IM3?¤(c>®ô¨c·\1QÐ8#C°gà-#CéÉYk{E8ø½··D(‡úÕ#CC¶µY¯ÖqÁ”ï8<”¦9€~é9#-‡»%$„ ÑE#C ¢¨£Jß)•ˆ‚"Z#-™Xæ{Æ™“sm˜‘ ?Ÿdò3£ESJÓ=ÈÙ0v›‚ëbI" §®ƒ)âŒD²@£"#-æ1"(ˆO#C*ÝÑYBQíõès˜ùZ‚€ªOYrDDP4D!4DÒ)qT”¬#`ÛmHPàoŸ…3†¢85º$#eCîïp§d)s .¤È]EF îG…W·Ñ8¾ýKÜ#CNE9ˆJ/’âgªãŽ!ixBÑŒ˜ ÄÈ뮈f"N¨B°Î<#Õ‚ùÀö$Œãjßë^U$—FqEŒ#ÝRîŠÎCDgRà¦è: "d Ö†Íúô4†ÆàI˜v b"ûLdúõ#- zLÀ9èÍêVv2 ÅPÖ'„(IS;@€vp¤ìæ›:›Ò‹æøòuŠ/›atHi:Hë˜ú##Ó­³Ïå©™8gC,?ôl`i±è)Ä2B¶—”=èi˜¾ž[½—–ÁÄ~¦#Cs´*'a èbˆÃKýŽ×#-€vú>€Šú|ÑĨ{ÅB†´¡b&´kb:Z~Ý5tL†h«Òílå½Ï‹•bj`P¬J™¸q¨Òƒ Ë# ÚáÊà<)Za„üÏX=³Y2õã,ºÅ›mvm“ý˜¼57dØ ±,ˆÚ (wU^Îð[óï´š(fMö´¢¬V¶†l™lÂP‚™EvƒZîÏ"f¯I€Ï1Êý_.<“Œ\¯.WìÛÜmu[ÑFk•½µY«0¢JÅ?q1&>ç#C#`[˜Q’F[0ÊÎZ¹-)‹>lOb/M&ó«)5}ˆ“‘÷³2!3T›«ü›Œ"ŽDWM#PŒO˜a$duQÛÎÊt¤D$i‘ÆŽó©H46·&Vh˜aVVäÐã0i7ãQií†Ú]iÔA¬˜niÍAšR#C[šÑª#CHø\<ƒ‘ïc’:™#`¨0¹14ã‚«Ìb=–”{¤](kÇTVmÉ$peËoWG‰ç¯.ó`ôhé:f®ºÃåæœÑ,‰§ù®cP2A%;³Ž\ÞèÀchÁÈ)àÈd ¸i7¡¡¡æÏÃRV5óÜŒ¶2 þßênSñ ÄNDßnSÞ³ó”åäãòû8z€ÐžCDh>ä¡Î:Y.} £ZvŒõÁD~œ%<%Û¼#CSðŠ=ÂóO6±­®ù,ŒàªDÕljŠõ5×5É5nÌÜ•ñRͪPÔú’ÇV„vE†šb²³YETsªQ”…a*m‡‹·¬èõ{½5sÙï˜(ªa!hfŽN–"™¢€‰×>NRW„ä#CSDXéšÛðN>Z{5Fua¬QHâΓ£A›‘i؆£i³#̦µÕ¾¸dÔ:Ò>èz´F^­qQŒlm ¤‹¯JayZI4”rRðÝëe#®´Žk"Mj™‡FqauFÊì Æú[™lˆ}69ºa‰¤‘B4£àh–À5%`ÚÄRz×6Ó©‚¼†¨ìw½Ž1Èõ EæË䜬b¡4%4˜Ÿ[$Iêp{Ú#Ù\{Ü7Å“‹#`b7#CÄkÝh0ÈFæTGÔ\È·½¬Che›E³×*&C†™"=tþ[Ó}#`+Ë ^•‡]47Tê«ÕÚúo+Ǯь>»J—I¢§fßжzu‡H/Ρ¹Ë½Ì: kkc0¸~;<î!¹É=A;š‚c^k•.áPðù‹Ìž3Æg,,mf92*]`Kà ™Æj°‰ß©R¥>ü%1 Q2¶ŠtB…è!Ø@œt @ ¶ØËh¨`ʱ†J=í³•5ƒ ì^rù\ªp#ŒŒ³%X‡DÂ& ¹clÉÌÑž¿6 ìêÝÔ%€#CY8#CRns/H&£8dÕ#CKÚÃ@€è̤bR•£a¹d³Ä´(ƒ”k%*i'ÞxÐÒåèÓÞÞ)Ø¡Xöûã‘Ó\irÇ›¤ip¼pj0èd­'´7`øõK— =iHG¦¥´›°8ºY‘ d `¡{¥—¨®H-̯1Ú¤K±‡_󪺓qe¡øÞe"å ËÄ!ЙëJJTõnÓÆÛ­³‹Û^jɾõ#ˆe‚—¬„a=|ÝÈ~D&윢­¹ã©£§KµÝ&ºBczᶟ邅–T,a.÷Ü^½ÜH¼&°0á¤`@o–2´Ø‚ .g`¢!Ž#CEd1Ùñú³%J›µW<ŽF,n*k0aËØvͬãe©µ³ha4‡ÖjšÛh‰£\o~ÇFàÔ«t¶C8¥£)R%˜ù.‚Š5ŒÃ1ŒfŒxBýôç=†„1/âþDðe(#-¿¼ÏÔ‘ºùŽÀrP8ͺ¶@Äì=0HÜ—á8GíÈôBgb.Ë©,g!ÀσºÎ×hßIþ¬vH^åÈ1$òaìI6×þãSE*î·J µŸ,Þ"c=Î8ãeo纠´Â•£~Æòº¦Ž¸tó“DÅÆg+| SnGÆ!ÐP[”¨|k·5Ïáµ…&œé>íò2ÄŒÔváSC ˜ê9•,ÕéŒbde˜üoÂþ¦Ø¹ñð5„–!Šf@K ,A$ç÷yõ)¥wÂ{¸*`7 Áp(JÄp^¡ºPè?bu6lwZ#-Иϻ­(H½PA…Â: 0Ò€ K’7ÓUÈÃA¬4„ЄT#-Ä©Pr}X…³#-«:%Mà ˆh@Þk¼¦ÃG+€Å cÂz ÈåOIbQÜ4„i$ I-%㦅¾Ó©ÆB»#k†#÷ ¥µÇc:„穉"õ#-¨''ÿ[í‡A]º;¢PU » æØh™…Œiã­}'º|%( ##-†`*–ÔBIW³1û·”qœXxxÕ²’¬Ä6.ã_«fð²‹`LØB—ìßÚ&(Pd®yx$UŠ"·E/‰/)™ °¢:¬S“¸Åšƒ2ÓÏ#CMJþh˜#`ªdªj‰()_tªD¡H(ž«ò¶%|ÂWH -$#`HëîÄ4PªH¯1Þ˜#-Ö‚„-9 ¦ ¡ßSˆ#C"Ъ`Ј#`;#”ÍA#Éç$IHL±cXÄQ@º"BŒ….³Z³c†È\2RPRACR”*4#¥)"DÐТ=€4`Ww#-hD#`T¤R•I©Z1ÐB¸† pÂ\ BÉP#^„™û¿O£êø³ŒQ“ãu(pÀFŸÇäNTÅyàÂkžëàé“âO¶CŠ¾ŒXÊL#-ÿÁCÇ·êû½#p÷j‡·-vÛ›ÉTÝ5u˜H£1Æ5Xaqk%z‘šs-Pä±#CN9ñWÌeK>Ù¤ÌE’‚"/Ó“QE0ÌLA0Â$Ī—ï˜Ì‘$”‘ R13@RÁDL#-À¥ E1S@Q$$QDÔLòœ“—- Š¦$Iš‰h‚XFšhfhˆ"‰&R"˜šŠ((¢h‚¦ˆ%¢HúlQJD1APU$ÍA?¢Âš)©š‘&¢fJB”€  ¢(&&¢‚™"#`h¨‘&‚F€ªª˜(‚& ˜%÷ ‚&hª)’©&R$šH‚)i"ˆŽœî…Ã/ÏÍéJr!‚_¢W´XXB¾`ÊÉ:³¹ ϘîÒXNÇŠ41½å»ªºç!¿ë²nè"!ü(²+¥¢2Þöýc#C`*5#`Ìö‹dÁzµ¥Œnx$;§K.M ņõTð2p3¿t¹)å–)6Eج@°¢$*$u7“D(˜#`ïÜô¼ÓÕÑØ’ð3 Ì”ÇöÑò—PÇ3‚sæB²÷ ;w½´g´0'xé2ý UD±Ç€ƒ¡Ö@îGE{!(Gy#-ìbç­M Á‰_w'5˜~ÇdävÑzßc­¦f öqXÁì˜È×óT¹1(ˆ)Å †7ν»]Ð8þñ0?¯2ª ûÖÚÛïæ’·9]užëJŒ#UÚ)µf ¬cL¦‹‡7*éÒ^nu×x÷›V#`B$탡=9˜ÃtØÒ¡ÀÒ^|‰÷"¼³1PKUùRPÈPq=›ƒKRųƒŒKCADãÎ-A[d´à,š#`B#`HŠ’)™e¢”˜`†*R’ŠYH€ˆa÷Üõíë¸yfƒ=`ÇSèÆŒXÚ Óß‚€úó“°àþ»Ì—ú#ù'ù`i8FÛãqœë×goyg= áî5)ÜkmaD5QÛ[‰@D‡ˆfºS¤¾,¼v#R>ÈGðù¹ãÛÄ:§é½Zù=ªˆ{±éî?0²2n&köö|Ú#†j ›?S×÷CùÏ7ŠœÔ>äÅà}qy` b1(ÑÌÉ‘”(ÖŒ‹è#C,#`ÛþOŸ$ðŽ å Ý'sEÁÐ\7OºoS•Á'Û=;Ç—­¡Êœ*Á^¤Ãjwe¶ˆ&¥¤cø±Ô]ìÑÛš1”ž½â Ö#C’½ƒÅ!1H€ë#-àm/^2ŒyÞšþ¬:þ¼{£‘‰Ù¿èc5ZAK$&®ß£`š’#` :Ê(ÂìV³[ð—³C6inc’­ræéˆT§©}Žï;S–ð’$ZÔ—M0çwœR®Êjsу4R‰W¼ø®çYeNÛhÔ ë´<\@+ÃÏŸT,a`Ùq–´¾0Zº#`¤¶Ù¶QlhýˆX!èg$™e ®µ<™·G7í4k¤~µâMÓ5÷,ã…;˜aàçi‚xAòÆ:Ån˜¦JÂRp´æ0xˆ¤Ù¥¥â‚~GÖÒ#`û$aó}•¨yˆšÀ¡mîV+¶#CE³iž&®i¨‚äÊÕ(³ªòÑå äAaçä¶ÎZ· _ƒÕÆb„¢Î{VÝ1cßèäu`U-#-ûP!ö³ #`VC#B|ûcâFJ2wÍz7å¨l×~qˆ]<cá„f‘ÕaÏ05BƒÁýÿ³cï»î52u0öv=ú>Eºvɼ@4‰Ã#C,•¥‰Š¾XÚýxtÙÁQ½ÃØ&(¢?4à#-úþ¿³(HFOŠ‡ÃÕˆ®ç< UÊ®@sî­UúÇ.e†í9pT¹Q€”cÔz£òÇéÞMÈ?S÷iîCÊoéL+ôzý/°öÁ;Ï¢T¥>ùÒñùÿNzu×è3ÕEzÓ «†Ùéì©ið2¼U׆Ê~}ýyHdↄÙGï1ºíaXòè0†õ°v¿ñÄ3k÷†L³ä#-#BË(°Õì#-GNyäó“‘òèA (= ÒJëYß{'˜ê-Ú ÏrŽ×”UFǺ·®JT›á‘^/½ï7¥•Vf‡˜óDFªÓµš˜·»›š 1Â8ÊÊ÷`dÛ{Q1èm–ó yÒÖídÁ@Pè£ßÆ^O{¨õx]àGnsãÌwÛÐØíx·e°Ã'þ<´ wÂ@Ä«¶hU ‘-í¾¤У=øœÓa{Cá ØÓ¸è®ä‘=s°]|³¥ð²ß;G„ÿ#eíßÔ{žÍà¥(!)LèÝœ?Þ^}ݽ¦ÿ—ïà Ñö|Óåô[Y,QŸšÚÇ_úÞ;…«íý03îg5j^.?±¢ý³áÁýrȱ˜ú$'¥qUVM³ŒbÇÄQC’ÏLä‘Êu"Î-A#û‘t¦â9ã_ŒÂÖø¬Nû‘žLf0K™—²¨xPÕšm'ÔÆ ˆ|lU w#`ØÈ£ZÛà«M}ÜÞâ#CæéÒu-*Sáæåo5nn÷½aòòaá'"Qréh‰©yd騛a;’;RÒÔõƒ#CM‡x¢bM¶‚›3° Äåõ2‚d6M·ñÓ¦™êÊ£tR´·©iÙã8«à}ðfòÔ<é…:Øœ"Ÿg„2o»:lÿjôbz·'—Ýçn9\íÒN)EžLÛ>‡¨‡‹•.ÑÓ½*QŒÞ6‚°Ø®Ç/zÆ'x£›5‚†¾¢œÆRëeVVÐáhB‡I‡L(˜‹Þù¬÷4ü)ZÄr囩Pu)·¢î÷^WÚ¯/Å<ÊŒÀîó™“gBŠ—uÄU5‰èz¼©u¼U©!v—ØÓ33¨O$^nÅk¨}ÝÐï‡ÒÂ57™·ÃÞبÆ7Í÷Ì[ºZÕ¤ø$5UÝË?wFÚ‰Òu­oxÃ^æÚ @Ò ò‡Oµz•:ÎÅð`ì»4ItEWjvá&ƪ²—á2QÔìáÉ)îó×屢 YMSˆ0·7{P.'Hz{—ƒ4>Ó•’o;´Å ‚æa–ª\pí.–Æ%kiÎ8´œ›Ï§~éÑOĺÖЄ’ VšÇJ§iC•MP½m¦*&¬T†Ú°Xpô¢îk¶†²R§uòa²p¢KFÉ£žë“aØà âÌÍ›åGfe‡’ª`Ql`ˆÐuifðñÑ£P­ψi¥ß´#`ØÑÊÜ$Q>0g/q–ËhuøÓaÓ¶Y4•—SDaE-G¬¶o¾Ñ¡¹Û8¾v1‰ì)ióÆÜ’Õ¾$Ä09°Î P@»R—´êt kN(Öß4ú™u#Úƒ,8°qA›»¶}íøz5 5(nHÉ™o8M N`Mgˆ"D º¸" ÂG"gƃl£Låž›ÝÑ%LaߊxbƒK$ ¶‘ €QƒÁ+‡rîöÂ,{ß+‡61¦Êàrc®š vy΃02‡dDQ”ØDˆ‡)ˆ¥ó˜#`-×W¦°†9ÖäD¶tN"ÒƒÉÚÅ>¶ÈÌlºn†@Ú§E†_%û÷¾ô 5#CÒ=pW?]Ó•ú 1¹½o5‡P†1.™µ³Ór±=K5£kÄp(ÝŸ—t"ÛQˆY¬á:Â;ñs[ö\t‘ELÒº5*@!1½3U)#C"KLœI”ãu;Ó.Æ:%c—´vv(ÓÃÙF»9ÉY"lÝ;ÄòÈÛ#`ÖpÓÑXÌ´˜àÎij¸ÔÓðÛ6ŠÀcsšÏ7¥/¸&7pÒcl–1&c ¸ÔqÚìT-3´oŠ–’TÈ#C€LháÇ} GÑê®Êç$á6d·„ç}ûoË¿ZÊñAl…$^vÑBiÔ¦’‘Òlö“&xÂm[Ý.ó%]$§•ÒoW|&Î4„í­’w¡²sQ”“ÁÌA³3òö‡‡#- ¾çkÚpvðoÐñ]ˆË3G1 †Ö1ƒ‹Ã+“_|‘×°,–³ƒ[>J\u¤Û³°Ø#`{œSêbñ9ÖÝ·À–Ô9´»¬P韙ã€@J#`XÝÍ­Çx†ás/ÒD9Þ;Á¤e C`5ÌJgãP7mÎü:l.öËpšÇQ#`¤wL76ÃÐmœ¹xÜvÚ]\áP<¾7ÙJìpÁ#4Ã…˜³†,“>Ü({öWŸlŽ‚£ÜÌÝ̽#`çƒRw²é°a³J Îy­V»¾Ò·Š§ÃÀîÖcÆÚâN»1krÝ ».5Ø‚íÒu|’žtCãŒK±š¡“‹FnSdPsÆZÃv6ÇHƒ×“] ï{0SŒ³¬L.PgTH–Ê´ŒÍ·99TŒ¢0ÍD«æ±=@a‘ß<ìšøvOSá…0í€Ù:.›m8¥Û6¸†¤“¹CLjÍÊ1’b7ç„Þ!©3 ˜Ï[`R2Öq#:–C‡¯Qw®$º&·“y#uM~¦‡Î¹'~d6§a±ÃŒBcq7dú˜`Ù }Ë6)ÓÃì‰`FÊÑ6­¶qG[_(d2ÂsÖ¡hb›4“HNtjVl™çÚG †ždíNÔ#Cèu¹¸›¡»&åõ+N‘ß¾FSF#CÝ÷ Þúss˜UH‹†¶J@tcfâíž…Õ=í&}Áј¢’!#C¶ÅxMhº”B7|sºÆÜYŒmµRãnŽ¸}LZ3ÛÆETind„´ŠìÏ%u}(¼›[“Ô¸FDefîÃai«„q¨v5n%}‰¢#Იë$m:Tf?*U•öàRÒ)Ãgc½ÞCÊÄÇZâ–dd§Î"];Ž°­*h]-ÃŒAPâ½á¼¬}õ\IÜs,š5,×mÛ¼Ylþ/Y@ôjàž©߇á¤4Š0Kzf %Š–P *9vÅys³Ž•>;>Œ<`Œfö£X´E6]¡¢ŽÂ[È)Hné‹L†q§Êååòœîvsº”t.¸ïo®{ðbú  æ:ÕA=ß8}fqªlmf2Cî]lž/€å)Eö*vtì¥*lð¢N#C§NîÎÁyã­Ê½·2›¡™F†­•YéV±á)å½›¶V¦}Y“EÉ%Fsˆ¼©,kTжÛìOgQÝ…hfu[=›¹uQýyVFœ8:e¥–!mÔ“ÒqÞpí&ä¹S4Láó!Ø„Ç-bÚàOzz7‚Òn·Nø%¢ì©vœi<ì×MM3‰‹‘ÂÓ$D3"»8ÑÉ{‹;ð¸Ë÷ŽË·O´b{Mo¿teç:à­']ã{Î;q¥e›ØÝ:L`·0[oÅ"¿cŒr[_´L™tãŽ']©Äïvzù—µÞ‘Ž¼«¢¯µ‘ÃV`0wD-ç¾Æé°2HGbÇâ¹µñµ,挲rˆM›À¢&ÔÕˆÁ7½o­+Û$6/x­¸ÌÎHÂbn#CFr>çjîFaìš×|ŸŒ(¬_ªÊ%¨h)´ã£­®!±º£ !EK™`%ì`¼”BLCÞ#:“&˽wŽe𸪠ó…Ù³ZqÙ$ Î'óÍ7§7Ï'}¸–áô›°.ª”ŒŽÑ*.Ì—Q†´&GXëOž1˜nÕî`Þë3Ÿ®¢ÛœàËnTĬq#` õè–»·ßOL‹BaÇÒW#`8ùUA$[Õ¾k%L%áéR[Ë:{›=µ,Ba4ê¤lKÜuÀfCq„9Í€š ëE‘ 3¬Ã06ñ#C­diÑœÖa‰6"5Æ(˜g hà‰`úDc1œ¸ëX5`Ã1\¾™a[–©#`i"’Y6{—ò¼ªs—3;ê³S†{Ô•\-ÖÛ­·yÜv§3¼òÛ>$b0eÉÉ’L!e>œ«#CWG âX÷ž8äÖΓ݈²oÊ]Ã%}SË.„e|ê·â]uËpG©‚ák(¼1%N:oúeÒÏ:.†iàÎòšâk"˜dQvëiÌG-´à¦¶IŸrÍîKØ[A–Â%k™‰H‡#CÓ¸8í‘…ÉÖørX´9)¯E6EµÙAãisÍ0ƒf1®2αV‹Ï“#CX#CµÊÎÜcžwCšÍÚVˆ˜ô²•)+g(”;ÜΉ'4ÀÄ.C-¢;sœRV^OÎ8ƒ*ò>ª‰‘•Æ“ÆÖÒZ(©ÉqhÄE)D;ô›-pñ½;c3lCé¯F#`™:hBÃ^‡¼\Ç-¸r´úvÌ#`ö±¥7Z Nq!±‰… Žu8U¡Pš…UB–L„^©4P÷èj “ŠÚ%¯8©Iö£f¢9·E N˜å…lˆßC¶Úwظ*…r92µ‰3&rùfh/@ø™b ºŽ>0%EìT‰ðîbÞ‡YH|L³F–gXRœxw€ÍFÛDn¾šŠ)ø“ôQ&Üiä˜{‡ Í+&+€Onòôìâ²åá½kˆÓÔ”…³¶üÚÙ“ƒ£Æžb²rÌÓšâÓ„os®ÔÁØÄhדּ‹ª d2Sµ` Ms0-æ#`ê2™K³g3dŽ©Ô2ͨªR%D,ÙJ3—OsIêe«OÃk/•›ƒ¼Vm$ßl¸;¼4ÛÄ;ÎõŠy3iÓ™z‡HDS–¨§šyšûME•\<¢Qá4¢ÝÄ·ÄÇŠœËŽï/›š—H^®bÍëžÎÁ„ÅYÌvîsžØÖý"5O‚)(ÆUML'Šµ'œ¼M#`M·‘ú“ÚEÒ$Ú¡#`çsTï:N+"ã”jó—x¶ÝÁÚ6šŽ2ÈÃi²¼bÆ‚)”n[keÂ(Û#ÇŒ(à솺…ÞG²M²¼ó5B¬8É8Ù8Ýã#`Î0’4iŠh±c3‡§§Ö¡«rcfrk°z®B9K ‰ÍÚX=ê,zõú88š¾Št¢½¹]_fø™z‹Ç³y9eÞ’V\±‰ÆuLœµeJᕉZ¡œNÐT±w Å:ÃÓ6'n@JBöv¦Ô9Es1ëFð\"kæJŒWÌ‘rCž<8‘Z½¤¦œKU*ô‚°³q#Hã¼ÌvÔåÕˆµft^že“Ø°\qtHŒ¢r„´Qw‹ƒ.“åÆÂò@[6\lhyTWLD:ºÃ«MAD“S\)±XtánaÐñ… TW-F‰h’Vj‘s„‡&Ýä’5LC5&¥¤IO–ç%NDeCŠê]©fÜHñ;6GÅ1”ë}ßb™í¶ÌcV4öÚ³!!¥`Æ¢Zß^÷¬ÿ­—vçü²D1;H?I%;K7…™{´’æo4Ì‚„+mõiuÉ>WÆÈF_¨ŽSåQ§Äœ–¥÷D¤¢Ë&ÍTR1¨g½Šž$ÍhðoœÑòYŸÖÒ‚*õ™²#-k$ d,©&g’@"¾¦Š#-×e–QöÙ<é—+‘ µ!&ÀyÜ‚’‚ Fnï¤ÏŽ 7a†æÏq"u¹‚Þrã±/’‰#C()$ š¥M mr¼@Áo[8d-7C¤6t>Ü*9°Ê\rHêFpÃ$,½¥œ.hžœqº2rI&6È3‡:0«m /Cò(Q¡¾Éyt`î/,‚a{“yÖš9ÕÜ¿wÝdØON¤Õ“’nòK2XD '’œ6’_Z…"ɧy+[ó¢Fà÷|qºß rÂn·®ª“$ø0`´ëŒeú‚wda”¤éÁ„":¢‘ºÔ¾ä±z“xI;;UJ óÆu‰¬Z#-ä…|E5t„U2±l5Ñt(dÞY‘™˜yÖ7ãPÔ4ã\Kü«× ã‹dnÓ…‡{QÌgf¥³#`¤Ú$ÎÃnPí žtÒsˆ+]Ûäz ‰äÆlàè°dt&wì1pò¡Ä!aØb”!Û„äÒDÓ‰¢ƒ„‡K&i—hf+ƒ´PQÿngŒ1&##-ëwSAÇpKË,â1™keÝ4‚ÿ##C‘›sGª:Ô<@ڨť#`¬ª¤Y¤œúÞ¿‹<ÂÒ\¦XÍdt€Úö4°g÷“Ž ˆég}’C™ÝÄâ)tÀ¹vg'¶–L%$ Ãp;ƒ=¥Ž†æND¯BmÆÒOyçB'Ò{_hÐ\N1hÄqˆ+!¡ËiºPd–X‰U Íq2j;MÉd~9#CBœ#C ÔM‚¡u†C"‡`é(½ÙF)p«¸h>S¡°d( ®N…ƒêm66 òä•##`*8x‘ìüÌ{^>äõ×"ŽR#ÐðZaŠôdr7.·cÁ#-)A$M!þ8ªb3 ”¢Ì ˆšÈRçöA‡j•¨õÀ©´Þ9ˆÒòXPÔîyœøk€ñïw?Èwþ¡$:AŒ9¯sÌ«“¬¡‰‰ÛD{·r§` #-Tÿ7äð¯%ߣ~ÑÜ_Q ÄŒ½Sº@øZã‘TÁ#‘étC¦&%#`#-eB}.â«äýOêñ˜rGš‹{„!G²ùN0¦s;Obئé¶ãNšÊÝßãP¥¯¤§¹(&üeÑú$æÇ$y#`¶Vj˜á‰‹1†)bº˜S£’Ò(J¼$rC€ vü:+± #¸>#-È5âF-% Ç<AÀ#C„äºîÁ@JÈ {ðùÂ$ÀlÀ u4 «ûül]Ÿ}¼vyö'ÊnD?×Ü›6ô„„ôž%(—懌|­Œ(VY§çææi𷃱·$6ÀÜK °–ÎZ§K`J–¡A3s#Cº‡v´’Â9²påå'WÌee X€ˆÝÈnª#7„I2¦D ÝBìœBgËX8O˾6ØhÅwA) gLÀ…ÝðºÚ ÃŒ¦Û“ÇðÅ¥Îoj!þpK!n7x;IÛh½'Iéܨ6®næªå5¥lÕ¢õÆe*ª:Jâè²D:=q2Ô@øÂë9³ۮs>è­6¦røznÇq)pÁ$µÌLÀ„¡:ªC6´ìÇ~Û–/f‰sx–5ËÌçf=Àí;ÓâR%62´š b‘‡•Z|žp⇻aÉÓœnò“›xÛ‹/lrùÛv¶ì&ÂðÉìvÐï™0ñ¾ª˜Ô×y ¬Üˆî¤åϵϳ¬.§°²Ç'£#Ck¶g]J4úPš3sç•“$rŠ„Ë+Nê1=«•A´ù¼’º>±¶{ÏœàÒ%,—T,ÒÄ­Û{öóŠ¶ÑÔ{žÜœP”ÒŠÍoPo0Þj²nÏSPÇ2 ¨ÖH]sÌömq*ÃG¦!F¶Î®ï5Ôo§¼1KE/>ü䤰[å#C±ó©de2õfIæîcŽïs–ydÙÆ̞¯þ,T!ö‚1Æ¢š@’±oœµOTyÍÏ,6ã[;†pÚ:D˜rعXÆj1pÛ2×»¹eî Úug:/TáÞUQv^(DÔ6îƒ'”åì’HoÒw!»×…ˆLêö”¾À©–Š(¨šˆ$Àmµ19 0ê4À˜æ+Þ;vb¶‡EïšmÚCÓ‘àµp6°IÊ8¬“É>à? Ü$ÇûO§‡ÞNê`ÐaD‡8µ$m!ú$âpÀŸµ2ˆRRÌÒ A‰Hž9z@Àäwš ¼¨ ÔÓB¤@CÚ /±W (T71v='Ñ Þ ù:â#-IµÙ‰Óþªl0uíQ×E`—?/rT.R6¢íÜQˆ÷SI±£ú@àíÜÖ£#`TU)v鉗3šåt¥Æáä/9F_Õ}MSss[âêºF¯çêõ|Ä=I"q u$tu”¤¼‡·s̹'¦N ¸!ô ¨ý0ŠÚÿSÜþøÜýx$•iþ(ävBÅ¢S3@‹q‰ÀU!ZXƒËˆ^OX¢?{ˆ=#C•íªW˜Ÿ  ôþr=_¸|_y‹l*š.ø·¯µ{ý˜‚t_%ÈÉKý;z„î©þùêC»×ãä1‡ÏMB¼Ü\ûlÕ–zX,†Ûcœ¨ý6Y#`Oe¡æÂW›†o"IÜà¡ ³}Ä'¸'Aäüiå.C™/—ò{º8½>ˆíîò±gH€368ÇÖ©ØÔ>ÕØYÛ"P€Åˆ'qÅþ‰ò"„ýnÃçÛˆ”kïƒò¯Ù¹ôÎ~E"~ó»zjdºk•lú…¬±†4Ê%Á§9ß rÎ^¬æaH¾¢ÊÀ™#`Ú“N¶Áò8c2ÞH¾¢KumE¦¶ö5:&ˆ“ZË¥—0YÆ6­…Eüîm ‚Û†›c`ÈräYÁšÂØjA:UHŽW^ÍtTØŠ†² !8F€(æɾW8Ò.¥ÄCaKjBØÌÑ×ÎiCŠˆv Æñðø`Á‹À\‚QÌ^rw'Ú}šh÷þk 2DIJ" ˜‚V‘)„¥"¡R”h‰¤(h™)j’I™IªE¥ 2 š‰H` ç]tÈ{>µv{J ðOµÙMw¤+á˜',ǘó{¾ ÀÐP1(öÀ’¢‘Ú~S¯z>Í}Ý«·È#-âp^§`Mnâ7–ª°øýB¹¢#`×> zõ'¸#Cì!¡ƒ#0/¡) Çip?*´a† <õlU*ú2" U×#CF1V°¹cW)WØͳ2ÆD`eØ391¤ã†‘ #CÓ‹ DúޣܱXÙ×Nn7673±ÝHødðejæ8"»©hX9ëO¬w"xžÜ§p.‡Ì‡²Iš —tgrV껨òÓpç¿g6‡³æƒpð`>3ïӅТ*!ˆòŒíLŸlœ¨€Ì¼’*§D¾:ñ‘î{°ÒžûàÅ8M„“zLŽˆy lÙçÆ´Ô¥¢M¤¿Û‘$̇êákòÊ-†éF Ü†G ì%jêä Ö8•PÑP{EîÇHè̱ˆOQ,íô:t‹±±ÖñŠ™ŠžŒ¯Ë¢Gc: ƒîc@ÄR<—@#CÝŠ€9<¹ ”‰Ê€5òíG;¦¨¡]fP䇅Èì1«Ø#`A˜#C [–¸lÐPPÝ¢¼¶YªAÑ’ÛRMˆm#`±¢Cw¶‘ü‡Çp€×­»µ#C$CT4…ŸÉE,BcBè E«ÝKNαƒ°”ke­¢œ—øèðÎzbÂØMRËÔn;ð¹ï]AEê!ù@>wJ³\iG…†óŒFÌ›„K« Òá´÷bZÈÞ"ÃufbLðú5¦6:0 S„ƒ@:I‰ãð-ý®9$C߆›a¬]uÐÛï}‡¹0D÷{%_XÆÑ›Ÿ§t8(ÆŽ‘Ä…k¸a9µ=N£;0'—í„]Âë Œ*e ¤†'0ñÁB©0s¬f ÙTõ#`ëµ<¥Aâ ƒòEÅ Qæž)>Cà)Ò#C_9M•S _£Ÿ~†šÅ=oç2gys’sœàÍûeé‡5¹Ít­\z”Z«còðSJEþZ9[Ú·AfÃÕ1îã±ÍCbÊrpî“Ep§U½¹<>?¤ÌEJ64Ѧ‚²KvçgIáù£5ÉÅGVÂs¡Ç‹ˆv±î5/¯çv¤YÆYÚ·¦4$Á¶`·e°¸ºÚ¨r¤xFÀî’W$-àÒ¦­´vŠ—tøqCoøÑXÅäž4†bàqݱ$·fš÷\ Â¦Â*q´bïlßñðÅi‡[ˬØ8µ£µYF`ÌÄJhx¨‚paYjKˆÀÐû"Ó¡&epðlNzɽÓ““¤mÓšíœ$,9…I㵸=“ë¾0ã0û™´@÷™%Ü-¢g±2-q—7:YG‹¿«hg¬:f«ž7â®;’ó™|WÂL^|çŽÝÍøŒ³H[Xw3 ««´<¸‚4ÐRV]7lÚÀg>Q…RíiлëQ\]ã‡Îø×Ѹ§Cˆ11´KQªq ì™BzåäJGòM òF¸¯3.:ÛQ,LJWsDQ“šƒNa5F³½ÚË0e\Á376({NÚXBiWDæTɤ塮±#C¥#-Ù«pÌÄ$<”ïaccg |µ×HãvC#C´\u®é¶M¸Ç¨gqeä@÷Ó.G¹ 2Žê½Lxni\zNÃ7#C6<§œ>[1T­,¢&¢Ÿ@iÒTŒñ[[¶]öê*tÚÆ饌”û«õÆ!ž* e˺—*J2æHf¥*ĺ·‚¸N#Âl;ĨÒù•Àù’×”Í)ç~wVcŠßM¸›­œçn¢ØæIH‡ÌlÛï‡Àñ‚$LRmDPiÊ%,•éÁÌ#C!£bâÕÖ#`5ÛVdîîŽÄ})Ó! ´â“¶;»ÜwX8凖å«MËõU³™Ã‹UI‹]q¹ƒ+C1–ƒijÆRfg¥ÉË4rMÝS|o"$$㉶”dËÊB¬&L›±ƒœ¢a¦+ÒÆðŒcÎ0®Ÿ&šÉ9@[Pæ†סÔEÈLÇ3£ê&ýòíÜŒ`fp—`¯ë=Ü=)²#`•”"i|;Tƒ³CrTð<ª°”t”8X$:fu€2]yhG ír«¤h£J$Ã(C 3,Ààò$.Uy ¤Ù8§‚À`/3‘}Òvé§=_I¨aX°Î½G :UI±¡¢—ªxò‰æÓ³ƒaO J\´Ãˆ°äèâY!=¸‹ìßÑÐOeÓ|zú50à sM€Snñ õ =çœúØ x¸\®q@ŒHÆeZñÔ¦³ë‰â#C°#` R™¥)"8ç2‹{¯x‰Á Ñ2C5—I‡ËgN1÷µÆEƒûfõW°÷˜Ñîìs¾p–-R¹±¦bÆÜ·>ëN/ ÞÂÁ‰Ð¨¥¥€¢ ˆdø ÖIƒl”)’£K‚œÃŠ†`+l­nÛ“™iU$#ŒcdmXQÌÀ­c!õbätËtÃ'2‹üNeì/ÆூёÉ€Îýp«£Äv÷Óg3DÙ#`¼qœ¼€×Íöž”’xx¦0c™Î¾mƒÚê#-NÀPJ³DEHAD$#-Ð¥R °”„Ë‚@Ä# 3"C ($²Êï÷ôöÍ+Ç·‘È( bT(¦¨hB`¡@j (@’%¶4÷àH~gôqOSšu©‚˜A™JSCœ œ:%DQNÈ+ùøptO‘•*b#-vÊ…#-)ò÷&lô  qÁût¤C¯å˱¼žl»IšL£È_t óóÜŒàq×tØÍô % ¨U²H“@}ò,M %”µPPQ4HÐDDQT¤‹Á¯ÈPñªŸºDÄ"<°dR#-0Œ> ¨)ÓT?ôСê<ŒeïMDù&#`<<\cZb軃ºçèøŽž¼àñž'Žèq°1ý_#ÑÞqTž“?Dz`¤-0Â[ÀÈR½ÌªŽGaÕà#C€YéGcpí«•Q*´†ý½1Ïêá;/!-¥'‡›Gð×M)’jÒ¬]Ž0ÀzHj6˜ÔÛ#î´"f™?!oºôÛ&ª“SÞO–ø‡t…éÏË]²†¯#-gÔµ«út—ô¿Ç  r#C3Êm0!,Ä\!(@uI?ÐdR'àFdÊ€Œ/BV…Iu}Q¯¾Î,A5#C®m飴ÈšÈÏ߯´ÑŸ¨Bj3'*‡xšî±GJ(×7‚à¦Zø·†Q=”>øß\¾£4jVHdAXÐаÙî>("ôÄÄ!ªïÿ’T•ÔæGgÑßíÛû.@“½TPyö¬ºÿ枈ê9ô°°«,N¾Ws²yó•#-î9v)íqD“‘-/綞zˆ$Ÿ[€Ú#Cýî Û ¯1ˆ$ˆ‰ñœ|swºè ÕËŽ7G‰Õñ¬)٘Ы#-ÖMvÅDåŽìFÙÅØ8ÅÆþŽAÆÔùtòW±±ú½*× 9(Q'xZ)ÏØUpØ@ _êæbœí451Bì º!£$Õ&DÝÄM‹ !7!ÎbX¡¨jhô²S…YdzԡúejRªgûöÈET”  Ÿšz>ïƒ]óéÀïõ‹Z`Æ%ǧÙëªÌÙ•ˆ?½gøÿG»×#C!VLà]q òÌ:ËÀªúy^³2 –Š}§-!JîÃK#`Œ¢ÌÝ ÌkÖ$ùßäú2ß´éá¼ásâ›vuÂ’&x­è´A¨ŒÂh4©›õGµ¤žŒmyŽDÅUÌ}MRÒlÖ#CŒwûï;*D^­Œdh‰øápxŽ'F´O,DNeEÎ#-àTms!Ä"ç"«& 8‘i‹H±¸)„îìÉÓ`#C0B‘°S‚–³­3’2*1 DÄJÍ8;+M±X"Ø„Ý+µ®"€Ë à‡öî\‰Ñc0c'Û`8H¤9ŠÌï\?/ÆòFèKƒÇ`&ˆ”  „BJ6Šµ€"¬˜âº–1—`™èˆ\3ë¯8”ÑATÅPL =”ÁG+(`‘#CŒE[‘€®6ÆšHŽXúðØà¿ŠáaÆÔškK|Ç.AB¸$tÒzÙ’Â((Æ1'8s)!¥#-™Ð‚(Âk%dƒ§ ò2°âÑs…Ç¥@õ;‹PFÚÌlù¸ †’R^îO(™Š)¤¡]#…ˆÅáÌs(  „iîÍ)SÍ,±@ì¹1[¬á"ºb;Ä1Á\HDËHNl»âkQ¨¨¨JZ{¢`‹c£P&«1)©¦ŒmE#CD•C²˜ ( 8 LPD·aÈCI¹¸< ¢1"šˆúl'#`§¥3Æ(¯.÷ºîÈI ‘XaU1\ØW ö{{¯^º(ÖNÜñ$€b×!I‹þº0®´ØvÞ5Ý¥ÁžMŸ°)BJžȉVôaÎ5÷âF·ïl„ƒèßUŒ0_Ÿ"çðà÷6ñL¤oDdG@æ9é¹æhîÅD#Cx·­«Ïžx¬‚ ”• èΨ(i( ¨¤d‰X¢‰Y ’$"¤žzn‰¢°!´$TQH²H²B=‹@Y‘’8Ù)!/ÆçÖCÈ®”ÒÒ4(”­¿SU!ÀP3ÂQð$GÂA b_hºñM1*±C ÒS42±-#C Hû¡À¡Š’„‚!X @e`!†…"A ¨`’’ |—U55UQÐ2@ÁŒDD AMAQ%$ÂDDAQKA,PÌ0AD TSÔ“4´“ËRLUÃA$,K#C ´$LÄC•#C$AE45IB„44…0TÓI1 ÈÒT@!@SD#C- ÊMUCAQDÐÔA3MD$Ä2…$E Q¬Q(RCDÊ1C*) A”A0B2RˆD'USdT„HšVTŠ’ á"¦¢Ib!D„¦”JBŠWÉ,R… ñPO”¥ ˆJ£‰H¢IQ(|—B$FƒîQ¨9PÑ–#-‚#`jKç‚ívç#-«À€N?›T±¦¿”ŒÉ5õL@=|Ð0ø[‹–COKï?ËŠÎ#`sÚ9—X>"±è¢H‰£_E_Ú\azvƒõq¾ôGúVÅ}Ä¿ 0Æ6 >ëh{rƒè²;òŸL?—ˆ`ŸîFÂ#-M¿b#`¿„nÃÈ蛧Á;!Éžh¥jšÊ‘3ÊcµwšÔ×¢NR!¦(”„÷ ë‹Ís6û›)Fí;¸Ê••Ó¢Ò_çù ×±|ÝáÈgURùD¼o;dëÌà’“ù@ßÊz% J:§·ü¯TçNBÅF(<ø؃‘ º#`qø»£¡ÏGéKìñˆÑ¡4Õñ)„FG#ËÞ>C^ƒä #C…$#C0<•|NCÝ|~Q³Eœy©¯)¾µ#-H¶ºšR›ø¤„C”|æþ‹Ñp7Ï—U^É ]}]Á¢ ÷¢"‚Æ>³ÁÙ™¬Mæ/á#-sö´î†!1²1ÂL®ƒ-ˆiT`Ô#-7fƒ”“ÄÍ#- ’{ÓÖÀ'ÐàG&¼#Cút4˜>í Õø¸$S10<5ŒÑ‘yuˆÝí,gÍuênµ·±t>lºðÖtº¦P„àòAPU†Ž£«ÚbßTF‘'™'d/x3JòÇGóþ) i=U'½©üÔÿ/#-âÞRES‘7ëÚ¢§ø˜ÐvV†'z;ˆƒ„x“Þÿé>¥]#`i!ßi±4lŒô¦ƒ³DxÄ‘¦#-¹hEi/‡Êd_GlÃøG9Jôm$D~þx~þvë¶Õ#VÊ7%ʉò8ÒÄšã¤QŒ¨îa1x43E40ƒ@‰|Ááˆè´©ÜJ;/¬ëÈÒÑ\Ù-UT…)UEEUÃjªª¢ª¡„#`B”®Ê„>„Æ]xZ§Ì™`'ìQ[`Àó@NP&€ý”¡@Ì”©£›–^Ìàvb&!Ú9ã6 ¤ºó*0#`žÝ:øæe©*âL‰ó}W$w{iœ ¨Z´ÆÖfdó—^‡D$’`ЩZ“ÇC8x2Ÿ|¦] ²… è ¢Î&hùùæ̦ˆ5*ñÓNïáÎϼàï߃œAîÍ_yøy½âª§Ô†Æ$ùOo¦ýR'kÚz“(P>¯€`ÉuG8ƒH¤ª‰%>Ää<‘{Â^HŠDgéIwè,%¤–0ØI §TáúµoÌÈâã êxÏÏÁWüR1«à~Áµ¾w}ÝšêvfÒÚ~É2#`IU¥û¬ÊÌ*AQ— qNÇB– h ( ¾¼Ú4[c Ê_Æ[Âã)H}ð¦~Û8ØÙ„II ÌRÉî\Ï=c·FI¥ìâfB¤~þmLM„doR²Ø£hee6„ÌAsÌðI{&Ri#¸0h5JI ÂÃJPW5¾omuŒ´ ÐDÄÙJØtÁ)PcÌi§‚‰±‹¹=yŽ·‘vtÜ3ƒBho 4JE-RÒÈ’öB¥¢Š¢ µ± š"v š6%¤­Ý®lDGvj ò Ô!KL~z¤’ŠŠ¬âŠâ©UPÓ4–uøö·Ð#-T qPk»8ɽršÑæ”}òBJˆŒÞ?|."€'¼~Ë”À°´(´$€0p#Cº!‰)Àwˆ€Ó¡(Ù8çÓ€rP§pÌ”2 !>kPêÒA!Ü÷u(°è P›jw8‘H-RÔæ99=‰-¤|˜sòkÞÏ"šóÌ6è`0Y$úº•fyòÏ\â‘ÓÄ&U22U‹“Ðlxm’ĆK´Àö>׶N¦ø&‰R;ÆÒGO"í–#`©(yS¥˜c+0 $­þHÒ4$`‚C#-UL€Î^冓o?ôòÔXu–’>ð7£òm!!(^&yŠ‰¡€·9½#C’ \!}ïnŸR?35šSâ•8–¾V»#`+˜~Lܘm¿UHcF´F×ÉësºÓjÝe³÷ÿ.·½¬SBµtˆ}:á¼7š°#CDrïˆ~0hœN~9Ñ9Ãx Xžö F#-ŸòAON;b@GÖh¬É]¼x'VpäžgúÓ}ÊRÞO¤„ò`db0÷¤¾¡æ΢²B F]+F#`¿v6½`B{iÛkÍREÐG{Š©ø4ìÓ?¦°~̘†7IºP©Ã$§,›š([¶3M*»µF“i&ë.­Žo‰ÓKU½öqó`X;ê§ âYÛRš\”ÃàÞ‡‘[©¾JÃfؘ´³d R@á4BpÄì(÷ hC–“•(öP’Äù&#!iB˜Eª¬<ºÙÄב †Cd:!I³03dÐ µT‚‹ÙDß,}g„‹÷] 9,·›4ã†JˆvBa Gd#-PîL`mˆÛšPÑ£çX,aÓ#C,+6ïXLoGÛö«¬š—š•Ç<㊱jQ¾x+ÇÜ9Oºmä¡­Îá¸o¼höÍTAÐÜ&¢EEŽ@£.!©Ê°´âÝBÜìåz<ša’0Æ¡”¯ >D$>qç£Äx’gΗá< š,k[ÖJLÌQPk´#iÔüZô žySÉÌŠ¸Y_l-²ÔÌ|>Mqw¥–êã˜ø¦ý',æHÎC¨âc‰ðïA¿.4/6òØ¡(Òý-í‘F1)ãåô_3²¶>–¡¦¯‰…˜§œƒU£%xÅ_nU¦(^oDÌ/± jªÍ±±?œïlÎ#`,ÂT”D#-€É!ºnVåö]kI´aL‹DV%•@ Áî¼f'Îø'šêéó5/`è-®¦µoQ´¡Ô;˜•Hàk@—h³•QM0>:a³4‹É#ÑÒ‚Ø?I*bªj¬xñGS×< Œ„Ä âz3G¹†QP*†]*›@ä*bŸ³—›^Ðk² ÁRa×»mn~—ÆäCŒ˜ÌF88fP™25 ¥¤ŒÁÆ#`Zt“%"|c€$pK4† …ú=yöPö{#YåWeB=Å2˜‘‹W† æ{b¯Oú·šâ]7ZOmëU‹LíD¼PËò ãêCõ‡½ó˜ÒL‘ü@’]}‡‡'8Â0D’*dB¸œ/Ó"zwS]ñ{‚b#`T ¤#`iGÆ/#`¢€’JOqå 'Hî ¨¥‰hbûðdªCïN„#-¦Ÿ¦u!uÁ•Dš…Ü8hhFš`¨H¨)!ઊðÚ (8÷œÍ3ÜIéÆ3À40ÁT^)µAmE6±$Qt-4óim·YNðÐGoeû‘àß Ñ1TÕCDÐU xF?V4-Ñï*õ‘4|JhOsê ’…8È´`ƒH:¢ªeiH$£I…ÛE ä©(8ÈAŠ"…ž§ËÙ¯Ü0gÉÔ#-?Šã®®UèS¸îãÀ,"ɾaÜ;ÀäaîçšÇqi!NA#C"?12ÑÈRz°Eç¨ðãûjDñ}'¦Jî†3#C üÿ-oÙßá׬¿gÖwhÒtϸ¨å#-æ´ÀîdY)5#`gFnkëÍü*©j©¤äÓX™L´æ¸#`¦)ÁwYÜ#-Ý6„KºKÙœC®uPÓ¹üÿ‡?H˜3–Š½§×ä¦Æš˜¢#äo‹û‰ù0÷t!õáÔüS„‹¨Ø oKhü·Viž¯¯_@`ýg­~f&Óe|$fU{íõ]FQ× D¨z‚>:HqÓ®º"ŒMSVŽ}#` „ž(³‚ù$#C¢‘@F*%^ýúß-ÚMLÈô ÛY˪CdÕ,ŸòcÇìI"œ”Ÿ‡«>ÝUb‰°cE²;Ô$Ûò¥Û¼dÔÂ4S”"Ü$Ÿ-H×aìu‚îÑ¢+µŽÈp5»»Í-?ÏÖtv %)A•<´pÖå¢SÝ¢)*)îÑCM/E9Ä8‡,IÈÄÃ\ÐF6Ĉ~­W\éfœÍ™ágÖ Ñͦ#`šn›EG0bˆ.HyÀÖØH:»ÉqÈ–ÝLyÁ´Çü®_FCsýúl›& d©f ”ðŠž"¼$)CÝBA5Q-!Q‰ )€%’$¥ƒA¤"­3lb¡(B•)úqú¡ˆƒ™ #`(#`@ ˆÆ@0GaÒr<ŽBÁ_9”)õÈ>Ö ËCBШ=ý ‡{€AÅÖpB‘ACì1"|æg¦9 ãïb™ Æâ 3=8À`ÈA0gД¢YáPt¦ÒHžeUÛ(´Äà62Å’æ”ùÐMG‚µ…&¤A#-D‡HoÓˆ&™’É „öŸF“ƒyYûnûÒá£Ìø‚hz&*h™ÐÅ1Sœ­D¼ÈB‘¢\®PEA•5çãÊŠ%/É“ {“žjW¡õÊ!@Ð#B¤n w@:¶Æ˜œ54àb'CÞJºìõg‰îú3¯b¤)ÿA–„±:4€¡#`Q0, ZR’Ю•ˆ(¡ÐhJJ"@¤i‘…ªV’¨"‰$"¡Z@¦eJR„X"5ª@¡#`ZZŠF€4Ò…!@J¤me$©( Jš¥ÑF –"” Y#`¢H1j#-h!Q(#`ZŠ)¢JhiZJÓ ¢ŠTªF ‚€"ƒl$@ÓBÒD-!HÔIZM#-QKKH%%RÒH%4”£JÐE@Ð1!@DHД”†'C@…"UPP‘ZÀ6Ä’ª#-H'³¯ÕSÞu¼Xn|4%¡†¢Õ«úæÒ48LßAè c@ãzK1ÄÁÂJ#íõ]ä•íL•ÓÌ`¢ƒ*¢ ‚V•( ¦š#`Š=CSPHÞ8 ÃÐ:†(èéÕ>ÅÎ*tŸE‚l{Së#CfÇ%› ±š_(L "n½Â#-B€àáP‹:¤Òèª&vË6ˆ‚µ£QA¢ƒUÑI³ŽÁƒ°‚@IW#-é\~†9ÌíÈ¢š«ˆnv¶ Çë_‰ˆ šªŒ7dành™=Ñ?wO%N!Ê¥yè8º‡Ìsn)m‹ÿ%ˆ¦ª°|úeA0ò10ª&ŠL 9ÁIM5@QL@ÑE5@S$MSDI%DA30E#`S#C+DÑ4KׄìÁ%uÅ»KíätÛû¸n þcõì*Ì9#Cˆ#-'€0<0’3?ÂÅ#Cö„Ôçð0MM`#-Ãfã#CÙëÔ÷i“4HjÁrÇ‘–‘‡‡‘}þ‘¾¶ÔfCXp ‰ |ßç#`0èDbW¬Aæ ?“³H »$¤"&‡&Qˆ@˜Æ:¹"ÿÚæ$M¤˜`½]»ˆÚ±8~ï†)¢~¶éjûߎºXOwåüGçû¶ß€¡A"D8ï jŠ‚ü+¤<êótvÜþæý‘Ÿz®#ÑLŽ­ºç8S`‡„ãÖmô…KrìüÛ…vfàA†Ò€Ÿq[Ä©“™#`?Ïøóobãe#$þà>”Pë6ð›t‰Þ\»(2$ÉñMÀðéô?·Òý¾Ôú*&™‚#CˆŠ&†™‡6ÓC#C¿Â0š€úþt÷*i­<@•íÕ#->i/l.ŠÒÕH&%­iEPÄ#-ºA}œ”U¥„ú+ùd1àlŒÔ@œ…(R!”6G#·MÞ0ÀP"ƒõ4Ãðek@-6¤éüo/}Mè·žþ8ûçàrÉW»V‚;9M>Ü¢ˆ¬[iis±^ƒ0l…ã˜×UcŒ7“HŠŒŽã—Rô2fÒi©(¨%Aé#CÓÒ~ãóäz¹K–Deú}¨¯`_â8!éÃÐztUyÍ»ú‡Ð)<»Nø}'ÏQ¯8z&‡#-äkË©w§t‘$G˜§£à&é©bQ W(‹ï(³0TÂÀ2“RQ«YoÓü¸óÇÉýÌvBŠNªÓVøâq®b"Ç7?&ØÊŸû1 †Z/×Êuêþ)Pê@éupAHwƒúzÐ)û#C @ï|o¦’"å†Úc#CóáŸq††#`ÌÈ4ñ¼°8@£r Ìê%5i¹e½}?›Ó~óíð²ú|KÝBß®°F#`üc„Lù#`-æŸ@;¿q~ñŸ( i#C™:wÚ’vÿäbìz‚4,%ù pM†ØÇïd-¼š5‹$!órå1,•ydª4` ” ¤®$_S¨¨¤æ;Äá!›'¤|‰ü}ÝXIÄþJEOÑ›CZhá!I†˜‰òÞwûœzŸÿD{ï#Á£|c£³¦Ø9)]6ÑO$pÖ4°lZbHìòœPŒkr6²0-–¢¨[P”šqvâš(2¨éF•ìvž¤Ò8B—d{ç®rÓ#Cµw#`2?”Q*#CPM®$Ë8¥ÿ¿F3þ )·Q#-þø4”6„DjÆuÔ }õº’ŒlÛOd6ÑÆ¥£Ç,YÅÐX¥[XÆØÑÉ0€µtHeØ›”‚Œ™VDŽ"3&³#¦0M.ùG'½Ã  ãî„y4†5¯3ÌâÆ(‘ÞûÁôwE>£DZ󱻧 ¤ºþ‚}õqûlzîNAÇžsç#-½%¹ñä¦ÐR;åŽH“oŸ9 @SH’¹Í„˄ᤋœÐa†#"ìË=øÈòP÷©9(к¦–/qÉbP䔾äN@r4hóÎ[šÔD{Ïd¯#Ýúž‘Ùär$®žJ³º`¨ÏwÒçwuyÚ'$ø‚¸0ÖEÙ`K¤ÞªÎQÕ°“,Þ2—1Îä;v)‰£]9´Û“AÈy<—ܤ`æ(i#`D Ðèõ"ˆ›Ö;Ë&žzuÍRWsÓ†ßÄÕ'"!ï˜òb(Q’¬) .+&õPÑ0ËlâÉ£“16†æ ŠVHHÓr ãPïþþµÎê9ËÖDÆ#¶ÐÍ•±´Œ±?)|¼Ø:\­hk„äÇ45±’ûsÒF@ó/Æ#<ÕI-!ŒɈù€ ð9Äæ¦1AÞF˜â`b֙˗ñ?³°Ñ~@aù4\î/š­„59'óŠBt¡ëviXìóÆ\<•PKôkÎ`¢mF¢#-üÌÇóÚ I‚‰”!Aï3Ž.!,˜3€ÂÛî¢êŸBôE#-Š3ó%F¿²ä.¨†°³eäHvÏñ?wžž™šs\wà ô*Šù—0Ì ò‡Ú>„µõ‡Ý2zcH¬ƒ#£¨a@À¾À§}R‘#`¥ +J‰4¥3#(ž¹Jª#`Ê%*ÑÉФšB€£T¥#¤B”‰ "èI‚$•UþÑ¡yôJhn~|@¤JT4‰Kì¸Üš? :}œØØé D±1ª'a´%Š`ZWPOæ=%Úwšxr¿CÓOOÔ^;¨þù°. &`àd<‚uu%Þíö~{¯º¨PKÜŸ:)pÂùÍ)>Ï $C2 #C`Ÿ+©#-›}”÷¢8ÈvAä‡5¾äþœ=–D?©‘ã± D¿oúJbîŒÁd3¼I†ñ¼¦jÀgöñ ^ŸèQh?X ÷>"ƒƒçgÑ_+£÷`ÂbrvC½!†#©¼šŒÕp…_²yHD ø@üs#-üB†’¢Ei"ªî²ád+÷ÆHï*.C@3´ BÒ‰¥^Œé(é#- HÓ¬#`z4Ê:OœhJ÷JšÖ†D´#KØuä)›ë#-%(®!¡'Øäéë|vň¨Å#ÿtÙ8,ži°jRä„zðQ:2dê\—²‰ˆR„iKáñ¾‘ÇÙk¶¶|5ñ{6AëNâŠp-:JÁ P­MJÁf¦¬éâuøGû¼ù†ª ¬!x߈ÝÒ'HãÁpdÁ%iÊÁ~À>Õº(`NAÄ$2sï,d»T??"‡yU_—vÏ ÂvÀ ÁT¨D–Úŧ´¨QU JQBPyÙ®Û>¹ú7|ð=×#CÄñRÄW³ð¦'zj'òÅ¥cþ[’’dÈd½šìME(ôð‰¿lÖj"‹R):£¸ÌCÁ¡ j£®Š›ßïâ¿÷ºë}þƒq‡áùxº~Ë!IB,,)¶RR32å“öoˆ'ÃD:ñ_†mõ/ç0ý8n4>­]h<\¾©øA¯¿Ðv, @|‘¶„š˜€ªŸ¼ò­L26Á?Œ;#CüÑ$’UAùËr™]CÊG'ƒ¨§ÅMDÐBA#`RP´+øð0ð že‰ #-’Š‚Cp¹´A44èœ×½ˆ£1JÑAALÔDB(#CJ†{bvßAšû15œýÁ¦p…&0úšËÇ •-H#`PÅðÒ–¿_Nëì€5ƒÊ!Gð¾mð#-¥#-6³}ôÁŽ2ù‹†¼|SÊWn N’I§÷¿ÙenlÈ.ã,GðÐ-ʺL¤€L›„âåÝaP7RæË3VñQîA½æîºðø™—y)/óì©IS³¨~-±«Ä¹Æ*6`yÓ;#Xdõfª&tßm)Ã]í¾SÛ¬à|ê^'¤¦.Ù‡q×}3¨‘©ÅîЉ¾¬6ç.ë±;HÓ©™Ñî@lû¨4ŽxÃ#CžîmÇÌ*z)™·èâØ‘â"”Y|SÄhÆ …Žd±˜0ô£­ä›YÏB#`B ÐR寵^ìX8ŒLkÇ$QÓD>#±ZMƺÓu™ãkÛ<œó¬3ôW8¥¥y¨)ÀÕÔ.ÑÌíA×y-Jl"á·©×g|'0îê¶s¦ÑJÂÙö²QJ¡ÇEL¹¬îXZ1ÏÆÊ’ÎaÓaœœ¦¤ÂL¦vÕG‘&è}Šv•Hz¹Ñ9ŠÑŽçu¬Ug"§¬)Œ±†bAÂàSá#`-Ç%Ž1uÚfì…Ž+µÓÌr£›sJÝÖµÙ¬ ™bB#®ijXÄ0ø`Ã2ŒÊÀˆv9 FÌ ¤$‡èÍO2€˜ŒÜÖCíY¿¤Ý¾™­‰­™þ: èéÇ8ÃÝv“ÁõÈ#-bÐ1¥WF#-í„LGöaÓÔgúMŸÒ01yH™ÀaÐ$ð$ähïêpàužÙ7“¬ñä~ÎÍ,Éëmæ¯ïʧH£/•Rµ èÒ&¤ˆš¦š#`Z¡‚™%¨¢R¤ š¤)#`B$iˆh#-‚"©ŠŠhFªˆ––"%$¦š()’ˆ˜ˆ‘ˆ(ª–i¤¦d¤hV„¥¢ ª B€¦’–Š#`ibZJ)$d¨‚ˆ ¨¡E”"¤(”)‰šH$÷aE41R2I $QWòNb¨b H„ˆXJ"hªe™ ‰’(J©"F#`H’¢Š¡ˆ ¡¡‰`e”I!Hd¢’&(Š‚©¨e„¨˜j†¢Š˜™ ªdf‰™)$šXŠ) ‰ª¡‚"( ¢©Q’ ‚‚@‚IB•@ Jj¤( dÍŠéÊÐM&ˆ˜‡ûü-ƒãf5ŠäÄ‹³Æÿš\¨†CòÌñÁ˜q¨§0MùAŸ²º¡È@N#-%¾JPþkõO¬,‚Ì ˆ;ÔÄ@hE0Z J¡¡¥§2²]©N3TDü¿Â‡yDªð‰…)Š#-" °™)R„À$1 XxÞ£ãë׈ww¥@sV©:ÉBSl©¤JL~ò¿³ æòÓÞð¢J%¨”÷I„AC£@AM7ðí-²”¤&™(¨iBRBŠ¥äiJ$B¥ûÉËSP‰2•HC*‘$QÄÝÐd„¯q ZfÓ (¥i#ÉÉ2Ï.!4°Ÿ³ù8òjWî÷h‚ÿ6ÄÒ5 ÉîÏ0hh!ìjH~|X¶CœšDJDuQôAè(“uöd9šö6šœšÝ"x2„W瀉‚p{‰ÙÁRö{Çdþç~iù ¤CÆ@¡…~éؼdß°Øú×Ç`¡¤^õý¬ À?+ø:#CÑ5Ò}Œ¥%3@DAE4„ACT%)J!@CI¡hùAò*„%x0:'Hvátá€êBÏY|#CUwö/Ò‰Ã}PÚÎÏŠbé¶Åæf!…ŠD»àô€ž…(¨‘ŸSTúkŽüIÍyfûYÜ|Ƭɳ0Éè`êísˆoµ†Œš‡¨)ÉÛž$ù!&9T6?¹Ð<;öœh¦È’Á®©$‡.ôñËBí ]û1µõ»š¦™ÃJ‘­r%FµÐ&w-¤Èp¤cäMà|­éþ§ÂHGPÃÀ•aµ‰Áˆ9,ê@|“Ö}(t7>'äÕ#CS³#`@I{`M£`X|vI^`'µÕNŽ0â1àtÔ½}½^RÇÂýŸ5‹ê›½È€`$ÿ&"ø¨m¯—¨r¦ ˜}ǧm¿NDs d1(Í0g@`Ø î]¢[ãêÎMpè2¯¤ ö›Ëç#-Ôéê2SîvK”¡KˆÃ‰P(¤ 6í“ÈIÛGÀ»ž…3oR$¡¬+V«0#MZ B¸D]P=HìVVjÁÂt×l-?-h:Ôï¬ÎXbP*^¸‘Ká½!§åøw!ÛR%Q!3QM>Èu(UDÃPA&“UPIC•IPÑUEDR)B$0?‡r¨¢¡’ #`fš¨ª¦ªB‘D75ç?è>úŽý(ÆÝt¼5;ªkp$‡ $ø„&mß$°^)J“’Iï ,ºêoàg‡/·¨®b¾Ž$N–Øâåê±À®¤46]æ¢öœnâM=’ªÛ,U”e`ÐÒ›3Óg:àÄkööÑp¼}pÛ IðÉ‹‰é9–tèÍÍÊa'ã¼¥F»RkhéõìôüRç›ì3áui¨'T!¢L°ûm¥Aƒ#`™¢Åš ¹d¢Ù3O%p>ÓñÈDÑSs¥ƒÉGÈ ¶ÅX’á“0Rþö0woprSÖÁñhñ#`H4Ûeü™‹zÑ6&ÿ\vëQSQ,$†:ý…ö[3_âÿ.š[2ð7Ð8Ø—,‚Ë\f©%’Óµ!@úƃ»‘Ì”Æ!rТ„,ªV$|»­ò£,?àÎ,Ì^£ *õ¢4Ý°P»˜O‡±­A¬ºÕ>Z<„;îÁ"P§?+߯#-ðA$9ë:"ÖËG*ÖWÔ÷{n¸£ˆ. ˜à±7¦ó&ãÎ,(Õ„>3lBÅ!!…ÇRPÞðº˜Ñ¢–ü¸ Ë°MŠM”þ¤€úáŸP€øúõHÎw°ŠGd#`w©ZH”dƒ»×Ó½:ûžòQKÇ#=?B¢2ÍÒÅvÝ´õé®tši'ò˘Aahf~®¨è00­Œ¦)»= b)!H(‰qˆ2`á²BfTâ’Àt‰2ˆ`ûCÇíÖk5Á§#sßéAôž%ÐP|ZhÛ,Å11TYŠ8B¾æ¤×T÷§r|:º1°09¢c!JÐN!Èk |¿£‡6vcdcíæ×ÁþÅ0Û;n ÞÊih¦iˆI¥ÒÊB~ä×Ì 4rŽœé$C“SHéx ýûùæº\qÃN€÷ùϹõ*&x/(áÄïô øId H1¼Å¸*’!p“™æh£²¼y#-<0‘ü\ä4€Èƒ[>ߧtr)xIÈNdÉôQ0ÅÙêG`ì'vÈb|-%NT…Dïy«Â»‘ª(IHJlnD†¿6!¤/ÖóПVÓ¼ÏÏêñN¥?,,F‚¡¡Õ+³"ÒE!J`Í#-DÄL¿Á ¥ÒùC“Hsd§À¡¸XÌóñ2~‚xx1džG8ÍÊ™¸e1vŒÍ5ÃDÇñ‘«¤™ƒ¦M ç6ŒþŸ‘‚<„âR¾uc¾ÙДÈ=Ö»úµ›`g ì[†¡XØó,a6½÷‰É‚ø†ŒÉ§ŒM<Ö³|½Ydí¸v¡² Qð·l ~S³³ã@Lw‰ô|#`´?ý#‘#-<<%3bqcíï¿åÍTA5û»G1kDΡ°‚ ía{X©ÃÕLd±p3f”„"c–¥K„ñð´àkž\e'cVàئ¥-X:-ÁH]ÛJJ[‘u¼xÝIÉHnÊÂ>,hÓˆò5szª#-˜U,FYM˜À¬#B¡w™©õ¦ÜK‰i¡«$ ê+·ˆà„àœšÁUÇ‹bIÑ Õ©"’雌k5R¥ª«íe”ÆïÞácÈp)@cU#-Mˆs#`v^8iƒ8"Ì‹é÷â7BI$Å­ÍÁ$¢èEw¶\Òƒ!#`é·¡Àjº.)46Fto,Éù¨4)¬ÌÙè5‡Ü¦¸Æ!s³²&\£C–Aq9xªšaúybÌa¤ g†= fÏ}mê‰A»´3LºLZ¢DpM{H0u¤æ&‡ŸKgŠO›h;GC^©™Æ$¹[J'žj ‹ý·Ô¦(µ„ܪ!3ê¡_bÙ¥æð™ÕMU«ø ¼wÄ(߃›€0-X2’0‡IFNu¿÷t€‹AÆ_?õþôè â{}ÞÌYÑ*F˜v +¯‡©¡ìÁÃœP"(ˆ¡ñ 3ˆŠ Œœ x$"ýÉÐÈaÉQ;f`)øðáC²ämH¢Œ ÃÄús ¶K½¶êízBÓÙd.a‚ƱƕiŒC¿9õÙ#CAã¦9!J‡œŒ†%øŽ†ÚIthƒ’èÄú†ótá·nÇ»°Pö<’w/7[”„@0[K²ã€`ÞVîaÖÛ„#´Æq‚ä HöÈàâÓ#xäi‰ƒgf=(N#CÓa¸¬\PŒW’¨Ž’ëí¶¼fL­ wf.!°Iå#þ£ÛÎoq­U6hK8sa=¼C˜#CbáP#C„)ãòLÓÜÈûx'_d8ôÌmŽi™ÃÔ’v‰\s#->¤”!„{ߤí#8)è¤x`|«c#-f§ºìJ‰—›%Ųo·ˆª ånlsXˆb)°±š2+!|¹xñù²Ðü#ÕÕ6äs.ç"Àï!D³åÍ8âriŠ¯âY k+HðBpxÞÈŽƒ¹‹ä#CèƒÂå@P‘(ñ…É#Cþ˜Ð!CÈÀÐA2TD*Q!É4(±J4į³2ã˜Õ#C 2”·™#`5¦“ò΄)á©ÄÐ˳ê{'m>XžCÉç3¶^Br"Q ¤(+‘KˆÓ´hÕ¶’õ•ÓËk>¡±‘ä*’!±o\æÌ´èR•ØçTãdõs4§R¹Ï§òéÏКˀüÄÒ‘Ù ™«¢z°Iˆ 'ø½³ù0áî >‹fê¨SÕ蜫¥qîý÷úú¿@I0'›! Kï»”\#`O“á;Òápbž'_¯×å¿=vÔ¦$;a1‚‚hŸg,bEœpg] sœ@ãmÝî|¸­¼ˆûÉAaøß-ywùo³;»ã+a? åZk’Ð#¹Pfâ"œ¦]ú¸Â´Ü&ſ°Vù‚[z¬M{a¥¿¾¼š§ Өᖰ×bÿ’#³N2òÉ’à›¦†¨Ñæ(zs†°ˆ"ƒCÑôÏûm¡œ™ëì;´ö½¿®wö`ÿˆÙ°Xp˜¶èÝ™™@ÑM0|ÏYØqo-¸)Ç“·¡œ¤Ÿ¶vÍPmnÌ|wE†á ¸ÕŒ`“E²4ŸWá=úr¿1÷¶äâdÜÔJŸN/YàöFç媰$È]Çy Äó¹=ðõÁäi}Òïo[¡ñqv@„}¬poû«ŠÐB¯¡¹ß§°|”`e0ñL:6˜Oä¼%¡4uTœÙ,BÒ)#-åìŸäî3[sGŠ gzü~]OᆌŸ×¹­Ób“‰: ƒ”#Û6ÄZÞ.@“¸¶>?ê¾Ðƒâ£õ„€vU@Á>§Wã o€Š×Ø•/ƒèèzþêK.Š£V/ÄÄ´7:Þ4ðþ,¿Úq½—ÆïM#-lI¡¡”ƒûGÈ4ñJbŠ–‡>s‡I K)*[tBb ˜>cSÎdbñ'úuÁVE Ž¯äƒ³K#lmuyÒVqþ´n`n8•¥¸À~V âýÍ©-aE@`ÏÊ ´­@ùl`©˜”ó¼á%Îrª]¤OD Ò { óà9'ë·L´Á(_‹MM@Д‘<¹ O$ƒÝª`)´aîçX€ÊPÀQý\F#`°ò¤»#`<¹ý”QjR$zçß ÛQí qøÊ®ˆu`"Ÿ$CæâÇg®Æ!?sd5ª"ý¹©ZÇ0€nÎœy3D[‹Ã=æiŠ`¯-X#`€ªn0¼‡„-AI´Pø$ÆŠÕºIp79$œëÔá ÂEËMݦá¸7.†ª14M̧(è À+™;#`q„Nª#-J¡"À‡&…䈈ÛÆ›ØÄО÷#-Þ”7ÜêqÝ °€oÐÛë\Jð>~ÞàyæMϘ­OU“¹<—â`šñûþ¯'•éß#Ò#-“P\,,îWC@$ìé#-¨€$ |ºCFHøøô¼CìºúºÏ›Ÿ,c’,žSéÛ×öçÁÜ&6;Ìôžæ#Cø $té+¿,Êó«ƒyÔ’D,ÅêÖ:þ—¥‰ŒöÈƈ(Ú7ðÕMq#-¸<.Œ#`ѲƘĹŠ!$ /µ/#CUt,9y8Áó PwÜ’›—aâÉÂtW'¡Nb*|‡ žÄ#CÇä†÷«##"Ó#¿La„¾žƒ˜Á#-öü¤6û„€ºÇŒ¦8zCÖ&€9̉ú ÐQV$›¿<1î4z6í1G ªxXTÙ’m[sC`Ìs-„#`å“40›K°éÎ&%%pfç ©À…K’Á$±.BQ’ d’ƒQÃÃóYª©Š$¥ˆJ¡D‡ÄÞ¸ôÀXòL=<3Ï_nŽ‡ša ’ØæƒQ-¶‘d3ñÒÈZh3IEÐ}HS f°ë‰Ñ{`Š‚î'8ÁU\Ç.#Hü­Ù3zÄP«*$ÉU·™á¹ô&èf7”G ¿K²<\jCY˜®!Qœ<üû;$rp¶ g§¬>ãë°Ð[÷”4É.‡ãrI¢¨^ÐÓ]£ù9|…OQ4#-#Cÿ˜‘]ÈS‡oWÊþ×·âÑã”*0ø î)Ѥ`Qô/ BÜõÐ#íÔ„öüßT ¨L@‡¢mòöYàý«fêwGÈ<%0Pè?ƒOCÈíÙ׿¼…xjã.G#ÐÀ>/—¦ö63U_LŒÌzµÍÅ£Dzý6ÖÆyW½ù’„k”ÜèfMGŽÌ+æè*1j¶Šc—N\!.ˆ(Æ2ó…­jZá¹ fôÌ'.0aÄ4êÕɆa9Υ哃ˆØeå4¢‰µŠ”‰”Àߣ劫[ Ã|3NË]ØX_¡ÉÌÏåÍ7„ÏG»…g—zi=þ.pÕTDŽØ)aWPù‚Ö×öbõÔú´RIQö͆…ë‰Îȼ·áYe a“+¸à .EƒE³¥)7cr9¥<¬ÈDhˆ5c°é#-#L” äê#¶€<»ÓàIã t% ò#C†@¹°BtéÌ3;r@\áuÊUˆ„)ŒÚ•áœLJö¢‰I%N\¡žHõ”ÜÓÏgáÞ#×1AEcaˆ [ÑÎ!ëûq¡¤‡ÈF`h#-ñÜÄy/.J>ªO$¤ÁPœæA‰HÑ^àrH7œmÒeÏl‹ƒ¾\|}·ìœS¦ãùšÊºÕ ¾tŒ>ŸëýŸåÿðoŸŸ!uþݽŠ=2Tb"¡– ŠFŽƒ­SxÖiO\§åÌ&ä‹Ã#k-UñïÁÁâd2@T8CSÃßL7}Õ‚M ¶UƒèÛ33C|K½kCæõæžͪžïAèf±Cז̃4S3p‡N ®$SŽ%uÏqÍ3kE5D÷çS0u›\L‹sd&¿‚Hþ#`¬r/Ûu{•’ÚrnÇ"hÌI­l¨Ù¨Fï÷¾—„èë¥hõQF“™š/Æ+qëødÄä¢Òà#-áŽå¡€Ê– ZBäh  L‡0 ÅïãÜöbéÂrV–Ly\N$û¥J¦##-DÝŽ]Ä O×xnÛOw§¦žž¥c'#C“2A('CHäe¥Ö±àÀ´5UE›€~oÍRS¡Îö—E‘õLØ "Á ù{ðü”)Æ óŠë£ïãP‰×éÁ9Œä²?d„vQæÀõcc¾e/— ö{Ì«s¡“Rz*aéE 5bÔ†ªŠL³öjJ ³HÍf•¤Ï³MæJ}/dÿ;Ý™X[w'ñh§o§íŽÅ:çÜFŒÔŸ¯«nóÌÍ´<‡µ9Ã9ð;LôhcNy ˜þo´ô#Cè•ô˜ÉCz±ÂK Ú×Ó%y#`˜ëp)„øéèÒC¬¥ã#C #-#CƒØDS¬°Ñ`ªÏàê9¨Êg·«ïëÏéOÍ*¿LÆF˜”9 e#-¡kYȨ Ü|cÁ#~wNñx¢.çO]RØ>LùvºVœ3M[c×\ïyÂs‘4 3L#`$p‡°”{"°L>ê¤ ? #ê8†ÕD(@!"|¹(41  /ºCñØuIaüüx8á ñ‘IGì5˜/ã÷–Î 0ž Èo×i ° ûC‹á2”3ñYD.¸5†ˆg†S"¥FÓ°1¨ô_A“$æºû¶ýÀ>M=G¿xvSaª¯h~xð)%z}áb Ü'•7{j+S÷g÷¦†˜è~y(LmŠBhÒŸ«¬$–,–ÍlF§|:2Ùó£]!ÙÆ…†á©Â#-{Ó3”ÈÇ 59ÇÆr=CªýÕ€ñh((#`C ƒ0( ¤NÄ|fƒ«@F#ëžú¯]Ž£öyt9ÖÄŽF‘N°"MØBuIÉˬ9%Éek€Ÿ$$y›=4U#-wÑQæ&M3I4Ð p@Ê#-ÿ‘è8àJÒ Ò ‹"Ðœ\cêpêš½W„DBÿT®ð>î¼6ÆÃ8V˜‚2›V6mƒÁ¢Is7¨-j²V Pm:#-ÜEIŒu8WQM¢Ìñ¸m´zǯ#CÖ|GÎn•$ZˆT#Ÿ-úYíOÓþ×»ùÂ/ªü†¹6x’ÿí<ÜD±ý3ðaŸÔF3¦×¥¦¨ã«]~ËxðØHäú߼ŕI«,ÄàÎÉ9àü‚Š )‰âÌ‘‰63Å´¿†qJðÈ£aN÷Éâ|HÞ†„ØêmÜÖÅ…&_¹…ÝMu‘/»ºá†”LÍ•&t:SÄ=~£=±ª¶чë‰##`xgÀ/šÝ)…2ªàxo2e“:c9öáÀÁƒ;<#C!‚éIS™ž«_CÔ¿ÉÛ¥/ßøÛz]úœd…SØ„ÿ1óο,½þ«€ùyNfgЉõ®#`˜^{azlä þU_ë@w“>çO«!íœw#-úÉ£ÑùOÙ Æ~måÇ[È°Ü[¸VÃCJîgÂýo9‘ƒlîÛùÔ½"]2#m4ÆôÀÌ‚Æ[º\"4H<— PT· žuéÓEƒÈ^Aëã@Ë&PPÓKÙœÜÕ$$>öô¬ÐS5mTFçÌ\Œ³ßA¹¨oD™YH>#C‹.#CVù„€ÒctzSTB„hŠ’¦‹E4FÎTmwœ.NÎsspá   NBÒV˜ÈÄÙ]#CS#CLû§‡²˜þ‘ÏOU2¦"œÄd3/ÄœPz²:#CœýУÞVêS)]òà© á6SQÔ ¤d"´—V*P¿bÖfQÕÑ¡‘ql^{ˆâyý_WÒkÒ,óŒ&fâ-I#CÏ´©È¼¦K7IpµˆËH:š´¡·Ž±$ÏÖ6é¦=!ƒ)mH&&îRP4œ?¹½#C`Û¢ âßGgÉÌ8ô¥óTp—YYj´}J¸fÖ©ië#Öhøj,aÏ'ãeHo¦¦!;f‡“U’Ä ’\3Â&Ò6[a¼P‘C[Õ0c{)AZöÐHÃ+¶„’S*£hyXäud1“ ƒezlf()ŌٴH54>911Iš2`“»¯0ð‘Çh7”!ZwzºY£¹VX´ŽíH¡+CÛÁÚ¡&ábTr"Õƒ#69#ÖNƒ°µ7cr£Gb.Ïv ‘uv¡"9KG 'd÷ÌÒèõHE©܃0Fàd‹meúß\À£HhgÓt¡_P¤OÃÞ‡„ÑäK‚ ø4M5žYY™ƒWÛ.x¦¦µölàÙÔj7L”TžHj<š4ëÌYHÆÄ,Ì•s˜?2lFäAêLsŽûûŽl#C>¼q£‰'K#Cðd! MG^°±á•Å+šË˜Éë+4Ì Œ¾YXÈ”D ÆÊ;¡e˜†‹(Ô=œÊ%xUÀ¶Þýq7ËÔSê=w z“DŒÎ2w8wœÝ§%4ÚÀÀ)HÔ.±cL(ÐAEž#`LôT¬y¹Ó¼ÏK‡Ý€3—]!`Þ ™0„0s,­P¤g®î^>Mƒ¡Ý°CÞ–Bf#`J©ªX •`’¢ hJh"(†IIh˜dd¢b—¦ H°ò·œëï—ÝòˆTÞŠoÓM#Cc’9³œÌƒ·XÕ’)xÌV#-Þ7ˆ•d…qÓ€ò–H3ñÇLso1ŒRÇ­Cd„°åÇp°UÑÀ]TpÒ+ ‘Â1Æ' ŒTMYq”FÌ·FŸ'Tij«sœuT˜,6ž8Þc‘™!¤ÑuŒ¬d7vc1Q¿$Ä4öSdï1ÍÏ7.„*v‰ƒÃŒ&-Syq²4XÆ2-P‡õ2Ôù‡$ó*—qÁò‹´6™£o{š#j4}mtx--ói¥@šhyÙïÇÍ.yïb}û#`ô·Š°Mƒyíy.‹—j ,"i³1óPV›0ÇØØñ×r*ÊÊÆj(X¦jǧ¼Êh#i#CF†§€×æ‚éÚÕÝS;8„±U)ÇH,”íUL‘,o0Ëóê2ŠF*Ãîb~Z¡Ž#CÓ¤@!Ì „nœK#fí¬p='U5ËœòáÄ„¢òTòe¨ŠáÞ-‡Š_.)ç™ 'xAÀŸ¬œd)9‚`ÑA>‹Ès\y°ó†#`’!€¨ 78s–yN­t„4ȳ±ß¶shˆ#`¦ïvæã‰×|û|yßICÊáÄ*ÚŽlHToX=tÁæñŽZÆqå\‰€›sŒ[öݬ0¢b¨ö3#C•Ñk Œ.YöÛ‘çÂ\ÍLèãØÃM–áÌ!M;' uçmš ¹ÑeY¸*6ÑHÛúU«¦:×*b5Ã,Ž»o#C+tÁr#CÙBƒ9úŠµ]:Ôo\VÑH”ÑY6±{žÑ¡Òf{V,{hœðà˜0ç(vXBzJìm<ÝN/ždåG`ðžU#-Lð„8›&.†Ôô•—ÞÅÏ2|Ïnø÷ãµ”mfÞڢƘÛo—ËUÓ!YÓ ²ÁÖ$@mU5¬T3›qN3J#["ÃêBi¥Äa•Å’áë`’ÍÆ߶*¢ÝNÔ)¡TQ±´Ë"ƒÖôüEϦËêSJppª*ÝŸ .ò5%:T<êãïË“17ˆÁÂpB%ç‚` úœˆð” =ÁÄ(JC/ œH| Lô:AAG cO pN!#.âbdSôÄpàO¤ñ1ÌêÈ羪¶ëÎ󕥨jz„§r¬„'¨éËÇÑí+3æ1ßÞú¿k< ‹ITÓΗ FˆŠbË1 ]g·ŽŸ‹‘§ÍEBtÛÏò÷1Pr`±w…r»w†¾ÃÎ{~úöÿV‰°²t|ŒÚJ5îÎUþcc…>&¡Y}†qÆ)4=Ìolü¦zÔß‚º»;kƒs6q+ßÄ 41¥½@úð€4ÃÈkWT9>¨E‘¼–Ö€á{5¼T“D@¡žZùÚÅâŽö×æªd5/{J‡rÆÁû|ËqE*¡I\Æej 5#MH–NÛ‡›zÅsÝ4c™¼6é“zLɆW— ”v&ÍF{3CÖÞÝ%ƒÉÞõ™nÍVåÓiïZMÁêZº¸S8¸Ìk3šß8|ãjrB’(Ûd2Ê,Õ×òUciöç®õm œÁHA^ªºrm›=—XFÇ“{Q`k#`ÃE¯*ÕW»ºhŽ´÷Y«Y»lµ1]ˆ«‘í`šæ²czoAlœ‚!¶ß’Ã0«ó„ž6pÓrkÖôxùyB'6¶±jbÁó-ƒMï®%H™#C’ºiB¦ý~ì3¸1£Ô:²rMIᕪƒÞ‚‘ðã9‚%Óq»†ÈB²"„¯Æî˜=˜Ò„PÑ"oìFYÃO[°¸é¦€y =£ò#-Ћ¦#-c… H S°œú¸ôwÃF Q"j1±·HRX[ýX`ŒL (ܱÑÎÝét¹ÎQ #-(Ä°fœOœœØ¥a’†þHõRÈ·D]Ò„Ç”#C BH#C(4‚tMo¯¡Ux: VB ÞSP#-9Œl#-rhÊÔæN<6äQ¨ÄFJxsÑnÈ‚ŒHˆvZDãÏ\SSß„ÞÅ$I1lé!f§Ë cs›sˆp‡B‡—ä'Ö@Ðr €Ð’˜@ì)É#`ܯ`ì+ˆvª¶áh"ÙW½±Éä=Øc™#-Òƒ¡Ò4¡I3E1Œ®gAPMË0™ƒ™¬*ZÔ:HUÅå® ¹L„KICš3±-RÛ*PŒ0„I'!S’‡AÒ¦Ç9Á"&€(D9*PDDZ^{ÀSEÍ•+0s"11á¨Ü§tR4 J¬õ‘TÒA„ ¢K„© (ð­û•#1€S© ;aO¬€²Àt„>ÅàòâÜg8Ì„k“ Ê¢­"a#Cá4JDÅ0³Wr¡Ž¢nP?n>¿O<¨=“Gf,ã.$6‚‡•¢€¤÷rNÁ“¤Ð”3R…-P$4«3BET}–EUCATJAM0P4UA,…ŠDP'¢4ATT1A#CP-Í0ICBÐ)TµIHÅCJRPƒHÒ0´È±1 IA%DJSQ@”ÈP´#-„†Ø©„Ç`›ÉL«ïU.‹ñ(Â&AS\U°fBÂ9I¤S$Hœ~¨½88y˜›ÜO»N'ûz@éHLÙa¨ˆ¦Çz5~¬b) i¤‰#`h…¨•‰T?èç÷²öðŽ#C4üÝÚïMR¯žÓ‚Ù|ó‡÷fÒs°Ÿ4ñŽA±k Ó>­#C4É:EÚø*€{ad•(‰˜E(õpÊ€pá‘C’@¤A3ONÿ„¬‘Øû¸jЪR™ ƒ- J I£A¤­%1¨„òCM¼‘Q)DÄÄ€DÃP”’q°¡¶›`#Cç !¥(F%hˆBƒ`Á!Nlˆ2ˆÔžA¥ š€ˆª䎕#CÄx¯#-œü¢z9ëÑí#†‰À‚Kw†«õb¬|µÒ·ê¿lÍ~Þykkœ~.•Üdc*ÃIÑL>†9„#`hÍÓB©gï—ÀÊb‰‚èh„0Ç@4îîÜ<û°xI¼äˆ”d$Š&O‰¦“rbšáÔ¹7‡«l Á|Q#`õãY0>8ŸqÇÏ®Þ*ÿõ“ߎ¼•ÄÀ2”ÁYŒZ2¡ÃÏÈW‡ë+ó”ò#C#-û%¤yI¨Ž¯Î$Ú¡sêɶ~=ò÷9%1“#`Ž2C>ƾcò 5TL„_¹©’=½ebìÙ¨9}IŒᯩ4KEdPÃ#`´ûKÀØ2–L4‰w~oÉå¡B(°UÇS%‰¡OM÷Àu@Ot(zúÊvt5"qíÖÓ¤a”»âíaû5·x®—q±ŒgZÕÔ— Ý- `ŽZÐz´lkT᲎ #C0ã [£¯uÈ7!¬rà\—&YFá)~ºLŒÓôözPY¸— ëØš0Lò×6l‘·ì‘iÏ–#CTÕGT²MÒ¨¢Ô%!¾ô¡®bÐã:3.ief00{ÔrigMQ’¼­ÊÏV(ì´ŒCññK¡Œc¡š†îWÔ»B`›FL‡Á4a“·+¦¨u#C5æU@ÐæÕ´³É¦6:JªbW•R÷Ô¯V nÀć$åržÕ.âù9<ßNú+N¸ œôÍ”ÅX°®nÁî6ƒñ¨ì¤¡õ! ϲÚ#-¨þvpCèN¡¢€Ä¡á#÷ÈE4S@T¡#- KH(}4ˆæÉú¥^Be1"±y'ä•;*ö4ÄQê#C#C D(¼4(Ї/ s‹ö?×ûиe‡Ñ¨¢Ñ‹ _Oœ#-äìzñÜšx†„-*‘J(gäíÑ8° sÁviý£ø8’ÄÓ3QV#`X{y’<<9I¸˜ ìwÌy—÷ 4/¢ðÉ˨y§A¹˜ŠSÔ‡*ï\úó Æ;³G I<õ44¸äó™ÜμðÓë8­wƒ…H”"@dÁ¼ç-#`=…ÐWviC‘:l®Ò!Uäþî_E ß8y`Ší³ôqAc†Ó#‹atÁû&lÝÆ#`y64‡«Àða;pƒŠ'=<“„‡¯$ ‰*>¤Ü«Q*L„@Ò4Ð¥4”M"‰BRQŽ°„åb¦ŸÚlÞâŒèk{8ó‘“\@Þ³‰5sA»„0á†À%B8ÖäÝŒc±’㬶á#Ct‰ÌêNÄ?½êðTâ%AòÒŠšª&ˆ¶pi)*³E- úvèä¬óšŸ§ðì~Ô(ñ¯bšyüë£å \Âmá6ç#C[©TYQð/.¤U<pÇ\óÔ•‡ûB"²[#CñíÕ×0ãe¸¶; 7Á†ù©?E:¼Cƒ”åB`»sØ×Tª¥`–ØhEiÒ"ŒË‘¤ëV®ù¤Hæ:uû¡qµ²¶’O衊ϬÐüü*’Á¦M”P1vE*¡Ôƒ<`j¨ „#-xþOçU¯„ƒBx'à蚎ÿO ?Ë#C"¯ %’NÏÔûa¸eS›ÕÐÓçúžÁÏ9ñûo¹ï>@é/휚¹«Ì6~T⯫ªý™ˆÁ4™ $Àn\„¸—YV':œÂ9q{2£h¬î ÖqùÃÉG_ªŒ¼ESéä4[‰C@AEXã"9‚Å•vV2Ÿ°˜#CpCÔ@…QOÛpMßho%RÑ’b@™#þPãíbžÇ™áüÚ?#C~òðU5;°p!÷Á9.J¤Z8˜#C‡`#-ˆˆ l™€£òDU0i€½Oãæ¸Æ4àm0’ ÊXˆD•$…Lt²¹d’#j ¤VÁ¢)HI›Z­–5‚B”¤£J… bðÆ àlÑ0RhCIÈ1±ÎØ8¤LM.E&#ChÄÿqŽAÒŠ€˜)Ž.†& €€%™¨’#`Zˆ¹*a„"(i(á–¨¨–‚ÖÄ:ªiij*äœålsLrMXÆ#`v¡#CEiÔ‘ÒpŠ#`yÞ‡tÁNM#C 0ÒcsŽ¹±ÅAÈ @IkJ1;`˜b-8F B!BhäŽ$KÍšW…¨·!är#C‹0EBáM+‰8#-RMD„!þÌTE€0’0+ê•X€ÔЈP$ì*÷)öJ§aàØ1(ÁØ€@œa#`D @ WÓÛõðx#-øÀj#-äxAR‚% J¯Òê†!ÐlèŸ](âÀ”›ä ƒBD4˜•B÷£E6TP÷„ {MÕÖjeûVßZAÐ>…D|€÷ ÍC¼ùÿ«‚š†F x\öN‚Ÿ–âYd!@ŽÊû~Óã×#CÇrDôÃÆr#-!f©óÍÏ¢0ñ 14ûÃ?ŸS°üzén!JŸ¦Q¥O¿‘ÔGëßCâ”PDŸÏ‚l³üß—_ïš¾Ÿ¼>äþH»‘Sñ~d5Ev‘H„µ¢(™‘™¥)Jߢ‡±©ˆIGˆhò±öÊ¥CZ@b(h(PZy ä'ºžÉâ #-ÀþÝ…ÂSW;Ư¥ƒ\^ãP{%#`JhBDˆ¨ N„œÏß㾨…(DEA‚PªÒ!J”4´´ˆ"1#-A)H”@ÀÓTH4›-Ò@ni‰öu“휑uçd/tý·9NÜ6ƒZ•Ö‘”Ö˜Û‘’šj ŒŒ2ãA‰CÛ#ã4…𞚘p×0fщ %_VæŒFy°5\%‚4ëÎ9S‹dÊÓ¶;#胰&—¥RØ惉ä8Àù9`8DšÈ=ÉäKm#CØ\sii"Jj ™v„# ™R.Ή¸H€«$ ç'²è¦2w q'ê “{ÝØÜÅ.”ÚO€~.ÓUÜ„ß1JEÉ2‚æ‘ðÅ2ÚÅR…2Šl†D‡rÝt„‰I„{ÃD&ü üÚ$i¸\À¸ZØÄ Ñïd*‚$¡ìvAFN#€Ó€û7ÎW ^ˆÄ “÷àÄȤ„üûDÑMwi½ŽTÿFâ â‰Á4O$ŠS¸„*ЬÕÖ!?à#5W-Á‡òSóúì4ÆÓl?‘¤qa;T^ZÊ\£`¹kÇSw]!EÞä#Csr‹b …Øssìÿ2û®&ÁÀî#`’Jt6ƒlrŠNàDï€@‰™ ¦– $¤(’h#`T¢– X "!…$B@T …ª™ž¤åZCŸ@&#`È|¦ „·CiÖ#Ct}rŒB-*‚Ò¢”¢!Ún‰*È2JhJ¦’D‚‚!h$iiˆ¢‚Z’b …˜E#-…žaJP²Ç«U}Ÿ;4Ÿ£¡#-èH¬Ãöã×bíÙS@Pð%G«¢#-(€a T™ ˆŠ`7NíîÄá਩þ¤—M;¹?w¾ˆï&_-±zÉç¦;¥ïyFœNr#CSÝÓSD]§¢£óÿ#-s{lKï>`¢R«s¸:;~c—y¡ e„'Da(wŸEÓ·ã²ÀŸð|C—sÒ30ÁÝ!?¢`4‘!†±é#øàxɼ¦ ˜°÷ÐÄkWIE‘&Æ̉áåÎtÉôj’$Ä#-ÁÓÖçv+Ô$àƃ0”©ti,Û[`Æ,t*fÿÀâ°L®šH`ÌÐì-üÙ˜|OXë€<Æ0 ÌÞõftý.çMÁæÑäÉØ—=%S_ž8ÁÞ–5 ËTPUEyÞœ;úÊ#`DV(a#`2i’©ADýÓá,0Q*Uµ2ÐÉUD@5”ÑPD]…1=ó]f ªAl‘"ºÔciYú¤vhé”50œ­—ã e"Bó Ê“ÎåšY^A$AHCÂÊ„1E>ý#Cx\eb „U‚ •„N6ÔaÌ %Áò»Íë(†Ó¼[u{ÒÈA#-R1ÐÈ`HJ¥ýßAâ—;}£)ó‹™O1”šÐ\sÏXC,úÈâ ðò\„3ˆ—4Z°J1#C¢4uÁ-–„)öêäéo³`¤‚(RHìF’„(ZLõÃ@|¡Ð”‘Õ%#-v#Cƒ|÷Ôæïò„:ÔÄR‚€‰À•¿k—ÅB²M‚Å—m5TZH% }ÙÒ0ßB7Ö3¿\)Ën'ÀmÌÄ!,PÒ!B¥Y.ÀÁŽN·7ùÐsjþÇÚ¸ ·S};ÎmªnœGÖu(¼!~þ#`餥T`Ù¦ï4Í#-‚êCûÙf(ÙØÊo4b‹#-[!#p Ø4ÁÇ@â'(¦#ë 0V#`dþÉ!#-õQBƒ@…®Œ’‰JvH! \1î Ò™U6$A)0z´Ÿ@t-"†5ü„#-ý¦Š£Í;µxG ÷EŒ)Z-† ã þ ';?ŠMRESAH2œ8óΧRCÑ©§úÿ¤š’K†úr€<"Gî“‘TFFøƒ"#`5ªX¨jƒA™)#Ûü+Jž‰Ä™“Hhþ¬kƒm„M)‡³“·^ ¾B‡»¸n™„‡†’[oéÔ4 ÐkGðbù™¥ÍˆÏsSý¯œç!2Ó.³êúƱ˜Ö8’m–ö5p‘(ù{àçDKÙ)RÞ©§—ò"ÆcŠÆ«6ŸZkp\¥qR«õD*"¬²gí˜tñ„¬Ñ!Y¦;šz.ÈaÙŒ‚(ÈQœ©NúŠ¡‚{bŠûpò9š]¡Ã°;¨Cãý«Gø °ÒsΡNCò`pÄtðC°Õݱâ³$Y!îÜ08±jþ†@èɱŸŒ\u©C(g‚@àÐK¸fæíÈÄX†qƒdÕõ)¹+ÀñÀTÇ 8ê›ã~MÑý wûþ)ùI”B H ñ}¸=ðuà ©ƒêເ„}\æˆöæUë«[¨#R4tõ4ÅD,ú¿D–tB?$~È )œöq¢Ž2"­eX›jw%›2Vm£ ü>íO—~›×v4pü“#`9Ú-ðï‰x÷š…ùo…‚j&…üéï¶&.LtM·y%Áš µÎ¹&ÔE N‰xåU;ä3ØÄr7Ž”™•­ùNY‘q¶.=5\þŒ°ò?榃 솕·“ò36X 7ßÏ3SóZSV@®%A4»ÊŠhŪ«Ë-±«•zqʹ‰DßF.o=o@&¥ÎqVrsjc\>&ŠRÙz¿N7tH¶8dåôâiOc3™’2ðfA²…3´6#C~ýb2gJ7ÖP#Q̾[$è)¬‹íjÿ‡Ü£GHo@CA!|‰Îͬ˜`Ôv{7Äh»nNžFÅæ9rQÛ$4‰Ò ó[ÐxgÔ^yÔí.˜ôI¥"y¡¥ä|!ݱ5œÒ©ž¯4çõÑ*Ä|`Ú‰â[8£hº+à¤ZeVN¨›yß$eòôÆÑGé®›4oÛM—õþãê]N#-qSCôÈL ’?ÕŸ²Ê”á´ÇŒ»çäLÒ/rHÇ(÷ÓéÎaÚ×ÍØ é#CE.´¼Þåà3¿ñ ÀFX̉–¬›+ÙycÉØ]½ëNé»ËÊ ±Ñ_Õëåi~`#CxœNÓž÷ZTÜÒÛÏ8l6 ÀàîíNñÈA{HLÚ¢­p-z3“ƒhhK’–(m`,²jõ¹öM#Ca#Cùoˆqñ(ß„ã—5S·]¸,ï½{ô~swÜne §çL}H ásiÎÉ¿Ûœÿ5{ôÂq­yõò= 0¤:#`Õ¦M‡×êõsÜ|äa#ýí2„#-1Èâ¤übJ."cäXsïa˜pÀº“ò ïö½CŠ|NA¼…$AŸ†Ù0˜šFùeIxïG^ýdôsÄÆÜÅ2f(n¯¯BµƒVÒ¤œQ°uƒC¯HüûQ#-Çóñ_·Úù(tÒ,à #Ct£¢©I5°û˜Æ’^‹¶ãJÖ#ù,öoÃ’2‚%rÇ$>櫤™þ䵎(ú/µqÆÿ3T“wdgÜ·…>k*nñ»‘ƒÔ£9úò>~½¬ùàEþ™0óâøé4Þut*r—¤Ã“ñOt4Ž”È^ÜF5Ç^Éá÷ܪVÉ$ÖõªÏ«Ò`÷”ÈÑ+‡>YÒýÛ'e8lƒîÑßV@ÐÄxÊéM`<•òC·œ¯Ïèï1“Acm0Ü•±É+ÍñxÍ­ë˜Ýg½úÛ$|¼ò¶VMDø¦; Ò™J¤dnÏy;–mm9ì³³!ƒm“«½ª¹©l~é†l”Žh#`{Ê anÌи•XmFÆqƒuÐëï¥éHeL‘èQffù…4=†STò#[ÒiD›rU¤’#CƒN”( 2EÔxq’Ží!ULQN•l{íÁ4•xÅ‘‡¦6¾Î¹ùò{úÍ5ðá m€_%™ÜhF%Îú¾ŽéwIdz¥qãð:ჹš#J©2IŽð”Â¥ÄS&¢ÿ9¸ ÔŽ°c\ÔgÌY²RU'¿§MŽ lƒ‚~ŽÐEAzùëåÖÌYu«l¥²¶¨Vs”ì8úf{ªIþ&@7Øܨ ×Òؽæí¬˜Ø4”æñ(ù^(‘Ü®^·¨`€ñŠ„"þ"#-˜@üAw I!70Ä1D¥ :{—ß²Uø+€ëöjaÈ5#ýÖQÉÁ^X?-B‚(b©DR@ ¡h)#-¡€Gß!‘D]9aYê9o#C(}k*@:KÁìa6ÓßẞÉB¾ë3Úž{ëÌM‡3C9š$ÏcÉ5#-{ßT'œ¹4áÞhx3>àœï.Y ,Òþ寤ûL4n€×ì>b»ÔJèx&»Ä-²x¢×…ÔäztÓ‰1¹"#-è}¯ÍWm_4pð?7Ó±ãìïü…üÇ¡ì5#cwý^:Z|8CÍO˜(w“Û' ((kÆ’BÕ6#`%ÒˆåÌ2ƒ#Ç©:yè3ÀÎ^Ã,ƒª3–-UŸ·7­0"*ßGôž*‡ìÏh%1'èÙý̘ýá(9€À1$R,}L& øúV…9àéå(ˆªª¤dªEYÓSßü÷øvJòîðô›ò×Ê/û¡"òSc'y ¼ú>LYúlÿ#C‡ÜñðÒýüB$~~©ÒS‰“±óô"}Bžm¸FbPM;ˆ6k!ùä…BH20!T~ÄŠh¡„™8µƒ["„‡‡ÖÜzÖ6I ÛPHÁç#-²ÊJ»Ìâ„1¢…¶xØ'¬r#-Á(»Œ\½bʧɇ16i=µC‰Œ‚Ÿ†Ì±Y,Ý#CÆpÚS'ÛDžÝêCöí?ÝTã]PÁ€Ã=¼—ÊP…?OnR¨úûX{'ù¹{,4ÈP|6¿Í‚ÒèÊkf±¾¾Îò’#­½àÌVûO¨ Jü»’‰¹)a‘SׯîÌ­¸s¢\õÉ´K#CÊ©^Y$8MX!ßÒVB÷GD|LÃmí·¤Ð10è/¶GÛÙšÒR•õ<ÁÀžˆ_wímü•ÄÆ‘­Â.#Cß'¤C,PhÀv.¬ZhÆÅ9´Z0Úd#CjÉú¾K;q±± &K¼Ð5P~Q€`#``Mùû‰÷;¾Ÿ´¬0çUÜûñ¥QœáŸ§ç]kùŒPÁ“ÔêŒÊÉwE'Z˜Ä6¶†ÊøÁm¬#Côä5ëìÇ †"h/#-ë€iV`⧷o»,Hƒ§YÂUê“ìa I$V#`!ÄGmRÈéÀ#`:v G2d¡B;Óù¸Š?¥Óžk[²ÓpCÆ`Œ´m¢vbŠþ=ÓŠ>zPc¨qŠD¿eh,£¿Æz Œ5ðB'>61A5‘¼ûkµq½ä?;¬Ö•4›Ì1„q\«{ždû„‚ M„/»AÛD.žya‰éègAácá"mAóBiH\“øa?œæx;g#ýLMB†#C#Cgoå’]5rhäI#ð’=">ÈöþÛÓ™‹{dL4æòkL1æ¥qHcßðZ[xÊõV´³†"92Á`â`@ÍRŒÔ÷MÕ&áLq}‹* éàc4òÕYu¼RÓÒIòî˜voú³þY¤hhˆ$Û·lÀ‚™%JHÿµ $#`ZË`ªc$£+M9ѬHp!Ø3¶Ù5F+b,CàÇVŠâ°»¤ÒÂ2•+C,AŒ"("©=1ß–Éï.úDÒ›XY¥Þ(cczAH2<‹tp±:ЉVM‘ká5¨‚ƒÂn‹t!œ˜l†W(¬4óƒ[¤ÇÔÐMDAñæF¸Cà!6ß>f¨åfË“‹çu¦ÚTˆÛ:`púg ê[PçêÞƒf¨ª=mò×! ¦šj©†ŽÈê"B’"¦¡¤’b(){h‰“î1°É’Ó:Ë”"oðŒÌM&]+b”'°{ƒ¤)¢v_”w›¤Ð”$¾æ*9Áï )@±°ðÜ8»˜¹€ªäÝŽqX¹°Lt€] Q’Ýl놵ÌUµcZ%æpÒDgf2É¢±²ì`®»ç®ô¼+ð-$ÓHMG m¹‰V‹DúÞî fsAåËÉî]QALF¨4v]9jS²ó›™ÆöUç ˆé#`T¼Àó›BB¼9µBZGICEϳ9!8E Eê8qÈ5T<0¸ã¸fΪø÷CÝ?ŒŸ(ø½øšäˆÒú¹ñg±ª ‡àæ8/*B‘;c¹i'ÍåÆó&¨@õH!õ9¤–…""H$ä¨V9¨ä×.(Lþ>L4ADE=Ù‚ ¢€¢"Š(¨bˆ[m!D5!$QEÎ&©‚˜«ëš¡‚B’`øÕG~[€véÇTÄLvÌ4IÛréÍß#`ìêä`õ%°$“D„œB3:ëa¯@ì×I‰»ôG¢ûXõæR0ÚhኅM3Wççõ$„Ž!°,€†ë\a‡²(5ÄTÀQ¬Lnàqäo[’!ÂhzN.B|­-§KTùï'©|¢*Z. ž½o R¨¡ì-—C2w™’1háŽ<`-Ä8cF÷d]æ(¦üµlL‘4Fm«Ti ]R‘\r›.âÇås©xma†‘;àïsÉà` Èh„èg1§ÍÓo0aT*•A“€6ä>ݘ¢E‚š#-ò=XÔoÚ÷¡ûz$áœi•È)›c"&e¡#-ÍT_JšŒ‡Ë?Fá/ N9PȆ2EC1ó:pðÉ{âÁèqƒ€_–#`#`TqÀô…À@ê¶Nžjk¤¬ !ÃÞ3Àé#C)He-8²lDËnæÆ/|‚ৠÕÑùßÖš*ñSû¿¸þ4~1b6#CåmS¶9ùþnš¦%7pbçƢ颶6dO¥€ËOÈê)uä=€½ P„MAH1(Ñ@R©@%-DHÂÀ$R…#-P- 0C2ƒJC,ÒˆR…JT²A2#-P=½ˆh©ýdjš! JÄDEHl*¿¬N èh#ýrcéßý]#䧖)t8Ùæðïlê@žjÈ «B*€ÐD¢`^7³àjür‰¾É¬#`d×`>ŸÝ¿)Ö B–ƒ@âh hÛ6Æ‚€` B|àR¥M*)Š•1"SqÀå4¾:xýF¬n7Ïæ´{7?Ók•oìÎáŽÏ{ £»/âé…vºÑ†<ÿ]fp6#-î@î$He(ˆJ)™‚dB!Dª(¥@¡"A–db@ \€‰(=vuéËmï¼NlKk‰’§ÐÂî=—#Cÿ#Cõ;.–äc)PL&öUIm‰ªh‘€JCùd4ÿ]1Š(åÇ)·æ8cP–Q¹Ñ‚X1¹”m`ö ÚÐ9RQ„Ö@°xˆÛvêxÔÄÊèFò«ÄÇ“’ž›œ¼ häòåD(bæá‡/¥#¼Á˸»Üv]wŽ5O’ºV‚cr;'HzÂy ‘™{Ìñ “¹Û=¢#çg—J8MMc”hä.ܛ͎ÀujNéhÛ×4‘4R<  š9ˆklTEÀ…R=»$‡‡é¨nrrŻÞ¼4•k#C)̉ØJ›A,QŸãj¢„k ¦•Aõ¿E.T†Œ)è‘ s™§2#CRt‡õMxÁ£u4q¥ú_ªy%#CÆ?’Jƶ"-¬#Ø”HÅ&Ú<Å¡ZÞ{©œÃ•¨ý$aÐI²29èjÛ¹_!#f’#CÚJ$[³Èµ¼ë§f'õª&‰‚i$%=¨ì´426Û‚ªß”ýO~ž&*–1ëLL M-üÇ<ÁÉkÀì|³XöjOI €q~rçÄ>5"þtJ*’ji(ªN€b(©Y‚ ¡¢‚“@ïóáÚš¯ž¢)€²}Œý§ ¿Çüzp“BZü€²KÝ…zêîœKü¸ÁÄEž#>yíU¤P06S±#1󞎎§sÆ){^+è‘GÑibd,ÔÌd˜Â4älHï‘ãhth[¨Ñ¡´\dD¹+I£5L—Vä4fˆ‚$Ð{<4r ôg>'ûuîßÀÿ?Ÿê¿O몸‚¨lÅUM. íËvÛW¸æGca†0Gך]¶#`ϧ¤Ä÷z§ÎdgÌÀÚXªu`~§Þt¯„ëín¤ÏƒQ/œ&û/uKm.”õT©}ìpT?uÙLé’ÝëД÷Íí{ð«uÜ#-©Õ$ýc Í|«LQ…‡–!Hóû$bµÁú•ý07¡F‹h³[I ¶„¡²%#`±—&ý¿Äž#ð¸Tå!Ð>Q¨‡n¹gv&äÔ Š§%C’¬ ™Æ–ø§Ÿ.Y¦’šÊä }_ 2SÎCVD1û,•Ÿ #mߪƒuŽÆÑ#šŠß&ĦT(2DN<°ŽÎå@ï´„—§†$eH'¿Ñ÷–»ž¥aÅJÜÄõ¢GMĪ$…%=}ë’Ç%o)'¤Ê Mz8„%¾ø*ù쾯¯Ö·vÐ{€yË4ý¼<$>¹>aʈšmŸ¿GqÑã™lënÎ¥¥Ñ—áS®^ðOiNÊé#`~dº#-¡"R¦š#`iƒZø†æFtö¯ðñþêá[TEæSM¡4F˨¨õý¬œ„;|"´®‡¸€4Ž‡ ˆCò79ÁÒ¯¢S•)ÙZ–´šSšT4& X©])•ˆtkBßæ|#-¡ní ¹ïƒ¢ƒ¯ùÄ°}_¬ÀXÒ¤"†~9ä%Õ€O8úÂð6:+ï¹ë¨n^\sIÔ[p¸Æ=O¶³é¯œø‰¼ €°08+C0A¤”‘C üÐ }W®DXóØB¨ê‹Ê1E§a’úA8h4`8)B톌—šê’b'.\.' ¼ƒ}÷Æ&Ú#CF6j×!IÄ)ùô%Dô¢fÇÎÀˆs¦`aºE‡ãå¢á[A|Òh! ÍR´€qUû0+zÁËGcK¡k#¢?ÁŽÎƒÎ‡Þ¾ßöŠ{”`?gW0h4*Z’©(°U4–ÃQÈ3ÊÉ6ÎæÁsFÎüyÀì!ºXÑ[NI,msˆµ’¢©;?×æëÝG&ñœÉ§…`³£š)0Ó$OF0BT10MDDÓ Ey°LSLÑ0pД ¥ (TÉ:Ñ¢Š˜è<^ Œª™TKù¥ýÃRKÊÓ_Ȇï°¯,ѧ%“&é˜ÿfkóq<³–U• à³2˜ï½”nªÞ#ÇàcMfÖõ#C51øüûAL’b©ù©ü߻ԓw9œB—í[IŠá/~0¬hÏ¥§a3A&'•:|¾ýÚò¤œ»ì>)U–UraÒ!øÜÏ^vo¦}-ÈO§vÜÜbÌÊk²ßâÂ/ŽôYNÏGxí¨zv7TTk4#`ßL dïòË=i÷SnâÕMe链k/²ãCå3e_8l>[)ÆÙv²*ˆ{Qàß-½ºŽ.ïñÛ–®íÐ:‡Ïµx®ç®7æütKIžË>~P%!f0vÛ¸l²þ-Fý·¦9[ܦ›\_#˜¶\›¹º7ùâëC¤z_-ØÈ’jªŒ (åÍU•m ZosÑTNË™€«kœá2dÙg2=èmŸ8©ô£Ë®I„ASéa%ç å¾”Uß•zË9êœð<+ôìÜ-KýâÙâlšùvÚõ[‡qFpnŽ°`€w‰µJS$æ(q߸¦³]Y_FŒ":.•¼¾\ä;A7½è‹ºÂtpÁŽDÛ³yЖ5J͹ÞqÄÛâ;T#CËšúótƒ{Ú‹&šÌ¶œf_nf²5Æñ×ã{+&ªoMë¼wrR¹XžµCcƒ²ÎðCœÐM,0•^>1ERÖ§js”cÕâ(ݯOM¦KgÒ&Tø¹Í:ÙZÀB¼!ò«©Œ%¼s’åÏ„>p&ìðݹsy.1/¼I-]ó…PnŽçŒ*camÍʸë³ÏQåo^!A¥´]¡‘‰ƒ%Ø<üŠ&÷d:OµÆo$Ýãp‘“FIgyM²äèóWX¬¢”ºÆ¥¢]SÇžº·“{6T˜‡ìå@üÉçA ZddÀ䨓#`Ë’¦mÞ0Lòîê5èõ"ÒwÕææîJ½ Ëì„!Ž$Ê#CB ÌA옕¢p¡Pö1 ]îC”; ç–g_JdyU¶wKPûwŒeÑNÍ{@Jî”&‹†ˆB17Òç.ñဠXw ¨LYǃè9à[flí9}èç¢Èç­àZ“ŠjâkmÖ·}°#tJÚñ-1‡‰!:m’[¬t¶èƒ3s¯E;RPõË⯥l“0·†l)P¦_r£„Û"ÚM囯V´Š@iˆ'¤ÈC»´ÞðäÚªi$SvéÈ´9”BgVa¯,÷Ñ=Ûm©¶D:ò·Œ>üJŽ=w©dj#C§x;I¹Þ UœT ŒRR“Ñܽ«w)߯ÀçVpÓÜí ºk]«7¾‡Va8¡Å1e3m{2i&ýì#eR5Å=2zzSLÄ5*JÃÓ´#`ëËBœwof§J***P”º·t'b]v)ì3"³·ÆÝÌž·KVu‡ff¼¹º0™ëœ“yRvÂ/uíM&Ô6sÕvËú5F#CöÁÍö®m”'‰vŒ¡Õm°må€õɧÉ£NÎp­¸Äʇ¸wpgÓ•ãz×zn݌Ǔ˜u¸ÊDüTÂÔb-é ‰t¶‡áç¬ë5LøO+`§HvN`A 5¾7zélù³ŠÑ”Y˜—g’ßÂyæ·O]²Ñb‚g:|™fy½xâ›xÖŸd¦¥jw‡&Ôw2þQµ¢è[>sPÔŠ«îQŒLÉŽyû—>Ø5ª×)_r$©ä“SÍÉ‚mà34GŽ§©³„û,Å’»ãƱå­,ÉŽ¨‘ ?3¡S¦]3°ë‹IÎ(YÇ#ÛhÙHy‹²­Ï-Ï:»í‚}ò ~Sα•.ÉåÞ÷⮾í`[aØÇ*ørWµzq†óìw÷UP°«]<>É|MnÓ]±30ä ó´«ä_’˜†œ2àCD¶¦Â·¿Ô@è‚û0©RëÖÏ`’bÛšL¦"蘮p«·=·<6úÙPÚ²ÑÎãl¡ßiïÌu¸,—ïÌÆWÃTW£X0è«‚k{Ý…„ðÑö–½²˜«ä¯¹*û¨þÂN›qõ³ãd^ØÅæbQòM"§O´Ánþˆ¼šñ·ONÌâˆUßÉNWͤŽLmËÛR­âöÞùªÜÑž®Yçn§0µ£™•ÚÞ6ûˆqygÈc¯&¼(£É…= „ö½GÇ+Ð}¢˜žìß©#Cï:ú‡_­7( ˆ‘£:ñÒÞg7!ÍÃAŒkÜÓW ³¯.ò>Ñ48ìÔÉ'ÄÌ$/ª…ª¶§àOɶäod1;y@ë, u¥ï‡Ó69Ëe]/<YD5‡Q”QYþüéÄY®þ»E7¢ôéÌéï :CéΧÓÈ#C‹0äƲ Ì#`)#C,—>*¤óc=æ|¾˜ê»ç@ Q,lQÎo#-DVÎœ²[mU5ÛWãQ±Öa3dáö­/ÇNíâø a¹÷Õ¤}J\3XéÝïµ<Ã6²”žïöùËñOp_S³—ŸÅÎ'ŸNû†¦Ø(#.­Ã#`ü8Þ=\'døÙõ‹3ˆ‹"# Ó²€óC¤°iç>x‘¶NŒ§°w‹§DÇ×G¥Á;»1ç¹Î?)¯AWH]Ô=Hîèv#`5:ë#-Ñ ‰ *×Ó´°;-Àø®¨Y#CØÖMÞ”øLÞíÞüb3x±?ÓlŠ&¨aÎÅ#`*f¦¼ðp8X«¤w)nÎk¡²{»UazñJÞ2—Žþ‰#C‡Ðô8¢KØÒ³J'jQÑw@´‚\m¸Šz·P|t‡)Æ1G_yÌ#-fÜ'a ­¡˜0pxçf©~Ê”ÉÂðGlwÊ7ïPš‰/{=âªá‡O?LM!DƒÆM xôàéN³Zñ#CAb|^ôêÎ)©:ª`Àh–Á.CYßaAˆ=¥C)ä¶gÚšÀ=ø*‰ !ÑÀkb1#-#C4$!ƒ h>G²yÒ[ íÞ¹ytôë¯=ÙfÔ,¥Ï¡0âb ’ ù&½¦tè§sO£hxZphÚÙ:¤™#l#-ˆ2Nç5ß=hé´0Y Úi¶!&MM$’V*NFf78W²4~û«ÆÇ®¨ô²Pª‘C˜ÝÕúj_ lcGŠ²˜Ù$“¤}uíÂ7«åL>:ŒØÎB™:D‚qÆôÄUñò“Žý»îq**&s‚ÉÆls>óî­nìpªp²‚,÷½×y²!ý–{]Ž‹¯™”Ý࢖%JÏ5kHµZƒåí·Ñn#`¢b!09º"ØÝZ¥fÍKžÄÅP¡Û™âxµîÁÂÇ3*ZRtòìÈ~Ý£š‡¡Ê+V!Þfî¨D×qÎs62+u™§¶„ÈÛMOˆä¢Îk¤«LUõz#¥DC§sAÛõYÎ.,)ZGr¼ã?¶DBŠc|éT÷LûÌ4§ë6@â;’Þ‡wEÙ“‡}nF¡ÐêÓfTqUÈᦾÐ[ïðÑfá…³s´߈7îÈ#-oHÆ<{o~{>XÊS/S•g«¹*·N£Î™ý±â #©3ày¤Î¹ƒ3Þñ¢„= @Pì°ã¤(j’ªªbê(ãÙÕŽ«X“"TÍ/6™a†èÄÆýt3‚¸Þ5âÀ²Åb,â÷êi<&aø#-‰ŸÔFÈÞ¸nj"·¨[Z6©U£q¯3".=žƒÝí´Ðƒ¨ÊŒinjˆ[h5`‘X(tØ1ïwZh’¡KzƶÂõFYÐñÏ$5\‹${q|§ g°Rï˜2iq$7†e5Uiì!½ ~få•“B.pïTŸ#`©òcÇFw8?Ißo¸çPÔÃœÂ!#`$HXÎ?lÀT#CDcÁ<ºµÔyp#CQã!¨¶N]ˆæŒBc:hª˜bóaô¸x 'Þ‘-»n@ÍçF²ƒ& ónú{¶à‘îLKŠ äjÅ©rŽ<+¯j¡V¶HÌ DhïÐm输ìkhóÒúdcR6sÐø)æ H8#7#ÉO©ˆ ft²]kP"¬Ö@çäÄD»$îè\Ja‹s¶.Z¾!ÞxÇ%ܹ¡èF¼+¥‚Æ$ÁpXN8,E³+”=¢´²Ñ ]EœY°/ø®ðQB4PúäA#-„&.Û¸÷Œ£C“þ6y¦b&âaˆl«øáüÒ?@þo·¯§ŸkçO„Ä’8Ì’K(´æè_@³—ìµ5Xƒ4…0î+ë¹#`AN]ÇÀõ7Ú‰üØí¨Î\áRðÇôxרr·™tvÞÃ{ÆÄ>„Š³ç •þ¦þÓÌã ñ#`§·íÂó“ø®õÊsÛK=_ã×$"âð0áÆ3L¤­ £W¯s»Ù±–ÖI¼º7¦ÍÊæíXì£N'˜¶zFˆóÎz=*ðÃO7%w#r“ÓÙØÔd¢SHc¸7½€(G²*(G á%jt8 C—¼Ÿ!—½Ç˜Ðxñ¯éƒêNJšP>ÛÉ@dqÒ©BŽ‘(Ð# ZO¬\4Å@2|w‰ò½„>UÐœñàÇàD¹à¼âpŽÌƒüÒ¬x: ¸}5bc—ÄÑAO}AâSQ5EÍ9M!TK@²Š§ñÊ”‚”*&H)B(NJ+#3"O×dãüzlÆ£ZulkCbçrò"Š(†5dÄlèî(Å q;¨ìÜáÊ›…cœwòS×bƒl²gzTêOLŒ3h «Ò5)Š,AŠ4ê1€ýùç6H<ÎIZ^­5]çyñ»ÌƒHÛÿ‡\e33z橆ֈZZ)tãgM»ÄäPÁ\ï¯bÁ´†ÝN#`Ž0ËYhu _2§é媋Ìpò>“s<ÎH’#C'•Rð|îZ#C¶„¶ÕJ9Å#^»Î&ƒâ1\Ú ‰†"SSq$lš¶ÖÆÕ>ãĤì˜NÊ1D8OWëÀØ ÀAIæ5T—s—#<Λ Â0 ¸GŒzÖ<¼7hÍB°‡z{ãª+Ñ‘ò#`$H¸G†Iš&$刀–8DAc¼âŒ!Α ˆ|•Ü¼ã¾\9r¢ ¦»¸KÈ/X\C¨tœ­®ðÉÙ±ž–‚¶†ÂFÚ{ëš×âòæ#-Áˆ"Š‹ÃáÌ3¹‘:”_Ý?Ìëlé]©ŒsP8)ew‚q˜‘Óœ‰4ÆrgÏ&sËÁ¢õÐçNæ`ˆ!†Iw_\àÛÁÈIÚÖ ±pÑ(Æ¡»Ž˜pS`¼ž„ã)Èà7pf.–d“9'CÅ:­J.cûx¼=‡h#o¡ ¸ÿ|ÚU&Ó#`˜u«ï{F"¤§85ETÅ£5h‘¸À$‘¶œ+UØwäºÐ ßiÖ¬°Új(%œQ1%!07JHÝ…#C§#l,´SbŠ{`¹“‡j‚™wp¨#Š4ôÈ41ŒjÖflsq7qÓµU¬Œ[sµöüg½qHhkDúŒ%ép(ûä5^G‡#C­E‹J\úùaëÜžsC\ iÇc…T7l=Õ¶0í¹Ìð.Æ’y±ArjX=Ç!ŽbŠ‚#1ÆÉÓ™š"ˆ%åJ»È;%\¼ç5QÂÇX1"‹@@¨m¾ºŒjên¨áı‘•'”Š¶ ¤ŽAHy»Rïóà6"ã./GSQ8ÁõîèvXÓnH({|v.|32Œ4Äï'ÚþÇCæù‘>ÉåùlÌŠ‚’ˆ’¹“Š¨Á4.]Ñ,9¤ú¢³E=Ù껵"' ·Oš‚‘vÀDJIDdL¡HhN…¥¨–w÷!+A˜©}É”§h…Ýl6n£t®ÆýÓwÎyУÈ.¸F“\" ̎Ÿ Û¬•ÄYa,•² U‘ŽØRrJ_™xôWôô݆$|ÿV!=m'ϤºÂ‡0 )Šè`càêCâÄ6 *#`'F%UTC¶«Ón„Ä?i•_4™v7(#C꘧à¨(]exE»­s“æ5Ó}Ç=¸U}rùô_Piêã*}ž•õ#§Ú"Ø#-·óõ6÷`f%D”ÇrZM`dè®!#`öšŒ¢¼´›_›ÏÉÃ#`1æ,ñ«67‘˜^¹Ÿ[|˜C ±Ýв{×r] ?¥?çÀ1Šƒ ³Vtcg!gÁü=&õ&ee’ÊYbm™U+Œ%Êô÷Îos·8ƒFCQþµ#-¨Š”Èʪڊ5¿î ž¶"ß¹’w¡KðY=·¡€ô”Ÿ5Á÷”ŽyŸ.º è{ÃŒr¨ç˜&õ®uÂb1ÌfÈ8ÛëRXÂ! §þÜàBô=þ™äŒ:Èv]ykÂò¯n9I™„CQCãV‡Ñt(]P * aacf6‘în(m¿ €¢•¢ ©É¡i¢Ž’ŽC©¨q.ÙÜœç8›…mÙx@͘BA‚JA‰­°ð…4…ˆ5$@ºÄÄ"s.&#`#-æhÓ"©ØÄA‹#-ÆΆ€¡ ÖG`ËùýG¾a¡~<ÿÉ+I3#-Mè<‰É%=¼=*Þ`Ï£Kúªß¹ßÊìÜEX¥! 13PMC5ADE€0‘#`Ј̣BPSBUUT’ÒAMST„$ÔJ´«5$°´-P‘Pˆ“ÊDŒCK$K!…4ÔÁE5DÅ´T’DÐRAHE$TL4•I)E-AÑ!TU0#-A D²DÐSÊ H Ò~ÇsÆNÃÜvi®?F‡Ñ$}ƒÄúÓÖ‡ÚýÆÂ|Oª'ÛìNªðBìqöhiÒn¾~ó&û{;»±?MõŠ·fáŒ[æ9¿f#`k¶D‚Íà%û»¦œÀâtE׉‚ ~%C$ùÉ’}Q’¡Å/¨í3ˆt#`< ™PË‹¨ÈŸE˜‚™üs¹CÔ?t*¥„…fņã7²#w8@å"_¯GÖfÉ IÔéÜæf€¥Ž}ÜtsªÐôÖe½g×°/¬Iûg³éÅ+Åá‚Sð#`‰ŸžÒÉ%3i´Û$#`…zÖˆAUÀßîƒí Î7TQC)ª»?zdu+Q3ƸùógÏFÄò£µ—KÕ˜`²/èee‹6þ?‘б¨BF QJb$È3‡xP]·É—uÇĘb""…¡q‚]àBòoQ™XÄK›…C¥À¨Aï6’Þ>Sðð÷ü~0D.ÇÏ §½=ºÓË2s(uù$~=6ï©ë³R±D„m0 þÖpçPPÒL)0E $HÄ£øl,dÚˆ)†%¦ˆ@˜‚"Ô˜ÃØ•¶LT&5Б*PS#JT…4LAM4P“4Ð’E1!IAM¶’ŒŠ Z)¿1œŒ)0*iD° ?¨0rBš£ÛƒË¨@È(†#`bh*‰#`¨)("GwêN#`ººîUFÔÉU•U5(UKCIÄ/ž$+A,€„h nÙ³V\_¦Ñëk͉¡ä<·9®A±¶#C9öÉæ©~Ññ^o« |Cim˜ŠÓhN^ÀæB&h“@ê&.SÑçKú08Ùø~kßßc}8‘ŠULoæHÏVÏG‘¤õ9Ø…e³rãÒ™kÑ¡¬ÈÞ¤Y‘-8á˜Ú6†a-Ìc^XoL“L$¦“ĈB‚Jñ]A‡»[le5aUÞ\`lxQ»J¡Æ¶ocÔh„ÁãÂá+ŒÜWýÊ´Q¿XFi1íäçÎ<äó†,Fäï0ëZºpz£¤Ʊ/8o0ÌüJ<ƒÈV€m Û.#-ÆX±& ¿FÑ\XQÍœUY¶Ã‰¡ªó˜ä”BØÓÛ>‹°IØÄ$KN«gõúõÏ<]cOËÙóóį à°}OŒGªï)/(²¸ãèˆÛ€ÆŒöÑ 0”kôj:êFûú#rrÂ~ÙÉ)Rd¦ÑMs<àž§æ';Xˆ"Û.‡=Dà%…U+ðüÏ—£’ñá#`s¶"­s }ÒoTTØy“=¢€~g›`­>¤ÛD¸xî¦!»²à2Àì‚–¡º»œ6 S…“øs>mù}U«ŽY&9¹ª€½©ÕÁtO²óÎêÝý×ÀN‹½Ñ0 ÁËh©_BÁŽG+$Ñ(ú±òÐñh-fJ€{ÿ"ó $;˜öŒŒ›×ϸIIÑÛŸÝOÕy%Ø&¯àÝâåêþn– ZP=O§=~óÚ ‰¦Ñý¥6 úìàáöT*BŠP”&šd)Sd£Œºe±âîÑË2œs'zñÄ÷ÊÆÆt`·ð¢'ñÀô@}ßMOÍé4A{”Âü+Þsïâu^I¢X/*?5ûP_âú)Z~¬§Ï䋨œŽÙw5êžï<|¿b;!PÐZ­B¸Ïf#`yÕ5HužK#CB™††ÜÛ¾”„7Õ`-\4¡ÏSîvGo•Q`—#C´ÎPÃÉRÛ^_>ˆË>DdèKŒ~}6"(ŒƒTe»QG!j" CþÔ*dŒhM/Ê@î×ÉsÕìOŠ§—Žªš0Ç 1ËÉßôy@4÷Ãç `‘_Õ5#ãìÇi¾„ô¦žPÖ#`ö*<›øŸ]BCƒÇ}õÖxïð©UÜ“¾ù©˜¢Yb¢&f•iDF0;†Tž“]{‹èé'ý=²öîn·3µ~¾ºˆz\zˆÉÀaü¤£PÄìúî#C–;MÍÇÉ+ᕃ•ßÌ3Ö"uHé ?µZ7ó¬9w©¦ ì@æÃð#ˆª8‡´”}²ž€ ‰Ç“Áýÿ2¿ÃñøªR Ǽ’ €" ± ‡#—J±8Īa“–X "ª~ÏšRð§n)„®ÅïÙp)2p»³ò£¸‡Õ ®òuÕ{£#C#- §ncã)#PЬD•QÀáÏéî5ãÆ ÀpÕÐ~¹w•’ȘðÇ û3¨Tü{Ä#`¤ãœoÜ¿M\²kÜÏ.A¥ø–c®IC8œ =©É¬Ãä¼öÍ`¿›š ˆªh¦“…Á™³‘Ž„¼:}›ýéîõéM#Cöëñ“ ÐÑËØ‹3WPý#`›ÇèÀîîÈ‚DÉñ‹Œ]£éSyœbê^nZåš»,c¬çeʵLOÇ3”©µ™¯ÔípØmm9©‡{Û–ÏXXr^&ã&ÓmdغÃ×ê³fN ‹‰—’)D\2'#-Ê뤜HÞdê÷šéÆ°w!¾Ú•íIÛ0Œ;í‹´Ò”½áÚxæbª8™Z§zŽ"î]ÚlQ$SŽŠ\"\IҸŔÂÍÕÕ"ð=‰,îØ‹ÅÆSO›¶…x}6qcfÜ­éß~üï—ÔJN§™{[ãá°L,FsÄŒ[>u>nr±®53¨lA Ì€b²n2mÅ0në³ïѸâçRÒ&¾paé€V»[ìpû;禮mI“TŠr·C_óñÔ_.;ló¾ØO‚°Ï6Ë™~tá¤^œhQD#Cép9Ƕ Ôí-8QŠ·-Ä:Z˜Ô_¯¦Chç‘‚ñ{•Õ™l"“›4!„D"BŒZiúuÁ¶¼È®œäàÙWdPX˜Š„uˆ¹0,Å0íâ"#-š˜%â .ÊtÆ4ó0‡O;¶- z"”äÌœŽ‘Ü#`Ü9v-#C˜Cøæ{¿}ðe¢eµ,ì8‚zÎÄá3$“¬3»FJ»Pïvì—ðå'bï5!VB´å0™:iª¦CN ~ì7Ók©/s²cQ¯C¨ö/”)¹pÙC H!ó>==E¥R{àî>,Ÿê@m¾ì×°íU`vir5Sã±Y¡1ÿ|ìCwÞíšÙ›qŸœ,Ùšánׂ3ˆH‹Œâ_ij‹0©NvøDl–]½7ÅPg|—nÐj4”©“'0ÚF5å/Ö[xqÔŒà£72†Ú§}Û½ÞC`ÆIÖãF‡FÞ"â¬Äº09ÏMñ»X×SœÄ/‚² Tb¢²=ÕB"‘UŽ¯;çÄ×M³^ŒË'M(Ì-ßgš»¿áñz¹ÞœÍ=êJVw[[Në,0î#R9’S[[Ë`!ÝE¶‹#`±m2îÃf“’^IlîLêb4ÓØÚÎO+PËc\8¸ëˆÄÆaÿHÑØ»q®g^Œ×4w‡œ]ZF‘ Óc¦U|òkwØFÛˆ‘ÜÛ«ž#C÷$ff1‹Vó„e³!åÚH( “sËÍXÖÙÞ¨I(ØÙ111#-´ÙÓ# n#F°†åÓÐò¥Iü=B‚x)CËn‰„òŒoX1³˜ÉCø²!f"ݤ)Ñ ÃB<·WÌé«~–x<¢Be¶ä]TËôéô`šÆ…{Ãó&¢›…„0Zfÿð2Y‚µÎ·FøÚ‘Ä°; ØâMóºÓ>r×´ÀÁ€]-Ý#`!q­o3œqÙ-knÞTlkD‹&ÎÅí¯U#¾cDÖ“µÑ»GügCÈA*ÓNîZ¡–Û&¯%×å½è²º«ß9Ùd;tÉï®ÂLbQ£SëO:;'æ¯f™Z%ÛuÖÚÞKPF³›>Ð;ªájFAEU#C2o1®,¼I¶ M`&”Ö˜Ì<1»{ž¦Ô̹Fu¤AA©)ÇUŽ$ ¬°Èͧ ჩ’D“/cO&E Ö`µ´2囿•õÎZ§—”s³:KihÀ»Ã.T3 •Æ#CYte¹ Æâ2°)‡'‡ Zñgãlé’t:psÍBQ§"(–”ª=Èé.á @Òˆ[(CKªˆwwL3eT]LGŽ+9ZÊÐåmqy9ç{~9wÜ€à0Ñ£‰D"G@Vi¤0åíÞ„§&Tø}˜SZmÁÉœL•F-ëSEK+CÂk~²ˆÂÙ«IÅ•/ ½˜J„ÈðÄÍá]œ¬o~ãYšiV¬A..gZF«þ–šÛÓ37@2‰C“qÌrÝ–u¼~)ßiŽu¼ã[ÐÉçÄ ghßg~.J56ÛffDK*G/uŠÃB!Õ1@žhkL“TÎ7ÒjZ!×BÑç7HòÓ²¶A™¼Ä̘­W¤“Z(Ƴ2‰·tCÊÇƦE¦ºÖ¦áèµ1[U³ZeAf‰*vì£=oÁÞ$áܽ‘¿Ì°ôf&©9a…x’±Ç2–5v¾KHS¤ûç>Cã¸ÕÞÐí²¤N(!‹æ ª¸Øj2F‹vîîÄ$¥“xÚgFÖ̱®0vl`ÂkLVB–“‰'UH(ÄV+€äd­ÂyA¾ÚM°+‹[i2àu‹’-4Cr¶Æñ…‚NµM±‘¨1:Ì…µL¦@§-‚¦™Æfc/ÊìÕ35‘Îÿ ¿_#C¨×K¶ã˜gæ Öx¬œÅïâbÚÀ]¼¸½4ÃìžKÊh$Á²)¨/1‘ïÒ›”ÐÈ@ ¼®ì[ÉÌPh¬Èµu%”ï—O:IÒÈèIôž¢õ¼æ à@R,rGÔÞ=Àé¡Æ#-QˆáˆXÆÄihÚ9»™£T#`Ê´že+b~Û¬3^tƱó¨÷&–ƒytø–îs$£ =L/’ªwzÆÑ.¥WT#Cˆí1‚? æcfA>Q„6P­AÓOîíVsøm.õòz2o¬d Uñ~âµ[oRØRX¢ä‘îû15›„E4'Îó,y+‹™6BM#ÒËÌ&´K¨ 3ªÆêÍØ¡aÉÕyÖD,Κ㦸GÓp·£Ã$ÊíºpeX8ïLH”tœY½PpÛ;ôÉ–yÏÃËaC u~ÛF’4Dõ`x›ÚúÎög[»@8bÜFû^ Y0üKøætÚ •ëEoÖƯTãŒâ. žF¡4ŸÄÃ6:¬$Ç|A)^;aÂïÅ^)îáßž”cTÞuÕéZ ŠÕóÞÄ$HH kh<Á“#C‡ÕŽ:|a¹„[#`îÖFÊR)Š¤”*Š¾>ëÎàCÐM5#CñPɳ¡Ó¾m%¸þ(~ûKkeÊ:(÷ö÷Å_‡{ÞÔÌM#Ø–l6*´*‚t#Cx°i©‘ä¡’bþÉÞ†,Ʋ¥–JZvcÏõñè]ጹ³“Ã1³áç’{]u¬Å=¥!¦O‡nY™ìFK¹æØÂI—@×k™g42Â|¹£2Âá6#`xÚ&m Á3QÚ_k‡;ýiïzW2Vmp~PÀmt —Ë]³ 3a4¥¹pÖêX㺎œóƒ«'¶(©’ Ia—“+§‹âïè%z.¶·Z¾{Ö:8ŽxRd%·¦Mô÷N¼éu³ª2ËÆâêË塸Øð죚,ÕòWíŒiX/wdä±L›3M¼TK' kD!2kj:)I;Òm{Ù ±Ö¯D¶È÷OµÌ3a#--”Ç+j§WaX©" ‘¤&Èm<¦*š#•½;á†.Dsô¸Üès_}ƒ!1i”& §K(œŒŽã”ÄH0T6“0k„‡œZk%š¸oX4ï‚-¥Ãè÷ç1pÎ×#-ÔŽJò*¨GôRTwrBæ½7¥·èæi1ñ´Q&-͇Þ‡‰ö¾×õxgä¤E “´* å`™¤^¬#`5œsx42‡=úÄhgaÄ#`¦&·ìdÕ(Úrã)È.mb(ùàÇ I5&´–‹f¿o”ÞùY—•å˳NaÍÉÒy)Ìriл°ß·ª¦Ò£Ç­ƒÕà±£!¹©Ôu†.†£0A"Rr=‡¿ŒâjÅm}ÿâò#-‚O—rtÜ`Kš`½¼‡F‹Ð8<ô¨ªrÔâñŒØ¢ÒTªÈÓq™P(mÉŠo1b*:dær#C˜àÁ˜,m±A앺èê¤) Ez )ÐI•¤zàÅI~=㉪®:!’´)dA±A4×d:ÔvaØC!À¹#bè•Aq½™‚F™¯FC‹)òO~/«}L|¶9Ö)ºfâ&æB(nNRB#-Hb`›l›¼Ë­º:RÓ’.ΰ`3ò&^ô)z¯$Ë01ò;ï¥$`Ñ„GxCƒž{óÚHÙÁ‚h\†@aÆ#`êN0‡A&0ÀNG@S½ÏŽN [èIyÃÃHyèHq †-õ°(Û…dwjÕ\Õ+îÉ“)ÏmÜùýâC^6Ȧ!#C–„¥‰Soˆb¿Ústð¹sòÈÑ¢æAËsÛ#C`ë_ƒÓz…åŽH#CDHc=g,O¬î#`àƒ„w'g3ow¯âf`j<ßînUCfib@Ó#-ÔA@å#Cy¡Ò#Cs¿LQX–)HæÞ(‚ÐáÈ#-I€…ÝÁùŠ-?¿¶¨$‘ëÛïp…ûff0ô#`ÉðiÏåwY¼÷p|ÁÛXÚéÇ©Bÿ?êr0顉‹þ‰i ¤àáüŘçè"”‚†áQHnEÊb>(u?oKÁ€vsj#-±ÚhêS…ú@!œ¤N*ÓEÜJÆ"P¾;öÔÔë4= |e4sáëñû 8ÉhKRÊÉÃæÌ%þ¹]ú}%iíÄÆLy?5ÛGc8…#`®Ð½ÝÙá d´¯”°Y œžp§çÓìÐÐÌ@9×Z¶<üU¡`Ôp!ñéaŒŽ±}µ(zjR0öUǽ‘¼:t±·qÒ¦)¹Ûôª‹L!H#A®ƒ0°­¶Ý©Gßd˜0‡¢ S—gŒw²¨t£¡rH>L§F<€¬?»AÆ#` ˆ+5õôÑZz˱"4±¤ œI´«èG$ø¢O:†„iZ¿F£íÅbá2!ÝŒŒ²(¦Ò#C ï}@"}(Ÿl'Ò}Züeø$ HóÂls¯²u?bDöi¨HI컜¸°h©Û"š²GYC™ª=5MóSÃÀ·ã!Þ«ö(@œ"¢áY‰Î±mªL’9q&ÎB#-héì/p¹» Â>2e°ºpFœ`µŒm/#CÍç8OV#›TQܘÝÜ-[T÷»Ý(b¹Œ±H\6y¹û¦{M rWGžs‘w®j¨‹¸Ö­MÌçjvpRÍÖÛ8:Æ&9HxU•äçR(lnn†f*ˆ‰¢#`b;nVc»ñÙ*)é6ÁÛ¡±»lm2M¢­4c !¸PÝlª9±ŒXŶ*ép8 ¸ÈIN“™s'I…\>œ ¨"E:."8BÐÿ¥‡'”é¢c¬v0oŒ5&¤±bÃ<ÛY8r#`«vM—ŒE^5„ÛUò8åenAÝåO,T# Ù>xš‹C9Éiˣ鄢srnÈßeYÖI’Eöknòúðœ€ß 9»}.†>effÞKT#`ÒŒÍæk­JfNxW[´n  ¶‰IÓ”˜5#8FlMm 3#CéÔrnk¡Ã0¡i~Ò 0Ôô]‡óúã=ÎRá^¦h/Qˆîœš˜÷Hcø‘^ÿMWGP…$ð‘’L’àz’3ѵí^w®¡ÇEm™©%ƒm4394W‡)„iäÐѺÓ9Þ–ÍLòÄhhÛFôæ…ÇnEÁŪ˜ÌÑȤ£UcDN‘¨i‘ƒ„=ØJôõ†na®i¼D[ШÐÑX#m¦´ì*šÄh‰„7ÊÄ‚da)ͪžg— Š†Ké=C²A@öy Ržƒ2ýC‚å]öóÖ;×êô§nñleò•2ÑÝì˜|ˆØ5éžrØ6FרóRFÙ‚‡Néc˲ð(‰"žÎ”¹gM±S;žstŽòÓ©ô4‡rM˜U,öÒµJ&¬ÚÜ„-õ‹­ágÄ{‰CjIù¬Ð¢0«æ¸r2œì[CsIµÔÆ1¶áa…gœ™'¸j·PRJèî¸sž—zF~¾~×7~\äRíjòSŒ†N¶Óîv×66žî˜»jq>ÙÎ([»¸´í<ƒÍMHF!(iHCÊž‘kŠ ÕŠüã:Gˆ <ôßÇcÜô%$½x"ð8vÍ}:‰þy55¼º¡ÞÔ@1•‡÷5k32±ª³“ì¢ÔE#CuÑ”ØÑ0ákw„tÝÃ…ȇZTiÃkn±¨Fæá•4”0S5~Ópfƒ2Œ…Ì]xöì` zD2¡#Cƒõ@[«Ù#Cü8’¡Ä‘z AÛËlº fîÝ—ÂNçË¢LÀlCI@Z"ä#-eBˆ‚ A&šˆû%3iH"h‰@<%…ë*QЇHh'.„¯ JÄ ¢H ’¬ 'Áaäu„D~çÙ•Ò  )6Ê H RJ)­´è#-H/ ăB%(Ð ÈJ?·#`®#`:FÐ B÷Š¤öJí#øý¯«¹ˆärÖƒ³£lÐõÉÒ˜u…⧂üà8øÓ”€=éŸ6±¶£ÄGç#ðJ'Ù#-i9JéÓŸ×Ë€O˜Ø9Oy@"!*¥²z.Q÷|WqËøJê<1UB‰¶‰FÝ=¶‚øÀú„<`#`#`iP¤)dˆ#`#-¢ G¿}_#C_§¿êö³ •×ñìà/OÇŠ†Ò;ºâºžØÐè|váÑ¥@úûúíÚêÂÓ¦(dË(@J#@!LJ7½÷>‹Þí…S^/‘áËB½ž‹ÑçG&aŒÄ’C‰»yŒÎÇ¿4»Î“pÀw`ûÆ]ÀÛa]×qÚ}4/1‘>àšuQNzÞ.­ËT®šL€¬ª{Xªg"dI£9"ŸV&&]´ÃM¼iÙ˜ˆ3W[ÍkDÖ·W*æ(¿½Ø ÚbÒNP‘£”rÍÔ|ZDés7š.‚6ì’¤VÁ¦Gk‰À´mÊ™-)Vž1ã†8#`;)QÎ[AÈy°Òp#`¢¨»°¦¹ƒ7qéƒOIŠUÇX6VVRš†çy¨†ˆ.4G"æ$ç5!g*k»rÄmb7-G7H92ÚãVºÈfL²ŒÆcIË^–à“v6êlÆ#Á0I‚®±[faŒŒ™#`Æa#lŽ< r;²K2EV<`cÉ ƒxˆ`J±pDé=S¤¡Â#`:\8™1/s&‚’hK±Ì#`Jj¡—%)«¸°OŽkj9lTé):pÜëgZ¶Å‹¡‹niÑÁ6é¹Æ#`)MyÍ#C#-Òï#-ZäòäDä(b d˜Q±Á&D—ca@;Àê€Ã”WR«F)Ç—¸ï¹àY;½‡"\ŠôI õG-*³.JšÓn«³Ll¦4|ã˜@ž>†O×å°©®‰ÆWÃç=ëä*&6?å‘Ó{ç˜iÍ…\°U- Ø@½x0ÃÇÄ=Þ¿zÒ8~ôh4ýKT1 Rð4Ò1U%E$A55D\¥ÆP¨—²ÉCÄI)¡)Q?\¦=B¢ S#C#`”‹@ºA¤i’&µ¤h¤#`T¡é1ìä¿t8…ö˜(&À#`qÑ`cÅ yŒìàqê¬Èãû¹#CÆÌÐ#-(#`8¨R‚&Q‰òÌJ#C,Ã`æ®k®~Û¡Ï8¼S@2"@Èç&!F‘úÿ£ÏžË³½.{óŽÐ~¿ßÁAÊzyRy¢‡ç`ú*j‘´ñ»“¬L¿’å æFñî_†¯­èQç6†æIDå\s+/6RæŸ×ñ:ñ:[Ññè¡·×ÇSLlgUà ղ!KÇÝ‹^µR2™_y{/}‚û¦0jÓ¼¨m l˛ٕìßymr|Æî@LR™ ƒ£Þ@D´‚Ð(rLQ•ß·Ôt-)Á"ZƒY@iˆ¤& I"F§°0ªXC~7nW‹Ba:—BÕ|<±ßøCÔu€z¤‡Ò@–#-<‘7öê± ˆS>ã?1!¡%p±+#-± 01!BC0ÐM4¥ ’U#-+H³D)HCA@@´$(EáÃÆ‚ \D`Ö©e0Nî‡}ÞÀ”Þ#CÝÒ$9{4TÐ{W´ïð¸ƒï!ä>£ˆ.rÔsõB‘£Ö=wÞ…åu6ÛËÓéQ9Œª0¥Òx„ŸOAòëªðx"v•HJR-P‰¢{s•Æ=«·GO¼çñ-¼öG¢GØj~§´S>=¾Ð#Cq'‰8¡wùèH)ôˆüú_¯Pà@ªz„6ÞƒÞ!õçTH˜õ3×Zų£uÛøê}Fr\Öð©\Êø2Tõ#¼Ï3nîqŠJ¹[Ý2F>ãiS5»ýÆõªÕ”´"©í#`¨C겉ÔgJ[­&jA)<¨ êä:09F2Ųu‡²Dôèm×Ëé 8)'µ†Ò"¦%âLGAŒl’Ða2Š¢éJŠ‰r\Î`‚nñ+Å<#Õ*‰L½¥ÂŠPM‘´q¾²Í³±‡•Y¦ÝŸÂÎtWÒHäc›{cyr篗b*W “ìž*žm%‚íArÅÍ^#(f+@·ÅxçRz#C>TÔ}ö&¨¨rðìŽaÙSËhyÝQ–°[ËLŽ6âcÀ’#C·e2o3S‰ôÃtŽA4ÐI#(‘JÌŽ#`ø‹aõš|,X'ÓÌ0ü<©Æ2d¬Õ^içˆvXá™ ¥‚3 @`R‚dˆ­)Ñ¥Œ-~‡}ºb2n€œ<3Y…rZQšÞgV”»¾]ùè×âß} F`(Á6$ 0ðÊð‘'’ˆ˜Tƒ¢ŽŒÄ°t9iÐíâcöö8SÔí_0<Ë¡3=úM ¬Dô9Ìñ6hzG?@Ëa„YÄDÏÙ/ö°ÀLÃ]4Ÿ ‡&Ƥ)Ù#C¯Aø`Å…ObR ¸”0#`ž¿Šx£¢hªúƒÒ{=“·Šùa§óN€Òºc$­#-1¼£IÂüH ²ª»+0A2‚Ñ+2mTœ9'9æE¡óÓ0DR%D‘G¿¸<ï ä¾Ê¦ÃÁ“†¢x.Ò&tì`ˆi‚ª(vJЈPÁ@Kà2iˆ¹*¾LBŽò$HŠàœÞø0GC¸Dw}²ó#-û~©š|`ñ=ˆ¼áùÔULAI!BÑ8íÍúë­UuÛ­«JAÍdJíF™ ¥ð-õƒ¸%1ÆÑ·V‚ÜI`¡qÌ~opóC“¨„®¡û€â»èy„.ૼ‰†ª‘#`Œ`$tQ<5ºƒ3´qHÇÄ4ΤÇXÃßãö"ù#CRE*Ц˜–Æ„…§í?ˆöiÑû>¿¿ÙÉùô?˜0–4*#-bªvüS£Ð¹€Ùô¤y§G@€ôz-å{ÙOùœéÈã€ó=?†7‘ðÛ¶@GÌ ‚íôf§¬ÿ„ÑsÅ8!?áà±_ än„-øœÏ)Š¹º‰àœƒ€ܲЕHxŸîƒÚ‚¡© H#-í–€0è¢zKŠJt…8Q†jê±T/«³¨#- n½Ý#MTÄE£SMóƈ)*‚¸b#C¢‹`ÄÄCL³lO1ÞeÐuK˜41f¤ ˜9Eƒª•™ˆÄ<ÔÐ:F)Â+Jrìô¹âç…'v(R#CQIKPI!AAKDz'i5Ti‚ˆ¥hyi)4š‰‰(#C±ÉqtI4…‰À6AKýðl|@#CzÛj&ÌDMµEêGøGˆ2ÒÊœœSùLíáèûÎÈìø|mÑ4ZûÆϧ¿ùŒO¡MXn¨þ«^ “0þé[þZÓz¡d½“#-r /Q<‚y  ©™#`D(J‘bD#`ˆEd$a˜  A%¡hhMUS»σ#-xJÇÒ4PÑRÞ¢*¾c8èæǹzû ¤¢‚ ¤¡ b¥ h˜¤)I"‘"Jh¢¨ †˜tD£‘)_<úP?9Î()‚ˆ’‚X¦#`V€¢†ˆi(‚†"¨Ie"#-™(H˜ßÛ¦‰RCI팔„ŸÙ$9! iþ£™M‹þ’¤t6G½芥 WQ! <16Gæeuï%8wAÕ!,,5ADžÏtŒèZ¤8Ý$’"€‚’ZV¡‘(¥¥¡‚"f  ©JI*#¯9ɈT€• „ªKÛíÑTÖÿT ªjª»ìz@@êKû?§èÿ7PçùïÈý_ßÜ÷ìtŸ —4,ðù¡Ö6û_Ôâëï“sˆÃ\×è5.÷.‚†Ù@Ôr…ƒ¾Aá'-/R¬žÜ¤ƒWÕ1 Æ¡¤¶ê”*#iö ’îð9éèë(;;¼›MäO ¡ï>µõžÄð‡íü¥‰cd=l=Š£N”ÒþÕÏôÓ†v á ÉDd’4zºÑênуM¤ü3U/~¢ùäGRBR#-çÁ0˜H$n€#CÞœôôú5 5Ó Š‰M¸‡7ÀW¡Ñ4#Cõ åÄ@Ð~z*!J@8¢>•úA !L‚PSB˜R誩ëéÛ¥\li Ñ“ ü1¤ìäBé'Ž9ƒ‰pɪ*‹lµ'1™&”B#-’¤†•"Éš<8’Ñ&€ä+ƒ˜Aƒã’Rº†ßÛZ(¨ûÐ#-ò#-àÝ4#-Ä‚vöeDˆP‰¢¬½Órz ’ç¼`ÂŒ](ù%R#-Py°·!S¤=£Âl‡ÔÉæêõ§¹¾žÿkyãO¸ZV šýüèûmäwГ؜¸"#`©\cŠúIbÑÆkwð€{Wy:ÈrÚÕ±ŸMÎ'÷ŒrÂ2CM(Pi‰}Î]!uE¥¡+¬\ðw8ò\ÓT'¬†¨í&!¹o݆E§Í©JüÐörÁN õ´¾æîÈ}Áâ#C‹ ‡Áá@rgx<.îì"ï ä'ÌœƒGŒ«ç½Ïri‡ÔÝþèŒMQùŒ2ø°2"I è±`*Ÿ«5>¿­ˆ(ç¡Ð<ûÁ!˜¦’ˆ…#`FªZú–2CUNœ˜#,! ÎŒÓ’¤Ò&ÖÆE´Dcm:­¦¦ÞùŠ.ZÉR´‘JD@PBÒÒQIT4”‹HFØ•j JJ¦? ®§2!d ¡o÷ qò÷röÒ0õ>‘}ô;F!ŽTÄgê«×œûTìR€.éÅÈ£û!ÂXfNâUî8AÙN0Äi‘g»$dÌÀ‹Æì$àƘ‚”;ÄýùãÚ‚´ÀGœÎ¹79t92ÈI×!Ý9”&fóâþ„5æ„&xa5l~hcc -‘¢#-âJ†¢ŽeÁ#`ðä}7^žzvC÷° ˆ)âT! #B"jK4Òë„2((Z *#-¡¢!Q¤¦IFI×àK;{UëÚÐyHž€ÈO§“#óò«]™âng‡Þ|rêÁü“ …*&’¹ÒcÙE„´c`ª@%3èÁDÆÖzô…93T#`³îC,F(ª\¾2bÓ]uÉ4#CXª þ4°åUФŒù‘ñÙyyúgÎ-bç óö3˜? Ÿ¼=–±~]‚‡I­œ5ÕO§;eÉ,ÆöÐãªÊ…ÝAI ˆ®ñ*6ÑïSÂ×û_Ƀ¤@˜}G˜`-*t48*i¡`!Ár*$0\‰d‡`¶#C$åL#C) §)Nq†g30#C9BÙ#CQ DÓ#`Ö6R#`ˆ”6Á¶D‰H (’$€ˆŒ†`Û1èã®#"ƒHF1ýbð'”Ô±çœäP!‡C ˜Là9È G`Á‰ÒÐSʲ°Hm—dΰÓ"Æ$ÄI¤0ZŽãå.“ã#-wg†ÁH #C0_A4HžÅŸy­ˆì4R*Â?(ýŒq @áÙ0}®±¶¿§¡¹ášº$“>Š=€äCoìü®\€ =ê—é©–Ì8pϨ.œšC§‘¥\Ém µîÅ#`ð•j©A¡ƒG,9!²kÜv¸j4“6âŽ!•^{ÃIâ™XüþÊp†Íß™‡#C¦KøT¸[Å™ò#-û3éèD(Ab«Å\f·*’d©œÕµàÊð†0™Ž~ç}Hï7Cy OÛpê’«ÔÑJ ˜’pÑ Â(¤#Õõ“Æu#¤&óæÄC‚W#ÏHd°-nÆJl_Œ t«ê#C&íS”™úP8†‡PP/`QEŠEK¢ªƒÎd˜/a?–9°¿¹#-)ù›ü>›`r)Š=Ð9Œ1êh™û!{}#-Y­Cýóýy¶/'ñ“×õ2qzu1ZO†¦ÈðÑüÆ®™œFÁûà“m%Fœn\ˇ!GÙeÔ`HC43~r'& ç6[+Dx»¦:áº}*z”æ¨=ï‰!'w­©™ 'Byâjw”Á&‡öü_3ÛÒš4aˆÛ¾øä¡TǨΠBG$$ŽØX m%î…jZVÛ2K4°^· $òÝÚm±H¤‰†HZb†%Á&'C-=6ÞmwÌ>pª9Fˆe™N_Jöê¹ô¹ƒÌðÿFÿ“NŸâó8n36˜t™+Éb·X¼\û#CÕ‡(1Oüݸé¥0üõ®.¦eÇþ®µQ©ÒC¬98r ¥¼ÌÖdCœ˜ò~ÊPÄ5>w·›²í†&š<Èxàþ°¢Š–"Ÿ0Ñ`G  ¤“÷ƒ|N4«}ÈQ¥Ð¶¬ÐkXA¤÷´•Ö‰£0ÝŸÒs„EgVÚ,x‡Ã-È íRàkÍVÖPÊøU(;`É|¨Y”8X JòJ <Áñ!úÉŒý2´‰²o¥™Œ¹gÔ\£‰¬. 8Ï/æiîç;$¬²„;.Ù±°Lˆ#C—að)ñ#CSá¢_ˆj?5·×G#ÝáÒüN=½ìNi0 gÃM#AÜ}øÖ,¹R(#-uHsׇÞQããè%Éñd¥ç?wˆˆƒð}ðjÁJ]þã"ÉL¤êl®øz|Z¡’¥2A)2“ø¸˜&Îì{N‹ÊèÙ"Šw<c…­˜J`æ2'GŽGaÏÕ¢ikT“›œ‘NççgaG¯ûÞ~°…”R1f(²Å?k(}”á‰;¨ÔË9}mó#C¾Xâs p½ ÑH8#C4Š•ýÏP9yA„Hìÿ¦ÄtÑú˜!#¬lûb…¶ñÈûSHBAϼ?˜¢þ¿Ëp£—õêë`)cs H,”5ÛÊkcÔ{÷£c9†$’+Èôï$ÖM|ãc‹ ’GÏLÞ‡¢qÔ575O÷A7e:€Ÿ ¢©bVT$þw9G˜ »âQ|n¤KºBŸ£CzX+!Æí#-OŽ&HˆF&J ˆ©©$¨ €´öÿþ–Š)¡"Š<,´7¾7àgf+à ÆÈšÐ3PÂ,µ-º\GÑ ìûÏÈæ)¯ZDgPÅ€'4£ë{ILIHÉÝ(>×±§Ó_Ù™fbQ51TMÞ >hFÏ @º+#-,î€|uØÚÓK»XQ§cÎi1ä›~ãFÊàFÚç‘–=:i¨ï#-¼áy‰A4RŽ¨)¸¸k<Ò·ôš(ž"Þ!Ó¸3¸Ö_ž<ñ"mYÃÉ#CoJ ä°ÞAØvbˆî.ô“Ä#-´ R©¤UÞö³#Cáwᆆ» 'íU¦M5ÀO¢#`§ŽE~b4¹¦Ÿ¶óš¬CR»'sÇ×8,€,?a„c#ø3Þ®>˜*€¡ ŠdiwEô&wPÿ‡eSÔÈ:Ý:ƒNû—1=ð´Ì…4E1#CAULÐ)ÉÐ!@CT#-$USCÑQÙÔTL @B 2@ 4‹JÄ!AÚe *'¿yP:B#C¦#-2PNš{Ì$:!Ù!Êö›?ÆÂWSmÙS¨,10h eùãÄ%álH˜ȃÍ>'GÀþ`ÛOÈ$‘óþþ› îsýíi³D1ÚçÛ2‘Ÿ™,n8ùdx216‰ÌŒEÂÞ–Ã}=·€aœ1F‘TbK™«0qŒÎz^†n!Bª;ÝoyÎêŽ £¥´l‚#Cu#˘€É|¬ ÅÛŒÈåñxüt³¹“CòøÐ7E}Ù#`m8C>¸™Cñøï’Ý5tí)†5†fŽ\HTÆ;æZSÅI…"·¶¢5."ÐKÃ(1ÊvdÚÔÎ…nîEéáôèìUhKj¨€B“o›:ï{F·‡M¡!:ÅÙð«²`4©‰~¿Of0Œ µëxÚðÁKQl”]JÅ*£ŽþSkýFDû²äÆ>|̤!%]‚ÊP]|Œb[vOoG2ÒªZ#CeyAŒ”šH15JýÒi¥ #`ä¡Z#`y#`ò#`C²ƒÒJ¤•ª@˜©’ ª&ˆi#`‰#`‰#`ÿV”¨8`ÑAòA<ó#ev ÷ý`?á‘÷þ1£ú<Ã@Ñõ×°Aì¡#->t˜>÷{ÃêÅ¡ø›éS÷¿OÏä^ÛQgÒ×<ÙvxNóåÖסÕÉC“‹þ=)ÿ^ì;\={½çˆXq 0ÐlýÛÿüŸÏ·2çÃý@ćãG…ÝÛúôAÂ:æÒ<Å)9®Šš*·ü°ÄVŒT@“>èÿ|"~@çýÐþ$‚#ñ(¥eäñÿ¢wuN—jüáu'Õøià;@Ý‘ý‚cú|£Ý§û'õw¡eí‡Þ]—hyA·«jv[»WQó®û¾oû¡‡}J|ž÷'Öè!ÉòKþo&.A7ÜþoÅ9Ü€ØÊ×Ô;Ù G9PtDcém¥¦Î‹þå'V<3_ÏVú6Ô½%pbÒ¬Ä&‡u!`Êgí‚€Ï÷V4EþŒnÎëT“Lg»Ô#³âUßäf'Û˜!®*\IO¸e²¥ëÑk8+·‰ÅQÎ ¿v«…ÆY̘wÈâ¿iÓ¶ˆÐgõ>…ŸáÖ‚V2Ž PÆ×+¦òƒ9"&jèÍÄ èèÕWDlýgrŽž]1•^KïØu4¨:Ÿ7 ˆ6Ô@"3Ð#C#`ª‰îfÞðÀ%j­$FU–‰Ð÷z÷³±°¡@#+#+#+#+#+#+#+#+#+#+#+#+#+#+P#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+ï¾µõ£[ìJuòÞ{Þ·Ð}w­I^öwÐ(îßw°õîݲòûï|^ûûxï½î-îr)6vÌîlâVƬÇwYjªÞ—n½`Zûey}9ì­y¾Ê¶k™Í;X´ç=<å{wnÔ³uÜÓu®í¹¶ÖÈû=”‘í`ÆÞöéͧ×{Ž¯¾—×M˜íõÓ׸wAÇT§|}66ßo·½WªÚÞ·T·tø4ÜwmhÇÛ×/[IÜ»(K¹ËqvNª¢¥EIEPí”éAÛ^˜ôÒ¯½º€º¾»½ïwŸqóßwÝ>#+b¨#+#5÷w[¬ÀÚ»4µÐÊ¢(¶ë“fR:îjªûi;Øê‚íËŠ)U°b™¶ËmR­e®·#/Ÿx<ô½b#+sivbH¢ºÔ yÜOm:eB¥âÒï=ï#5™hÕ²OkÛ»v»ÏeHh%°Ûãï>7·RÓ¸äÝ•.îáRæ2”Î(õ»»Ž¼ò:V¤™ãÝèªT¨( H…#+ªI{dÂVÚÜîªTTÖIÕåÜ›}Ûõï#/¹·…}ê´iÖm‘7¾û¼õµõݺT»gé ë -‹Ôr8#+#+#+Ð#+#+>Ã@>Žø¼å»xŽ!][€#+œ*%$],ÐP¶Öƒ\û½½rth5 †²ò¥Ü]šè"©v#5nÅ9±GZPu\@#5#/ è;aÀR#5 ª{œ*@#+P#+@ È NÀíœI#5 @׾ϼáíÖðëçß"¡îÞl—wv¶¨Dè÷n@ 64 Pm©2Ëbß |¯>·V¾t¥T¨oyõ¦Kgo±½Îãhs¾îûÜ}ö÷nž¾½·Í½åç«·Üo¾ÜõÍ÷=÷/oC}ïk½¼ÎÛœßsv6Þ·iØõ>û¾†ÓÓ¾÷u«míïz·}ß|û;nÆÜ“ß|ä#5=÷pÓ$ÙÝ3«WW¥åF… J”P.MèÛ‹®ºÚ´”wa×wv=݆¥Ol…'-7_ŠñËÝåîû»Ç :»w0ï£ï”7­âÚªŸm>Q_bŸ{Ý/{¼ûm‡ßct;¥´yíMæÖ̯uÝפ^û¾½èöuè>>Z÷w}e½Õ6Óí”±ëï·¾«wÛ“o@;Ûïk·‹}½Ûß{ïnö¾»Ú½s†ÝË›íŸO'#5²íîûéë»Ö¼íqöoYÛ×Vûºt¾ÜmeÝk—˜=­±·sŸ<·r«èõõãËÞç›i¹»[®·k¾ÞwOv§Mïw½÷1÷Ôëqn¾÷lÞ+j»ªuïÍõ}Ͼ¦®ØúÑU½Ü§o´Þ¹ìíîö÷žQfOuøvÙóžß-ótÜ°wNÙk[ݹ’cµžs¸jQÎÝ®ês=ï=:ÅÖÏkŽÜ;7×Áª 5nëuÃî‡ÝÞ{žzؾã®/kG Ù»µlîÊ[Ék.\äYݱÖúî>ãà:zÖÛšr©÷»êCÙâ͸Dxw9¯Lñ!º»…×µ){{œ@.¤`Pšõ^ׇK„W;¼:|ººú.Þóîú—>€Ö"ªT>†œ¨VZ#+VÚ€eT@ß-R=°#5‰Bm@[]kè×6¶Ì]»ºÖê÷vî¶7r…íÃEP¥ÜÒ‘‘íµ{ÃËÞkq ‚Ý·m˜WjÛ°»Å#¬Ÿmo»íÛyzö÷#5J€URªãÉs$ AGDììÊs;eÏ»o#+tP#+Ï»¢€#+#+»Ï]Š÷9ëo]åÍ¢õÝs¥À˜…›Nk7S¹®l®ûßyõ=óîˆ#55¾ãÎרcdtt£®¶îª#+Õ¥„‰{zLzŸ\skw#/¹»\[EÛ†¬í n溋¬î·Ù÷ÞEûé ¬ñ÷¾¾ßvùïyÇ€H¶É¬R „ν³ž÷Yë˜^äîí¦#5i“´Åv:àè#/(}7ÙUë›ç£½¶÷qC'Qí’„=l½@vsÝWtîîÛ§¶sgž6ãÞ{íñ×Ýk[p`ùëç­õž¯«Šû{ÝÇfÚ)oq®w1Î÷m:{_Yíõ·Ü=Û#/¬5I#+(‰%+B˜²™om:´z­ÞO{ÞÌ^{LØ#+P¨³f %Vîîé°Ë,³—]Å6î#+”çq}Ýååwo¾žžßiÉÐ#+#+èÚ(#5'6KX‘,»#+îÊÜOMêÄ¡öSWf7@qÓy$½ç8}o2õöÛxé“L½ÛÙÙèõâË®«Z·9ÝÛµ»‹nºsŽŒ@[µ;`r=/p#+€'›l(l=Û¶#+^™]´:Q#5jÛs#+5‰•«]rÛ­–­v6U¦¶­M»† è(#+V9Þ»À=Þ#+@©P#+#+#+v˜’…·QÛv®’•Þàr}íÛ«Û—{})]¶ŒÍUî÷ž”½ÝõàÁ¥¶öÒ@•[j€K]]œ­8Ìîl@ ºÖíw×Jà4¼]ƒ”®”ëbäÚíÚrúiJíowÍ#+zhî°§R#+[»—mR#+6TŠ½ïWŠö`ì{»seîÓw;MÎ˵ڷ#w\Ð#5@íÍÅks5µÛ™ÀRIÐ#+‡&uL°)''(`ñÒÀ×”H>÷–yÆuÀ4Û×rÑõ|«Ê÷~oöÕ?wý¯?õ}]/>ÅYt)ÍÂw7Ð+4U$/½¬°ÿžèÑ­M]»gГF'¾ƒÃOhkþýIÇ< M}3¹PqO±eÃKÁ2ùú·°îv¼[$<;'ÌO™9V»ví„– Â!ô”@ošëŠœsílÄÑþþìµPûÎë/+´~LŒoå2ÆÊYÎð¹4RFñÇ™ÄòéEû±‰¦‰ ðÞ.ìED¬êçåëZãVÔÿµÛá»&ûmt‘?ÞѪcç^ÂÌ ˆÌ!)EEU|ªaDý¼m™­£Ø”3ŠnÓ œÝ'­Ë3oFÏ#5 Ê@ð`a1øoŽ9çéÄ…ý–@¨ “J¥>äštîo3£@yÕ#QúÓ«ýÿ-õãè߃Ái>àIÕ˜•4Ï Á‚þ4)N¯Å±Sm<ì0˜ª».\r»öãÿK w8aï̦$DŠGƒYª5'8tAZ¹Cª$§¬'ÈJîÊ%¨P(Õ(kT'â¯Kýb,U#/Ç=%£Ô“ÙÓ{žp¤4œ'Èþ8:^¶ò4D´…ì,}ä“ø§²””þœùZBŠho,?4d9F»ª”ah˜²’‘^¿ÑíçƒËO$=lŽ~â™ÖŽlÓr§+®m%#T½þyüø5B2ºVÇï¤ÿ¦µ¼Ý~ÎË7M\ŽÃL¤Qø×¹ÛêÒÂØ'u[ÁÝyT’Sÿ*uãºZñºœEžº†ïVس8õÜ) ­p³ƒ-&ìÚèÑ)h”®Ta×íº°ò.YË'|(VF¹„9›õklúâ6qÍûp“F¤S$‘Эõ¹©àV93¨úskp¡Æu„\Ζqº«Îa©¥f¯6ÙöþÊ6fä]ÓÐa™ŸÌßòIš©-9ß±ãú\ÌÏóö ͉ 3U¸¼Jœlå#/1?¥²àÃíBdî>¼à7ãíõgþÞܱâj’ÅEò+å°*E>§Q hg³¸;ÝGɘ‰#/™¨ÈZ²#4”Á_eFmüŸ’ç»\ýXë²pûΕîtÑÝ#­ÿ×åºÇi_ ñÒS.­f©WI³{N˜«éŒý{|d?Š}AØ>TÓ?^Æð'äåeÿBP/z¾¥#*àŽE~™œ³Ñë¸#/>ûö€¡Ò‹ÃÙeªHì < pâ¨^¶³·)èÈ箞ç.Z<šÿ½—¯tÌÕWPìÕ(ã²?¯‹}Æ”Õâ”ìàO'T6M8‰²&’«ve‹K-çèâë‚sé¿r¬'‡W/Û_T†Ù êoåóßëÉžÓ[5èQÓw{äÊ%2"ÆÐ… ,åè?4÷0͆C#3pñe³ƒ¦ÏÌ?k1˜ÑÀ?’åWq³ÚÑlÕ‰˜O:Zƒóà§#/FVcC,m£µ¤›¡Øcf`JÛÛœþûùñPÕË;Õ¤õ1žc¬*Qþî¹Pã?} ÒmR,¿;$íñ{§ñs.QëNÛÂt's|Lݦ>© MCêÄí¿žÔ‘Ls¦¨¿û1¢[ÂrO˜éøÅã¤ñ¥;>¬¼ÛF‡¢( áÉÅ[ÔŠÏÒLwŽ×0r@oñ¤U±î²£uÍ­èø2÷öV¢aÑXTƒ¦|p÷©’{îZ1ôIE½ïwpxA¼¡hO»ðÖMj„PðëRl†ûZCóÕ1OwvþïèwmP䆈^(©õ³­X~ä(0Ó ³ÞËI}˜¸iùŠÀ#5,Yü§;±DCà”1zQA†u_Ue™ïh–ÉHB†ñ£¢XáÓž,8%‰íøÑ-=*¶v¹BqÓéЪÏv”*¢#5RJ`,ÊåÜíÀìÛ"ƒ·2d"™ ìÓQAÚŠ9 /Ê©"$íhU÷U3Á?0ݘ½(y²’)ï%⃭ÖÑnl¾ÌÃGÚÓGk)‘`iT2nûÙl khö¶ãæ£éqëúî¼8ñßqw'¢b=3P¶Jiç½qdÙ=ÚT6þbƒøwÍŽµ*'…Ð|ïåI-µ±þ}Mó¯ ‡Äà”!˜‚§mI)Džï;³B¨<*‘ªRööÓ`´‘òïÎ e¢]x²È²(`²Ï±%8BÚÖïÀuÈR+ILq%ØRp‡#.¥KYP¹Pa„™#+ îQ‘Œ”‚<«®˜˜Ù¦‰çú8Þþ¼Ìieþ ÆÎÜŸŒY:èaNŽ©;±òk‚Èԇ‚a±c%°¤æ;9½¸l ”a:XN6Ê´Þ"˜_”L¶h~ìXئ@y#5e."#5ëù#5lÇŒ>èÉŠÅÞsþs㿲ïêÃpOÙê#+õHJêPîÙÛ+³ 9Ûˆ±“ 3ZkC û›UŽþ¶ê\Ï*U'2fÿ,RªyéA¦™—hŸþ#/Y2ñI¸ŠëîQ;WhœèO7ÙS_Q:Ó©QJí¦{„C“Š¯ÇÆj‹{oŸ~MŠÆÉ#5QždÂÿ{9çöúgÜ%ÆÕFo©Û6ØyQŒ9näß‹zšé¸ÕZ\(W—%JÆÆ-â¡Ö¢½5Xœßã©Û­úê|kào3õMñc§l¦,IÐŒ»;õ'¢4Š*G90Ãäf@:Îé-Ä1(H¶lu=z}ŽbÑú,sÙøƒÒ6ÿ—»¶÷\©LôqšÖ©Ês—JõöÅƈƷ6)Í­-ž"çˆtk¤Äà3 S›m˜¯“£àøâ (ÛÐPÌz7ø…PAîeÝœäèoׄ“üwGÒþ1¶”ˆÌb40“‹râôeÔ²©±*Íè y¦Ä`\gáv²›|>™%Ñ—*XÞp‡Ò:5cæŠuÔÌ•ˆ Üèq u›(>”<\Hz¤YBSÿÀ%½ຢ!qK±­ª)&&»)‹&ìµÄwBR¹G‹Ÿ/]ÆѨ½Y#/aÆÖ~—Zqë#5$ž_h£gI‹zI8”hOYý8˜ûjV>^gWQÇYxgSÀžR%<°ï€Ì”M!Ý"úÅžLF'§Í¢ajGâöˆwURcx w­¬¦¬ì=ÿ¼Š¿çýŸÁ€³¬_Tëç4½9Ýw¡ŸEJ|®ŠrPø§od&ù8º†ŒPÜîaE p†Æ å]c¿Üø½QT)•ìì8·Š-‰‚Æ-Ê~ð<óþÝÓ$E¾2b‡CŠpðcî#Š´Y8ûH/¸å[¦“Q&v‚ Ôš‡^Rzøs{óxÆ~,Š_(­<ÝÁþÇr´ÿÅ#ñù³7?Ý›2s"Lž³5ˆ×0òÆ «™c%¾³TZ{˜1Pš‰ÿ}N(ÄCŽ“íÆ.PˆNÇß4Õȶ¯Íïsø$¿#/q¶ZæùTïé&ZÅk½1(Â7§Åo9øQÙØþ+ÞÛÆWÞk5¬Š4úõé‹ÜùTæÙ×Lú™S˜ixIÎ0T*ÿ­ø=V¡í‡NŸ:ñŠ£îö/šÓlžn’PB.D ðxÌÄ=FÞ¹³}æüC5s¯_á7¾|HÆì>¹Ugê´Äò{Ý2ËZ1Ö›¸Ã¨ÿŽg:YìÒºôøqA#/$0g1™ ¨U^õíüúñÂQÚ¾#/UyÞ!QŸäƒ«»–6‰„§cìý#/Â4‚°,ñI‰P¦ßÜbà”PV"‘zzr‘Io²•Î´³Z«N©l7ÿ}“ÖfÛ\Á×8Ïéჴ‰³ÚYÇÚê\|q·[6oyncØ]‚¼xRõE î:ûeÍ2^SËäñæâÑâÇuíbw>–Ô&·>ÞÏ*Ö랎:ᄍŒ°ÄŽîÒûh8¼íMÐÙ%¦[çXôg.¦£5SJJoãaeµ¢F3vM3YrÊæE—}té“$óâ}“Wúõ0kòen#5ŠPX¡(W=•íëÿÀ­­g ¼özàÚ ¾¢¢ «Îë(„8Ë–9¹q#5/ä°iÂ1F(#ÎÌ:©BSç©LˆÁ7?ÇZžÂŸgNYÂÝ8ºOÞ»Y'‰ƒOíý;YêH~ˆnÅüŒþ?Û¦Ýßl,÷eÀ²ÕñȵýsxÏVßn3åŠ ôn^\$íH؃2ò•}Tn•Y7YNP´ãÌŽÒìæØ_’@èÒ¡&l|œËOÑÏ™&Úý‘ͽfÖêÞå5U¼6.…„LJýu”på&•ölKkWúýuNQ÷Œýßw];> #Ž¾zë<÷¸A™põ‹3ÉÒÏ#FVÎ#+p'XoöM¼}‰3´0`q1ìS£…J)ݶûÞè¶Ìþ+ÖyA²`Æ h‡å a’?ݨ{ƾ¾ß—Ñ:2¹èw^"Æ)O Þ;«.jŠžê¼¼\óÍq»¨<ÍþÌ—À+_ÒýT"ˆ9‡#ˆ#äúMÞÙÊìð›qÝK¸"~nÝþf¡#ÿ.{W­„Ð2î’½½*«?-æ«O>£çõ*ä/×å[j?-Úš×#5VÛ&š-é%q–o7£3F¼µø©WIÐû˜…³“ ¹"ÒD:AÈ‚½iŠ‰Bt9i˜yNË-â¿·ñ°µ­§uþŸ¥Žîbä-#5ïÅCWŸTÒaî#/L™ íŠÌƒsèððýUÊÐ÷äÑö`ß'Æ>SV‘¾sœxY5f-kœzLppd%ŒÄ;_ã²æk‰ªÂÝp¨qôp†íý72\CŽ‡>;A5´;Ëèõâ'jx0¥2^õp\*=q¼ÕùƒõcTÛáÈW*ÃÃXÊ6óë´7éb³«Žðˆøk‘aÛÝ >ǺÜ?]¼IGƒef¯®`t$ôã,/ÜáÛ cq‘6£Oì'Åßf½¿uÎ8>N=o†Ž¼l±ëGû¸ÑðÒ ) Û¼Œö°ÂHQfUU)¡‹!D ÂNå%«ÉsŽ6B¿=#5m½Œ>fW?fKîøÔDÇ÷¿99/žÛVfe/H¨R’âÞp6‘ÉÊ•¿!J(¿ÞÃRª^óñ¬ †þÿ³ž¾}~Úáµfé'T„†¬„Å fêBB’hÈ@ՄߧÏpžîý¸ãáÍ×®ÝÚq²cŠeó˜cØ<<¥G#+-öoý¦’¥‡H&Ȩ×Áó®çnŠ:ÝÈL¿Ù‹*‘…»eê\ ÁIu›UVöœ5­}kDz­¶Qm2³pf€V1g\ÛøÍS¬F5ö9úŵ#//åfu3o<áó6ðVkü0»Á;|9Z¨\‹æÞá2*Y”Ð^n.€'Tу×xÖê?äëZtë èÉø`š£Ú[¯ø.ÐQñ 5¾|w:—#/ö¿û¬óPÁûR9°bA!Q^löVGO53hÊĉ-³6%ܾ̉áô_’¿ÜþœñˆVJ¿iBæƒ'¾°’‡þCÛÙ^Ï.§Úyï¿Ôzx¼-g 8Q9#/;²vßÓ!¼ñN¬qfŠè:WÈÒ+¥C^4uÖhTl?™àËJgÊ—Ò}FB÷Ù~7»úcAÛë3ë¿ôéì(¸ìŸ…;Çö—ã-V¹÷µGÄ»„#/U›ÊÃG/ah+¶1ñ«`òT]ŠÇ‘Ï|>ço š6x1tfPÚ6mºÑ-TúÖ¸ÂO.Ö»!ªC¥GÍÂBnýú‚CjÙHÌ¥r`/7) ¢ˆ"Ž(¤W,ZfÝïå#/óKJ›lë¿Ÿº!S­Þµ–"±yÆ”¾8Á>šÛ}"†µZ˜RNÏ°³¯#/ÑkêÖ©cWTä#¬oÔòf”K’VÎïO+ëãð×[ìðDD·gj‡M‡©ZIi€¢§ùsÛ±TüGRd4½Wž6èÕ ëðTpMí¡ÑÍ=òƒf¾¸‚&Íúƒ…òß)x:øà–Ç)„j2¥>\n'wâ?¥Î"vÄ¢C­ü÷Ë­yåãFÃŽ›ž8÷‰æØçµ%¶Jü_PªeóÚ'Üôt5ëÙÙ¶æÐÛãñV  £(ª¢A/¢7âNðIV‹¿l#5Ãn1¿©ñÉD ñI·¯[Û¡@Q„À€R÷úJ'’”ƒæŒ? ý6·Mgî\“‡Ú0¬9O½™û»l¸Óâ|!nN>'Òá ½2ø0#/™/ô¾6‚Ÿ–ÂÙÅ*êÏ@P¿îÐâûtÐÚ”¡ŠR1¤¤~ ”€ÂP<žcdðk)’1FÅÆŠ!fsôg*Z~­oÙ7KªÅž­­F}>ÏOÐÊ꾟=Öå ’±äPIšîEå3ÉÌT¯"0r9‡»¥‘àÝ’úÅj‡#/8L#/—’ã:M\סÇYßÜç¸ÊÊ3î@ÑäpnýuN”e¡^Ç”OÔtû>îñ3.!å™,ÄË©ùË ³è²„QT““0$#/MzY ‹«¥1×õ™„´0l£E-ÑL+ͼ`£Ê¥ 9HèÌÜÔ(Ó±|ÏágË„Kº†{u¯—#5þ)­HVëùZÚ`Hä¨*ûÍ»Ù{±ªMÎ~}™M>OÚ\‰Á&P#/étd@DÑ%"£y«ÓËÄ2$žÖ¡­P+CpH(¹d0vO'|°Ÿœw™NMÐœ”Â[#5@4fPT†ìŸ{#+δ!cGF%É"XßY”µ>¹hÐhf1V°fÉ2€°´‡íÒ»6í²j™aù}^L‰ÃJ”*4œ¨T«ôo±ëŸ×ê÷g (¾R@(H$’dû>碑ûîŸ$ƒÂ_жæ|n¯ %=x^.Œ¿{‚I,Æч¶˜u×Ï*ßÇ Þè|s·ów4²ë[ÉkÜôWÍ~‚‹B³èˆr’³š¡À²’¥÷´ž¦¤\@ÚÎþ_L±K~ŒIJ"î{‚—Å™œãƒòNw&ê(/ä,É9øÕÓsR/—­_ê}KZ€ÁöùÃ#5‚âýþ,iX€+!“g0מÌsâ(U›ÑÔbêëƒ;µƒfá˜gʳvÁÜ]f^R¤“> ñ ìHÔøwKò˜b~Uý‰º—åðx}æbæÚ$Hàåöøþi3F¯#/Êêh½fñŒ×LE4ÆÉyE {¶³à¶òÊjÓ‡ˆ†azëg^ï%³§›3àçi ¾Ø*›:d,ó±7Q:IßÓÓ:iÕûr|¯Ž”ë¡”ójÕœ»*ØZK½œãµ¿…_â©LìÙÑ>W"¬†ß²Ìr:ÑXöUX£¡Úß+é\Ö9<—ÅQÃ`ÉW¸0ffz¤K—«ªæ¼–¦ðá.ðc…Bø(N‹¤(0s¬=i¨†!ÕJØÿ,›OzíF¢¡×PU1ýÛþ©h£Þ%t¥C’(dp›ó¿×F;»¥Ù;:÷Éü3FŠ¹èÑEÖØŸáR‹æÒÄÍ(¾ÿ—K{ Χÿ¼ýæÑ\×m¯Û#5Ð#5>.³Xšïüjtüpø™Çô}¹#/ÓaiЄñ“=C¬aƒË¼+ûõ_õ!“,)ÓCŠFA^=þ#5¢«œ˜ 329Á~sÀáï郞¡#½C’ß«^ÿÖ¢ÑɵŸ‰Ø×H8””ý#5ö’ü݃tÐPИ$H†Ž{4"ŸOŽI"ê2T9íµÃ‘Âv‡‡})ò€§ßÓ(ØåØ߇S<×÷ÞRÇÁð¢=a¶L'pT³EO!#5Å"‹B„‰â˜Q@¾-_~Ê4Hà¢4Â!îÉ&AòFo8-áT¤šXLQ¶ŠÛÛ‰Ÿ›ˆ_Í#5ë󙹜HO+xbš™ü.zúùãÑìùØDÑÄãêÈâ¡ï”Fv{;(‘fÆÓ ¤0—ξm)Öµ§èaJÍZ_±/º®2£@ì{‰O²BΑ}—õ-šþµ€BEĘ*ÌðˆóåÓg‹O›0’(ÓD*¨`VN7-9bTð¡5ÖªŠ#5-ÐëZ¤ÛÌ@Éœà`«‘Ìï"Ÿk†GÝ“yKaÐÀ,}¶£ìôò½Úß×~QqçÚ+8bŒö–•ùÓÖËM¾{M(*tÚ¥ûÜûÜbÓv§œî„Žó 3¦ÉÜôq©C½RF`E{î t5rWñ_> .`WÚOðb¿)Ã(Ywv‡o=#wg¶&VsUÃ#/ö|k؆®u¦4л!Ûs…ôq·e÷¼í5EkQú׿°a²çà‰ŒSŽÉ0,'áªW_²æÙÖ€¸“ªòöýxœÒw°ïtwùjØiÓÛ|“Ú‡c«é½;Qâ=’Nt4òáýj:Æ^×+¿ohk·íömÚÜ¡œ§8M(ÕžÁÐP«çZ»¢’ëº6’„k#+òQ*AøhÙ”‘SS#¨ºêñÆp4YQn0tY.-µ™×…‘o•—HWeË¡AéGò¾Í8YèZ蔇•ÃN)Û‰ÌØÛ)’¼dɤýMS¦áÑ©…=*“:Ô¬Ñæý 4a wKnUR#/pÞgù—}Ÿ3!2öCæÜHkFQÀ£ì¨#/tãá„C“¢’²õ¾e—¤Ô)Jgs˜#/„w9™#+錺*”Žj/8?gÆ|¦©9ö9ü«²N<9)ü,Âqß…ŸÔØž¬ûm‡Ü7¾{µ?'7w?}mU)v•qW¨ú)œžò{âŽ,¹Dñ[ìæy¾õI‚LÎ|ëò\ó‹dŒ/w›‘ï€ÊZ—Gbó¦õölÜûƒéÖ•E{{¥èšÎòöŒ#5_² —·Wi,§ð²?Fmºœ8u‡mâ¯ð˜{‘± ÝÈϽù•ÃÐTã¿‘%EþIÉÛ1¯Èýši$äTKÍâ«‘HÞ¸fÿ— ¹’¥ò#5(õ\rçË/²n&#Qx—@ÝÌ9+Ù5éÌðúR¶Íc…u©4¹ã­ÛSð²q¢j‹ìy£Ò©:¹sÊq Ù'¬NœJ¼o(Fý³‡ûòo´§$™Ö“lˆM¥°Žè=v|ìí±zíO`lŒÅdÞªgãÆþÿ#+äÅÙøVëëhòIÓ믱-HÆ`£ÊÍJ©>e‘‘Ö³– ¿ÛÊäí2š¥ˆ™é6()mÍ´¦u‹ êTr¸¤üª)q B¯|#Ü· ¸;\bhvûeÛ‘7ÿV_…HEÑÅé#–r÷ä×}µ¾i2ü]Û:æú¦t;NÆÊ–ª®öa Bs¼]¨%“Ô1§öQ¿ðrÈÝÌ#²i×Øðp© è£(tÎT àF¢“ý.d¡!_ ¥‹ú5¬ˆ”†¦"—ü¤¹Õª¤t oŒŽDÃLðKyzÚ6ö5V§ô¼Ùßå)ö=&ZSËé1ȉÐä!­6éŠD£ ‰dF>°ÃÃÿ6³ŠôÀ÷Hq~¹‘/ë|iÄ£;ï>Ó´8ëaþD=Ó0ÈYš5¦²,X¨ÅòUÙA#5‡ÛÛ\\W·ÄÁý?–¾~Z^>¦Øp{Ÿ¿Î©-#/½ÅCTê÷vWßç!G#5®æOWðgõOùaòƒ;3o 2®ìdÕßêÀ<^¬0b³o¸kïxm×::ËñØLøm€èw+‚BJ#5£“ØÎ¥ó Ëu…§#â<«—~L#/N¢­bD„ï`ÝZ>&ݾ֙qA’‚Çô>⎠U}’ÁT?ŒÁçGXüh‹oËdiçxÈÓ“›Wçm¯[3u‘½Ò#/ñ5üzs{‹o§”Db¦GÓ‹],£¼¹kìŽw|ÕÆ¡´¿é'å^~ê7G+âü«“>]/(ÅSYp ëY§g|ÏÎ"—:Z)³Ð<k0oSH£M¯œpƒÝ¸ËØ庪+/–Ÿm>±~4Sv‘ë;±»‡á±µ›(ÛÍ<Ž½œÁ–^|“„<W…Vöqs/Ú]±Qçç¦nR-OÏTîâd7/¢¨ùZ0’‹ž7´´tƒ½,ÿšm°åwÈ \®V{µ2:ÒÕ2>Žlð‘§sä¹á«þ¿ŒÎÖ8 $”>Ðæ[ôK¶s0ÅÜé]Ï®¯8ÿjZ„;2µC¤–+¨Á‚¿­¼„¯T¢…(H½Ë"òÒ€`Q7ªl‚‹ÕcÅl-ü°³€xhŸ½î9g¶=·[9<Øì¤#8xcbeWaJZ´=ÅHͱUYÉP]±N´þŠ´Â{R–êû+‹ºî£~tJh-âõ‹˜(Ik/H'ĉï¡Ä’^§#/”daÇ‘ÊôúfðÇzàÚ#5L4„ybJ}§"ã„#/‹àZ}x%ÒS’¸µœÙZ(ìÙ\äØËꢵ(Ü»Öå0F\5Ì`hLJå#5ÃB£¤(oÁß%KÉˤq“}eÛï³Sýúûa~!§±ÖEy^ˆ‡,]ŸòBømKYðÔøæÞ}Å›pg+¤V‘u¤‡Ýe/é]³ßÓ눸Ê*³wã¢/±¶~»#+z°@GèN¥§r•ˆ) ЀûºpÍÿ¨þïô|+v²bT1;qÄͼ š—ô¢Ð>?ššvý½q݃r›±#5JsûÖö|ðñ'•O?Õ¡”ÀèÀÉ¿«ú>\É‚rÚX}âf ×jÇÃå ß7¢US‹~À˜!Gû¸¨™N#+’;Bdd<þ8*š#/g·¸pïS…píï‡Ë?•þŸÒ,_¥çx`œÄb:ûôÈHKöx%Éçú4#±8úMîŠì(¤jPæ"¶Íæ‹—ÒÿOâàùYÕäõPrHa„RE#+Bì¢-F7k~L£‡+T4Úçw;ÿ“Ïíø/ˆñð×ÐåŠqQì:»——|‹}œû˜ëúÿP“#+|ûœéKÎö#釘O¶ÜO2…)"†‰AF’‚™Dû¤ú±ÿ¡¯Ñ¾wFÑ'È’UpQü‰ž˜å©ô2òÐÿ®ŽÆXFåQ‚QRŸ¶=nïz¸„ÍfÉÑrñϳÐè€A ðJEgg¾ìýØøÞ#+Ñ#5G­dï£ø™„Èž-?~´[ÝT#ÚëôTÂÄòL8žM>½Ê E¯”ä“Â9sššjÆaàSŒAyÕCõ5þº(s¥'¨wó¨e#?·äøç]«Ý´ú·þçêu`¯UÉ@ë€.ì:GòpèêúKÿ‹‘’¬.!¤ Çë„¡ ¢¡#5i,T(ô²r*ÚÉ£‘Èåóxs–Ð!3Ò=´ÉS­IÈ iç »K»,²¢Š"Rˆ«íd…3j¬ j´L±¤+;9Œ”G+m)¤i3jSrÛÊi¤Ç·>ïÀ‰Ç:\»ã£cÚ#5Ü.ÐŽ Ï÷ªÅþ}Gh?$aÃl^‰|>׶#/#+õP#5d1gNE.^î–g'âñÀI“ å—‹^ûL­¹Ç"DC Öš¥{cRZåiZr)O”D#/eµ†V‚2‚… 0,åƒd_Ï$¥iM_^r¾H×.ãõY|.[æHS$)¸É-³ôèÈõxAywÁF+袊Ü`8ä£#/›+ƒúø(Û?äÂ̯6h0ª|—]í'¼ãòwÖúÉ;˜wª(¦7ª×b¨_\ãxQÚ""7ylv*·~U±qÅŸ•ip×·kÁÝ3_gþᎽi¤ÞEÖч„‘çY€g[5Í£Ã=>êý¶Ÿ£|éV›·OœÓßá?ø¸©‘ª¦I ÁV ;}'‡øÇŸ¿¦àzµÛL_c{k¦šÝHB#+bxž=?|ïü‡¬åðÿ#/8~žjLûHV þEaŠD?Ô5xfŠŸHüëü;Û`L†c~홪À2¿ã6ÀR<ÑÉ€ÑC(Xpå]lš*XXbÁ]Øè4™‚$ðZ¦A<ž&¨ô"T]­/Y;>#/×´Ô&@ÏËë}y8= r–&MÎ罓C ûYíÔðõânó4“sX{7Qzš ZCKÖð×—mŸWϸzÎÐÜþ]‚Ù˜3ßEÔ“æ‡ìæQ'¹d͉8¶t#/gû‚„AÊSøíÏ£) n‚ˆ¯WO8è"\…(W·!çωñO8…úzЋãdÝöp›ÙSàÔØfN#/JKoïü0t@ÈÁU‹ÞýÂ{OkŒgÉÒqT£ëøbo^üŒþi5Ø?€Ô=ŸE\~'åãX”d¡JgFÙ‹?W§æ°òhõ'ß”ÌþUè°Ç+îùz¢›ê’vn>Q… #¦* ,VV €{ÄÕÅòÀ ¸{'ê5ój$/$&È|oW=Ý]/™ûðã¢Xu úTºC%åÁ£AdÈÐBŽ:oÞ{Ïáeqt•#+–LÀ–̓#+ò¹FTJ ßÛTÌÖUo*è‚Ehˆßú|´óÓmì9ßá¯,øáÖO\}xr©#+aKaaƒòà¼<þ'Ÿåÿ_.4©Û=i~¢ìš#+çÙ·~ ÌQ‹Å»8ß[«Ž|C\pü_çöÀöß…ôRñÌ!£¿[èÉJ¢‘ `•ª=ù‘\§ç‡…ê÷c½÷@8CÙ@P4´é•¤êºÂÑß”þÀa!ÃŒcWÝ׋¨z 0a;,†“ï²DÑ)’ÒÄ>p¥¿³ùý›+Žî­d¬ëiùÈçšQú0ØÞ@Pp@Ú¬Œ…&‰Èà°èÂCÉ€›Ç&‚pX#5€éÆ8yhé¬eG-0½¢pœ8NfÛD³¯h¿øƒCœ$ —µg}åÿà AÞ» {9ŽÎظ G+ó¬Ì>‹£PÞõMyÅHœC•[ZÒÈ÷•A%(ãåGéY?Ö£?«\£­[¢ûíBòæ}Σ §ÞWì ¯”÷QÂã´Ø]«ê†0QxËúæ\ÊÎŒ3á°e€nOªøÆó„qÜçï|`ôzkx8U:á"Ò#57J©“€›ãg0h÷‰tÊ¥â6ýzýØ]ÎH‹|6ñ÷c7¿V¹\#+n’‡W˜ èÛ?¿Â¼š:“g·»·kÈQÂ#`#Mƒs½Ê Þ½†…&•Þ‹lz•©…jŒSü¼ºžý²hä¦AÛ¶ÂuOsq oTWdÒÕ¥z€13%,@±›Ý¢HŽó 1âf¤ÛRÜL¡í=)±M›ä†TÂkWÔд#j êKEN±q„ho Ûxs¼Ëk|ØÇ¢÷Í‹D$G·¬<{öÈ ˆeH¢HIäåšãœâå ,`¢¨e¨°h7~©Ÿk‘Õ8ß;Xš s½ôÍ'býΣ§íŠB3¤-cKÀ6#âíCfŒ#„ïB¯Ä!Tà†Ï¹Àkø;ÄãQDRÉPFÚj&b#Š‚Ⱦ +NwhðêÂñM6¼ä¡–›æÖ—Š! ×¤È 74æcÇ1Ñ$ÛÏ)#/C7oMBøDÆ1³0*+' ëa¶`•% #/#/E¬Iæ’BfrÖnF b* ^Ùê—‰¶·âmÄ&’‰a‰s1&ó.iùI™¤òÀk>¸aåz9R,!$ËèUÚš¤Ôž¼Àÿ‡• ÜË--Zß8Ði¥ÄR¦ùÉØzñÇ,̆gçbXX”L9µ.òœ}ü*Á8I¾ÈÑH÷ZhƒÛw±¸Aå7t½fçÝ–(±G:è8™É0v3Ó‘ñ#/,û›>`7¢é/#5z!F°»<™›"•ƒ£«hȳ¦Æ.$cÞ7Ÿÿ^Ntcò„é 'iƒeiF—³Ñy¡÷I‡0˜ÎÝþšÌÎ3íòÎ R÷0þo£9E‚}\“± #+~Gš¼‘PÄrÊœl<«u¢0Páz”uA°ºÎœæÆ À9“·›a¥v5Aup«Ûª‹2âüW˜u";-sMWÖt¹Ã9çb‚ò6E#5‹{$§ae©œ5;«èQVÝ”|{*زÉ-@@aÈ =4„ŒO“\iV|€S_L)aÝ£ÂÄÉB)AF>?ØÃg5ÿxPôJPn8ªÿw»¶·ðý<Ǥ“æ—é€ÜÛÇ’ž>a«ËÙéþ¾'´§š”šÛ½Ëz¸9™éXž @F®Ã4aWS•òõæñAºlÿ+ôçDËpyµÚÓÎ]·LÓâÈŽÃŽÏÊŽ#/Y‡}ç•ÉTÙGÜ´ÔšÜ"1]<¶ë`aªs¸®Èñ1.ªó‹ÔøX+}O/»Ö­7tmhv7 ã­2o£Ž§¿žªGO³_ ^dwÖŸÿy}QG´¾€w Ô|´ƒ}%>XÙ…ø‘üá‰ùŠ‘yÆJºüZ­÷…jõ§Úø¶#ÿ+ çëdý€rèèYOéÁ·“Zë©êŽë¶ò;¢vL]#/^©®ñ{‘Ë^‰«„–'|IBËxAþ4[7eBÂI:k}¥HùÙ¡ðáÖ²`vM™õÏc;-ëgñ_~ò£aÛ<‘p.Ù*šŸ€¼`ê&D<’úkÉððÝíÑSGù@=Ý”3‹&Ïïx@RÂŒh êßr_bSìµOóý¯¨4äp ƒb"«¾GÚIw"#/Ôí}¸H`ï>y?mÜÃçù²N‡rc×<º~˼â€æ}tQÿ#5ù5‚æ0o‡rå·Å©(¯S•GYÁèÕáÁÄŽ©¯#W½âkƒø¦F~á¨~Ø2c|ölÓñ/r]|“ßFÜuäAx”EÞv›žIµŒdÔ›¡\èæó¬ãÛKäîãÑìüòÅ [™ãüuÒø”QbÃÅŠsI«/ ÇVâ%\=Âßð͘Ê(¥¤ÁÑ{#/ƒ8â\Ï5¢IIy {ÏÇÐØR<*¼OCŠ#/‘‰ #¯-:@‘ÑÆ2þr)@ò”žlŸûhØFš ¤>üoþ¯~MúHej·Ó]-H;Õ"ÕU(Ím_Úã0QhªþΊS#/l¹¯fCû˜v á!Ñ%Ýäff0fbwó=ší'‰ž#û-DÈ€–•ììÅÎ$ŠZÎêp]ÜW"ˆÂ#5ŒýÚ¢øª­ù¾J{$šÉ„Š9#/5DÑìLŸò2Þý®Ý@Ìñ]– Ö#4ÚRfàjú{{S¾  #+?RMSÖ[/"}) ÐŽöͳõ>˜)¬Éùay¢±U*žOwvr5TvJG3mïP˜:¶÷´ŠTåÍÕ?eëˆèjfæ«KþP_š…Zêláí*ý½9¥óô_wŽqŒüÕŽ¼„˜Æ_ ¼£1”($Í Tžòn€ÄÉ~—ïãÅÎ(¤Cú…C‡ö7-7çÜi“o x!;ŽúÏ×ô­>ø3»~írTMé§x#iÑ?—N?'I4Ó'ôã®wýÕ‰L2Ky¥%2¢RÔÝl*;×ÅNºòÔÊ(ˆ!‡Æ©´Þ‰„wQo›ÆZš÷í -×7ÉßœLœ“Ž]£ù–ÝU¾Hi‡wͧ¦¥áÝ­ÛŒ3¬#8¼béLÒ¹²ôãXÝ^½4tûÅöQìæ3¡µÅEÊ›Ââ‘X 9Ä>ðYÔxÕýÂYx|>¡"ቯa‘r?&Jô™3’#+m•~I¡üÁ?*l¯—VŽ 4ü?ÍxÈ^Žƒ¸7ÎípL=yL¸f¢îËQÛ’‰Ð€˜(PF(›Ó/£O¤aâÓÞ´{‚Jh¢—yÌ©Ûw>'ž)Å/þüw¹H¾‚ùsöIø™*Ïa”67êÞ6/ùïàGâ0Œ·ø/CÜ™tlã·†®‡cu;^Ææ¸E IBA#+ã´m[qYÅx#5¿’ã-L8þ‰¾Våªx$xž7[-×uÄçUK˧gúÎ5áö缤E—“ž,8 °LçÇ9 ÊW™`Ê2èxf°Ôgæ°Ö›+¦@F„Šf#+´IÉxö>nM¯ç¨ ·¹e·_$зGŒ(Ø'hÔ*0¡6Ù³!D y²OFÑááŸH±+lÞ2ì&Û[Íg5âH†—ø•¹´8¯­kϯ:Ø(‘¥á×sõ«!(i fTmƒx!TÚcÛª ;jÍ.ê ÆJ›’#ƾÝoÜ¡£Ê™rPq®£`%Í/órêݘÏ/PÎ|5W>#[æ*˜Ë{Ð;F7]uù+i}î:kÃˈ:Då#/kXèis(㦪jeúxJýX‰k4¢»Jþ¿ë•6òý"‡öÿH¿·ŽWMHæëi˜Ë•I…HçÙ΢Æ]q¡ nŽyŒ#•`üœ¨+bŸЋֽ4zÚÅæ’‡œ¸<ˆ,à´Þ%#5àì@àQQíUíºíòN×H;ÌøB³ôÁ3Æ=ùMËñ%Ï ¶œô@˜Ã¯ÁÊségÝF.°¦Ý/’sqæ–AôÑ]E¾y+W©µoÖìñ•ç #+a°#5RZ¨¦G 0y¯\( ¿âÑ燑¥X«°¨Ôó-öÒ®ß0ã:Û$p2„j>ÖÐù(å.wBÉÎ/ró¬Aƒ–Ç®z'"'GUò¢†/&F¢oCò^éŽ-Þîœj}ðáÑÎÚ¼So¾lÌ¿}¶AŒÔ¶#wt²ï.Wå,mÞ³?/³É[öÆ$%xN¬^Ê)Ĺկeøœ˜¢. ¶Û®Ù£Yr“3ˆßóèŠnf>‹Ô'ȲNßXpù%-;Lÿ7K×›ë‹æøÅM(ž*õT†!7äû{Ø_^ŸE#5²°A¢|žx¡Æpj6ö§¢·-I)œ5X@o±0—ŽC»ûßô£íÍòûyÜxýÂhg‹"Çõú<„Ž ŒÎÝü úÎçç“ñõ;;9×µjâ‚©¿‚S¨ml’“â¾#/—A$¢é¼©Ùéf„kQ¯QGSså'Ÿ¯»Ü)aµJYìQ‚x'‹ÞBí5÷ðÆ íÈûtÔïÆJù檋W\tœÔ ”ˆ‘ãpìšwt÷nß„³C-湑ÕúÈ@HßF³±Å-äIÀ ³08¼wë#5ò6b¥„SA.S<*"(£©˜P¢wŽü#=bbæí.KP°O ¡G쨶­ú7uØDM @׊æüt½šžŸ1ÔÔ^›rº}'"ÿ-ˆúl`"ê#ãà/Dé»xpvýâœt᳓›ñ¹¬<>3 žAZÁ¡µõoh’hû”~+ŽïÂÎkz“ðgA¬6y(™òãŸïj°Üñ¶åŽOåâ版ižN¿ ˆ8ÐuVZ΢9l¸J½»z£»ã0Wv~þÓïê1{auÎ:60;cæºëGå“´¹c³#+>‡l/Ì{[ToÏRÇdFâ$æ°|WZýNcG|«Á£³ðçÂÜÊs#œê.5Ù§“4²šÜžqÍëÜ«u÷ã¨3¦çÓkž\E`NBd.¨½›}C˜=LÊî:ö²\Óú Ž\›á~³T]*9n"ž¹bæ®!¯Ñ{ûÎæuœFÂ\Ɖ]ÎÕ ò3}c7‘Z™s ÜS“–Ï<}4@ºŠÀŠd¨¡s]Ãs±Ê": ½GrZo÷ø6Ù8öäàǯ^€NWè8çíîaG•rÞèH5Ò¿ªüÁwSs ¥@»X†jÍÃmp}ü÷† h\*j±é¬ý0Øw"Þ#ÿ=7+è¡`ˆHb+ƒÔûÝr˜— q~˜Ýg`”.)‚^íçf³®¢Nqçimƒn¹øÔŸ/Ç|µW;>")ŒÉ8E×£…AÊÔ-ƒU稵S†:#5ä:šÙàÃ}Í„Þdœ>±-õ4t$Œ“míÁ#/N)¹×l<°5¢6Ç·GH†—¾·8`¸dåº=.äñbðIÝ©¹mÔX£"::ßdmµì´¢,k4SÙ Ý­íe L"âà^\˜a¾q1]KÃ&-æzÂKÞ²}ÑiöùTŽïÌ»›òÈõ2­ú_;üõ64!ó¾bW€çPÚýwäíƒ$ò;-¼4x†tˆDÓýÄ?ì¨Ò–óú/ŽÇ[)+8#/¹ª'9AÎõ¸X繞úã¨i¿-2ž‚”(¹¯H%0šê±ìqñ›llk#5áFíœôëˆ.Ÿh&o¶¨ïÝÚïC‡–it@íoí‘-A0ðÅyç㎳FØê6Pýߎ°D½N “+¤`½bƒñ2øè1cjÚÍ‹„0‹ZõiUvØ,±UdY‚¶ë -#51Œ{÷,àwdÂð¸¢LlŽ¹âEä­,@Ê–Hç©x€á@·à0ÝAjÐx/}îºòš~_ž»³–¶–öö(¿fö#SÓo'ÀìQ“еé•ØPc!Œo˜/”wIãÔ«Ä‹äô;¯”' ½+Ö>…y¿Òt‘_RÙâýÂFG§ÏíøÔÝôL9Ù&[€<1ʉDÕ™˜xz»Ÿ—–=˜.9pp¥Ð€¨T`î\k¹íIÄ®~†p Ôtï“ÆE ìYœàÕ$„ÛÁÕÚø×’·¬œŸ¬¹FLΪ-ËÖÊ#ƒpQró·k9žaãïeg‰– ‚€‚´Ž{æ‡ øžÄÐÆ¡ÝñÁï!#/Ÿ‚Ýf-±úÏbÓŸ¨RœQзÖI쵓ß%ítÞð êõðü“q‘#5mGH ¹† w§R;d½¦’KÂ0pžIV~ ¯‹ã›Ò×8³cŽ®šc.\*ÀkÀ@Ó3‚XµT¹xøÓN× °á0Éá xßr6#/7ÍùÚ%°Ø–h\õP@g4‹*1APÞ>¬0ô8`‚ƒ.¨¼bí¿³¸ëé[¥NeÖ>P>Ô?܇Àóý_ˆRÀ#5Du³Ý/ÕT,Š9ªˆ!îï#5ìóán¾›â7g¶ävˆ!`0!ò’ÓÊÓ CyãáY}7}¡ÛÊþ\õîü˜ ?ïïD•GBªI$’!ÝO¿Îáv?}瓼!ùý7øõü>ù,ð30¿ßâ[aFÇ…˜ðùÀ$Ð$2®Þ &é#/)ÖúX2YP˜üä(»Ž.5n0ô÷xœ ¦õÝï<4w8ºM› #+˜UÈ* »+FʶžêÜ]2Âïšj/ŒpùœÏ·ÜÀsÛ߃¬kéÀïŸ/ÿÊhâ9²E*Úi2ÅZ„Sdþ࣢Ûyzqþ”"A Q0Ø_·žæ‚#*‡èu÷XaÌÃÄMÃ\ú­ÚÆ@c &÷"EÑ£cûx¼ë‹µ9Ê–ŒÁÎC-Ûƒ ¯È‹¢£«+Ç€{¹× ð¶¿»¡®r ÁÃŽõÛåÅϽ ™Rƒã:ƒxÂ" 8ºr?u›Û㮺ð;û§­¼ðs2ð"‘Œ"ªœp"*eÔ€âá#œ1¯EügáÔpCü>'9óïü’zgÉòq$QIÜ0)X1Š0c¿qß0‚ȲM¼z{kÇ·—¯·ö4?ŠÿqM3Û íé¿tA’Çõÿk öçËÆ[,9†’Ö®‚¯=G^»2Ú87—Íò¡#ŸöЛ–jöyg²ù2,#5˜#5h˜èår#ƒnD!ÃÖÑ'ñ®ë‚IâázÝ ¸Fìy±“ÈÈ—y.Qý9)( ©W#5ðQYñÞ;ÕUàE2S·Ãû<àîÓwŒh\‚‚c² šÆ Ȧ©U–AÄ‚{·• Hºrsô!{‡ë 0#/V$èD‹Õ³#5CbÉ{mêtULTDEIÑø+lï#/Å •ˆ#/@ƒ±,Nº…‰)‹+V(nü??…{ëèÇØbÎ!Κ=dŠõrÜs  #+ ›ì]“¾¤ â,NÔ ¬®dIÏ—3øýFdÉ)7ªË„´PU0•éÄ?<•ëô[컚è#+-`óÀMúÙx2$yÇ_îΠØ\G’¾sò¸Yç{lߌ'8„DŒZ6† °G”hròfL#+™ºáƒ7eŒkŸ)ˆPFAݯÙúá"'òåØYŸŸv4U1‹ ¨ªƒJÄ!#+Q#/íxF ñp[¾›'×1}å»ÝgöÓ–ütî î”P³‰*ÁÞ¥±\ëø#ë/4`GJ"Ãñ+ÝËc£±gm ¬+dÊQ€â.íR(MÍõÑ÷üÕ­m©-Ù׆þÿÙ°\î~ÏÅ]ªÏ]hízøÒh€ˆ"‰ÛtŒÌidÄŸN=_ëI¯söç]‰›(¢—e›jR3‡b¬¥c‚“œÚ4êÚ–š9%ÓÂÿ6 ÓoIJ#+x`…°{£<Ø3la—rBfˆ™…‹²µ¸=n¥)ðKÒ‡gGc’öTøŒ]&¡’%Ë„5á¯O?ê6ÎÛ˜s“¾5œa6”VHÚÇœ#©ÊÚV’BšÐPLìÔ,v:þ÷örÈUÀèÆÑÑ—"%iYjþìƒia"Od¶-†%æS@âÛmû®%¥é}ûÿµZäÿ²äD &`¢{§xC '$ƒ^ºbõ-L‚)ÜŒiË.ð‚3Ü9oÜÍ#+½Aû¦*(ªªÓhOál#/£Zmû›qÁÆø·!fQ3‹-–Òf‚Á§f•àÛtË“?Óºx½ü?ÉYÔå[iL++³ËlêÑ”*¯ê]*#Òä½_´Ã@dýÖÊï/\úOfÔ¯1/}, õ4X¬uX™™ÅµÂu¿(É¥Ò…Kí-³øØy#5wçûmwCË߇€æ|Þ!GOƒá9£aØ êï¬nñéÊwvߦ’d˜ÚµµõÀë¿+Q*¸ãe^²bÖÚåPUš3Š­bUVÛW6qz½·WMïºÉShÝák³BÞowÓ g)\'BÎ#ˆs5´Ï¡[«X†€­NK9)¥”¢0·”éasÆÆò+sá•HÔ8ñ;ͦĽ¼ÖÝh›ÖLÃòÚÍS˜ì`¾6ñ”æù ëzŠ‚ߦ¾#/6W5#5e[Ž–55äs˜|%nklâ–4êÍfÎÐnŸoè]=Ûq¾Ômã’òŽIaü˜ªÎÚwŒM8lK²£¥rðµOrø}y¸l®T6Y'2G•WËœáèÒmøsy“ ˜ˆ»L„ò]?‡«ìAÂØ­âãC„WéW”5øI*#©c·¤½_—ðIÖæß’Ôü½vöÁ¬”Æa͹ÆÚe/·.°gg7Jê£_Û¦;©ÈÀ=â–háu¿Œf¹ÙF%ß wÃ̸êVUjóKìå€Ã#+ê2˜ÔãE+9ÝÓR·¢ÌîË=Ìðõé/ñ6ŠÚ#O`Îëu?ôpïÂP`[‡ñ¿‘Å^Æ&*·˜,ƒƒ5ú–ð2.5$SHÌw¨Í¬0`÷¼Ö¸Zr–µ"åW©G”ˆz¹Z”#/E6ÓkPŠé:¼2â3½Ïׯ˜¼(³ãÉÛÏèßz5ƒ¶ÜÇ8ÇdփĶ!ËŠ×ïga¡r89ÅAºõº>­·$ÑùY…ÄÌ`n¶-„é°]õ’©ÜýþseÙÓºQ+‹y½ý£uºïÉQ7*9-yuCšŽ»ÑBäÝ»#/zb²¤—ŸÙ|z“æŠûÇ8×C±ã¼$Â#/!äçLãÇn¾MoÚˆ?P×[õ¶ ~Œ4ÓQ" dwm·áv§êG’åúùŸáóˆÄÏôÏ]ÍžFƒæû:¯¦?#/Ìÿp ±Þî—vx¡«(É©r@¸•›àH3PŽÂßjô˜ë¦c^5¿†. _R‹8HTÙöÄ Ã•pãåú/…,£¬r/ú ñ}]BÚq<â€ðyã`PE®òõÃcèhgÓµ9$-¼ta'…Qm€}Ù“¨xÅ}ücü7Ü ‰WN§¶_ 9Â|‹W#/ºàåpèº6rø}þœ¼9 „Gˆ§)(·-ðéñEÐè‡[´«¬§ÒÊ\ •¼ê‰åm²§~чÞ=xBSàëxxØ/‰Nzqì.õ>Wì—¹ªT¼a#+¡…‡}]iÀŽg®"¤1¸aÖÈä#œ¤5—bæcÄàç‡ùÓ\ñ.Bÿ_«¶ïp¹cm#+"aQx?;£Œ€Yî\›¶ôÓ€JÇe[ÊþH={`ù76_28xs¹."$(7‘tfÙ,ùÙØõô‚×JºWCkT`_ÕlÓ, ¤)Œ¨)T&#/óKšŸñØøoйÇÃT14ðÈ;lþŸ$0F&(ÃzÒg²Ae6Bñƒ·+¦Â̤|±P”A†²Œkj*ÅÙª5YNeMuxîOµ“ÊŽi7Åm[;W ´ö¬çÝf®­1&ìpNwÖ7˜õ—þe0{âY‰ þÉ/¯ö‹ a…åŠ>x5dèÉåìo <)µ¸¦UÏP¼ˆÌD7ŽÍHëï‡EÓçÆž&½´#5DnmÃjÓ3üqçÄ×P¬œ–"*®oµòº=mf°¾÷›ÕÅñ>!r±ƒüf#éºXÌ0Cæ¸d«O€½±†t %šÕ¶IêtIR…õ¿üïlÿ'| T*Ž¤¯ƒëÖ²fá' ²QB•žNå‡_Ê3î‚ŠkÏH(èÚ‰žýƒé{Aîx l¾Šgh”—¬Áp­æ¦“˜:&â’!­=näb¬CƒBqo`n¿¸í'°ÌÇdÞ†]}y™^»¾ÇÚ‹sèh¦/˜ÇC+SnŽ’ŒÄ™Ø]'8Xu¢ˆ*˜>8Y)dLÓåÑÆaŒ#lÜ âhL6ÂXå9ãK’têìý¥Ÿ+#5Ó>¶bÕlŽÉýG“’o#5@ÖDJÂõD‘¿òÝ»[­*iŽ¬ ˆR¡#+ †Ž›×µp..eÝ"—k˜BJ¿j®×>êA"bÑc­èu··˜¼5ñ3ø¤ñ³Ê¦ÔMwf²¿6-ùCýŸñSZfïäö†4iíÛ;TÖ^zvÀ™±&„#ä”Ø_®ÌŒŸ±f8x Z¹¬–{Ìf?,>§]æ9{ƒþŠÁ ;â —óüûkµ^õ FÏM¯ øLqN¬&~lnǸ+.ÓÕ¨7Í>8ú¦ }XÛøö”†1±ù ú&Kò+‰Nݺfóœã °V»ç ¾bùÀe#+xhý¤°HÜI%Ó|³L²²tä ‚Þì&¤oQà#/ùe£ƒ×î_)Õ–¶ˆ=éò¯Ç¡Š´º8hê\çØJAJ;ä$_;e]{Üý ‹ ¤ˆ§kìÃ’eX ƒ"€àÁA+’*%YH®Q~G$2qN6P›ó…¨¥öçòp­ßUµ{ZÆèn×1Ú¢Bq’ÁêçQÃÎ@ NX´_uWÄì nÛ/Bvú‘$§;}Ǧ>rky¯q`Ð"àø»„åg\CV<\á/ÞC¡=BŒ«dòý&˜%B$æ #vc€j®÷i}„jYW:µW ›ÄFê¨L>xÚ1G‚;MÆàT‹°­-Zœz«lb¢#+µv»®þ4šÔRþ=‰!Lf.'|ŒšÜ›põCÈìŤÃ6þvž¶11ï”3J9¿[8ƒ1»çikª˜Ó]’»}õÅ{ª®Fû¥à’¥½ÅQÚûL–#/Ud‡)Ç àÐy#5n-gÁÃyu¿–¼ÏÒ ‰Ryò_çÑVãÓŽ'IÛ·üŠ©™È»ÐL³Šr¦ê…‹žf~ïéÆ/›˜ðK…!™ÉÛK`BF  » ue’Ô,ᯠV›WÇ­Û ¾8õrÝKŽÑXùpý¯û2ýÎàl¸C˵n£IvHä”Ç‚Œ-©Ø*½þ 6õké3·nP‘ÂF›Là×𛥊ŠìïþŽø²¤BÜs“³B?wŸâ*ht»p>y<äáRyWj²Î‹#¶í÷³ùŠ^Ñ#Ü°r×yÎÜÞ#9ï$­:çsùó7•C¬ëooëkøtlЙ°þ}=(“ˆ÷ˆî9}Kö. ì"÷4¾çB#5|ú ›Û˜THkÇyãå£Æ”¥‹“³o_a‹gr©Ÿ7©Q>_Bcšö"æRêW[E]rz‘\`£^…ÄkyÛÄ/UÝd)ÚÆS±ÖÓêœ4¾gÁ¹YüÕQß«ë#/ùÀ"*¤1y†c—=s,Ü9?“y^×G”ïË{ƒ##/¢©¨å ŸÊáBù#5¦ƒo+(âS‹ñyRÊGyÕgJ#5l‹ì*"'®|µjö³^KÔå[°Áÿÿ2Sãt¼È1ƒÎð‚wO‰Ø6Ónæ*šä^Î:2ÐCs2iålð¥)®â}CU±•™ùé‹Üñˆ´ò¯LU,çjv^* ­9o¸ŠéUÛ ˆzLÄ¡ø¾|ÎÛ¹óöè²êûüŠÔX¯ pB;°ý ûwŸlQ›óô”ñ‰1&ûft†`»yõ­ý~F“ô];Ž†ˆ€ÇÖŸ5ø£G¢=¼ù#+ŽrŽæ+™¾^`Q`‚¹(×ÀõS'ÙIØÖ™ÛhÐî·UŸŠ©uØþmÓŽI¯³›m0ÐpºG^w<úštãù,œcª˜=ÏÐÀ¼¾!èø¨!SÏù≨>PñïQÛZž üú/©w¿ô¤ˆl@ü È$+%[ÕÝ×#5m~8dÛH!ÈðÃ_†ÙÝ…ïh”¸íåx+#6D7׿HÝ Ú M˜¢ˆ• ¹m#5lSxõw>ÑgJÐrxúˆÏea®·=ÏD±’©þtƒP™hå.:|Ü?‹-YÎsžº Ю‡‹•OÁva#/ž¥÷JíG”n†ÝŠ-^vÌ䢷Êë´•Ôw1ðÆ€OF~c ºŸ4AU ¤–æ«ÔæÜôð¤—cÅ#+ôD+ê:i)ˆŠ€üf(Py ÅÈB7fõ™)Gö>„Ç•3ß«õCú>o‹ÅH|®œB/sú.ûgL+/ã¨*Ù"á`Ô®5”ûãóü°¼^ÝX™ù0nÑ¢¹ÞÁ¯l4ÕeíõVqâ\«{»¨¤YZ¦ÜÒÙ#561£ Š8¶ã¶=Ü4C3SßÅ ýì¸Cõ~ñ5#+å™Ìlh9//~k°Ûºêu<$L¢cAQÌÚúUÐX.!é¬ïo±›*}£`>ÖÁ¥Ú|ñ$-û÷ŸÑÈÌçc/¢‡IÑÝv‰9SoÖlŠ_Â5Öµˆ@í³å-%A…18˜‡ô¨¤aÚ01äc,#/”|ˆQ «•aÈ¿×Ó#+'ã‡õ­Ñ®ã1JÅ\âéµBìÄéé±ìÂpîL_©üŽãIÆ$ kÆøã‡6„?éâúN÷Ï3{×/Ÿ©Þ²¦2(f¹Úx'wGÈì9®9}0`˜ó]eÉü9“”È9rÎä¼íÇ8†Òfs®!†å3$#+îR•!úƒŠ…’©åñˆàù‚¬«&“nLô’ W|øK‰kûoî’—) Â<˃‹l³ùF#5¸CV%°Ã!pw4Eײ%>ÏH§ª‰h^#5^@¢àöÚ°e¯¸ÆS;.dÛˆgOn·ºñ–}µ¤eGJSn†ßs¢*«ˆ]R”N>ãÖµ!®Ár}©i>x‹•FØìÕ…œ…nݲvwÜìºVÏméùÐl®ýþÙ~ÊlüƒæíÇÜ›oG\ÐgÞª¬¶ð«ŠöÌ$…µuñùÌ‚¹Z›hôĬraJU±%Jkªˆ9B®Àå@ì þWÂ¸è š´¯#5šÄxrÙ þn;³áX#/h„>*TPv¹]j¢)1PeCÜ®•ÈßF'á$}gI[bÕÇKê~ú!ŽÕ¯KþÆ8,ákÃSØ|==[éçÖyá#/ò1‹‚PVÞVuš¢¤ùíñÔ'YI+®VÏ•²ë„æP„IºË{nÙWůã0´P  ô·&"»Îl1×r2)Uã°?ŸE0Y­ä4ÚNqeKÏ&¨öÁÒ¤‡.·ŠºîˆT¥3Q7®[«]ÁãVš«ÓI@`È;ˆ3[ ˆmKE ¹-Å¿‚å)KG>Dp`ñÖæÉE‘ê4UÙf¾›O¯<ÛûâƒióÐþáëw‘R§âË­ÙÔ˜…#5nc²æf=«K_‡¨ÁxsüëÛ¶Üɲ֡öõy¯^þ>ªáåÝçUœűò_€˜=”×Ø·sâlCžJÖ!zmt™66·àršˆ¬[^ËÛ6­7ѵÀ5òÖ|Ö75# U’qƒ†›á'oUؼ-ÉAsD«ÕKlƒâªEÆ°ÏM‘À_ í©K£ig)ÛlÇ‘CIÚwm@~9ï+Vªgey|hù>"uºr>»ÜoØß%Ñq€•÷>C1nBì^=Im÷-^7¡Ò꩹G!äQÆ;ƒKˆl-Ëx}ìüf1²=+µòæ\3£¿oŒ0“{û|#+†±ÍÃÔ¨GÄ}•(Mú•q¦[j¬T@¡¤÷ëx$ÂBÞþS°u+í¹ÖpE'Ïf*ú¿1_núbÛcv‡²˜ušbŒ=GkgÙ vƒD¸£¼8s¶½C<ÀÜyäîƒu(ü2U½®tØC“À#^©É7÷?×]ØÒZý"/Xo—¾ç`ì,ˆtrüa“ð†U:ç W%|.nß±&“pÜ­:yö|¹ŠG}ÐÅÂðªAŠ© £6pÇFÛ2l.rž³€›™Úû¶ÝE§u·n;jÂÓ0é€_½øϹèc™{ëƽnˆ~ÊÑ^÷àžd;)q´Ah!}Í]tOÇ·3|åMÂξÓ{àËi†VOëeŠ>#/æDD|,A”.þó½—û¼"Àqßœî=ý˼¹«wº¹"/#5äU3Çë3šÃ ósÏS]ö.dÏOùuõœâS7׌9w$0ZÔÌ*Óî0Åù9£Á^.[²t+ˆ¿]tÅe~RY¶`Ë¿˜oS±õí=½ö5éKÓ••ç *0cª{$¾P.NZݪbyZGu€M¤³«[0ô@šÍÉTZrŸÏÛ¿2c?gÁ§Wž1™ßŽmÞH,Î/áëf1ï©-I¨ôºHQñãYmõ¾7[Y”-f âõs‘î#íÍþ1hÃB#/´~ÅÝgÆ‘®‚µA‹¿Üñ³óŽþŽø˜¨”ÝöƒeˆÙd{=dVO«¥¼Îc•ë'1¡’ÕÃlˆ§:!¾X°¸¶Êƒh¡B"pþyÆÛFºÖLãìŸYøfvJê>„›@©cŸ6ñ}Tf‡=p§ÇŸK¶|à_Á:³htž>LœÆyìø€* ÚO3Óø]°Öƒ5Çžý˜“뜲f3^ún¢)êtÙÑ’¢lhh/(+|`üଳ@ÔôÄD Œ':£ˆ!h2#5M²sâüÅ¿z±äyœöîÐц¨¬S#/†+ª‰¯ f~Ot±U÷Ž1Œµ¥l#‰pÕÅòk†©$kj†A¬‹†±k®*oG Ź\WR†™j7­P¶s¤¢QŒM#/Xiipµî=ÓÅšrºæåjÈ#/Œƒ A#/HÔf ƒ^ïÏx}åç81Á”Îs N*õŸ¯~»ÎöGlüöñ·dЃ>Ýù.’ ùÓÂä@:#5éþp±/æ}¯ÔÿÅí›=P|#¦ú8ÙÜÇrà×Öö¨érºôÔ<Wÿwf×\2ŠâÊÉþﶌQ–¤¢ìGòl=gÍÏÖ3£Šnѱõ,Ã^*7ôm»ÖrUÏ>•ï¾8}&–s¦#5å:$"Ae¹‡Ñ#/´v3åÃã€t_ØéÍð–ÉÅ^1ù °a¡ß¦9ðá>Nþ“Ç1ŒB‹B…€ÄU &ì€ÞräypÞ†âŠåÆ–Z§ /æžÍ2Ð âcÓh¢vÙ¾V¼pnÞ×…2F¿>äYêeóEoæR/÷Gi‘SÇÆyÕâE©'?bÿܶ<P&B„€ÜÕª"ÙBàë ¶† Öô;㋵²3<&+ŠóÐ,uÂ$ 5=Éz.œ.ó;«ö„DAhËŠ †Â @SäÌ"ønœAAöÆb¯`3»ƒ ‰½G7\c• D…UYH)ÊC—ö0»|5Ì)NX*r:G!pS· ¶O_%þÈúô׎BTꙫÛUt9Ö²ÄAºú!€ÛºRC ²û¾‘“ƒžJͲ2„UuRæÒPÀ» E¦ñƒá¯sÞ<"qlµT=[ÍPÃëÒRWå˜+TqÃxÛ6¿HP#5}- îHA‚Å Ò“ž!‘©!•œ÷zfŠêÒt˜ ÷Ѐɲ³æ6εn@0*äOZ*e&ø:vƒº'R2ÊJ° mL$àþ·sG¶ËùH.ŒÖqx=³Ã#+¼DÀPksùÓ@¶T Ÿ¸ë°j$tä: ¼¢õAªó£ÝCc,BéÁ¸4{ïïëf2_/Æ+MOß¾'èCC˜éY `(Ü·4(³¹\+¨Yxλ®Óª@@îëÇË -}Þ½ÈG¾'y荒X+ògïü¦3}<¿£!yGeÌÔ9‡„ÊâÚn_§á«5ÄTg\nز‰\Érª”hÃmî}T<±ÿ œìñõs@lqr•SÜiµGè†,2#/0÷ß…·Íá°A¢ëVšRT¯PT±Ï \‹Ô´½ÂŠàÐQ€¹v½#M¥´MÊíœ[6‚›/ÂscL:adN¸(#/vNÔ]Ãèà5º€Òñ´J6ð.ÃXF¯enw<0⌟#5¬|pÓ áQÇjìšÆÖˆÅ2ˆèšãîw#+6i|è.¬ÝÏG8`ØZBíY@8¦F]!då2QznÍDS]Ž¤ôîÍ‹Ñ#+ä0„·—Aºñ~ç­ Ð4w¯<Ü ŸÚí)-ðëm{Ò&Gº™Å{g1æÓû¯Z»;¿]Ýùç)ÉQ ’ í «8óŠO1ÎMã:M…Ñ«‰ÚôXíUrßh£¶åj2ibE]Éàãv²™Sš#’¢åB8ÈXwh(§!‘m'x¨¤uÈš©’…‘³Ï!lˆjuçùýæ½›UhÚ:>Á⹶–HD§BgžR K³jƒ’œ9û& ˳í¶üÙJæNba:aÃ(ðp9‹=B÷KB¡(äŠv,ç_K•œ1ÆHžÏÎßZ·¿$DŒßÜáçðÔÎx…ñHB[MD'ˆ{JGtó—ÙM‰Ç/oãP*Îc܃þ²ÕŒþŽbqÚ:§—I’FÒbÀó.<¯¾¯Ó5‚ª©£¹vx˜`)×"]¾\÷öW«˜µ!$)"¯Û¿|_î}¾ß.%­“QÉÇ@~#e¨\o!¼‡{†Â"YÀ¿y¯‹xsSYQÐ;9¶p½i‚S*Yꮼh²‚#56Éä"„¢#ÁDm5†Ü#/ÆöAqŽBÖÚtÏ×€S{Û[€¢ [§ø\÷·â_'.2Få–í!àŨCÇe÷nÁ¢¤‹mÂ]A3BF«ÓpÕ–6–¬e¼rŽú`ž;ï)ôÈjŒC:gœ'ä–ÿªCÁ´jD{;m`ŸjvnY³·÷ûŠÿ3’B}æ-ìå\*ÒœïwøP÷~#/£ë6l68¾ÃáEÎ4/ª®²gj$U¥´!Ä/·gêDw<³=];¼>ÙÃËîWoXñ¯Œ³”+ƒFÒÎ0Ž­ÑÍÒPV ¥6ÕŸOw½—Ë&É&(Í­Î lCàoSGP†O##5#/S^Lá;ž(Š;(# ±g!󖮌2-Mµ‘ ÊëÎ#+Š4€”k×{2X4Bj05q zQÝ™Ý>Üó#/·ÃñygFÙÑjI3ºgfq3é¢Y-ÉçTÆÖj×Ûôà†dåÛ¹“V㉣‚0»On—¤´æÑÙüÔ¡¶“N²Cy×<´‰½==ƒ”'pÒgpxaŠÙ¸ÌWS ºÓsÛ†ìD˜âñn#Üà.Âl¡`A0àD†#¢3ÊÜ>µÿe¨;&Þôº_ ÔeG(Ž”SfÉÎv8»0»ÄÃl˜Õ»y‚»òýgBŽþÜëÛ¼c:ß=¥¬®5£Aœê>ð~åЄ¼!/#5#5&uv ƒ ¾QŽ‚­˜Nü. {^wßoCÊ“HÕ2-Nè4ÔHÈ“mÐÓakq-+­#5ÄQܘÎnÊKs¯[Û#/@å%ÒµU© ‡#ÁØ#/Àå5lf–ŠP ê~“ÐÛ6‡6Eë™=ùòyo™²Êî(ï÷ǧmAG#/9•ÑBlÇ—”LÜ®MmËJê#+mÑJ͘®ÅÅï$1Î#Y”žjLNŠ©²ófRAVŒ£à¹f xÞ3˜Z¨†ýR˜â#}¤YØmÛÎñ³ƒš3ôêåv„#+Ñ]wQî§7æt#k¨¾+œÂÙ În5+L$Š ‡„w¢;ªÉ}¤Ûõé™-¶r|ðM'Ä£d¡î8ʼn@®Åo÷¸;Š¦ 0GÏ*=âèé©üKÁ†{M:òYõÔ]î½!êô“2G»¡EûäüÕWçÆ°šÅ ‚ۋៃÉÊüÄÎÇgŸ×Á#55žhXȨNX‡F4jéÕ“9Š„H.ÍÌSt1³w[ ƒÖåæ÷vjw¥ÄÕÎ,ÍÔÖw›7scË ï0¥v` 0³Àæ€]žëÁÿ]ÿÕ_‡Ã–ìaŸŽê»ÜC·Û/Zûˆ1¶ÀÇìGÛMê+(>×VOÛŸ´"#/¯šÝ}à pS3 ‡cÄÌ ÝèÏ­þoŽL^]rë8…ò°èÒWÏqÁˆB6‰t¸Zùo€²¨¼“B´W‘¿¹„è‰ÉA}`<ª÷cº/ÂÜ÷܃¶ëžá‘Ë6U\ïòtùüóôóܾ³1é…‚ 6äæ@)˜2s·±ïã#/||cE·Š®ÏÁ^C¿Ã¤|kZÿpû#+õX‘Þ¶P+Áõ&~SÙ=íx$#5(Íü#5)„QÍçR_æ})J6oµõt‹oÒ¶±{¸ÍÅl¦ø­ÕÕ·%´ 7·A•ô×?ñ^ï¾<]šA‡.Ç‹˜[6QÍRýœoœåVªu_é™ … ¯Æ%ŒììÖ¤LÉK¥ VLãÎ÷+/¼Á¨m³ùÙ*¶ƒrÎþ;¸ˆžÏ#5áÍ—)V#T\f¾#5\‡ðo0oZœW§QÍã\®ßwµztrm9#µ¾ÚÿÆL2-:öpxGÏE=ùÓ@g·ç†ªÕ¦Ð&NÈ#5Én{‰d1^iAÁdipTVÊähÙ&]EE6XV:û>Ù XŽÈ­dìª xs!XÔ¶ ÒèfseðútÔéO>Ô‹½Q’¾iÎX¼ú­&Dxˆ:tÂV€yv¢‰—¥Y‰X§™àR:A«í÷!€i¡íç׿rЇ†hDX,Rn]~nuŽ5ÀC\födꙫ7 T-’ÌèÅBjí#+Þùm+/‚0ƒ‹ÄoºÈ0ãør»ÎaI{â^;&»Ø7¦ðä9í苵þö+“•$l1È!ŽS#+är4$Ò½8NKçoïàÕ0Ó¿ìîý}ºñ’‡›¯,†\MÌÙÁïFñœ[†•ƒ‡Ööï½áܯãÏP®ãØU6JÒób«±¢oìÅ°Jõ¤}#/£\å‡vPÎ4.†‚¢$šå¶ÿú}æÿÈméœ}Z=È=Šþ±u*#çèðws°‡{ò.Þt0RñæJxlO`Ä'“Éý#<+Á2Ǿ€5¼YJÈ "žM:›W#b–¯lë­£iá÷bF0£Oˆj‘O”¾IKAò)ÎaõnÃÎkxp7ç%­ì¹Ä#/Dµ¼#3ŠÔhxaÊm(v´$1õÜûã¾Ë/½þãQ»*[9…e}ûW+zQµîê÷¬€í œ Ã>ØÕN«)ª“»á‚!Uƒ4;m\ÜÄ^/à ŽÚ‹ú‹‡¿èÍ6Ù§?Ǩây;=…þšCŒaë#/i¤þx¹í¹„o=½:ÛÆפøí3³Š„Ò‘8ìUB­žûÕÎæ>^!ÜíÅ™6oƒ÷YÞO×ÔÀÏqݹfÓ…¼D@LT;Õ–2ÚiãUÑ$ùÚÊSO‡á(>Äã[Ì[ª)> ‡5žU#/\±Rª5‰)8Yµ‚ÃÖëm+) #5”P¢ŠMË¢uwÖ³f^óVT³#+;!,@ã@™5i>RD­ÅkJåXßó†ÍÑ«î|C~ ÖÆ#‰â\â &äT̆¨"6¢ö‘eÖ"&SÂ(}E!SÚÛ13[;鹧6ABŒ0ÄaÍCµ&áabQú‚ëÉM_À¼Q¸_º÷‰¦Ëóß¾”¸w)¦O–±ý‰9~·U!鞢ŽÏ:Cç^¼÷ ªX1—!úá¢y¢&±ÐÎѧŠ×^ßÃXý؈Ƨe)iÜýq&+ò†Šùg‘K|)‚õú6{ í¿ønBºß»þ(Ö%§†‡íò|^t§2{GÞ®ÍîÞ©<ãM™ìµø}ôÑmÁøæ ·ÚbG*ß·³µK£„X:î…|×7D9‚†gâçXts„¡õ˜¹MwéË·ÌÆ!ðÙv ò×P^+ ÃWù$6ô/û/Ù~.¾ñ¡¶“×öߨ“è«økÖNg¤ûŸ}@Ôßz§>IYí #/Õ÷oÎøÝvXª-Ýßé©X=,žŸ7qì=ˆüÄMèä:²Ráóú=-é€]B;\—\•Ý_;$]'S©57þ_yü†xün¸ÿ OàîA–ð`§Í#/qüYFÈt$5#5ú¸ý)€çþº’þNmA{|¯Gx‡¯[¹Þ·…@6^NgCmnŸF¤‚Ltm§Ê>}Ò@žÝÝßѱˆÛ¥àº|66|σj¤ŒF šÖÐäAy2>’0ÃH»¬V—<ȃ!¹ü—ã‹”—´+s|hè;$`yúѤzn¯[ª'òÇdà=v³œ7‘â #5/׬fç‘Öw%8&ɱS42r0)fI:h S[¢u_™Š£<‘ÒËläŒq¡Þ#H4+ 1ë üCptkÇ9çM˜·oåÁItÌÞ¶1<å3¥ÙØýœ›\yW´CÂcù·¤Ò{ÿ“Ú¯_þÿwYqž¿Fj Ïaañº#/}Ê™X^ûµ üdDhè²­6å3Pä/}•°ú§ŒÇ³ÁÚ$y_-Ïä>­§7Ã¥s>{ùïŽâÊ0ƒ©ï·òØò‰l~Q<©LuØ~$ßÉ©gP+=rõ¾À,™Oßg$üÞѹ´÷€ÐÝ®4Íã¨%6ØxAîã:-¯.ÝÚÔßSÏF1éôq;Ì~S½!Ìà‹Íuq‘´ï‚"{U£`β©(lšæŽë+ƒÉ'»…&N;q“PÛ>d1ìvã÷µøo¼y·Jb„š³ÃoÇYë;sÇgE©ߩͦ/”¢al6¼ó»;I£Ö(çï:í7õÁð[$>ïV!7éW»iEÏ@2Ö%~×ð¥%Flij#/ck'p8bCeTq€¼Œ“Òƒ*µúu6ä+tg±na\jEÿ¢pÛ9‘þxø9?9F}&þ+ûçÞ‡,FžcYÐæXc’¯_7•x¼îúöK!ŽQÞîr›äƒ·Ç~c0hë‡å‹J™{C¨d¸À‘×=à“ìÝ·©(/ü[À·àËðSŸ{Oúcu#+|µÇ©eÓÝù÷tá¹$î¿m1ŒÉåëÖɣíýŸ^«¥GãŽüze(^#+b¦«á L”ǽÐs‹¹e‡Üe¹oˆ´0ÍáûŠs #5 3Ô&Ï“•yw#/2š’&Fy‰"زa„ ´Æ·Ò)ƒäk4¨`KŽ®1Æ0 ‰²v°­¡ÉU¾pwäùñ^ž­¯ã±êúôSªvs­þRõ6ÎÔ…&ä”q ½êzÇ´i',j»7Vc*ôZøÉÑÔ¢ýp ›Ý2ö:÷Å3¹W9ž Wë"‡)…T…Å2pRþÓ¨S„楘—{ÖëÌói/CÑÌøqÇiÚÉv¼Ù<âñ:£¦ò‡f>#+ÕuÏnó‚\sÛ|¹p]U#+½RU§ ý~Né ߌM`Þ>îHM?Æ¡6ß*€1Hì‹£ž8Š;%Tˆª0¨­ê“ÁˆoÝî¤ëè3 ÃüçÂÜ‚ð‡‚q·¿gk˜bÍ(•¢"\ËÞæªÐµ{òHq×}÷CØmc³¨fnt«’z¼ç~çŒ$/Ð!óÄ?[Rí©Kãa›v¯¶+–1mz(€)0Fm=KK6Îo]¦ô6P˜LÁd}s[ÔKôüew[Ñ\éâØ©Qö÷â¹Ê‚ª0DAb½ü|q®›!Ô#/H%é×­ƒ*È@cmY[cU•6Ö’|e¡ ƒ=bNøÎ6¬íÝ6èjb“;+2ÕTxoÙp¹<A)ÅoUÇ€ÊW«$úŒ€‹‘½v’(È&i^<”ªü­ŽOKjmÌ—]GDNpÖ/!0›Ve#5Jû²9‰ñ‡mjÝQp€Â襎RGê”o:N—úúµ#5Ä$”ˆ—šäõ<·èª8 ¼åڰΑÎ{°œôŸö×c3P®]‰!R‰D½%ö¼#+ðù]6Û²(:©™k?m6Ng:ô9û:oÓrzÁGíÎS^.ª&¨‡ÑÕGzP<Ïñ‡#±ð³ÚÆt¨vf\Æ<]üžžˆ7Êp²c‹üЛŒ–ã}&ù†ƒ,Á“ îuõO¥ð«éšÅE{¯¬Í’~&voFö÷sç€)ú*@®dȯ/š+ äÇ}h ŒðE)VmÙÝ·¿;ùO€½­Øí yriͧx³1´çÖcÞGøæ‘1œgý5ÆthÖ ¨q,!¢—ÙxÏÁ=/go]ä}WXoÅX»=Gup)­”ºÑDk>fpá::Y‹ œÞ‘ns xéˆsÔnÈènª¦ðRøèÉbO[*ªëZˆJ0+ÈÌF /áúõ|¡#Hâh×/¤ÃD›Ôv_auÙÕ‡J­WAb…óA!xY)NŠ¢è##+ª&öP€hB@z‚™ÃÀ}·.#+*¼?B¤KR;Æïì1ê‡`ÁŸBØë/Çnuô|nÝ$‚„rû0LˆEš^õF2ñ)i“9ý#5›õêjB´ŒÓ› tF|œµ‹ó.*m1«TFµº™Ç`€|Õd°)¿aLPI‡KØDì/Ðàò$]LÙ#xxCTá®lýZxY¬,µO…U+º->ZK°PZð¹ÚLWR© ª¶8«í$ ¨àÔ—æ8š¬#5^ÚxÕÀb_ÒÖñ_LØEUN[¦¢;ÚYÓWîãQÅÁ:<8ɧ²¥F§æþÌ_J<™ô-S¡u¼vˆ¸56’lÈ5¶öO±ø2ŽõAW«ŠíSÞ êGÀï÷7¤3ˆî#+wß³Pþ~¶Ð|} ¯šIÓŒeXV‰TÆaù»Aè˜`òEÔE)^zæíD¹ï†ì’ISßÖ}ýM™›lR„ (#/ É3ÅÒcPá#/”<Á˜ýê6¬·mGyHý–þ=©ûÜç(ZFëç4oY ŽU3r>iæUhtK 2X€^ÿsÅ_oèé…]Öbý6Òþ1œhÈØÂÜ&Ù´Æ÷¶Ky6SÃç‡5ÔÞ ÉŸæû¯ã†i1¿=¢¯_ͨ¬†XõÞŸ·Ing7H*œY;önŒ=ñà›}”§7 >Žn©­k:‰þ§ã‚¹–x–hÓ‚ÛH`jñR/zîìçŽ08 ±B-0)XôÏ!á!$zƒ]ƒ54‘PCEUTE)KEDYĆ!К+1¯ ù^¢óãs(|›899=5Î$F’ÄÞJjmÂ0•§lež^D&8I•Ëõg“÷½ãç®â¡#+ù0–¬%JfÙØXSf[žû¥#5ks´²ÊÓBTˆ4kÞ#/¹¢ÈíôߊñÈ£ª¯gnǶÚ;ž¦#/ÄIŒÇ§‚+Ɖéë—vŸÝ©%3òì9àÑÓçO³âþ!õìŽu½¼il°ØOI„„‡ÿ³3­—²µß˜Ž\*wdŽm‚×ë)áÛÀ‹ˆ¨ª2hç™oæŸY{õÞ»÷Ç5’þüB ’`ø}Ó0ãÉÂ3‹Q¢ëHb\å‚‘¦bYŠÄûœ#+>Eš{J¥æϦ*|ò¥2ïçãèL­æçXþ‹ÙnrðàØã›ê>ØgÇÈNÐ0ž9Éíƒ;ãöæA„¡¦ÓyÙòêS÷™Ýð|±zNð·¨ÏtYÜ`5Œ¼·õüã®z/=âKÊ#5*Ùü®ÊU¦ìͺË8É3¢ G¡0@—”83[D¨ñ0:·±°piQr|Ü’8X=4˜m6½Ã€´§OMÔBõ=õ·£¡ëoJ‡í¦Èïƒg”RjT¡x_)MÓQb,hZ¥ˆ¼¸‘ì%o(ó†b‚¼ªw[Ü›)(—xœ(þ½ÜöR¢­Dó®v÷cäi¼öôMÇ6l»½ZÙ4Á‡†Ì\˜1Dlð¡{©n¾w{[Úâ+Äznäò';pžéýž~mh:?RÇ]GÎÜüc׳ò­¬s>·Õo8*·bBtc6Du±¸Ÿ]WiɽcÜXë ùMúv”ç¤ï“‘ÚÔ#5ì#5—É. 9í‹qžÉÅ”-f¾¦â=4·$cÌu–ö!ü­Ô³úóèÀ%µï‡öÀ“¦ø`ëŸè3~ž`ÐÝÜMqôÅH{û•‚‹ÀýÛÓ3ùÇž%È4m“…üœßÇãé3iÜr§o3‹ÑÈr1g–*E×Ûb¼DÊVo 3Š„H·É€“èæ}ðöÎØ0ç¶5§×µƒ‡\c{HZÁOÿRQ-øué"9@èiMOðRtom£H>.km±´gç’ŽÎÅkX­ZâÆ|¼ŸD:mðÿ»júŒN‚Ùø™Æ ˜®.ópD*ÅU|É—*:|D­HûaÐú1u¾Ø×7¡b H|•–]eg >„ÄZT.Þ\%…++èªÛÜðèÃ+l)ÿ% —× 2À—Ê9¾Pç­of’]z¾þ+cNw·;#/#L2u³ÏU®#5‰ßp¦(Ê9à}V3œufèÒ¦raÛO¯ÁfŠÞ'^“É|G+*‘½'œª%Ø~fV`zuˆè³m¥úÚknn#¾¸ƒŒ6#<+\ñZZÔçTò Ý9³™GlíXK~üNxÎ¥òܾ8¤p‚P-¥”™&âÖ|§ZLj¬ñ•âõ›S*^üš†ªc–"xJè­f°rß;¢ Э C#/¬«HÖŽ©´Bà &ýÎ5v±BJ#/µuºP"ùF5›œ÷ÇàáåÉÑ>û9O¿N‘;â„´úô7¾g«sÓ¯ß<™Çé]8’~ÎñÎÓ[wusÜó˜Ô×XxeW5Ì<~DáÁs¥8ô#/ˆ;wÍÙóY¼ÉNqGëÚ1Óî§#lçh›}j&ÛQ#5‚ðù«ÓéÇó±|j¸Y’Ž&ÈÓêësF^å°³’:½Ö¨9ƒUÎÂìï©¥dæP Û«Iã0VÆ&ð÷‚êSe2I7t2Ý:º–3¾Zʲ+<ª’” Â’¼¬«T­!RÃv70HN‚Ug2æ¤]}Œs[EK<Â%qj:oŽç—~2ÜèÕY½eVê±… u´çÇmÓí7µcº6Tø:ÓV ë>_„uYçuæÝooÃ^Ÿ}çuËQZϳ_”°{á¥\ êFµÄ`çȱ\lÄÎR}1²Î|Ú\ϺYË1"#¿«èñŽQŒ¯µÒL¡•²uú®RÖjYít'€x†2&v"xÐLøãHYž…­{µÙFXZ:ðˆ‘‰Ó*Q×Õñc€¸ƒC:`ùÖ­)ÖùÔ¨/F0첧¡üøÚ”ú¢cb‡¸ëŽôØ A…IºCzb1yN:án¥–ˆ}š'hsZ;÷žbu¾°±½i;/ò;ãb¼tæ*½g…w[ñDø쵎ë8ÎUb±ã¨ªO¾ÐvÝUöïnZß63j&£WXÐEUHÀˆ˜]#5=ÏÆËŒZ0¡Q·#/Œ¬-#v—ƒe­§FÙž–¤Æ–tŠWŒ4¡³ªºó½lÛ¹­O|mU¾ž5¶fö˧‡#9ÂŒð#/ÞbGÍ⇎ðØɬ¼^Ï—£WxÇiÙšÅGIαtkÐIf3„>8ÁU±ÎãhTùÈ•Aùs­¤t:ŠÊ4UÕvñG­˜ëµò÷·m.-ñ˜LN|ÏuµÒí3G [ηv'ôòmô ÇÒ9)¢[´1fñߞ¬ÓwÞp6½#ãoÅl‹Ú KCfRò‘„#j¾µ¹¼}²êw¸±þ@¨JK"·Ã¹5#564XOÎ÷`#Q)® "¶TU§h!’ßj…í0z…èfôN¤chÊ¡Õi§˜}¶:Yk¦qïh•4 ÿ6ÜûKabH;¨1ñ¾³Ž`º×Îo÷ëZjÎS•pÓÿls“"v]>»»Jz|5‹û(î§ôáð§YúPJò㤎ɋÌbf6D&eZò+¥I¡u{®ÿ¥t{ÍåÊfaIE¡[Õ‹³~àËå*qž ®5bZ ¸‰:.{±)#5àô?o‰µuÒàýj ZŒ.X¯tBz~Ëãu´¸¿=‰)2—Íø明}p±Ö®#/¸XûjÖÒšì›jQv‹!A¼†ë”X‚’0õ5•ÈÄÀD(ÎS#/ÛƒÞ{²í®W¨Jø«âe—K0ÖA&U9WLvëÓÞqâp“f=ÏNÚm¾k°»eÌaØ´H¥â·Ô%û~Ý«Õ›\ë1óˆ÷‘Ñórñ"; ½;Éo^¥ŸKÀòGö¿’ÎÇ°zxÏó?mö"…ˆcN,rš«×èíñ>e|dèòø£Ë[3õàà8kTÑ —!ˆöVAöÅÌ:#5#+Æä#5(7%Â0~M™‚Ô¶õÒüYŠdn~ÀO¬kvRß·žó ïØ(Ä"%I,êsÇ™m+¼œØk®ºo]ŒÐ&Ì2Dl˜…1 ÞbUõ:d8“¼*a¶í¹¥®ò, ,x¨ `d#5ÞÊÆEˆh0Çdoã=ó^w•}m;ûås"W/φ”ÒÚÛQµS‹T9NÐÛÏzÝBt,_Uªg,Ä88 H>LdK˜¢Û2ÊÉ}ŠMU’ë:Xcû;×XÑ ú)bI=š "ºŠI|/ÚsÆbtã§Âƒ)–…ƒ7¢UO‚0udÙÖ’«š‰kBÀ$ƒC#+ì‘7hÙ(ØÉ­ø#/æ‚q¾=§ÌûMÇ“_5˜¡AëêüáüìcˆÒiD6Õ½^¸JCÉ ht7GF5»WÓ}¡‘Pà„$PëÚq‹,·6z/©*ªp3#/´¢ +H¤#CbdÉ![q¥„5¯¥8_6Â2¡kV·Ò7żõë­´´˜Î·ƒ™Œ>)KB"‘ŽÛì²J*ð½<¼ãy$1Lƒ€ÆÁ#…¬f;y5F ¨%’d-0e1Ä¿*!Çû.ëo;~Ÿö§KκÙÛP $zØ¿#™[Ò=ç(ÖD¹Ug錵¹®Ö­Ì Pú¡äk¹š~…xÿƒ¹Ù›aԇɰ|¸fS¥ß±¦W DN!\û¯É¸¹CÑ'Ì·ÛÈظßüáâdFÞ.ÜtSÜÈ7êõá8×¾2œc‹L•ÑŠÃ\v‰^$7‰>½áWx8L§_BvÅ ‰–—˜q¡Ù{jJúq¹ùØW0΀€Ã.o²ì`'9H÷¸¨€Ù€òG*ìòKiu"'u…~´~J o™înø>+–ÃÙ àÚUm¼äÔðôÕüwü¨;7ÉF;L³1RÒ†âÿ—ÐÂK¥Xn—ˆô6ØKÕ¹Q`ŽÕTX8[·HE4Ç„CP,‹¶æ}‘kƒdU^8\åóéúvâwèÿ_†ô ã6JHI –è¶ç€‡¦Ü?̇>Kx——qôÉúþ³ ߨ#/¶GW¥˜h°Ôµ<%_¹sÝ»¡jš 9l@@ƒÐ0¤{HD@Xþ—Ur5ÌŸ”|JÑM[tnЖ#+F ¾·#5ô±xñY Õô°Š•R%=¸9Ñ#m@zŸ*‚SDôÏ'jx*M³ áÚŽ¹ŠÙ»½ñ_8Û]RÍeÑyÚ<Ó—cuŒá–O…ti¥f_”>3Úzð†ÜêÒ]ÆÖ¯nÒ祸W~q´íè~×ÆæØ.*¾Í®§ˆla{g¼õ?Ø¡¼pVWŠ;Ypm—–8˜ê»(hU¹ø£Â¹ÝÉ¿ówœ|óû{ÁÎ9c9§ñèBê‹êùõ(ÝfË;ü±|Ù€å¥}’Ôü£Þ8Boh3ÄŠ=Ê®ùþÖq7/ù~•Äƒ£ ¬8¹íÈÃw­Ù_Ômiq#/Îî7r<†DGwZ»Z_Íà¨1:© ÂZ>Œp“U±—;Üz3ÄîMâfOnÏ„kWó[)ž.'.÷Ì`W¢õÌýóé‚ô+ƒ ½1Gðºæ¢‘q›¬Å´v˜ÿF³‰þ]chÀB†¹PæÊ?•‹ÂÞoI7†#/¨¢ì Á"g_Œ0së`b)W'%—¥×m<@ƒ¯ƒS‰ðŠ˜·Mª‘á ÀW^†¾%q=¼Ê\'j]x_Þbûú׿Žø …ñtôäèÁ.ÿHDDĬ¾P@U*YUJ¸yz½]…Ši¬˜€\7ªRAdoV Jjeä"bR"”Ðâ¥t«‰vO^½îós×´ÞÛT7ÓdAˆ‚îH,Doé½4†ù06Xˆ˜²Z*"iæ…趧ч„|p6AŠÝ÷ÃÛ€‚´‹&´±ã3%B± X‚ÌÌìñí»‡P]yÖ ÓÞŒS@ÈZu]Ym€#+d,h¡B¡F&<ä6Š{¥·¯•—.ÙŸ‰kUõ#aÓ§íCã\}•ö,Ïd|KGh~* ¥7¬°Ï†Q80ïÛ™™‰Õ#5#5AAIrF!#/Ic—«„|¤ø>é_·Ä%<]ãÎ]â{1pè¥Gpà_.Gù~œzþiU7Ъlv|ñ¯áþ­ÞÀgæ7Í9'lüƒàK¿‚õïþ/ß傆0¿Û£Ô÷>þÝÌß¡WZ¹;>UÛÛïü™š'9D©jñr­®u½*Ô ïŸã?åút)?¹ ? O®#+øk?~SxáüØöÊz¥M ]¤ZÔ>‰¡@\!ÿ¢H§îȻºyIÕ‡óHž˜O²>›h”*ñ”.#+8XI’{%Â|°ýÿɯ-‹„/I@Ó#+¾ô@+æ_žÉu,Êr—Ó(ÿ¦xGʺåRgoâá´Ê:J&òw¨_Thã{¿[”¹B̾aËóPuºÄ?¹D…ƒyg²¥uó:‹mÿÎ#tU|xY_s%ø|Òäè÷é_#/[FÚA8é'ƒÌ2óßv&é·ÍÕ/ÇÙ&ü!äÝQ§ñ÷'ÒŠ(ÒƒBŠ‡ÊQÿ˜ECå)×+Ù/âH´ŽHz J:ãcïÎ/ ‡§×¥ã’‘ÃX¡Æ¡àÞyi?…ìeWÎ ªòD¡~š#+ÿЄÑIØ=~8œwA‘¼º“Ÿp¹!´(PèÀèÀ·ÆôáPa5CgDÂHOÈGôfá#+•#dÛ»ïs#5ª«NRñ7w ò¦šâ«ª#´j^Rw2¡ÇI Ì9I‡·røC¢üOZ–ÿ7ÏšÓÓýñÝô™[áq5w‰\£õ2ñlia°ti7䃤ҾdBëÀC)é?6vóá·ÚÙde RC¢ëŠ²¼/üfj EA šXŠ)¢‚$) Š%†R™%‰(IJ"Ÿ#Qyê#5‚¤AÞ‘3ú#/P¶{J¡ù.‹‹|.’Èrß®]¼Zt‘L#/îwŠß[Ÿ´Ã­Xârøá€ùÆ€>áóÁoõ…<=ž~„çáΛŠüméƒù¾yüîL’w ¼ã}1?«L®¹á¨#+sº°Ëß×”'¿–sË+›~K÷t uÕåý[oÊûû«ÉßU'Ý¿aõø¾ku×Óùùoâ/“¸¿!ñˆ×#+àGH_C£&ÿ%Ö.Ü#/L¾®›\b®RJd_ÅÙë:!èçF•©—‘ƒ¤^#5:écTü‰ö_œÃå«áo´/# ï@„QŸÐ¿¡Z|Ú…vÛ‰4)š¡ÖŽ×LRpîSO’?{z„þ´=ᬥg6fp´š”i0ØãHÑÜ©æéuëÉwž¸ž¯Py哲Fv*K¸`ØaSY$™Å_ôa»ÓaK£èüÞM1Ýø‡]ZŽã‰ÑÁ@¼¡BÊJG«Šºý_ïÚ´äàfìI½m@ǦQ±½*ƒHš˜ÇH‰i˜ˆÆñÉ?Ý:%þÅÂÆQF” 0hí®i 1¢Ò12 ÓN†»Œ‘þÔâ†ÁÉ€¤ƒú`øó&6«mÙOù`A³™Í#"E«A§Fnu‘Q„j3Ž.õF:À°”„BÎÔ¾üI!h&®¸iV˜0q·r ñ—«ŒëÄ\Ù+¢«•ÍIk!¡£Z(òä1Pô‡EòŒùjˆØ=ùÉñEIãügÕ¶Gþ F"¦G³{R´³v«.‰’–³N³¡ÙÛl5‘µ©HhV46½pÜ°UlnZÑŠ æ1w¼äG®îöŽ–昢/6½[­äšär¤S´À¤#Õ­Q‡þÝ_øM*ØÐÞ¤9nÚ vÅd ¥[9j\¥8¡nò‚(FQ3MB´c­°‘‰)¢z´sì#/ʈ$äúØ5Ȥù÷6[XÑ[~ ÿì*ˆvI~_ë¸pÈJ²!²B^£o/ãÿMCªRLîýVƒ©®Ÿ`ªfˆã×ÛÛãÓ»qν”‰Ù°;ˆ–5hîÕ«¡õ¯j—¯…¼±¹ô O-@¢Ã?,‡€ùGÞT<|ºøO»òÔ?ùý¡ï;2Î_zÏYx)?k>ËÿÒ*`()j ˆYôcMá`J¢‚e”‰(‰¢ª™HáD|Å”“àÃ#+¤–€üc~Xf—#+SHñc8ÐûIåGÙ<àr˜ç çÕ#+þeBÖa-õ #//oç;4¾« v냔;îÿ…òƒˆ%*àÂ)ô$î`ÂI#ÿ iÃúÿ9Ù×OŸý‡0÷‰¦ëž¼+Ò¹uíF+úøþ›þ>v¸8~õ0…‚´yýú{¿ää|ØË„rŒ(ÛvQB,#/“F@û?ÛÁä¥R= 1EJö"(L$?„—ôˆT6v##0ÂPñ†ëäŸ%S³Ê’¼%PU[mˆÄálÆS–ˆpÄcNÅ$^¾›8ŸF€¾‡ V”Yøwî‘”Ž­°ÿ‚ª™+™‹D.?êÿŸjtkFOøÏïürÍ(üýx\˜àñ¦ë6ܺ·Ê0$ÉfvïÓ„º¥ôxÃ|P¿ï@×Þý45ªÑÓÝñ¯ƒuƒ3Õï®÷½ Ä>ó­4T;ÆF´™ö³¨ÄvòöÓ§?ªÃ¯XGÞa³)héì„,À{ž÷üDŘ»1—À09hƒF?˜ŸrRˆ ªÂàÕZå~¢¸¼_Š>!0_ärbJ$éO×Æì¶'V½p¡ƒ%¯Êf@ô—øƒcM¯)¢|õ XŸ7z“ b=+#tTaøˆoþN_á™QéP¡Ö´,–¼â=Ÿ¦f_èÓPÂ8LþÍŠù¾CïoÔ¥“ƒãÐ/:Ëüõó[åù~WóŸ?«Ø­ý¾/1ÓØzÞ<’Øv‚>b5lZS“=·*ëoè1ŸFtíçOÞG$%Œ“Ñáò²Ê{½R—ø#5‡çoÙ[} |K}Çùª§%½,3>#+õGÿ.û~Xä^·Mª*ã«—»ûvó™#+éƒ9å:J'r zf$9^ÝNÚE?N¾‘ßù©m’QÆ{6ݼU—äØ–Ü?¸ŽMn„ÿ½ žùô¿÷d<]9О&eúC‡ äáÙçïò=õßfAá´öžŸ»©âgžx#/kYc‘S¥Hepp#+ª¢<Ÿr.ÆdnAÍ9ùnnâœ-´°¼_œ)ç†Ãù´¦nzgéUñY†¬JënEnþ5–Ï\<=Ú±òÚ½þ·õx|1õ¿ùnhà5Æä³yLg×Å@>Êbþ-ß0Rþ2Û7v9ÎË)¿èÕÅS2ƒÓÒ£!Uò7A¨Ï˜\*ƒ\ãÜS¶jn»[‘ú*j»£»Ž+/è_×æûê‰ßŠÐ„M#5xÇÚ˜§ƒãþÓ<ûüú]»ûâ‚ÿÕ×蔼 ZÝUþû§÷þOÛ À oò“£‘:Qãä(Ÿô—=èÞ¥SÑ@eSØï{H¼¤©*=´­(!¯Ïcþb‹­« ÿÄ׿øÌÖu²>²Òÿ•pR«ë:ûùÔx û]Š 1^ÕÖjÆ Å)õ³ÇÇü.;ÿ-ÿ6ç=kŠNãð$wéF4qî¸)âšðÑe˜w®öȃ/«ëp‚Ox(™0L6ª~N­l>àBÝo_ÇSžS©yHùÞjRƒ×ÛÌÀch$Šs2)F*JÍXœ—“I†‹–VY˜(g0eQÀmÁ#+ô’DNB3"·.Û]¿w›Ñû¹|þ!õþ®ÿÌê…]yólð´7úáóyý_ sõæiۅͪÓÊñú=€.ÏÀCã-<•g^•÷~¾FJLˆŽ&0Å(ˆãl^~FNó›!úˆiÒBj¿æü‘õuü~¡ž«éäŸQ»=’’z:6æDý˜÷ž‘×馜›{7z>8sôlÓFïÈE…ÙþÏåËÙÓw'Ξ‡kþ¾®{ïÛù<¿’n¹röu ?oNèNúy#¿æ_ÓÓÂOedz\qûV÷} èNnaꊒT|_2è¿»_ê|‡Q§2:K<ì=îr#/`›†!ú0âjB"[&Ñ’|¸ƒ’“RHÙ“™1‘€?87hA¿2#5ýBl±:ì&ù95o<¯s†óÛG¿Q‚)ªŠ©J(‰ hª€‚ j¨ $ È…új‚ÁNî­Å}®}gæöølëýrxkÊÿW”“ûØh‡ NÆJc£ÒÃü]1犭9)IJGç–?«8“âØ¿>MD= z&î9@OéòÒwu˜t²Hú0žw'ª9„œùþç•#+úO÷9Asÿaý¿g‡ÛöïßÆÌù=Öòsé:þŽnzõË>·‡ íú>ˆ¸4Ûßô>¿Zgc¯éóVWßþO}:GÕû}| eêˆû?>¿Ïíùý÷Ixpü¾ |]\/eZœ;LG9N^#/Äj‡N+Ñðö]íz¡÷ éÌÔùú5¹z‡‡ùwôýõËfq\#/îí"ÃwüæùîçöÄnìè—sýšÿG´gšN“»§Ì|ØqƒÊú ß ·6£§’ϯaÿ<4íõµ‹†'ãæW-ß·²n89~>¤£w|yäîuoU;!€ó;cÿËÉ×ÛâaZ=÷åþ=ý=~>H¶‚àïrë—ÌîxuÛ+ºwø~€ÃóupÙ«Ÿ³†’ÝîÈhÿgv‰!ø§Ñú¾x‚2=IMäy±ù1¾?âè¿g91w½ÁÁ·U¥ã»Ä;2¢sWêú =zž=ÃÛ‹#‡ª’µ¢JKýúŸB哸KÊïÛŸK±èýfô¦¥\•…ón¾Í«;‹v}—¦ÛäÖ.¡'Î_#5Ô»›.0Ð7L]žÜ¾eòÎþŽi\Àmð¿™ýXe8<}“¿ŒcâÇ×G'§å—\|<³tÿ³›j<Ÿ}Z9ã(;Û߃rS_Ç—^¼Œ¹oŒ&C\mÉŸÉuý9esä N£b½®»¡ïƒùË“ßÖ³/º¿ç¹½‹ã¶ÿBý1Ï~¼|šbÿéÏôìºÓð˜2òë‡8äÏ^ÕËÓv¯Nã•R¼Äý¬={´×?ì(ßãü.îÃíøugÇV­Y$çø³Õ·O¤¨ñ|ÿ£óòçèýr\¿—Ò÷¹ÐØ#²^uÑ¥Õð#/õùù¶Ó^%ù»‡D!†àž¡ãú}«ü¾KÿFÍcðv-‡¤ŒŸ¿Ýë÷Z´Ã?qæoðˆÏå÷Gsø¢ºŽu'ù6zvݨÎ|þ0NÆwôÇåÝo=/}ÅïÆW6­&ûÞ¾JýÚ9Ô»õ|TGs1e#Ø>±Ì-ÇOpÈâ-@Z kxc—W~)·Ÿ×|Ûÿãóø²›?m¼øìœò؉ËÝÞžíû}q®nÂá»cpà_ý2æ¯'²'ûºCÀÜ®æž^<4t¸>]¼xl3û̶–6N;­„ƒ×ÃÀp#¦8]­ô.} ó×|>mh´#³@¡käiý·~ŒºØoÙE÷­ãã·WËñu”—&Ó­—[Þ~ƒ#5º9«ó¿æŸ)•À)å¹í/$kÿ½¡¿õçwå:0C˵eƒîúû|¿~†«ºë?Ëç‹ 9?ÕÓž½?¯‡øL‘3ù£Di˜¤§$5 Â23÷½x¿¼j!³¤u÷éÕ¼ô|?!ÔÝ~>ém¾‹×Áz„%òY«sRW¬Ö~ƒéŸg½4ë„Ç™‡¿`¿Ç®Ÿ^¬t~X~kþhû!žµ™­Ãü€„ñÛ8Ìmôò|ŸOÚviˆëËSÊHK$ä:oõjV–Oûæòa­‰æ%ìý¿³Î½×É9KF0w'{¾îφ<ñÿs‡¢½\„w­aú:‡ŽOñU¿œ‡¹‚îÇòþ¾aë¿"ÛÆßg8ççËÆì½:j»íŽní#5«Â>‹mòþ÷XÙ·ykø#5éåǨj4Oѯgíׂ'Z|:¿i;WœÏøýUêìÓÓ·˜xòþÏ}:{;?w^ jeðõø|ƒÔ=ãÄ:Ç“òÓUs6úB…‡o¡5ó3„9cS]|Ÿ¦Û«#+8×?T|‹fæÀ þÇòå‡óš[PóûöïîûôíÙ±ì5éÏÌÊñ€ÏóK4µý÷8YÝ8öœÝýRüoÜ£Ëùv'È?mDÇ‹û=ã—X‚Ê!³æüWÜ7åøŸZ\:ŽcÆ<\¢Î#+=#5>½êö‘Ü#5DK{7F˜~°Š¡#+nû¢?pÍü}úõ§D½D¸$Ñ:C±ý‰>.1úyO‹ó" ðy¨cËÝú¢yy†`wx#Åõ;”‘pö>Ê¢þ£³²Iôè‡Ãk¥âÕÇbOÏÆJu¨A.ÂY¼•\ÈÅJ†)ó¸&¼±Ê"’ö¨Á‡ºþ°î*†)zÉ1&d‚u:ÕUÌ € bNÖú!¼+ëÀ^+QKIñŽ^ð%ôNýyqšöjÀmMPçù°0zeæ)v†›2qs,Œ°mŽ× *D‡S0_蜨tïüË4¿eS`ÕLçcz¯#ò#d¼äÎ~5´QÞ^{FŠ7¥šÉmìùÔ}ìq$ÆÎÃ$“ ÇI;í“Ÿpùj7Dy±ÞÿYòÜ3CY,N±Ê‹¹„:5Ý|®—ÁYÄ3Udé=ÄdCG9"‚ÜÈùS¥Ÿ†)^H'=ÜÙõR#/>½û²ó?ð#/›ý:dËõþ]]ÕŸW&Ú¦îV»2ܦ[ “3ùv«çå£6ÎS‘öFOvß'ÓÐ~0`3ôx#+F® žc dÜ{í%ý>}í½‡”ðw‡—`s©ú½Ý0§Dà×=z óØbÉ¢oÜúzu!ôc×IÙ¤î'š1ÀÖºÔyˆläP\³·/@dP|"*èù[ýºiWáâþšÄO³AšP@%ªÊ(E;:Ñ)Ÿ¢žó_¸=GX{y×OŸ\ýþ[U«Àj)‚¨×È~n^ALö|\/KôÀj#ÅÑ¿+ƒª ýHät—“à<qÔÇYkDüê¼'lš£|w¢·Õôøz?R#Š&±ÙŠ=oÕßIËc£ý=²½75œqD-—ñö'Õ½61ŸãhÿT²Jö˜\L’6Ù$M£bqÜóŠX؃AsfX¢9‰'!RãFSGþ1&àå#5Ûi1úK\Áç9p1°Î€ÆÖ8¯.Vœ›Ò˜¨*$™‰´I8Xb¨64LiÐj<u§žqáÞžt ‘ÂÍ‘'ähÕÉ#/Ë1°–Á¨ÔE®ŸÇú;w8’0ÕÕ‰’ÆaÊ0´µ#+´þ€f1Gà‰Æ*騵vÝ«vÒŒTOÏÛ†`+!OaÓîpdbnÓý=#«¡ü¥_§SþzÈÚ¹¶g÷2moŽ C5ËÈ÷'³Mo{]»‘ÊBÅÆ `¢€ÄP¦P¨¿£Ãoâð¼|Þûø»›‚Dç˜I CI0ô—Q§«Ñ¡;'eŒ¸%ƒAˆ$$4UY°¦)þÆØa(AŒDN¿‹×òVÏ¿RhៗÙ~KôEeÊäÕ­®ý?'¬[WÔèúY2v½)“ÒsˆéôD^6ø/¿á-%)`­ÙosÏ/¿g_?£ÎáÉòÝÆ^á=wïÛù¹~-NS[¿Gæþš@¼wÝ׫ó»wöÅ7‰bžý_ÇWJþL¿¦ŸÅÞóGñXæ5ÙÚe~:»¶˜¾.{R¾ßÉÊö®_.<"(€§ö“a¨YÈJæjÓŽkpÈl”ñ#žÏ·[¾òï’b2a¯vÛ,vH?÷GÆà¶#5¢áý?w—ñ¿ß>Ž?’Üÿ²ó?ʉ/ßô~9þÈŠjY#/á{¿}âxfšV#/Pôù‚t#+¶Owò„K™Ž™YÀ‚ø¾9êÚþÞÝ<ÎéøµÆ›† ° š]šƒþ&ä‘ÿZCû*(?ù©OÛ*þ¹‰·¾•/úŒ6&–UT”éZ„D*küË3û¸G"*âÍܺ´r1´(0#51saäq-¹ à7™ì¯-DÓˆÉf‚ˆ Äïb3D'œv¥M#+ksoVç^$LQÝDkG¥2bÝKF …½-jCŠB¸Yd+CM¦4Áו\f–ÚÅà ®²1šXî6cŸØÕ¦P¦Æ4F ÓQ™Â%Qó`Ø*ÍÅJ6ö93>\ï‘Œ†Ë0à-´cqq.`%b¸p(ˆ³o÷çÁóó§ŽòIô!s7B½œœh¢Š¢Æ#Vã†)#+„pd°’Å[l¨šY½xG#+ÂÓ7'yÜrIL2K lÎ*bž–8ß+˜HÙÒÏ6ž³Øër˜ƒ··¼Ñ1ö$6Å0æpÚbE“Óu,E<'®îPv‡&óp+ëhØȈ§ößp$Æ lˆX`[ÀYJ¬g8R6YŠ#/6ÙËÝÓ)DxH›¯hƒ_7îüÝ ©/Ôhì^ßÕ j?ÚìívyßÞXà£ÖÂø¹u ¾g¯óÂ>7Öžãíä=ºgåû.ŸÑF窑ÈD½³½ì=W-ü߈ütÎ$}ƒs|㨦ˆõNîøUðï{ÜðöxÇfÇná¿öƒ°ƒˆðÛÁÉ€ûÕ)GJ>ƇñTú›$bi¬òñ[´Þÿ#8ÀCa„¡ÈèÂöû§Ç®=_£å»‹ÙL÷a¬$ú±èìQÌ:FñÔ¼çìÞ,”}¼ÈÀP (ð#û˜så&Ì"»õAƒvlŒC†Ï¥“x#}ˆÖÂÂÏð×Â…Ý·,€îL¦ðª)—Å7©5ƒ’BÔj×RT1¸>Ò%߇&÷xˆ°Új¦øx=N;õßJS«Œ ˆ‡…¦zœåw#TåÁñ¤=p•@x±#Å,Gkÿƒ®aèUj£+s‘Ôt,ÁÀ¢C»Å±‡Ä~~òR<:sü5€ÿ<£x$…BI*ITÚiXc$Pw`¨¶çxN#+=$žØb™Hû´»Æ'6ñžƒ­WøRC]Ebõðþ}Žê¸ë¯˜Dæîa¶lÒÅqhüeJWŒu#5.¨‡ø)õ}•åb 9QÍöà@ÿÂáþ)iJØØÕ#c$#´ PÕmWZ+,ƒQ³éøÎŒ0‰%{ã7cÍìsv:7â-wv$)’šH¹b¿Û/]ËÐÄâ‹™Ó‚4Ð<ÞFìQ׌I!þŠnÅí[‡¹¶áÀÔRVÜ3‰©JHZ‘,r©­‘„"CL}rÔ»Æçîtyf#IˆÒEí—›˜kþIKNËzf:`ÒÿË.Pž1Mäo|ƒ4m*xÒ™ˆ"£gK8bÆjÊÁ‘ØÑ9¢á͘3Ã<Æ5<ɯ§ÐËÄ£×ËØB)© ²#/.ÓE:zÄŒg/t8Ln¨21´:àäAn‹nŒ{Ð+DÉÛ©Ú(øòôê½F¸IƤ9%5ÇŒsTínK‹×qÔE}0v9iÅ4ÉTÎ0|ݹ$ëÖ#/(a؈döÒoêyšƒ‘ŒlhîR4ìݲ1Q°ÁXÑ0Ø´ÑEÞ‡¿Øþ#—+†óŽÔqmZ>E´íª–ƒŸ4·nε1Q÷žfï³&ÚŒM`È q›)Fª#œ4ç!CØ!;=­DÅ@ÄI¨ÉO¬ü¨(#*Ç:%nžFLìã@'†ø®çÀÆÅLÄf&‰ìr®”LÅÁÛ”wlê¶ÎÄk!·Ëc‘‰ˆ¤÷•ð9û†ñ_ŒvX1C`4¦ÆnÐ~çÈ¢ŽK·Ä‡£¸í¥0àžZj'”œ‹±‡®H#/±£ý»0Ädˆƒ A‚ ­¹ÀôcòΟ‰ö-Ážç~ Î^”nŽ]éß²0ç‰ ‰w"ùyC¸˜#+"Š7ßQÀüœ¯„1x¯­‚xåÄÐ)¨(˜xÞ£iìiÊÚñ¶µ5¶a#÷#5TšŒËmØÃÃ?͇sý\ÖÅËàg#SéÅS·ˆ¿× ùô Í#57 ‡‰eᙢ5ÅŒƒ›;½(v!;»®%‚ؤ$ ü‰lSmèÚ¼y¹ƒ ‹úãˆøû=ú‰¯‰*©O§›s!]´$Í´Õ7.ÝÔN†Ä4Õ¨Ršjt‘i±¦vj.CŒíÏtfæHø2É"­ÕÓ߆a¦2MÈØV‰0„pãæá¼!‘6Åc¡;Žm¹¹Ü M³™@FéÓFñ3F½¼3´îúË#5w׉Í÷Ö¸Ïq¨ØÄmAxn®Ro!h5p¼é¯EX±@‘òya];í­&¨×8Ê‹“¼¡¿Ú3ɹýF hM«A¼Ä”Q¸øLYÀuqÂE>æÛNbÚ„ÃkŽ“lìn# mrzî@ƒÓUŒmpÎ39£!↷<­(õ¡8lìÜJƒõ#Iåmæ~yß×j€BIÞxã|O„g~wÁ9Ã|À¹áÂæX™û‰° ÂbD <Åjkìc8"Íךìqfâ‡?‡FìÌÙ¦£$²øÆ´ö&zí¾c:Ô«£A‡¡Í ×ü¶W`árq³áŠÛ¦…ìUrt#+´‚ÅZÁä¥PwÖsxı(“ÂO©å¹Åàð®w=6c•ÉÌ2nvfÔáQœaÁd:P½ÅëâãOglnñ/Õ&å¶×jìM0õ_w&úÛ±éŽôÒÄʈ—‚„C?…ÀnZôÄ_• l(Ûj;»»¨+-¿Ššd¶éDÙžN¹´„"kMáŸZòíÖ­Yͨ€›#5‰ ½–#+é±³Àk:ËÞIœ|–#M…c´"tb›¶×EáA¹6ÊZ·ÄàŽTo¼T$ú΢‰Ã¢°ÊœÞCºs¢s¶m—'ª/¹d›í³ ‘„µÄb‹ÜƱvÀ±§ˆ|'1“;CfRÆMš=5a\š܃#/§©±Ìeö{Ö6&¿Ežé¯O{þ hVÁß;‚†”pSC~JSQÖê+@µ¸à{#5 Û··^#/9ÑV©õŠ™2ãë¬kÆ3Ž}RöãD™VY–päœ#58‹ {»B.#Tk¾)PU¶¸4•ÿuÆDœNuÜ’ N¿Ë¥3ö6Q{ÃÀq)ߧ§äy§ë°¸šãi*s30ã˺¸ÿ%× Ãªï–WLëÓYè2uœ9%U¥^tW¥_ŠÒÅ,%˜ÕFfì´OÚìaò\oƒSDìtÒ¶®‡lðÓ 묾™ÞÙ.$YîzdÉ¡ÿc¦‡F+Ÿ#/|qƒhÁŒPšp¾%õ³,{§gØëNÒÒÇ€êfõ[¶ó‹XÓ©µ˜‰··”“&r§ît„ö‘V!c#Vg8ÆmXæé§)#/z‰”2×tÙÛ´ŽM%71‡;ò.Y.ñ"2¬.bbÒçX©)²b*Yñ φ1Ö'i«=‘ˆwroʾ±ë}eËZ> {–½«K³èÅí|Õ‹Õ¦6Û?ïQç62Œm&5][ú»=6ñwž1P®èÙÝ„»çƒ%äd!zx¬ ”Ü?¬O!”Ü+x[ôÚ#/ôÙ¾ÛìP?\j¯•#/ŠÄ½¨”FÜžž*Q¿&{s*ƒD¬¤¹#H}”ÅÐí”a2·”BÚn)Ž“µ9#5”M5)¥}á-gŠ¾¾¸½ÍnH´œ\=4t™ô÷_åˆÛÔhp_+O#X3àî×]‘-Ò)©ÞEô†ŠvH&)ª¥7ØÙ«¥ÔQ‚¢ÅYϧ®ÏüóËô0xš -û6I› „É@bŒâó¥+¤U#+¡¥¥~Ð £Åákù0&¾…ÔÚÏvœ`ñKÖ—$\îx9Îä1Tœìò3(çìòÂzÿVÄãÈï PS÷ŒÚË“¹Ê¹ÆÌõºi7˜t?% ÷3O-ÎÕëa Z.”ø™Šm÷¨ýzÿ£}ü]»ŒžÈà!âY—×Dõ0wõ—âiù€„Ò$(ªëÌjÕ©x#5\€7×û¡ƒ+þÀdÔ,5²F{é ,kæ¬{Ç^ß¿aÈåOßðhŠ®XƒØõåWƒW”<` Aa•#ýï†ì¿-™™¥[#/¦°éKuû16_éùµ"Ã陿jf#+ ”ñJ pEËÛ‹’CâH1 ¦_Pñ(Ìâ&¥’O„zºÓŸ‘Qû+Ÿ†‡£ë£Ôþr,2¿'$w#‚mžàéÙ1S=uŠŠº#5³Cþ/‚¹ôú5Œ.$¨Q!Ý&0iç½ Ë`HN/±Çm˜.¬P“!JUIû4V¥¿·Éçg£å;s¸·Ý Â2@ nc‡™,\ ED@,‰ŠWjϯ×#QÆvãÛ£Kowo5ÈÙ‡rNiñÚš7v0Ýï d¦0-9¹ýCð0g,˜†2zæZhê³vÁx±•íèxɶDÇ»ÜÉßèùðI¨a†ÓÌýï¶ãûÛŸŠj²ž0ê”›“5Þ»ü®dä|VË1Góĉ"|w‘®8´ï™Ǻˆwj»óE H{|ðpÖSXñ8ÌŸ+¬ÉIÎÑÔzß+Û¹i%{2n¼çÈKe™ŸûèxÑ _ I›.OZ5SÖ–^Œ…k6¿2Ï€*vôCÉ~5U±M‹à±sÝA(¨%z„˜Š2¬β#/½¦rLâ`=Õ¯hj]N©A|úÐÆN|s(µ¡sfËŽ’ÌBFïåL¾YÊ&rdËÔÝèt"›¸‹#5h0@Jñz¡ÙÖT|Yh ­;/ŸuDœdmB}“¼™Ÿ€C‰`•|¼ŒN&ñ"wƒ¶#»‚y°½*Š`&ˆC>o&‡Ôú×\Nv~ÖhÞÙè¦g)§èY·¹[±câÓîgxÙûËX/,¡wr”cÊ=’­qš™Fž kwQpãL@-˜^k¾r6‹´ýô´ÂÏuÎuK\Ÿ0Ãö=—­I¦ºÐüÂhíŠV<5•É­{1¡ì÷½ðÑ\ŽtýºÑÊÅüÓ9r” ¦OÕpÚ¶9ÿZlÌìØ4[KÚi{†(!â:^†{¹C~d ¶¿r|%S‹ëòFôÌîžALj 0RQjGžDºTl§÷wØøwÞã‹ôŒrÜ\µu½§wS‹ëž|o[PÂö^M#5félº9Ô¤xÑ©ÌnŒÚÉ-ÿ=hP6“7ÉÙú\´n`6òåo_ë/÷Þ ×º$R¯ÕÄc׃ƒšìŽÒØÛ ýÅvds:iLa¯5’Å&'Ж‡['û‚**±Å-‹¤I ¤Äâdê«û¤};·gÖ4×Hý‡Â[Ù61ã#‘Ze|¬LŒ>þOµ<Ÿ˜ÿ&oøÎ[Ž‡[€ü—Iƒ}b‘„,aë0ê™äü¦ô*/Õá‘‚k÷{šÅ†S(ˆdŽÊ¡Æ8k8¡ŒÑ\ââ‘…>Û :&†`×~Y³µ¢áW®Ž}‰Ýööβ ¤’`ò©ÆAÞaPZ°×™oTÅYu#/¥™ü´}Ñ]ì}UÜî<3DÌ_Ò^Ö JèÍOâV+„ò‰ÂìíñPÒ¬·LbsJáìªqÈ]–(§ÝmM)©9ŒCwZR•'Æc'}çèµ·<‘Û¯7;úúí^·Þgã=}”ºÁ\y¼HèHîѼ»_„ú’)¶P¨9"3]§ ah@Á"†,L7«6 jŽ{rý ~·QÝÁæ‹—|ñÝÝvÏ:I oTÛò†æ°°ð!ƒ ÙŸ‚¹IöÛÄØwôˆlç(⦥ùöÜ›²ÖðKš¸OQ3ÛwYp‘ÓZ. ³=ÒA£”PjtÂ:¨µÅoŽhÅSwK ìðåQ›7ªõãáÑ­XèFÉÒò˜»•ë©ŸCåàŸ†¯s<»˜1$›u¯m„Á–-¦Ž#MÑG¼Æ2-#Z;sE¡Ü¼f·º"m<5´š÷;Ym³ñ£@y7NØ],#åõo½]ŽãÁÚ{,֛ݦE£ê`Ãll”v2‹R=Ö¥Üï­ÝKt·7swè÷ L‘]3Á՛͆ž©^BÚwp‡j¨Ñ⊺cžÎ}ÿYLAζgxôòøïÙÌ#5;¹N”¾~;X‹ß¹F¯%é§%%…;?ø%ˆo[·œ¾"ë¿#5ööm§OMÈ‘;¡ø·8l ûÝ9àÔÎ%J]iší)9;òf†…ÏòfmZ—~€]='dÉœŠU¯ßš©PrÙÜèp¥6/¶ðá½Yn˜“Ê5L×A‚JªMœ Nµ ¿“$Ê»dz[»É¢0}®U&îâH—"^I>\á/¹ë8aõ„ƒ8Óg[NÔ­ýÖ;å¡¢È8v ”ØâPbÕ§ªœZcÕNK-áüΖfã× òí–stÓ¦ÙÆû Q‰6#úŸî•ÜuÓG\]ÔŽ½Û5éÈZNÍî‘»Þ³teÜx¦ŒÕ0¢î^ìifÊJ%yôõ5°©g|ÌœêÇšw©BBêxó‹°Ù²Úxga7z†*L¢ ½M2æäÔCä¥-Bà·!¤©u,ÈÒÚÍ[áj ¼MïNŽM¯öqÓ7(wýŠ&uÇoÍ[rÁ¡DQ0šÜ™©¾…±³ìÜüMÉuá´ŽçÀËN¾?yüÆq#/vCäå;õb´"äe̹ÆN߈„…”,¹1£ñ[ÙÝ:†Ü,ƒnVuKÜÖ«½ «ka8E¦“·é枀’á®ÍjŒ²/v(í{º?9Ì_gQÃÕýdÒ1:œ&.…>÷££ƒìÑesòYËÉ\æ–Õ¯ñP»;Z;=wvj掃š„;8ù>WNË`oŽ‹>j×M›ƒ§0øÃ[ºtdñMwct&æ"=/,,n{{>ý¢”„TßÃc¯Ò3ºnQIFü]']–Mï^cì\c¢2§5ÕFv½«I»-z9Vj{,`Ä¥un¤ÿͺÄÀaNµë¾o]<úú± wÍD¼`(8…Z´šÙG[·µŽ;$ЦtÑòŠˆ‘™Í#+\6IÂX‘¨xz…Z‘‚øe+—8ò>ºW'É3CÆ—Q÷Ëó>g“œe·C=×p-Gñ:ü‰ ÄË÷BøA»—Â6½‰ŽÌØ„Aê8é #Ž/æ p¸m°¾FWÐ2Œý~’×:È·ëF:;àQ>æ½»córF¸™Ÿž£­ùËñ½ïçú6Íâëx§””?…¼j¹rÑ´®þo-ôäÒ‚~µœ%&áÆ d5G7Nh±ÖêÏ­ð ‡1Á±XÕbÏóÉÒÏ^þé$úAè²ÆˆÙß›)ëÜaÐE yõÖiÖ nx“Þ£š»öϸw èÛp'uÂÚ0Å@Á-±`WŒ.`U×Uâ ÛÉ„µ_H¿Ò­Ù5vÃH0Êࡧƒ#/6.ùÕ„v˳UEûdÙ,2©xÎÚMdbD0}3f¦¨¶Ç‘ÅÕ½è©Ô…}«tpk퟿*ÀEumŸ†·ˆYíE`¬É]Ÿêvô´„ïؚ p« #/WëeFzYgxïúÞ2[¸¡?t‘ú-ßÇ×ÛLg:NÿW}m·8JË"}¥ÀäE¦èxòär9–$m¹X—¨=ôv1Âtˆ¶ÐÏnÊÏ_dˆåÛ·9} ÜÚ•ÂNõ-ÿ,GŸœm®Ñû´wÏ~–ÁaQ½ã™¥bŽk˜éFÏ»Ç W™:S §òîzûæ¢òn¶*å ¨U‘XÐdr fÆ5d»¶ýؼYÂEÀ½,Êmµ¨û¦d5Ÿô_,¨B#+PŒ/L²°C%ÓÀã,üAoÓÏåÐm<¦‚Ao¢Ùé—Í”_ת®•qF­YwO$á δ{"ŠEœv³J,‰‰(5RIÝVä#y*Eû ~zí=¬8\±¾ï¶H‹öM¶Œ[ÒiTxb¸MýçÈ$š¨8ùc|- ¹ß#©ûzÔ%ÆPSW9Ãó4\Lˆ©¾+„uxŽ|þs©±s;óT¿^ô8Øá< àðn˜u4òº2[ƒ‡p.Nî6ŽIâºE®¬)t8n0wïPifÄÀÒ”s¨³ph=µLGo[FeìN 0ªÏ‘úÌ^L|ëßdØõÏXÂç—q„»¯†N|úW—Õtq`uÙ¾°´ò{#/¸ß7‰(?&ðí~ï[ UGF4LxñÁÑ2ÀÜ:–n‰ÏŒ>xL‰V-RÊ…°Ÿ÷wB[Àîž©{«)6°W²Qµž¾ú]³Ïyçtê­Ö5š]š#5åÖŽ‚n7×àë5rˆÀdƒð¤ÕíŸÊë'Æ.{Zäßm!žYdÇ/{9¢Ö§ë{z8Yð”–uQ?äøàù–”Šïª¾Y¬öɶ¸BO“¯#5o˱Îè¬[åÊB)O›†}± ‹„Tp÷ug9¼BORIŸ ­Â±Îï6?’;8t·G]Lw(¿^­~‰áè×´`ª›Øu¿ä×G¤}L0.í2¤©Î##/UæŒøm{½2§)¶{żwó].5«ù]Èô“Ë»If³æbcY«ï“žöÌ*„Š#+̬9ýùûóÓ¤4›{ߢq{(!4×ÚžÜrùaùì_»ÝJÈ?¿îÀÆ—ÇÔã0ˆñdŠ}9ŸåýÌ!´þ“ßÌh7|Å“ŸtÉ£¿|I«ý5¾þ(qk-¡~:iÿ¥˜®ÃP:“PZB¹QZ#+^ù¿™ïul!Ìþ>¥yÐ)ðßê-4°‹#/<{”%ÁΔåϦ "‰ùÚ–ï,Åq8‚!Üyˆ\‹àY?ãÞ*YmFTxø^øzÂßöâImç¤;n™Ð´¡=|öœVPAx’$_Íþ·Ù2ð×Û[Ë)Ô>-^ÙÙæÿ_˜½j$œéàQk‰ÎÝDlû /té¯jZRœ6ý|ËVpK‰JLÜ•5NÆ>×´$H?Òu=“]¾aÁþñ ~ÄsÝ£ ­£ônauz>Cl>Ø>²uTÚàèÂ’–±}ân{ U³îmõ´´;=yXÁáMêÄv”d#QA)*YáBÝÉ¥¥`öšW1“'¨š’žSáßÖ1j«Ûâþ$¿ÓŠöz/}¼ÖºN¥ûÎ*nCwB¸Yê0Þê¹ð?3[sõ%y‡ ‘àôsG¦ÜKû¬Á4oPCL(ö9îöÔë<<Ÿ«_–Õ]¾òé~˜•…"gÛöÄ:4A‹‰x8“øFï8ë¦rˆXapöξ’\l_Él)#/•ÅòÜ¡üaËÛ;Ü+…fä³WšAÓ„JÕÑÑÉòã“Ý|Ÿ›¨°ÛQ=S„ý/vºÃüÑ~%d©ªüYò£=Y™Hàì÷;†O±«ÅFeZ²ý¾w¸VS.˜ x¾t^<ì«fa7‡0ÙU«·³Y 2ó3#R»lÖw‚]ØA¹DXƒÞÀ:[Î/xq¾ˆÕØ©|ʹšÙhÑ­®¨/1çvˆìÆ»¬ùínÊ+w0»µyì[Êå)~ó6þm=z£‘P\š;ïf&íáxZ%†ŒôÍì ù°c‡köFYê#/!âÒ3æŽÌp»k<‚Ó ÇCÀ\B1ã2¢<«2Å3ñ¸ ‰À­$Õ3ã.«|KW¬V¬º¡1;„•Àu8?S;ŸIoç„Š‡¸,Þ€Òy}ë¹u:¯ä8wuS…AÒ{ðÃ#5:èEë>}Y\ù»´Íœ³g(ghÍhÒèX¬C>pùµ6˜\7OmÔ¬'#/xŠAïblø½Ï¼5.‡1Ù²[fëèöuZã#/8e‰ׂÛIÆÀÚbt„c(Øàk•N'•cX`#//µºæ ^:íWRgeãjF}·j0Zß<Õ©eð>Ïe÷G öóÂÆûIC]ÍxwMP7=øUÃlTXŽÐ¡C+Qã™UØÃwSàg]E¹nƒÅ°tVîV­ÒÙÛÀ¡ nk±®‹ßÚ äc̯ÈÎN-µ÷½ï¢¬o{Þ÷¨x¬Þö†9ˆÐW¦Äv>TjÉ‘ì¼ÐSrнݫWÍÎKô{ÝœÄÀÕábˆ‰´¤Ú”uê·ÛtxÇ¥uÁ½¶ï-_mÆ”ŒCØ¡Ít÷£Ü%IMÈòZ‰A}èÁCÑÀÉ¢ŽXIb)ƒ™ËBç¾rß.#/JÉ–,½<)»‘âåuï½âÝ6\¯€†µ¬^ì²å%`«™åsæà2ôt¹'HD´óa¬Ý”qsûJêkÿ;§b½V3ÖHìw-÷°…1ów0ç€ímÚ—‘ê=ú(åÒX  Y£PPÔYÎè/ƒF˜#/Òí˜Ç¨ï3§.n9@K°éíïðêß.Ü~b9/N4ã¾€¤³Ñxß|F·\+®MVà“Àb;UX×´ë|Ònç#ç+Æž‰·2š€–]‰ìÃx²«­×d#+•?)™|*Êj#+BACå#W±B H(ŠB8êš8WH±£ñŽÁ:y«§3»=R¹Eö#/%e6(ÄÅÍU—s…í¯Ë12EËá4œ™ ,^â@)ãƒ0<ÂWoÙ϶Wx#/S:ͪX¯²©ù˜(l•“\CtA{îA×äïÒ™í»_kú‡GdðéTðýuó¨‚× ¹£¦w,¹) n@@‚ž'Þ›²7UÌ‘:í·Ö)~¥%Ýïb#tá4›Mþñb=ù/¨òwª[k¿¶/ˆG‚6¥œ¦+:9žç@g²Ž_|’yäÐùSç@Í·‰GÆX;43w#®æôczyÀü–¿Ö.Wú(!Îg¸àºx8ðÅ&” yqG’B+×[kvØ(#5÷ÜŽuè#+Êáµ²ØÒÙdÛ©ò7U1un¦_)}kßæYXLã—'Ï°¢/ÙÏíwzíÿVïñŠ¥åÇE%kû Ê<®(p_}°^ëŽB#5©.J%íöj$D¬µcÏÉãUI¯p44UW…ðlˆË{ÆcÖ´‘Ù©G$®¡ÙÚý|åG¿Â °Šör(ýLGŠjMŽ¥FWy\5SÁpÈv¨Ùrì,UÔbB‡¼Zî~ÙqÐÖbžXx‡vV¯yžk†éï¯ó9a[È6ušnÜ›öÇ\m´&Ð/ŽË•*BðVÜo˜óg@Œ;/P/ã™Ç…óŸSDn òù:’–dwÍy¶ïxÕtED]üþ¡ë½ò†>;iÀåë«~Íæ›p¢Ê€½ê9öíçt؉rUÎÅr›ìõt¸ÞΙ°Ã¶nv¹ø —j¨‰¬°¬*ì*ðXw³±]¶®+,‘ƒ$G#5zäýËUO~H€wì×U²9@rÆ.x1ƒpn…æ‰Ôu5"y]†àTÊÅÖ’ne¶>Zü–·§]9N(¸ƒÔÃVÇð4q¯¹¤—Å“=W½üÆçRsy*šâæk#5—`k9 ‡BÉÈ¢¯©é_SÆSsª®å|Cú_¾ú9åF0ä=&êÙ–sk•™ŠP‚ƺծºm*úµÂRöÎy˜œ]›•M£„ oÖ­b[ ¥öà¼Ñf¬íá@Ì|‡ òºfA uW:=ä;4òkŽ !^uR §ç»á8ÏE3 àbù™¾#5Gw7;ÄO8EQ5˜¨í´Yãð4ƒÙA„²¨YwwBPù¾XÿOÛ¦þøkùËãþšåùê´œ'ÓðeRPý>ž>{èìk-v©ÎB9Þ® 0O@ù#àñ\–’'6^,U¾ƒ›9%!Ó†/w€Ôˆ‚áìÉì੶7±r¾YµÁUéµtzítáà00;MÄBÏ—[ÇŠÈö±@îÛ4Òœ íð.báõÜ'p @¡ÕÉóÛ¿·|…×r6±mA° ¨ÁÃZU9¾õáËvœÜÔçsó†šp…¯ÜÜLé;¿%Ã~ÞD†LPEÝ(öæ~ä=ÀEú> Ôºd¹Â9Í9Ç&œ±j¥Å#+ÅŸh`Ã9\±btkI?Õ.ÄÄzTÚÆK ê$¢œý/Ì>j;»‚¸U4ó²ÉTlU…ê†Nçáw>55+1D@8>¡ï¸;°A]ôæyÀR7=j¼¯î”8Ih6DO'Åκ¶cAW²j²Ï°Êt«#Þö­œ¡yµÚéH«ÈŽ6lïÉÙ P1"“pi\ðç@`w=‚l„båá8™±Bë}(¦ªßbí»^×6¥m(صÓm°QñjE¤ñ6WACU̘õÖòÃ÷w9ÓøF#/Lanº:Û‚áñ·9o3w\cci˜æ·ø}hãjó˜ãúwãJ]Dc–j‚ÔiÀåù­ÀbŒ~ÙÛõ9IfÃ#Tûˆ'ÃHŸºäí´5u`ÖJj¢qœÛmm}à¶ÊÍœZrxMõé^hSu&/{î»dÐŽÌĹ‚3]Y†¨æ†é´Yí¨”S-ÑY‹á£Å1Áœ~þ{šqùÛ³ÝÔ+æìß|`õ–÷¾3¯cCú¬ðwËâ*±F*³ôöl¡tŽ¸|÷šÚj×B iæj¹ø^ÉÒÅp§âöÊ}ùqÁdåã\Ê#}Þz¨ˆÓ¶QNï&×ÎQ¶"›â;7RÞèŸdœ$ƒÅx´xƒ­I„dËà¦IPƒé1ø!ðàë;Þì˜ùR„ÈÞõ*vP²BÉw…)œÑáRÜ2ÙOaI®Õ-t ,³J¼ì óÈžÈ6½bÎÙª`£J´x$6û\7GkÛe·ºö3Ÿ8ƤZW «SÈæ9Ü=çCLÚĪNÁväšIè?Ú=HfnÚ´_mÅwÑØlêÉŸk FUŠ@DA“fÐëS6”œZ¯– jBîÛKkö‹õÙ®Áç±Æ0¿s˜cÒN„ÃÈ|2ºBàéFHò(ô;á)™ ÒÌ8¹Ÿî¯,e†˜Ï"«óºîËåŒzÕOÃQ™óåÅ#ËŽÉÙºæwØ ÷ö¸úœfyŽÓ¿—.a¶O‰cb±‹ÑQƒ ÌÓ}˜‚:]¯’M¨Aä`V`ëEîr€oä§0qx¢lÅR¢œúp“¼PI‰ˆ7•«·š3‘†híý°Ò.ßÁà×®´—‚Å#5tçÉÆŒŸ)¹#5ùd*Ѷ#ï–Û‡G';Ò'ºÑsPlål…KÝ1µ#/ŸÁfä´ø'gsí‚[j"›UÓPoG·‡‹v­iL?¼›hå(Ãà¨h@¸ Gß#.{ˆÁ™–º]˜xzŒo_?Ù“È *Ÿ7±ÞP·Ýàx ÿOô‚£•¬·cNÁ.}nËPѾ¶t\¾–q'oxµ5hoŒ`æU²p"p]âø¸`é2sx!Åj¸—ÛÖ¬ 'B’ýköyiû©¤±ßìïƒù#+üËo¨göu´ëp?w7í„&¸Tp!G!#5J"s#5* #5=Èæ:Ûº¼Çè'{ÃÅSÈÿl&>ž¸üøÉ£õõ¶wýíí($‘?Š|ãÀýÝàyãõ~@9 =JÃõ)TQT!ÇïÔ5øÈ¡êäÛ¯c¼pQ¾eÏÏê?ÐP;¢9"oðò»xnŒ>é.;BÈh"nGÁ‘×ä¡ýÏÛi‘R/ ÎC5ÓåÇÔëÌhpåZ ßÕl;0Y2SK“&^BàH*€¤žlÕ€g9 #5.Ñ1+=QÀ‰¾æïgýøkÀ[“¿=<#eçÃfà1˜ÃÂÀ‹ü.kÊàè¸ÅÄã%¸gaa£ø¡y¡öÎ{.»—úªû}WZê…ºG9„HVmOLÞ)ÇZ'Ý/›3ì8¿ªûc½I»Ãœ…¢!"©i?¯/适ZJ&$b!¤¤DJ #+€Pp·éÞ>Wúk†õgŠˆ‚GÏ·_gúþGÓ—×#+#5É_Æ#/Ûñ½6ñB§Ò¡‰àõ. ¹VìåRŒÜ‘˜œ«IŒ_1»RHc¿ÛlOñýYëÞš}exþ5EŒwºiD2¨j@s×\ïîÏ›ÃO«¿„œUH¨üÔTI‹(&Hjþ‚c´ *ª…‰€)Z6#+ˆ|¨ÄѪ:µá\I ‚«!ç¬ixÈð„ï9ŸQ D¬å€ê–#5#5H¤HFJˆ#/@Á` QS*0P Ìöw^‘¤´ýmcSê+¦R\ì&áö õl†„ºü+“æù¯Ø÷-šUÜ«ïç?bÉìH±b€ŠŠI4K}™ç%¢ž@瘧`¶L#+«0)üÑtý_]ãcÂ|œžñ£9#+ûF}Ê € #+]Û¸#/·îH"Ž4IÁFéRõÝ“@KHÜ4½è¢b$içWûNÙ/}Îèò¬qe#5ˆ321ÇÒ ȵX•P¨LUA õáƒÌw•R@—®éâ[]Žj?Ç~‚Þ’–"5½«R/~„apçÓ°ÝÒbfƒ¾–oYÜj•\Nw)$PÏVÙ7£œÃ\%¼ÉßÔ‹$ ‡„’ÉèÛHÛ‚I I±Kgç_ö¶L?”Å›ü8ßkFG †c,1 ÅÅsð3¯—Ÿ.ý¹M~X=Caü¯mº Qê ¯Ùégþ/ͦØî{“ç¡Në¨öéû¶ÒºÐ!UWuè~mä% )'.X½Ò{ç¤dxC¨OW͇ß#Ým)´ð‘Ãñ” 2D~QÆ?šLÙx6^èøT½½Uª$Cƒºè~º§¾—×δžýl±=*“£`t£‘ŠÇ´¬;æ³#/XfyV#mwsî/¡Ù@zÙ&ÜŠ§Ä{:t-`eS}B+BdƒHîÙÌt:Ћmn1—¬/5͵‰ôÀÆ~í·”Œà½8×ßüØ”ÂEŒîô#/LÊ`f&åÛþM¸âzòpØ4Þ¾‡”¡‘Od×µÈ7Q" Q0=¥Ìë™/ ‘!‘$/2ë.qC8:þܹ:SÑfYÈ«º´ŠwOôGÄ#ÇÙ’pýu?$7aFºJè%6þÛÀöiBÜkKàÛ=ˆúª·#5X{¥ÊYAU7/ÚÜïxÆņ›{¬7ÃǦûN •³æeÐvBL7Ü‚í€1û²å(ëè#5Y!çQ ¥7Ám3æbW8) àöoÃnd4»b¬X§B.j1ƒD9ˆœD?$fÑßÛQѤ¸-¶Ðr +õŠpõ#5XSäK›Æ½Ã¸k^4ÛÅÌîšÐö«¾_£ÒSõ¤ñw…Șü_ç ›ÒzÌ:6G¸¯™æóøð€~3õé.ó}õwɳB¨Ó8=BzŸ*êäs¨Yú¬cjrÍu;¨¾ž´7ÝûƒÝzwsC$Eär’7NˆxýF™¼ ìrûàÏÛ[YB,…H#5¶`Ì ñÝ=w ÂaÞ#ç\`ð’7 õ* êïÓL €È% 9 ·œôweïw[ëÂwf±ãóª(w0óS °èöz+Raòx·ßH¹ üÒƒ’Ï?ñÿ¢ãé;&°Œ4>«¢0 ÷÷Û¡Ô7ôÃËmuÌOPŽ"…NwC!%yr'\gŸêÊQìI£íJI°h<ÒÉÎÜ…æk­°÷UÛgŒP ¿åTNü¡C'':£S'ÖAÈ¢e"¬˜;ª„M’ûíß%”ƒe4†žî¡…ü®Â1<ÇÊDt`.Õ¦7¼tø|Õš¡HPYv¨>»;72Çh< Þ»=ß….‡ÎNKí#5Ÿ#/5WÏJR.«¶oÅ…¤µ¡’ªß+ÁëÁ…̺Y¸‹WB€n¹%ÉDBçÖ~ëq uE:¨>—Ïc`yÑî=é? ¬Çusóõp :Œ5.¹uW(´=t“ DÒµ=ÚxÝ?Eºó¿äi}‰—–žzdϯ!œ½ŸàõOnk·[•I)W”‡†™Ç¢amä w×y|TÈ^¯m×@sfý¼>#Ϫ¡xÛ†0´Õú›ÇsÑæZquä su%¼ryÆPÍQ#+ÎÑß#+¨ J]S{><åc|d7Q;úºÓèó:ŽˆnU#+ujÄjI:¢‚âÊ%àËãÃÞ"ÀACñ+¹–î ý`ÚÔw „ŽÉTƒR¿vŠ°®ñ…Q“ç®Cq!ònÅJS1©6«Çpån¥X…X'º¢šggèWù£‘y`¼ÍgìÚQ„à‚…~yD#5Aº1§”ßÍÕR¹GI¤Ü´çŒÎØi#5oÌ@‚uÌAz‹ÐQë®ç£v¬QÅÀÑ)zNý~¶À†‹F8’#/} „@¯¼öÞ—M(F¦„ Ù¹`€|»Ÿ<ºƒ¼Ø0æ6ºàŠ ­M#+*”ïÖØorç¸Ð8Ö4 +£zò°¿H ž±€kˆ6“y^‚.I¾vríÀsó”Àâ:sD%FáµÛc~D“ˆk#5¼Z="ç‰#+%ŽÕ­‚5)ÊU[>×ËéPþ,³ñrÈ=¿½ªí±f|"¼$ìør)xWjQ(D¡”â̻ˎ\$J,0ö\÷Jî;¼q%Ëþ€ÝN_”ÕNân3¾¤Ò-3[Ôb¶ITú8Øôºí¾k}>#5²Êa#+Ò¥ 週.J c<™ã®qDp™{6 y¦¦šG_¯NÁI¨ä–´†[räÀˆÎ+ÔD„'‡r¿W3 ±Yzx#/·—ˆ‘Ž´°ÓªœCƒ#¹]°ƒ¢#5±#5c‘#5„Añ“àåÍ0äT`¨ u°ã(pºžd*)ÔCN«ÒLT°“Äxµ¦¥€N:B+ÑÍ¥'Ð:˜tÅ;A~K–ì¬UË°ÅâŽ&fŒvÇ”8Ì„”Aú|‰¸ÀDóÞâì[ŒçÍ€ÈЄ¥4!Ù®âiš=Ö‰åt#/¹°‹©xÉËi‰ ¶ÈŒu„|FUM$ŽT/wD¡ ¡£0–f.H†€f¹Ý¥¬Lšº#5&÷óJ“‚ð“ uKÚÕÿ;”0UƒjŽ#5ÊÀ”;ujix¿Xmá˜LX;ù×j+Fd82$“ 'ÝE#5œ*¼ÏQ´èé¤Ü7vÉÍ©é¯?ÙRgtc#£Ø$ïL9|¾óÇÓ±YâèÞôïs”áˆD­½ž×&ðÆÿ#Õ5FÃÊ6?Ñ„0»¿+¨(~#+%þ‡o~¨Xe[ñÞØVIÑòÎHÏØ@)[s]·Ô8-‡mR`¢l) þæ>Þ½ç:ãi÷Y6²#/}ìÞMÝ nîG³sƒð@ËñÇ©ßb!㘇o´ÑÕvs4j#ÖÇpíÇ}Å8Ýì)(c7iT¤ˆÓeÐÊNÎÞcŒç={4aœ™õÉœîzæ#wy^ëöŠX–8±áÍÏápÀH”â\AòaÏ‹>Ét) ë›‰Ý¾Ø0#Ò\b©ËlÆ[yT¡àQH[”ntÈÔLÞ|¥–YbSù˜CýOù ¹#/mc»®ká×õ˜ØûG7ÇŽ~`ÿG˜zF3ØÂ}(cð~Û#/ çYƒ!ÏÙ55Œ{®À áÝc xù:Èûù‡xæØ >Uoo 9þ‘Úm7È& Çx€HlÝU\7NuèIJ:#5+¨$Î5-úæ‹lÐx!°;9ö2åæË´G÷öÿOœ9AUQ%QC\¯ºtA÷jhb˜Ÿôñ¢ó`¸gŽÏå|8hª-w¸äAAGÎqÏ[“M¶€©X´*¨­áþCþo¿B«\‡/ůé¿ïa>ž<•TQaPA/æ>ñ®¿âù>OÎ_ïµø?ý üŸ”¯ÀÌʹ‰ŠX#†bé\7{IÂ^îø»ÿ»Ÿ§»Åÿ<ˆ…C)ÕÓÓ¿:•øËåTl¡óTšÏÊ|üt}aBªª"=¢O‚3A!õ’†¼[ø#P?d(ok’ôŒÇ›¡<®Nù'5¤hˆ€7¨ƒîhŽò$€ŠìÖ²”R§ŠSÉó#+AX€,žD¹P ¼ya€¢¢#5°/+žq{5^KûR_§¯‚K‹yÉýß¼6˜ÛýÇ™4vá±úÉ2&.eèQéù—î9 œFñöˆ§3Ô1§»ry/Ù&:!áÉ8õ›Ÿ×#+2ðrÏ¥†dÑ#5]¥l2ÐOÏE´}Ðøñ#¡Ï¥:.Å&Ÿ›ÕÜôa»_G%â<ý“û6¹‘¾‡¥á~Á SÀª]*½_·²UáÌÃ3ƒÙì·gFKÕÍÄ#+­`›P„$éDåKă—‘©Aa¶¼´Œ†'U#5#+‰;ýÔf~Pn‚'=Ký¢­øBCqò;·2I°ègaiI¤‡–ûÝ™ÄÝÂ#º0Æ~ÛL]vfÏkyÂ;‡ãvlñ€ñ‘¾‡Ûæ#5E»‚=#/­yðV 1ÛÙˆnþõÙúïÂI3NóÏNïÁîÚ£˜øl7;pêy?9Ží¢ÑJPâQ·R‰µî½¸Ìò™‡`À~±:G‘]Æ9;¦:ÆÊ(Ë匲“ÅQŸ¸±ç’¾Ò}›l_Æç«5Ç7kWJxˆ¤”ou½\ãù[Q8¸dcˆÌo7‹6‰–â]æ¡0ˆ\WÒ$Ž cd%­¨ÂEh¬gs+R3$.#›!ȦDÉgI—’?y»OCõ¡DFÝðÙ•N>8¨Rf£'­í¬ß¦OG‚ø>¸Äå¸Ûd¦aÔÙÞ·½,pçK:Î}%¢°Ï ¬nbüK5n‹¢éÀiÉjJJ¦{8‚ë•óŒhìp,Ì»ê —¶—±Øí‡Î_;;åöVšp¹Ùeûýq€„•VuÃ]oœî¾YlºØÒ& ¯ýÙ8þ«äu‘ðY&Þñ¥•ž½ÞÑ걟Ãu2d÷ô¦‚Ñþƒ ŸM™Ç[Ñ^€þs1}–¾šTRMÛú~ÞgÀTœð~ªE†{)#-ðÐd à_E¼?/„>ç_ƒ±pPD«îg` úÕ°X0én{>G¡QîŠäøgL)ÆK «k܌ꑵº+8Ù¬¶ªÅ]åˆ3?fªŠ_Ì\Γ»ªë¬Ýî§R‹ÊÂgŠ­ 0o5”I´¾¤à«{ó¥ì?…‡èJÌp:+xÕH£êæß¼dîU[¼u¦[ÂÍ$Ê ßÇcæà P€@ÈKÀéz_êû:©ú}lŸÀvunwÔz0=HOé‚OcÚ#Òj%CÙ¶½[ßö~~Ž×û”9>áp—#+1H‰ý8(#1ãÐYþ°d¼ÿÿêýþOþoÙÿ§åÏþvÿ‡üŸò×þ/ø¿éÿþÎnOú¿óÿüßéÿ£ÿ7ÿüßòèèÿ£ŸÿWƒæÿ¿ÿGþ¿pÿÉúWþçý½?ýŸ'úÿê§ýŸÆßúpÿøÿ§ÿëÇÝÿoü½ÿgùåÿ£þï'çÿ¿þ¿ûËÿ›_ûß/ÿ·ýÖ×ÿWýŸ÷ÿþìý¡zÀóÿkýßü^%þ¿î'óÿOûÞЊª‰4_üb æ{êª -<9?ÜV„»$ˆ«O Çÿ{ÔéämÙˆkW­U ¿áÃÉ„ 0Œá£mV‰\zxgþzØé-ëÁL‡ÿ#5zFäþcý±ÿW#+?òÁöJåsßÿ$ò#/”ê©Müc—,|ÕÎ/3AÑÒ£‘€7‡Â2°ÂõTà)ÿ­ÕÈ„¥1 T§D uA Ÿúl<¡µ¡x>‡£•“ÂÿÍ«œd ²0BÒ/`bé^ØÐ2=ûùÄØ!^ ¾*‰ÐðþÁ>0¤éõhÁ{ÌN¾Ä©¢ZJ@ÁH!…áÿ³?[ˆü”|OKÇ¢2 ð.l ã È#/8½ýnÆì ÚàqFÐîJ„ÉÁ9¦ŽHuw Þ/[ϯc|Žü|ÉQ„X ¡J÷²©'@„.@H7Ï#+š©þ£¡#+¡ÀÀÀ⢽úûÕP|ٌﶴÎYf?ó¾öÿ$Wþ•ÿ£™ÿÁóëåòñ?íÏKÔxý:ódá'‘áIœlu*{G³^wœo‹ÏÕl8Çϧ¾ A°ä´þ±ÿ²ùë…/¤ÿsÒÖmb"LèPÁÆ‘œ"¯oÉòèT`'®¾Q¾ÙÓ/ü“ ñªºÅ´P‘Ê#³×¼Øø3”e3Æj™4©E§F¥Ù;½¹e®5š?,‘{£0ã«ÚâŸ{¢= ø=ÿ;œ–,¾³"ÑiØ!5#+gžCö%ô¶æ»üL#;´³æÑ&R³‡u£Gb>[ÔI(IõÍ={q^NfJ½Øì¼àWgÎ+LBlG–ÎŽ‰m ·¶*]ăULeƒ4Ö¦ÌÞÅ”öÐäÉ âemvøq²_¹ñóéC×Rí ˆNï†UÿZJ¹_ažZü.°ÁûÌø\ÑÆý{&°:bèèÛÙdŒ£Ce„#/4_\½†)׬§,\2ÎïV0ÆFI=Ôm]T8 x!£5FªCÀ<«ÿ9Ï¥S–˜°Àœ\¦%ìË¿Cž%ÌšlCB´ø쨎fÕ­‰Òëd×fñ\´%¥±ð$l!.qùSÑ9¢Š÷¤ÏÑO§|°â%C%yüµíøTw½ã—J#/Œ° A2@Ã#+ÌÉÐßyÔû^„gm™ m„$>¢LõÃf4”F¢îæ>W:qÆ'-J—Ç|tTT(@âPH•Ò2WË€è1rä ‘;wø° Á“VüLؽQ÷yù°ÐÌ!ξð#/!¡òç˜é"‡'%êÔ‡¨ìÈžº 3Ïϲ™!j÷{ÃfïvŸ¢÷ëñã–}šæÓìÉœ8T&Qˆ@]íx*%œ}ûzç˜Î¢q ŠÈˆŠÀÉÓÓãÐÀ‡J4“¯a7#+ò…ll@ëuœÈv˜6`Mv›C°’iØ™KäÃI“òla™ºÙv«ÆÍâ%9Ø+&y„Þ*X!È„@’çÅ)xû;lÝ!GP(=¥Ú&ÙÔ¨Ã㞢î<6(g¢D'‘Ùìå ¾¼&Ù±4C·]äƒß¸ç®ô ÔˆG½>ÍéíÓ¦ÌúÐ¥×–ŸÁâzüf¡:S Û ëK®JJŠ¼s›ô$Š¦„ DôH„Î#Vë di® úI5ã™[›ýuÏ… @á‡Ìšê¬÷½Ý±Af.q<ÏG— }ÚjÙ»>>³ÆóCå£ Çþ¤P€…È$Ö2'¬TcÅÎdÓzžª^ï:O*NˆñP7 ¯³»C£Ÿ¢Ã(¡ºQ›wû+X;­ž G…ÛsPÅ·´¦b‡KÃ6? Q¾_œ&Û;[ašŠa™ ͵=ãºTÄ‘!KB…!Q-!f*vƒÑ#+g3:ˆN?aÒ¢’€¤ˆ¡AIŠ8°žq ]ÝãôQ8gKø§IÝÔ øAbÒ”ëצÆÄÀq‡2 ÍIªÙyh_]/™‰®»Ð_[ërhlQnôÉEÄL“YÆ‚¹#5×FÙŒõ離î™p|s<~\~§³Âfe ÷YTÜ°^À4Jjû‡öæQêî˜Î9¹pÂ0Ht´=»8"ñ€“S}þŽ'sNê#5VI¨\죸oÜÅ l#/¿Ýd¨0¶­m”²í›ˆgý‚{x¾Ò³©ëò9ùq=çŸyc‹À’C®Lò'mÔ--†MüGO»â|ª}|Ló–ëià÷:W›¾ß H®Ê6ÏL‡Bšžq#>×Lb´é8N¡Û'$0IóòÂ0ñæoN!C@·[)Èñð×–d’ÔÖsØðäúôÅd,chQ> '`C°îA*ª|çŸ-âj:Ù»ÏaN²Ø·-T‚2ðE…ïãüsN¥â"#+©Ø¤õ|È(4(*½¦0\€U4„Di;Àç®A¸g•ŒÂ›bˆ#5£:h¨E&B„ž™þ¼ÚcxWhÜŠuó,Qù#+°›qsÜedx°rå}S7) Iõ×H)â2 hËzÞÌc'Þ}SY:9fAøð©Ë­Ñb± ëVÉ/Òó[#5I) Ø’›Æ’ÙMs‘ H¢TÁU3L¥QEuØÈwž82Äõàó>æ¼$m€á—¶ËI­&ì1‡w·à9ˆãèÎÞH~0 a¸@Ògå]jú¥†ÂÚ]Žz¾SmÏc‡ïL˜´'˜*µÍ˜ã$ÝÖFÌe²‘“„pvSÚÛcI¤D€âésÀ~x™î€ÕŸ™ƒÒˆ91¸I5A€ÇË'-N©Òê]Lœ7ëùÔ© Ÿ¡Ó~Ú3ú½žšj{*sšðù#/HæLšJRM§+´êT™S†íï½7ŸžÆ»¥¾©æ«d£ÖZ Aºf ÄüɆ—ßû8)öXù©ýU|™ÅÂáé¦g¥][­¡8Ñ jyćÐÆåF~-ú7ù#5ús·³(“Ge~§]™e›,Ø·%6àƒc`ƒÞC#+å#+‰hÃ^X›÷aÁ<s8gRz’ô`KXŸY?7‡æÞºôVzaÉ&e÷3ŠN2PbÓÇNnä\Æ«—¥7$lƒm5J{W,¯·sØ^(©ê³$XÌ0]Ɉ‡œÚyQUÉ«ÕŠ"¥´,ANL#/Ù‡fxŸ7#‡M2gOnb É;7(º"i;˜…S`ÑbÅ‚®+ÊÁK8„Z<'Pwï™áŠvº—€÷PìÉà‡$²¼fž:jáu*©HϪ»â‡†ä#/0è#+7Xâl7±TnHài„`Ø‘ýr£rpm‚Z¶Â°KŽP˜l/€èËÕtÚ)û¿h8Z&Ný9¿6rÇ„ÃgÏæ#îrbmœ9Ú~C‡ÙØ¡wˆ‰Ï1‘ä—³­p•i¦ÿ0äpÔ¼tƒä|g„a#,Í¡}~ CÙ¶ûaôÒNy¢9Üaœ-T#5³ÌÚ:ë-gR$ÊÒ©SÚW:(ã0m€Æ#+VÍmT‹'„¤aj‚ÉHã±Ðð—ÙåÖëÕ\VkÁs8ß¹ÊÕÅÑœbÕrgj»’*î}άá(²ångÂnZšiôÂ!¡6F Ù»Âк:¡™Xê§"^$Ü÷6lOÝò½îøí›ÇÜlc‚ñô>aU^Z„z~cÑ~œØyñ9 z >:{3ˆx÷Ý¿üчmÿ§ÏÈ9 èp>v ‰ÐBÛ®uŽ·Ð© W~¸|‘ø6&q‰¬äUvtµ>i÷{…8›@rõý’w&„.H<ˆCvòñ¾ ðÿû5ߧk^¡Ô¶“p\M|’`äB6-]‚©nc#/÷¢{cˆ?#ÓPcP.Nœ§ŠO2N»Æ¡§t#5›žt1‰Q#/FŠ+PÒt;y79 ±ÅeR†;­+ôvPÄ#/çkÍVa¦#5T‰¦Ð¦ÓR;#Ò{“®ú&Sav§±ÃcÀ_ü¾~F˜UNr.¼`€âÍ(1*¦ Õ¶žFZDAŒ=•æiP:§ÑÒd؆vÆ”)UGlMÄ82™óüÉŸ*~Íûö‘]ý¼@;õ½ƒl7i$<#/ä™Íz4 wütkÞo^Æ1ì‹({FÍN±C€’ˆï‹ÀÅÜ“u›Zt”I‚‹°&á¾ÛÝÔ<µÄ ÆÌC±ÁüqÉìw)á*$|B¢ TÕU /5¶_·Ë°èuxdwÝŒ4ÞØ`å߈xI‡§÷ê¤ãôí¸Ÿ‹é²;8?ݯQŒšÀfw£ƒû׶Ыk¹kÑ ¨ˆ0Zdöv+Y¹6‡h‡’§ÊûzÍ´ˆæœùw´¡V<Í<+3κGŠv }ç(öpá„“¶`öœGf†ã#/:$îC‚S!Úx׃ x¢›§:Ÿ ÀÉô 1{3Aæc³²²¹Ù”Õ&D]ä 7Œ@äÍbCÒm<'c‹ÐÓÖJCÈÌì4¦¥b˜(‚ŠèFILT½LKê{°OX~¹öv嶧o©!ƒ"®«å(¥#/(ñ¯Ê"ùGHØN`ù_/[¸9—Æ/7×ÄäúQ¿G™ÌÔÛ`Ø‘AT”ò›ŒÁø± Gß×ñ¯ÔYRÖfj|4Sh>\ù·Ü§â¢í¦{¬«ö–ø3yïa=¢vyÔ êØ ì@8½ù{®XoÕAÐÃ<¼®y‚€ì0=ïp¾S㓵ãU?0ðÛ€ÉуX|ª‡K¿©Ç¿ìÎþþÚ.I4î(ì²g¬õþvŒ,9Žº{{—moܘ tïåܯMÀ‚P`aTi®û·?L¥yLèË#+Éâ•ûY Ln}k`VÚ¡ÒYšÈ@3P)E¢"û&á­Ãž0¸³A+ºFA$@;¾ l½Ÿ?‘Á)ôÿåå Úh¤¦Øi‘¹T //˜Ñ^+öË>h0ˆhÔÆ“ó;&qî éÜÀK¯Éþä:6wH#/×tt)&¨"ê>ó(äÃÌÙPzNöÍ^çë•÷%GA™$‰a;ÔûÆCADzü½dyÒúúü&¾™¿ÎQ[FoâT=Þ½¢Cðp9ÜtÕÔú½—Òx”íëò0u¡eTÁï$ÉÄ`qm¶tê<±”^³ÂOd¶Ý‰>«–ú9¯É_Š>âã¡û|?ì!$2BI+B2ªEÿÕ¸Ÿ¯ÿ×ß­¡J…PŸ W:lâhˆˆ9 =9˜r’:I`’GòyÿGî¹üYÇý$²+Ä.÷(€H\ŸÙ…&ì% È,!l-šéE<3 âHîþïêº7*Ù»!öpù“‡qÆT£(õí’ï?)¤4²€Ý…–RîtgñŸ”Š6¹{š Nâóæ|ÿ~ÿÌ(tCôÓÞ°¸–ýÿõp/Ã[(’p]Gye(Ä?óÍéÿÞþþV?3S³éåæ¨b.oÆOWÈ""@¤\ˆ#+D ºÉòp yTF!#+?_Íÿþ¿øÿ_üïoùþÎFƒ£þü¿óÅ2©¨7Q£Z3-G9ÝfMÌ'ƒE÷™5¦¯„äèÁÔ{åým(V›îÖ•Ðpç} ŽP€„@#5ÅR@7öð;7rWŒ#5}9î÷:…SÑÃçï·ˆx*“ÙõÁ0^Ò)¡ø‘¦…,H†ÐµÛ¡­qg"IPI$²øQùõÁ妇#+×uç.å!˜V°UQ³ =Ê¬Õ fߎ"[fwÓ 0~(â '!ÿ-ñ²Í°Ý.Èfà-À6ÐÇó&ñµ×fˆðäû}uÿí’6wº€" ¡#+Aìm>µ!ÎSñ¿Ô’S!-Ÿ’ ãË_byÕõ!PÆ|/ÅÂfï8á‚#+=Ìîd’)[TŸàêïšI2ÛžP%qºï¿Üßóß—öP"§?>þuiFöR-mUYÞ©>Àáôs}1íu^9‰ =çêØôªï11W9?Þípç©ìëGŒñ¡³Æä ÛàWû ;ŠâG€/_œqDdT#5£Âƒhxz(Í{—’Ai#[[³•íæ{ê}cë{»ý´Âä`ÂjžnXS Þl89DE jŸG¿ßˆ~#+ü²Îhˆˆ4µgËç­$  ªÂëP¯£œ:ëñ‰úWZ²ÂõA},åÆØ  к—ì_(dAž+w°JT|ÃȺah—WÅ'Žþ‘¶_ÿøaâm¶^#/ð 1¬ÈÙ+Åü-ìÕþ7ü®2þ{îϾ¢4ÓžVxϼ«úD_ÛsõtþßÉ?˜ÛY¿Î¿—ø¡b§á©J8Sø±ŸYÂN…}”P›?®À ô‚ˆ8>D@ßD=þÍðìå{Úç„å ¡ï.@žNþ`Ýå(Gw†îÃPÏŸÚ9c`|¤#Ï¡RSè“‚€®¤Œ‘o¥îúÊß=n?#5}“”—0½üG0N?Çoƃ"ŒoùW"…DŠ{^>`‚³tŸ¿ïû®q÷§ŽCD ÓðËè¼ s¹†‡,cŸkü}Áþ«ú:”~o:‡>Aýòwä>"ŽþõìûÞÑ^îï>4¸ÇÞ«fÿ œ×HEEÇ÷OÝ Áûâdäµ$žØ>ôÈ@ò‡}Ÿ“߯¶‹ÏôÖIÄCú£^põþÏ–LÈ~?üÄ1Ù:¥Wågeý·6f¤ã¸@]#5´žðæóB¦HÃÁ^¨ÀH„^𶚢(¡T´°"»JƇ¾*ʨPúkkÓÊ+#+ˆˆÎQ*#5ÍëOé°#/úJ÷Íɵqêy…~LÃø½æ“½ëÌŸŸÙúoaJŠýóϳ‡«¿¿«V8®u€’!š½ìÇ&ŒÉ£ZÞgúñ $›M¦æ>/¢‚­åÝÝŸ-£ÓõŽÕv.‡ÂWqHº™‚ ¸ý÷ª&°ï½oR~“`ä—Çôpã¾ínýjÐ)Kø(›Y¤<Ÿ*Ÿ=}¤•ûV®šÌ\¤Þ“C÷PÁR阘õ`yçëx!É'ëá„,í>jÑβ^Ix#/™ ͽE°~|s Øf];ÂƵª8b7Yœç;ŒÌÓÙq†2^åkl#/R™ñ¢žR©amÏhÝöÊäA¨i#®¶s€ü«7ò§oäÿòÊôÒ±dg S%/Á¶¢‘ƒÑsEH¼K`»¸®!˜¾#+Z¶ƒúìŸr:ôëz5®¼9VŸW?U¢žø°ýŒ_µ”*âf()˜)v|uhuwk˜†¤#+V@h‚ëÈRX»ú|é‘#+û£v(ÄO=¢}ÛœD’æg¢¥:Ìâ¥-~8ºv)áãñ1³Ï‡¿–Ö¾æN:>þyg¯a$¢î†²“§<5W=÷‰$×Ûº·q¡KËÅí©Ë33xòø©;žàîî†kw aC ÀN°a(½¿yôê=n¸LÌΆM=㨺]›{›û»îÌa×£q”É¥KƱ}Ó©¨ª0E#&ž{M`,B+o,c#/t¸I2Úåݯ@Ž "#5ÒJëᤠÁ´UÇu×#=§{V*È#+¬Hˆ‹…G7?È|žóïÞ€’=ÃÒñï„DD^¤R½Â/3W#€×¯à‰÷!5k-¬Ü‚ÖÇ5A¦ h%3Ãj[v˜@º<v9G!Ù^pZo‘÷=/Asê#d–¾éd1JW.ë6Ös#/†ž}ô–×n»ê+”Æ0ŒTЦ±91Yþ¼E@«@‚ΘŒÛÛs#5 •`ëïïD‘»33j÷xÍ E#5˜­¢çÚ?Œg-§4Ähó¬H›úW+ Æ.D†v΃yUHߺ·áS„pB¬‚žˆ—®c¬"ƒU¹bµò ¸Õï¯CLÍkØïäϬ×,Ldï¯ Æ!þHõ¨÷û—©½Ÿä Ä|°æpö9„Cˆˆ=”/îüÏÔä/XÔ(ã+µ•É{ª.øšÐ}e&}ÏAQ©^ñà£pøÕ¥$T‡{%ø—#/TQÒ “ØmÀyF!èh{[`·GòåY" ™¨sQy*«xš„âk%^V##/dÜ;¥,ú|èÿe\¹#•’#/ʬ˭ ­™öÕÑrد…ø¯4„®sØd­£‚”#EŠd¡Ä@k9½Æ¯ÅÎb‚Ίyë¤_3ab"'bkóUTi±µrf±?ÛñµL¯9ƒbp(×Ðû'Kåç»»RIÄ(åâR§=QÚñz̺¾>OnW‹Ú²^aúÃƳ(¹gcHûP€hB÷~ëbßÞ°ø‰ï_×ôÈ| æ7vØùy‡ÔÕÑv9VóOIˆ¯/͸“)‘¤3N¡~rjM%µQeµ¯{y6؇]þ¿e•CˆUš<-ï#/ú°Eë„9^Ûë.—ð¡ l¬Äƒõ*†V2” ?¨¸uÇÁ©v¤R—š›QË ¥!Í7åk^¥ïW\‹d@ç’7ÀòŸõ'nêaÃ$9wâUïRq1 PÉÑM®|¥È±~RÀ-9¡`À—&ô9¨p‰yŽîxûx³?Ýßù÷Ÿwë–¨<˵êÁ×;⾸| ¨$&õˆÿ%[E™}·ñ‘æŒèÍ4·ÏØžéäpŸh];î`ÆD~&ðAIr(Výv˜×´e<‹`zð+ù­löØ2·K+›ÒU—2ŒW&Í]WìÛ½‘×ÇåÞðæâuuï b_åûw–nuXÌTñ‘üs‡ŽE÷¯ZSÁªNÊ á(Iò×¼9ñ¯Lzœ³ðFÞ›t§ŒŸ‚±ÜÃÌ;Pùîu;•¬Ëè-¿{¸Uñâ6ËŠ `’qüv䪅|Ö•Óž½ïïc‚WƸ1íoÛ#“Œ7>±WêäÄìøÌÅþw¨Þ‹†=]ñ.%ë'n¸×Šç^Q²èìf$Kê³ošýª¿Ðü‹K‹Ñɇ¾nd!f-òÛþ^cõxÆ]±#/Äí*i_ÂäŽiY#5óñÄ|~Y€à§"1n,sƒ ÷¬çç¢ ‰¤Ê&7³"ß&(#+. Š#5d#5ìIÆ%ùdÆtUZx·|óW»k]ø°Ç!¯Ì…D1‡ÕÌS¿ˆÑyOöyh{kŒ5ˆCTódxQ›Ûq€'–5—[ä°rhk5èãï0T®*ÑÍzE ñÅCà"¯:ÕÅ✚–clzŽçp7®Õ¤~þo¢òšÅ€rÜN¡Ïåâ½j£Ì1Ýêã6ñ¹ _çg|2>:Õq#/Ê?\åÞ2`Æ>8ýgT}›|­øpÙ\1/%Íå亣ŠzNõêI”Sš‰e /ÌðE®õpø¯‘u‰Ÿ†ÆúÁ{_mA³#¾iØŽêæqÏÊ#ãƒ{ý÷9Ǫè¦í¦˜þoÆÊI{š®7ß³–'O3Bzw”þ±ºû\zçYv$臄†t’Û}‘{Úñ¨¦:§[õ×UšâF#©'7óºR1Ÿ¢…Œ4[੧w)ÏŒ:?5„h¾ìþ¢¡¶ÔbmÙµ¶47Ôüœ>¼×E×äñç8Ñhrd *( íñ}Nä«ïñÓÇ]Ù+v#5ñidÉì5˜Þ¼¥P'&Çï˜à Ž6‚‡*26 ôÚóœþ¬>YM¯]·º$Î>X´æ~<ÝN¤ Äsc`<ãë`›½*„¦1ä!ªû7#õEF§,laÇcØçؼ³Qe+3¬!å½ýýU…Âj«Õªõ|Hz“”L¸”¡ïï*FHΤ}Úò±“¡ãêïÃàRlÒ.‹‘bý#à™ÇãJõ}XÆ QSrvúîÑu秧çœÒÆbý}f ?º8ÙišZiK‡´2æc~l¨ÓæûÝEÏ°wÎNOŒAZï>5T}µimÏ©iü\¥Ê~ë'’å¿nØ¿›*ŒÂJQjÍW¨s~Hæeü³€Î<ÎIW¶ŒÎ`Ñë“…ÕûLj3^1“„L×ë ¼#“WªkE¹÷¨«¨oÔúݺ÷ñ·Ýðû3a®yTÁ¿Ê*¥DÀÈ—ÒÁ"#+ÜNëg§aÛà{Ç^‹ÕÏ!^G3ð‹&7(ìeÜ@7ó-ÊcQþŸ¯¿wœ3u1ž£ùë î¼úÝiEŒÙÚ àxNfd«¦äî¹²(&ŠÜ˜?}jcáÇüf x³¿Ü¡L±5ùú³-¤áŒ€3ò nQãþ,ýñ˜‰€ÚÁ»¸?yfâ#/\›È9}ì'Þ2$RUM—ÄÑ`â÷Ä|Eð"çççö5µ’ìEì=Y^B.Gd¶3øLø{öy ˜m§5-®ßoûV©9ÁWi°)ãì«œ ß©§Šˆq@ÑKÞãÖª6vò]÷³nåyÌr¤V}Ø̳ü‡ÓuÚ+÷ŸwÀƒHvOØwëZ”°ã‰ñ²ÇiŽ¬wvf§ÆÒ5Ðãôâ0p8?ˆb!(GoáÌ~Qû&æÆþ¼ëú5¯¦¨b®:¶&ÍÝ> ½”ÆtÔD×_^÷f9ï²Ö>OVòÞ³šMé#59Û<§L~N‡ìoû ¦Ç{Û½i·xæRÓ.]ÓP6º®gxê'ìv "²áI›™˜cõ&cø£ãNÍœñùIKÅó½>8ˆÓ¦I889ø†ÍåÂ<2Þ#5–ë!JÂ!DzS©x â<ÖéwWG·¢Iã±år¦)á›Ê<= èýŒ`¢{¼aÄ‘L'ÕƒíÏfŸ!¬†ùhèœDø!åzŧgæÇ!Ü5%5ýÓ(Ì÷ÀõxÍ:×ÅÜR`PÅ•J]3zÍ"½êÚpw/He8¯2‰*Xú(cyÙs>Cx0?ÁQd<ÓÑdI %ùþ˜/èu¤ñßÍ®›.»| w„ÿu‘ ^dlÒ,jÀŽC¾-"I´i÷º¦™óLù`¸È ô²»ù~åÅéP^ýòùðÏR:¼u¨?\&BR‰Îå:Šª©Æpø’’U#+R€)IÃÃnz؇¹tÉFÐHgò»Š‡é!÷ˆd™~Ç'†¨­Rò{ŸÝŽÞç^æ§Ã7O£}¤ÿô¨Ék®€S$ w>Ýþ¶ï‡ÈfRÞ#/Œ ‰6#5QÂ,ï$UŠñòJˆ‚¤(B<bª¯Ûþ*æðÿ3úîQÿ®T”ÌÖ#/ä3'‹ž>¾î¾=ÕzOÕ1‘"#/ H ÉRiy$ƒÊzq#5@Э¨òþ•éÚ·wÂmàöþƒé¼rŒöb)‡n‚ÙwZ¶ƒþÏu÷ížS…8hÆ5ýBƒÇ)÷‹Û–s¹c¾­…ÙV9f-›éÇ~ý´CÈ››;Ñ·S—4ý‘"äJ¢ûÄö <ø¢(š¼/V‡C8!áü`•ï_<6u4ËcªÁø-Û4`¤\q˜h’,AbVAŒÕ`:>w6$PÚìg\BÃ#5Q˜e¿ný„F(´Œ(E1ḼíÚ8;ˆõ±­É>JÑ‘i²|ƒCž R~¤ü>:¯ ŠóBë‘Ó‚ N‘¾k7¤(5Tˆ¤$K:¾ï6T$¦ô#/ }EßÂl$¡W9-Yø˜ búv“ÕÄF#59löAŸ^mæQ@ê*R†—HRAv¨+g¸lãÆùf’»0$÷ ±p†,I¹ÊªJ*¨S,¯d¼'Çì´ÖMÈÞœÔXúÜeêãÛ)CAQ¨–£ÛsÓp$£8ƒÜ}‘#/þ= 6Ç[Qî4…ù¸/`åÓ¢< ]l^”Dhº²zÈ ¬aMÑL$¢U¶s‘ÏhQa¶ï‘ÁÙ™gsŽMgyÜĵÓ0ügx‘³pÐM$WôAÅ°$ë¤Ìp°™¥ö¼•¼Ø’,,˜Ž_Ómã.ò–øÍ?™Ò\mÚÍåh”ˆ&8D¯Rßœ­DlA´m3(ä…˜lÃÖTt§NÉ)4•MfÁíWX z‰ZŠ³•#Šîã_ÊxÚ6;}`)—MÛ9 ”É4¦H`ã?Ù‡+ðü=<’Ûm㇑±—÷¬èöck‘noì0ÌÄ`MËN>1×®ªƒ¢=NU4ÒˆøC‘\€#5þ“Õ´pô¿î†ÜË…Oà5?§i  Tx—fî…DFäTݶMÇ˯ì€M@Ï8«#/¿Ùd‡ÕéðÔÿ?yòÝÆ5EšÊ¨/¢v²$OåóvüÛ±X|¸ ¿Ã“ó§¿ÓãÐöu僺äÑû‰ñΙe^’´¢…æ G¢™V©%iowë)óŽ&=ÝŽbçB lï—ÍXXØþÎCH‡¥Uë7%%‡|eFbªšât»Ó"gÜ .#/Û¹×#+g<;ãw’á»EÚ#5'“ÕçG¸$ ±%+˽‰íp4¼Ü#5N"#W–ãÚ‡¥ÈåÜîDžJÓqù$b#5Ÿ7Óˆ™ô{<°à(ü×}r<»«ûz€ùÿ!n~?å?°â#+!ý²J#Û¿‹Ô¼š?rrUÿ<#+ä”j@Ò+J…Ч!Ò$ƒí#5‹â¿iAÀhUSÖyþŽ¿÷|Yþ¸s&þôsýa¹x!¾Ôæ`ë¾³ØáHp³~ù¨&4ÿ°rîãƒðÚšMQt#™ÊW˜|Ç÷„ÇôÓ@$ˆÕ¼;#+7ÊŸ·Äœþ¡_/æ»xùÆÿC”CíᄵñO²Iô²"Õ =/ìúìûói=5#/Ÿ½1£î©ó1åE¹B˜yPa 9 ø¥#sß±ôþõc\zû÷~šÃ=3LÉö°«8?>Át¡²¢ `C(¿ãŠ0FaM2Œ+Ei}FÑY®½–“NŽXÿPv²'ñ=F¸#+«4UßÔ?züÅç™B€!ê+;ïA¾¨Ã‡Hy ÄbÓać®*’üë·]à9 ;‡ð…Ç%˜ü ‰a÷›SÒá†D?O¶PgCð_ÇÎyõeÅÝ€¯Üí>×tœ`TH|Q'®‚¡ÆD|¥†k#+ÐÁ±0‚phfBiÐÐâƲ½“yÌlIYIQX•r)ŸîÌÊ:ô#/Ãi4²€ÙÔÄĭʘÊ3†FšøZ“'Lß¡#/ZÙo!/wÈÜ"‚j6ÁÈr©Âu· v6E‰ª51:ÆšƒF²§ÃÞ¡@ÄCFYYhäY#M0å3Zä®JhZOÇ>¨¼~ÓßIGøÒŠƒ#+©#5½_¹ý[0ˆþcmñN‡íö£—¼ßãóòÑn¯Ëô#5 ütx4€@n¢0ùÄ#+ø ¢ ä;û=‡ÞDD´|KÝÒ,ùŠ=ßGŒ³@ÕºÜ$ÍYùþMŸ#/1èâ#/"]…¦­lLŸ¹à«ÿv)®7¬ãá¬-ÎÆg[a¥:Cê#5¹~Ƴ÷Î »›>4v¥Œ6Y17͇;~˜nÉŒ†7c1]E9#/vwäÇÈŒÝî‚R.‚¤   k#5ëã±m‰ïÕ²‡\ƒÁ«!;4_›õ¯Nè^ÖóålFã„o @Í®ÿI–qM«#/£x\•›ôþ?¡àyçÉ7mN#+B1þäã½·ó Ý¿cÃõiô>»ùxïÀs0¹Üƒ• ûË÷ñüGß_pð»—¿ÌÖ݌Ƣãõ^?¤.#/éZKV1xI‹‘Gì Š ý¥Be‘ãþ8""-`.¸28 ð„giëݳéýž%•Ë_ëÿgèÃòŠ¦)øöÿÛÇstMãüRÿ›0up>ï#/#5©ð0ÀÚŒ¡ÆPÒJ‡ìÿJzŒeŠôãÀý?øôxÙ§ÄeëY¸âå_ytÆ1b+¡•‹ÙýGîþ«÷ÿø8g”öõ¯ö¿ÖÇõÑþܯúJÿÆ¿À ¡ñðK>òÖÐ×o¬„Ç ¥¤i˜é™ÿh5ê‡mUÀŽ¯§íªj› ·ý“‘þ„ÌH|¿ë­#+9Ÿ{D‡—‘á,Q¾WÝ y@eÃ)  ä@6 ¬Nã¬æ U8¿âÕTkuÑÖóäأ̈́í4:k%± yš¸œ8ÏÇèW»åÏ „u<{ýRÿ¥6'ºç·ä?ÚA–?ʼΈHîòãÑò8wúýg>ðš#+ò#+ë0ñà„£Þ½ç¨»18½½Äç}ŸÀé—é#»ØüiÜéÖíÍ(õɶÎQIž¾å#/fºŽ ‹ÙÖ#êI\ Áž†Eݞᤖ˜-·W‚·ÒwM€GØ'Åññ4z”`j7·¸{ú=zO^¯¸ò,ŽÌ=^aÁÔ‘Ý’F¹V‡ˆIr§wÑÜl"”`]ä÷xôˆçERi8*/×HûäÏT÷¢/Ì#+"t8¥Nô¨¡+묌“½$цŸíøØOºÿf¦O¹aziÏr{ˆ7NØY´ΧoËËåõã„šÀ7‰ÃGòæwÈr'S2lB;’­ß§O.Øg Í‚ÚNEIHrQ!dýޮǎçÚõ'±~ óõ¬qöòU´þ/yéeÏ«ûRéƒó!¶Ûû2 ÏHõFfœ\õ2˜‡¿Hw˜ýÊrvÏ&Ø~\|a`dÒ#52,#/RÚ`JLxþ¾`"Ì¡GÔ3@;¼ m(@><ƒùÄ· ¸A­1碠$söH15ªÉT Û¡—#+¢/Ô§'°éಆE½{ò3yÙv²ÑP¡¶%Mqî`îÙƒ„Ä!ªJa»¬¾|<8+àPu~PºÐxʯ^jOó“óÖd¹Þ >èkøË»ú ®‹Ù[ß™êÓ%DšdiÀ6½szûý|S%ë„7|“øCâ’¨,|8î¡Õ­ t¸ô=Á©Ã°(ç’]‰ÙàuÏ2' ª#5Šò„'i§ÏËS9šm)néç*0ö‰Í „ëØ«—Läéì=ö±ªþ“yº É‘à“â}·™G°ÃÒ/Ä=g´¼WÛiÏaÞ$ñõZ÷> c– /Î c·à2˜&!ÚþÃÙºïqˆu».»83÷ày³±Ñüù“Ù†l”Dzðùo¸œ)ÃsN!0”xM%(PÇ 4Ú;ü€ëëÒø|Ò¸¦(i û>sG%}aϸž”}G)ÍÌÓӤÅ#FÇ IÕ4acLp:±©Ç€i×Çðx’Ñ…²\ž£Çä ##53!†B(É3›6”_¦|ç݃ÏHˆÉ'ùÿZvÚZ½bLˆSýœxƒ8v„|zÏõ~0ãÄ8©¾‚”FÉþk#/áîBî„E zXôžô8\…*#üÇ“´ÞÐ^P­šà#/“mÍÙIL„?çÐó6âoª— É–Zvÿ| †³f#5ôÓ#/îxFwçú¥ûΡٷòóéöX¤Ä$ÿ6gûýŽ3¶½µ8|czÔåãg’bSBRÁQY¢á†’ÚüRkÈGÁ¤°²FAb8JÆD@¶¡‰¦yQ,Prâ ¾Ÿî·Ó\%EKLj†›Y˶YXXðR…F/4Îtšç|6Öñ~Ñ€?„#5"¬¨Òá×SpBx~Ù_׿’95‰›~]šÐ>†«ì¿·± ¦÷Ê3õ‘ŸÏpû.Mð<_¹;:Õþ#5)Ÿ¶{™y(ÀýÅu™HµÍïéXi8¢=k\`3tw°0$ÎêÅ3N@€#5C5P(~À.Î o³¨·ãËôÆÄ JbÔ—jØ©èÙóûSÖÿ£ÿ—þ¹ÿ­òDûñCЃí€``²ªæžÿîÿ’Sv0àX{1yœþ®´ã¸`ôâoõk Ü#5)#5 úèRë‰ûHòýg®p;A!9xPi"Ö´ëö?Êø‰¤xú‡Çá9ô©†n츲ÙIhëŸ#+ó;&™B'I¸6B‰âTÌ=ÿôVC÷q›ë‰±Âc(™Œ°aÓúj{m Õ–wJzF-Cªa£h6Pé&«8ã²Ü.ÔKU%*™Iämiœ%JÐÝjt‚šÓËû˳l2I %¡o‚êÝÒª®ÕX»ª4…LJ³ƒÊ3ªHÕ×_›bl"ÃÈ.#5 #ŠYŠBË·»ÿ¸]0?´f-©=÷+à“Ð/Ù381õT¦,_ †2ôøuØÙ=;üNÆì[8ÝÚÿNͱAÓÑmð#/`Cµ5€æFVäQâM~Ÿ‡ëž~áïÅ q’Ð(üï#5Qþ` ï˜OØ?´"Š}C5tÿ7H6èÿ2À”`SüÆĕCÚeãäjjƒPÔÉ ÿ¤ðßS6+ýìbí±¥=—ïªÆõ$Eª?Óßÿ/ÏÃœù§èŸ¼ü£ u…Q»\¡‡Š‹Ÿ)Dò4¨XE‰+'|#+~PVj`ßXa$¶#?FßèK8fΫkJ#Ç5Ûú4ºÞðß)rë^,þáÃÒ蛟ð7†#/ðàÔ­â —~Q&%^ÿÞ8#Ëìß*®ÛãµÉÝ3ÛTõ³Œõ\7q–1#5ÎÞy鸂ÑÁÉ9|ó÷þ«ø~Ÿ¸ îÚ»˜*8ÔÁÆzÎà#5 Ë6Ì”=úCäY• j`2ÿKX%á|t\Õ¹r-î6PæjP•T‰‘ I¶7ß#/”^ë­ H7×y¡Ü4H:O[¸ÈZõûÁë,7Ÿ¯²I:tøB#/¬Ô:”ŸÞCcZ!±#+ܾî[„9&aÝü<þ¿Ýÿ6{ ÷¼¹zûRºñ "M9}8¯¬בÕÜ>ÿ¬=ZòìUö ¹Öy‡™ŽÚ{•wÖ.¸liuufí×{ŽöùÉà¿Þy‡MÑG•Íïð{ñŽ–÷9„Àñ…ÏlÔÈ3Œ”-¼o#ö}Pß·ThxÈÛ)œ¼<0×#+@À6Ð~$¼O©åñb")Ñe#+ø/`˜–hhÉ7ê1Ã|„à¸÷r;Ïì΢H.DDÆ@I#5´$q+¡o:…vñÿçÿ¯ÿßÌ®zØ»¡EÒ§á×!p?ÏŠQV9ÁWbŽ|ßàª0)öû¿“¿³ìÊË÷䘩ßÞ•ÏÛú»ô"e-ZÅæ°½*gRc“Y7¢sA(«BÒI·=Ïëü¼}ºSÕÀt¼ÒµÏòvÖvÏà}Nø·Ý áóXº&í°×÷âÊ°Ù–à"b4CC-5#/¨aÁ ¥;ê-‘:]êË5ƒ‘BLœÅ¹†„mf*VK–ÛkŒa$”ôÛ‹lX¾¦áýmÚ®úeËQS$ÅÁÃEDìêc‰Š£ò/Ž[ê(T'\¾7åØë>Šo¿;Ä«ÉÜî&Aq»Iòª–þñMÍvžìô¹å•€-Ô92}‚ä£óW@/»ë÷†XnF0‡Aà÷oµ^'ëñíÒ;¯·¿½â€#¾,°¦pl1#/Rh4“DAª„”Ÿ(•ÓãÜÞ¿‹ãí|E,ÀdÄâ~áÓ*Ë»ßîZÿ(nêÍ™##~ú,¥_aÁólDoŸáV´lüª§œ6É,8ý„k«Üýÿ™ÚÆ÷M²´0ëý_ã,d ;c­Â7»ß_Ae'Šýã¶Á#+#“?¿ø‹éFÚéA­šTå#/qh˜¥ú¾>=¼J£ïŽ¡ûÓ{ì&ÍCƒ`çî×Úþ»k|ý„<" `HV61³žqÝ  —øh–€úŸ”ày Ó€†º€þYWÂv6y&•L Hha øasvˆÐ?›ß3wã;7ó& ïqC·¢áûîæøGÝÿ—cձ俞MO#$¥÷ýª™U¨¸û~ÃØú‡ï®²bæ0”e]}=Ü#/~ÃáõÁðøáIJd#¢PQAá:d_P‘ ÔÿºCáÕö ºîAyüóâ‹°WôìAÀtŒ>_“ßì fU#5…I5>#+ýfù€c¸=§æÀt 9 Ãþ(#/…t„M£G;·C¯ #+?n>×?§2svI#/Æ#+‚H&’{~`ø{¿±ýC‰ôåÎ>+_íß$?a$…ø~ª=QÓÁÐkç0÷’ÓP$ §]µcÒÁŸíX¢ Jê*³‡8~TúÿoÁíîàéT~®/¨ÐŠá#+©Ëˆ€;ieß^ÓÀî6>¯Ç´#/ɉ¬6}¤ëúfoéü¡¼:Šò§^p;ÿ4†½ÓõŒÒÁŸËÔœìšRT‘)Öt‚hCðË žîù±ÎÓœöõ$îõúö#5³ðuÄ©TÜ k£¯òL~CÌ(O;äà?¹Âàdìèo§û€|ÏMº'ñÂ%ïô¸=hžø=¤  ’LÝÀÙÜóäurμöª¤gT`D¾Ðëõ˜#/!‚{z³ß'§ÒøvÎRŒ’YðÛcªÕJÚ3˜§¯Ó¡r×ÖkþNÉñ^n#/zÞg¶4ù5M#4-  s>ƒÔ&'Oœ8—ê:Àb#+}&鈯_sïÛœ{á¾gôd°¦ífFðýY’Cèc¹²F‰ÛƒâW¥Ä7—%t'£´w~jgÛø‡uÉ¢¢#5ˆh›õyÇüÓ*gù^p ¬T²ybàòLGÏðYùj¾Àˆ÷t~ÑÏ ß‹ü ûo¬ÏãÜv³³2}~#úñßgÄ3VÎWCÑÊŨ£Ÿ¿Þ6™ô5ù7×±/©ÀÛ&ìL)hÔDü}bVì½tŸÃtèÐÎ1:'Ãò}a¡#+ú²&<Éã ‹Ú#y¶.•#Éë J¦‘ÑŠ3O­O¾‰ Oªý³ìsÿòYÎöâu  ¯æúþ$”®H§ÝíÄÜ8t° éQ …ÛQ3ò­”7$0>±6"_¡™ÔôK>\Õ”šá±[`r[á™öëNãÇ‹Ëà„~Î&Åϟǘ¿N¿—<~nÈ &Æ`C[½¦–´&IÊž?@oÄì&³ ‹ ‘9Lxç'ÔU~Ö8âz’ %û£/L&^“Þ‡«Ý°ÚyíXõlw°4KêmK¿övžºÊ½¦?¬ô4C²÷Okë;65w\!FA'ù|ÕˆŽ‹‹G$Côç*Úzí!­‡CSp>äP‡VÉ#‰†#+Àòðª$}#5ÌÕ–ÎÆ¥B°U4B‘xþîÈLÆÅEŠª%³–¬Ç-«iŠ‘’©f9®l6à":ɱâkŽÃâ ÙñöI©¤íšŸmHlÁ’ä“ÎÇ·è4È(E’(Eúwò ¡„+E¦>c‘ùä ê}ÌÄ©bà `¢?‹¼ésçžDj¸Ô¹`¢Ø–—|OO…=²û@>VIN~Xê|B°è{Có~yã<ÃÌD“Xxúww#åØC©ñ9§Nµö&>îôΧÅí#¨X ##+å!î#/«ë>ÑÙ½¹Ê'áo:z÷}äÍ붡RÀf?{ÃÖqÈMN“å°°SVÐþ?~ž‡Âtži»lÛ}áÅsæòþX¿µè?é¿ø˜v¢&'$ÿãsHÐ÷T”+ÿˆ,ÓMîÑeÔÐÕµMM%‚¦ós»R¤-’ðDãr CƒéWûëhsãêæê$xFÐIésps‘ãˆñ¨óÛ4 9*$þ.ø^òë>ÑGÜQ!\.s·•´60w¸gã¹pÓÿY9}ïxºr€ó$ëýŠ§èCŠÏßÜ`àÇ_vvþoÐv÷ŸlYÜ!Ö#>¼Ì?M¦¨‹D…FGv¿2w@{¡Þ~p;¤Óôo$œ X&9Ôî1¦|Þƒ€M¸”$¡¢‚öSa.Ú¬æ‚ø Äf#Šûºé·ý°|tcúÒù¶|33Ãõp†ç‰-¸âuÙ÷÷`ÃæÃø‡ã©8}‚„((0q ‘f‡þN—À*K3´(h•ÔP3Ø{Q:ˆ“—£z)ÚÁB׺Æ¢foöâÞ ,Ì‚e*¶._èƒ.áû¦—=¥Â~ í“û,ÙOÓ·W¯d÷ÐÒSo‡%ù¾nuû‡Ä±)`þYCH7©04Ànâ¿+Ô¼I1C‡.¢ßðeKg#/9Ä•~¦«£àÖ³“CÕ$àR™©!Ô‚f5èųATåËÂ#/BÙòÅ„?"C).„+ý…Ï,@>c:¦­pþw~T$«ÓK0c/ähª „±Ò&%¹»ÞD©éíá¤Nõ¼!×Ǧ#+!Y…Ñ¡¸·U“©P»¥Õ#+*¸ý!£ŽüÁ” „[Ë/4#wï檯<Êú»Â[€ªØ0R#+sz´°ˆ¨‚@£z¼ ð¹C·9ñ3‰MnšQñ#5Jã Fêd!-Áld›ÝÔèì§Ûߢ§ïûÉÉ;ÃøÒPt7 ´Øá 'ÕȆL!APÑ9&Åp8ppbHöçP zú‡¥|n¿B~-O¤ÑWRWà?š‰Ã‡§Œdö–d=„ƒ±&¨S0s§LÒ®+ "ëeF?:ƒc7¡ýubCµ¾æ=zÕ!H ÖÈô’‡R…PR¢JAHÊD’ͦ0ŒÏ ÷àÆ'üÃïù £~ðû’}«‰HV_ÓÎÊõü8˜Ûíöž„؇—bÃ3¯@êpçõC§PûgÒpHD*pâlrgBO²E˜Év õûŒH`êÀá¬aÌ›äœ|¤•,:LgÖ 1 ±¯C­£ö=Ï«°#/œYvèG‚£Ô÷ÏY”ªå ’æ{PV(8ÕAÔ±#à™ˆJ­-¬6p{q³I cËYgJ„¡ ˆGÙ™ &I1°C!8t¨#5‡ÈÐä}ùÓ°.'”˜4Ù;÷Ä\=æ‰Ò¼K¢#/‰_ó$”#+‰z§µçŠ#+öiöÇ_G‹r3¸M¥í`ýÖû0Á¼siì^ÿ4äï ‡3`#­ÇÐAÏgÄ#/øñ9°#5ž¤¹É6ë.NîÈN“Ï×Âɵr‰…ú^}?ª¸lü³ÀÁµ¸‘œYl‚ò3ßô½³#/EUßr³X÷qQLJÒ}?_ë4†Ù5–À•ÁaL‚–IöŒ)ì@P0y´àx›cm?-œ¿Qºúêlé½C¸Ÿ=^]¤1íƒ*Á#+ø…VV%KÉÛÌËÇÑ«ÈÏ°ÚýrH}!ìŸAÇÃÎO4Ðúé÷˜(o -,k)“ø„+È©ý0«>’d–É–±…Ó%ÒÔ9f $„¶ÁrhüsæAú7ÕõgF’¬cAòI¥€ÂïçÓËb02¬x24Š”Pi°Ÿš~ƒþ>žËð¾ÜN ‡Î!Ǩ\õlñ:NÅ–p£]fæÂÁVpÏ7Óý]!O\ÐD•N³­HMÆÉÞÃÐœ÷D™_01Oœâ»>„8ûWNÑQuóƒÓÍðõžjl¬ß¤Û——ýK³F¦Lý³ŸGýßù4ÿO£ýlssæbI<üS?9åì&÷3ÊË|Ê3ßåݤ­²|$…ý¾ßù˜§±8úÇÄä?#óÀAÉ ŒïC­CèFMp9@‘ì¼ÁR) iÚÌm¥¦£ e*t,D¡¥ª°ó²’CEd¤Wgs‘ˆ_ÆlÛh[þõ?EZ þ‚…Ž?ÎI è!§/Ãü!:Ÿ¦~-ÃÜKÂçp0ƒ©Oö9N@'WÜ€&œÄK‡’^÷8l«ÜQ#5·3 d‘-BˆPp×û¬ûø?Õîo?¯Ý?è‡p÷ÂX‰ÒÇX«Kç½TFëäLÛ`6ÎmáG2#/œ#ˆ†Š¡ ›P~CÓ¼õCå4>9Ô¥Ø35Q?_—¿‡M䆣N÷5Uµ±ývà³o'^—-Áÿ¨«lúxÍ$~BCÇ62B0“'ߎÝB9îñ\JIÑ*ØêØ;.ZíÈ9A£ø;Yr”̨Lm,kl)2š2 Ú€5çxÛ{^c]ÿ‡ø„•süäûdÛ\vwâO¸ïý!^^¤öéµþ½ Ç`›s‡J¦(Sa~Ch´…íPï =Žç(“ýÇ&ZHv,ö4ÇÕyýßá#/ÍãìÜîŽèücˆ÷ùy¾ô%^sêGfNÉ"_x`ýsÀ0“ÇoÓrbD„Gdºž@‡©æüØx=ü„=)ÅìûþZè/‚G¼:À=Éz.àH(½™ìO2c$‘iT‚û´BÙƒ5\AÙ…I‚€0Øð¼´v–h"WR#/©%“/á烆ÜBs·@ Û½Ö55M> Å–ÂtbÛò@pÿ‡ÔqÒ4ÿ¾®XwÓò?"¿SU¬McMu5fŒÏËýA“åö}¼0í6N/*±Y1WqeŒ%²ZÁBº»ò†ºëM4{#+ᎬÀüwáåçéШ²ª@3íöÿ'–¬)¦ïF#/øndIF® ƒÇD‰µIuY¼d€ ~å<»Äóˆ-]¯Ó$(˦nü3VŽÒLŸ7ö7êòpNû‹š5H#’lí<Øÿ4ß ;­øv8äU²ºÖ¿Í_ŠèËÁöο†À¶~¥Ru[¡l Ôb¼^þ]/#+ˆÞA(q1D{ZF’%á¦&½ôëXQ çm,[yUj´x¾Œž®O1¨þù˜—:ÄîÎ"¸ø‡QȇÔòîÄ5kh±UU*$êMÑÍVó©FOÚ#íôúfÇœAB`È9NŒÄùì60­xY‰ •õÑÎA-qîYèxqå=ž¯¾yEBïÍ *úŽÏ=¸Ÿj~§žÁ̾C^®¥:†ìCåè«9í7µïùÙÅ$MAü‡Ÿ4Ͷ}ÑÞýQÓ30è n@ 2g°ë#5@Ý>ì’™ü´—ù(Õ¹ê^ÎÍÖ[† ;3v ­CöÙa˜n#/â/CöõhY"ôSÊé!äô™Þð](~Ë$|ÅSñ€83Mb#/*á=ž˜‡qÝ߶1Þ¯©æ'¼1ŒmÆÊÜÐõ•·E` ã»àP€P`9Î|<’¶àˆÊ¼wON‘¼#/p?™>süœJy4dñ¸| #+þ¯ªoÊ}êfgŸpÑþo>òH¡~Xl`±ÖÉ¡îø@ð#/#5çïF·AE‘F\º ²²{11784yî…#ÂV¶¥vϽ(üýwO³5l,?Fî:ýÕã<ƒÛ²ì$EEóÈ`ÿyÕï3þwüèÕ$(8€#/‰pDƒŒ›PâtáÏã7:ò1k×…–¥Ôs$¾,äÉ›ìÙ¤þÐýdDíI%Œ4A¦r™+ÞzMHý9)3‚ ‘mýº|Ü€{& @ÎÂÈnVÙ#g’iLàTúU÷VÄ|%Ûßî_4|Ò{•EðŠ#îýºÓ­¡´38…÷ñ¬—è‡üã9âqI}{Ñœ}³èãÃAT ¤Ù/`£³+üYÓM/B‘o¡@á]sˆ©Ñ¾Éïƒ+6fŒY‰5œÿiYö[wÎ3ÅŸrÓÿo˜äE'[u´UœÎ(mê¹álmþ}Tq_šÛnð+|@aÑ×–.«Ôé·!åÒ6¸éV Sz½ª\A~±×M‡®_GÖ/#/,=©‰±ÐË#+¡Å>üÂa&8…n!Âj„ÈX à’@tW à§ã?Oµâg ØM°~ˆŠ/ìÇõÂÒ“ƤD’)¤&I‘ëkÕÿÙ¾€C°ò‡Á›#+Ç*ùOšÅPÖO8zOMƒë^Ë•5L‡ÈY'Wܤ ézþqó1{ÏCÐÅ:½“ÉÄËÐóøúêÃœÉ>yÀ ‡#+>K´'ˆÖ˜séã0`Çìia„Ö+Ø-!þ\äØÿ©%Dt%úþEä⨇«S0‡8èO=u€N;À¡ŠmU4!#O€r\Ì<#+rÆÒá‰2‹ °ž[qòü½ÿ“ñƒù?oñé°#/BûýáGï&ªŠ¢gø0gŠ#5²KR”xôq1HrÄi)Øô`öµÒ’¿Ç>Mæ7p—þ¬_ºKëA€Ò!$Ûƒ’bÙe£œ–Xi¤ÂÑHÕ,dPSìi1N´ïú·ô£)óYþ1{~iXuœ4pµTE6¹Â7x”ó¹5'6³4^;ÎòãÍŽZHæ¶Ñ¢‚5Gzë¦{ªvï&¶ÅŽ`Ô‡9';¨Ö¹'6¨h*eÖ‚j¡ »n]á©#æŸTa® ì·ØÌlqÌŠÊì×/Ú!ÏúògÝê§Â…CÖÍ3§`ÌI$ü‰ba#+$ÿÛèæ†; óû“Îfd•¬""±#/AD[`¨¢˜) ª¦,#ï tkA_èœ"µ+_ßó~LÏŽ´¿ òÁn~˜Œû‡€qPûa6‚Š~Xý€pY~ÿ×~þ;ßpí—™ù×÷ƒ±?IèFÅNØ÷ñ‚Œlï¿°ïòï⯉î8Âö„ëž¾Xœ&Áüµî³¶ùó¯u–¬nPT“­_³´3šJLŠg@ÔæGŒ…NÉ, 5„èFPœôçýñ>ÿ÷îsr[Ùgßú}¦°ãÝTŠâ ŽÈÆ1þé#+w’õî¤ÙTû#/ ß0û÷ã¾Î™œº©ÍË x«×òãÀ˸»¨Ì0Ocä”#5!}H9áðTM-!ëõïöþMS™àùÄŽ}}d“üÕj>H石ۈÎN¨_Ø/ùÔðB‡#/ž0 È9ùÃáų2á‡íºý؉ö˜Y]—£‰œ¬“#,çÌëzÔ{9ŸoÕƒº™–`x2A‚.?œ»Rüùœ«®Z€°?ZÁ¾VP€* Üãòúà‚¾ÀÃrùGÖ.—wØUC_©P &5Wyô~Ó³Ëë²Oô²ë,³ßÏ—¤ôTÍX5aù$•$ÁŠÏÓ~îo?uQ¿Ö‰@G½;d¥Àv‰|ÜÇ+,\¸þj@ y#5P#/¯§8©«EÀ3iT×-?|kÓæá´<‘ΖÃùfuˆþù¡ÚC2/Ø°¿à†þ²$1‚ð#+K“xKgA[Õþüµø Î‹QóD*tüXÏÏ2‹=*±9ë˽6eÓv6<úó%q}’#/cåè2~ÅÙAHÒq!ßí£sŸ³Õƒ#/>B¬`ÅHF^jˆé#/³³Ð‡ß™@K@ 4sþ|çüéÔYÆIÅ…Œœÿ%÷ý¾]uÜY¥$>ìýgÐ(?Äȧ#5ý½‡®*€«±·;Íì?SÕlÉ$9AÜjÊ8g$öðÓÒ Øó€#/ ŠŒEŠƒØ}@yàñþ.Rc¬X $rN¨7î;#/Mz ¬9Îé>Så(¥ˆ×!÷‹ñWXâœ=¹ÅÖ‘#5 ðþÐþŠŽîø¢l?Iý•lßã®.*^H¿‡Rƒ _¿PxQüu‹Pˆò•Ð:Õ#5/%M4Ã*Âƨ‚/#R¯îj1?žfïLzÀÀª§BŠÁfI¥k‹+l‘R*â|M«ò¿èŒÑH9S¯þ ¬5ÿùGœ˜‘«#+äÇ^ Å+ óïõ£b*™C˜ø)‡þyîÜÈØœÿÃÄÿ«þÎQCÕR¢S¶Z<¢\U›ï9†ªgË–ry¬_#/Oplà€í@Fžx"Ì/Mwa^1‹ÊÝCö$–ÔÓ&AÀdX„ÂÛñ3-; Eý…G÷WøOßéÿmñåýŸÆ×$XƒµçMƽØ7÷;áñgù“ùöd¿R‚Að”P#5÷hààGè/r£–>@ã‘Y”F0÷0ùš‰ìkšUw'ôR Q#+ißrµì³þ ËÍ‹¿pþŠ‚_x\_#/Ûôã›_³³‡^Î:Ì0†‡ñª#/#5§ 0å@AJGëZ”‚á,žˆPB/#/­šœ~TNX¯ž{q-Í—©ÃÖdÖ}!Ó?Oõ[ýóíuíö= ¿ºœñ¿Ê"üéü˞˞œó¢&'ÌÂ%ÊmæE½Ø £8?Èý¡ˆ°Ú¡Œm–ÖÕ²èì)VG6l†á€ï~žó¼ú؆޲æ?ƒôhSœö\Ã>Æ~š#/ô*é‘@ðû ài66• ‘‡ö<ŒˆDn¾üIü7Ê&?e»Îö3FéQc_W¨)6ž‡KÓïdòüÜ,S<ú°bÄÂ7Þ†€ëAD÷i=9ù¢ÃMúÖ÷ÙÙK˜8Ãôe=bÛO`ëηx^>RÉyK¸¹­{0cî˜pî.ÅgÕÚÕ£‚¥‰ðîïéó}GË#/Òm_0n áÿH/ÓxBÂüÓ\½d{Œ&Œ¹I¶¥X?öÜÚ0QF:­JúZ2Ä:ÑÉ1¤§‚‘°œ£ágS²Ê h€•>eŠ]{Ïîš7˜r8éAÁ®T«,£&¨Y'®Œ\;u?åί›^×ä÷=o3ÞÆ‘#/â‹¢9ñƒ‚Äw{HšÁÀAÞwtqsøñTaE”äÃÊ7[ž„‰¡iD¬`ˆ#+|Cà €_paÉ;2}$-|rwS½gc PLé–4/ÿöÿ©¯DïçË£Âõýø¡ÁíI›|CøK„#/ý>vÛɹJïëÊÁB‡üù¿gPÞȈÄl #+¬”kÿîNÌ{8[Œ#Î/˜4r£Ï÷•{¢íE8dñ&4Ši#+fˆº M.DħA"ÑæòðÏÒ5\’Ý#5®Ó¯›+»QI€)¶a :#5kùÊ#5—jm~#+÷kðÑѬiU!íĮ´ýZ"È#¥1Ší¤$±~!–Æe8­ƒ©È‰Žóé?½¡~‚äÅѯOÇPï•À\xC°±úzÇÅ\œÀƸJÈÃc…|äugõ^ïï’QöÎ+!;‰99œ<×ÿè[d³¨¼J Á‚‡Ê耿U§°^öúN“š‘ƒ.EC¥(lvõÉA6ÒÞÈý.®’À@õ¶ýW]ÄN'íQ®‰ÄùðÛõÜH$¡…ïm²ûÝÉ#+x#+ Šmà9?ƒ{¾¿ÐGå„þ]ys¤¤÷#ˆ¸mê ño;÷Ã"‘º:…Ð|‘{•_Ú]‘_§3·„/3xpëªlcªÄ¡¿„¿ZËg.eÞqvØt—‡N_}Ë cÈ>UïhÖåzÉa4‹ºÆÝ\2WDzÞO „.Îôá["M#5FZíµR1ˆðK^¸¥ƒJ—E®É㺅ò#5¬ÚöHo]°;£ráÞÜ8éØl©Ž[Ñsæ°Ž þ«ïƒ©µG‹ ÌÞS"–š ‰ßéXä¡H9^q4äóÀež­udGøP¤0É_ «zßü>Ù£þ¿b÷§òö¯;<ï_Çï¡ßÓSP²Ó.Ó¸ë’³Õ #+ €ŽÔQãŵ D<\Ý|QG|ÔK¡á:m4ßQ Q‰‘ôæx¨±0¢G?Jû1a!DÈ’,+Éw¼ê#/Y@vñ¨(˜é:qçWÁpD#5´wmàD@Ô…„ÓK·”pÿodZ[£ˆóßµðˆ1c’-ØPËÂÏdb÷q!ˆyÀ$™ŸAÐ;Ã^Ot&(r%T€H<Þ9üÎgËÑ!2G*¥¡Ö÷r£ZÁTQS=š„¼Ò:f%¯ÁßÕús8$•Í'JŸÆûó¡?6#/,<ì¢Â}‡À€ ¥ð thÅ&ëé·û'Y´#/çÕìßïýŸÚ¶¹r Úÿï[ÑÁÀÈ ÿ³‚ïû¨Ÿ¦¥@„<Ç÷ý¾‰ÙîáÚpâ{‡¼°?£F'â£åߊ?Pô=ç3öíú;ߘóœf,ýœ?wž'üÎCþCûÿ®|Ò`ýÇ˵ŸDI:1ñÿø/è•ÛQþ¡~…P°Ò?Îpâ´ö”½~À^Æ+ÝÓ¥Ó31#cêꩪZj~1Œ6zBjh¶eúæç‘ÏýG p{5>k[v[nYŠ s7m¡ðöÈq--­ œ#+{é¼Aðg4áQ Û<ù”g¾hš'¸ûO¿üèÿ(Q°Q¡~ì Œ sùuLXHàcMQãT#/Ý{§|øÑפö‹OPzƒ!þ³skO¡¿w>A/5&ªß/r„‡T’èùÙÝ>ˆðk#/nÂvÙ¬ñ00YùvÉ$¼o?4Ç(m°Òå}>gaª¢´†Xñ¯.{äË#+…¢á,áS:#/&4˜`ÉrL=4°Ó†h«ÒÀĪ±†Æi†Ãpr}¦Î¤áñÅÌcKþÜ0}ý’æLÁo£o¸ŽŠªŠß"m\ybz!Ž¬zôO$í]Æ/wkÖ\HWÓØu´ŸOM¢ e·ÁÈèýOE'(™x·(<ö˜NÛvçÆ|%=5ü/õ¯¬úÃù#/ÉÒÁ&_$a…ÆYÄãè×éðº?’›àÕüy0tüX´•å_àæýd†îm›cí¿îßm?.÷áã~Σ©’¯kE¤üÅ‚Z¤âÉ@(vÀ=“Óí|îÅjçNˆšwyÁx‰±bJ`ƒûŸÆ @`Cü¿®¦#5 *’ØZˆ ýw%ˆ‘Fƒü_êÿVÀlK#4“S COú£-q„lDRA±NÊš ‹HÃìõ~Oê{Gâ>•ûOš$øU#5´Ý·aÇ‘üÁê’CÌóœ¤p©UUUoC§Ø<ƒI lˆ‘BkÈ(67:·7ú5€S@­›¸ïŠ Ûþ]˜…Ü ÿWé;‹ÙØl§"ª"¬DG•ówSøf½¦;‡ï ö°RHbX2ÌàdƒC0š͌†r~ x'¢Š—¾„óú¼áÂ$ók]ä`)PˆBBCà|Å-«bZH?%Ÿ;¥÷‰©Î¨éþiý?¾õÑÔ<“Œ‰¹QàîƒÌ#/Dø‰)$ÄKkoÞp’‡c  ¡iø*#/žxC^pÉÑàæ X¨¤îéáãxœC#AYÍèŒì%pì¨×;†O 1èìÑáÑ­‰£8ž@sˆbYûJ¯:™7—eC„‰ø AM ë§=aF˜ûŽŒ¶Žp¸¦œOËÃÅ– ¡ŒØÖõ)nH£x#+íÎ-·w€›xjGÂK]@”Âñ4zÁ‡é, M$^³Ž–ƒr66~“Hhc°l7Ìv0ÀÛÚ‡$÷#/‚0ãkç íqXìÉ#/Ñ…ÌÌzmáÓÁºc ‡Å£“"Í*TE5ï†#/Œ¹gIÖx–#5àtCžI¸ïëÍ>™2™ó\¹G[¶Ëéd‰ç¼DMK©$<\%•Õê5œµü0âšÞAêv}/;[{dÃð”kmÂÐÍL,’gEft©Ô´ §AÓ4ÛAØÎœ×ûÑõaŠ,6,«Uí~Χ"ìÌÝÇ9Enûàï·ÁíÆ=Îô@yßpÂv]z¨[¤Ë»™DÚvFMÃf²‚GMó%™½Û i©xLÅ¡ÈÁæYÎM§Û?¸Ç8»®žKôý:ÏŸdï, KžFÅ>‡·¿$’Z"kþñ’-ß>ÌKt::ÄNG¤\”)Z#5°T.˜rDÁ¼™šJ)Ñš¥%ëÔÌj`0—¼æ‡¡Î3;›À½Êbö4ÄÖi¤HÂ=ÇÕ2=dá¸)>¯Z-¶Ö 4ÄÑPR‘Q1U‘×j¨l+ î0öwt^ÛoƒÂïÀÀk¨kÝóxÖ`ê“*-·%¹™eÌyrÆÚ,Y™na™BL¹äŸštÅಗ 1€ÂI†0ŽÐb$ âÚ]Á1Þ«4”ƒIáµ¼ò»ãÓîû_ ᧥ó–àm‡l#+…ê&lF}—­¿f\ø=úÓP„"êwœµ…iYk¨4´,Å”Êu¢§Z¬:Q}xgLJ5ªŠ"¼gŸg,Pˆ£ŒÁƒ¶zs5®%Ln)ߺæTm†;é«õ%ùO7#/l ¼ŸA»Îƒ49 è˜r–@Ñž©‹—áž}ÇbÃ[ced> ïŒó Î"ÆØ~ª`ßUÊK?»Ÿ$gîî`]¤â5¶ñ­…–ð„dÍù`x¹‘œ‰f‚¨æ3/v çnrÛ@Q"d¹ Û«9ÚáÊÝ25gʽ¨kÀòä#¬<½ÌEÃï#5öƒ‘Ê|JÒ=|Âx8ÔÓUi#/ˆª¶ #/:ðz–SÌ{§6MÖªÀ;z°Ž_ñ#5¦#Û³ÐïDhåÀê·ççêòœa$銛 ÅWðíõ³ç³ŸÃbì˜( °¨I ?ØÛC4žÁÇç _y´y4Óž98&œra]š[AÙÝC>Üg¿/.½ŸOtñ]pMãœöÄʈŽPð)YIg½\_ºñŠ_bþ“³!m]Â`ЙnÄ‘ójÌHïº]ö ã¡ÜJ§7]Ü7ŒD5Ahå™m-Õ÷âL¥¶ÁéSiŒ²kX&FoǦŠ6•}$4r i†½‹ÔEZ^ŶVpvG…`êF_Dš1#/#/½v˜,¶9ŒyŠdxˆq#/¢“Íã7°Ó8|N£A»ÎDiE“¤â@5,rdÈÄåqªºó=QÚoËÙG!æá†Àa :&M³Ç”Ò ƒ!ƒ¦z»”`°£|#|¿¯ìÁ·worîèÚ„Q-FPP1» a b°4ˆ FâQ'a»:³LÍÑXÍL@SŸm zs79øá‘â"#/΂N˜bp<„êC°ËéKªW‚OÀoMÆþ‹X´•.ïåq¹€â#ÒÐkØÎÈ6\e;òÐ)ƒjGZˆ÷#ósÔ8ÑuxÈEæ3ÿ"I ,Ü÷i†xáXMF1„f#/Æhô•ìNã»j€âñjý¥£X Z^ žî«x©CÎ"¢áÚü Ü9àûþ7@û£½$›âo#/!Çâ*‚#/ÊÅVéO·²ðÁ¢õŒ·5¸PïÞS’⃠ý¢’‰¤jw°ôÞ^¬5¢:10¤ÚÊdÌ“3 #+kD…ͧAUV+rŸÚ½µ¶½óز6}³{Iy¼“#/ hƒ™Ðó¢ÚäΙQ!Î=Q¢¡¼‰õZÂÚ»“®ƒëv´"2ûS,¹šzbG j„½MqÊ‘:Æ¨ØƒàœŽ£: ÷Š§3^;£9…B3S1 #+qGXu=çË‚â<ÍÆêq[­|Cuàø|õàvGi˜úÏ-zGò’oCŒØ1–ö< ¸7Ã?Ãê1es4hjŽµõ·×hXŸX¡‚zžðÒó\ ö)F#+G<‡b*Œ=~zƒí#/Ð3MŒ=?ÕÓ¶ “ãRª³Ba&ê¾í£m¸?'o¢ðhùnhó2V|º£),_)ÃÌ`±¡Ž¼|‹ÅÖ’HLæ‘X}£V¶j’M†”™òÚ÷lCF2ÝNþÍ»®÷˾¼H”ƒkôHAíó©}Ž¸U=ÈÃ$¡½ÃA#/ɇøÓ .X9²¬’lRìÌ›'Eõ–0Í`XQüÖnȳïäÏžÇÿõcÛ§Šr½0óÊʼӒ" èG”@¾ôþ©>Ú=y"RaV'coäˆ1Î ›æáSƒN«ÍžRK5#5üéFsॢÎx+ƒÜÕšì‡mמ\1ùí&(âGx©„ÓÉ¡åêÎïtA×ÄÑ#+£ÕÅJ }ʈKŠÞ¨©?+¡DD«ï.»ÅÞM‰Ãf †t4Únîn¯È¦éò²åvç3²)N%,¼±ü¾ðA²Œõ°üUïäß~9Íå/Â"œîå(%Óíøä‰~Ì*ik“Ýç·1ÙhÈ•Ù?q/ê¾æ2œëÚ·Þô»¤’ÇAÈ‘«ùcÞ1%„¤•Ûëä‚̘¹ìý-Þ¦7³Ï—Š]·‰EÞg)®0vMQÁmpý¨€  ’ÊýßŨ'òÛ¦NõÄjãÆ ¢Ú}’ÑÞH^4âÎônñ»y†>æç~ZÏ,<š+„3ì7t‰v¬Šõ)÷ú¹[L;)ã/!ߊå¨Ýæá/Œ4“ÆÃ#/²æo<—À;%£T>½"ç#/m‰Ê<`"ì7òj‡_£–äDtnÀ“%z#+jÏoaãÁ~idÕ"P€R'åJ#5@¿˜Î$¡¢$hXš¿¤LÉ(#/ ªF§¹çèv»ý£AÏ?ƒ0iáòÝ£{ûñwu‹Î9'ÁÝ®1€ ,È-ŽÆ}øŠPYÔº&F%QÉ%Š“d6L¡l99ºut‰ôÝén¡zÉ2¡ùöy'œÂÇ7“ÕurÁŒ_¢OÖB<§¿tíáÁª^Éí¹²q„ ]ÿ{{ {`háÕÓd:²ó !¢”Ó?öÀáC™g¬»ðóÌÄÚDø}ý}™ÿ}•£vÞãÕV:ÖÍqÒ¼k%Ü$ ‚w3aà>+égïËPÇüÍlÇúÈHò‚·¯†#/p&8¸~ÄžQè‚ØÇw‘,c¸WüA@7" ^Q0„Zƒì)à;yô÷q;U<#Îð@àyJj`Ò»»sQ_ªÏôþu•ØÀ–;¿/Ž÷Üñ~?7æ󘜑Hˆÿàû¢3¢ŠÏÐ:+60»Üb-t‰×}tíTðßzÛ&o\,aI ‘m†f`©‡€vn›bá2¯$˜ƒ$xEÌnXhä†Ò ‡çq#+/ÆPì L)=HŠa:5ï:»ü•—ËÍ8AhØÆÓ´nwX•)Ñœ[#+ì…Tᵂ×Wž|ŽöH`LÏ0ÂpäF,ÁBÌ…T`©þh­/I{6?Ÿà‡¸{Jp€œþŽÆëĽ#/ |>w%N¸:¾Òø| \0†8=¦žeÈ:'=øOï{Ù#+QÑIT>™2¥’Jh@ €” Šf™G†bÿ”€1`† •<—¼Ø:à=7/îÈ9¤#ÇÑ­ØüIé¸pRÑ šÓÀøÕb“¡#+ÒVô—,°Y  ©UU?7-#+#5¥mÂK–¡¢¯hÄDU{Ûû–¨à`õ*pÀâ¡èí\ 8{G×غðWÕjWo¦*}šSÕÃíTþŽ=[åKC4aðÒ#î‘÷¿ØWóÿÇD $ÁÅ%'³"hŇ ¢É܇O#/ŸX#+#+û3ÖÍ^²?‹¡Õ¿Ã+Ùr~òl½¿{Øþß$aÿt¼'ãÀëttðMº…óÄ)¸Â„ú ÍÜ~; ¹M…Ñïpþþsj4~4¤"$q&Ÿìguί}£\J{‡·¯žC¨®“>}¢~°€PoéŸOôŸø‡ÇЉÜ})á& x'Ö¦—%e¡<¹›¹lþž©ºÚŠÛ&ÏLp8¿É#/,^G$\É #5#+G€jP½ ~B }PÛÜJ±ä{ÃÞuù#+§¢sšzýT öÑ(Aðj›@LÌ^`C’P$‚Ä)ÄÛAz6çÙ¤Cìí#5Oèúåóœý'h”šïR™ d‹!°^œ4 ‰#+½‹ßwcVعÅ8bbfñ2ævlBM¾°;$·[>Œ+¹ýÛpŠeÃÔ<ãM#›$ѽáCÔ#ƒ0èø£æ#5µµcDM÷焧1òÁÇÑÆçdÚmòä‚íhU¤th!ßXð„»áOÆù‹ì>ŒBÉŸ”0ô Á˜8Œ¦"À€h¦LBÀèõIËêóú> I±¶ØÌ@ü5(Ÿ–Ô‡D„‹çÖzÄ\œ(­g\'Çï¬Í‚Ö`XÎDÑÀƒ…F¦ ®“¬~|QŠƒŸ¿Ìœ‰)ZÓˆ‹Dæ$=¿À ¥,(2ÏÈ¡öù¦ ¡¤>@²!êQÅЫùÿ£NOÌ?Y˜›¹n§± 1)Hœm€ÊØ5­ a›õŸ% bu÷àÖM‚bÕª¨?p$çVLf¡lŒñZ†ÌÍ}¬ê–(œ"¯Iòî/˜/Žïéîíõù9BaLÔŽï Ã-4L¶éÌÔQ!v:oãyóøT#çßnÝó¯GΪ÷ÈÑ$ª!)Þ:k¢õ»¶‹)‹¥d(œõ$¨i#+Hö&3®— êéçmt[tú¼AÒÊz¯ç Eÿ †°ˆÅ5o™ÉU@šQ#5 0À³Hd#/4£ž¢·ÄÚÏwJ•oðT®¤ÜÏg ø½aÛnœð#+Ó£¨ThV€ZHànnyÕ#Á_Q}Ü°ÿ¡ÍÈ‚ šD¹p5ú5Aë#m‡4Òô9ÁAò=]ÂûÁTû×ñÂêÎe¬B=|ƒ|¡ !ÆÂfrÚ§6~ÁÊmaÂÖ|:N¿DƒÓ0óPå›XfGI`p|k±–òÏq˜gSÜLGá¹aÁ³¯ÉùA. #RâÑ‚ô“br"8MÛ“ QÑ@ïÛÅzTu1úwÅqìðe¢MzæU}þÓ(ý_FrÆN¯#+à৲æ±!L*€~‡—¨Ò<–_AÌíÍ<õ#/߃èúG€FUí¡¾ÝéIùâ›ýä6ó×Òø\ünÐ|}»Ð5ÛË7h瘃1%$Òn0Ûø¾{bJ~ou¿d>ÈzÐùYBJ¢ûÿ8Y1ëø?v¦*º‹C'ÝžØé“5‚¨#/GÆ$#5ìv:Àž¤ö:«Ã<0ׯãÞU3ˆNøÒªÈÏmÕ>g÷ýðp¬¨½˜ü˜¬:/5Pü U&’‘‰'ʇ´÷;õäùà¬l`¡­Ÿ’¢Ð›^œáÂ>%¥pÕ²oH;yò@ý¿Ùp×ø'GRÃ,ìYs0m&?>Ñw0ÑÍ7ü\œ\…k‰,:3ð!s«U#5âïÆ ö‡êAiD{‡î+ôØÑA’R5Y…†FK˜™Ï#màχOÒz>wõŒäzx¿£É4h¬0Ë< Ú«ÖŸ›ž—øåïàË¿é3ð>³G]Á9#/Á舾ù#5 ¹?#5i(‚ˆ H ®¤Iyô!#+Pò0Œœ#+ûÍñÊ›€­ˆB÷5QRÁ$é6c­6‰ÊnÛ¦sÝŠÚáĨˆÑÎ`ÍõõõÑj}ÔßÚɃט@.N¨Eª!U_{[ aŠ!œ:ÄÅ׉€cTN§ÇNs_9?0&<]`,‡Z6ÑóÎ9—N™ˆ—AO’ïcåû£æø¸s™ÀlMƒÀ¤5W–U Mà6?• °Æ™©ÂÍîX:T’NšviÖ¬<õüž÷Ô-ýØXÓ†b+yŒ•e¹ e5ƒ&X¨±6h¶ç1#5„ý{Ûß–˜—U#ø˜&Äù$E¶©‘#)Ka_šƒ;ë#/¡ø7 Œ± (B„H†Š(¡¤Z (i‰)#5 i(¨¢f‰…#5@•#5bª)–J¥ˆ‰ ’’%˜%(’V%"!(X¥)H"€Xb‚Vf”F B†JhZZ#5’€IB;~³ñð?ƒÄõdýœl{K\JÛÌ1gË!þǤõIákÀ÷ˆŸ2úeJ¨•fR€fD `¢¢”@=X' ù ÷Êå° ÓŽ&´¸ Ðê}R`nóñŠ¤4&`¾ƒm?ºùaŸëÉý>6ºäÓ–ù;áðÏDI‚Üi»5¤! }@¦‡Ÿ³œôAäŠwY—Ôã¸Ä-5ï#+a%ˆŠ*B€4ט´Öq¢…:!ÿ-t¡¬CæÆz˜â֑ס¥¢½pÐûB?ÄHÜ‚ÑSð7¯í21g€cX÷éã´ t¶w!:'âÄ*~Që_o[®$/áÍÉ5xp§/œq7¡É¢ØŠLq•“áôéÓ‚¨Û܇çhŒsŽlÎŽäæÚLæ1¶Ë®Ãn¶ÒáÙàà˜ðR$5]©iX'N ¸•nÒ3eÖ`(ÐO¦2H¯Â¿9å§Î÷i9?9èÓ®#/š™¶7G¥×1Øä­¹—#D2ï—&ž² ®®-4«ª²­Ý#/ç)rTС·XÓ#/WVÐ#5PÐ¥ÅÝq´ Ø“`µÆJò7·80§rê¤D£KHzy²t#/˜oyT/’^eÅØ#+nm! y±ªˆ]Wˆ•‘¶˜K± úN½wÕ1"´_wÎ3èÀΠÚœæfH%(2#5½¢½P‚öuøÄw½\Ár4Š êãO]€š†J;÷ë#5üSɤÁô€Ä#/ ’)$U#+|††–‚ˆ)ÒéMh&B €¨‚…™ª "#+)ªZØÓ%#/òðÿ"#×ÊŽÆ@~ÉÔ»qPô÷P·\™151R”ÒJÄÑA%T… DÑJ,B‰º€|#+9¢ø+×U®žÙ^±îß„?ÔáÕ–"X98Ÿ®àpOÓŸÚ@ÿÒJSÑ”ùú ¥.üåƒZs$(d>†¡wâß÷~ƒˆ¾ðOaÄݱL“>TÅ#/ÜQ#53QCõ±sNLH„Ýe÷DjÒLbéÒ+``pÁ}nÿOð@ý4DNe>Æ#+cITÅö|𻚾Š¤fòåŸcŠ»#/(c5'`ÛèÃUvÖ©¥Œ’& ©`µŽ°ÌŒs9AÒCíëÃé…OK-{@8l~;_ŒQl”|wèQ7ÅÑ`¬Ù™~]t.,†KsÞîåÈ%Mƒj$ƒ~Ý''ç<é?КbÕP„KÃB€ßßì„9´qÏøáU_a#+ïV(/£bÆ;FcŸ5Th;L›dkù—ìRõÂÙîÄÔä¹{%x¿Þýž!ìôg¶3¡ãý õòÏŽgíÁ#+©ÜÄ©q„êÈ°$=ª¬å§#/Ãút'¨û÷£¿r|¶[Cؘ„^=’Y|Oç(ê=ü€…Œ#/ƒ‚4«A ïêݯ^3‹°ÐOïϤz?‰m<ú•ýŽáø2ú­gSÊ ÏÊh#5fþïç+Ü&0Ú–ØV†6Â-ÛtÌüñÄX£ûш‚S©õMø¾æ'Óí_îÝ~§¼óc¹>*—ätû0ù€“¶¿ÄÄ9@øRo(©F¡_ÞƒZÁy Öyü÷‰ídÃ$RxCå©Ï]÷ÂÁ#\uÉöçÔÂ=‰ä.~{¸Ol´Jà»b»¾!çÃO¤=¡‚[‘þG6x#/å¿{·ˆ£Äôé¤ÆÚÄ| tè%æ%vA­?s£Ì¡"ò÷Õæòä˜ÑsLëÇÏ‚BC[1ô<¡¨„=ÇÕNžs%P`êsãíÄ;™°Ž±…¶ŽÇl½}ùð×k6älÈnš^85‡>'Ns͇ùj›‘>C‰$‡ùÐ;=cê ˆ/jùœ¹¡@Ýö#/9P†çˆqOäçž#5áêLD4K%^”ƒDŸ—…±êMJ)•Õj`ÉEk'×t¶a|h¨ìšgLg(¨v«h!½öÕß[‘ÐïKÏåw«ˆ-ëišš{°÷žn3¦Æ7Ö<þÇt”eŽ)cþÈS:2”}7*oR¸¸º†‹ŸUÑbÉήÆ(šèÂ>q" 鳇 ìö c˜ºÔ¾Œèªq”ÕçáDQ@‘ ýP£P“1P%+JÅ)(žËß'°%PÙCœâ{Iààó¤Avc©*Ä?­>XC4Íov[H0XŒ×÷^%Í(͓Ò`ÕÄÅIKµà&cƒ`QÀ—‡Çö¸QÜvÀpÿ[†´ /Ò¿?R§:‡l} l#â×®¶È¡¸Doj8Ï©#Î/6$+â:F€=C£F S÷«d&bBJH|©“JøÙ]ý¹/lP-(Àã_~O˜r¦`šq½@K(Q`ϧ8$œÂƒ¶€7© ±*(ˆ© ÉÁG¤@úÁ˜˜gĦ–“¤.KAÁ@YG×(êGB‘#5•ÓDÔsb¢(û$äJ€ëŸ"(ßÔY¦ì$ütMçÍ÷õëá~Ž¿­îýz±•vüÔ›ÔɃsªßY]*þûšp`DëiH9}NšŠ'™ß©¦};“Ÿä>’ü¢`{¸îìBƒ‘ ö¯wñl–ÍÔ?2'YÀr6^â;³&;®yåÓ#/ÌbT@ŒÜ¦–û¡ÛÍÓŪe¤}Ìe4ü#/ßoÖÃјהƒS‚^éìœÇ׳‡=;ádžÐyª`¨a© ¿fÓ­†Ù,ä^\µ|!èx‡i[i!GQE• ÅQ¸sr\›3”9hm\†Š· u—v;Dg6ĽúߺóŠ1··¦ÜV}J°ø`™˜®FÕŒäÍyYa£U×BL&J.ºËRùEI¸IÄ&ÅVß‹Mñ‡îã'Ýw9“Â,ÎGG—8¡kâ˜a“åe¾~œ_{ÚêÎFÊ-iQ$±¤\|³ÔúO#/l9NoÌo×ÁºˆZÞzØ€ÒnOµ›7&ÈÌX·œ²#žýy©ÞyàùÖC™6Ys˜éí£“Þ[&÷Û=Òäß;HiµpíFYK {WÊ÷n5Û4ËãŠÈ¨Þeäƒ{1s³µF)ÝÖ?ØÍjí°µB5çGëá¹\R×ðñ¹ð:1=¾‚I½­kâêkõè‘pzCïÇšïýcN¯%üf¶Ç}õYY“µ•Ìq ÀØÅŸèN¤z°Û–1ýÞœØüý#+ÜÜ~XžÛØUB§I›ð0ÎýO¾™Øé„oñ> Ýϧ¾>WZÈ ñö³ÍÞ}•Ü6"=OÞ„âÑËL`g€}<¤>4©¡ï"Ñ’)â” 0ví/t¤7f#/“MJ”H¼ò=]…ß=«ìóÿ¡éå³É¾4S>¼Z#5æ¾bF¾#åa[P‡1câêÀT#ŒÀ}Áòþ­¦³°H‡™ÚZ’¢‘-̉`› j~ÌcPŒQ#êµlö*"sÀ»¾] kÙˆ¡Â$BǶè)§i¿ß f'RªŽ¬î6ÏÂp£^š£Ì4©Ý$ITÓÄŽàÓâ¥á!´ˆwF¡Ô¼"’¨R’)K”éã£dX^gåŒi¿£¯¦âú÷÷%éºuàž<]:~L»Üáá'””ëÀÁˆ_OrœÖÛj© ¦B$h~÷õøxÓ"¯O¡>ž§h}<Ó;Åúµ;¤­3òî]ÙSE,g,ƒ_9"U¨hó5ƒf™¯¾íïêK-sb‰š4oÏ ˆs#/w5çI!"÷Ë~™‘%Ë4çšýÍTÃp&H>þ?ºÿ?ÆfšH" ¦‘áŸ\>Ž´;)!~  Ô”ø0óÅ.ƒ–°è,9Þç6¤3éDî;P[<™#5iíÅcM+L–•ŒÛ¼M̆,s*ÛAb¶Ä\Á¹bY”¸ÎG’ˆtfˆmŒ#/¦\cyªî2QÛ„cåw‰ ¹ñƒÌ š%#5QŠ¨Èh!ŠJ™bÄi`­ƒ²jÐ)0¥Ÿ6¹/63ŽmÌðãæ#/MA¡{:y$hÅ8¢h#/ç8¤ç¯ BäÏN._ÌýO³¯± x¾Ë‡Ûª5¼#“‚{û@Ú8ÛlÄm-´ $LL„)"7<„¹EG#‹µÓ±U‘Û8™-4äî#/ëéB”´@Ð!ÐÈHÐLÅB)ð++¡FÉGƒ×³]o¹õp…;Wøƒ±†ìh$#+ó#+b<$%³»™¤°Tè‡#/,55?ÕðcCSR{~цl( ã2#Naf-EŽªR%[c˜« ´ªÇ#5Ú®M<Ö‹¤¥2:ÇÊÉU‰å%Ë—$ºéÅ •P´v CUÜ`@4Ñ@ÀÈ„†HÐÏ8‚†)ˆED8·ÍêO·8v¡Jt—±ë€~U˜#+v;¡J6ìõë;ú²¨ÊÒFñä¹@nèÔ}Hö^Ì hÙFÖR¦Ëke1Õ•’¤¡RÚQ¨ŠÜ/³›ÀÔÔÖù“MQE½ÌÐÖ¾máUGD8󣧌¢¸PgÓ PHKC‘Æäß~³Ò*H^#+ð 9}#+ O6 ãªéÂkËp(¤‚Œ>æ>5ÑÜ=p•C¯¤ê}8õpòáÔ>NPŸò¤ô ïK!'¤îÄÆú@^œ³#5@A¥TÇ'#5‡@ÖŒbÔPh ‚1äÇ#5Äì H8“'4cpΉ DÙxpš™.§„rBŒ‘f^8ˆ/,HPD`Q¯•œÓ\¸[xËnÕó[I6€îb_™ýg#+½ÅÊ"A¢…/Á{»=†…s“éÎÙѳcÈbCD) ÔL`Ä›äóÛµrSl”„‡2ãª$ "ˆÅ5iÄ0X‚3yHÊ›AZÁ—ƒ`Ë7¤þúLQ߆ðo&ˆÆ)™KAÆM|œ–¨\4¼$ÃŽSÏ ÜD~˜9KCØŽ¦q¬ÔýæV†M²L£èÔLã¹Q4àlqÌÇy1^•·8òã .NPMª –ƒL?‚Až ’;m…+ûn7œsUÓÌÄù¾Ã¥#5U)@´”± ÐÀ£ Iã›:ó˜ÕD(8ƒÆHá÷ßåKfZÑ:\**"•rß´‰~(—R|ÃÞ5åÉæ3š3QI,{HéQãM4ê ‡©iß…Öëk2·†Æ#£,vŒu¡"É™ú¿mBˆºEvŇÛîô<<‰é>îDÞKLCŽz™äÐ|ÅßuCð_ý€{aÁ˜À|Âë-#/Óa#+D›'ƹ–éu ¥‹,ŽôP‰2˜ˆc}mÅ¥»ã•˜$DÜ$œê‹¶ŸÝR\¾F#/‡Ö~K~°ÑÂ÷·T¡#+ùº²ù×ëªHF)íB„ü~Ä%¡LÊ…®¬ì#/¾|ÚRñ*ívŒ=ú݆ÎØu«"H+Y4ªtjg7‚Å lL‰¬Bbáö¸:^ˆf¥_Œ s–øíb ÆÁwç'’(}rœ˜aÎ#ºªCøu èÍ%hK"˜#Ü!“ßZfÕUÔö·r7Òä8f©"†À2bŠú1yˆòj$Æ‘)HM%{^ž‡e¨àViŒ Cg:‹V(cCèÁtfo2†¡ôPÍ`‹D±q#/ŽÄ#ü¿ÙȇBP&>#/1ó ˜Ð`(A ÏÌÔABn ¸·2¦A4‡Ô(c¢bOÎM²vF£Ñ„®ˆê0î‘€“Åú2o´4ÏF—dÇ>ðÝ»‹»DFD†ÃH[²ò*3F)?äêçÙÏeºrq4óÐó0WÔäÒ%Ì  ¸0÷zúã_7RˆÍ#+=U‚É;ê€@yÕl@D-3 U(D|àzŽ?¤öݽ¹Å9AèÁªŠ¶(‹U@èž±’&AcDO—îª#/o‚W¶iÆðâŽ#/š†&Y#qÖI.æŽ#/1j}³™ó' àxై±{{ç†Â’®aª‚Ø> äTÁ,Ð#/|04¨v´´Pa¢‰ŸƒS«KoŽ´#/0gŽ4Ns‡³ÃAs›;×±°¨bêÒ }ÝßE†u –5d´Aàép#/MãÜ )RçBM›–:tŠq{zŽ#¼ªä¤K.¹{³Ïct_é,¢F åÎ60ð¸¡RNÌ›±Tš÷×bþ}ŽìߧÝ´¶D뉠$;ä~ÆT4s3N"ëŽð:”Û²\îÃçÔúà“í=‚Jœ¥uVãÂzIí‡ýðmÚ?œ{h!îÂ)àýF€ƒÑñ kp›!¯$¥ƒ»·ë7:AÜ}!áùßÃ}â?©%v=­­ÙWºÕ©I*µªÈÄí~³ŽüNóƒÉJc˜í“È{²RH@r#/PК*Š4¡HýÒ™Xˆ"%  •Žg¼aù76Ù‰úvO!Ã:4U4­3Ü“`é±È.¶$’ ºzàø2ž(øÄA‹ ä#51àb c$E 㪷tƒÂP”{}š#/È9€ÌuYA@U'¬¹""("#5š" iƒ8ƒªJr•S¡ýC¾~Έà\ÖèŒ`4y´;Ü)Ù#5DÜÈ ¥‚s4PAC¼:9žïKÏ^KŒ#/'"œÄ%·áq3ÕqÇç ÄÝ!hÆLPbdu×D3#5'T!ƒ€>`‡bxÀùà©ÃŽîAÕöWLI…â¾DÕ]"î8HßvyÌb~’#:7@)Ñ!´6oÕ¡¤67Léà‹ì0M“éÔ#+äfÐð‰ÞµŽmÈØ+žag›„$©  LÊæ9ð†f “Y-Gä÷ï0Š?&I. XLUNÔ¤ñÙÃႧyHí¯ Ž³÷õ8lgËd<ƒPbl{Y1‹0#M1öÑ‚i'˜Ò®@¨]ÈÜp¨„/¡’¡ƒ3ý#/Û@P}žÏ"ïçPÇ P÷#5…n jŒgâŒ{¬þmðöÍ¡.ínœw¹ò9QV!¶¡v#5ĪƔkš̃ Ë# ÚáÊà<)Za„ü°{f²eëÆX-u‹6ÚìÛ'ý¾#+¯#/NÉ®#5lo‘A%ê¢kÝÞ ~~I¢&LÚÌ!ÇR',O¼ypìb#5<Š¶ƒZî©hÆ+¤(#5–f‹_f÷-†I66ž[8ÍjÝ&Ù®ˆYk´ÐŠ‡’‰+ýÄĘpûÜ4(enaDfIAlxÄN`¸.Œõø±gö!P!² '#+Õ&ág"ô‚j3ƒ&¨j^hl=µ¦&™›t68ÀƹŠ êŽR¦¤ä›Ï"AØ—•°'§¼1¼S±B±í÷Ç#¦¸6Òå7HÒ:áxàÔaÐÉZNhj@äLÛ2#/‚mA#5`[4-¤ÝÂéfD`1‚…î–^£¥ÌiEyˆFÓøý’u.Æu|sΪêLqÅ–‡ãy”‹”ƒ/‡Bg­))SÔ}ÛOlj¶Î/l9y«$FûÔŽ!–#5^Ò„õôw }÷m&mŠr­¹c©£‡;µÝ&ºBczàÛOóABË*0—{î/NÎ/Ö0 Ý57ËZlA3ˆ°QÇ¢² ‡ìøý™‚¥ ÍÀŽZ«žG#70‰:2¬7›i~"!˜Êk-„L-ŸPäÖÛDMã{÷:7#+6 Õ[¥²Å(µ3 ™’è(£XÌ3 èÆh††%à;[Ä„2Â_cáŠ&Ã)@ÿ9Ëàp^_A#/ìĺ°Fu&˜$nK𜟠ïÌŽ†=fðŽ„÷„øœÎû8 ®Ð+¶“þ,uH^ãÄ1$ñaëI6×þ;á©#²BÙâHp¤õíQ,‰®êq‹!.P&X`ˆ$‚„ÅøJaC=I®Â²h˜£Ìåo*cîÈøÄ:#5 r•cVïè¹à`Á"v°¢AÓ'ݾfXf §aL…M ‚b¡È©f¯8sc›H|ß#ÓO¨ëÅöv‡ËØ-†$$€E``’rûL19IÛ!¥]Ì,1I€ÀÛÄÐv{~¯±»Ò7v¨{r×m±)¼•AíÓWY„Š3cU†²W©§0crEq9õóÍu“‡7GÜ‰’‚"/®¦éí Æ»G¶†‚|YŽ¶Ú[aX‚a@jZF?‹EÞÍ9¹£Ú7Ä=àÐy+Ø8Rˆ`LÖI6d#*ì†?-¿5N©»«åêþ+wªá¥(kÓÏË¿Äö"#lŽU÷ÿmól!3 ±ZÍoÂ^í Ù¥¹ŽJ´Ë›¦ MRž¥ö;¼íN[ÂH‘kPB>}4ÃÝþVqJ¸?9©ÏF ÑJ%^óâ»e•;m£P,C®Ðñq4®NÙòÃJ Ú#/—ùkKã« ªKm›d+N½…©·CRg?NÞɳʨúŸ™ý«Ä›¦kîþsŽîa‡ƒ¦ áÏëºcnÒÛÞs'уÄE&Í-/ò>¶WÙ ³˜Ëì­CÌDÔ† or±]°j-›Lñ5sMD ÎV©C)¿9ŒÙ #5D™ÞKlå«p•òX=œf(I 7|ó0=¾NŒ#+Ê¥ Z>¶aà¥d2.#ðÌýàP™œ£Ãc•Ø §ñS,6¹BÂ#5LÄÁC¶Ô°°p%—ïÉö=ŽÊÆÈ|¼/J½ÃÚ<š6ÀŒíhG 4²V–D–ì‹>ìMô5.iGi"‰øY@à‡ôý™BB0ð>*^#žÎ=3çøzqàüzxžîæf<ûýÝjNu"Œz‘øãòï&ä¡û4öÎã7òL+òz¼§¨õ &JŒíd‚È}Œ·¿åü•ãŒ|JóQ}!E+®kDz¥§ÄÊô+ÃŽÊ~øméþ¨¦:àþš?×k#5Çžƒokkÿ,C7÷¿xly¢“~ #+â4,²‹#+==`éË<^[bgo’(Fž…é%u¬Œo½“Ìuíg¹GkÊFƒ¶õÉJ“|2+Åá×½æô²ªÂLÐóhˆÕZv³S÷ssA†8GY^ìwªôæ/#5ŽsßÀ»¢_7kIH¦»T–Ìb”ÑÈà>˜Tèv“ŸoyTyêôœÎíøïÌFÏñÐühUÌÁUI$aµ!N"—/^ó„5ØȽ„!ð„ìiÜtƒ¹$O^v £ŸVt¾[çhñŸãl¼÷Å8/sÙ¼¢€"Ad+ˆO®µÿ);½+ëéÐÓëÖ¹€ú¾4ù}ÖKgámc¯þÕã¸Z¾ßÕ>æsQ¶¥âãû/Û>ñÙ!¼‹£lèàã{°åUdÛ8Æ,|E9,ôÎI§R,âÔ?À‹¥4ïÏ°ºü Þ·ÅbwÜŒòc1‚\̽•C†¬Ói>¦1Cãb¨0[¸VÆEÖØÇZkîàæ÷o0ŸN“©iRŸ7+y«s´ßmq9˜q+r˜Íäo¡sY18֌ڹ#µ--OX0ÔØwŠ&$Ûh)³;#5 N_S(&CdÑ.4ÏVU¢•¥½KNÏÅ_ïƒ7–¡àwL)ÖÄáûÉ#/UwrÏÝѶ¢tk[Þ0×¹¶‚P4ˆ<ç‡ÚŒ½Jgbø0v]š$„:N¢«µ;Hp“cUÙKð™(êvpä”á×yë÷ÒÅ„¬¦©Ä[‹½¨“¤==ËÁšiÊÉ7ŠÚb…ŠAÎó0ËU.8v—Kcµ´çZNMçŠÓ¿tèŒ'â]khBI«M—ÝJØs­(q©ª£M´ÅDŠÛV #/—.D63f$wÓFÉkFÉ£žë“b#„(>s6våß4Ÿ#5ãÒÆV–oz5 úØüø†š]û@­­ÂEãr÷l¶‡_8ö;ršÃ\ÂcQQKQÆk-›ï´hnvÎ/€Ìb{#5E|ðá·d€uo…‰1 l3‚Ô6ðA¾Æ‡#5¤ìé­cT¶&)Ý[8ˆs»±ÛiÛs¼Õöº…¢cpx&“íœ5_Hp5³¼Ä¢P]\a#‘3ãAh22Mîè’¦0ïÅ<1A¥’Ž.Œ­#+ï%‚WåÝí„X÷¾WlcM•;…#5#5 vy΃02‡dDQ”ØDÆ0ì±6^ëSÉøáÎó ˜ÊׇjFéëÅ>¶ÈÌlºn†@Ú§Eƒ³Rfîø™±¢6'ƒ7D”]æ¶gaTv;k¶kˆêÄ<¸RfÖÏLeÊÄõ,Ö¯À£v~]ЈwmF!f³„ëïÅÍlñ)Ê•N>60\Üáî­©&ŽðÙÉŠÒ—Ãp\>Ð:®‘•†ÃÔæ-Ÿˆå²&Û«l^|#/ö☤2ªç£#áˆýïGX* 6ÆØ’/Zå+:Bþì–ebÂRQéùß^ÂÍ–*\áÎ.ýs¼ò¯K]œ¦Ç«³cÅlü¥.ÏÞ&9Õèn°½m8‘Úˆm»=M)†j#.½S·^½ Ù®»ºnùm©Û#5mÎýTÐõÆÙ†o]ÆêsA“&ϧEæoÄqM4ÓF€vž|LÂ4%Ð`ŽÝà eãƒ:f/F0k}ùIAfúÙÕHqãŠå#e|¼ÒÛPÝÅ”9K”G¤åC¿'¡8î×m¾ñÉ×xº•àNÛðíDE‹Mæ¶âéŠdá™/ç#/ª^ºšïˆI{-pºÕ;hÒÉÛ|ú9±† ‡™t#/m~6’RuÌW8¸Û»0r„™´‚Xui„2â0Áu‘ÛÍÃ¥êÓ®¡¦hÁ®¸ˆ‡ÞËŽ’(©šWF¥H&7¦j¥!¤Ii“‰2œn§zqåØÂD¬röŽÒÅ`Ør(×g9+$Bm›§aØžYaZÎz+–“Í-Wš~fÑX nsYæô¥÷ÀæîLm’Æ#„Ì`#‚Ž;]Š…¦vñRÒK<¸$#+òÃV›ì §w78ËÆÅk$m¼'+íÛ~8ºÐŽPß…²‘y[E 8c{9³ ´Ï&¹±ti¡v¼5Ósz~™„±Š>Ö3z»à˜w8i ×Z8¤íCd䣅`šœîÊD”ñ6É`Mú{ëœ~÷‘.¼ËÊJøºmc8¼2¹05÷ÉpûÉk85³á$¥ÇZM»;#/§¹Å>¦!ŸmÛ| mC›KºÅ™ùž8 ¥ÜÚÜwˆn2ð$Cã¼FP$6\Ħ~5vÜïæÂïa¬·  |uªGtÃqÓl1ÙË—Çm¡µÕÁËã}”®Ç Ò3L0HØY‹8bÉ1Ã퇿uyñÈá8)Ú9ÝÊm*h±ø’l¤íeÓ`Ãf”Aœ³ZºµU\0ñ¼åðð;µ˜ñ¶¸“®ÌZÜ·BnˆI·môû,’žtCãŒK±š¡™GvÎwr(9ã-a»c¤AíÉ®„÷½˜)ÇÆYÖ&(3ª$KeZFf‰ÛÆœœªFQf¢UóXž 0ÈïžvM|;'3á…0í€Ù:.›m8¥Û6¸†¤“¹CLjÍÊ1’b7ç„Þ!©3 ˜Ï[`R2Öq#:Rær3;q Ù›Lšåî]Ò†{÷ÝèY¬“¿2S°ØáÆ!1¸›²}L0l…¾å›Àé‹NˆáöD°#eh›VÛN¸£­¯”2aaØ„!„ÉN†¶Ï`z¨QÇfŽ#/<ÉÚ¨Ôësp 7CvMËêV#¿z:rBDÆ–´¾S!;»2”ùC :1³qvâßBêžö“ >àèÌQɆۇŒâ¼&´F‡ÝJ!¾9Ýcn,Æ6Ú©q·FÇ\>¦-íã"ª4· 2BZEvNƇ璺¾”^M­Éê\#"2‡³ ÷a°´Œ#5UÂ8€Ô;·H¾ÆÄÑŽðÙLu’6(+¨!¦n "œ6v;Ýä<¬Lu¡ïŠg&Ÿ8‰tHî:´©¡t·1CŠ÷†ócïªã rš½1-Ü:ùú]œ ÑÖx½eÑ«„se3NmC› jÈX„še50b¤ ¹@l$¨åÛ瘜t¨áñÙôaãc7µø<¸Û £e1UtyöÀxؽ6ÒPióròùNwF»9ÝJ:\w·×=ðK}\žd¹nDɧMâŽî\@ç¦8äê{4f§[;ww4Ž!¤ÓH§ièÖ‘Œ †µÔÝŸfÀþFAÃÆ‹Õ€é˜Ò[0ˆ½BMIÊ“¹Ùᬀ =<À’KÇ[ìðq‡^3åé„”ð#/ë=“R:£€žÇcNÁ¬ÖP[ªÍ BP 2 a4, ,@=H©¤`ðœC&éRñ‡A“e¾Ʋá[‰Äîiù< ¦ë®øí¾ž“Êœ<¤§ˆÑSŒmêX©©Âʤû;ƶ¦Íµh|ID`PžYR…tDìðúóx3F]m©ãŠÅk&î«cm˜¡j³iÑOØ|É(‡V'——•¡V)ˤìÖ‹]Ñ1‡ÜÇZ¨'»ç¬ÁÎ5M¬ÆHrÝË­“Åð¥(¾Å@.Δ¥MžIÁ´ã ÝÙÓ q=C]ë¼s/…ÄmU œ.͘ÂÓŽÉ!q8Ϧi½y¾y;íÄ·¤ÝuT¤dv‰QvdºŒ5¤2:ÇZ|ñŒÃpž¯s÷Yœýu¨vÜç[r¦%cˆPÑ·D¶ݾúzdZ>’¸PÇʪ "Þ­ðÛY*a('J’ÞYÔëàlöÔT± „ÓªHÈlÑ¡bCHõƒÐŽëØ>!dH ë0·«©O£Ódó÷¿|ïN­ §Úƒ®`ï¶â{z_LŠ;¸ëX5`Ã1\¾™#5‡¸mé)"’Y6{—óyTæ+.*fwÕf§ ÷©*¸[¾½Ÿ^Ó;jÜíL˜ºÎlâðs ØšI0…”úr¬5\I3‰cá[œ•9•+©v¤A›q#ÄìÙÆS­÷}Šg¶Û1XÓÛhnÌ„†•ƒ‰k}xwÞ³þî\mÛŸ÷¤ˆc(v~’Jv–o 2÷i%ÌÞi™=VÛêÒë’|-ÏŒ¿Q§Ê£Oˆ58 -KîŒåÎ1åÜÛÜŠ¨N¾‰iö¡u­5g[ÄWå¶tºj²#+8ª„ÐGh»>P 5s64=QöÙ<é—+‘ µ!&Ð>õ!±@vcïRP›KVÕ³¡HͤÆ›>Dësƒs9„FNJ‚#£¦›m€Ú4pȶck¢ z\ Á´Ý‰6Ü­CJÞ\'†Ë„Øù›n˜w~4‡ON8Ý@¹$“dÃCZahüJho²^]X;‹Ë ˜^‚è¼£‡yl¿oÙdØO-I«'Ýâ–$82X 'Šœ6’_J…"É;ÚdÎy-´&Él–8áÂɇŠ]ø] ¦¨)#/Y8©“!Ï#/0ìÑ^œí fžË^»oalÍT 0à £#/تم³(Ä…¦=ðœ‘ø比ùòÞ¹¼Ä:(6U&¥ïe2ÐÃÍÂDe³KB9¦®˜¨ õh))E]*Ó‹%¤[MFØm¥½ê^i@Õ“‹5)ŘHhÕ{{²5Xiaá¬dï#/të&èù+C¦=`åŽyÕKì&‘“E‚À×’Ryc¶¤"«3ȘÇm»”¦ïNÓœQ¨¶tó/ e]gE>aŠ„`0°ñMÃK™Õ‡Âh6Ãoxñ^úÖe¤€Xc^Z8ÙÊuåâ]ÒjHƒ.Ž6QcQQ³¼íVúDÙX©:ó›Ï±ó™0Dé›ÉZߘ}7#+¿«Ç­ñê7,&ëqÊê¡©Šœy#5jjhÓË]«¥˜ãXái ‚%ùhÓ.v}2v1é‡jÛŠAª¡ A>xα5‹@¯ˆ¦®Š£Ò{6…½B†Mç233:ÁÆüj‘¦œk‰r•záœqlÚ`аïj9ŒìÔ¶aT›D™Ð8mÃ* Á#³ÎšNq±c+»|ÏQq<˜Íœ Ï!©<ÄnÌv câ!uŠHˆ¬¤‰§E –—½ÏóØûÎÐmAGý@ÑfÈg%1ˆUw9x'.{Õr–ÝÆg˜s”c8Cë‚‚ìt8 MQ BFq†ck¸K•M)vHä=´ß(Ø®€Á´ƒ†ƒ¦oyÖÚ-c›b=óqÈTÜCÚ¶¨ªb#5SïÄö]+ÂN“˜` ˆÎf¨Ÿ$Ïè ¯Í‘ÑèºÝŒK#+D¥‘4@‡ùES™¥`LDá!H;ŸÕYªT" f?ØSÈ;~;#Kð°¡ÐG¸haæsãÃåÞî4 Œîû"„ é0çç™V'\¡‰‰Û=‡Û¹S°€ Á?Ó÷ø×¥G·èêËæ;‹ë$#+ b‘—­;¤…Üèª`‘ÑùÞ!ˆ¦&%#5#+e,ú\Ыé~§ññu#ÍE½Â#5£Ù|§(SPs;Ojئé¶ãN¸JÝßâP¥®’œd ›÷%Ñû²sc’<…#/QÈ<ž±ÖÉØ:îgžnœ;„ä´DJ'd…Ò¯ à¾þÀäŠÆ,hîЉïÓ)€KIBñÏBpÃ`À4Ò$#5V@ ŽÇ8D˜h6Ì «üsdáËÊN¯˜ÊËN([ÚyjÞwÃ0ÌyevÑÈëópbgËX8O˾6ØhÅwA) gLÀ…ÝðºÚ ÃŒ¦Û“ÇñÅ¥Îoj!þK!n7x;IÛh½'IéåØBã3®•úÉìé:öï{uŸD‘g3qW£Ð÷AeÜ”ÎÀ±$TæHG¥fqK„V›S9|=›Ù Þ@àÃòæQש ÚÓ³IzD÷Š&åšSeLæÉVá¼ïLc‰H|”ØÊÒj€iŠFzUiòzCŠFîA‡'Nq»ÊNmãn,½±ËçmÚÛ°› Ã'±ÛC¾dÃÄfúªcS]äx0^³r#º“—<(MŸ9\ÂBBËœRŒ5®Ùu(ÔèëWå°õ¢dŽQP™eiÝF'µr¨6ŸG’WC'Ö6ÏyôœD¥“²âª…šX€Õ»o~™–„“šî{rqBSJ(s5½A¼ÃyªÉ»=MCÇa4†œŠÎ_Ŷ‘#/%;âklêîó]Fú{ÁáaC¸ÔRôïÎJK¾P’iáÝLl‰wì¢e:‹·Ê:—³ Ë&z¿øl±P‡ÚLj^‰JžrÕ»5ežLCm¨¡!jž·,…'±hy°•æÅá†ó‘}Î!6co¸„÷èGøÿ:zeÈr™|ýÜOnÛz̦=Ÿõ½u2]5ʶ} BÖXÃeÃ’&Odù–rõ`ç0£#5EõV|ÈØVÔšu¶‘ÖòDmõ[«j-5·±©Ñ4DšÖ],¹‚̆1µl(Â/ãsmÜ4Û@¤ë—"Î ÖÃR Ó žïËç?ëÞ4'Bˆ$„á#+£›&ø¹Æ‘u/X=Ê9e+Mj>]ÑÕqRÛOœ#bõGB†.‡2\Rh Np®'))êØ6ƒIÇ¥I³#/jllUÊCB#+u!ÜäOFHo©!³#ÖQJÉŽÏ»'êhº;p[o¥TÂfƒª¢l@“ßQ#ý#ÃædTC¨`õ·¼ ^ä$"©À“‚ÉöŸf´÷þ厠2DIJ" ˜‚V‘)„¥"¡R”h‰¤(h™)j’I™IªE¥ 4ÉD¤0 ï§#/d=ŸZ»=¥ùGö»)ÃzB¾‚uf<Äo›Ýñ‚‰G¶ •ˆøaËå<9{Ñöð÷v®ß 8ƒ‰ÅzÎ&Àœ.â7–ª°øýB¹¤®|€ð#ô§ä«ÐÁ£&Àâü—lE+ùU£ 0aç¨ób©WѱXò¨6¸j1Œjµ…˹J¾Æm™†2#.Á™É'´4ˆ`n•&&1­Í³†“bdŠ<,©Ù c±ÝHødðejæ8"»©h8q­À¹Äörœ} ]™bIš «º3—9+u]Ôzµ¸sß³›CÙóA¸x°ö{C¡«ÄQ"x%t…C£ T@̼’*§Dûð~Ov§ 4§°’o##‹±‡hïÜkMJZ$ØÚHý¹LÈ~¾¿¢Ñ XõÄâ¶;­‹˜Üœéƃ„r*¡¢ óS»G`Dí3õDÙO?½ì¶ÆBÑÑÖñŠ™ŠžŒ¯Ë¢Gc: ƒîB&2‘äº#+nìTÉåɤNT¯‰Ôsº`jŠÖeHx\ŽÃ°m€¤€Ò°ék†Íìíå³pÅÂ79jI±#/¡V"4HnöÂ1Œkò9¥£^¶îÔ4‘#/PÒßÛàîdbpH]Ï .Î-¢#/ÑTke­¢œ[þü=u¯B´.KÀ꽘"Ê&”h#tI&è#ŠÀÚg\Êp]f?™7—VA¥Ãiîĵ#¼E†ëÞõcë|O‘@bOˆë#5{H4l*è(h„öJ”†ðí¢óFå‹6ü´ï÷&žïl«ìøÚfמpNÑÒ"˜­b7 '6 ãÇ©Ôp'fãñú‚.‰áu†F?Íd()¡£IÌüÐPªLëYˆ6U=Bƒúî‚šTuB.(J4ô$øp Gˆ§F$/ƒHÒI#°?«óuô¥ÆSÖþs&w‘–r>g2"ý5&a'Ì(!0õb‹U`l~~#5iH¿ÏG+{Vè",Øz¦>;Ô6,§!é4W#5u[Û“ÃãúÌÄT£cIŠL‡x‡9ÙÒx~hÍrqQƆ0ž:—vŲ\"j__ÎíH³Œ³µoLhIƒlÁnËaquµPåHðÝ$®H[Á¤-M[hí.éðâ†ßò¢4±‹É

ú FªRƒï”#5Úýi‚©ýj[HA©UB&$Š¢§æ?Ϥâ.áí#+m$É÷ìCgò0ߺr©#5#Û2t`ŽbÛÑÜ{V8ÆZ• ?ÇÛ'o±ø]?¹¼â”nŸ`ÞZŸ8Uf+U5 Äº#5ü ´OÍÈA¿Fª‹}Fßn¡Š#/A C¹1§ßw©Î'çº~Íå®ÒÄ­Í|z¼áŽ`ó˜ƒÚã0~J= ­õ7ì€i”a•#5†€#5ˆ#+)E I!:Ðá‡Ba)HXJX ùddÀ¢l ‘_y“¡ØT_ÞiˆLÇX´]~¹ÛÅÂ-7zær;SÁ0æ–".g}ªìÒYÛRv·Nîu¨ZÖgmŸgxw/FfxÌحñŠfRi¢%ÒÛa=“!º‡´¶¬SܾíšåòS­ÝiçŠ7‘9·/IˆA­Ò¦ÔÕGNû3¿ãÓ­Òm½X¬àé..Z®3y%T>”rzCÖ´m¨×g{~æ3¤:I+ˆfºÞ"úÅLjí=D=CrJ~eÛ}"¥S7âôJ‘uâ>&NÖu|>üg1uä&É&œjUÑÉIÚCÀÈvy¤ã«ìíKµ¾²+ÇmYX|Ä@NÇ8R52˜—ûƒ…˜ß´¼º.±µ½ßO =N'vLU¼<Æ:xÞçzæ'OFf y[Nc…D7diV1K.Ü¥˜ŠœÆÜÜo«™áëwÉÞš‘)OlÎå;2FÝLr‰×0Ú€Áœ?:„ßž0±$tüÀ«š®&o}ïÍ™˜´3>«£r÷.ÕKlc|1ü”ÚâÁP91$Cu.sNÚ›D>Ÿ­â¹¯$½¢3wƒ Äå8×Æ3[Øñ£+OŒ‹\eÍΖQâïÆ*Úë™…cÙ?7Ì6mÞ33(hŸï-Û3ÎüË¢DH[Xw3 ««´<¸‚4Ði½‡–ô¼ùÚÀg>c#5¥ÚÓ¡vÖ¢¸»Çñ®9£qN,ˆ0ã/[‹GMƒM:æ¼LÂzµ\äq^†\u¶¢X˜”®æˆ£'5œÂjg{´-–`ʹ‚fn#5lPö´°„Ò®‰Ì©’#£†Ø·®*èè.u¸å­“#5w°1±³‰ >Úë¤q»!„€Ú.:×HtÛ&ÜcÔ3‰8È2„r {é—#܆Gu^¦<74®='a›†›.g•Í½uK(‰¨§Át‡Õ#êç}q‚D犃rî¥Ê’Œ¹’©J±.­à®„Hð›ñ*4»®%>Gåp>dµæf”ó¿;«1Åo¦ÜMÖÎs·Q ls$¤Cæ6]»wvA/Á£Mt·AÒ1¾M\Ž˜ CFÅÅ«¬k¶¬ÉÝÝŽµ¥ÇaÏ ´â“¶;»ÜwX8凖åÖÚñ<¬5µ áŪ¤Å®¸ÜÁ•¡˜ËA´‹/gE5s ÛÓ›£KÔé™a›m›6å¦!¤yyu˜±>Xy)#/‡JGBq…tù4ÖIÈŠÚ‡40†½¢.Bf9Q7î˜P8ÐÅàG@v ˆ5ý‡ÑÀƒÞ$t*TbP‰¥ñíRÍ’§‰é¨©(òPö°óo¤¥×–€är×*°:FŠ4¢L2„:2Ì#+#+ÿi!r«É#+¥&Ô©é($àn?[—|1Ë áÂTdì†|?–N1‡V±t»ƒºçéøŽ½˜Áèž('£t9 Øþ?#üä©=&~ˆò‚µ†ئB•îe`â#ÙÜ#/€YäŽ:Fá†KW*¢*Ó½ôÄ¿®ˆa’Ò#/QÅC8xA¬?>w̬I«J±v8ÃèI!¨ÚcHÔDgâèÉšdûË}צÙ5Tœy>ñé Ë?D#/vÊ Ϭ.ÁýZ—õ?Å¡@øoÍç0„¶EÄ%#+h¨Ðªþù`ÚL2€*3&Tax"´*K«êxÃú{ìèÒÆ à9«Ÿ„;€y‘A1Ågø¸ó8å†ßYÄŽ˜žqꦋ|PÀ$Óg´Æ…À¦YòÏ*D„p6.(:…;ĶÌÑ©Y!‘s€M‰ Ïé=ˆEå‡Þýî@9$©+ÀæGgÿÌ;oê}=€Ã²'š(x:HÉáоÔè9ð3aaVXNž7sªyò•#+ì8õ,ïjT‘ª‹Køí§ž‡b 'Öà6ƒ™ÁQð˜ˆõ0hcs9³2<#/Qˆ¸õpºÜsë³Ó‡Î;#Ȳw̨ŸM"5´ÈlŠ4íÜ(¤Y^­úKØØý^•k†”(“¼-ç†hé(h@6i/òu"GWi„H-‘l¶IªL‰ºx›%@6AŽä­n Ò‡#+àiéd§³—g­CñA•©J©Ÿæ³ ET” ‚?Óù9Š\“±Àϸ°ps‘ÝàÞªô‘ BîeúüôbZá¤*Éœ ®! ^Y‡R}^Ÿ‡kÂÁAØ€ÿ{Ž›§s•uI¾ƒÁ#+XL€–wÔGIùúÞe$»9¤M$÷X )>ë}÷j#Hbh4©›òe¤žŒmyŽDÅUÌ|ÏMRÒlÖ#/Œw±¼Xî4©x:¶1’u¢'ß…ÀAâ8Ñ<±9”8\QµÌ‡‹œŠ¬˜€âE¦-"Æ঻³'L#/€4Á#5FÁN#5ZB6δÎHÊt¨Ä<0F5c+M‹›“P¬lBn•Ú×@el¾·]cZ,c dû, ‡1Yë‡ãï¼€‘ºàñØ ¢%((a’¢­A@ÅÌ`¹B£RÆ2ì##+p`n‘o8”ÑATÅPL =”ÁG+(`‘#/ŒE[‘€®6ÆšHŽXûpØà¾öŠáaÆÔškK|Ç.AB¸$tÒzÙ’Â((Æ1'8s)!¥#+™Ð‚(Âk%dƒ§ ò2°âÑs…Ç¥@õ;‹PFÚÌlù¸ †’R^îO(™Š)¤¡]#…ˆÅáÌs(  „iîÍ)SÍ,±@ì¹1[¬á"ºb;Ä1Á\HDËHNlÃâkQ¨¨¨JZ{¢`‹c£P&«1)©¦ŒmE#/D•C²˜ ( 8 LPD·aÈCI¹¸< ¢1"šˆùì'#5§¥3Æ(¯.÷»œà†HŠÃ#5©ˆ:æ¸/Õìì½zhe—ÊzøzɴжùÆntý¥wÙÇÆy>}}üÉKÌá]AÂ+—†òD«z0çü1#[‚w¶BAô½šP@¿>DÏáÁîmâ™HÞˆ(Æ3#+°°Ñ~õÏ3Gv* hCŽm^|³Ådd  „¨OFuACIAE#$JÅJÈLÈ1!Í$„óÖèšS¶<„ŠŠ)IHG²bhË22G%$#¥÷ÜâÁc9ÒšZF…•£oËJ©ÁHGCâ( ã(ì#± 1@¯²$.„|dÓLJ¤,PÃ4”Í …,KCB@R?”8(b¤¡ ˆVXa¡Hh*$¤€‚_%ÕAMMUTD4 0A#SDTIAI0‘TRÐEK3 QBDÕĵ$Í-$Á2ÁT“E0Ð@I ÃC- 1Å%CIÄ„QM#/RP¡#/!L4ÀAD’A C24•AHDP$ÑDKH2“UPÁPTD45LÁQÄI1A ¡I QHD+AJÑ2„LDPÊŠC#+ÐDÄ%$DŒ ”¢#+‰ÕTÙ a¦••"¤ˆ8H©¨’XˆQ!)„FeP¢•òC‹¡H<`F”âÒ„D%QÄ#/$Q#5$„(”»K‰Äôч¤áÏOb›Õº×gø \}Àž‚Æú£×8Ó_ƒÌŒÉ5ôÌ,+%žÄ òÎZbƒO&7Ú(n+8(]ÌObsä]`ù'S‰$4# û.yq4¸Âôíêã}èŽ)ô­ŠýKê„#+ö¦D­ÊNbJPø´”³Å'àØ(‡øÓ"#+M¿b#5¿–·aê:&éñŽè:O®&N‰3]–yÏaÄ\xbì“#/â‰HOp¾¸¼×0co¹²”nÓ°‹©Y]:-%ý¿$ö/›¼9 êªS ?(—çl€w±@hm>ð3ï7% J8 Dmo~嶎[Y…ŠŒPyñ±"tãô;£즆aKìxÄhКj÷”ÄžÒx{JŸ¬@."H0  ð’Nóru}ÿÌQ­¿qƒí#/0¨#+L㬆Àþ_í!êœßÂð¸›çË­W²F‡¯¸4ˆ=與S¸ÇØxâ®äú ÷4#+7?sNèb#üdÁ*è2 Anä˜#+©Ü¶…ô`Ì•~í¦àE±¯¹¿-#/&³Bt~GÄŠb¦"6a288ã»u³±q»Ã£áä¸MjÆÑð˜®Aü¾®©”!8dÆ]xZ§Ì™`'ê¢#/·€~ã@P&ú!)B™)R F´}TÔçP…@èJïeˆ).¼ÆÌd }Ûöú6v±sL;òúõ¶ÊuÛÌq@Z¡jÓoY™“Î]zp’Iƒ@f¥øß_ƒ¦,}G±‹‡ìژ悜PD#+Ôrñjw}SLÛIsoŸóO¨âiÙD­‚u¬Oiöø„’Iœ7#/ŒIñžÏ+ô‘:ÞÓÑ1ñ…éï .¨åÀ´X*¢’ŸbuR/x@‹ÇcR% OãµàXKI,a°’AN‰Áú5G©ŠJšàÀç ôjT.ßU"<G´ƒ…ÒŠ:ý|ñ€«³6”Ó÷dÈ)%V—ºÆVaR#5ˆüzìt!‰`–Š€›ø÷‘Àøó<Kûøר\ÊRØ…7ùö÷³l‰" 4ŠY=¥ÌóÖ;tdš^Î'wSûžüã#/^nG9$r8l ˜‚ç™à’öL¤ÒGp`Ðj”’…†” ®k|ž<Úëh"A #5‰ˆ#/²•°é‚R Ç˜ÓOcrzóo"ìé¸g„ÐÞ@h¨h(åG#ƒ=àÏ"Š¢ ªØÐM;M›ÒVgk›Ýšˆ<ƒ5HRÓ§hCÃ¥g#5+…Rª¡¦i,éòu·À#+¨áPk³8æɽqšÑæ”}ÒBJˆŒÞ?t-Š#+}ºrCA¨0Ô#5@8ÝĈ”â;Ä@k¡(Ù8†çÓ€u(S¸fJÐÄQùj€S£I‡cÙТØBhÊäkU*‰›.ãX 66;dšHzÛÔÏñ)¯<Àó`  #$ŸGB¬Ñí>"Yêœ);ÁÂaÕS#%QárxÝÛd±!‘©õ©ë”²u›àšH:ÈïGR9:‹…‹T”<ƒH)ÒÌ1•˜#+ˆ’VŠÒ4#$ªd#+–p}[”nM¼ÿá㨩0È°O°Üðûö”/Až¡Q4`¹u½X|w*Hp…ö½yýþ#+ÌÖiOzTà -{®¬)vV0ûô™¹0Û~•!†ÉK=Nw;­6­ÖQ»1ô1põ½íbœê…« ÄCé×#/á¼ÐÅ€j#—xƒ¾¶œdüó¤ê-†ñ@° =ð.@#/ú0SÓŽØõš+2WF/ Õ®¯Bùç§TýI¾åN‰oÈ„ñ`db0÷$¾æ΂²B F]+F#5¿n6½`B{)ÛkÍREÐG{J©÷´ìÓ>×M`ýY1 oTaQÎW-¥ê7  ÕîFÒéëj&ÒMÖ][ߦ–«{ó„ç`l`äé¬áA¾zôƲÑ8 ¹¢`Û±tÑ1ÓlLZYŒ²) pMœ !º5P4!ËIÊ‚”{(QÂ?du”´‹!L"ÕV=,áÆ5ÆdC!Ù¬=ç¤ûÀ€}”¡{(›ãIá"ý·HAAË-æÁì@‡=ñ¥D;!0†#²#+(w(€ÞxtÒ†:Ác˜haY·zÂcz>ßµ]` Ô¼Ô®9çUˆÃRÝ *¸b‰ tÛÉCZ Ãpßq'ã¼âb1¡¸MDŠ‹F2\0#5Ũka·‹oZvÙð¬Â)¨»ÜY&;h™Dh:³[4…PѨ@OÂx4XÖ·¬”™˜¢ ×hFÓ©øµè<ò§“™p²¾Ø[e©˜ø|<šâïK,%ÕÇ1ñKîùF]Ý#/„ì„ì°£B]mdT-ò8‡4¿&öH‚£ðøü×Èãš÷pi«âagf)ç ÕhÉF1WÛ•iŠ#W›ÅQ3 ìHª³llOç=ŽšÂÆI(ˆ ’CtÜ­Ë%v›‰yÎSh™ˆ¬K*€7ƒ˜ªik4'šêéZ¸k´¡Ø]EÇ¿®áÉEM£ UQÙmlªy\ßz˜¡5ЉnzêÀžl«â“IÀέê6”:‡s¢I#/hó»<;ut²•¯HM¶Þ`{”ð¥³HWEV"«Z›m ƒÑšŽÌ"ï<Ëî™ÅºÐqŽÝ*›@ä*bŸ³—›^Ðk² ÁR`[¸ÓZhfæÙ˜ŒáÆÔ&™„ÒÒF `ƒc-:IŠ’‘9… ¢M ?–õöPö{#YåWeB=Å2˜‘‹W† æ{b¯.íÓ©7ÒÙ9´™L*2\Qê Ó¼‡Ìsœ„ÆI2D Ê#+ü!5×Ø<99Æ‚$‘S@D+‰ÆýR!·–ãÕ5Ð?7¸& ¥J#5@¦”}xÐ…’R{L, #5¸ 2 j–%¡‹¿J¤>äè@#5iùçR\TI¨]Ɔ„i¦#5„Š‚’#+Z¨¯#/¢#+’ƒqÌÓ0}¤žœc<C Eâ›TæÔSk2EBÑSO6–Ûu”ï#/vö/à÷}‰Š¦ªª&‚¨KÂ1ùcBÐPMpÀ«|EòŽR™ Æw…¨$¡N2- Ò„(ª™ZR (ÒavÑBù*J2p¢ˆ¡g©ñìkø òz#+Í"ºc•íêuÛPhF˜ü„Ð;#+Ü£Ù°K‘à|#5|4ˆþ²e¢5IëÁŸãËw.Ô‰äùRW|&J<9þoÙßã×´¿gÖwhÒtϸœÞ€€Ò¦c"ÉI¨S9³s_VoßUKUM'ΚÄÊe§5À*˜§Ýgp;ƒHe!«Ô•#/ƒ•UR,Ðý¿o?V÷$£Á#EÂU^ãq/é¨ü˜{ºúðê~„á"ê6ƒ›ÒÚ?Õ–|ßž?0A~sØ¿3“ke|dfU|5{5ŒáÛ¡ͨ•@~’Øs鮈£TÕ£—2ƒH¾ˆ§™®‡&†€Š*%^ýÚ߇4&š™‘èA¶³—*C12¥“‘ùÿ#/Ž I˜ç#F}>1ê£Ç3š“œÚæ4âL9¿4»wƒlš˜CFŠcñ· 'ËAR5Ø{`»´hŠíc²#/nîóKO¿3ôÝ‚IFŠPceO-5¹h”ö´E%E=Ú(i¢eè§8‡å‰9˜bgŠ‹²dÈÆìÓpÊ:U{ÈŽM8uN$ˆ–h¨Š38•’{¸eä¼8pÊ™ f”¯MäÜéN¾À5›Hü„À!Ñ}†a7ë>~‡ßîÛ±x«8˜pgŸÙú{vô£Èô#+ŠÊ:óDû9ý{çêvÛÇQ<.À@×ZZøkíñt4!ãG(Ôj°« "îÈrMÜkìžAÂ’{j£XýüºÚÈe6Pa6aÉøf}bá8<™ÀBZ#+²ˈP€ 2Òðd¹Ÿp’rÒ[&†Š]óÞÐq­«KKæÑÕE' p¶4MIçï`äpƒT12È{a>þ#›+1òæ‚1¶$CõêºçK4æHš£‹p#G6˜*iºmPEÁŠ ¹!ç[a êì%Ëeõq‹¸ÐSô[ŸtcÖÿÍà>ïD›2T¾¨1)ѪtŠè¥5 PÕD´D…DŠ&€¦#+–H’– mŠ´Í±ˆb„¡#5T¤O¦=ÐÄA¬hŠ("ñ Øtœ#„0W¸ACÎe#5G}2ÆO±…G#+BÄGsbÅb {y—×#+ƒ‹¬à…!"‚„Í€Q"|¦g¦9 ãí±LóÝI<Gp0ÐC0×¹‘±”Cúâ)´’'™Ç‰á>£ƒä6#5PŽgªoÓý8aàV°¢äÔˆ à4†ñ|±Ó1Q#@¾ƒëìmu:)¯Æß1VTUÔý#+Ä&€ç¤ÅM$&t1LTç+E/È„)ú |_Eƒ*kÓÇUJ^8˜ÆMm”¯Cë”B F…Hô }°[Ø%=ºsØÈy*ðÙëg‘îú3¯‰1Rÿ$c- ;bth3B¢`X@´¥%¡]+QC Ð””DHÒ"% T­%P"D%HEB´LÊ”¥°E:kT4B´´#+i¥#5B€4”#/!HÚÊIRP•5K¢Œ@,E(²#DbÕ:#+Ð,B4¢PµSD”ÐÒ4´•§AE©TŒ@4-#+D4%ØH¦…¤(ˆZB‘¨’´š#+¢––JJ¥¤Ji)F• (Š€) b"B€ˆ‘¡)) N†#5Dª$ ¡"µ€mŠ#+Ã$†ìéôT÷/žý A¨a•3w?2udŽ#+9Ò,¸ýÖ9ÌíÈ¢š«ˆnv¶ Çë_A5UôÉÄÜPÑ2xDþž•NAÕR¼ô9<æ9Š·$¶ÅÿšÄS‚€l>² Š~ ˜ª&ŠL| ¤¦š (¦ h¢š )’&©¢$‚’‰¢ €™Š˜"…)†•¢h‚š#5¥ëã; qI^¸¢7i}½GM¿ÏÇq÷ÊœÒ ä„#+7ԇؠ*ž?éM—;0Ø8ûI #+•„Æ“|°}—E8ÌbJØ<dçyðõ»uc;ç#+Ä$B‡Ëü‡ï„F!Gñö<ÄïìÔÐ]’R#+ñ:›ÃXwˆ¾cêä‹ÿk˜‘4’a‚õvî#hTPJ Íʨ‚áöþ§‹‰ížü[C:þ¸üD¯¯:jHP¦É‡Ô†¨¨)ßÀrºCί7GmÁOîoÙð‡ jâ=ÈêÛ®qps`E6èxN1fßAÁHT·.ÏȸWfnm(/Û~Êèhxn½ÉGüßã÷‡wå 첑’#+>H¡Òl=¡6ç´¹vPdI“ß#/#+îâžØ¢Oëò~ß4ú*&™‚#/ˆŠ&†™‡6ÖŒ6þÁ„àìùÓÜ@D©®òW·‚#+|Ò^p¸B+H T#/ ˜”´ ¥CéÜ$Ô¢­(@c Ñ_D†#+ŽÀ€øÍD ÈR…)‰CÁTrhû9é÷€áÐpg¨Ñ¦{+Ziµ'Oè¼½õcšãš¸º–J½­Zìå4û[ƒ“ú™™¶„½ÄÀ¥®£8K¦Ñ…¢*Ru7ÛÎN%1±b°QP”x‡nžGê1>'€WrÈŒ¢ŸŸWHI9ƒý¦°<°ð<t‚¼æÝüGÀaòî|ê>tœ8õ§¥Üæý–yp&µ¿z°Àrðõ#5x|Çá°~z–%âJèh#+ï>óf#5˜CX@ñaè”jÕHúÿzŒæeŸ vBŠNªÓV÷âq®b"Ç7è1“Ê5dtx Áö€õœõÊD*”€­*$@Ò”Ì#5Œ¢{$ ”tŸð@ù)Q4#/:)#5t±GQÆšT›s‡ —!lr/,‘8AT@s„`¥„bŠb"N!†ªÖb@(CCÂX— U4DÄèqET”e1 D«–D¥F˳$ L$¬ÉEÅ&¨)i„©V9ÁP>¤T”JU£“¡H 4…F©J#5GH&…)@:EГH%*$«ûͦ…çÑ) 9¹øb"R¡’%/Ójhþ0àùA;(ôõ'#5̪ã#5¿dõHD ù`yk#+yB†’¢Ei"ªûm{Zü‘…;Ê‹Ð À­д¢D)W†t”u#+ HÓÂ<5”tŸTh•î•8VŒ‰hF—°ëÈS7Ò#+ JPW,’-ó9:D:Çß±b*1Hÿß cEhÑälØ”¹!Ùx(²u.KÂQ0…(F”¹;7Håí¸mÂÏ’CÙ²];Š)ĵÐ=+B²Ådˆ!#5ÁˆËï9{“û¸p#/TX!s§Tì=g„ïaPÒÁ)¾Ôý ~-ØA8‡HdåÚXÉv¨~>'ÔÕWïúwßÃNáú¨+»2ÁyzúqOÓõ…Œ§LC J(a ©L”§v¢T|žjƒ @üO,ExûcòðÞñóÙCe.…|üL+‘#/^gÂìME(òà‰¿\Öj"‹R)9…؃@#52†AG á¹õÅñ£án"` òyèÊyù)£IÀ€×'FŠf]iþOYå¸b(‡_%øfßRþ«#/Ƈ×ÁáAèprø#5¦僇¿ÀìY@€ù"m 51U?Ø?5tÆ0ŸÌ†þCh’I*Š ý%¹L®¡‰e#“ÁÔSˆb¦¢h! ‚)(Z>÷Ù ÁЛbÄ#+I?Ø€ ‚ä.mM#/„ú'5í±c&)Z("()šˆ‚¨BÎO>Þ¿iÞ'°Ì±öx¯«QÅÏÌld#/a*°‡¹˜½u›J» ,1<³ Ó?vý_¥#+Â(Ä…úßÛ¸#+6§7»c;ó9»«xù'”®Ý@$“Oï#¶ÊÜØ!]ÆXâ) }oM<(4×wâGÃÐ7RæË3VñQîA½æîºðø™—y)/ñìÖ6õvwÚƯäO¨ÙçLì>4a“ÔQš¨™Ó}Ôt§#/w¶ùLGlF³ó©z4ž“<¶ó&Çlmz†bŸÂ` –È, Î\ ×bv‘§S3¢7Ü€Ù÷Piñ†þ;2¢Éç¿Òã˜#5`aUT‘~™rvõýz;†Ê”aE" ™ƒéùÒÀXx`P®L1× çñM/zü#+Öˆ$‘DB“a'Å#+æCí¢zž=¥U¨-4XºÒЇ·Yiø<¼£Ÿæn°ûN#²P©¤4‘þßÅÈìØS¼‡#5ÆXŸ‰Á¥ÀL:¢NiOlÈAúðÍtC¡™& ƒö}ÜHH#/1iO—6ç6­\lð“/ò bPˆ×î¸öt¦Å‰ˆ7ÓD'4»8b-~2Ïɉ¦Å,ÒIÀÖç)!=ž…Mx&…%ùuóÛ‹@ÇŽ;nïÜtàu<ìþ.=-P>ŽØ¯\d²‘uÚ"„#+g^Sé‡î”ýi6½Í(æŸ#b#5ÿ„œ9ÎÜ2bMö¶e"Ú\#+Ä cJ®Œ$" Û˜ëÂ'.i?Äh~¡¥TM@Ñ#/Áƒ¸a¹sO9A4BahÓ6ÜûG…šY“ÔÚÃ=|8~%ÖͲ\©@¯Ã¡-TÈiO¡A3é)ùDêêNž˜ÂLβâÝúé”eè™U!;PÎñÝ(jH‰©#5i ¥ª)’ZŠ %*JªB¤"F˜†€"*˜¨¦„j¨‰ib"PbJi¢¡"™(‰ˆ‰‚Š¡)aa¦šJfJF…hJZ"#5ª(#5i)h ¦–%¤¢’FJˆ(‚#5Š”YB jB‰B˜™¤‚OySC!!A~yÌU T ‘ DMA ³"D2EIU$HÁIBÔQT"Q44"‘, ²‰$)Â,‚TRDÅBÐU5 °•#/PÔQS4LŒÑ3%$“KB!15T0@@¤ECU*2A0PHI(R¨ MT…,ƒûœh ^V„ÔÑþüYËf5ŠäÄ‹³Æÿeã˜<è|‡S˜pÔ#/Ó&Žü`ÏÑ]Ü€CP Ô ÷Â)JÉ~3ì ³²ƒ‡S#+A¡Áh@1*††”BœÊÉv¥8ÍPìíýä;Ê x+Æ&¤Z(#+ˆ@‚ÀÀï|Ϧ6!:õñb±aÉ‚")ÌÀ¥L‘)0 úõH9¼´÷¼h’‰j$%?)1PèÐSDA#/ûv–Ù JRL„T4¡)!ERò4¥!R÷“‹SP‰2•HC*‘$QÄßlBW¸Ð-3iÐR´‘äŽä™g— šXOWŽMJ÷q² ¿éØM!ÃXÐÌžÖyƒCAc¤ýŒ[õ1l‡95)Ö£à`¤ÁxÛç°uœ{ŽO¥ŠuC#/ Uùà"`ƒœâAöñT½¾ñÙ?ÎïÍ?r#5D=+÷HžÕå vúÃcêC‡£`¡¤^õýöãÄ•û:#/Ñ5Ò}Œ¥%3@DAE4„ACT%)J!@CI¡høƒà¨•àÀèt!Ø„ Ó†© >äeüªïí_¥ŽüÚÎÏŠbé¶ÅæfšC ‰v9Áäx)5Ïæí8ÇÏ\;q'%ã›ëgað5fM™†O®×8C}¬4dÔ=œybOŠcCaúS”. ³#+tÙX5Õ$åÞž9h]¤ ¢(³ÞÞ3fSTuÖ®C9Ø’É£ÐTP[IáH$ÇÌ›‡@ø#Z+RçøV˜c`ÃÀ•aµ‰Áˆ9,ê ©%ò?ÍÏá¾MPÕ:°¤‡²DÚ6#+ô‡É°¢Jó#+ï<í®Š`•õúBéŒ#cÿ„ê‰fn³}´©lD!hŒy—† 쀊#“Á먈²0É”8Ýb˜ÿ¢Ø™2Ý–YeÃŒÏ#&Dq²S ¯»Ç)åøƒúOoì?›–Íp‚'ò Ú¨ÁŒ¡ÌŒx~H%ÌØÁ³jóOÑ®,8ÓUr’À9Úl?¦ÏÐDÁÏÆOÓøÍ`Ñ)HB0‚U£øò²³V¦»aiü3cØ4ÒÈï¬ÎXbP*^¸‘KàÞÓðûºÀè¬ ¥HLÔSD¶¢¢¥#5¨˜j$Òjª©(bR©!#5*¨¢ ¨ŠE(D†ÃFµTQPÉ… 3MTUSU!Q)uz×õuÏú¢}Iûg—#$’gÌp© ›–A-“4òW쟹((%‰¢¦:çK’‘AmŠ#5±%Ã&`((¥ý˜Áݽ ä§­ƒÞÑâi¶Ë÷Ÿ#¯<;45üÙÛ­EMD°d’ëöØ]lI’?Óþ»^”¾Ç®G½’øÐZî(ÍRK$½µ!@úƃ»‘Íf<‚Š²©X‘ñì·ÆŒ°ÿFqeDrz®X¯Z#MÛK¹„÷Ø{ÔZË­Så£ÈC±Þà%#5Q‡µgz„h.âŒcÖËG*ÖWÔ÷{n¸£ˆ. ˜à±7§t—A¡Ó­ {J"~·ß©Â„S¤œŽ4¤¡½°º˜Ñ¢–üq@/—`›:'#5LÅ>¹ßF;³ǘªp彄R; S½JÒD£$,›í˜báªS©E-œF27zA´õFŠ¹ßÉöïÇmÝ÷_ätð‚“Plž¯_Z:VŠFO°ÊzgêE#¤).1L6BLÊœRB8‘&Q `p8øcôø‡`<·piÈÁDÜ÷ù ù‚ˆˆh(9Y4m–b‚ˆ˜ˆ*-”hHÓCkŽ!í‡X{¹K;Ó N#+p‚c!JÐNAÔ5<ÿw¯`‡g4â<„ùvo{3Ù¤Ùgž`ï„ÙM-Í1 4º„rƒÁ?…6õˆ“Œ§:IäÔÒ:A^Mˆ|óU|Å Ø ÷"¾µC`êgŠõGG’ŠèCD¡B{ð÷—M¡ƒˆ¿CôœQñGúÃðÁ£‘þo‡Ô‡ 4 Á­ŸgçÝŠ^r™2|çTL1vz„QØ; ݇²Ÿ GIS•!Q»ÃžGjð®äj€‰!Ð:à܉#/~†¿SËB}NÓ?/§zX\ïEˆÐQ£T4:¥q¶dZP(¤)L ˜‰—ù!tº_#Hril€4ø7 ž~á“÷IáàdÅ’yHà@o7*fá”ÄØN34×#/ÌF®’f™4T0CœÚ3ûßéóüÇï² R²NÞüb'c™c!H{.Ûúš»M3‡ö-ÃP¬ly–0›^ûŒÄäÁ| ÃFdÓŠF&žkY>^¬²v‹]âáèaÀú°ØùgÇqpÂxqE8ã#5r£²Ð6ãý–V0M#/"' áå•#+Í®ÇQD×ðíÅ­:Æ/A„Åk ÚÅNªc%‹›4¤!µ*\'…§\ðŠã);·ÆÎÜ öî|G^ôÐsœ’#F®,MÊíÚâÒG’²°‹4â<\ŪƒÀ&K–G$f0+Ш]æjF=i·âZhjÉ:Šíâ8!8'f°AqâØ’DôH5jGÈë¾S3£ðøíÒ)RÕUö²Êcwïp±ä8 1ª€&”Dº\­e”ç‚ìäMŸw³m´mö;IEЊïl¹¥BÓn 1C€Õt\RhlŒ»§s‡ ¯ÏÌZ­k•ÛM7r~‰¹Aû¾”o,Éù¨4)¬ÌÙê5‡Ü¦¸Æ+ñz†(™™#/l¦ï†øÖ³HžSW´BÀhG=Ñî6—>:ӳь;ERÌ£j‰AÀM4qï ÁÖ“š4™ÎÚˆ~`…°v#5Ž†½S3ŒIr¶”O<ÔLßå¿*°1E¬&åQ ŸU#5ûÍ/7„Ψ*j­\¹MñéÅt ;ž7@à}6 ÉC¤£&ÙÓü3#+©××ûÿ,l èwøvàdÆòXp‡b#+°Q *ðzš¡%0¥©"ˆŠ8ˆ ˜ÉÀ—‚B/Ú ƒÆ‰U üçO ˆC#+ëÛ® ÁuŠ¡ë~ž+ö’ÃéS{G]½ù|¿CÓ#+J„Q z“ÄúÞXsۻ׃œ0O´@v¬ÑR€0&tÀJ·?Gg(uq{¡ëñ=Éè%iõ¼WfyQ]œ_‡,×Î< )@"U¥¡&"R€CŠwD:ÖP>¯RO›êÇÊ@ÎÀІO½èœâs×M~¶{òÒ×{"rÊ:²ÌÏ÷ºˆ1'#5L@°H#ƒ‰ºâþbh‰ú¸úóB”rSS±ËH-œ$ˆÄ/“A׸¿SJ~?*ôŸ›É$ùJÚ{7,Úú€OY’v«Aö¡ÈAIô¿OÌõõ\08§>ÎqÒOÂÂ&ŠÄŸ<äDY¥#57 Öt„­€#/)—H@=ÏÅÈ滟#ª¡]ÚW¨5£´ÐPm!QWŽ`ÕHDT‰@á9AÙÄÈÐPP‚ÐR[a+@äD‚” ZH‘F(€%%iV¡šbI’iA4 F™¢IC#+°`}Û{u<-û%K–1±­`þÕ±€3SÝv%DˬĴ¶&û1ÈŠ žVæÇ5ˆ†"›h¥"Ô#+×¼†ýû|”Ùö§Ÿ(mÄ$ä\?à%!à&£®9ózY4³æ4h8JÒú! „8¼Nïl¨ÇC¹—Ò#/áŒ#Õ@P‘(ò…É „|ã B‡9 ‚d¨#5ˆT¢CRd(±J4į·kßf¨h X‰”¥¼ÈQ­4ŸŒèBžzœM\»>§²vÑCå‰ä<žs;eä'"!¥#5B‚¹¸8ÛA[i/Y]1¶³ê@h©"õÎiÜËAN¡¥)]Œ:uN1¦OQç3Ju!+œùþF0¿$G€{Æ›@Òሞ&*^ 1ÿ€Áíþ<8ûƒVîo§H籜láÄôžŸµ<¼|nðe€}rˆ¢Kí»”\#5O‹Ý:†Òápd‡>}ç/£ÓÃNΈÀ訂ƒg«o bEœ8×BÕT°m-C~;>}†?pνe7\|¯tp£œ­†€¨,©¸'N&Û+]u¨v›på¶ÀfîŽîäöôàjï,a¬01`“"ÌF“­äœwq^cÆ.—›±Ôqá—…êüØêþ( h-O¢ÀFxìg§Ƕ&—ß.öõºgñTGÜÆÆ¿©ö{€`€î’ü/®†à!%?K"½O¿ÁòýP<™tPÅzbûÌKCs˜¸²Ï²¤ü©ªV£µc C#5H¤MЫ'´Ü)ø¥1EKCŸ9ä‡#†‹†y ol!¥‡ØcÓ>e¡¦q'÷k‚¬ŠBT,˜¥EœT7«M¾˜Ž%iB.0E‚w¿cjKXQPO®„»]Øt|©©”9ï®7sOµÄAÇF­µ"Ué;a¾ùv6LÄ°Ý¿ƒýé†ç·ÌvmƒŒw$`6Ž¸Çª}e0u/èA™V’$XÖ“‚ÝR¢?u–`¡Ü–ÏŠ¤àp%ú˜ë㽜%/ lÀÑqTŠÜ”mÔ>0úøòØÀ#5#5 S1)çz¡EÎrª]¤OA¤@ã!A·¸r"O×n™i‚P¿qÀ¦¦ hJHž\„§’Aíj˜#5m{¹ÄÖ 2”0. D•H}ÅIvxòú¨ ¢6Ô¤;Äôgµ!Ñ9"!Úãñ”3†ë`"¤C˜_¸Çg®Æ!?ƒd5ª"ýRRµŽa#+Ýœÿ78òfˆ¶3†{ÌÓÁ^Z<°TÜ6ayZ‚’9h¡ðH9«t’àkZ.,E#!Ž¸ÓÉtetN®]…#/Tbh›™NQÐ:"€W2 vãœ!Tþ@•BEM#52!ÉÇÉêìÈ! ¡•Ûtξ#/¨4„„¿3o¥q+¸ùz÷_qç™7>jy´Ä=ð“ÞQ mö|þwÇJO1Puhk¬]NÁ>ú@Ÿ£±9ñ%Nš\Îáàeí½9Ü!Ôç9*A(šÓ±û¼ Ê#+¡…xžÈ†šŽÍ#/0—Å’i½S{fð#:Ô’D,ÅêÖ:þ—¥‰ŒöÈƈ(Ú7ðÕMq#+¸<.Œ#5ѲƘĹŠ!$ /µ/#/Ut*;{Þaö0 xi\´ëAè—œ;*ØôÉÌEOôò@l_b#/¿š'dc±žÌ¢ÌhWǸú#+Ý?/ا¿í€)+±øÊaÎd¦áâ‚“uyc×rÏ$Â=÷%±ê+®1´}»¢+65´š…ùØS f°ë‰Ñ{`Š‚ÕÂî ª‘\Ç.#Hü[²fõ†ƒpÈh™*¶ó;·>dÂ|ÌÆòˆáwçvS-¦UÌÅq#5ŒáççÙÙ#“…°c*í¸.ÞAP[öÊ#+䎗C’Qè†Rhª´5ÃOÇÃøúƒ>B§¬šF€ˆÒH®ä)Ç·­í×ßova†Çg$tAìC¬sªS€î{Aßn ÒÞî">ï—ÖƒÄ'¥åôyèñ¾žìàfn§t|CÆPS>ƒökÁê;A6xxx † ¢©£­GßØ÷4Övüß ÉÉ©'á#3­sqhÑ¿Lµ£ôO*÷£2Pr› ɨñÃ9ƒe|ÝeF-VÂQLréË„%ÑÆ^pµ­K\7!ŒÞ™„åÆ 8†Z¹0Ì G9Ô¼²pqææcXÍEÓ))¿WÏV¶†øæ–ºÁGÑ;¸è™þ3IHóÝ\Iëˆ)•‹þÛ2a$„!”§Ø)aWPùB‚ÖÓõbõÔú4U\Ÿ h¶';"òße”1†LP®àl,È¢EÑC ÑléJMØÞå¼wÙÌDc!Ÿ ‡œ×1ƒîÀbRƒ“¨ŽÚ#+òïLy'ŒÐ–ƒÈ4^æÁ Ó¦0ÌìØÛ¼÷Â딪390Sµ*#´Š R½¨¢RIS—(g’=e74óØûûÒ¤zæ((¬l1…›šÒþi¡¤‡hF`h#+ÙÜÄy/.J>ªO$¤ÁPœæA1¤1 ŒÀèV†U=ÒeÏl‹ƒ¾3ŒzbÂpS´£™(Èð#5@ú¯ö—üÁ¿#5~|…×ø|³kÉ*Ö—²*Î …›ª3ۛLjÛ1¦Ò?rSû{ @ü"ûhò÷øê¾=÷8ƒö!C#5¤*Pvžb:>|C°P^ÁéÁ]"¾À„€v|NÊBØÅïj¼$qŽQѧ=òNÈÊe˜þ„ÝÃEûáÒ¶ä0_¡®ûƒùwR8;¨»vA‘”>Âü(aY¶R&(va^Hpøé@z˜æš>:`â<#/7/i ,%z}Áb Úʇ½µΩü3û&†˜è~Y(LmŠõj?žÜÜ!tRêxè‹ìœ:§ç;™'Xƒçµ  êŠmÚ†F€“d}©©,)>µD ›0#+Ê#5#5À£ Ì#5)‹± ë2ãžZck1,þ:Ñš±˜È™ˆ6pt6“ sìÅémqH‘¥Ñ‚˜qàЄ3`‡¦•@ô¨ó&™¤šh@„¸ e€Ö‡t#5p%iiE‘hM.uš:ÓFï@àñˆˆ_è•Á'Ý×Çll3kÈÑ»¬ £ll#/’H{™½AkP…’µ…7#++'"ãc—†ÑfxÜ $‘›†ôF~µ¤-Yƒm ""µ¨A+àr¢È…5y’/pTåmऎ×\ªß(h!t"%éŸ{ þ¢16½,hå5GZëö[džÃkb4þ«ó{Ço9¹ƒBàÎÉ9àüS/¼ ™4-õç8Gå8‚’ìéGJYaÝ*ˆÉÑ™Ž(Ú#4ÿSE±m×ÂL¾ž•iDÌÙRfæös£»Üb'aðeͼCî"7RC`#5xgÀ/šÝ)…2ªà{³3SQ£9÷áÀÁƒ;<#/!‚éIS;¿Á_Çowïtµ“ìûC9ñšr6`’Hz  iò+1ð§ÛçqO¦s0‹>„O­pPò6ïçÐ:~ç |'øÔ|s­5ÞLû>¬‡¶qÜè&Gæ5?(f3ðÞY q•¼‹#/Å»…l44®æ|/Öó™6Îì9±KÒ%Ó"6ÓLoL È,e»¥Â#DƒÉpq°m¤ébóν:h°y À¨={ä<—°P(i¥ŒlÎnj’szV#5h)š¶ª#«ì*MSêÐà:A-†£Á¢Ã1eé<¯}ŒlÅp¼#ŒÐq#5¢*Jš,mÑ9QµÞp¸A;9ÍÍÆ‚‚¶9Ãrb#et5L50l;îžÊCê.Ö$ˆ”ÄS˜Œ†`e÷“…Âç6¨6s÷By[©L¥wË‚¤3„ÙMDcP2‘ŠÒ]X©Bý‹Y™£Ëz: ›»FøDݽóÇêú§F·Ñ›8Ši.©¸’:‰bàœiS£@2¶˜ÕdMZPÛÃi$O¸üzY”sË»ŒIP’¤áüÝÈuèhÀÓ؈)DšÙŠa”%²¢kÙeˆã60—YYj´}J¸fÖ©ië#Öhøj,aÏ'ãeHo¦¦!;f‡“U’Ä ’\3Â&Ò6[a¼P‘C[Õ0c{)AZöÐHÃ+¶„’S*á!7pnkkqØÝ;Š9yDtÉ@!N,fÍ¢A©¡Ñ´ƘäÍ0IÝטxHã¶+¼ rnoWK4w*Ë‘Ý¢‚)2h`Ûx;T$Ü,JŽD@z°dd…hXš0€Ý©»•;v{°dˆã«µ ÊZ8Y;'·0v_‹ÎΤor Á’-µ”ì}s! mœ[MÒ…|B‘?zG‘.#54ƒàÑ4Öyeff#/_l¹âš˜"×Ù²CƒgQ¨Ý2QRy!¨ðjh @Ó¯1e#³2UÎ``üÈA±‘Ø’D‹TôÈY6·¥ʆŒàÈ*B4šŽ½acÂ+ŠW5—1’#/ÖVi˜|²2±‘(ˆA”v5BÊ#/1 'Q¤$#/ˆoCnƒ’qº‰RÓÛó#+ÛDcFg;œ;În‡S’š§¡Ð(áHÔ.±cL(ÐAEž#5LØÛI3Rá–,(A¬&#/°o̘B9ƒHÖ¨R3y+ÒÐa t;¶{ÀrÈLÁIU5K¬TA- MDEÉ)- ”LRÖ$@i pU¹«‰™š~}"7¢›Ã¤4ÓCXäŽlç3 ÄmÖ5d„Š^3¡ž`+¥«©¸÷c–¸pRÉf}øéŽmæ1±ŠXõ´¥#5×ÂÁWG#+auQÃH¬2GÇœ&1Q5d!ÆQ2ØÈÖš²KU[œà˜Óª¤Áb›¶®öÚ2CI¢ëXÈnìÆb£zhƒkDˆË !.¥t m©"`ðã ‹\=wG;NhˆÏœ q/S-OQ#/X`Û¼…›66™£o{™)e0÷³w Q34ÚìÌ°±B$±‹9ê‡uă[¸ö·Š°Mƒyíy.‹—j ,"i³1óPV›0ÇØØñ×r*ÊÊÆj(X¦jǧ¼Êh#i#/F†§€×áMê-½ïZQP‹®s†ŠPÐÃS9ÌSp a _ŸP0$ 4Pr1Vsü2#/<9:3ÀÜŒv¼TN“p¡´UÕMrç<¸q!(¼•<™j"¸w‹aÆâ—Çó̉< àOÒN2œÁ0h ‚E‹d9®<ØyÃIÀTPœ9Ë<§V:BäYØï„[9´D#/Sw»sqÄë¾}ž<嵐åpâNer6QÒÈ4#/²nð©¤ÊãÆqå\‰€›s“\ü¹ÊNИª=lÃcãtZÅC# —TŽŠÕ8ò—3S:8Ãòa¦ƒËpæN‰¦“…ºáÓ6Í\責Üh¤mý:ÛÞ‘å^<·½˜º:uãs˜ Õ¶ åèUªè0ð1Äï9Mc#„Ñͧ±‰í.áp‹*¦g±…bǶ‰tP#5åËOI]§›©ÅóÌœ¨ìÂJ  ž‡>½96#+EB#ïfô¾³q´SP×#+TL9{xhIÀŸ9¹¾s$/é(/„JvTц.šÐTCƒ2¸åì¿ »33øâÍ?Hï5ÙL8°öAtúW ˜SŒ‹‡áµ=%eñI¶Õ^nóñÃv–,ÆŽŒ²a#5L*»<µ]22 ,`DÕSZÁµC9·ôã4¢5²,1n¤&ÍÔÆ7”xŸºQ P¢ãð\½°W9ž9ÝNÔ)¡TQ±´Ç"@±0þê„çÓeõ)¥ ¸#5¸Y ªçÕÒÅÞF¤§J‡\}Ùq²f"B³#+›XœP‰yÆ ˜>‡â<%h8… Hb@%á“ €„ žƒ'H((á,`‰á ¤$eÍ&"žÒX Ý6Ls:²9節ºó¼åEÒèJž°”îUÑ ë:uz<<ÊÌæ1Ãèë{YÔhLZJ Öžpœ¸dðÉïyFá]úïôµLv¸\#fOÞÅDET+QºÞ<¤~#/Soæ¡oõè–陋ÌΛÖrÑüʼnÈy€z_2§i76}‰”f¾ÔÉÓ¶¶¥•i°[Ë"Ì=cã[ãX”†4·¨^˜y#/jê‡'Õ#+£H²7’ÚÐ/f·Š1mŒ5åÇתh«ž«å™)Ä’¼,¨;餿—ôœ»Pô'ÇÌ=ói’ÉÛpóoX®{¦Œs7†Ý#ÒoI™0Êòá’ŽÄÙ¨ÏfhzÛÛ¤¢Ây2Þ³-Ù±êܺm=ëI£˜=BëWW#5gøÎc|8|ãjrB’(Ûd2Ê,Õ×òUciöç®õm œÁHA^ªºrm›=—XFÇ“{Q`k#5ÃE.›rN¨·tÑiì³V³vÙj*:랥çL¤Cý5“Óz‹dä#/¶ü>$4¥ÙÇmlá¦ä×­!èñòò„Nl+mbÔŃæ[›ß\J‘2%tÒ…MúýØgpFÁÁ+b³UÍ-–8L¶6Ó•ÉœB‰;OñړúZÉH&#òÔ O¿0<˜h¤¥ê×7ª^ úýœ·2S«IÌ.›ˆ½Ü6B‘# è´=‘Ò„PÑ"oí ³Ž½ŽÂã­hJÚ? #/tÀ `¢¡)*vŸGÐ>oðøhÁj6gEp…%…±¿×†ÄÀÂÊÊV\yƒÂç8\iF€#+£Á˜"q<rsb•†J#ûQꥑo~ˆ»¥?ž)(#5@„PièšQ¾¼…Uâð@¬„:A¶‘€#+u4#+tM‘¼,ET’±³QˆŒ”ðæ-¢4#\Ù¦K›œ3k¿‡]æüý7¯C¶T™‹gI 5>\xcœÛœC„:4<¸Ï!>’ƒL€ ”ÂaNHP>Ò½ƒ°® ]ÚªÛ… ‹e^öÇ'÷aŽdJ‡HÒ…$hÍÆ0b¹@I7,DÂff°©kPè9!W–¸‚å2-% RhÌvÄTPµKl©B0Â$œ…NJ!JšBC爚#+¡ä©@>0® X©”#+¦ÆÏfÊ‚•˜=Übéæ­Ã臃 («=DdU4Da0€C¨’á*C#5<+DHÌ`êDÂÂ`NØSé ,°#+¡'O°¼Þ\[Œçra  9TU¤L!¼&‰H˜¦jîT1ÔBMÊê§ÏÏ*bhñãŒÅœeĆÐPò tPž×$àŒ:M C5(RÕA#J°S4$UAÙdUT4D¤ÓEQDÀxØ¡DEnFA$ECÑÐLÓD4-#+R•AKT”ŒT4¥%H4(K@LKÁ”QD¡%0E L… @PHy•11õÔ‘¥[Ÿ}QÌ_yF0Ò#5šâ­€ƒ2#/ » Ed‰ŠÕ–£{ˆ‰÷k‘È~Þ²Cˆ¤H&mc¢"›èÕùcMM$HS@Ä(Ђ£$Fˆ{ÛÃëSëÕ5â]þ¸Ñb¬WÕ´ãâ¶_<áþ|Â:NvêODuÅÂ#5 ¦}Z5¬“¡„]¯Š¨œ,’¢%#+¢3¥#+þ\2 Ü82(jBHˆ&iéßðƒ@p’>¯åíÖ…R”Ѐ˜e¡)A)4h4‚ Ä¢f5ž@ˆi W’*%(˜˜ƒöàÁT%$œl#+èm¦ØyÄÁ£HÈiJ‘‰E" Ø0HS›" ¢5'iH& "#+„*€y#’¡šGJè Ãóh?ÖOÌ7^itœH$·xð_«cåÃP sÐs7MHh“ùq Œ ¦(˜ ™Ä!€ìt]ÝÛ‡«»Œ›ÎH‰A™ˆ$Œ!PÙ‹@¶x,[0— &ðôÛ#:ˆÚÆ0 òÎÀH3š2­w“I'ÿúOoy+‰€e)‚³´eCFÞ%l|Ã¥yÊmE#+^n¡º/_”>)µBçÑ“lü²•ö¹%"V †¸ª1ö5󉪢d"þ&¦Höõ“-9iüC·çf0 †¾„Ñ-‘C *Óë/v`ÊY0Ò%Ýù¿Ž…¢ÁWŠd"£SÆ}ð î…g\§KA¶ce½¦Ó¤a”»âíaùknñ].ãcε!«©.AºZÁ´#/ õhØÖ©ÃeaÆ@¶.#/G^ënCXåÀ¹.L²Æå#ÏÙI‘š~žÏJ 7äB”Œ0Dé®´ªW¶ªiËŽ#/TÕGT²MÒ¨¢Ì%!¾ô¡ªdi7L¹¥•˜ÀÁïQÈQ¥5FJò¶C+Xå­:b‚rÓ6Sb¹4W3ÀÒÓp줡õ! ϲÚ#+¨þ6pƒó#¸T€bPñ‘ü’$QMÐ U(D€RÒÊ2Äódü¥^Be1"±y'á*vUìiˆ£Ô(@ˆQud(І­Œ5¥ìGzM¤úxú­°ÅóïqiÅÐ ÚN§«‰€ð£íI)Tˆ#5QC>þÝ'&\LMnm|Ãø8’ÄÓ3QV#5X{X†hÑi"Žù8RþÈ#/ 臼2rãê@éÐnf"”õ!Ê»×>¼ÂqŽìÑÂÒO }M#/î9+b–(õ¢5¸ ÜbÉUÐQÒe0d/9ÇKBatÝšPädN›+¤4‡HUu>Æ]ʆÚ#/:°#V´i1hm28°6L²fÍÑl`«¢Œƒ›ÔB¸ë[$©ƒ,,`q:ò@È’£êA=¥X¢‰PBd"‘¦…)¤ iJ‘âÁ§*{lô€oûCœ¸šZëI3°Ðâ‚((­) ö΂k‘}KÇ9ÎW(Œ²]»Ëíqçç8ÉBRiL@ĪHÈò‘4Ã/>#$÷a䆴ÐÒC;R¡_ž[¸ŽFá#+#5P‚a„$/c#¯<Úç·8òZ(JÄvF•¥¥@vç5Th@ô”LQ@Ø8U0Ž.#‡šÿ3Àt‹Ä÷éD³ u¤UKâ+Çjæ §¤?>œ4+ÈÉÌx"©®@¾9#/Tƒ‚¸ûâiEÚ_}ûpØm¬,À9É´ ¨J(+à´4C ^ø܀РÌË pç@_¹ø‹Ñ:˜ø>ÈýÏÇßmèù&Œã…pŽèùF:”EŠši³{Š3 Ð£Úy|ö“’Rv.‡Tú0ƒ¤Ðø2'æ¨W„}lzð0jR*T8ÎôjÀú¹é]õ€#/ù#/j~ ÆZ!ž5%«8¤C±!®•Hö{C³RO7©@ˆ¤ß¾»™èb€ãjâI†fwh¸ö¾B©/ 0YJào66â9U„#/ЊBaPÒ1ë"ËØ‘¤!´ë–·Ózøè>z€ïGÆzzéU#+Ð#+Cj¦*¸[<1iùK¸;# 켑K„(¸c®yêJÃýmK“pU|¼ü·¯ •ßhô`oƒ#/óR:~šuy‡)Ê„Ávç±®©T#JÁ-°ÐŠÒ9¤E–"IÖ¬]óH$‘Ìtëù‚¡qµ²¶’O衊ϬÐüü*’Á¦M”P1vE*¡ÔaX*˜'U#+ˆ@χ‚µÉ Ñ(ñŽo*cé€×Ž½ß®§ÄÊ&Zê¡·Ê:fM‰$5Ü8!þ´'K¥R.8˜#/‡`#+ˆˆ l™€£ùS<`˜êGš>ãÓ´ÂH')b!T’‹‡7-ͶÁœG óhŠRfÖ«@e`¥)(Ò¡B@X‡cE<1‚x4LšÒr ls†¶)F˃†ÑIƒZ1?å¸Ç éE@LÇCÀ@ÌÔI-D \•0Â4”pŒËTTKAkbƒNU4´µrNr¶9¦9&¬c;P†¢´êHé8E<ïGƒÃº`§&Œ…˜i#5±¹Ç\Øâ á $µ¥H°L1œ#!¡4#/‡F‹r#/G#5’%æÍ+‰BÔ[ò9Ń"¡p¦‚•Ä‚œ#+)#5¦¢BlÿW¯áM´!AüP<š#5…@>å>ÉOíØx„ö J0v '(B‘((òíúÇöx€ú 8#+9ÞCªPC!¡)UúCàýPÄ:#ÎtO‚/Ð8…ðå&Ä#+¹`ÁБ#/LJ¡{Y¥6TP÷„ y›«ÂjeùN>½¿iAô*#éÜ/4óçþŽ*h´db÷…Ô¶t)ú.E–B쯴9rí0þ.n;$‚'”<§ jaʳ4>)GxQP¿°+öàæ}ø·BÉÕ(Ò§äê:Ä~½ô|RŠ€sùp@Í–“ôpþñÁòüðK÷À5܈zß‹ó!ÁÚE"NÖˆ¢f@#5Ff”¤Q(S|zÅkS’ ÓÕb!ç*”#/j¡ T¡AhEÖA¨M(¬|ß@ P ÉÉÈØÙŒ6§{˜½éš ‰qÖ:ÖP¡T¦€d$@(Š€™èAÔæ{ÿ'£~…(DEA‚PªÒ!J”4´´ˆ"1#+A)H”@ÀÓTH4ž-¡¥ €ÜÖ'Ù×'Û9"ðçd/tý·9Ÿf<ƒµ+ÚDRS°œÆÜŒ”ÓQ#+Lda—ª JÌŒÒ Âzh>AÃ\Á›F$ €•}[š1æÀÕp–ÐS¯8`åN-“+LDNØì¢Àš^”IH<74O 4ÀÎÉËÂ$Ö@ЉèŽO$#5[hnÂã›KISQËä|! % Rìé7 Lš‡p*àIÂyÉÁlº#çŒÂûs®$ü‚äÞÛ»˜¥ÒžOôþüßaÕôBzÙJEÒh" ˜G»ËkLLAJÐQï„'|߬#+áÙ ¡B=á¤&ÆuøAý>räƒ^×Èàk…­Œ@Á@|Þñ&B¨"JÇddâ8#/8®ùJô Ñ€À²‰=øLŠH@>ÀÒiN´ÞŽTþÝÄ6ЉÅ4ž”ŠS¸ŒÉ"€L1^HÿˆJļ]àCðYôwhEŠ‡àd5º+Âöf,wLƒ¾6ÁP_P#KÈQ}Cê@îÂ:‹Ä °æçÙýþe÷\ƒ‰Ü$ IÐÚ#/±Ê);€¾#+#+d&f‚šX$’¢I )RŠX%`P€ˆ`„"‘ #+ R‚ªB]»E,Ý:ýu#+í@L S1LZbR û%LMCÀ•¬ˆ#+  f†RdP‚")€Ý;·¸‡#Š¢§úÒ]k»©û¼MôŽòeòÛ®OTQÝ/x»Ê4âsjžîœ#/"í=8Ïû›Ûb_ùƒ*­=áà«Ïòç;{#/ˆ­PBðÆŒ÷£Ø}õ<ÿm(Çú¾`éßv3²)ÀOßiüÜÀh*!"þ3äGñ@ò“yL@X…¾€¶#ZºJ,‰66dO+p hì j’$Ä#+ÁÓÖçv9yˆÁt‰ô¿—q~%<9½Brˆ¤™š{"Õþד¦÷Àü##/-žgõ¸× Hë=#+'¢äD #GÌaUÇÕÅJcd½¢1É“°.zJ¦¿Lqƒ½,j–¨ ªŠó½8wû¦#/SAØ1à#Ãj#5#ü÷¡á,0Q*Uµ2ÐÉUD@5”ÑPD]…1=óÏ–èöóŠæÙN\¼ßÙÚæB£c2†¦•²ûä ¤@C^a9RyܳK+È$‚¨)ü8dNã/†çuÃjb¤A•(Ø@Kƒåw›ÖQ#/¦5xýsÆ16€bB #5F: T¿ÃèQs)æ1ñ&´®à‚ EÝ¡AŒ4i¥P†`àq—߃ç0º“Iò®:GKF%¡#5}˜u\/îă}v#5H ‚…$ŽÀdi(B…¤Ào\4Ä:’!¤ Á£cßPW#5ä]c!º@ÄV",€ "p·îÓ®x™°ò#/ëVìe‰Phf†#/ i}³!º3xÉéN¼]C¨sÜÄ!šl6˜A¶J+@#5ÔrÏû¨FM¿âÐcn‚Rb'l¶HÛO#5Ž¹ÊQtB÷èW$BB•Qƒf›¼Öh Ž°ëîrScž ÙMèŒP1ò` c1ãÄnã;@dNxÐP‡¬R›™ ¤ üSL>Xa$Á.e2&óUñ'ìv¶ÁûOÙÞ¡m#/Ž]:ÙªDœ˜°DLaÜÇ—#5i—Ìùæ4Z v.ðF}Š)¦DŠŠÓ‘­î«kv¶0$#+Àdq„z§ßåÐ&ÜàîE&'@é°pW^ïŸÖ79ïù0]w¤ƒ£JŸ“GÓ =àHaR!E ÑE#+b‚yòûxj>#USHˆ²Ñ £E6$¨´e ãm˜‰T%#+%—0LãåëãèÍä€@4Ȳ)ý£É9"SE0b‚Ú1—8'«§Š†Ò/xŽ…Á\ž¤±B£œ8÷ëÓT‘MS¥¸)G@w”JˆP)TœWÄð„¶K݉Ü=Û•ÞœçN.Â'(õ§Ùûõ¥Þ‘‚ƒíÊÌÌ@Ù¢èÍÞ`þät;ön/ëŸU<#O>0. ªT!‚#5š¹ŒÕG79¢*‰‚àÙå‰ÒVªŠLk&Øt?Ë#€„óçñm?+·0ƒTµaMÝ–[ŸtËÀêKã§ROôÛQ”Ê' ô΃Sæ q4G–¶j”èr<ƒ^ê’X.A’§öóEû!Nˆ’JIì–œ“aÂ’ì2É‘ýúŤŠñáÞ?Z*ž5EQ!OÏŠ)ˆûL#ׂ™?»# ÆJ!ÈPh¡Ñ’Q)O¬‚ Ž=ĦUM‰JD #/ì“æ.…¤PÁ¿´Iàâtõž#͵xG ÷EŒ)Z-„ªh±)5Â}ˆtÊ’(ªš#5A”ãËžud‡‡þÝÿäý@Ô’\wàÕ#+xÄÝ'Á¨À_Ì" £Z¥Š†¨4’’0}ŸµiSÑ8“2i#/ËàÛaJp¡úü>}=Á}ÂLH9@$˜ŸiƒÖ’[oêÂ0$!ÎOΓÍ(6º†<_sûÎØÛ™i—Yõ}cXÌkI6Ë{¸ È”|½ðs¢%씩oTÓËø‘c1ÆÚrj³ô3q…Q·‘Iü‰„LmµE¯ÅaÓƳD…f˜ìFiè¸S!‡f0Z£!Fr¥;ê*† íŠ(KíÃÈæit‡ NÀî¡ ;ö­ß.ÃIÏ:…9ÅÃÐkÁÃTcv6ÇŠÌ‘d‡¸ q ëFâü²‡d¼¾d“MÇ' ñ„9›‘­&Úz­,Å0mÁ QÆõɹ+Äô`*c„œ¸&øÆF~èþïhþÿŠC¨}$ŤÔB H û¿yÜ?tLbë0}|Wp«œÑy•1öf«X‘bØv{,MD>¿Ôº#/­ÉDû“óàLç³qñhË*ÄÛˆ§£)ËH:L6rɘ_oìÁøø½?yµ+ñD¤s´4[áßñðš…ùï…‚j&…ý)ï¶&.I3Ê3 cÙ#/mƒs¼ìÎ`› ^s´“¾C=ŒG#xéI™Zß”å™bãÓUÏäËfÑýí*q–Âûa¥måù™›,Pï险‚y­)« ‡W š]åE4 bÕUå–ØÕʉ;ã,"ÞY7Ñ‹›Ï[Ð ©sœUœœÚ˜×‰¢”¶^¯ÓÝ-…‡9}8šSØÎl i6s)™ÊÎÐØ4~cöˆÉ(ßS#+:Æv±}4*·%g>F_íð”Ãz4È IÁ9Ùµ“ ŽÏf÷mÉÓÈؼÇ.CŠ#›}·U¤†‘:1†¡¹°ÑØõ¬F6–˜ôI¥"y¡¥ä|!ݱ5œÒ©ž²ûš‡ûš1Ô±(6¢x–Î(Ú.ŠÆø)™U“ª&ÞwÉ|½1´Qúë¦ÍöÓEeý¿¾ú—S€ÀTÐý2ƒ$õgì²¥8A­1ã nùù“4‹Ü’1ÀÇÎýIŠtç0íkèÎìH*ÚýSÏýZÞåà3¿ñ ÀFX̉–¬›+Øæ4#5á€8m|pÓw—”c¢¾./«×ÎÒý#+“'FÖ²™“i#c@ÁW¯„ãBpKƒ…»µ8{Ç! ÞQXÒY-š“=‰±©¤I±K6ƒ°Y#/5z\ú¦†ƒ°†üwÄ@¸ˆw”3Ùo¶S»vÙÈ…$ë¯?I»ð™BGëúSb a<"!îaÍ9Ùýu_Üÿ[”m­yôñ< aHt#5Õ”›ÓÏφ„îr‰êá€D¡#+ èùeþr\DÇÄ°ÆÚÃ0*á˜ðìy½aÉ>'Po!ID'†…6&HßAÒ^;Ñ׿I>ÿ–LÞŒÀB˜ÄR:‚SþÞ[.ÍÝ—$jU¨,#/>¬ú÷q/>Köù¾•€<‹c@å* *•gÐ D¤½l4,3Æ•¬Gð>Yì߇$eJåŽ"H}ÍWI3ýÖ8£è¾ÕÇü¤œ »#>å¼)óYSwÜŒ¥yÏÙ‘óõígÏ/î”"u¾{ÖW QÆvV#/¹R,%¬hÏ™‘g†ƒùšÞöU +d’kzÕgÕé0aÁÛÊdh•ÆÂôŠ4¼äGe8lƒíhï« hb#5<`#/)¡ì’¾Hvó•ú}æ3!ÍSä­ŽI^o‹Æmo\lÆë=ïÐ~Ù#å畲²j'Å1Ù”ÊU##t†{ȹܳki®Û®)FR¸7¸ææ×&ƒáXfÉHæ€Ø@­òÖÊ™&:Øè×7]¾ú^4†TÉ…fo˜SCØPш÷ íùÎQÏ}*ÒI#5†A§JH"ãxq’"o‹0’4ØÙâV†t¾}LÞÔ¹‹"7Lm}ÛK²ùËûlô›ß 3Z#+Žšg0ijföZQS”:ÛNcmÆ2R@ÂLåŠÃÒ·•ç\ƒ4šÑOO—CYÎéN”*Æ7Õ#> xýfÉITžþ68¥°vZI¡#òu‚*‹ÓÏ_–bË­[e-•µB³œ§QÇÏ3ÙROñ²#+ ©¾ÆåF?-ög/?;—ä÷=#/Ïo·ö¹ýtlÓõ—ü©ÈÐ>Ö`„_ÌB ”dp”’5ƒ4Åo(m#+ƒëOrûáJ¿²D¬\¿ÉÓÒ?ÊÊ:p¯Æ—#+ ŠªQ†P((Z#5@(`÷ÈdDËi3nÒÚ× (>Êq@ì×ÎG“}^;©í”+à55ä†ó¨#5JN€ÑPÓ^ˆÀ½õÂz¥É ïóG‹3äÇ™ÕÔéЖY¥ý‹_9õ˜h<Ý#+!¯Ô|#5íQ({ tàMox[dïE®ë©ÄòÓNcrH,øžžRIœO(J;ÃódïBz»?xˆíœÌ “IþÎûoݪ<Ôñù‚‡y<äã"¥Ù²£¶‹FÂDºQü9€ÆPdbõ'O=xËØ`òuFrŪ£M~™¶ì2­ñ4þ’SШqŽÀìó¤¦$öæ?Á“».ƒ˜ E"ÇÐÂ`¿7#+ÅZÔFÀR>zª"*ªTŒ•BÈ«9ê{¿’þþ©^=ÞFM7Ç‚=äúÑ"ô©±‡#¿¼^}#/ŸJn}Ûƒ¿ÑÌÀ{ÛNÙŠìí´Á\ýÍ)0ÃDcs˶ùÅ‚ÉØ8²Õ#+˜¨WøJ&ÏñóÎ|>µÀÄ3é‡äììäŸ1TDÊ(¼Â5ÄÂÒ'%&9¤@Àð2x ò¼Í÷2>̉׀oï<¬9Ã꼇't tÉF#/F¢Cøö@âÿ^#´ ô+ù=:¶!¤»\Ó¡qƒ®ÆÆ$øö2–ÆO®qÀÇdáv¶ÏâÕŽ@%b±‹—¬YTù0öôu£Æõ9RÌFýGxT²Sññ¢yÚŒ—îÁ}ÝX§ïr~ýkCÍ\'š €†;ö/Å–YöqÚK‡Ì'Ô„èÃÚÀ_ó{|ô7 õó‹#5Ë#/3ŠG ߃+hc19ĘÜò;€†Ò]2"ƒ3ŒÞ`ЙMjkßûyÇ×¼<Œ‡¿“­ÄPuHøí’C‚jÀ¡Þp:2¸q8‰ï*—>vôš L: ç#çÙš!¤¥+¬Û„1.Ó'äÝD$npnù^&:C×t.gå’v‡F÷çÂL­B?_‰gn ¶6 hi¡ú>_2‚êÎÿ’¡B°&ü½¤û߇âLhï"¨nû3rÖ5û³që­xÅ =N¨Ì¬—tRu©ŒCkhlo˜ l“,é!¨k~Ì4L1A|€†¨’³<öômÜjÀÃ1®yÅyyŽvÛªr"XÆ";j~œ#+£§`Äs&J!#½?£€!Ø£÷úsÍkvZnb30Eð@×5'©‚}*~/O|ÉœžÃáÒOé“íGç_Œn4Væ¹Äyki5D¶]%ÂË0Si4t ¨‘¢4nçIÍ)U%aI•%”%!•B€`=|ÇÇ‚[嶪–å'¢MP’y¯#+_>O¬^k?O Ñ­B@Ýbœ¨)š"™(ƒF‰"‚ª™‰ˆ"v’1DÃ<â„’D%üÿÖþÞAïZgÚÑþ]¨¤ý[ZÐSQ5UTJS0Q!U2MML”AD%4TS%L“ECSQ#/ITD-AQÍJ~ ÓzáäôNĦŠ† B|¥‚ÃvŠ‚¨üXÍý#+$ £¯¬ ¾ãëHC+vÀê®Öm”£+[‘cˆºZzZƒ[· üX©Å@|ŸR§ß}z¸¿TyÄíÍËßÍ+ˆÇÓ[‡?¨­¿[; †štº8°róëFÚ'f(¯èèÎœQóÒzÈ<ÈÄ¿]h,›·Ã=F‚w!FᎺ ‚ȃ^}µÚ¸ÞòÖkJš MæÂ8®UŒ½Ï2}ÂA&Á‰}Z¨.ž¬°ÄòègAãcã"mõ!4¤HIL?JCöœéšÜ£³C#+¢ Ž?†ª]5rhäI#ï’=">ÄöþÛÓ™‹{dL4æòkL1æ¥qHcßò­-¼ez«ZFÙÃŒ™`°q0 f©F#/‡ë{¦ê“p¦8þ·Ë§=%Φ-k˜û]>“M¹Ì/ûìÿFiš" 6íÛ0`¦IR’ñ!„KYl1pŽE3o„Èœ…;vÀ[&¨ÅlEˆ|ùIË<Ç=RiaJ•¡– ÆŒÆ#/ƒmC:H†pôèÅ°ØÓi6°²K¼PÆÆô‚‘eyécŒ?K1Y6E¯ŒÔ>¢#5 PC º-ÐÂ2Kc˜D#/\|L¢°ÓÌn#/n“SA5ÄS™âA€„Û|ù˜n£”›.NAÛæ¡2IšbÑ© -NAC5³™úÝ•¤ð“Zå<ƒ'$ffjhh쎨$)"*aZI&"‚—¶ˆ)>ØÓç1ÝÝÎùnbï6AÍ]‘õÓc15ý¬º8VÅ(O`öƒ¤)¢v_ˆï7I¡(H)}¦*9Áï )@±°ðÜ8»˜¹€ªäÝŽqX¹°Lt€] Q’Ýl놵ÌUµcZ%æpÒDgf2É¢±²ì`®»ç®ô¼+ð-$ÓHMG m¹‰V{DúÞî c9 òåä÷.¨ ¦#T;.œµ)ÙyÍÌã{ÕyÃ":B•/0<æЇ¯mP…V‘ÒPÑ@ó뜜""ˆõ8äª\qÜF³gJí­ÏF¦Î¹ L[@(JdÑ¿{=P‘>ç1À áyR‰ÛËI>l>7™5B¨ÒA©Í$´)A'%B±ÈÍG&¹qB`õ2`©¢#5"()îÌQ@ECBÛi#5!© "Š(6q5LÔÅ_HÌÐ% “¼†ª;ñ¸n{õLDÇlÃD·.œÐíïp®Î®FRrAWÔ!„œ#5¡u°×˜ukœÄÝùž€”Å!#+›¡6iÖøã‡Þ„8“„™EôN]Å$$lmÈÇΉ§dÁQŒ1Óà ¯å©¢•hveŸ8Ï#<¡7AÞ‘LG¬É Ë':Aq¡#/9ÎOŨV¹+6qŽŠÃEé¨ø–Q£È}¢¨0ÚÐÐyµ†Ì(ó›Û]à4‡%0ZS²'$h")'žUç"|Ø<Œ\«3T\ÜVÄLÎ6äè>ø«ÖxrrWO·Ùw1B÷‡Š²ÑŒD!î{ÊrSLJ¢Á@L6šcŽCXÊö9Í”õ›EbBáãÑDøW.– ŽaçCxU– ÎmµžO;»½j¹ÊÕtEõxÙð$Âê8ÙÑl‹ÀåŽE·b#K€¤´²ÃBàiy°,WcL’•T¨èÒÐÐ2I“¼åác'[Rií©-Û7 „p¹™Œ{!FDÖ¤Àmm¹$kL˜Wyðcm˜=K5‡ê鼨iÙŠŽÍ2õ3 :'»©eHQ*š$!/l{[YK˜=w‡Jryi9#/W3.Pt¥ÈJá&.#57cm0w/tÄŽxšçDÊ‚¦æ#/dà&`#(ø“—!kd#%æ4—†OW‘çÌTva&,q2Y+%›X¬ñpkLŠ'®bÃ#+Œ‡¸6L´í ^žî#/”cÍÇ>ZGLsCb£8aÁ&D,+)âI" È”Œb6òÄ›ƒ@£Z—’ŒJGp•€6 ˆìŠFa$å5–´4™«dc(˜˜ðX¨VóDPfÉ#+Á‘T(D0ØÐL‘Ó6?$Íë)m4HpÅB¦ˆ™«óóúÒBG€Ø@#/Cu®0ÃÙâ*`(Ö& …(UY7¹(Ò&‡¤âä'Å å 4éj¸âo.ÔCµKAµ¡“}óhª({ A¥ÐÌæDäŒZ8c„ d1 {'¯YCyŠ)¿-[$M›jÕcfÍëOº]ã\È>?#/çL•tá\\œ™‘V¨Ž°d4Ât3ƒ„DØѺmæ j…R¨2c0báH˜Ñ„ˆ,P ¦¥{u¤{÷‘#/Ìk”å°ìzˆz™Žó€ï•Φ†dAÒvA#5-‚*‰ ˆD"EC1ò:pðÉ{âÁèqƒ€_Œ¨ãé €Õl2œ#<Ô×IX@C†¼gÒ $RÊZp;d؉–$ôï ^ùÁN àéùßÖœ#+•t©ûß'ÙG²,FÑ¡ Ü­ªvÇ"ÿ7 MS›¸1ôãQtÑ[6Ñ?€Ë_{ÀRëêÀ^Ð(B& ¤”h )T –¢$a`)B€#5(!™A¥!–iD)B¥ªY ™€(ÞÄ4©ý$pM! JÄDEHl*¿¬N@èÐôÉÿ7ôtŒS©ON¡§°çÊåñð´vˆ>º”)ŽÛ¨€È"Q0/Ø÷¿s è›ë5‚™;à#/_ÿ )áP!KA8M#+b#/fØÐP ˆO”#5C©¥E1R¦$JoqþÉñ+'}÷üæ"èé_ÄØI9èËþºmZül«1Ùïs¡4weáýŽ˜WËéDz ü¿]fq6#+î@î$He(ˆJ)™‚dB!Dª(¥@¡"A–db@ \’€‰(=y˜ã¾lŸcÜxÖFG#5d‡Æ$š›®Ÿn˜9¿;Ñ”¨&Õª¤¼Éªh‘€JCïÉý †Q~š»‹ eá ‹%ƒ™FѦO`Á=­•%Ma1” ˆ·aÛÙÝc—Wpœ~¸0NJznrð-£“Ë•¡‹›†¼#5”Žó.âïqÙuÞ8Õ>HvéZ Èì!ë ä.Feï3Ä2NæwlöˆŒsž](àA55ŽQ£»rlp>;Õ©;¥£o\tÒDÑHð2€zhæ!­±QHöv8I;ÓPÜäå‹w‡=xi*ÖSßfæ7Cs:?ªýozsÉx‡ê¾æ]RaO À79šæAÁ'Pþ3C£¨6`ÑŒ:š ¸ÈRüϧÑ<éàý=¿¿W¡Ú-lÚØ_ŒÁfÖ˜I‹Bµ¼"÷S9‡+QúHà“ddrÐÕ·r¾BFÍ$´”H·g‘ky×MÄÅQúÛaÑ0M$„§²ŽËCC#m±¨*­øŸ“ߟ‰ŠƒãõŠ#5¦&É¥¿pÖØ‘–¼NãèÛ8yñ 4Ï^·OÌ(|Ø°'÷b(¨bI©¤¢¨Ty:ˆ¢¥f‚†ŠM«jp_VQÀQ^E"_²™VhŒA§/¼#5%z4g·WTsuûœ8اÐOÎûª†© #/$xC$PñÖª°ç'µ¥ä¹_Dˆú>‹Kžã›ÍÞÆ݃Mµ#5};®ÄÉp‘n£F†Ñq‘ä­&ŒÕ2][Ñš$#5‘Û͆Àj‰Ï¡­=ùðÓÈþ®¿mû~âf«ˆ *†ÌUTÒàÎܵ­bóúд`Ãí0ÆïúóWm‚£ßóëÈàûýÎlOÊ@q¢¨íýwÀìÏ‹Ûî´†–=S‹æ¾ËÝRÛ@gGË¥=U*_™Ž#5õZ#à¼h·zðJa×áì{p£tÜ#+©Ñ$üã Í|k~p¤òà™_Ú} Àç¹_8qF‹h³[I ¶„¡±…Ì! q3ãù“Ð?ˆmºÊXCà‰‰1Mu¨h ±PîLª|*#5°P2oxOYOÑññ¹ÉNÊè@þ/ß“J~‰²c·ß͹¯,«~ë´n–Mf¥ ØÃ"Š¨Pdˆœº°ŽÎå@ïµë„ÂÊOw‡ÜXL>ÞZ•‡+sÒN:Z$…%=ë’ÇR·¦C‘äeP'~nB’õë#5¿£Åý?Ç„ìní ö€u«Fý9›xœÃT54½/ÕVÅW2=,`¦°uöM.RF(–P==¥;+¤(ZAù脉Jšh)¤#5#/kÞ™AÓÚ¿Þcü<W#5Ú¢/2˜‚m  Š6]@ÅG¯îdä!ØÓáí¥t8%Ĥt<€áBh—‘¹Î•}œ©NÊдµ¤Ò˜”Ò¡¡1ÅHÒéH€t¬C£Zÿ3à#5òF"^(£ _È€CÝ|}fÆ!3ôg—V<à#ë ÀتKé}sA¹zyf§€¶àqŒzß8NÏ«>sæ#©#/ ´$>D»§Ë‚x@tƒ’(b?¢¢õÈ‹#+û¬>ÂG€òÁ˜#+üüGiNrVĹ83¨X¦@ˆi0ñx™†O_ÙÓƒòºó8[Ÿ‡ïtêÔXŠ¯ŒdPkZÑW –ŠÐ‘JÒ+RòhÇÒJ>#5!æS)ITŒ!Ò â &wú·ïáø~³´%ýd•@!-ß®U»û?I›¨Uÿc#/¸:ž°,Še ) )¤ Øð€˜Ÿ¤TÐû†•Ô ™¦ä›iy}3D†#+ ]¤Ñ"æHO·r5ò°I#p#`ž"€þ“ …Z="VA!„˜ý¡0C7¬)i V…a P9¦…i#5iP)#5í-@ BþŸ¨‡h"àtGdu ì'^‚…JŠ$š)=Ejï‡S jRCÞB z*!"¤¤Q%¦šI”Bi…"(Z#+!A‰Z˜¡ b&F…F#+iQýÓØz=¯à¢€lÔ¨½i·V‘Cü©ÿö*žŸoìÖ!Ôû£_¯ôCOñ¶ì8C>—­”Ýž kèMp#5k˜v0>ìÓá•,Œülš8»È{½~è2 €È¢ÀðAy#+œB—áÉ·ò6”8ñ‡Þ£þ`%¡) !XA=þ㿹7™´½µR4 P4<ÈÏ>¡Þ(š}G¤è}Ýg Äüpxv$üÀõ@‡B)#+ ( |Éûò+®PM"iP?”$&YBH†bj&" ¨*†š’’%¥’¥‚”) ‰ˆ‚*€"&‚( €Z@–ª”„" iH¨IF•#5”‰†±,k¹Á§a•C-rA¤ÛŽFírð”Ã#/¸r·8‚‚á¶ÄFy·#NÜG›%MU¬P:Ç$Ç Ý³Øë''wœ#5Ž]…äp“G‚g˜Ñ\œikZ¿EÓ¦;SA&»Ç¼ ãŒS!GZ!áHåñ¬auiæìFå¶åÊ ¢ì`;Ò¸s’Æå4¨Ž7/6Ü!®=ÉÊᘶ'F{2F´è6CÇ")§†G›œÑçCMxÏ‘Ž ˆa#5‚œH°Á¶Ei¤H–Ä"HŠ#+)†ýÃÒ Üøéj.¥ˆíaÛ&M"€Ë¬Ã#+W÷«Û‡B„¨ž”Lس`;pjd$¸¤ý=QB„ |Ö Eób›#+y'ïÀA:L;mh¯°ñ¡Tf;:pq$?"ùúÌSÜ£êÊÖƒA¢¥© Š’ˆÛSIl5ƒ<¬‘clîl4lïÜç°†écEm9$±µÌ"ÖJŠ¤8ìÿo›¯u›Æs&ž5‚ÎŽh¤ÃL‘=Á PÄÁ5L\,EæÁ1LQ3DÁÀBR€R”€¡S$ëFŠ(Bc ñxK;!㨗ð–÷¤–;”ÿÃ_±#/ßXó=1“-UÕbn™òÍ~O,å•eH8,Ì…æúvÙ£³Öå¾?k6·¨¨i©ˆwÇéÚ#5d“OÍOèýø¾¡ãoöCç¶åHƒ{­£¥y{Mi>Ry“U5/0Ýb™$0$ ¤ØuOý;F¡ñ¥yÔäÉþÎre±â3(Œ<è .c‰u¥>\ºr’‚ñ×,éûg«Ã>.u$]&»_fM4¬îQè÷jÈq»‘4ê™å°ôŠ@¢$g%©ØäTLªæ:åþÍžëYŸù/{\“샺ÂL»f.}÷ö­Õ-×#ùzÞbMÔâ5ÅÕðÍ~ŒÓ9Ì%„’‡¾ÑO‡g«#/÷v©vtmùÛšÎ$_=ž“K&óëêCsÔ÷ˆ!n§«ÐÕo‰}úTi úÅêq2ùJ^ê7•bs—•œ»êã6æÉ^³/ÝǪE1è¦]þVÄü½/d.~[IÛŽÞ—£†áwÁ祥Ô!î‡/¸çN;|j&^f3-©§®ê-Öõׇ[œ#/¬\Á#5\o Ž“Qü„9뻤 Ò¹¬ÞL ðvÕ¡ß±+/ÅŦ]8ñàmÌ|ÔäÆpvø;UÊ#/±´J=ÑráÊ MJUÃŽ›9±Ê$'i€ÁP£e„//i% ÒB³ß dU©âAêvŸ=Ç*ER× õh®ë2YÔ‹6£.Uy¥ ´°ÌˆPâs/}~Ê™Ugû½ö¿‡ï,û»¶Ë6>“w9œB—í[IŠá/‡VG‹´gÆÒÓŠN°™ “ “Í:|¾ýÚò¤œ»ì>)U–UraÒ!øÜÏ^–o¦}-ÈO§vÜÜbÌÊk²ßâÂ/ŽôYNÏWxí¨zv7TTk4#5ßL dïóË=i÷SnâÕMe链k/²ãCå3e_8l>[)Æßv²*ˆ{Qàß-½ºŽ.ïñÛ–®íÐ:‡Óµx®é®7æütKIžË>ž`JBÌ`í·pÙeüZûoLr·¸8#M6.¸¾>g1l¹7rtoôÅÖ>äzß-ØÉ–õÝÏ;m/åÞø¾ñª˜ÑÜ×åY¼{kW¤dÙg2=êmŸH©õ£Ï\“‚#5§ÒÂKÒAË}(«¿5í,ç°zóÀð¯×°;pµ/öÄ5³ÃŒ$=|·¶ò¶3.âŒàÝ%`Á#+ïj4”¦;åÙÌqÀÏS["“&6¡)|³.nâo{Ñu„è჉·fó¡,j•›s¼ã‰·Äv¨—5õæ0é…÷ 7µJM3&cÒf¥ÓbåBæì„=C%IŒÖ-K»’½ÊÄõª kÅ%,ù£Žlì?O¼ÜÔx8#ž"‡”$iʧLä¾<¸Ü+Œí&:|¦VòÝ&UŒ+1Tä¦<øjiâšÝÓzvt̺”ÙÚp˾åßY5#fd«‹ÆjêY]e)o#5aüÜÖ „ …8åÓÃNZ‹š»¤¯g±.1IÜtò鑬;i]½=ÂÕ\Ò4Ù8ƒ«[ªK±š‘æˉ§¶;yάí?¾*+óQ¼M')‹CV¤ÊÞÌGתtÙmsuI¸z#bv+8xekyM"D@•,gaΑ&1jÞ»ø]ì×n)ÆÈFRE{{hÞ¹D›u·çoL{¦û¡lÉ»#DqÖŽï×dZ÷Änû·Å66œ|ª¥SÊÊÂ4ÚÈgLètÑ ºá8³¹CR«‰i¯)›Ý·ÖÂd_KÉQ ù»Ïu²-”›w#¤Û*çg†½ŸKŠÊÊG)q|SúS‘應OÄiÈžvÙJcÎN˜Épr´wSc‘ –’fm¥$áÙs}µˆÒ„…²òDxs¶._͠ĉÇB#Ô}•³¿º”Æ=__eЃ`1{$|v¥¸›â`ÀïºÅT$2¤œ##+=hffÙ#+• ‘&#+‘NËW­Ǧ1Šš e6[eÎÚ⤩Pp°£ÒX¨*Ròâm­Í¢èºò<¦Uè½ú²{Kˆ9݇r¡qüîÈYÚh È8Î$EfÃyPp8¿‹b,0«¥„:8ãTÉÓÅA†i>)ÙA$‘#5Œ¢Ú´}ñ{D6q’`"˜q3}[tk® ›(dPÁªÂ²žIÒà_Ý{¢…‘æ›ô7ÖŽ'#5Ö_™œ9h„ÙM.ícd«YÒ±wΛ}FQ[(T’)\¡ü˜«XჟMQr"Ê—.Š5ʲÌm’àp.ŠyTEfÚÚè0çÌeRIšŸ ®jº<¤g„lúÛGZÅ=¥ç I‚+:2BäÓv¦ÕÅ:ióÉÏ‹j¯kßШ.2æ'çç®ö! Œ8Ð,2RVãr3Í”y em¶Ó$ŒÜâÞ«Üš¼êðíbOó·:Èá¹­˜šha& qLÛ Î¶Ô´1nÐ6Zæû›È¹×#/Û-\¢M³U:ÖÐ0iVŽ'²å™¢–u^¼¤#¯æ(5)ÉÊS7¯SoÓì”Ô­NðäÚæ_ÌmhºÏœÔ5"ªû”c2cž~õϾ#/jµÊW܉*y$Ôór`›x Íã©êlá>Ë1D$®øñ¬yÖ–dÇTH„Ÿ‡ЩÓ.™ØuŤç,㎌m´l¤=ÙV„g–ç]öÁ? ¨”ó¬geK²yw£ýQW_~°-°ìc•üy+Þ½xÃzv;üT,*×O‡²_cÛ´×lLÌ9|üÖUxu‚5 ¸Ñ-©°­ïÇ5: ¾Ì*Tºõ³Ø$˜¶æ“)ˆº&+œ*íÏmÏ#/¾¶T6¬´s­²­8;ó]gn %ûÆs1•ñÄUêÃŒpA÷ï#{SÝ…„ðÑö–½²˜«ä¯¹*û¨þÂLÒ¡gE…1‹ÌÄ£æšEN&Ÿy‚ÝýQy5ânž™Å«¾/’œ¯£I˜Û—¶¥[Åí½óU¹£=\³ÎÝNajÿW3+µ¼m÷â:óŸ#{'òãžÉ‡]`„ö=¿+Ì|3E1=¹¿D7¾¶pîô¶7( ˆ‘£:ñÒÞg7!e#BXÆæš¹yw‘îƉæ(ËãIñ3 áàUP³µVÔü ù6Üð,†'o0:ËÝi|0úfÇ9l«¥éË(†°áê2Š+"câ_á ù 5ßÛh¦õ^½9=á‡H}9ÔúùÃbÌ91§l¨sŠCK%ÏŠ©=ÏyŸ?f:®ùãÆP$TKA³›À³„ç,–ÛUMvÕøÔlu£XLÙ8}«KÃñÓ»x¾˜n~µ=ÆCÇY#/3¯J¤ãdžk&è쌞ßöùËï?h_Cˆ³ŸÈçÏŸmà Slµ(éA«§‡dz„ìŸ>±cÆqdDaašvPˆt–#/<çÓ6Éє೶ñtè˜ã3#/ºÃ¹vÚÎqú]Bî¡ëGwG`¤ãÀ#žÜ#+Ñ ‰ *×Ó´°;-Àø®¨YÏXÏdtäí)O„ÍáŽÝïÁëò=qfÈq°‚UdqELÔ×–…Š±ŠGr¦–P"oÂÝ#/#ÝÚ« ׊Vñ”¼wôHhÄ>£¼ã¡_ GÃgZ\´=h:‚\m¸†”õî ú4©Æ1G‡¼æ#+(g@„2¶†`ÀqÁã˜xn¥û*O/†ä>›È¸|£~õ ¨’÷³Þ*®tóôÁ4ÒH óáË8˜ç.ôÅ_W¢0ŠTD:w4¿UœèÒâ•£b£KxJІb˜ß:U=Ó>ó#/)úÍ8…Îä·©ÝÑvrw“«ÅéTŒmsŽ÷²á­á¢ß7+ºb DÑ.g€¸Ù…qB#+54c–ÊÃJ,I)—©Ê3ÕÜ•[§QéLþøñf6eýÏ2™×0f{Þ4P‡´#+µb#5‘HPÕ%UTÅÔQÇ»«V±&D;S¥ÂtÌ1*–ueHûi“ú& xGƒ_/XÎIÜIþ`“»øÓË^“Ng¥ð)8ô¢¹]‹,¹v.5ædEdzÏâp{½¶šqõQ-ÍQ m¬+›=îëMV<^ûÃÚê?{9Q’øðU­Ò3fp>pú½‚—|Á’›î\“0¨hL¬»`]Ë+&„\áÞ(©>5Så>p~³¾/5Êó¹o¿Œä‰ëdÕ5ãaf½Úÿ.áPæx SXxq=$%‡™á¨bf#+ôŒ7çv1‰¾J©†/6A!‹‡2Â~šf.˱D»òo²Ég|å+ yé¤ä”ó&¥Åv8i°dUC¯¬òócR·I”·É}•†¸™Òs=µHÊ¥6Üò•ÖPU ›€Š‘䊧‚TÄP4¼$QYÄ@Õ‘?Tø{DñÙôLT LS Œ’ÉŠ¦Œ kÜ2Q’ˆ.Nt+’ôÍeB„!ñ~j#5¹>¦­ IœTÂVZ0£ä“L¦#5_'‘¡îòNk®hŽñœ™3Q9%ÙSj-ˆA¹¹@Ë„C¨|Y—+±iÝ'³£È=4}‚g`^2<—ÇAÒŽoQ³ø8‹‹ÀÇÍ2’´2^v½ÎïfÆ[Y'#5òèÞ›7+›´9c²Œ+Pj `äX21šÕÙ²oDkJÊÒR³r“ÓÙØÔd¢SHc¸7½€(G²*(G á%jt8 C—¶O—½Ç˜Ðxñ¯éƒêNJšP>ËÉ@dqÒ©BŽ‘(Ð# Y#/&”6SP ÆbỨ$ʨ˜ Ï qKž ÁŽ'ãÄ0!3ˆ õÓŒO‰%‘¢üÃ$ÓïسÜo9 S²8JFªRò(QƒÅ]ôfž$ðëÆyW˜Å°cËÅI.±9mèÉh^ 6“x„cÅ(~z UË1šO«4Ggš§#/þ"š‰ª(?\è#5i#5 ¢Z”U?Á*R#5P¨šAJ@äéBø-h“üèúIïþxÇF´êØÖ†ÅÎåäEQ jɈÙÑÜQ‹@â&þLvQٹÕ7#5Ç8ïíS×bƒlÆ#5>œ8Ü[ÈÐÝí¤à<„øáƒÉLQbœQ§PAŒñO9²AærJÌöãM·—/3,0 È/骺Êf0g8õÍS#/­´´RéÆΛw‰È¡‚>™ßNäˆ)Iºœ:a–²Ðê¾d15Ù™\m²ó<œÜÏ3’$ƒIåAB¼‚Ÿ+‡ƒm¡-µF’…ÎqGH×®ó‰ ÷ƒŒW6ˆ"aˆ”ÔÆœI&­µ±µO‡xñ);&²ŒQÕçzð6(0RyU%ÃœåÈÏ3ª1€&á1ëXòðÝ£4A#5‡FØÞÌP1"EÂ8ä2HDÑ1',D±Â" çatŒ#/Cä®àuçñ×*"#5i »„¼‚õ…Ä1j‡IÊÙ#5ï ›éh+hl!4m§¾¹­q}ï.` ‚(¨¼0ýüÄ}æâçüÓûm+µ1Žjç,¡rˆ©¦3¡…¬h2(ˆ uª´ÔT©Q6=às§s0DÃ$»¯®p«¤VÆ­`Ë#/Œj¸é‡$¦°ÆS‘ÀnàÌ],É&8rN:† ÔmÆ#/ŽÂÊ¥NC´·ÐÌ?è7]-0iƒžŸ¢Ý8RSŽ¢(¢J˜´cÆ­V€6Ѷœ+UØwäºÐ ßiÖ¨bà$mE³Š"†$¤)‚GBì#:ÎÇ8Ú¬lä„~x\ÉõALç®tâ':o#DDÚÌÍŽn&áí;UZÈű—;_g¾{׆†´O¨Â^—#+bºCUäxpÚÔX±¥ÁϯŒ={“Îhk-8áÌrª†í‡º¶Æ·9žØÒO6(.MK´rᱶ(¨"1cl9™¢(‚^T«¼ƒ²UÁ;ÎsU.Ü‚àè#/·×Q]MÕ#–22¤ò‘VÁ”‘±È)7j]à[ÈH1ÔÓ‹ÑÇEÔAN0};º[4Þƒðô ¡çèعñÌÊ0Ö'y>`oít|ß2'Ù ?Û¶ÑPRQWÈœÕEÃÑrî‰aÉ'Ñš)á]^‹»R"pM¹ü(1ûp9!A$(CŒ±·#+sk³¨$Fƒ0}¦GÊalätèѱ·!>âf®°(ò ®‘¤×‚s%ÌúÎ*änY9¹›rŒCÈÑ^#5N’—æ^]ý@}wa‡ ŸêÃŒ'„›IóÀj^¡é=ÔQ%1Bt0äpCˆ{؆ÁƒEADèÉAUUíÁzmИ‡ãæeWÍ&]ÇPsÀ”êå¦Ïɶ#/…šo«¶q­i—{öb…W×/«¢úÃ^¾R§Ùä¾´uöˆ‡œ#+ Ûøu‚{°³Š¢Jc¾5ðð9‡§B½†£(î-&×æòÇ"paF<ÅõfÆò3 Ó3äE·Å„0»x€sgSI42÷øÃïüTDT(cXB†ÄS`³àþ“z“G²²Ée,±6̪•Æåz{ç7¹ÛŽA£¡¨Æÿb€D#/²5œÌå†uóÝá§Û1Z÷ÊøAKða=·ƒäR|Ö?= î%)õ.½ à÷‡(ø¨ùl'ªîúbb3³6ŪùqG„f¨0,×oÞƤ'aãÙŽô£“›Íü‰Òjø/®¥,*¨DŠŠNú´l>k¡CYPL›š-9+ÜÜDPÛ¼€¢•¢#/S¦…¦Š@8B:J9¦¡Ä»di9Îq7#56Û²ð›„.%0„ƒ”ƒ[aá#5i#5j.H&uˆˆDæ\LÌѦ,ES±ˆƒ #/B¬ŽÁ—ðSò=¸tŠOB~ÿ|ÿ¯V’f#+(›Ðx›ŠK {8<êyî0gÃKú*ß±ßƬÜEX¢Š@¤ LÔSPÍEAQD  $@B´"3(ЀÔЕUU$´…SDTÕ! 5­*ÍI,- T$T"$Á2…#ÒÉ’ÈA!AM50QMQ1A-$‘4RIUM%ERJQKEE4HULEÁ@C¬‘4ÀC²‚æ¤?÷Ñ4;Øs=‡;ÅOŸ¤7 =DØú!éêN¦H{Îó” ÃêõC”€~t“&Þ«„èqcËËÞdßogwv'ê¾±VìÜ1‹|Ç3ÃXÁMpp„0a6B€A¥ç–õ“²ÎD>(ù• 9ê“$ú£Ny^ùۄ9…ÐLˆ(eÅÔØ^ÿ¢ ±ž˜æ¨x!ó…P4°¬›‡Azÿe½¡Ýƒ“xý|#/ŸVfÌ@mŒ29„™ )cŸoê´=5™oYÓ¢æÌ#+‚¥ß >'¦”­—F¦%à?-¥’Jji¨Ø.ø[#5(—5ñWØ8Ñ[auE2˜Ie~äP°>V؟ üÈÞÎoÞQ$ŠŸ™-.¦4}<½Fà|EH2BAb$™qï#5 ¶ù2õ^>‚`Šb"-{Lz¨å“zŒÈ€Æ"\Ü*îBi´ÞññŸw»ä÷ÎO¿~s˧½<øSË2s(x|÷Ç3JŸgVö¼J8*c5FÉ*BD#5_ÜP ä¨PÒL)0E $HÄ£÷ì,dÚˆ)†%¦ˆ@˜‚"Ô˜ÃØ•¶LT&5Б*PS#JT…4LAM4P“4Ð’E1!IAM¶’ŒŠ Z)¿9œŒ)0*d¢X‚”Ö0jBš£Ï–1P ¢)‰ ª$* ¤ ‰Oñ'²¯^ú*£jdª‰ŠJªš”*¦IlGÏ+A,€¸#/´P·ìÙªÒƒùÜfäzi±V*å±Ö ®@ÓŸl˜Þj—íæú°ÇÄ6–Ùˆ­6„ÚXÁDÐDÍdQ3èÅ76à_¯€q4n|ß=ã“ñº]õªIW*TEóÇ3‚œó†c™ÎÄ+-›—”Ë^#/fFõ"̉iÇ ÆÑ´3 ncòÃzdša%4ž$耬BäWŠê =ÚÛc)«#5®òãcÂÚU5³{£D& \fâ¸ßé¦U¢Œmø:À:3I‹š‹U[<ዹ;Ì:Ö®§\¨é1¬KÎÌó>ò òy R‡¦\Œ±bLA~¢¶83l²,UY¶Ã‰¡ªó˜ä”BØÓÙ>‹°IØÄ$KN§ "ûw»­%#]8:ëHoF˜P(1ÀúøUÞR^ PeqÇÑ·!ŒŒxY“ AÚôj:øѾþèblé)‰4§‘Ms<àž§æ';Xˆ"Û.‡=Dà%EU+Éæuh8%²!ÆçlD]ØÏå'ª¤ú)àü‰žˆQ@?#Í°VŸRm‚"\<})ôø¸#"! H8#/$šä´Ï·òi¿Å*ºçZLïDTûèáxŸÉz³º·G¿uñS¢ïtL0päÚT_ŒˆT¤Ý¦Ÿ=O óK4vÁ›Ÿ#ë1ø¿*°×Êö#<{ûÃ#*¯ôÃØ僄¯ÓX¸[£øp4KšÑ *á™ùg³Þy‚bk螃%#¶³Ø#5ÄØ1 ³«Xûö”Ø@'ì«©Õ¿ž"$B6#5 Þô¶rÈuÀV5Z}mB±m;G>±æÜ+Ÿ7àáAF†9}¨‰ù x¸Ý2žoh‚ã)‚ò’bï²áˆå…#"U€r ü`æÁ=þª™` ñ‡Œ/Ç0„¡‡-ŠþCé¨B¨pwᄎÎýýâ*»vÃÜÅcEeŠˆ™˜U¦"&C¼(¨›‚[ñ#/q‚%-³œ1€âãAz'Wy6Þ—.±8Œ? €ðàŸ@=Á²Çi¹¸á%|2°bR»ù†xâg;ÞZ6÷K±}sÃŽW!LHC“Ü&ÀG0q2Qó”ð#+¹ºÕ¶+öòö*R §†¤ˆ ˆ,7 ètìÍé†N9`"€Š©ú> M7fvL %v/~ËI“€}ÝŸ¡Ä>©w“¯‚÷F#+0~¹ý¢RF¡¡X#TÔׇÌJêcm’=Ä£Éô2i+$¤˜ñÇ û3¨ž#5{8i©4çø/Þ«€öMcûÙåÈ4߬áÞê»4”3€œ¡ætN-fëÜŽ±‹@þª‚"©¢šN[fßxKíÓìßò'»Ùªho·‡ÆL3Fœ½¨ªfÛëàm²ö~ž $L¦#/9Ǽolúu›ç8Ë»ª«zf®ËÅ+9Æ€™r­SæÛheÌßlWìv¸l6¶œÔýíÏg¬,9/q“i„6²l]aÇëöNÙ³N§EĈËÉ¡ÛvNî’q#y“«Þk§Á܆ûjW×N.hVql]¦”¥ïÓÇ3QÄÊÕ;ÔqrîÓb‰"œtRáÒâN•Æ,¦n®©ìIgvÄ^.2š}´+Ãé°Ã‹6åoNü øg|¾ èÈâRu<ËÚ܇#/‚ab3ž ´bÜó¨ós•q©žCbf`¤“q“n)ƒwX­Ÿx&Ç:–‘0óƒÝLµÚßc‡Ùß}5³jL š¤lS•¸êÿŸŽ¢ùqÛgœèöÂ|†y¶\Ëó§#/"ôãBŠ hGK€éÎ8å°N§iiÂŒU¹n!ÒÔÆ¢þ\Ðe•nIçÁ¾Éòà0h~“è¨N’dÆQÐÔ™Šû+¦ì;E'È%‚ÈmØ ±1ër`XŠaÛÄD50KÄ]”éŒiæaˬ츉ƒèÂhºyÉÍ#…òeñº=ÀåØ|´6aã™îý÷ÀI–‰–Ô³°â ë;„ÌbN°Îî=*íC½Û²\SÔ‹¼Ô…YjÓ”Âd馪™#/:û9qK3vl¢¦8œ€$ç'‚ìw×2#+`@à{úw½RU{ç;,‡n™=õѸIŒJ4j}©çGdüÕìÓ+D»nºÛ[ÉcêÖ`sgÚu\-CÀÁ–LÂBm¶ÑÑgK‰s©‹„ Ü[h‘*7os×ØÚ™—(δˆ(5%"˜ê±Ä•–´á<0u2H’eîiäÈ¡:̶‚&\³wó}s–©åÇeìÎ’ÚZ0.ðËwjÄ>ÒpÕ†ZÙ¤êcHh#G]“Ë …‘™çu®šè›â«s}M¶¸ILj ¡‘bUÒ:K¸C4¢›œ;ªˆwwL3eT]LGŽ+9ZÊÐåmqy9ç{~9yØ¡ÜŠ¯C¾2³Áƒ×#+ƒ4ÒF˜röïBS“*|>Ì)­6àä‹Pi½^75©¢¥•¡á5¿YDalÕŒ$âÊh–N”ÃT&Gƒk4_uur°He½ù Æ2Éh^±l¸¹œii¯ùq-5¶ÚIy#+˜˜”0i7Ç-Ùg[Á—âö˜ç[Ìâ,£á³Æk”ï›éÕõ©CƒžykI1”‰C}ŸáVX©ˆÂóCZdš¦q¾“RÑ´D#5sÇTàl­fo13&+GUé$ÖŠ1¬Ì£âmÝò…1ñ©‘i®µ©»4må×]m+C¢L¨,Âq%NÝ”g­ã#›Äœ;€÷²7à™–ŒÄÕ',0¯V"æRÆ®×Íi#5tŸ|çÈøÁ.5F7´;l©Šbùˆ*®6Œ‘¢Ý»»± )dÞ6™Ñµ³¬kŒ›0šÓ‡Ð¥¤âIÕR#51ŠÀ`9+pžPo¶“l#5ÄâÖÚL¸bä‹ACÜ­±§|a`“­Sldj N³!cmS))Ë`©¦q™˜Ëò»5LÍds‡?Ú—ëãµéq¶Üs üÄÏ“˜½üL[@ø ·‚—·®˜}“ÉyM˜6E#•æ2=âZSrš•ÂƒKy9€ª#/™®¤¢’‡ˆ@œƒ2`P©n«ù¾?ôÊ6ÏemsÍUS»vÏEJfvéÖ[ m/¶¢Ó>Î4Gܹ5­:îí¿!B/•=õľ bF%à)šX™ã·.΃¶¤©Y "“Ô^·œÔ#5P%ŽHàz›Ç¸"t ô@N…ŒlF–£›¹š5@¬«IæR¶2ôŠ’k´šViÕ»ÓP\Å,=»œÉ(Å‚OS æªÞ±´K§F)UÕCK¼ cò‡ñ–®ig3z 2ž#+º%Ñ›¼`í!N:ø=¬PU‹œéa‡ŠÚËØY‚ Ôp½9S.-‚ƒD¾Ê¹{|ðQÃ…¬¦8°›uìÓ&¯Fh¨ÎƒÛ)g`|ƒÝ$”ªÕMcƧ#+Ð9–6k æX)‹“]bgS#5Ñs)ù‰Î ìó´£#­#[Lµ93Nc'÷߆ÈdÞœ}Ö‡¯Á–®¦â;ºPå¬s]âU.#+dÜ×Z,i#/9NÍŒ#/­tÒîPÈÅ3˜ lͦuJžq%ŽÉ¥ïOQŽ°b°ã˜Ú¤ûKYtÁ5/&\aelâMTïzV1jÜHIöŸßIaõo,(+ixêeÓŒÒmr<2ñC.¤ŽÂíÒæVmǦ£ú8ã6Yp£¼FÛí|æ¹z&:ªÄ4½^&¢¡÷32 éòŒ!²…jšxïwj³ŸÇiw¯›Ñ“}c J¯“÷ªÛz–Â’Å$ˆì‡wÙ‰¬Ü")¡>w™cʸ¹“d$Ò=,¼ÂkD±‡ñ:€³:¬n¬ÙÝŠ˜Bœ‘Øižšã¦¸GÓp·£Ã$ÊíºpeX8ãRÌ<#/>!î –Ìê^lôž!‡–†@êüx㻆]p¼“…#nc°¸ÕÆ£˜B—Î3(â1šÙ˜Ž%›MO¸=é‚£:\3¦µ/I˜i-éÚµB4ämE`Ñ#/úwm~HÑÕâokë;Ùœ#/ní#+á‹qíx0IdÃñ.Cã™Óh‚W¶u=¿[½SŽ3ˆ¸0vy„Ò|S pØê°X“ñ¥x í‡#5C¿x§»‡~zQ:ŠÆ/b&µ|÷€p„‰#+ç#/bmF]ÌëC¤êÜÃ,ãÅì#t·ß{ò”Šb€ée#5¢‡¯‡¤:ó½ t¾Mˆ†ø¨dÙŠPéß6’Ü~¦.LáKeÊ*(öû;⯻½·²ÇYO½z°ØªÐª Ð5àÁ¦¦GÉ¡1¶ýs½ YeK,”´ìǧíãÔ»Ãsg'†cf#/ÃÓ$öºëYŠ{J CLž!ܳ3Ù Œ—sͱ„’7.!®×2Îhe„ùsFeÂ`¸5/Zrp{;õ¸éÁO«æ¯G{ûü£ªÑÓ1³Ô%'ñ Â){c4Ħȋu¹pÖêX㲎|³ƒ£'²(©’ %CO³SÖ/kö¾žáOuœ×ĽúLèâ9áI–Þ™7ÓÝ:ó¥ÖΨË/‹«.C—S£êùhTì&Âö…8Ìͼ‘¥”n˜¦M„¦Þ*%“†5¢™5µ‰$Lji6½ìÐXëW¢[d{§Úæ°€ÊcÀ•Ž5S«°¬T‘ÈÒd6žSM ‘ÊÞðÃ"9û.7:çA—ß`ÈLZe I"v`œŒŽã”ÄH0TRÇIêâCÎ-5Í\7‰,wÁÒáô{ó˜¸g#/k€j@Ç%yTF£ú)*;Έƹ!s^›ÒÛôs4˜øÈ Ú(‘,‚âC¤ív¿vÆ=JDPÉ@‰iÄl€4ã1œ¡ãD¡Šó,ƒÆõ÷ìFó‡#5m‚q”ä3,"Žxh²I©2É7×00éË ô߇ÎLvCyÎjv3£]:Š4Ú£ó†ü»¤4¹î໮ɕ40î,™—†2ŒÁ‰JAðzo|âj³.þE²ê#+‚M—rt3¸À—4À:¼ z‡L' qz÷ƨéÄç®÷ÎVŒíákŒøC‚ƒßç4|=à†JŽ Y9Cf80f#5*’‡@"V룪¤l1:A耑€†šL…#ÐG(ÊKïíŽ3Ǹ$vHФ‘ÄUÓ]lëQÙ‡`} ‡ä‹¢UÆöf M7² ©2Ã\Ég(ò(Þ¨´¦š]3qs ÐuELƒÀz€Ó&ï2ë·BÏR øt§Ì1îøö ü/z½W’e˜ø;íÒ’:x`¡Þàçžüö’6p`š!åƒ}dãtc ê:˜•Ævq4i'Øhz¦ˆi ÖÄ1A‹†-õ°(Û…dwjÕ\Õ+îÉ“)ÏmÙzöK–ƒ^6Ȧ!#/–„¥‰SÏÔ$þľç~—xPßB ÝÔÕ˜g¡¯¥ÐåsEìU*@1+ÐÞ¡ôB¸ ÃãÉÙÌÛÝìø™˜Š»ýÍÕT1ä9d¦! ¨‚>€|C_¡œßw2ŠÄ±JG6ñD€ã@L#+D(~O¨Ðí}6Ð$‘ëÛïp…ûff0ô#5Éðh¿µLM=pø>`í¬mtãÔ¡§òrAñá ?ú&u¤&“ƒ‡÷‹1ÏÒE)#/¢܋”ôƒqïÞõ0´”#+läÂSc½ŸÚ&#+û÷S{qò¦3ÜJÆ"±ÑP CŠL%~8\¯w§xè@Pö>D´%ÒÕ§¯b_ðìÏÓäbøÕ¤W·2uäü.Øê8‹È6$’Ó»»CÒª-0… Œaº ¶Ûv(~Ê%†0ð‚TãÕáŽÃôU”s#"†ñÁ( @QúT. ¡(…Z³ïß%©wðÜô)¥ì ûõiy|­íáÓñòå¡F^÷ð×:élE.ª”Ò(­¨5{ëéDûa>“êáñŸ¥DPÏ °}ξÉÔü’'³MBBOeÙÓ”Ä/!$íŠM»#ÂPæpG§ß8>%¿)¸!Wù Oh¨½«fÄçX¶ÀÕ&#/Â9q&ÎB'ÇÞsè wdGÆL¶NÓŒ¤›„6†æóœ'«͈ˆª(îLnî­Šª{ÝŽîÆ”1#/\ÆX¤.<Üþ=¦Š9+£Ï9È»W@µTEÜkV¦‚‹fsµ;8)fŒkmœc‡¤< ªáJòs©F¶77#5#+Ã3DDÑ1·«1݈xì•ôƒ`íÐØõÎsSzEZhÆ8Cp 1ºÙS,„ á lUÒàqAq0’œ%&!2æN0“#5¸}8Cˆe8|."8BÐÿ¥‡'”é¢c¬ˆfSµß-5¦øØøS6¶g¦µ¸šæ^"¯Âmªù@ò²· îò§Æ–*„ìŸb Í]o5­ZÝ\«˜¢ýýŠ#/¦!$å 9HÁ‡,ÝI׍N—3y¢è#®m±ÉNA¬Lkœµ#/ƒœ)5¸™-)Vž1ã†8#5;(Û-r0¬y°Òp#5¢¨»°¦¹Ò=s·KÒb£ÕqÇ–#/••”¦¡.XØÄÙƈä\Ãäœæ¤,åMwnXƒ@M¬Bå¨æé'sœƒ¦t0¬#uÌ™eŒÆ“–$%ƒ”dݺ›1ˆÄ0LË󜙆22d+„²8ð1ÈìÉ,"6xöìnìs¥u1ÐÕbà‰Òz§IC„t¸q2b^æL$Зc˜4”ÕC.JSWq`ž;ÖÔr Ø©ÒRtá¹Öεm‹CÜÓ¢-‚mÓsŒRš,óš(¤-Þ#+µÉåÈ62‰ÈPÆ"6é‹ŒW¡¸aÞCÀ9 p:¢¹J•XÊ1N=^áþ΂çdîö…ä:êUèlÉ`„:jl\`Môjº´ÆÊcAQwÎ1Y„ ßàÃçú|qõ –×ñ´–Ó |ç½|…DÆÇü2:o|ó#/9° Ë–#5¥¡)nŠ á©!ƒ?@ðîòñ™†³² M!Kô-PÄKÀXÓHÅT•‘ÔÕ|K™B¢^Ê%C$¦„P¥Dýrš@hõvˆ%N@4*R-鑤HšÖ‘¢)QHE‡¬£RÐ#5ùBà_y‚Q#+³]æ#+ê8™#+£¨œZ#/jnªÌŽ1î¾)ð\<À@Qw&Á#/(˜×ºD6#~µÈh:¤¦dþwØë9bK”P2"@Èç&!F£õþßW=—gz\ö¡ä#+=´z~ía;D<ÙÄCÁ“Í?¦©×Nû¹1ºÄËø.Rd`/j÷Ü5}O29´72J'áȬ¼™HOÂÏÍï9lqrO{ŒŸ£lQ¨ÌIEžÈ…/³¿)j5?ayoÔ¾a}ÐaÀE©B½¤#/™s{2½Ûð-®O ÝÐ3 `#+~H¥4‚¸€‰i P䘣+¿WQ䧉hR#/dhM¦"˜%$‰BœCAB¬‰ƒM½t½ñ- к«ãåÃÁ#âžÃµØ©õh€©C>#+v!‘#5gò7ô$®%`!&$"Hf ¦”¡’J ih…"‰b((V€ä…¼8`4hÈ ÂÒ#YR0Ê`Ýû½)¼4iÝÝ"C«Û¥M`ªöþ7 žÑ&äó6 +|ªÍ$ç$ô~ȃÈÛoO—’‰ÌeP!….{Ä 0ê|º§¯‚ñx¢v•HJR-P‰¤óΫ”y®Ý~CŸÄ¶õlD°à~/h¦|{|Âb.±÷MήUËÔ¥]àÞÝ7ˆÞh¦øŒx¡˜­"NnZ1šx6ÐùÑK,ô뉪*¼;3ʼnêbèLìôr¶#/ÌY„N›q1ÆàIÛ²™7™©ŽÄúaºG€Öh0½Ù”aH¥fG|E‰°úÍ>,éæ~Tã2Vj¯4óD;,p̆RÁ 0)A2DV”èÒƃ?aßne“6ØlšU&”•½¹O¦¬( ‹Ž¸Î“`Ü[à¤Ì&Ä‚´^$òQ#5tAã±ÂQ˜”:^Ç—$_oc…=gjú€õBf(xµÈ`eb'¡Îg‰³CÒ9û¤òNÅ,8ˆ™ú¥þ– ˜k¦“ß0äØÔ… ›!µè>ü°©ëJA7†AÇÓßøK…Å_Xyßlíè_NþIÐ4®±’V€‚^¨ÔˆÆá~dÙU@Ý•˜ ™Ah•™6„*N=IÎy‘hùé˜#5¢)¢H£ßÜ«Æz—È2©°ñdãÀOÚDÂŒ1#/0UEÎÉZ#5! |FCZÄ\•ß#5&P£¼€‰¥#+ñNo|чܤw|åæöüS5ñƒÐ{Qu¢ÅL¢ªb#5I#5ˆ¦^}ÝwÙ¹˜U×\¶­):´y‘+­Æ,™ F½`Áí¦"Ú:\½¬ºË Ž`ò{’\}%tài4³È!}¯©Ê©©#5|9xkugh(6⑈iI Ld§¾CT QJ…4)ë¯&Ä&O|£„.>ï#5ž˜#ƒˆ`T#5€ª¿èôOP¾¿qñ;0oëé×6ÃKJy9úªX²ýC =_ãäD¼¶íôC Õø¼ŸýF—= Å þÎ ûUù#+ž“p˜Œ³Øps<¦*æê'‚r#+ʲЕHzü0{AШp$#+ i ²ÐÊ(ž¥É%ºBŒœ(±1"*’yóä#+#+êÇ\ LUbDZ14ß,h‚’¨+† Ú(¶ LD4Ë6Äóæ]T¹ƒCQFj@¹ƒÝ9ˆ/8¼ÝÊ\ÉÔ»æ#/!pÒxîZS—g¥È÷ÿ&xRwb… Õ‘D±Q‘R´G¢v“UAƈ(ŠY‡–’“I¨˜’€ÛçD“HP1š 0L‚—ù°å#+4à~ýÀM˜ˆš/5¬ï¤ d¥:É#/åH~ºkÛöÓŸ»ÞÙÞ— Zû†ÏŸ·øŸ2š °ÝQüÖ¼A&aûŠßðÖ›Ð͹G#+:ž^Áò×#+RLÈR!BP”‹ P„B+!#„ÀE#+J (¥ C@šàªØ®|ÄúPv>‘¢†Š–ò#5"«æ3–œØ÷"/_µh#5J("#5J€‘¦*Z‰ŠB”’!`‰$¦Š*Ši‡H”u•óÏ’àsŠ#5`¢$ –)‚• (¡¤"J ¡ˆªYH€&DJ©úõ¤ˆ#5©!¤ˆâ‡€’“ô’ˆP5þ'2›ûH‘Ѳ=è÷DU(J¸¼„$ñÄÙ™•à+ÞJ)„ÂA#t÷®ôâ'—O£€d ÂXdTJmÈ9¾"½ˆ ß€_.B‡ç ‚¢¤’#ä¿@d@°@BÈ%4!äT€ºUTötíŠ W+H4É€CþØÒvr!t“Ç œÁĸdÕFŶZ“˜Ì“J!#+ ÉRCÊ‘dÍ#5Ih“@rÁÌ ‡¹ïÉ)^ßwÙZQPø ê#+â皀bA;{2¢D(DÒ¬½Öåv÷FŸ$˜ï5}ÐíB#+( ?Jn×$*s‡°aÔMú<ÃÛ^¤ö·ƒ_Ji¬æ+Æ•ˆ&¾¬r;l€Ú<0 ‡Î8ð"#5©\1¾rX´pÍnáeˆ ´ÅÕç^N²¶µlD§Ïs‰ýFµad†M(Pdļg.¼Òë£IcX-œôJ\@×vƒ´ÑltJëE#/#zŸ‡É>Dœ÷¦ÍK£ûÙ>T ä”|¡Î…& Qä”?´ixÍî»»°‹¼ƒ¨O™:ƒO)WÕÍÏrk¨åÿ(ŒMQý5ëàÑH_²Ä= ª~9Àúþ¶ £žŽêìO†bšJ")B©kêXĆª90FXCA§%I¤L1­Œ6‹hˆÆÚu[MM½¹Š.ZÉR´‘JD@PBÒÒQIT4”‹HFØ•j JJ¦>ü®ëä#/²P·ø qôûº½¦tŒ=nè¼x#50ñ r¦#?E^¼àò'Sê¥#+_låDQ˜þHq,3'q*÷ ì§b4ȳ݊’2f`Eãv#/ pcLA2.;.aPZbfêUÌ ¹Ç™Ä±–BHu¹ÉÈ ¡37Ÿ#ùÏP9ÈtNpo9Í}©AÍÎm&ÌhØÂT5s.W‡#çºôóÓââ` §âT! #B"t#5–i¥×dPPµ@0TCDB4£H!L’ŒoÄtw{ªínî8KØAÙå-óôÍ6k &t™¬p”;ÎZ£îƒóÎ@-#5S¹Ö_QÃÏ#5#5±§A¶ÄF¾ü ³š–íèÍP*ϱ ±¢©rødÄ1¦ºë’hK5AäŽÏnüA¡§ÌˆŽÅÕ·œí¥¬-hÇ^œqƒÂGŸÖÛ 6(ÑŠdlüQÚãÓЩ(æ#5M6Ä6-Œco`ÈÉ#™U$§…¯ö  ù„ăê8Ãh1S É¡ÀAP@CM  ‘Q!‚äK$;°i'*` iH€ M9JsŒ39™€0iʆÈjˆ€b&˜V±²TD¡¶#/²$J@AD‘$@ìd 3ØÀ‘Gp˜ÁqB1ì§<¦¥<ç"$:\ÀÂgÈ@J; H&–„‚˜&U•‚Cl»&u„†™1&"M!‚ÔwÂé>0vxl‰Ò#+ÐN}ICxOÚO?˜ã¢.E†ER/ ”{Òò` û.±¶¿Çƒæz?1¹ÁÒI3áG´œˆmý_¡Í(Ÿ#5€Ë¿yóÔËà8pÏ .œšCŸ‰¥\Åm µîÅ#`î•j©`Á0ä†Èy¯qÚá¨ÒLÛŠ8†Uyï#/'Šec óû)Â77~f6˜¯×E}i¯«>\È…,UxUÆoÄÎV.e¯ùž¬¯c ˜çïwÔŽót7” R1ëô8¥PLI8âE ~cŒ?ÒO)àGHMCÌO›Ù»“3ƒµó†KÖìd¦ÅøÂJ¾ ÒnÐU9Hz ì #5 Ð ØQb‘R誠ÍÔƒ¨O¢5˜/Ê#+(SÉ¿—™€äþƒ{  J*9#5ý 'NÐÅÁüµýøÑ'Gã'¯ëdâôê*b´,Ÿ#/M‘á£ûÆ®™œFÁûà“m%Fœn\ˇ!IÕóàlc43~’'& ç6[+B<‚¶‡h#/ÃtúTõ©ÍP{ÞO"BNïmŽÊ„v¿›° [?ßúÄ^çÑâ8FA1“b·Ý”*˜áÁŒmmµl,6’÷Bµ-+m™%‘¤ÁîQ¢O-ݦÛ¹„ŠH(d… 9(`‚\bq„2ÓÓmæ×|Ãç#+꣔h€†Y’:}éybQ÷©Ôõÿ‡¶öÿOÀãcZ¬Çµ¢dñX­Ö/¾§º°ã)ÛÇ×—I†Ϧ¸º™—Cû:ÕDR§I°äáPKƒ¤¸Nç Cég½þ˜ƒL8>ååÕi-B"éæCËõ…T±úƒTƒpSŒ#5#5A7ÿoƒÀS†•oµ#5"sºÖ}öCÒD?~ѾÌ>§µÜ)ï#/%؈µ)j©2~Û*FÐ3µHOy¯%[YC+ßT íƒ$eñ¡fP±¡8ƒ’QæyÖHtgçšÑðãpQ¬À:üåPê¸ECC¦´#//zr'ÛÎvIYev]³fÂh€Ì™'q",ï CÝp=Þ›{ÃQøQïK}Tn}žyCoV–ð»#/Â&#+`©,)3¿Å—*E#+à©#+.zâPê~Âßo¸UO‘’—”ý]â"4xÏsîƒV#5PÊíö˜8šœŽÐ#/Nx'«Äé¹q#/ŒiÑ`CÓHkörAPÍ9Ù.—FÉS¹às(ålÂS1‘:8ìr;Æ~½ÐÃKXÚ¤œÜäŠw?;; ?/îù¾@ÿÃÌhš{Ž(þüàˆ'öç IÝF ¾3‘§Öß ÛãOït²#/ƒ„€dÒ*Wî#5§K÷õ‰Õ±ù`4¢œð9k>lDGyÓˆñS$! ×x~áEú:³EN/͘‡¶B¬ˆ¬CiAÏ?záωõt‡&s I$W‘éÞI¬šùÆÇA$ž™½£œ 6b?ÀB »)Ö#+~eK²¡'ês”q€»½åÆé#5D°K¨SôèÁ–#5Èr‡»@'-&$D# “%ÄTÔ’T@Zþþu¢ŠhAˆ£…ÒC’O|oÀÎ:ÌWÀ‘5 f¡8ðu~-8„q/³òx¨S‡Zΰň''4GÖö’™>D9 't ù½ˆòåtÌØ|Ýèø¡ëâ`ñ€Ô¡ø•é¿mîW ­ÄÀxHz„ФŸÖè4¯!þj¢(Ä17QØY xM(»Ù ÊÎ9õ>Q%ÿ‡µ¶J"&¦*‰¾à{úq¨ú¨D ¢°ÎèÇ]­­4²A±À6Í;~SI <Ûï'm^ÄyÝì Ò3±òdz§Aí8ð Θ”E(ðAMÅÄóHvßÈÒ‰èôNàÎã„¿<(z±"n áéCK·’ ä°ÞAØvbÜ^¾€P‚¥4U÷‡·Û÷1 û±Ž#/ØY9gj­2k†zˆUŠ¾B4n­ïù®µ&#‚y³Ñõý ©DÃò"pŒd{ÕLJð[4¤R³ôH·«¦Ç 5ùtªÌbªdŽÖÍ„åC!D3,‡y¤QìDǨxSòMQøq3õ¬…1­Œi]ÍŽØtšt´Â`zäm#W;>ÈTÖÉšÕ‰§š7íN,PQIZvØ㲃¼ ´‚#`2D6sÚó¯&_ð8µÖµ¹0s©m>2ŒoñÕºÿ¸¼0Ì(è¼5–"Û&BR6ëjÏàÝúüµÇ2¦"º¤¸ÊªQg¯KL*™ºÌ¥Äm[Ç›°dÀòî–0!ÇLQ!ÕTñKšháÓ\ÌF=³A0€‚¢ÅƸ1x)’‘ ¦Mµ¡ªLÞ93žrèfŸ6(=mkCt š(Óèžr1Ï#+݆°QŽU½=Ñlš)0õ–ņ=2Œ’“:CZˆÎ¢’¬ÃÆM*u6‹wsZ †Ë1ng8·ãu‹&aë*L&*†(Ž^¾ÐÝÊ_­ãäôÌoºŠX™(b%U0­j¤ìÛ¬$“ …‡>ŽLzäh§ VPRT"Ä0ñ¢#ùoùdñVÓÌÞ`f>__‰ÌH6¤_kKÈ›¡mI| ÝŸ3EËÉ´ñ´±­[¶Â&QÑ„ÞÌAG·Ã3‹ÿFÅÜU?¿éìæÄC:ÉÀ6ÄSBj‰ 3IáÏŸgÇÙÉÙN<8”R>“¡¹À9<^'ᙘ)ïi#5ßÔb®ã#5(ðF¡*J#+€O‰÷‹žþZj‡¿˜œtiú¸!Ç‚#5ôˆ¢N)´7ÙÌäs¤€Ã¦ä‚<ö>_©IM–_!½ö/×gË!ª ¾ºúô™ù|Êöxfwø ÔÈÀ„S#Kº/‚aêºÃý[*ž¶@é×tèxﺹ‰ï…¦d)¢)ˆ(j#5ªf€€¡NN’ ("ªš†ŠŽÎ¢¢`b#+ª¥’#+™¤ZV!#5;LPÁQ8ñÅ@àB#/p’‚qýÆv09@æÀÝ ú†_ÖÂWSmÙhRC1ÂQ@m|íÈ%ãlH˜ȃÍ>'GÄþ@Û^¼U<ÿ_§žš«ZlÑ v¹ö̤gϲmñyô¹’˜¸dϘ ¦ŸèoÍ5£(¤Ž•Lìâ§ÀØfÍÑl³Šº(Ù‚ŒAZ„~~¹Ëc‡3.L7D8•-2U‹LßžaŒ«Ð©Wñh¡-!Û#“¶¦¼'R]‹#56=HÅ>ùÏœ0ix‹,}C›@lfT2D´Þ5T%™BK•+^¶)‡²vS°îN‡I9¾;ä–ò/ŽîjÖ½ã]#CI½ö„æ ½î‚ði¿}êaMRR‘TbKjx$ÚrSqqeCÌ8’H[ÝoyÎêŽ £¥´l‚#/u#˘€—uÄÂ1éã˜Çé Ê8%èB~¾0诱͡ÅÐï©Ó¾c%ˆ¼ü»rn5¯(±¡8I_HTÆ;æZSÅI…"·¶¢5."ÔKâtãÉ5Ó¦g áîH]ô•‘éÑتЕ´Cäg{X²Í®í‰o›BBu‹³ãWdÀiS0ü5~¾Ìa˜ÄNbS -DY²Qu+PªŽ;xâùoþ(˜ó3]}œñ…\õ#/ ¶iŸY®¶£ógN2©´‰T´•øƒ2RrA‰ªWí“M-#+W$#/#5ÐSÈWR”’P% D­Rã"hùØ^Ø׃oÇ'!»µ±ã`#/HÓµÕÞ`š7Œ<÷}ûbfA¿Î ë­ d¨ -² ÎÎ|[–òpÏmê`À6#5id8Ò€Hôé.‹™ ù k%Ær³Âxdàrª9Ô äøG®$ÁÞØ{&ÇÇõ >LâCŒϼ¯AÀÚ‚¨#5ièbÜÝG)è@þ>^ãû§ìÍp9t}“™X>Äí$®ŽzÚæ"{ê<£u?ÆÊ`€OH‡ñH?›¬N·³A²)PHa …#5êzŽ÷Sî?/+²ÛQHV¡M,"3 “™‡4ˆgpÒè0ù¦$–˜><A÷¿V¦ô=m×ý¢ŸLTÉU#/D4…D…#/D…“V”¨4`dPDºM¶Äl®Ôÿ£ìò~\Éù~cOéñ.ƒH²½¢dŒ@ÏXÓˆ?WÔŸ+Ö©þw¼¼ó#5~wÕ-<_èíGÛ¹Ût4§I)¤OâÕ?áË݇l£ÓIØwƒFÀ‹#_Á§ïÐÑûsÀSöÑþÀbCóÀ£ÆîíýzAÂ:óiGžb“ µbä…É#+ÃÿRQ¿¼© ¹Ï<õ÷Ÿ9Ät?‰ ˆüJ)Dy<èÝ_Ó¥Ú¿8]Iõ~xÐwdp ˜þŸ(÷céþßÉý]ÃèY{a÷—eÚPmêÚ–îÕÔC¼ë¾ï›Ç>è@aßRŸ'½Éõ„úG²|…’ÿ›É‹†M÷?›ô'Ý$ö=áã­ÉþŒ‚M2ì81挴#*Òÿ¦ty4 }ZãûÒ¿w<jued¦!lôX2™ú` 3ýÕ…Ígo†7¼|Õ6½Â³óþÈkåú˪†s÷ݯçîù´áYüÂpoîÎû²híÒ»xœULà›÷ê¸\eœÁ©‡|Ž!ñü6;hcèYþ=h%c(à1y L±Ç/ à¨(3’"f®ŒÜJŽUtFÏÖw(èåÓÉUä¾ý‡Tgcñ­I¢ãàÌ<ú¢d„næ¾1* $õV’#*€ËDè{ž,ÏžšõPTHPן’«¿d±èÖÎ%ü¿×ào ˜18'…‚çÀ©_NÀ@0è=7/<öœG¸û‹£Óç…*°`Šûþ~A¯‡¹Êùï89U]´Øjw"ZÚ'­/Ÿh €ú¥îà.^Ñÿr CýÌÅþ«ßþLd£˜ ÿü¿üM¦t¸ @Á¢xŸçûƒ_xi‰çûåh²ý‹ÿÓÛ#/AþÅ?ÌYÿ™£Íâÿþ.äŠp¡ ߻վ #<== From 8dda35a128aa55e322defce0348cfac0d455bef4 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 29 May 2022 18:26:55 +0300 Subject: [PATCH 245/548] mainui: update --- mainui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainui b/mainui index 01e964fd..c5d61f89 160000 --- a/mainui +++ b/mainui @@ -1 +1 @@ -Subproject commit 01e964fdc26f5dce1512c030d0dfd68e17be2858 +Subproject commit c5d61f89b91f8a5e8febbeddb0200f275dc84759 From 74485f30d309b9d1b5b2d34a9c1cdaa8b43f8de4 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 29 May 2022 18:27:16 +0300 Subject: [PATCH 246/548] ref_gl: gl4es: update --- ref_gl/gl4es | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_gl/gl4es b/ref_gl/gl4es index 4f2151a1..36a5cd54 160000 --- a/ref_gl/gl4es +++ b/ref_gl/gl4es @@ -1 +1 @@ -Subproject commit 4f2151a104ac45bf5417a0063e96148204f8256c +Subproject commit 36a5cd54e7ff6cad15ce6d1924c63bf5cf64c1f1 From 060835cf2fadb8b4873005e3d4d8f8b2ba6ed550 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 29 May 2022 19:22:48 +0300 Subject: [PATCH 247/548] waf.bat: correctly parse reg query on Windows 10+, fallback only to py.exe wrapper --- waf.bat | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/waf.bat b/waf.bat index 2d15819d..92d4ed37 100644 --- a/waf.bat +++ b/waf.bat @@ -8,17 +8,13 @@ rem from issue #964 Setlocal EnableDelayedExpansion rem Check Windows Version -set TOKEN=tokens=3* +set TOKEN=tokens=2* ver | findstr /i "5\.0\." > nul if %ERRORLEVEL% EQU 0 SET TOKEN=tokens=3* ver | findstr /i "5\.1\." > nul if %ERRORLEVEL% EQU 0 SET TOKEN=tokens=3* ver | findstr /i "5\.2\." > nul if %ERRORLEVEL% EQU 0 SET TOKEN=tokens=3* -ver | findstr /i "6\.0\." > nul -if %ERRORLEVEL% EQU 0 SET TOKEN=tokens=2* -ver | findstr /i "6\.1\." > nul -if %ERRORLEVEL% EQU 0 SET TOKEN=tokens=2* rem Start calculating PYTHON and PYTHON_DIR set PYTHON= @@ -88,7 +84,7 @@ rem @echo %PYTHON_DIR% if "%PYTHON%" == "" ( rem @echo No Python -set PYTHON=python +set PYTHON=py goto running ) From efdf9e359982cccbe9a20d6c5e5260ede35acdd4 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 30 May 2022 02:35:06 +0300 Subject: [PATCH 248/548] scripts: gha: include activities.txt on win32 continious builds --- scripts/gha/build_win32.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/gha/build_win32.sh b/scripts/gha/build_win32.sh index 4676458f..e52f6ddf 100755 --- a/scripts/gha/build_win32.sh +++ b/scripts/gha/build_win32.sh @@ -28,4 +28,6 @@ mkdir valve/ python3 scripts/makepak.py xash-extras/ valve/extras.pak mkdir -p artifacts/ -7z a -t7z artifacts/xash3d-fwgs-win32-$ARCH.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on *.dll *.exe *.pdb valve/ +7z a -t7z artifacts/xash3d-fwgs-win32-$ARCH.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on \ + *.dll *.exe *.pdb activities.txt \ + valve/ From d1de9e28020cf8e734036a7015c03a0df590489d Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 30 May 2022 03:33:03 +0300 Subject: [PATCH 249/548] engine: platform: sdl: proper cursors allocation and free --- engine/platform/sdl/events.h | 6 +++ engine/platform/sdl/in_sdl.c | 76 +++++++++++++++++++++++------------ engine/platform/sdl/sys_sdl.c | 6 ++- 3 files changed, 62 insertions(+), 26 deletions(-) diff --git a/engine/platform/sdl/events.h b/engine/platform/sdl/events.h index 1d4a69fc..e42df28e 100644 --- a/engine/platform/sdl/events.h +++ b/engine/platform/sdl/events.h @@ -33,5 +33,11 @@ void VID_SaveWindowSize( int width, int height ); // joystick events extern SDL_Joystick *g_joy; +// +// in_sdl.c +// +void SDLash_InitCursors( void ); +void SDLash_FreeCursors( void ); + #endif // XASH_SDL #endif // KEYWRAPPER_H diff --git a/engine/platform/sdl/in_sdl.c b/engine/platform/sdl/in_sdl.c index cc85c191..a369a90d 100644 --- a/engine/platform/sdl/in_sdl.c +++ b/engine/platform/sdl/in_sdl.c @@ -28,7 +28,11 @@ SDL_Joystick *g_joy = NULL; #if !SDL_VERSION_ATLEAST( 2, 0, 0 ) #define SDL_WarpMouseInWindow( win, x, y ) SDL_WarpMouse( ( x ), ( y ) ) #else -static SDL_Cursor *g_pDefaultCursor[dc_last]; +static struct +{ + qboolean initialized; + SDL_Cursor *cursors[dc_last]; +} cursors; #endif /* @@ -254,29 +258,50 @@ SDLash_InitCursors ======================== */ -static void SDLash_InitCursors( void ) +void SDLash_InitCursors( void ) { - static qboolean initialized = false; - if( !initialized ) - { - // load up all default cursors + if( cursors.initialized ) + SDLash_FreeCursors(); + + // load up all default cursors #if SDL_VERSION_ATLEAST( 2, 0, 0 ) - g_pDefaultCursor[dc_none] = NULL; - g_pDefaultCursor[dc_arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); - g_pDefaultCursor[dc_ibeam] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); - g_pDefaultCursor[dc_hourglass] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); - g_pDefaultCursor[dc_crosshair] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR); - g_pDefaultCursor[dc_up] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); - g_pDefaultCursor[dc_sizenwse] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); - g_pDefaultCursor[dc_sizenesw] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); - g_pDefaultCursor[dc_sizewe] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); - g_pDefaultCursor[dc_sizens] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); - g_pDefaultCursor[dc_sizeall] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); - g_pDefaultCursor[dc_no] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); - g_pDefaultCursor[dc_hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); + cursors.cursors[dc_none] = NULL; + cursors.cursors[dc_arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); + cursors.cursors[dc_ibeam] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); + cursors.cursors[dc_hourglass] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); + cursors.cursors[dc_crosshair] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR); + cursors.cursors[dc_up] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); + cursors.cursors[dc_sizenwse] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); + cursors.cursors[dc_sizenesw] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); + cursors.cursors[dc_sizewe] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); + cursors.cursors[dc_sizens] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); + cursors.cursors[dc_sizeall] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); + cursors.cursors[dc_no] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); + cursors.cursors[dc_hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); #endif - initialized = true; + cursors.initialized = true; +} + +/* +======================== +SDLash_FreeCursors + +======================== +*/ +void SDLash_FreeCursors( void ) +{ +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) + int i = 0; + + for( ; i < ARRAYSIZE( cursors.cursors ); i++ ) + { + if( cursors.cursors[i] ) + SDL_FreeCursor( cursors.cursors[i] ); + cursors.cursors[i] = NULL; } + + cursors.initialized = false; +#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } /* @@ -289,10 +314,11 @@ void Platform_SetCursorType( VGUI_DefaultCursor type ) { qboolean visible; - if (cls.key_dest != key_game || cl.paused) + if( !cursors.initialized ) return; - SDLash_InitCursors(); + if( cls.key_dest != key_game || cl.paused ) + return; switch( type ) { @@ -309,13 +335,13 @@ void Platform_SetCursorType( VGUI_DefaultCursor type ) if( CVAR_TO_BOOL( touch_emulate )) return; - if (visible && !host.mouse_visible) + if( visible && !host.mouse_visible ) { - SDL_SetCursor( g_pDefaultCursor[type] ); + SDL_SetCursor( cursors.cursors[type] ); SDL_ShowCursor( true ); Key_EnableTextInput( true, false ); } - else if (!visible && host.mouse_visible) + else if( !visible && host.mouse_visible ) { SDL_ShowCursor( false ); Key_EnableTextInput( false, false ); diff --git a/engine/platform/sdl/sys_sdl.c b/engine/platform/sdl/sys_sdl.c index b1ad5838..7041bf06 100644 --- a/engine/platform/sdl/sys_sdl.c +++ b/engine/platform/sdl/sys_sdl.c @@ -67,11 +67,15 @@ void Platform_Init( void ) #ifdef XASH_WIN32 Wcon_CreateConsole(); // system console used by dedicated server or show fatal errors #endif + + SDLash_InitCursors(); } void Platform_Shutdown( void ) { + SDLash_FreeCursors(); + #ifdef XASH_WIN32 Wcon_DestroyConsole(); #endif -} \ No newline at end of file +} From b82d0bad7a9f84cf102dd0538e2c4eb1aee9df8d Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 30 May 2022 22:41:28 +0300 Subject: [PATCH 250/548] engine: client: vgui: unload client DLLL if no vgui_support export was found --- engine/client/vgui/vgui_draw.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/engine/client/vgui/vgui_draw.c b/engine/client/vgui/vgui_draw.c index a9b02640..94ff455b 100644 --- a/engine/client/vgui/vgui_draw.c +++ b/engine/client/vgui/vgui_draw.c @@ -163,6 +163,10 @@ void VGui_Startup( const char *clientlib, int width, int height ) vgui.initialized = true; Con_Reportf( "vgui_support: found internal client support\n" ); } + else + { + COM_FreeLibrary( s_pVGuiSupport ); + } } if( !vgui.initialized ) From ca975d01685d9829c4a26c91c4a1eb789074253f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 30 May 2022 22:44:52 +0300 Subject: [PATCH 251/548] mainui: update --- mainui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainui b/mainui index c5d61f89..132b9f75 160000 --- a/mainui +++ b/mainui @@ -1 +1 @@ -Subproject commit c5d61f89b91f8a5e8febbeddb0200f275dc84759 +Subproject commit 132b9f755f303c4133fe8eefb2625a11cbca7c1b From 4809fc5d026e1dff35e7a8f1f8f21aabb4490e0e Mon Sep 17 00:00:00 2001 From: NightFox <0x4E69676874466F78@users.noreply.github.com> Date: Tue, 31 May 2022 02:18:48 +0300 Subject: [PATCH 252/548] more correct base_color processing --- ref_vk/shaders/denoiser.comp | 2 +- ref_vk/shaders/ray_primary.rchit | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index be11a3b3..c8646dc8 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -173,7 +173,7 @@ void main() { #endif const vec4 base_color = imageLoad(src_base_color, pix); - colour *= base_color.rgb; + colour *= SRGBtoLINEAR(base_color.rgb); colour += imageLoad(src_emissive, pix).rgb; //colour += imageLoad(src_additive, pix).rgb; diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index aefb4f7a..ac49056e 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -35,8 +35,7 @@ void main() { payload.emissive.rgb = SRGBtoLINEAR(texture(skybox, gl_WorldRayDirectionEXT).rgb); return; } else { - const vec4 base_color_a = sampleTexture(tex_base_color, geom.uv, geom.uv_lods) * kusok.color; - payload.base_color_a = vec4(SRGBtoLINEAR(base_color_a.rgb), base_color_a.a); + payload.base_color_a = sampleTexture(tex_base_color, geom.uv, geom.uv_lods) * kusok.color; payload.material_rmxx.r = (kusok.tex_roughness > 0) ? sampleTexture(kusok.tex_roughness, geom.uv, geom.uv_lods).r : kusok.roughness; payload.material_rmxx.g = (kusok.tex_metalness > 0) ? sampleTexture(kusok.tex_metalness, geom.uv, geom.uv_lods).r : kusok.metalness; @@ -57,7 +56,7 @@ void main() { #if 1 // Real correct emissive color //payload.emissive.rgb = kusok.emissive; - payload.emissive.rgb = clamp(kusok.emissive / (1.0/3.0) / 25, 0, 1.5) * payload.base_color_a.rgb; + payload.emissive.rgb = clamp(kusok.emissive / (1.0/3.0) / 25, 0, 1.5) * SRGBtoLINEAR(payload.base_color_a.rgb); #else // Fake texture color if (any(greaterThan(kusok.emissive, vec3(0.)))) From 13f93412cdcdc9cfaac9834f41871b1dc45ddba2 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 2 Jun 2022 03:01:58 +0300 Subject: [PATCH 253/548] engine: platform: sdl: forgot to add an include, sorry\! --- engine/platform/sdl/sys_sdl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/platform/sdl/sys_sdl.c b/engine/platform/sdl/sys_sdl.c index 7041bf06..0194c291 100644 --- a/engine/platform/sdl/sys_sdl.c +++ b/engine/platform/sdl/sys_sdl.c @@ -15,6 +15,7 @@ GNU General Public License for more details. #include #include "platform/platform.h" +#include "events.h" #if XASH_TIMER == TIMER_SDL double Platform_DoubleTime( void ) From 080dc38f11e9857adef6996832c6c7234352f90a Mon Sep 17 00:00:00 2001 From: Andrey Akhmichin <15944199+nekonomicon@users.noreply.github.com> Date: Fri, 3 Jun 2022 19:24:21 +0500 Subject: [PATCH 254/548] ci: upgrade freebsd tasks --- .cirrus.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index dbc2e87a..5c6b0a24 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -4,7 +4,7 @@ task: image_family: freebsd-12-3 setup_script: - pkg update - - pkg install -y git sdl2 python + - pkg install -y git sdl2 python fontconfig - git submodule update --init --recursive test_script: - ./scripts/cirrus/build_freebsd.sh dedicated @@ -13,10 +13,10 @@ task: task: name: freebsd-13-amd64 freebsd_instance: - image_family: freebsd-13-0 + image_family: freebsd-13-1 setup_script: - pkg update - - pkg install -y git sdl2 python + - pkg install -y git sdl2 python fontconfig - git submodule update --init --recursive test_script: - ./scripts/cirrus/build_freebsd.sh dedicated @@ -28,7 +28,7 @@ task: image_family: freebsd-14-0-snap setup_script: - pkg update - - pkg install -y git sdl2 python + - pkg install -y git sdl2 python fontconfig - git submodule update --init --recursive test_script: - ./scripts/cirrus/build_freebsd.sh dedicated From f417433f59aef5e08774a2e39753fc84e7be9284 Mon Sep 17 00:00:00 2001 From: Andrey Akhmichin <15944199+nekonomicon@users.noreply.github.com> Date: Fri, 3 Jun 2022 20:19:53 +0500 Subject: [PATCH 255/548] Documentation: opensource-mods.md: update --- Documentation/opensource-mods.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Documentation/opensource-mods.md b/Documentation/opensource-mods.md index be6ee7dd..70e666e8 100644 --- a/Documentation/opensource-mods.md +++ b/Documentation/opensource-mods.md @@ -24,6 +24,10 @@ Official github repository - https://github.com/CKFDevPowered/CKF3Alpha ## Cold Ice Version 1.9 is available on ModDB - https://www.moddb.com/mods/cold-ice/downloads/cold-ice-sdk +Version 1.9 mirrored on github - https://github.com/solidi/hl-mods/tree/master/ci + +## Cold Ice Ressurection +Mirrored on github - https://github.com/solidi/hl-mods/tree/master/cir ## Cthulhu Uploaded to github by Oleg Cherkasky - https://github.com/gunrunners-paradise/Cthulhu-HLmod-SDK @@ -46,8 +50,14 @@ Versions 2.0 and 3.0, available in mod archives on ModDB - https://www.moddb.com ## GT mod Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/GT_mod_(Polnie_ishodniki) +## Half-Life: Advanced Deathmatch +Mirrored on github - https://github.com/solidi/hl-mods/tree/master/hla + +## Half-Life: Decay +Mirrored on github(PC Port) - https://github.com/hoaxer/Half-Life-Decay + ## Half-Life: Echoes -Download link on dropbox - https://www.dropbox.com/s/s6j8gtegn10wgvj/dlls.zip?dl=1 +Available on ModDB - https://www.moddb.com/mods/half-life-echoes ## Half-Life: Expanded Arsenal Available on ModDB - https://www.moddb.com/mods/half-life-expanded-arsenal @@ -58,6 +68,9 @@ Branch **gravgun** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/g ## Half-Life: Invasion official github repository - https://github.com/jlecorre/hlinvasion +## Half-Life: Pong +Mirrored on github - https://github.com/solidi/hl-mods/tree/master/pong + ## Half-Life: Quest Mode Available on cs-mapping.com.ua - https://old.cs-mapping.com.ua/forum/showthread.php?t=38030 From ae6ad74994be9877ed82134ba5e4099e90b5747f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 9 Jun 2022 03:09:48 +0300 Subject: [PATCH 256/548] update: mainui --- mainui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainui b/mainui index 132b9f75..0a28db38 160000 --- a/mainui +++ b/mainui @@ -1 +1 @@ -Subproject commit 132b9f755f303c4133fe8eefb2625a11cbca7c1b +Subproject commit 0a28db384b9572f2aefbc335fe5255e65f9f7b7f From c532c3032cfafebe819ff58099be342add51582f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 9 Jun 2022 03:28:38 +0300 Subject: [PATCH 257/548] engine: menu_int: hack to compile on MotoMAGX --- engine/menu_int.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/engine/menu_int.h b/engine/menu_int.h index cdb918bf..f8b23614 100644 --- a/engine/menu_int.h +++ b/engine/menu_int.h @@ -53,6 +53,11 @@ typedef struct ui_globalvars_s struct ref_viewpass_s; +#if __GNUC__ == 3 +#undef NORETURN +#define NORETURN +#endif // GCC 3.x have problems with noreturn attribute on function pointer + typedef struct ui_enginefuncs_s { // image handlers From eeb170af2201dbaf8ea196917f39ad9ccf33ceaf Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 9 Jun 2022 13:41:37 +0300 Subject: [PATCH 258/548] engine: client: set failed status for vgui_support if we wasn't able to load one. Unload library in case of error --- engine/client/vgui/vgui_draw.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/engine/client/vgui/vgui_draw.c b/engine/client/vgui/vgui_draw.c index 94ff455b..56f456f1 100644 --- a/engine/client/vgui/vgui_draw.c +++ b/engine/client/vgui/vgui_draw.c @@ -200,9 +200,14 @@ void VGui_Startup( const char *clientlib, int width, int height ) if( !s_pVGuiSupport ) { if( FS_FileExists( vguiloader, false )) + { Con_Reportf( S_ERROR "Failed to load vgui_support library: %s\n", COM_GetLibraryError() ); + } else + { Con_Reportf( "vgui_support: not found\n" ); + } + failed = true; } else { @@ -213,7 +218,11 @@ void VGui_Startup( const char *clientlib, int width, int height ) vgui.initialized = true; } else + { Con_Reportf( S_ERROR "Failed to find vgui_support library entry point!\n" ); + failed = true; + COM_FreeLibrary( s_pVGuiSupport ); + } } } } From 07a9c4602daa8527e8b52456ce3a2072004a8e6c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 9 Jun 2022 13:42:16 +0300 Subject: [PATCH 259/548] game_launch: change game with execve on supported platforms --- engine/common/launcher.c | 77 +++++++++++++++++++++++++--------------- game_launch/game.cpp | 69 ++++++++++++++++++++++------------- 2 files changed, 93 insertions(+), 53 deletions(-) diff --git a/engine/common/launcher.c b/engine/common/launcher.c index 7f85ff06..85e71d6a 100644 --- a/engine/common/launcher.c +++ b/engine/common/launcher.c @@ -25,63 +25,82 @@ GNU General Public License for more details. #include #endif -#if XASH_WIN32 -#define XASH_NOCONHOST 1 -#endif - static char szGameDir[128]; // safe place to keep gamedir static int g_iArgc; static char **g_pszArgv; +#if XASH_WIN32 || XASH_POSIX +#define USE_EXECVE_FOR_CHANGE_GAME 1 +#else +#define USE_EXECVE_FOR_CHANGE_GAME 0 +#endif + +#define E_GAME "XASH3D_GAME" // default env dir to start from +#define GAME_PATH "valve" // default dir to start from + void Launcher_ChangeGame( const char *progname ) { +#if USE_EXECVE_FOR_CHANGE_GAME + Host_Shutdown(); + +#if XASH_WIN32 + _putenv_s( E_GAME, progname ); + _execve( szArgv[0], szArgv, environ ); +#else + char envstr[256]; + snprintf( envstr, sizeof( envstr ), E_GAME "=%s", progname ); + putenv( envstr ); + execve( szArgv[0], szArgv, environ ); +#endif + +#else Q_strncpy( szGameDir, progname, sizeof( szGameDir ) - 1 ); Host_Shutdown( ); exit( Host_Main( g_iArgc, g_pszArgv, szGameDir, 1, &Launcher_ChangeGame ) ); +#endif } -#if XASH_NOCONHOST +#if XASH_WIN32 #include #include // CommandLineToArgvW int __stdcall WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int nShow) { - int szArgc; - char **szArgv; - LPWSTR* lpArgv = CommandLineToArgvW(GetCommandLineW(), &szArgc); - int i = 0; + LPWSTR* lpArgv; + int ret, i; - szArgv = (char**)malloc(szArgc*sizeof(char*)); - for (; i < szArgc; ++i) + lpArgv = CommandLineToArgvW( GetCommandLineW(), &szArgc ); + szArgv = ( char** )malloc( (szArgc + 1) * sizeof( char* )); + + for( i = 0; i < szArgc; ++i ) { size_t size = wcslen(lpArgv[i]) + 1; - szArgv[i] = (char*)malloc(size); - wcstombs(szArgv[i], lpArgv[i], size); + + // just in case, allocate some more memory + szArgv[i] = ( char * )malloc( size * sizeof( wchar_t )); + wcstombs( szArgv[i], lpArgv[i], size ); } - szArgv[i] = NULL; + szArgv[szArgc] = 0; - LocalFree(lpArgv); + LocalFree( lpArgv ); - main( szArgc, szArgv ); + ret = main( szArgc, szArgv ); for( i = 0; i < szArgc; ++i ) free( szArgv[i] ); free( szArgv ); + + return ret; } -#endif +#endif // XASH_WIN32 + int main( int argc, char** argv ) { - char gamedir_buf[32] = ""; - const char *gamedir = getenv( "XASH3D_GAMEDIR" ); + const char *game = getenv( E_GAME ); - if( !COM_CheckString( gamedir ) ) - { - gamedir = "valve"; - } - else - { - Q_strncpy( gamedir_buf, gamedir, 32 ); - gamedir = gamedir_buf; - } + if( !game ) + game = GAME_PATH; + + Q_strncpy( szGameDir, gamedir, sizeof( szGameDir )); #if XASH_EMSCRIPTEN #ifdef EMSCRIPTEN_LIB_FS @@ -108,7 +127,7 @@ int main( int argc, char** argv ) IOS_LaunchDialog(); } #endif - return Host_Main( g_iArgc, g_pszArgv, gamedir, 0, &Launcher_ChangeGame ); + return Host_Main( g_iArgc, g_pszArgv, szGameDir, 0, &Launcher_ChangeGame ); } #endif diff --git a/game_launch/game.cpp b/game_launch/game.cpp index 7b637989..78d53b84 100644 --- a/game_launch/game.cpp +++ b/game_launch/game.cpp @@ -14,24 +14,25 @@ GNU General Public License for more details. */ #include "port.h" +#include "build.h" #include #include #include #include +#include -#if defined(__APPLE__) || defined(__unix__) || defined(__HAIKU__) - #define XASHLIB "libxash." OS_LIB_EXT -#elif _WIN32 - #if !__MINGW32__ && _MSC_VER >= 1200 - #define USE_WINMAIN - #endif - #define XASHLIB "xash.dll" - #define dlerror() GetStringLastError() - #include // CommandLineToArgvW +#if XASH_POSIX +#define XASHLIB "libxash." OS_LIB_EXT +#elif XASH_WIN32 +#define XASHLIB "xash.dll" +#define dlerror() GetStringLastError() +#include // CommandLineToArgvW +#else +#error // port me! #endif -#ifdef WIN32 +#ifdef XASH_WIN32 extern "C" { // Enable NVIDIA High Performance Graphics while using Integrated Graphics. @@ -42,6 +43,12 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif +#if XASH_WIN32 || XASH_POSIX +#define USE_EXECVE_FOR_CHANGE_GAME 1 +#else +#define USE_EXECVE_FOR_CHANGE_GAME 0 +#endif + #define E_GAME "XASH3D_GAME" // default env dir to start from #define GAME_PATH "valve" // default dir to start from @@ -54,7 +61,7 @@ static pfnShutdown Xash_Shutdown = NULL; static char szGameDir[128]; // safe place to keep gamedir static int szArgc; static char **szArgv; -static HINSTANCE hEngine; +static HINSTANCE hEngine; static void Xash_Error( const char *szFmt, ... ) { @@ -70,6 +77,7 @@ static void Xash_Error( const char *szFmt, ... ) #else fprintf( stderr, "Xash Error: %s\n", buffer ); #endif + exit( 1 ); } @@ -116,32 +124,44 @@ static void Sys_ChangeGame( const char *progname ) if( !progname || !progname[0] ) Xash_Error( "Sys_ChangeGame: NULL gamedir" ); +#if USE_EXECVE_FOR_CHANGE_GAME +#if XASH_WIN32 + _putenv_s( E_GAME, progname ); + + Sys_UnloadEngine(); + _execve( szArgv[0], szArgv, environ ); +#else + char envstr[256]; + snprintf( envstr, sizeof( envstr ), E_GAME "=%s", progname ); + putenv( envstr ); + + Sys_UnloadEngine(); + execve( szArgv[0], szArgv, environ ); +#endif +#else if( Xash_Shutdown == NULL ) Xash_Error( "Sys_ChangeGame: missed 'Host_Shutdown' export\n" ); + Sys_UnloadEngine(); strncpy( szGameDir, progname, sizeof( szGameDir ) - 1 ); - - Sys_UnloadEngine (); Sys_LoadEngine (); - Xash_Main( szArgc, szArgv, szGameDir, 1, Sys_ChangeGame ); +#endif } _inline int Sys_Start( void ) { int ret; pfnChangeGame changeGame = NULL; + const char *game = getenv( E_GAME ); + + if( !game ) + game = GAME_PATH; Sys_LoadEngine(); -#ifndef XASH_DISABLE_MENU_CHANGEGAME if( Xash_Shutdown ) changeGame = Sys_ChangeGame; -#endif - - const char *game = getenv( E_GAME ); - if( !game ) - game = GAME_PATH; ret = Xash_Main( szArgc, szArgv, game, 0, changeGame ); @@ -150,7 +170,7 @@ _inline int Sys_Start( void ) return ret; } -#ifndef USE_WINMAIN +#if !XASH_WIN32 int main( int argc, char **argv ) { szArgc = argc; @@ -159,7 +179,6 @@ int main( int argc, char **argv ) return Sys_Start(); } #else -//#pragma comment(lib, "shell32.lib") int __stdcall WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int nShow ) { LPWSTR* lpArgv; @@ -170,8 +189,10 @@ int __stdcall WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int for( i = 0; i < szArgc; ++i ) { - int size = wcslen(lpArgv[i]) + 1; - szArgv[i] = ( char* )malloc( size ); + size_t size = wcslen(lpArgv[i]) + 1; + + // just in case, allocate some more memory + szArgv[i] = ( char * )malloc( size * sizeof( wchar_t )); wcstombs( szArgv[i], lpArgv[i], size ); } szArgv[szArgc] = 0; From a717b7fc492c6bcbb0190eb0bd56443c1775b8b8 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 9 Jun 2022 14:44:04 +0300 Subject: [PATCH 260/548] game_launch: declare environ variable, by standard it must be declared by user program --- engine/common/launcher.c | 12 ++++++++---- game_launch/game.cpp | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/engine/common/launcher.c b/engine/common/launcher.c index 85e71d6a..e3ed73a4 100644 --- a/engine/common/launcher.c +++ b/engine/common/launcher.c @@ -25,6 +25,9 @@ GNU General Public License for more details. #include #endif +#include + +extern char **environ; static char szGameDir[128]; // safe place to keep gamedir static int g_iArgc; static char **g_pszArgv; @@ -40,17 +43,18 @@ static char **g_pszArgv; void Launcher_ChangeGame( const char *progname ) { + char envstr[256]; + #if USE_EXECVE_FOR_CHANGE_GAME Host_Shutdown(); #if XASH_WIN32 _putenv_s( E_GAME, progname ); - _execve( szArgv[0], szArgv, environ ); + _execve( g_pszArgv[0], g_pszArgv, _environ ); #else - char envstr[256]; snprintf( envstr, sizeof( envstr ), E_GAME "=%s", progname ); putenv( envstr ); - execve( szArgv[0], szArgv, environ ); + execve( g_pszArgv[0], g_pszArgv, environ ); #endif #else @@ -100,7 +104,7 @@ int main( int argc, char** argv ) if( !game ) game = GAME_PATH; - Q_strncpy( szGameDir, gamedir, sizeof( szGameDir )); + Q_strncpy( szGameDir, game, sizeof( szGameDir )); #if XASH_EMSCRIPTEN #ifdef EMSCRIPTEN_LIB_FS diff --git a/game_launch/game.cpp b/game_launch/game.cpp index 78d53b84..1f50c956 100644 --- a/game_launch/game.cpp +++ b/game_launch/game.cpp @@ -56,6 +56,7 @@ typedef void (*pfnChangeGame)( const char *progname ); typedef int (*pfnInit)( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func ); typedef void (*pfnShutdown)( void ); +extern char **environ; static pfnInit Xash_Main; static pfnShutdown Xash_Shutdown = NULL; static char szGameDir[128]; // safe place to keep gamedir @@ -129,7 +130,7 @@ static void Sys_ChangeGame( const char *progname ) _putenv_s( E_GAME, progname ); Sys_UnloadEngine(); - _execve( szArgv[0], szArgv, environ ); + _execve( szArgv[0], szArgv, _environ ); #else char envstr[256]; snprintf( envstr, sizeof( envstr ), E_GAME "=%s", progname ); From f467d0c807c6756c73c90a179556cf7dce0ac244 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 9 Jun 2022 20:27:27 +0300 Subject: [PATCH 261/548] game_launch: fix Windows build (as suggested by @SNMetamorph) --- engine/common/launcher.c | 6 +++++- game_launch/game.cpp | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/engine/common/launcher.c b/engine/common/launcher.c index e3ed73a4..2a47b597 100644 --- a/engine/common/launcher.c +++ b/engine/common/launcher.c @@ -25,7 +25,11 @@ GNU General Public License for more details. #include #endif -#include +#if XASH_WIN32 +#include // _execve +#else +#include // execve +#endif extern char **environ; static char szGameDir[128]; // safe place to keep gamedir diff --git a/game_launch/game.cpp b/game_launch/game.cpp index 1f50c956..e9ad83c9 100644 --- a/game_launch/game.cpp +++ b/game_launch/game.cpp @@ -20,14 +20,15 @@ GNU General Public License for more details. #include #include #include -#include #if XASH_POSIX #define XASHLIB "libxash." OS_LIB_EXT +#include // execve #elif XASH_WIN32 #define XASHLIB "xash.dll" #define dlerror() GetStringLastError() #include // CommandLineToArgvW +#include // _execve #else #error // port me! #endif From 4c7bf1ff441b7a7b316667ba966ca127c9646a20 Mon Sep 17 00:00:00 2001 From: Velaron Date: Thu, 9 Jun 2022 22:19:02 +0300 Subject: [PATCH 262/548] platform: win32: improve error reporting when loading DLLs and move custom DLL loader to a separate file --- engine/platform/win32/lib_custom_win.c | 478 ++++++++++++++++++ engine/platform/win32/lib_win.c | 655 +++++-------------------- engine/platform/win32/lib_win.h | 24 + 3 files changed, 637 insertions(+), 520 deletions(-) create mode 100644 engine/platform/win32/lib_custom_win.c create mode 100644 engine/platform/win32/lib_win.h diff --git a/engine/platform/win32/lib_custom_win.c b/engine/platform/win32/lib_custom_win.c new file mode 100644 index 00000000..f4dfe4aa --- /dev/null +++ b/engine/platform/win32/lib_custom_win.c @@ -0,0 +1,478 @@ +/* +lib_custom_win.c - win32 custom dlls loader +Copyright (C) 2008 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include "common.h" + +#if XASH_LIB == LIB_WIN32 && XASH_X86 +#include "lib_win.h" + +#define NUMBER_OF_DIRECTORY_ENTRIES 16 +#ifndef IMAGE_SIZEOF_BASE_RELOCATION +#define IMAGE_SIZEOF_BASE_RELOCATION ( sizeof( IMAGE_BASE_RELOCATION )) +#endif + +typedef struct +{ + PIMAGE_NT_HEADERS headers; + byte *codeBase; + void **modules; + int numModules; + int initialized; +} MEMORYMODULE, *PMEMORYMODULE; + +// Protection flags for memory pages (Executable, Readable, Writeable) +static int ProtectionFlags[2][2][2] = +{ +{ +{ PAGE_NOACCESS, PAGE_WRITECOPY }, // not executable +{ PAGE_READONLY, PAGE_READWRITE }, +}, +{ +{ PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY }, // executable +{ PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE }, +}, +}; + +typedef BOOL (WINAPI *DllEntryProc)( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved ); + +#define GET_HEADER_DICTIONARY( module, idx ) &(module)->headers->OptionalHeader.DataDirectory[idx] + +static void CopySections( const byte *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) +{ + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); + byte *codeBase = module->codeBase; + int i, size; + byte *dest; + + for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) + { + if( section->SizeOfRawData == 0 ) + { + // section doesn't contain data in the dll itself, but may define + // uninitialized data + size = old_headers->OptionalHeader.SectionAlignment; + + if( size > 0 ) + { + dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), size, MEM_COMMIT, PAGE_READWRITE ); + section->Misc.PhysicalAddress = (DWORD)dest; + memset( dest, 0, size ); + } + // section is empty + continue; + } + + // commit memory block and copy data from dll + dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE ); + memcpy( dest, (byte *)CALCULATE_ADDRESS(data, section->PointerToRawData), section->SizeOfRawData ); + section->Misc.PhysicalAddress = (DWORD)dest; + } +} + +static void FreeSections( PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) +{ + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); + byte *codeBase = module->codeBase; + int i, size; + + for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) + { + if( section->SizeOfRawData == 0 ) + { + size = old_headers->OptionalHeader.SectionAlignment; + if( size > 0 ) + { + VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), size, MEM_DECOMMIT ); + section->Misc.PhysicalAddress = 0; + } + continue; + } + + VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), section->SizeOfRawData, MEM_DECOMMIT ); + section->Misc.PhysicalAddress = 0; + } +} + +static void FinalizeSections( MEMORYMODULE *module ) +{ + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); + int i; + + // loop through all sections and change access flags + for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) + { + DWORD protect, oldProtect, size; + int executable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; + int readable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0; + int writeable = (section->Characteristics & IMAGE_SCN_MEM_WRITE) != 0; + + if( section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE ) + { + // section is not needed any more and can safely be freed + VirtualFree((LPVOID)section->Misc.PhysicalAddress, section->SizeOfRawData, MEM_DECOMMIT); + continue; + } + + // determine protection flags based on characteristics + protect = ProtectionFlags[executable][readable][writeable]; + if( section->Characteristics & IMAGE_SCN_MEM_NOT_CACHED ) + protect |= PAGE_NOCACHE; + + // determine size of region + size = section->SizeOfRawData; + + if( size == 0 ) + { + if( section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA ) + size = module->headers->OptionalHeader.SizeOfInitializedData; + else if( section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA ) + size = module->headers->OptionalHeader.SizeOfUninitializedData; + } + + if( size > 0 ) + { + // change memory access flags + if( !VirtualProtect((LPVOID)section->Misc.PhysicalAddress, size, protect, &oldProtect )) + Sys_Error( "error protecting memory page\n" ); + } + } +} + +static void PerformBaseRelocation( MEMORYMODULE *module, DWORD delta ) +{ + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_BASERELOC ); + byte *codeBase = module->codeBase; + DWORD i; + + if( directory->Size > 0 ) + { + PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); + for( ; relocation->VirtualAddress > 0; ) + { + byte *dest = (byte *)CALCULATE_ADDRESS( codeBase, relocation->VirtualAddress ); + word *relInfo = (word *)((byte *)relocation + IMAGE_SIZEOF_BASE_RELOCATION ); + + for( i = 0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++ ) + { + DWORD *patchAddrHL; + int type, offset; + + // the upper 4 bits define the type of relocation + type = *relInfo >> 12; + // the lower 12 bits define the offset + offset = *relInfo & 0xfff; + + switch( type ) + { + case IMAGE_REL_BASED_ABSOLUTE: + // skip relocation + break; + case IMAGE_REL_BASED_HIGHLOW: + // change complete 32 bit address + patchAddrHL = (DWORD *)CALCULATE_ADDRESS( dest, offset ); + *patchAddrHL += delta; + break; + default: + Con_Reportf( S_ERROR "PerformBaseRelocation: unknown relocation: %d\n", type ); + break; + } + } + + // advance to next relocation block + relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( relocation, relocation->SizeOfBlock ); + } + } +} + +FARPROC MemoryGetProcAddress( void *module, const char *name ) +{ + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((MEMORYMODULE *)module, IMAGE_DIRECTORY_ENTRY_EXPORT ); + byte *codeBase = ((PMEMORYMODULE)module)->codeBase; + PIMAGE_EXPORT_DIRECTORY exports; + int idx = -1; + DWORD i, *nameRef; + WORD *ordinal; + + if( directory->Size == 0 ) + { + // no export table found + return NULL; + } + + exports = (PIMAGE_EXPORT_DIRECTORY)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); + + if( exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0 ) + { + // DLL doesn't export anything + return NULL; + } + + // search function name in list of exported names + nameRef = (DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNames ); + ordinal = (WORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNameOrdinals ); + + for( i = 0; i < exports->NumberOfNames; i++, nameRef++, ordinal++ ) + { + // GetProcAddress case insensative ????? + if( !Q_stricmp( name, (const char *)CALCULATE_ADDRESS( codeBase, *nameRef ))) + { + idx = *ordinal; + break; + } + } + + if( idx == -1 ) + { + // exported symbol not found + return NULL; + } + + if((DWORD)idx > exports->NumberOfFunctions ) + { + // name <-> ordinal number don't match + return NULL; + } + + // addressOfFunctions contains the RVAs to the "real" functions + return (FARPROC)CALCULATE_ADDRESS( codeBase, *(DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfFunctions + (idx * 4))); +} + +static int BuildImportTable( MEMORYMODULE *module ) +{ + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_IMPORT ); + byte *codeBase = module->codeBase; + int result = 1; + + if( directory->Size > 0 ) + { + PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); + + for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR )) && importDesc->Name; importDesc++ ) + { + DWORD *thunkRef, *funcRef; + LPCSTR libname; + void *handle; + + libname = (LPCSTR)CALCULATE_ADDRESS( codeBase, importDesc->Name ); + handle = COM_LoadLibrary( libname, false, true ); + + if( handle == NULL ) + { + Con_Printf( S_ERROR "couldn't load library %s\n", libname ); + result = 0; + break; + } + + module->modules = (void *)Mem_Realloc( host.mempool, module->modules, (module->numModules + 1) * (sizeof( void* ))); + module->modules[module->numModules++] = handle; + + if( importDesc->OriginalFirstThunk ) + { + thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->OriginalFirstThunk ); + funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); + } + else + { + // no hint table + thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); + funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); + } + + for( ; *thunkRef; thunkRef++, funcRef++ ) + { + LPCSTR funcName; + + if( IMAGE_SNAP_BY_ORDINAL( *thunkRef )) + { + funcName = (LPCSTR)IMAGE_ORDINAL( *thunkRef ); + *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); + } + else + { + PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)CALCULATE_ADDRESS( codeBase, *thunkRef ); + funcName = (LPCSTR)&thunkData->Name; + *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); + } + + if( *funcRef == 0 ) + { + Con_Printf( S_ERROR "%s unable to find address: %s\n", libname, funcName ); + result = 0; + break; + } + } + if( !result ) break; + } + } + return result; +} + +void MemoryFreeLibrary( void *hInstance ) +{ + MEMORYMODULE *module = (MEMORYMODULE *)hInstance; + + if( module != NULL ) + { + int i; + + if( module->initialized != 0 ) + { + // notify library about detaching from process + DllEntryProc DllEntry = (DllEntryProc)CALCULATE_ADDRESS( module->codeBase, module->headers->OptionalHeader.AddressOfEntryPoint ); + (*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0 ); + module->initialized = 0; + } + + if( module->modules != NULL ) + { + // free previously opened libraries + for( i = 0; i < module->numModules; i++ ) + { + if( module->modules[i] != NULL ) + COM_FreeLibrary( module->modules[i] ); + } + Mem_Free( module->modules ); // Mem_Realloc end + } + + FreeSections( module->headers, module ); + + if( module->codeBase != NULL ) + { + // release memory of library + VirtualFree( module->codeBase, 0, MEM_RELEASE ); + } + + HeapFree( GetProcessHeap(), 0, module ); + } +} + +void *MemoryLoadLibrary( const char *name ) +{ + MEMORYMODULE *result = NULL; + PIMAGE_DOS_HEADER dos_header; + PIMAGE_NT_HEADERS old_header; + byte *code, *headers; + DWORD locationDelta; + DllEntryProc DllEntry; + string errorstring; + qboolean successfull; + void *data = NULL; + + data = FS_LoadFile( name, NULL, false ); + + if( !data ) + { + Q_sprintf( errorstring, "couldn't load %s", name ); + goto library_error; + } + + dos_header = (PIMAGE_DOS_HEADER)data; + if( dos_header->e_magic != IMAGE_DOS_SIGNATURE ) + { + Q_sprintf( errorstring, "%s it's not a valid executable file", name ); + goto library_error; + } + + old_header = (PIMAGE_NT_HEADERS)&((const byte *)(data))[dos_header->e_lfanew]; + if( old_header->Signature != IMAGE_NT_SIGNATURE ) + { + Q_sprintf( errorstring, "%s missing PE header", name ); + goto library_error; + } + + // reserve memory for image of library + code = (byte *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase), old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); + + if( code == NULL ) + { + // try to allocate memory at arbitrary position + code = (byte *)VirtualAlloc( NULL, old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); + } + + if( code == NULL ) + { + Q_sprintf( errorstring, "%s can't reserve memory", name ); + goto library_error; + } + + result = (MEMORYMODULE *)HeapAlloc( GetProcessHeap(), 0, sizeof( MEMORYMODULE )); + result->codeBase = code; + result->numModules = 0; + result->modules = NULL; + result->initialized = 0; + + // XXX: is it correct to commit the complete memory region at once? + // calling DllEntry raises an exception if we don't... + VirtualAlloc( code, old_header->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE ); + + // commit memory for headers + headers = (byte *)VirtualAlloc( code, old_header->OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE ); + + // copy PE header to code + memcpy( headers, dos_header, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders ); + result->headers = (PIMAGE_NT_HEADERS)&((const byte *)(headers))[dos_header->e_lfanew]; + + // update position + result->headers->OptionalHeader.ImageBase = (DWORD)code; + + // copy sections from DLL file block to new memory location + CopySections( data, old_header, result ); + + // adjust base address of imported data + locationDelta = (DWORD)(code - old_header->OptionalHeader.ImageBase); + if( locationDelta != 0 ) PerformBaseRelocation( result, locationDelta ); + + // load required dlls and adjust function table of imports + if( !BuildImportTable( result )) + { + Q_sprintf( errorstring, "%s failed to build import table", name ); + goto library_error; + } + + // mark memory pages depending on section headers and release + // sections that are marked as "discardable" + FinalizeSections( result ); + + // get entry point of loaded library + if( result->headers->OptionalHeader.AddressOfEntryPoint != 0 ) + { + DllEntry = (DllEntryProc)CALCULATE_ADDRESS( code, result->headers->OptionalHeader.AddressOfEntryPoint ); + if( DllEntry == 0 ) + { + Q_sprintf( errorstring, "%s has no entry point", name ); + goto library_error; + } + + // notify library about attaching to process + successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0 ); + if( !successfull ) + { + Q_sprintf( errorstring, "can't attach library %s", name ); + goto library_error; + } + result->initialized = 1; + } + + Mem_Free( data ); // release memory + return (void *)result; +library_error: + // cleanup + if( data ) Mem_Free( data ); + MemoryFreeLibrary( result ); + Con_Printf( S_ERROR "LoadLibrary: %s\n", errorstring ); + + return NULL; +} + +#endif // XASH_LIB == LIB_WIN32 && XASH_X86 \ No newline at end of file diff --git a/engine/platform/win32/lib_win.c b/engine/platform/win32/lib_win.c index b5ee3e76..f5133b58 100644 --- a/engine/platform/win32/lib_win.c +++ b/engine/platform/win32/lib_win.c @@ -1,5 +1,5 @@ /* -library.c - custom dlls loader +lin_win.c - win32 dynamic library loading Copyright (C) 2008 Uncle Mike This program is free software: you can redistribute it and/or modify @@ -12,481 +12,11 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#include "platform/platform.h" -#if XASH_LIB == LIB_WIN32 + #include "common.h" -#include "library.h" -#include -#define CALCULATE_ADDRESS( base, offset ) ( ( DWORD )( base ) + ( DWORD )( offset ) ) - -#if XASH_X86 -/* ---------------------------------------------------------------- - - Custom dlls loader - ---------------------------------------------------------------- -*/ - -#define NUMBER_OF_DIRECTORY_ENTRIES 16 -#ifndef IMAGE_SIZEOF_BASE_RELOCATION -#define IMAGE_SIZEOF_BASE_RELOCATION ( sizeof( IMAGE_BASE_RELOCATION )) -#endif - -typedef struct -{ - PIMAGE_NT_HEADERS headers; - byte *codeBase; - void **modules; - int numModules; - int initialized; -} MEMORYMODULE, *PMEMORYMODULE; - -// Protection flags for memory pages (Executable, Readable, Writeable) -static int ProtectionFlags[2][2][2] = -{ -{ -{ PAGE_NOACCESS, PAGE_WRITECOPY }, // not executable -{ PAGE_READONLY, PAGE_READWRITE }, -}, -{ -{ PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY }, // executable -{ PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE }, -}, -}; - -typedef BOOL (WINAPI *DllEntryProc)( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved ); - -#define GET_HEADER_DICTIONARY( module, idx ) &(module)->headers->OptionalHeader.DataDirectory[idx] - -static void CopySections( const byte *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) -{ - PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); - byte *codeBase = module->codeBase; - int i, size; - byte *dest; - - for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) - { - if( section->SizeOfRawData == 0 ) - { - // section doesn't contain data in the dll itself, but may define - // uninitialized data - size = old_headers->OptionalHeader.SectionAlignment; - - if( size > 0 ) - { - dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), size, MEM_COMMIT, PAGE_READWRITE ); - section->Misc.PhysicalAddress = (DWORD)dest; - memset( dest, 0, size ); - } - // section is empty - continue; - } - - // commit memory block and copy data from dll - dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE ); - memcpy( dest, (byte *)CALCULATE_ADDRESS(data, section->PointerToRawData), section->SizeOfRawData ); - section->Misc.PhysicalAddress = (DWORD)dest; - } -} - -static void FreeSections( PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) -{ - PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); - byte *codeBase = module->codeBase; - int i, size; - - for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) - { - if( section->SizeOfRawData == 0 ) - { - size = old_headers->OptionalHeader.SectionAlignment; - if( size > 0 ) - { - VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), size, MEM_DECOMMIT ); - section->Misc.PhysicalAddress = 0; - } - continue; - } - - VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), section->SizeOfRawData, MEM_DECOMMIT ); - section->Misc.PhysicalAddress = 0; - } -} - -static void FinalizeSections( MEMORYMODULE *module ) -{ - PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); - int i; - - // loop through all sections and change access flags - for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) - { - DWORD protect, oldProtect, size; - int executable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; - int readable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0; - int writeable = (section->Characteristics & IMAGE_SCN_MEM_WRITE) != 0; - - if( section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE ) - { - // section is not needed any more and can safely be freed - VirtualFree((LPVOID)section->Misc.PhysicalAddress, section->SizeOfRawData, MEM_DECOMMIT); - continue; - } - - // determine protection flags based on characteristics - protect = ProtectionFlags[executable][readable][writeable]; - if( section->Characteristics & IMAGE_SCN_MEM_NOT_CACHED ) - protect |= PAGE_NOCACHE; - - // determine size of region - size = section->SizeOfRawData; - - if( size == 0 ) - { - if( section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA ) - size = module->headers->OptionalHeader.SizeOfInitializedData; - else if( section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA ) - size = module->headers->OptionalHeader.SizeOfUninitializedData; - } - - if( size > 0 ) - { - // change memory access flags - if( !VirtualProtect((LPVOID)section->Misc.PhysicalAddress, size, protect, &oldProtect )) - Sys_Error( "error protecting memory page\n" ); - } - } -} - -static void PerformBaseRelocation( MEMORYMODULE *module, DWORD delta ) -{ - PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_BASERELOC ); - byte *codeBase = module->codeBase; - DWORD i; - - if( directory->Size > 0 ) - { - PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); - for( ; relocation->VirtualAddress > 0; ) - { - byte *dest = (byte *)CALCULATE_ADDRESS( codeBase, relocation->VirtualAddress ); - word *relInfo = (word *)((byte *)relocation + IMAGE_SIZEOF_BASE_RELOCATION ); - - for( i = 0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++ ) - { - DWORD *patchAddrHL; - int type, offset; - - // the upper 4 bits define the type of relocation - type = *relInfo >> 12; - // the lower 12 bits define the offset - offset = *relInfo & 0xfff; - - switch( type ) - { - case IMAGE_REL_BASED_ABSOLUTE: - // skip relocation - break; - case IMAGE_REL_BASED_HIGHLOW: - // change complete 32 bit address - patchAddrHL = (DWORD *)CALCULATE_ADDRESS( dest, offset ); - *patchAddrHL += delta; - break; - default: - Con_Reportf( S_ERROR "PerformBaseRelocation: unknown relocation: %d\n", type ); - break; - } - } - - // advance to next relocation block - relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( relocation, relocation->SizeOfBlock ); - } - } -} - -static FARPROC MemoryGetProcAddress( void *module, const char *name ) -{ - PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((MEMORYMODULE *)module, IMAGE_DIRECTORY_ENTRY_EXPORT ); - byte *codeBase = ((PMEMORYMODULE)module)->codeBase; - PIMAGE_EXPORT_DIRECTORY exports; - int idx = -1; - DWORD i, *nameRef; - WORD *ordinal; - - if( directory->Size == 0 ) - { - // no export table found - return NULL; - } - - exports = (PIMAGE_EXPORT_DIRECTORY)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); - - if( exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0 ) - { - // DLL doesn't export anything - return NULL; - } - - // search function name in list of exported names - nameRef = (DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNames ); - ordinal = (WORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNameOrdinals ); - - for( i = 0; i < exports->NumberOfNames; i++, nameRef++, ordinal++ ) - { - // GetProcAddress case insensative ????? - if( !Q_stricmp( name, (const char *)CALCULATE_ADDRESS( codeBase, *nameRef ))) - { - idx = *ordinal; - break; - } - } - - if( idx == -1 ) - { - // exported symbol not found - return NULL; - } - - if((DWORD)idx > exports->NumberOfFunctions ) - { - // name <-> ordinal number don't match - return NULL; - } - - // addressOfFunctions contains the RVAs to the "real" functions - return (FARPROC)CALCULATE_ADDRESS( codeBase, *(DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfFunctions + (idx * 4))); -} - -static int BuildImportTable( MEMORYMODULE *module ) -{ - PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_IMPORT ); - byte *codeBase = module->codeBase; - int result = 1; - - if( directory->Size > 0 ) - { - PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); - - for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR )) && importDesc->Name; importDesc++ ) - { - DWORD *thunkRef, *funcRef; - LPCSTR libname; - void *handle; - - libname = (LPCSTR)CALCULATE_ADDRESS( codeBase, importDesc->Name ); - handle = COM_LoadLibrary( libname, false, true ); - - if( handle == NULL ) - { - Con_Printf( S_ERROR "couldn't load library %s\n", libname ); - result = 0; - break; - } - - module->modules = (void *)Mem_Realloc( host.mempool, module->modules, (module->numModules + 1) * (sizeof( void* ))); - module->modules[module->numModules++] = handle; - - if( importDesc->OriginalFirstThunk ) - { - thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->OriginalFirstThunk ); - funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); - } - else - { - // no hint table - thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); - funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); - } - - for( ; *thunkRef; thunkRef++, funcRef++ ) - { - LPCSTR funcName; - - if( IMAGE_SNAP_BY_ORDINAL( *thunkRef )) - { - funcName = (LPCSTR)IMAGE_ORDINAL( *thunkRef ); - *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); - } - else - { - PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)CALCULATE_ADDRESS( codeBase, *thunkRef ); - funcName = (LPCSTR)&thunkData->Name; - *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); - } - - if( *funcRef == 0 ) - { - Con_Printf( S_ERROR "%s unable to find address: %s\n", libname, funcName ); - result = 0; - break; - } - } - if( !result ) break; - } - } - return result; -} - -static void MemoryFreeLibrary( void *hInstance ) -{ - MEMORYMODULE *module = (MEMORYMODULE *)hInstance; - - if( module != NULL ) - { - int i; - - if( module->initialized != 0 ) - { - // notify library about detaching from process - DllEntryProc DllEntry = (DllEntryProc)CALCULATE_ADDRESS( module->codeBase, module->headers->OptionalHeader.AddressOfEntryPoint ); - (*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0 ); - module->initialized = 0; - } - - if( module->modules != NULL ) - { - // free previously opened libraries - for( i = 0; i < module->numModules; i++ ) - { - if( module->modules[i] != NULL ) - COM_FreeLibrary( module->modules[i] ); - } - Mem_Free( module->modules ); // Mem_Realloc end - } - - FreeSections( module->headers, module ); - - if( module->codeBase != NULL ) - { - // release memory of library - VirtualFree( module->codeBase, 0, MEM_RELEASE ); - } - - HeapFree( GetProcessHeap(), 0, module ); - } -} - -void *MemoryLoadLibrary( const char *name ) -{ - MEMORYMODULE *result = NULL; - PIMAGE_DOS_HEADER dos_header; - PIMAGE_NT_HEADERS old_header; - byte *code, *headers; - DWORD locationDelta; - DllEntryProc DllEntry; - string errorstring; - qboolean successfull; - void *data = NULL; - - data = FS_LoadFile( name, NULL, false ); - - if( !data ) - { - Q_sprintf( errorstring, "couldn't load %s", name ); - goto library_error; - } - - dos_header = (PIMAGE_DOS_HEADER)data; - if( dos_header->e_magic != IMAGE_DOS_SIGNATURE ) - { - Q_sprintf( errorstring, "%s it's not a valid executable file", name ); - goto library_error; - } - - old_header = (PIMAGE_NT_HEADERS)&((const byte *)(data))[dos_header->e_lfanew]; - if( old_header->Signature != IMAGE_NT_SIGNATURE ) - { - Q_sprintf( errorstring, "%s missing PE header", name ); - goto library_error; - } - - // reserve memory for image of library - code = (byte *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase), old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); - - if( code == NULL ) - { - // try to allocate memory at arbitrary position - code = (byte *)VirtualAlloc( NULL, old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); - } - - if( code == NULL ) - { - Q_sprintf( errorstring, "%s can't reserve memory", name ); - goto library_error; - } - - result = (MEMORYMODULE *)HeapAlloc( GetProcessHeap(), 0, sizeof( MEMORYMODULE )); - result->codeBase = code; - result->numModules = 0; - result->modules = NULL; - result->initialized = 0; - - // XXX: is it correct to commit the complete memory region at once? - // calling DllEntry raises an exception if we don't... - VirtualAlloc( code, old_header->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE ); - - // commit memory for headers - headers = (byte *)VirtualAlloc( code, old_header->OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE ); - - // copy PE header to code - memcpy( headers, dos_header, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders ); - result->headers = (PIMAGE_NT_HEADERS)&((const byte *)(headers))[dos_header->e_lfanew]; - - // update position - result->headers->OptionalHeader.ImageBase = (DWORD)code; - - // copy sections from DLL file block to new memory location - CopySections( data, old_header, result ); - - // adjust base address of imported data - locationDelta = (DWORD)(code - old_header->OptionalHeader.ImageBase); - if( locationDelta != 0 ) PerformBaseRelocation( result, locationDelta ); - - // load required dlls and adjust function table of imports - if( !BuildImportTable( result )) - { - Q_sprintf( errorstring, "%s failed to build import table", name ); - goto library_error; - } - - // mark memory pages depending on section headers and release - // sections that are marked as "discardable" - FinalizeSections( result ); - - // get entry point of loaded library - if( result->headers->OptionalHeader.AddressOfEntryPoint != 0 ) - { - DllEntry = (DllEntryProc)CALCULATE_ADDRESS( code, result->headers->OptionalHeader.AddressOfEntryPoint ); - if( DllEntry == 0 ) - { - Q_sprintf( errorstring, "%s has no entry point", name ); - goto library_error; - } - - // notify library about attaching to process - successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0 ); - if( !successfull ) - { - Q_sprintf( errorstring, "can't attach library %s", name ); - goto library_error; - } - result->initialized = 1; - } - - Mem_Free( data ); // release memory - return (void *)result; -library_error: - // cleanup - if( data ) Mem_Free( data ); - MemoryFreeLibrary( result ); - Con_Printf( S_ERROR "LoadLibrary: %s\n", errorstring ); - - return NULL; -} -#endif +#if XASH_LIB == LIB_WIN32 +#include "lib_win.h" static DWORD GetOffsetByRVA( DWORD rva, PIMAGE_NT_HEADERS nt_header ) { @@ -764,70 +294,146 @@ table_error: return false; } -qboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname, qboolean directpath ) +static const char *GetLastErrorAsString( void ) { - PIMAGE_DOS_HEADER dosHeader; - PIMAGE_NT_HEADERS peHeader; - PIMAGE_DATA_DIRECTORY importDir; + DWORD errorcode; + static string errormessage; + + errorcode = GetLastError(); + if ( !errorcode ) return ""; + + FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, errorcode, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), + (LPSTR)&errormessage, sizeof( errormessage ), NULL ); + + return errormessage; +} + +static PIMAGE_IMPORT_DESCRIPTOR GetImportDescriptor( const char *name, byte *data, PIMAGE_NT_HEADERS *peheader ) +{ + PIMAGE_DOS_HEADER dosHeader; + PIMAGE_NT_HEADERS peHeader; + PIMAGE_DATA_DIRECTORY importDir; PIMAGE_IMPORT_DESCRIPTOR importDesc; - string errorstring = ""; - byte *data = NULL; - dll_user_t *hInst; - hInst = FS_FindLibrary( name, directpath ); - if( !hInst ) + if ( !data ) { - return false; // nothing to load + Con_Printf( S_ERROR "%s: couldn't load %s\n", __FUNCTION__, name ); + return NULL; } - data = FS_LoadFile( name, NULL, false ); - if( !data ) + dosHeader = (PIMAGE_DOS_HEADER)data; + if ( dosHeader->e_magic != IMAGE_DOS_SIGNATURE ) { - Q_snprintf( errorstring, sizeof( errorstring ), "couldn't load %s", name ); - goto libraryerror; + Con_Printf( S_ERROR "%s: %s is not a valid executable file\n", __FUNCTION__, name ); + return NULL; } - dosHeader = ( PIMAGE_DOS_HEADER )data; - if( dosHeader->e_magic != IMAGE_DOS_SIGNATURE ) + peHeader = (PIMAGE_NT_HEADERS)( data + dosHeader->e_lfanew ); + if ( peHeader->Signature != IMAGE_NT_SIGNATURE ) { - Q_snprintf( errorstring, sizeof( errorstring ), "%s it's not a valid executable file", name ); - goto libraryerror; - } - - peHeader = ( PIMAGE_NT_HEADERS )(data + dosHeader->e_lfanew); - if( peHeader->Signature != IMAGE_NT_SIGNATURE ) - { - Q_snprintf( errorstring, sizeof( errorstring ), "%s missing PE header", name ); - goto libraryerror; + Con_Printf( S_ERROR "%s: %s is missing a PE header\n", __FUNCTION__, name ); + return NULL; } importDir = &peHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; if( importDir->Size <= 0 ) { - Con_Printf( S_WARN "%s: %s has no dependencies. Is this library valid?\n", __FUNCTION__, name ); - goto libraryerror; + Con_Printf( S_ERROR "%s: %s has no dependencies\n", __FUNCTION__, name ); + return NULL; } - importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( data, GetOffsetByRVA(importDir->VirtualAddress, peHeader) ); - for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR)) && importDesc->Name; importDesc++ ) + *peheader = peHeader; + importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDir->VirtualAddress, peHeader ) ); + + return importDesc; +} + +static void ListMissingModules( dll_user_t *hInst ) +{ + DWORD cbNeeded; + PIMAGE_NT_HEADERS peHeader; + PIMAGE_IMPORT_DESCRIPTOR importDesc; + HANDLE hProcess; + byte *data; + + if ( !hInst ) return; + + hProcess = GetCurrentProcess(); + + data = FS_LoadFile( hInst->dllName, NULL, false ); + if ( !data ) + { + CloseHandle( hProcess ); + return; + } + + importDesc = GetImportDescriptor( hInst->dllName, data, &peHeader ); + if ( !importDesc ) + { + CloseHandle( hProcess ); + Mem_Free( data ); + return; + } + + for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR ) ) && importDesc->Name; importDesc++ ) + { + HMODULE hMod; + const char *importName = (const char *)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDesc->Name, peHeader ) ); + + hMod = LoadLibraryEx( importName, NULL, LOAD_LIBRARY_AS_DATAFILE ); + if ( !hMod ) + COM_PushLibraryError( va( "%s not found!", importName ) ); + else + FreeLibrary( hMod ); + } + + CloseHandle( hProcess ); + Mem_Free( data ); + return; +} + +qboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname, qboolean directpath ) +{ + PIMAGE_NT_HEADERS peHeader; + PIMAGE_IMPORT_DESCRIPTOR importDesc; + byte *data; + dll_user_t *hInst; + qboolean ret = FALSE; + + hInst = FS_FindLibrary( name, directpath ); + if ( !hInst ) return FALSE; + + data = FS_LoadFile( name, NULL, false ); + if ( !data ) + { + COM_FreeLibrary( hInst ); + return FALSE; + } + + importDesc = GetImportDescriptor( name, data, &peHeader ); + if ( !importDesc ) + { + COM_FreeLibrary( hInst ); + Mem_Free( data ); + return FALSE; + } + + for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR ) ) && importDesc->Name; importDesc++ ) { const char *importName = (const char *)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDesc->Name, peHeader ) ); - Con_Reportf( "library %s has direct dependency %s\n", name, importName ); - if( !Q_stricmp( importName, depname ) ) + if ( !Q_stricmp( importName, depname ) ) { + COM_FreeLibrary( hInst ); Mem_Free( data ); - return true; + return TRUE; } } -libraryerror: - if( errorstring[0] ) - { - Con_Printf( S_ERROR "%s: %s\n", __FUNCTION__, errorstring ); - } - if( data ) Mem_Free( data ); // release memory - return false; + COM_FreeLibrary( hInst ); + Mem_Free( data ); + return FALSE; } /* @@ -841,12 +447,19 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d { dll_user_t *hInst; + COM_ResetLibraryError(); + hInst = FS_FindLibrary( dllname, directpath ); - if( !hInst ) return NULL; // nothing to load + if( !hInst ) + { + COM_PushLibraryError( va( "Failed to find library %s", dllname ) ); + return NULL; + } if( hInst->encrypted ) { - Con_Printf( S_ERROR "LoadLibrary: couldn't load encrypted library %s\n", dllname ); + COM_PushLibraryError( va( "Library %s is encrypted, cannot load", hInst->shortPath ) ); + COM_FreeLibrary( hInst ); return NULL; } @@ -863,7 +476,11 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d if( !hInst->hInstance ) { - Con_Reportf( "LoadLibrary: Loading %s - failed\n", dllname ); + COM_PushLibraryError( GetLastErrorAsString() ); + + if ( GetLastError() == ERROR_MOD_NOT_FOUND ) + ListMissingModules( hInst ); + COM_FreeLibrary( hInst ); return NULL; } @@ -873,14 +490,12 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d { if( !LibraryLoadSymbols( hInst )) { - Con_Reportf( "LoadLibrary: Loading %s - failed\n", dllname ); + COM_PushLibraryError( va( "Failed to load library %s", dllname ) ); COM_FreeLibrary( hInst ); return NULL; } } - Con_Reportf( "LoadLibrary: Loading %s - ok\n", dllname ); - return hInst; } @@ -944,7 +559,7 @@ void *COM_FunctionFromName( void *hInstance, const char *pName ) if( !Q_strcmp( pName, hInst->names[i] )) { index = hInst->ordinals[i]; - return hInst->funcs[index] + hInst->funcBase; + return (void *)( hInst->funcs[index] + hInst->funcBase ); } } diff --git a/engine/platform/win32/lib_win.h b/engine/platform/win32/lib_win.h new file mode 100644 index 00000000..429dd4f8 --- /dev/null +++ b/engine/platform/win32/lib_win.h @@ -0,0 +1,24 @@ +/* +lib_win.h - common win32 dll definitions +Copyright (C) 2022 Flying With Gauss + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "library.h" +#include +#include + +#define CALCULATE_ADDRESS( base, offset ) ( ( DWORD )( base ) + ( DWORD )( offset ) ) + +FARPROC MemoryGetProcAddress( void *module, const char *name ); +void MemoryFreeLibrary( void *hInstance ); +void *MemoryLoadLibrary( const char *name ); \ No newline at end of file From c10b70cb6143a217d4247bc3d4fbfe803c96a7eb Mon Sep 17 00:00:00 2001 From: Velaron Date: Thu, 9 Jun 2022 22:19:56 +0300 Subject: [PATCH 263/548] game_launch: explicitly check for SDL2 on win32 platforms --- game_launch/game.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/game_launch/game.cpp b/game_launch/game.cpp index e9ad83c9..347f1973 100644 --- a/game_launch/game.cpp +++ b/game_launch/game.cpp @@ -26,6 +26,7 @@ GNU General Public License for more details. #include // execve #elif XASH_WIN32 #define XASHLIB "xash.dll" +#define SDL2LIB "SDL2.dll" #define dlerror() GetStringLastError() #include // CommandLineToArgvW #include // _execve @@ -98,6 +99,15 @@ static const char *GetStringLastError() static void Sys_LoadEngine( void ) { +#if XASH_WIN32 + HMODULE hSdl; + + if ( ( hSdl = LoadLibraryEx( SDL2LIB, NULL, LOAD_LIBRARY_AS_DATAFILE ) ) == NULL ) + Xash_Error("Unable to load the " SDL2LIB ": %s", dlerror() ); + else + FreeLibrary( hSdl ); +#endif + if(( hEngine = LoadLibrary( XASHLIB )) == NULL ) { Xash_Error("Unable to load the " XASHLIB ": %s", dlerror() ); From 341c9dae39bb1675a548b29f8f2f18e6c850aee1 Mon Sep 17 00:00:00 2001 From: Bohdan Shulyar Date: Thu, 9 Jun 2022 22:29:52 +0300 Subject: [PATCH 264/548] paltform: win32: fix typo >_< --- engine/platform/win32/lib_win.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/platform/win32/lib_win.c b/engine/platform/win32/lib_win.c index f5133b58..997123f3 100644 --- a/engine/platform/win32/lib_win.c +++ b/engine/platform/win32/lib_win.c @@ -1,5 +1,5 @@ /* -lin_win.c - win32 dynamic library loading +lib_win.c - win32 dynamic library loading Copyright (C) 2008 Uncle Mike This program is free software: you can redistribute it and/or modify From 437630d26d4529c9bc937b864d51d00194eb470d Mon Sep 17 00:00:00 2001 From: Velaron Date: Fri, 10 Jun 2022 00:06:37 +0300 Subject: [PATCH 265/548] platform: win32: remove forgotten code --- 3rdparty/opus/opus | 1 + engine/platform/win32/lib_win.c | 12 +----------- 2 files changed, 2 insertions(+), 11 deletions(-) create mode 160000 3rdparty/opus/opus diff --git a/3rdparty/opus/opus b/3rdparty/opus/opus new file mode 160000 index 00000000..16395923 --- /dev/null +++ b/3rdparty/opus/opus @@ -0,0 +1 @@ +Subproject commit 1639592368fc2dadc82d63f3be6f17ed0b554d71 diff --git a/engine/platform/win32/lib_win.c b/engine/platform/win32/lib_win.c index 997123f3..21ee1fab 100644 --- a/engine/platform/win32/lib_win.c +++ b/engine/platform/win32/lib_win.c @@ -351,27 +351,18 @@ static PIMAGE_IMPORT_DESCRIPTOR GetImportDescriptor( const char *name, byte *dat static void ListMissingModules( dll_user_t *hInst ) { - DWORD cbNeeded; PIMAGE_NT_HEADERS peHeader; PIMAGE_IMPORT_DESCRIPTOR importDesc; - HANDLE hProcess; byte *data; if ( !hInst ) return; - - hProcess = GetCurrentProcess(); data = FS_LoadFile( hInst->dllName, NULL, false ); - if ( !data ) - { - CloseHandle( hProcess ); - return; - } + if ( !data ) return; importDesc = GetImportDescriptor( hInst->dllName, data, &peHeader ); if ( !importDesc ) { - CloseHandle( hProcess ); Mem_Free( data ); return; } @@ -388,7 +379,6 @@ static void ListMissingModules( dll_user_t *hInst ) FreeLibrary( hMod ); } - CloseHandle( hProcess ); Mem_Free( data ); return; } From 89335938c1ebfe1aa3579b661b4145e68a811dc8 Mon Sep 17 00:00:00 2001 From: Velaron Date: Fri, 10 Jun 2022 09:56:44 +0300 Subject: [PATCH 266/548] remove accidentally added submodule --- 3rdparty/opus/opus | 1 - 1 file changed, 1 deletion(-) delete mode 160000 3rdparty/opus/opus diff --git a/3rdparty/opus/opus b/3rdparty/opus/opus deleted file mode 160000 index 16395923..00000000 --- a/3rdparty/opus/opus +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1639592368fc2dadc82d63f3be6f17ed0b554d71 From 6b4f55c4bfc6816a14d57a7d218ea02b1295dd6c Mon Sep 17 00:00:00 2001 From: Andrey Akhmichin <15944199+nekonomicon@users.noreply.github.com> Date: Fri, 10 Jun 2022 22:12:19 +0500 Subject: [PATCH 267/548] engine: common: filesystem.c: do not load archives with zip extention again --- engine/common/filesystem.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index 0438b153..3b036b73 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -1210,7 +1210,7 @@ static qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loade if( already_loaded ) *already_loaded = false; - if( !Q_stricmp( ext, "pk3" ) || !Q_stricmp( ext, "zip" )) + if( !Q_stricmp( ext, "pk3" ) ) zip = FS_LoadZip( zipfile, &errorcode ); if( zip ) @@ -1254,7 +1254,7 @@ static qboolean FS_AddArchive_Fullpath( const char *file, qboolean *already_load { const char *ext = COM_FileExtension( file ); - if( !Q_stricmp( ext, "zip" ) || !Q_stricmp( ext, "pk3" )) + if( !Q_stricmp( ext, "pk3" ) ) return FS_AddZip_Fullpath( file, already_loaded, flags ); else if ( !Q_stricmp( ext, "pak" )) return FS_AddPak_Fullpath( file, already_loaded, flags ); @@ -1297,7 +1297,7 @@ void FS_AddGameDirectory( const char *dir, uint flags ) // add any Zip package in the directory for( i = 0; i < list.numstrings; i++ ) { - if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "zip" ) || !Q_stricmp( COM_FileExtension( list.strings[i] ), "pk3" )) + if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "pk3" ) ) { Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); FS_AddZip_Fullpath( fullpath, NULL, flags ); From 7c9f5f8ab1478bda25156d9153c05008a8d36f80 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 11 Jun 2022 02:17:04 +0300 Subject: [PATCH 268/548] engine: remove LoadLibrary macros, to avoid possible misuse. Although macros moved to game_launch, it's part of it's own problem from now --- common/port.h | 6 ------ engine/common/system.c | 9 ++++++--- game_launch/game.cpp | 5 ++++- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/common/port.h b/common/port.h index 435de115..ae60122f 100644 --- a/common/port.h +++ b/common/port.h @@ -47,14 +47,8 @@ GNU General Public License for more details. #define O_BINARY 0 #define O_TEXT 0 #define _mkdir( x ) mkdir( x, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) - #define LoadLibrary( x ) dlopen( x, RTLD_NOW ) - #define GetProcAddress( x, y ) dlsym( x, y ) - #define FreeLibrary( x ) dlclose( x ) #elif XASH_DOS4GW #define PATH_SPLITTER "\\" - #define LoadLibrary( x ) (0) - #define GetProcAddress( x, y ) (0) - #define FreeLibrary( x ) (0) #endif typedef void* HANDLE; diff --git a/engine/common/system.c b/engine/common/system.c index 6916fdd1..c41854e8 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -34,6 +34,8 @@ GNU General Public License for more details. #include "menu_int.h" // _UPDATE_PAGE macro +#include "library.h" + qboolean error_on_exit = false; // arg for exit(); #define DEBUG_BREAK @@ -272,7 +274,8 @@ qboolean Sys_LoadLibrary( dll_info_t *dll ) *func->func = NULL; } - if( !dll->link ) dll->link = LoadLibrary ( dll->name ); // environment pathes + if( !dll->link ) + dll->link = COM_LoadLibrary( dll->name, false, true ); // environment pathes // no DLL found if( !dll->link ) @@ -307,7 +310,7 @@ void* Sys_GetProcAddress( dll_info_t *dll, const char* name ) if( !dll || !dll->link ) // invalid desc return NULL; - return (void *)GetProcAddress( dll->link, name ); + return (void *)COM_GetProcAddress( dll->link, name ); } qboolean Sys_FreeLibrary( dll_info_t *dll ) @@ -324,7 +327,7 @@ qboolean Sys_FreeLibrary( dll_info_t *dll ) } else Con_Reportf( "Sys_FreeLibrary: Unloading %s\n", dll->name ); - FreeLibrary( dll->link ); + COM_FreeLibrary( dll->link ); dll->link = NULL; return true; diff --git a/game_launch/game.cpp b/game_launch/game.cpp index 347f1973..5ebac7e8 100644 --- a/game_launch/game.cpp +++ b/game_launch/game.cpp @@ -23,6 +23,9 @@ GNU General Public License for more details. #if XASH_POSIX #define XASHLIB "libxash." OS_LIB_EXT +#define LoadLibrary( x ) dlopen( x, RTLD_NOW ) +#define GetProcAddress( x, y ) dlsym( x, y ) +#define FreeLibrary( x ) dlclose( x ) #include // execve #elif XASH_WIN32 #define XASHLIB "xash.dll" @@ -46,7 +49,7 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; #endif #if XASH_WIN32 || XASH_POSIX -#define USE_EXECVE_FOR_CHANGE_GAME 1 +#define USE_EXECVE_FOR_CHANGE_GAME 0 #else #define USE_EXECVE_FOR_CHANGE_GAME 0 #endif From 6c7d57e1eb7c191fddad52588997396f150d3c4d Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 12 Jun 2022 03:06:03 +0300 Subject: [PATCH 269/548] engine: client: move vgui deinitialization out of CL_UnloadProgs. Delete cls.initialized check in CL_Shutdown, that used to shutdown various client-side subsystems that usually have needed checks by themselves --- engine/client/cl_game.c | 1 - engine/client/cl_main.c | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index 2b64bec0..66a0fc76 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -3914,7 +3914,6 @@ void CL_UnloadProgs( void ) Cvar_FullSet( "host_clientloaded", "0", FCVAR_READ_ONLY ); COM_FreeLibrary( clgame.hInstance ); - VGui_Shutdown(); Mem_FreePool( &cls.mempool ); Mem_FreePool( &clgame.mempool ); memset( &clgame, 0, sizeof( clgame )); diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index c5807555..518f4225 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -3089,10 +3089,6 @@ CL_Shutdown */ void CL_Shutdown( void ) { - // already freed - if( !cls.initialized ) return; - cls.initialized = false; - Con_Printf( "CL_Shutdown()\n" ); if( !host.crashed ) @@ -3109,6 +3105,9 @@ void CL_Shutdown( void ) Mobile_Shutdown (); SCR_Shutdown (); CL_UnloadProgs (); + cls.initialized = false; + + VGui_Shutdown(); FS_Delete( "demoheader.tmp" ); // remove tmp file SCR_FreeCinematic (); // release AVI's *after* client.dll because custom renderer may use them @@ -3116,4 +3115,5 @@ void CL_Shutdown( void ) R_Shutdown (); Con_Shutdown (); + } From acffeb1dadea8dc0b75bf6fa557f8512f951b166 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 12 Jun 2022 03:06:31 +0300 Subject: [PATCH 270/548] wscript: enable gl4es for Android builds --- wscript | 1 + 1 file changed, 1 insertion(+) diff --git a/wscript b/wscript index 2254efa4..0b23397c 100644 --- a/wscript +++ b/wscript @@ -154,6 +154,7 @@ def configure(conf): conf.options.NO_VGUI= True # skip vgui conf.options.NANOGL = True conf.options.GLWES = True + conf.options.GL4ES = True conf.options.GL = False elif conf.env.MAGX: conf.options.USE_SELECT = True From 5524aaae1ec3c3b8c4c0eec155302c5d300ee23e Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 12 Jun 2022 03:07:09 +0300 Subject: [PATCH 271/548] engine: server: fix server dll leak when no map was loaded --- engine/server/sv_main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 44b28e4d..52f314c0 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -1091,6 +1091,8 @@ void SV_Shutdown( const char *finalmsg ) // drop the client if want to load a new map if( CL_IsPlaybackDemo( )) CL_Drop(); + + SV_UnloadProgs (); return; } From 8eec5389fe2a979be502351c2ff3e29442d38128 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 12 Jun 2022 03:07:37 +0300 Subject: [PATCH 272/548] game_launch: fix gamedir pointer being lost --- game_launch/game.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/game_launch/game.cpp b/game_launch/game.cpp index 5ebac7e8..c4da3a55 100644 --- a/game_launch/game.cpp +++ b/game_launch/game.cpp @@ -138,7 +138,6 @@ static void Sys_ChangeGame( const char *progname ) { if( !progname || !progname[0] ) Xash_Error( "Sys_ChangeGame: NULL gamedir" ); - #if USE_EXECVE_FOR_CHANGE_GAME #if XASH_WIN32 _putenv_s( E_GAME, progname ); @@ -157,8 +156,9 @@ static void Sys_ChangeGame( const char *progname ) if( Xash_Shutdown == NULL ) Xash_Error( "Sys_ChangeGame: missed 'Host_Shutdown' export\n" ); - Sys_UnloadEngine(); strncpy( szGameDir, progname, sizeof( szGameDir ) - 1 ); + + Sys_UnloadEngine(); Sys_LoadEngine (); Xash_Main( szArgc, szArgv, szGameDir, 1, Sys_ChangeGame ); #endif From b138a6350234c644c2a23658ec86d802092c684e Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 12 Jun 2022 03:54:35 +0300 Subject: [PATCH 273/548] vgui_support: remove it from the engine --- .gitmodules | 3 - vgui-dev | 1 - vgui_support/Android.mk | 23 -- vgui_support/Makefile.linux | 29 --- vgui_support/cl.bat | 5 - vgui_support/vgui_clip.cpp | 124 ---------- vgui_support/vgui_input.cpp | 91 -------- vgui_support/vgui_int.cpp | 123 ---------- vgui_support/vgui_main.h | 156 ------------- vgui_support/vgui_surf.cpp | 440 ------------------------------------ vgui_support/wscript | 125 ---------- 11 files changed, 1120 deletions(-) delete mode 160000 vgui-dev delete mode 100644 vgui_support/Android.mk delete mode 100644 vgui_support/Makefile.linux delete mode 100644 vgui_support/cl.bat delete mode 100644 vgui_support/vgui_clip.cpp delete mode 100644 vgui_support/vgui_input.cpp delete mode 100644 vgui_support/vgui_int.cpp delete mode 100644 vgui_support/vgui_main.h delete mode 100644 vgui_support/vgui_surf.cpp delete mode 100644 vgui_support/wscript diff --git a/.gitmodules b/.gitmodules index f281fbd6..a3179edb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,9 +7,6 @@ [submodule "ref_gl/gl-wes-v2"] path = ref_gl/gl-wes-v2 url = https://github.com/FWGS/gl-wes-v2 -[submodule "vgui-dev"] - path = vgui-dev - url = https://github.com/FWGS/vgui-dev [submodule "ref_gl/gl4es"] path = ref_gl/gl4es url = https://github.com/ptitSeb/gl4es diff --git a/vgui-dev b/vgui-dev deleted file mode 160000 index 93573075..00000000 --- a/vgui-dev +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 93573075afe885618ea15831e72d44bdacd65bfb diff --git a/vgui_support/Android.mk b/vgui_support/Android.mk deleted file mode 100644 index 78c7db35..00000000 --- a/vgui_support/Android.mk +++ /dev/null @@ -1,23 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_MODULE = vgui_support - -include $(XASH3D_CONFIG) - -LOCAL_CFLAGS = -fsigned-char -DVGUI_TOUCH_SCROLL -DNO_STL -DXASH_GLES -DINTERNAL_VGUI_SUPPORT -Wall -Wextra -Wno-unused-parameter -Wno-unused-variable - -LOCAL_CPPFLAGS = -frtti -fno-exceptions -Wno-write-strings -Wno-invalid-offsetof -std=gnu++98 - -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/../common \ - $(LOCAL_PATH)/../engine \ - $(HLSDK_PATH)/utils/vgui/include \ - $(VGUI_DIR)/include - -LOCAL_SRC_FILES := vgui_clip.cpp vgui_font.cpp vgui_input.cpp vgui_int.cpp vgui_surf.cpp - -LOCAL_STATIC_LIBRARIES := vgui - -include $(BUILD_STATIC_LIBRARY) diff --git a/vgui_support/Makefile.linux b/vgui_support/Makefile.linux deleted file mode 100644 index 8031d116..00000000 --- a/vgui_support/Makefile.linux +++ /dev/null @@ -1,29 +0,0 @@ -CC ?= gcc -m32 -CXX ?= g++ -m32 -std=gnu++98 -CFLAGS ?= -O2 -ggdb -fPIC -TOPDIR = $(PWD)/.. -VGUI_DIR ?= ./vgui-dev -INCLUDES = -I../common -I../engine -I../engine/common -I../engine/client -I../engine/client/vgui -I../pm_shared -INCLUDES += -I$(VGUI_DIR)/include/ -DEFINES = -DNO_STL -%.o : %.cpp - $(CXX) $(CFLAGS) $(INCLUDES) $(DEFINES) -c $< -o $@ - -SRCS = $(wildcard *.cpp) -OBJS = $(SRCS:.cpp=.o) - -libvgui_support.so : $(OBJS) - $(CXX) $(LDFLAGS) -o libvgui_support.so -shared $(OBJS) vgui.so - -.PHONY: depend clean - -clean : - $(RM) $(OBJS) libvgui_support.so - -depend: Makefile.dep - -Makefile.dep: $(SRCS) - rm -f ./Makefile.dep - $(CC) $(CFLAGS) $(INCLUDES) $(DEFINES) -MM $^>>./Makefile.dep - -include Makefile.dep diff --git a/vgui_support/cl.bat b/vgui_support/cl.bat deleted file mode 100644 index c0f59c0d..00000000 --- a/vgui_support/cl.bat +++ /dev/null @@ -1,5 +0,0 @@ - set MSVCDir=Z:\path\to\msvc - set INCLUDE=%MSVCDir%\include - set LIB=%MSVCDir%\lib - set PATH=%MSVCDir%\bin;%PATH% -cl.exe %* \ No newline at end of file diff --git a/vgui_support/vgui_clip.cpp b/vgui_support/vgui_clip.cpp deleted file mode 100644 index 67085577..00000000 --- a/vgui_support/vgui_clip.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* -vgui_clip.cpp - clip in 2D space -Copyright (C) 2011 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the author gives permission -to link the code of this program with VGUI library developed by -Valve, L.L.C ("Valve"). You must obey the GNU General Public License -in all respects for all of the code used other than VGUI library. -If you modify this file, you may extend this exception to your -version of the file, but you are not obligated to do so. If -you do not wish to do so, delete this exception statement -from your version. - -*/ - -#include "vgui_main.h" -#include "wrect.h" - -//----------------------------------------------------------------------------- -// For simulated scissor tests... -//----------------------------------------------------------------------------- -static wrect_t g_ScissorRect; -static qboolean g_bScissor = false; -namespace vgui_support { -//----------------------------------------------------------------------------- -// Enable/disable scissoring... -//----------------------------------------------------------------------------- -void EnableScissor( qboolean enable ) -{ - g_bScissor = enable; -} - -void SetScissorRect( int left, int top, int right, int bottom ) -{ - // Check for a valid rectangle... - assert( left <= right ); - assert( top <= bottom ); - - g_ScissorRect.left = left; - g_ScissorRect.top = top; - g_ScissorRect.right = right; - g_ScissorRect.bottom = bottom; -} - -//----------------------------------------------------------------------------- -// Purpose: Used for clipping, produces an interpolated texture coordinate -//----------------------------------------------------------------------------- -inline float InterpTCoord( float val, float mins, float maxs, float tMin, float tMax ) -{ - float flPercent; - - if( mins != maxs ) - flPercent = (float)(val - mins) / (maxs - mins); - else flPercent = 0.5f; - - return tMin + (tMax - tMin) * flPercent; -} - -//----------------------------------------------------------------------------- -// Purpose: Does a scissor clip of the input rectangle. -// Returns false if it is completely clipped off. -//----------------------------------------------------------------------------- -qboolean ClipRect( const vpoint_t &inUL, const vpoint_t &inLR, vpoint_t *pOutUL, vpoint_t *pOutLR ) -{ - if( g_bScissor ) - { - // pick whichever left side is larger - if( g_ScissorRect.left > inUL.point[0] ) - pOutUL->point[0] = g_ScissorRect.left; - else - pOutUL->point[0] = inUL.point[0]; - - // pick whichever right side is smaller - if( g_ScissorRect.right <= inLR.point[0] ) - pOutLR->point[0] = g_ScissorRect.right; - else - pOutLR->point[0] = inLR.point[0]; - - // pick whichever top side is larger - if( g_ScissorRect.top > inUL.point[1] ) - pOutUL->point[1] = g_ScissorRect.top; - else - pOutUL->point[1] = inUL.point[1]; - - // pick whichever bottom side is smaller - if( g_ScissorRect.bottom <= inLR.point[1] ) - pOutLR->point[1] = g_ScissorRect.bottom; - else - pOutLR->point[1] = inLR.point[1]; - - // Check for non-intersecting - if(( pOutUL->point[0] > pOutLR->point[0] ) || ( pOutUL->point[1] > pOutLR->point[1] )) - { - return false; - } - - pOutUL->coord[0] = InterpTCoord(pOutUL->point[0], - inUL.point[0], inLR.point[0], inUL.coord[0], inLR.coord[0] ); - pOutLR->coord[0] = InterpTCoord(pOutLR->point[0], - inUL.point[0], inLR.point[0], inUL.coord[0], inLR.coord[0] ); - - pOutUL->coord[1] = InterpTCoord(pOutUL->point[1], - inUL.point[1], inLR.point[1], inUL.coord[1], inLR.coord[1] ); - pOutLR->coord[1] = InterpTCoord(pOutLR->point[1], - inUL.point[1], inLR.point[1], inUL.coord[1], inLR.coord[1] ); - } - else - { - *pOutUL = inUL; - *pOutLR = inLR; - } - return true; -} -} diff --git a/vgui_support/vgui_input.cpp b/vgui_support/vgui_input.cpp deleted file mode 100644 index 0a6ee74b..00000000 --- a/vgui_support/vgui_input.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* -vgui_input.cpp - handle kb & mouse -Copyright (C) 2011 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the author gives permission -to link the code of this program with VGUI library developed by -Valve, L.L.C ("Valve"). You must obey the GNU General Public License -in all respects for all of the code used other than VGUI library. -If you modify this file, you may extend this exception to your -version of the file, but you are not obligated to do so. If -you do not wish to do so, delete this exception statement -from your version. - -*/ - -#define OEMRESOURCE // for OCR_* cursor junk - -#include "vgui_main.h" - -namespace vgui_support { -void VGUI_Key(VGUI_KeyAction action, VGUI_KeyCode code) -{ - App *pApp = App::getInstance(); - if(!surface) - return; - switch( action ) - { - case KA_PRESSED: - pApp->internalKeyPressed( (KeyCode) code, surface ); - break; - case KA_RELEASED: - pApp->internalKeyReleased( (KeyCode) code, surface ); - break; - case KA_TYPED: - pApp->internalKeyTyped( (KeyCode) code, surface ); - break; - } - //fprintf(stdout,"vgui_support: VGUI key action %d %d\n", action, code); - //fflush(stdout); -} - -void VGUI_Mouse(VGUI_MouseAction action, int code) -{ - App *pApp = App::getInstance(); - if(!surface) - return; - switch( action ) - { - case MA_PRESSED: - pApp->internalMousePressed( (MouseCode) code, surface ); - break; - case MA_RELEASED: - pApp->internalMouseReleased( (MouseCode) code, surface ); - break; - case MA_DOUBLE: - pApp->internalMouseDoublePressed( (MouseCode) code, surface ); - break; - case MA_WHEEL: - //fprintf(stdout, "vgui_support: VGUI mouse wheeled %d %d\n", action, code); - pApp->internalMouseWheeled( code, surface ); - break; - } - //fprintf(stdout, "vgui_support: VGUI mouse action %d %d\n", action, code); - //fflush(stdout); -} - -void VGUI_MouseMove(int x, int y) -{ - App *pApp = App::getInstance(); - //fprintf(stdout, "vgui_support: VGUI mouse move %d %d %p\n", x, y, surface); - //fflush(stdout); - if(!surface) - return; - pApp->internalCursorMoved( x, y, surface ); -} - -void VGUI_TextInput(const char *text) -{ - // stub -} -} diff --git a/vgui_support/vgui_int.cpp b/vgui_support/vgui_int.cpp deleted file mode 100644 index 1506767a..00000000 --- a/vgui_support/vgui_int.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* -vgui_int.c - vgui dll interaction -Copyright (C) 2011 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the author gives permission -to link the code of this program with VGUI library developed by -Valve, L.L.C ("Valve"). You must obey the GNU General Public License -in all respects for all of the code used other than VGUI library. -If you modify this file, you may extend this exception to your -version of the file, but you are not obligated to do so. If -you do not wish to do so, delete this exception statement -from your version. - -*/ - -#include "vgui_main.h" -#include "xash3d_types.h" -namespace vgui_support { - -vguiapi_t *g_api; - -Panel *rootpanel = NULL; -CEngineSurface *surface = NULL; -CEngineApp staticApp; - -void VGui_Startup( int width, int height ) -{ - if( rootpanel ) - { - rootpanel->setSize( width, height ); - return; - } - - rootpanel = new Panel; - rootpanel->setSize( width, height ); - rootpanel->setPaintBorderEnabled( false ); - rootpanel->setPaintBackgroundEnabled( false ); - rootpanel->setVisible( true ); - rootpanel->setCursor( new Cursor( Cursor::dc_none )); - - staticApp.start(); - staticApp.setMinimumTickMillisInterval( 0 ); - - surface = new CEngineSurface( rootpanel ); - rootpanel->setSurfaceBaseTraverse( surface ); - - - //ASSERT( rootpanel->getApp() != NULL ); - //ASSERT( rootpanel->getSurfaceBase() != NULL ); - - g_api->DrawInit (); -} - -void VGui_Shutdown( void ) -{ - staticApp.stop(); - - delete rootpanel; - delete surface; - - rootpanel = NULL; - surface = NULL; -} - -void VGui_Paint( void ) -{ - int w, h; - - //if( cls.state != ca_active || !rootpanel ) - // return; - if( !g_api->IsInGame() || !rootpanel ) - return; - - // setup the base panel to cover the screen - Panel *pVPanel = surface->getEmbeddedPanel(); - if( !pVPanel ) return; - //SDL_GetWindowSize(host.hWnd, &w, &h); - //host.input_enabled = rootpanel->isVisible(); - rootpanel->getSize(w, h); - EnableScissor( true ); - - staticApp.externalTick (); - - pVPanel->setBounds( 0, 0, w, h ); - pVPanel->repaint(); - - // paint everything - pVPanel->paintTraverse(); - - EnableScissor( false ); -} -void *VGui_GetPanel( void ) -{ - return (void *)rootpanel; -} -} - -#ifdef INTERNAL_VGUI_SUPPORT -#define InitAPI InitVGUISupportAPI -#endif - -extern "C" EXPORT void InitAPI(vguiapi_t * api) -{ - g_api = api; - g_api->Startup = VGui_Startup; - g_api->Shutdown = VGui_Shutdown; - g_api->GetPanel = VGui_GetPanel; - g_api->Paint = VGui_Paint; - g_api->Mouse = VGUI_Mouse; - g_api->MouseMove = VGUI_MouseMove; - g_api->Key = VGUI_Key; - g_api->TextInput = VGUI_TextInput; -} diff --git a/vgui_support/vgui_main.h b/vgui_support/vgui_main.h deleted file mode 100644 index 5819531d..00000000 --- a/vgui_support/vgui_main.h +++ /dev/null @@ -1,156 +0,0 @@ -/* -vgui_main.h - vgui main header -Copyright (C) 2011 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the author gives permission -to link the code of this program with VGUI library developed by -Valve, L.L.C ("Valve"). You must obey the GNU General Public License -in all respects for all of the code used other than VGUI library. -If you modify this file, you may extend this exception to your -version of the file, but you are not obligated to do so. If -you do not wish to do so, delete this exception statement -from your version. - -*/ -#ifndef VGUI_MAIN_H -#define VGUI_MAIN_H - -#ifdef _WIN32 -#include -#else -#include -#endif - -#include - -#include "vgui_api.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace vgui_support -{ -extern vguiapi_t *g_api; - -using namespace vgui; - -struct PaintStack -{ - Panel *m_pPanel; - int iTranslateX; - int iTranslateY; - int iScissorLeft; - int iScissorRight; - int iScissorTop; - int iScissorBottom; -}; - -class CEngineSurface : public SurfaceBase -{ -private: - - // point translation for current panel - int _translateX; - int _translateY; - - // the size of the window to draw into - int _surfaceExtents[4]; - - void SetupPaintState( const PaintStack *paintState ); - void InitVertex( vpoint_t &vertex, int x, int y, float u, float v ); -public: - CEngineSurface( Panel *embeddedPanel ); - ~CEngineSurface(); -public: - virtual Panel *getEmbeddedPanel( void ); - virtual bool setFullscreenMode( int wide, int tall, int bpp ); - virtual void setWindowedMode( void ); - virtual void setTitle( const char *title ) { } - virtual void createPopup( Panel* embeddedPanel ) { } - virtual bool isWithin( int x, int y ) { return true; } - virtual bool hasFocus( void ); - // now it's not abstract class, yay - virtual void GetMousePos(int &x, int &y) { - g_api->GetCursorPos(&x, &y); - } - void drawPrintChar(int x, int y, int wide, int tall, float s0, float t0, float s1, float t1, int color[]); -protected: - virtual int createNewTextureID( void ); - virtual void drawSetColor( int r, int g, int b, int a ); - virtual void drawSetTextColor( int r, int g, int b, int a ); - virtual void drawFilledRect( int x0, int y0, int x1, int y1 ); - virtual void drawOutlinedRect( int x0,int y0,int x1,int y1 ); - virtual void drawSetTextFont( Font *font ); - virtual void drawSetTextPos( int x, int y ); - virtual void drawPrintText( const char* text, int textLen ); - virtual void drawSetTextureRGBA( int id, const char* rgba, int wide, int tall ); - virtual void drawSetTexture( int id ); - virtual void drawTexturedRect( int x0, int y0, int x1, int y1 ); - virtual bool createPlat( void ) { return false; } - virtual bool recreateContext( void ) { return false; } - virtual void setCursor( Cursor* cursor ); - virtual void pushMakeCurrent( Panel* panel, bool useInsets ); - virtual void popMakeCurrent( Panel* panel ); - - // not used in engine instance - virtual void enableMouseCapture( bool state ) { } - virtual void invalidate( Panel *panel ) { } - virtual void setAsTopMost( bool state ) { } - virtual void applyChanges( void ) { } - virtual void swapBuffers( void ) { } -protected: - Cursor* _hCurrentCursor; - int _drawTextPos[2]; - int _drawColor[4]; - int _drawTextColor[4]; - friend class App; - friend class Panel; -}; - -// initialize VGUI::App as external (part of engine) -class CEngineApp : public App -{ -public: - CEngineApp( bool externalMain = true ) : App( externalMain ) { } - virtual void main( int argc, char* argv[] ) { } // stub -}; - -// -// vgui_input.cpp -// -void *VGui_GetPanel( void ); -void VGui_Paint( void ); -void VGUI_Mouse(VGUI_MouseAction action, int code); -void VGUI_Key(VGUI_KeyAction action, VGUI_KeyCode code); -void VGUI_MouseMove(int x, int y); -void VGUI_TextInput(const char *text); - -// -// vgui_clip.cpp -// -void EnableScissor( qboolean enable ); -void SetScissorRect( int left, int top, int right, int bottom ); -qboolean ClipRect( const vpoint_t &inUL, const vpoint_t &inLR, vpoint_t *pOutUL, vpoint_t *pOutLR ); - -extern CEngineSurface *surface; -extern Panel *root; -} -using namespace vgui_support; -#endif//VGUI_MAIN_H diff --git a/vgui_support/vgui_surf.cpp b/vgui_support/vgui_surf.cpp deleted file mode 100644 index 21802dab..00000000 --- a/vgui_support/vgui_surf.cpp +++ /dev/null @@ -1,440 +0,0 @@ -/* -vgui_surf.cpp - main vgui layer -Copyright (C) 2011 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the author gives permission -to link the code of this program with VGUI library developed by -Valve, L.L.C ("Valve"). You must obey the GNU General Public License -in all respects for all of the code used other than VGUI library. -If you modify this file, you may extend this exception to your -version of the file, but you are not obligated to do so. If -you do not wish to do so, delete this exception statement -from your version. - -*/ - -#include -#include "vgui_main.h" - -#define MAX_PAINT_STACK 16 -#define FONT_SIZE 512 -#define FONT_PAGES 8 - -struct FontInfo -{ - int id; - int pageCount; - int pageForChar[256]; - int bindIndex[FONT_PAGES]; - float texCoord[256][FONT_PAGES]; - int contextCount; -}; - -static int staticContextCount = 0; -static char staticRGBA[FONT_SIZE * FONT_SIZE * 4]; -static Font* staticFont = NULL; -static FontInfo* staticFontInfo; -static Dar staticFontInfoDar; -static PaintStack paintStack[MAX_PAINT_STACK]; -static int staticPaintStackPos = 0; - -#define ColorIndex( c )((( c ) - '0' ) & 7 ) - -CEngineSurface :: CEngineSurface( Panel *embeddedPanel ):SurfaceBase( embeddedPanel ) -{ - _embeddedPanel = embeddedPanel; - _drawColor[0] = _drawColor[1] = _drawColor[2] = _drawColor[3] = 255; - _drawTextColor[0] = _drawTextColor[1] = _drawTextColor[2] = _drawTextColor[3] = 255; - - _surfaceExtents[0] = _surfaceExtents[1] = 0; - //_surfaceExtents[2] = menu.globals->scrWidth; - //_surfaceExtents[3] = menu.globals->scrHeight; - embeddedPanel->getSize(_surfaceExtents[2], _surfaceExtents[3]); - _drawTextPos[0] = _drawTextPos[1] = 0; - _hCurrentCursor = null; - - staticFont = NULL; - staticFontInfo = NULL; - staticFontInfoDar.setCount( 0 ); - staticPaintStackPos = 0; - staticContextCount++; - - _translateX = _translateY = 0; -} - -CEngineSurface :: ~CEngineSurface( void ) -{ - g_api->DrawShutdown (); -} - -Panel *CEngineSurface :: getEmbeddedPanel( void ) -{ - return _embeddedPanel; -} - -bool CEngineSurface :: hasFocus( void ) -{ - // What differs when window does not has focus? - //return host.state != HOST_NOFOCUS; - return true; -} - -void CEngineSurface :: setCursor( Cursor *cursor ) -{ - _currentCursor = cursor; - - if( cursor ) - { - g_api->CursorSelect( (VGUI_DefaultCursor)cursor->getDefaultCursor() ); - } -} - -void CEngineSurface :: SetupPaintState( const PaintStack *paintState ) -{ - _translateX = paintState->iTranslateX; - _translateY = paintState->iTranslateY; - SetScissorRect( paintState->iScissorLeft, paintState->iScissorTop, - paintState->iScissorRight, paintState->iScissorBottom ); -} - -void CEngineSurface :: InitVertex( vpoint_t &vertex, int x, int y, float u, float v ) -{ - vertex.point[0] = x + _translateX; - vertex.point[1] = y + _translateY; - vertex.coord[0] = u; - vertex.coord[1] = v; -} - -int CEngineSurface :: createNewTextureID( void ) -{ - return g_api->GenerateTexture(); -} - -void CEngineSurface :: drawSetColor( int r, int g, int b, int a ) -{ - _drawColor[0] = r; - _drawColor[1] = g; - _drawColor[2] = b; - _drawColor[3] = a; -} - -void CEngineSurface :: drawSetTextColor( int r, int g, int b, int a ) -{ - _drawTextColor[0] = r; - _drawTextColor[1] = g; - _drawTextColor[2] = b; - _drawTextColor[3] = a; -} - -void CEngineSurface :: drawFilledRect( int x0, int y0, int x1, int y1 ) -{ - vpoint_t rect[2]; - vpoint_t clippedRect[2]; - - if( _drawColor[3] >= 255 ) return; - - InitVertex( rect[0], x0, y0, 0, 0 ); - InitVertex( rect[1], x1, y1, 0, 0 ); - - // fully clipped? - if( !ClipRect( rect[0], rect[1], &clippedRect[0], &clippedRect[1] )) - return; - - g_api->SetupDrawingRect( _drawColor ); - g_api->EnableTexture( false ); - g_api->DrawQuad( &clippedRect[0], &clippedRect[1] ); - g_api->EnableTexture( true ); -} - -void CEngineSurface :: drawOutlinedRect( int x0, int y0, int x1, int y1 ) -{ - if( _drawColor[3] >= 255 ) return; - - drawFilledRect( x0, y0, x1, y0 + 1 ); // top - drawFilledRect( x0, y1 - 1, x1, y1 ); // bottom - drawFilledRect( x0, y0 + 1, x0 + 1, y1 - 1 ); // left - drawFilledRect( x1 - 1, y0 + 1, x1, y1 - 1 ); // right -} - -void CEngineSurface :: drawSetTextFont( Font *font ) -{ - staticFont = font; - - if( font ) - { - bool buildFont = false; - - staticFontInfo = NULL; - - for( int i = 0; i < staticFontInfoDar.getCount(); i++ ) - { - if( staticFontInfoDar[i]->id == font->getId( )) - { - staticFontInfo = staticFontInfoDar[i]; - if( staticFontInfo->contextCount != staticContextCount ) - buildFont = true; - } - } - - if( !staticFontInfo || buildFont ) - { - staticFontInfo = new FontInfo; - staticFontInfo->id = 0; - staticFontInfo->pageCount = 0; - staticFontInfo->bindIndex[0] = 0; - staticFontInfo->bindIndex[1] = 0; - staticFontInfo->bindIndex[2] = 0; - staticFontInfo->bindIndex[3] = 0; - memset( staticFontInfo->pageForChar, 0, sizeof( staticFontInfo->pageForChar )); - staticFontInfo->contextCount = -1; - staticFontInfo->id = staticFont->getId(); - staticFontInfoDar.putElement( staticFontInfo ); - staticFontInfo->contextCount = staticContextCount; - - int currentPage = 0; - int x = 0, y = 0; - - memset( staticRGBA, 0, sizeof( staticRGBA )); - - for( int i = 0; i < 256; i++ ) - { - int abcA, abcB, abcC; - staticFont->getCharABCwide( i, abcA, abcB, abcC ); - - int wide = abcB; - - if( isspace( i )) continue; - - int tall = staticFont->getTall(); - - if( x + wide + 1 > FONT_SIZE ) - { - x = 0; - y += tall + 1; - } - - if( y + tall + 1 > FONT_SIZE ) - { - if( !staticFontInfo->bindIndex[currentPage] ) - staticFontInfo->bindIndex[currentPage] = createNewTextureID(); - drawSetTextureRGBA( staticFontInfo->bindIndex[currentPage], staticRGBA, FONT_SIZE, FONT_SIZE ); - currentPage++; - - if( currentPage == FONT_PAGES ) - break; - - memset( staticRGBA, 0, sizeof( staticRGBA )); - x = y = 0; - } - - staticFont->getCharRGBA( i, x, y, FONT_SIZE, FONT_SIZE, (byte *)staticRGBA ); - staticFontInfo->pageForChar[i] = currentPage; - staticFontInfo->texCoord[i][0] = (float)((double)x / (double)FONT_SIZE ); - staticFontInfo->texCoord[i][1] = (float)((double)y / (double)FONT_SIZE ); - staticFontInfo->texCoord[i][2] = (float)((double)(x + wide)/(double)FONT_SIZE ); - staticFontInfo->texCoord[i][3] = (float)((double)(y + tall)/(double)FONT_SIZE ); - x += wide + 1; - } - - if( currentPage != FONT_PAGES ) - { - if( !staticFontInfo->bindIndex[currentPage] ) - staticFontInfo->bindIndex[currentPage] = createNewTextureID(); - drawSetTextureRGBA( staticFontInfo->bindIndex[currentPage], staticRGBA, FONT_SIZE, FONT_SIZE ); - } - staticFontInfo->pageCount = currentPage + 1; - } - } -} - -void CEngineSurface :: drawSetTextPos( int x, int y ) -{ - _drawTextPos[0] = x; - _drawTextPos[1] = y; -} - -void CEngineSurface :: drawPrintChar( int x, int y, int wide, int tall, float s0, float t0, float s1, float t1, int color[4] ) -{ - vpoint_t ul, lr; - - ul.point[0] = x; - ul.point[1] = y; - lr.point[0] = x + wide; - lr.point[1] = y + tall; - - // gets at the texture coords for this character in its texture page - ul.coord[0] = s0; - ul.coord[1] = t0; - lr.coord[0] = s1; - lr.coord[1] = t1; - - vpoint_t clippedRect[2]; - - if( !ClipRect( ul, lr, &clippedRect[0], &clippedRect[1] )) - return; - - g_api->SetupDrawingImage( color ); - g_api->DrawQuad( &clippedRect[0], &clippedRect[1] ); // draw the letter -} - -void CEngineSurface :: drawPrintText( const char* text, int textLen ) -{ - //return; - static bool hasColor = 0; - static int numColor = 7; - - if( !text || !staticFont || _drawTextColor[3] >= 255 ) - return; - - int x = _drawTextPos[0] + _translateX; - int y = _drawTextPos[1] + _translateY; - - int tall = staticFont->getTall(); - - int j, iTotalWidth = 0; - int curTextColor[4]; - - // HACKHACK: allow color strings in VGUI - if( numColor != 7 ) - { - for( j = 0; j < 3; j++ ) // grab predefined color - curTextColor[j] = g_api->GetColor(numColor,j); - } - else - { - for( j = 0; j < 3; j++ ) // revert default color - curTextColor[j] = _drawTextColor[j]; - } - curTextColor[3] = _drawTextColor[3]; // copy alpha - - if( textLen == 1 ) - { - if( *text == '^' ) - { - hasColor = true; - return; // skip '^' - } - else if( hasColor && isdigit( *text )) - { - numColor = ColorIndex( *text ); - hasColor = false; // handled - return; // skip colornum - } - else hasColor = false; - } - for( int i = 0; i < textLen; i++ ) - { - int curCh = g_api->ProcessUtfChar( (unsigned char)text[i] ); - if( !curCh ) - { - continue; - } - - int abcA, abcB, abcC; - - staticFont->getCharABCwide( curCh, abcA, abcB, abcC ); - - float s0 = staticFontInfo->texCoord[curCh][0]; - float t0 = staticFontInfo->texCoord[curCh][1]; - float s1 = staticFontInfo->texCoord[curCh][2]; - float t1 = staticFontInfo->texCoord[curCh][3]; - int wide = abcB; - - iTotalWidth += abcA; - drawSetTexture( staticFontInfo->bindIndex[staticFontInfo->pageForChar[curCh]] ); - drawPrintChar( x + iTotalWidth, y, wide, tall, s0, t0, s1, t1, curTextColor ); - iTotalWidth += wide + abcC; - } - - _drawTextPos[0] += iTotalWidth; -} - -void CEngineSurface :: drawSetTextureRGBA( int id, const char* rgba, int wide, int tall ) -{ - g_api->UploadTexture( id, rgba, wide, tall ); -} - -void CEngineSurface :: drawSetTexture( int id ) -{ - g_api->BindTexture( id ); -} - -void CEngineSurface :: drawTexturedRect( int x0, int y0, int x1, int y1 ) -{ - vpoint_t rect[2]; - vpoint_t clippedRect[2]; - - InitVertex( rect[0], x0, y0, 0, 0 ); - InitVertex( rect[1], x1, y1, 1, 1 ); - - // fully clipped? - if( !ClipRect( rect[0], rect[1], &clippedRect[0], &clippedRect[1] )) - return; - - g_api->SetupDrawingImage( _drawColor ); - g_api->DrawQuad( &clippedRect[0], &clippedRect[1] ); -} - -void CEngineSurface :: pushMakeCurrent( Panel* panel, bool useInsets ) -{ - int insets[4] = { 0, 0, 0, 0 }; - int absExtents[4]; - int clipRect[4]; - - if( useInsets ) - panel->getInset( insets[0], insets[1], insets[2], insets[3] ); - panel->getAbsExtents( absExtents[0], absExtents[1], absExtents[2], absExtents[3] ); - panel->getClipRect( clipRect[0], clipRect[1], clipRect[2], clipRect[3] ); - - PaintStack *paintState = &paintStack[staticPaintStackPos]; - - assert( staticPaintStackPos < MAX_PAINT_STACK ); - - paintState->m_pPanel = panel; - - // determine corrected top left origin - paintState->iTranslateX = insets[0] + absExtents[0]; - paintState->iTranslateY = insets[1] + absExtents[1]; - // setup clipping rectangle for scissoring - paintState->iScissorLeft = clipRect[0]; - paintState->iScissorTop = clipRect[1]; - paintState->iScissorRight = clipRect[2]; - paintState->iScissorBottom = clipRect[3]; - - SetupPaintState( paintState ); - staticPaintStackPos++; -} - -void CEngineSurface :: popMakeCurrent( Panel *panel ) -{ - int top = staticPaintStackPos - 1; - - // more pops that pushes? - assert( top >= 0 ); - - // didn't pop in reverse order of push? - assert( paintStack[top].m_pPanel == panel ); - - staticPaintStackPos--; - - if( top > 0 ) SetupPaintState( &paintStack[top-1] ); -} - -bool CEngineSurface :: setFullscreenMode( int wide, int tall, int bpp ) -{ - return false; -} - -void CEngineSurface :: setWindowedMode( void ) -{ -} diff --git a/vgui_support/wscript b/vgui_support/wscript deleted file mode 100644 index b3613ec2..00000000 --- a/vgui_support/wscript +++ /dev/null @@ -1,125 +0,0 @@ -#! /usr/bin/env python -# encoding: utf-8 -# mittorn, 2018 - -from waflib import Logs -import os - -top = '.' - -VGUI_SUPPORTED_OS = ['win32', 'darwin', 'linux'] - -def options(opt): - grp = opt.add_option_group('VGUI options') - grp.add_option('--vgui', action = 'store', dest = 'VGUI_DEV', default='vgui-dev', - help = 'path to vgui-dev repo [default: %default]') - - grp.add_option('--disable-vgui', action = 'store_true', dest = 'NO_VGUI', default = False, - help = 'disable vgui_support [default: %default]') - - grp.add_option('--skip-vgui-sanity-check', action = 'store_false', dest = 'VGUI_SANITY_CHECK', default=False, - help = 'skip checking VGUI sanity [default: %default]' ) - return - -def configure(conf): - conf.env.NO_VGUI = conf.options.NO_VGUI - if conf.options.NO_VGUI: - return - - conf.start_msg('Does this architecture support VGUI?') - - if conf.env.DEST_CPU != 'x86' and not (conf.env.DEST_CPU == 'x86_64' and not conf.options.ALLOW64): - conf.end_msg('no') - Logs.warn('vgui is not supported on this CPU: ' + str(conf.env.DEST_CPU)) - conf.env.NO_VGUI = True - return - else: - conf.end_msg('yes') - - conf.start_msg('Does this OS support VGUI?') - if conf.env.DEST_OS not in VGUI_SUPPORTED_OS: - conf.end_msg('no') - Logs.warn('vgui is not supported on this OS: ' + str(conf.env.DEST_OS)) - conf.env.NO_VGUI = True - return - else: - conf.end_msg('yes') - - conf.start_msg('Does this toolchain able to link VGUI?') - if conf.env.DEST_OS == 'win32' and conf.env.COMPILER_CXX == 'g++': - conf.end_msg('no') - # we have ABI incompatibility ONLY on MinGW - Logs.warn('vgui_support can\'t be built with MinGW') - conf.env.NO_VGUI = True - return - else: - conf.end_msg('yes') - - if conf.env.NO_VGUI: - return - - if conf.options.VGUI_DEV: - conf.start_msg('Configuring VGUI by provided path') - conf.env.VGUI_DEV = conf.options.VGUI_DEV - else: - conf.start_msg('Configuring VGUI by default path') - conf.env.VGUI_DEV = 'vgui-dev' - - if conf.env.DEST_OS == 'win32': - conf.env.LIB_VGUI = ['vgui'] - conf.env.LIBPATH_VGUI = [os.path.abspath(os.path.join(conf.env.VGUI_DEV, 'lib/win32_vc6/'))] - else: - libpath = os.path.abspath(os.path.join(conf.env.VGUI_DEV, 'lib')) - if conf.env.DEST_OS == 'linux': - conf.env.LIB_VGUI = [':vgui.so'] - conf.env.LIBPATH_VGUI = [libpath] - elif conf.env.DEST_OS == 'darwin': - conf.env.LDFLAGS_VGUI = [os.path.join(libpath, 'vgui.dylib')] - else: - conf.fatal('vgui is not supported on this OS: ' + conf.env.DEST_OS) - conf.env.INCLUDES_VGUI = [os.path.abspath(os.path.join(conf.env.VGUI_DEV, 'include'))] - - conf.env.HAVE_VGUI = 1 - conf.end_msg('yes: {0}, {1}, {2}'.format(conf.env.LIB_VGUI, conf.env.LIBPATH_VGUI, conf.env.INCLUDES_VGUI)) - - if conf.env.HAVE_VGUI and conf.options.VGUI_SANITY_CHECK: - try: - conf.check_cxx( - fragment=''' - #include - int main( int argc, char **argv ) - { - return 0; - }''', - msg = 'Checking for library VGUI sanity', - use = 'VGUI', - execute = False) - except conf.errors.ConfigurationError: - conf.fatal("Can't compile simple program. Check your path to vgui-dev repository.") - -def build(bld): - if bld.env.NO_VGUI: - return - - libs = [] - - # basic build: dedicated only, no dependencies - if bld.env.DEST_OS != 'win32': - libs += ['DL','M'] - - libs.append('VGUI') - - source = bld.path.ant_glob(['*.cpp']) - - includes = [ '.', '../common', '../engine', '../public' ] - - bld.shlib( - source = source, - target = 'vgui_support', - features = 'cxx', - includes = includes, - use = libs, - rpath = '.', - install_path = bld.env.LIBDIR, - subsystem = bld.env.MSVC_SUBSYSTEM - ) From 5b0b9174680166e22b027dcbbad2f4ca2c7b1b97 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 12 Jun 2022 04:15:41 +0300 Subject: [PATCH 274/548] vgui_support: re-add as submodule --- .gitmodules | 3 +++ vgui_support | 1 + 2 files changed, 4 insertions(+) create mode 160000 vgui_support diff --git a/.gitmodules b/.gitmodules index a3179edb..d16a4037 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "ref_gl/gl4es"] path = ref_gl/gl4es url = https://github.com/ptitSeb/gl4es +[submodule "vgui_support"] + path = vgui_support + url = https://github.com/FWGS/vgui_support diff --git a/vgui_support b/vgui_support new file mode 160000 index 00000000..2c17ffff --- /dev/null +++ b/vgui_support @@ -0,0 +1 @@ +Subproject commit 2c17ffffdb9afc282fffe45c863868485064b1fb From f63dd7226039b16d6eb176b8c869dd68884125ac Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 12 Jun 2022 04:16:41 +0300 Subject: [PATCH 275/548] scripts: gha: fix including vgui.dll --- scripts/gha/build_linux.sh | 2 +- scripts/gha/build_win32.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/gha/build_linux.sh b/scripts/gha/build_linux.sh index c238e8d6..a98afa32 100755 --- a/scripts/gha/build_linux.sh +++ b/scripts/gha/build_linux.sh @@ -67,7 +67,7 @@ build_appimage() cp SDL2_linux/lib/libSDL2-2.0.so.0 "$APPDIR/" if [ "$ARCH" = "i386" ]; then - cp vgui-dev/lib/vgui.so "$APPDIR/" + cp vgui_support/vgui-dev/lib/vgui.so "$APPDIR/" fi cat > "$APPDIR"/AppRun << 'EOF' diff --git a/scripts/gha/build_win32.sh b/scripts/gha/build_win32.sh index e52f6ddf..5f36d124 100755 --- a/scripts/gha/build_win32.sh +++ b/scripts/gha/build_win32.sh @@ -17,7 +17,7 @@ fi if [ "$ARCH" = "i386" ]; then cp SDL2_VC/lib/x86/SDL2.dll . # Install SDL2 - cp vgui-dev/lib/win32_vc6/vgui.dll . + cp vgui_support/vgui-dev/lib/win32_vc6/vgui.dll . elif [ "$ARCH" = "amd64" ]; then cp SDL2_VC/lib/x64/SDL2.dll . else From c69ea00e793119131feb114a5f61f8877e9b7fa9 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 12 Jun 2022 04:25:09 +0300 Subject: [PATCH 276/548] wscript: better _FILE_OFFSET_BITS check, as suggested by @nekonomicon --- wscript | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/wscript b/wscript index 0b23397c..378c29b6 100644 --- a/wscript +++ b/wscript @@ -301,12 +301,17 @@ def configure(conf): # set _FILE_OFFSET_BITS=64 for filesystems with 64-bit inodes if conf.env.DEST_OS != 'win32' and conf.env.DEST_SIZEOF_VOID_P == 4: - file_offset_bits_usable = conf.check_cc(fragment='''#define _FILE_OFFSET_BITS 64 - #include - #ifndef __USE_FILE_OFFSET64 - #error - #endif - int main(void){ return 0; }''', + # check was borrowed from libarchive source code + file_offset_bits_usable = conf.check_cc(fragment=''' +#define _FILE_OFFSET_BITS 64 +#include +#define KB ((off_t)1024) +#define MB ((off_t)1024 * KB) +#define GB ((off_t)1024 * MB) +#define TB ((off_t)1024 * GB) +int t2[(((64 * GB -1) % 671088649) == 268434537) + && (((TB - (64 * GB -1) + 255) % 1792151290) == 305159546)? 1: -1]; +int main(void) { return 0; }''', msg='Checking if _FILE_OFFSET_BITS can be defined to 64', mandatory=False) if file_offset_bits_usable: conf.define('_FILE_OFFSET_BITS', 64) From cc2c97cfad44db585329c777c9ed58b58d2589dd Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 13 Jun 2022 03:07:37 +0300 Subject: [PATCH 277/548] engine: include whereami library to detect current executable path, may be reworked later if needed --- engine/common/whereami.c | 804 +++++++++++++++++++++++++++++++++++++++ engine/common/whereami.h | 67 ++++ 2 files changed, 871 insertions(+) create mode 100644 engine/common/whereami.c create mode 100644 engine/common/whereami.h diff --git a/engine/common/whereami.c b/engine/common/whereami.c new file mode 100644 index 00000000..97f7f858 --- /dev/null +++ b/engine/common/whereami.c @@ -0,0 +1,804 @@ +// (‑â—‑â—)> dual licensed under the WTFPL v2 and MIT licenses +// without any warranty. +// by Gregory Pakosz (@gpakosz) +// https://github.com/gpakosz/whereami + +// in case you want to #include "whereami.c" in a larger compilation unit +#if !defined(WHEREAMI_H) +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__linux__) || defined(__CYGWIN__) +#undef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#elif defined(__APPLE__) +#undef _DARWIN_C_SOURCE +#define _DARWIN_C_SOURCE +#define _DARWIN_BETTER_REALPATH +#endif + +#if !defined(WAI_MALLOC) || !defined(WAI_FREE) || !defined(WAI_REALLOC) +#include +#endif + +#if !defined(WAI_MALLOC) +#define WAI_MALLOC(size) malloc(size) +#endif + +#if !defined(WAI_FREE) +#define WAI_FREE(p) free(p) +#endif + +#if !defined(WAI_REALLOC) +#define WAI_REALLOC(p, size) realloc(p, size) +#endif + +#ifndef WAI_NOINLINE +#if defined(_MSC_VER) +#define WAI_NOINLINE __declspec(noinline) +#elif defined(__GNUC__) +#define WAI_NOINLINE __attribute__((noinline)) +#else +#error unsupported compiler +#endif +#endif + +#if defined(_MSC_VER) +#define WAI_RETURN_ADDRESS() _ReturnAddress() +#elif defined(__GNUC__) +#define WAI_RETURN_ADDRESS() __builtin_extract_return_addr(__builtin_return_address(0)) +#else +#error unsupported compiler +#endif + +#if defined(_WIN32) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#if defined(_MSC_VER) +#pragma warning(push, 3) +#endif +#include +#include +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +#include + +static int WAI_PREFIX(getModulePath_)(HMODULE module, char* out, int capacity, int* dirname_length) +{ + wchar_t buffer1[MAX_PATH]; + wchar_t buffer2[MAX_PATH]; + wchar_t* path = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + DWORD size; + int length_, length__; + + size = GetModuleFileNameW(module, buffer1, sizeof(buffer1) / sizeof(buffer1[0])); + + if (size == 0) + break; + else if (size == (DWORD)(sizeof(buffer1) / sizeof(buffer1[0]))) + { + DWORD size_ = size; + do + { + wchar_t* path_; + + path_ = (wchar_t*)WAI_REALLOC(path, sizeof(wchar_t) * size_ * 2); + if (!path_) + break; + size_ *= 2; + path = path_; + size = GetModuleFileNameW(module, path, size_); + } + while (size == size_); + + if (size == size_) + break; + } + else + path = buffer1; + + if (!_wfullpath(buffer2, path, MAX_PATH)) + break; + length_ = (int)wcslen(buffer2); + length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_ , out, capacity, NULL, NULL); + + if (length__ == 0) + length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_, NULL, 0, NULL, NULL); + if (length__ == 0) + break; + + if (length__ <= capacity && dirname_length) + { + int i; + + for (i = length__ - 1; i >= 0; --i) + { + if (out[i] == '\\') + { + *dirname_length = i; + break; + } + } + } + + length = length__; + } + + if (path != buffer1) + WAI_FREE(path); + + return ok ? length : -1; +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + return WAI_PREFIX(getModulePath_)(NULL, out, capacity, dirname_length); +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + HMODULE module; + int length = -1; + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4054) +#endif + if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)WAI_RETURN_ADDRESS(), &module)) +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + { + length = WAI_PREFIX(getModulePath_)(module, out, capacity, dirname_length); + } + + return length; +} + +#elif defined(__linux__) || defined(__CYGWIN__) || defined(__sun) || defined(WAI_USE_PROC_SELF_EXE) + +#include +#include +#include +#if defined(__linux__) +#include +#else +#include +#endif +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include +#include + +#if !defined(WAI_PROC_SELF_EXE) +#if defined(__sun) +#define WAI_PROC_SELF_EXE "/proc/self/path/a.out" +#else +#define WAI_PROC_SELF_EXE "/proc/self/exe" +#endif +#endif + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + resolved = realpath(WAI_PROC_SELF_EXE, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + return ok ? length : -1; +} + +#if !defined(WAI_PROC_SELF_MAPS_RETRY) +#define WAI_PROC_SELF_MAPS_RETRY 5 +#endif + +#if !defined(WAI_PROC_SELF_MAPS) +#if defined(__sun) +#define WAI_PROC_SELF_MAPS "/proc/self/map" +#else +#define WAI_PROC_SELF_MAPS "/proc/self/maps" +#endif +#endif + +#if defined(__ANDROID__) || defined(ANDROID) +#include +#include +#include +#endif +#include + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + int length = -1; + FILE* maps = NULL; + + for (int r = 0; r < WAI_PROC_SELF_MAPS_RETRY; ++r) + { + maps = fopen(WAI_PROC_SELF_MAPS, "r"); + if (!maps) + break; + + for (;;) + { + char buffer[PATH_MAX < 1024 ? 1024 : PATH_MAX]; + uint64_t low, high; + char perms[5]; + uint64_t offset; + uint32_t major, minor; + char path[PATH_MAX]; + uint32_t inode; + + if (!fgets(buffer, sizeof(buffer), maps)) + break; + + if (sscanf(buffer, "%" PRIx64 "-%" PRIx64 " %s %" PRIx64 " %x:%x %u %s\n", &low, &high, perms, &offset, &major, &minor, &inode, path) == 8) + { + uint64_t addr = (uintptr_t)WAI_RETURN_ADDRESS(); + if (low <= addr && addr <= high) + { + char* resolved; + + resolved = realpath(path, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); +#if defined(__ANDROID__) || defined(ANDROID) + if (length > 4 + &&buffer[length - 1] == 'k' + &&buffer[length - 2] == 'p' + &&buffer[length - 3] == 'a' + &&buffer[length - 4] == '.') + { + int fd = open(path, O_RDONLY); + if (fd == -1) + { + length = -1; // retry + break; + } + + char* begin = (char*)mmap(0, offset, PROT_READ, MAP_SHARED, fd, 0); + if (begin == MAP_FAILED) + { + close(fd); + length = -1; // retry + break; + } + + char* p = begin + offset - 30; // minimum size of local file header + while (p >= begin) // scan backwards + { + if (*((uint32_t*)p) == 0x04034b50UL) // local file header signature found + { + uint16_t length_ = *((uint16_t*)(p + 26)); + + if (length + 2 + length_ < (int)sizeof(buffer)) + { + memcpy(&buffer[length], "!/", 2); + memcpy(&buffer[length + 2], p + 30, length_); + length += 2 + length_; + } + + break; + } + + --p; + } + + munmap(begin, offset); + close(fd); + } +#endif + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + + break; + } + } + } + + fclose(maps); + maps = NULL; + + if (length != -1) + break; + } + + return length; +} + +#elif defined(__APPLE__) + +#include +#include +#include +#include +#include +#include + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* path = buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + uint32_t size = (uint32_t)sizeof(buffer1); + if (_NSGetExecutablePath(path, &size) == -1) + { + path = (char*)WAI_MALLOC(size); + if (!_NSGetExecutablePath(path, &size)) + break; + } + + resolved = realpath(path, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + if (path != buffer1) + WAI_FREE(path); + + return ok ? length : -1; +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#elif defined(__QNXNTO__) + +#include +#include +#include +#include +#include +#include + +#if !defined(WAI_PROC_SELF_EXE) +#define WAI_PROC_SELF_EXE "/proc/self/exefile" +#endif + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* resolved = NULL; + FILE* self_exe = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + self_exe = fopen(WAI_PROC_SELF_EXE, "r"); + if (!self_exe) + break; + + if (!fgets(buffer1, sizeof(buffer1), self_exe)) + break; + + resolved = realpath(buffer1, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + fclose(self_exe); + + return ok ? length : -1; +} + +WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#elif defined(__DragonFly__) || defined(__FreeBSD__) || \ + defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) + +#include +#include +#include +#include +#include +#include +#include + +#if defined(__OpenBSD__) + +#include + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[4096]; + char buffer2[PATH_MAX]; + char buffer3[PATH_MAX]; + char** argv = (char**)buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + int mib[4] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV }; + size_t size; + + if (sysctl(mib, 4, NULL, &size, NULL, 0) != 0) + break; + + if (size > sizeof(buffer1)) + { + argv = (char**)WAI_MALLOC(size); + if (!argv) + break; + } + + if (sysctl(mib, 4, argv, &size, NULL, 0) != 0) + break; + + if (strchr(argv[0], '/')) + { + resolved = realpath(argv[0], buffer2); + if (!resolved) + break; + } + else + { + const char* PATH = getenv("PATH"); + if (!PATH) + break; + + size_t argv0_length = strlen(argv[0]); + + const char* begin = PATH; + while (1) + { + const char* separator = strchr(begin, ':'); + const char* end = separator ? separator : begin + strlen(begin); + + if (end - begin > 0) + { + if (*(end -1) == '/') + --end; + + if (((end - begin) + 1 + argv0_length + 1) <= sizeof(buffer2)) + { + memcpy(buffer2, begin, end - begin); + buffer2[end - begin] = '/'; + memcpy(buffer2 + (end - begin) + 1, argv[0], argv0_length + 1); + + resolved = realpath(buffer2, buffer3); + if (resolved) + break; + } + } + + if (!separator) + break; + + begin = ++separator; + } + + if (!resolved) + break; + } + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + if (argv != (char**)buffer1) + WAI_FREE(argv); + + return ok ? length : -1; +} + +#else + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* path = buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { +#if defined(__NetBSD__) + int mib[4] = { CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME }; +#else + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; +#endif + size_t size = sizeof(buffer1); + + if (sysctl(mib, 4, path, &size, NULL, 0) != 0) + break; + + resolved = realpath(path, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + return ok ? length : -1; +} + +#endif + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#else + +#error unsupported platform + +#endif + +#ifdef __cplusplus +} +#endif diff --git a/engine/common/whereami.h b/engine/common/whereami.h new file mode 100644 index 00000000..670db54c --- /dev/null +++ b/engine/common/whereami.h @@ -0,0 +1,67 @@ +// (‑â—‑â—)> dual licensed under the WTFPL v2 and MIT licenses +// without any warranty. +// by Gregory Pakosz (@gpakosz) +// https://github.com/gpakosz/whereami + +#ifndef WHEREAMI_H +#define WHEREAMI_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef WAI_FUNCSPEC + #define WAI_FUNCSPEC +#endif +#ifndef WAI_PREFIX +#define WAI_PREFIX(function) wai_##function +#endif + +/** + * Returns the path to the current executable. + * + * Usage: + * - first call `int length = wai_getExecutablePath(NULL, 0, NULL);` to + * retrieve the length of the path + * - allocate the destination buffer with `path = (char*)malloc(length + 1);` + * - call `wai_getExecutablePath(path, length, NULL)` again to retrieve the + * path + * - add a terminal NUL character with `path[length] = '\0';` + * + * @param out destination buffer, optional + * @param capacity destination buffer capacity + * @param dirname_length optional recipient for the length of the dirname part + * of the path. + * + * @return the length of the executable path on success (without a terminal NUL + * character), otherwise `-1` + */ +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length); + +/** + * Returns the path to the current module + * + * Usage: + * - first call `int length = wai_getModulePath(NULL, 0, NULL);` to retrieve + * the length of the path + * - allocate the destination buffer with `path = (char*)malloc(length + 1);` + * - call `wai_getModulePath(path, length, NULL)` again to retrieve the path + * - add a terminal NUL character with `path[length] = '\0';` + * + * @param out destination buffer, optional + * @param capacity destination buffer capacity + * @param dirname_length optional recipient for the length of the dirname part + * of the path. + * + * @return the length of the module path on success (without a terminal NUL + * character), otherwise `-1` + */ +WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length); + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef WHEREAMI_H From df83b155a142c2cd132794ba36815731c46f0235 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 13 Jun 2022 03:26:44 +0300 Subject: [PATCH 278/548] game_launch: rip out execv code --- engine/common/launcher.c | 138 ++++++++++++++++++--------------------- game_launch/game.cpp | 42 ++++-------- 2 files changed, 75 insertions(+), 105 deletions(-) diff --git a/engine/common/launcher.c b/engine/common/launcher.c index 2a47b597..d1ae3706 100644 --- a/engine/common/launcher.c +++ b/engine/common/launcher.c @@ -23,54 +23,78 @@ GNU General Public License for more details. #if XASH_EMSCRIPTEN #include -#endif +#elif XASH_WIN32 +extern "C" +{ +// Enable NVIDIA High Performance Graphics while using Integrated Graphics. +__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; -#if XASH_WIN32 -#include // _execve -#else -#include // execve -#endif - -extern char **environ; -static char szGameDir[128]; // safe place to keep gamedir -static int g_iArgc; -static char **g_pszArgv; - -#if XASH_WIN32 || XASH_POSIX -#define USE_EXECVE_FOR_CHANGE_GAME 1 -#else -#define USE_EXECVE_FOR_CHANGE_GAME 0 +// Enable AMD High Performance Graphics while using Integrated Graphics. +__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +} #endif #define E_GAME "XASH3D_GAME" // default env dir to start from #define GAME_PATH "valve" // default dir to start from -void Launcher_ChangeGame( const char *progname ) +static char szGameDir[128]; // safe place to keep gamedir +static int szArgc; +static char **szArgv; + +static void Sys_ChangeGame( const char *progname ) { - char envstr[256]; - -#if USE_EXECVE_FOR_CHANGE_GAME - Host_Shutdown(); - -#if XASH_WIN32 - _putenv_s( E_GAME, progname ); - _execve( g_pszArgv[0], g_pszArgv, _environ ); -#else - snprintf( envstr, sizeof( envstr ), E_GAME "=%s", progname ); - putenv( envstr ); - execve( g_pszArgv[0], g_pszArgv, environ ); -#endif - -#else + // a1ba: may never be called within engine + // if platform supports execv() function Q_strncpy( szGameDir, progname, sizeof( szGameDir ) - 1 ); Host_Shutdown( ); - exit( Host_Main( g_iArgc, g_pszArgv, szGameDir, 1, &Launcher_ChangeGame ) ); -#endif + exit( Host_Main( szArgc, szArgv, szGameDir, 1, &Launcher_ChangeGame ) ); } -#if XASH_WIN32 -#include -#include // CommandLineToArgvW +_inline int Sys_Start( void ) +{ + int ret; + const char *game = getenv( E_GAME ); + + if( !game ) + game = GAME_PATH; + + Q_strncpy( szGameDir, game, sizeof( szGameDir )); +#if XASH_EMSCRIPTEN +#ifdef EMSCRIPTEN_LIB_FS + // For some unknown reason emscripten refusing to load libraries later + COM_LoadLibrary("menu", 0 ); + COM_LoadLibrary("server", 0 ); + COM_LoadLibrary("client", 0 ); +#endif +#if XASH_DEDICATED + // NodeJS support for debug + EM_ASM(try{ + FS.mkdir('/xash'); + FS.mount(NODEFS, { root: '.'}, '/xash' ); + FS.chdir('/xash'); + }catch(e){};); +#endif +#elif XASH_IOS + { + void IOS_LaunchDialog( void ); + IOS_LaunchDialog(); + } +#endif + + ret = Host_Main( szArgc, szArgv, game, 0, Launcher_ChangeGame ); + + return ret; +} + +#if !XASH_WIN32 +int main( int argc, char **argv ) +{ + szArgc = argc; + szArgv = argv; + + return Sys_Start(); +} +#else int __stdcall WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int nShow) { LPWSTR* lpArgv; @@ -91,9 +115,9 @@ int __stdcall WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int n LocalFree( lpArgv ); - ret = main( szArgc, szArgv ); + ret = Sys_Start(); - for( i = 0; i < szArgc; ++i ) + for( ; i < szArgc; ++i ) free( szArgv[i] ); free( szArgv ); @@ -101,41 +125,5 @@ int __stdcall WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int n } #endif // XASH_WIN32 -int main( int argc, char** argv ) -{ - const char *game = getenv( E_GAME ); - - if( !game ) - game = GAME_PATH; - - Q_strncpy( szGameDir, game, sizeof( szGameDir )); - -#if XASH_EMSCRIPTEN -#ifdef EMSCRIPTEN_LIB_FS - // For some unknown reason emscripten refusing to load libraries later - COM_LoadLibrary("menu", 0 ); - COM_LoadLibrary("server", 0 ); - COM_LoadLibrary("client", 0 ); -#endif -#if XASH_DEDICATED - // NodeJS support for debug - EM_ASM(try{ - FS.mkdir('/xash'); - FS.mount(NODEFS, { root: '.'}, '/xash' ); - FS.chdir('/xash'); - }catch(e){};); -#endif -#endif - - g_iArgc = argc; - g_pszArgv = argv; -#if XASH_IOS - { - void IOS_LaunchDialog( void ); - IOS_LaunchDialog(); - } -#endif - return Host_Main( g_iArgc, g_pszArgv, szGameDir, 0, &Launcher_ChangeGame ); -} #endif diff --git a/game_launch/game.cpp b/game_launch/game.cpp index c4da3a55..cdb6556a 100644 --- a/game_launch/game.cpp +++ b/game_launch/game.cpp @@ -26,18 +26,11 @@ GNU General Public License for more details. #define LoadLibrary( x ) dlopen( x, RTLD_NOW ) #define GetProcAddress( x, y ) dlsym( x, y ) #define FreeLibrary( x ) dlclose( x ) -#include // execve #elif XASH_WIN32 #define XASHLIB "xash.dll" #define SDL2LIB "SDL2.dll" #define dlerror() GetStringLastError() -#include // CommandLineToArgvW -#include // _execve -#else -#error // port me! -#endif -#ifdef XASH_WIN32 extern "C" { // Enable NVIDIA High Performance Graphics while using Integrated Graphics. @@ -46,12 +39,8 @@ __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; // Enable AMD High Performance Graphics while using Integrated Graphics. __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } -#endif - -#if XASH_WIN32 || XASH_POSIX -#define USE_EXECVE_FOR_CHANGE_GAME 0 #else -#define USE_EXECVE_FOR_CHANGE_GAME 0 +#error // port me! #endif #define E_GAME "XASH3D_GAME" // default env dir to start from @@ -61,7 +50,6 @@ typedef void (*pfnChangeGame)( const char *progname ); typedef int (*pfnInit)( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func ); typedef void (*pfnShutdown)( void ); -extern char **environ; static pfnInit Xash_Main; static pfnShutdown Xash_Shutdown = NULL; static char szGameDir[128]; // safe place to keep gamedir @@ -105,10 +93,14 @@ static void Sys_LoadEngine( void ) #if XASH_WIN32 HMODULE hSdl; - if ( ( hSdl = LoadLibraryEx( SDL2LIB, NULL, LOAD_LIBRARY_AS_DATAFILE ) ) == NULL ) + if (( hSdl = LoadLibraryEx( SDL2LIB, NULL, LOAD_LIBRARY_AS_DATAFILE )) == NULL ) + { Xash_Error("Unable to load the " SDL2LIB ": %s", dlerror() ); + } else + { FreeLibrary( hSdl ); + } #endif if(( hEngine = LoadLibrary( XASHLIB )) == NULL ) @@ -130,29 +122,18 @@ static void Sys_UnloadEngine( void ) if( Xash_Shutdown ) Xash_Shutdown( ); if( hEngine ) FreeLibrary( hEngine ); + hEngine = NULL; Xash_Main = NULL; Xash_Shutdown = NULL; } static void Sys_ChangeGame( const char *progname ) { + // a1ba: may never be called within engine + // if platform supports execv() function if( !progname || !progname[0] ) Xash_Error( "Sys_ChangeGame: NULL gamedir" ); -#if USE_EXECVE_FOR_CHANGE_GAME -#if XASH_WIN32 - _putenv_s( E_GAME, progname ); - Sys_UnloadEngine(); - _execve( szArgv[0], szArgv, _environ ); -#else - char envstr[256]; - snprintf( envstr, sizeof( envstr ), E_GAME "=%s", progname ); - putenv( envstr ); - - Sys_UnloadEngine(); - execve( szArgv[0], szArgv, environ ); -#endif -#else if( Xash_Shutdown == NULL ) Xash_Error( "Sys_ChangeGame: missed 'Host_Shutdown' export\n" ); @@ -161,7 +142,6 @@ static void Sys_ChangeGame( const char *progname ) Sys_UnloadEngine(); Sys_LoadEngine (); Xash_Main( szArgc, szArgv, szGameDir, 1, Sys_ChangeGame ); -#endif } _inline int Sys_Start( void ) @@ -173,12 +153,14 @@ _inline int Sys_Start( void ) if( !game ) game = GAME_PATH; + strncpy( szGameDir, game, sizeof( szGameDir ) - 1 ); + Sys_LoadEngine(); if( Xash_Shutdown ) changeGame = Sys_ChangeGame; - ret = Xash_Main( szArgc, szArgv, game, 0, changeGame ); + ret = Xash_Main( szArgc, szArgv, szGameDir, 0, changeGame ); Sys_UnloadEngine(); From e6a2c207de9ca2f2e21dbede197029c0b02d4896 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 13 Jun 2022 03:42:20 +0300 Subject: [PATCH 279/548] engine: implement change game with execv in-engine. For now it enabled for all platforms, will probably disabled selectively --- engine/common/host.c | 6 ++-- engine/common/system.c | 69 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/engine/common/host.c b/engine/common/host.c index d3ca35b8..fcac92dd 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -305,7 +305,9 @@ void Host_NewInstance( const char *name, const char *finalmsg ) host.change_game = true; Q_strncpy( host.finalmsg, finalmsg, sizeof( host.finalmsg )); - pChangeGame( name ); // call from hl.exe + + if( !Platform_ChangeGame( name )) + pChangeGame( name ); // call from hl.exe } /* @@ -868,7 +870,7 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha host.enabledll = !Sys_CheckParm( "-nodll" ); - host.change_game = bChangeGame; + host.change_game = bChangeGame || Sys_CheckParm( "-changegame" ); host.config_executed = false; host.status = HOST_INIT; // initialzation started diff --git a/engine/common/system.c b/engine/common/system.c index c41854e8..f154f7ee 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -32,9 +32,14 @@ GNU General Public License for more details. #endif #endif +#if XASH_WIN32 +#include +#endif + #include "menu_int.h" // _UPDATE_PAGE macro #include "library.h" +#include "whereami.h" qboolean error_on_exit = false; // arg for exit(); #define DEBUG_BREAK @@ -539,3 +544,67 @@ void Sys_Print( const char *pMsg ) Rcon_Print( pMsg ); } + +/* +================== +Sys_ChangeGame + +This is a special function + +Here we restart engine with new -game parameter +but since engine will be unloaded during this call +it explicitly doesn't use internal allocation or string copy utils +================== +*/ +qboolean Sys_ChangeGame( const char *gamedir ) +{ + int i = 0; + qboolean replacedArg = false; + size_t exelen; + char *exe, **newargs; + + // don't use engine allocation utils here + // they will be freed after Host_Shutdown + newargs = calloc( host.argc + 4, sizeof( *newargs )); + while( i < host.argc ) + { + newargs[i] = strdup( host.argv[i] ); + + // replace existing -game argument + if( !Q_stricmp( newargs[i], "-game" )) + { + newargs[i + 1] = strdup( gamedir ); + replacedArg = true; + i += 2; + } + else i++; + } + + if( !replacedArg ) + { + newargs[i++] = strdup( "-game" ); + newargs[i++] = strdup( gamedir ); + } + + newargs[i++] = strdup( "-changegame" ); + newargs[i] = NULL; + + exelen = wai_getExecutablePath( NULL, 0, NULL ); + exe = malloc( exelen + 1 ); + wai_getExecutablePath( exe, exelen, NULL ); + exe[exelen] = 0; + + Host_Shutdown(); + + execv( exe, newargs ); + + // if execv returned, it's probably an error + printf( "execv failed: %s", strerror( errno )); + + for( ; i >= 0; i-- ) + free( newargs[i] ); + free( newargs ); + free( exe ); + + return false; +} From e44718d531142da34a9ee5b52db76efcc8179a77 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 13 Jun 2022 04:05:50 +0300 Subject: [PATCH 280/548] engine: fix build --- engine/common/host.c | 2 +- engine/common/launcher.c | 4 ++-- engine/common/system.c | 3 ++- engine/common/system.h | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/engine/common/host.c b/engine/common/host.c index fcac92dd..fee751ce 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -306,7 +306,7 @@ void Host_NewInstance( const char *name, const char *finalmsg ) host.change_game = true; Q_strncpy( host.finalmsg, finalmsg, sizeof( host.finalmsg )); - if( !Platform_ChangeGame( name )) + if( !Sys_NewInstance( name )) pChangeGame( name ); // call from hl.exe } diff --git a/engine/common/launcher.c b/engine/common/launcher.c index d1ae3706..9410024c 100644 --- a/engine/common/launcher.c +++ b/engine/common/launcher.c @@ -47,7 +47,7 @@ static void Sys_ChangeGame( const char *progname ) // if platform supports execv() function Q_strncpy( szGameDir, progname, sizeof( szGameDir ) - 1 ); Host_Shutdown( ); - exit( Host_Main( szArgc, szArgv, szGameDir, 1, &Launcher_ChangeGame ) ); + exit( Host_Main( szArgc, szArgv, szGameDir, 1, &Sys_ChangeGame ) ); } _inline int Sys_Start( void ) @@ -81,7 +81,7 @@ _inline int Sys_Start( void ) } #endif - ret = Host_Main( szArgc, szArgv, game, 0, Launcher_ChangeGame ); + ret = Host_Main( szArgc, szArgv, game, 0, Sys_ChangeGame ); return ret; } diff --git a/engine/common/system.c b/engine/common/system.c index f154f7ee..bfff6c66 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -17,6 +17,7 @@ GNU General Public License for more details. #include "xash3d_mathlib.h" #include "platform/platform.h" #include +#include #ifdef XASH_SDL #include @@ -556,7 +557,7 @@ but since engine will be unloaded during this call it explicitly doesn't use internal allocation or string copy utils ================== */ -qboolean Sys_ChangeGame( const char *gamedir ) +qboolean Sys_NewInstance( const char *gamedir ) { int i = 0; qboolean replacedArg = false; diff --git a/engine/common/system.h b/engine/common/system.h index 48fb1807..6a186602 100644 --- a/engine/common/system.h +++ b/engine/common/system.h @@ -68,6 +68,7 @@ void Sys_PrintLog( const char *pMsg ); void Sys_InitLog( void ); void Sys_CloseLog( void ); void Sys_Quit( void ) NORETURN; +qboolean Sys_NewInstance( const char *gamedir ); // // sys_con.c From 7a883799170218398b562f19cd4a0a934b9f1f17 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 13 Jun 2022 04:21:57 +0300 Subject: [PATCH 281/548] game_launch: fix win32 build --- game_launch/game.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/game_launch/game.cpp b/game_launch/game.cpp index cdb6556a..98389f9f 100644 --- a/game_launch/game.cpp +++ b/game_launch/game.cpp @@ -27,6 +27,7 @@ GNU General Public License for more details. #define GetProcAddress( x, y ) dlsym( x, y ) #define FreeLibrary( x ) dlclose( x ) #elif XASH_WIN32 +#include // CommandLineToArgvW #define XASHLIB "xash.dll" #define SDL2LIB "SDL2.dll" #define dlerror() GetStringLastError() From ac308f729827fd8415f36c1645f1c9b9c6aecc47 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 13 Jun 2022 04:27:54 +0300 Subject: [PATCH 282/548] engine: platform: sdl: fix SDL1.2 build --- engine/platform/sdl/in_sdl.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/engine/platform/sdl/in_sdl.c b/engine/platform/sdl/in_sdl.c index a369a90d..129759bb 100644 --- a/engine/platform/sdl/in_sdl.c +++ b/engine/platform/sdl/in_sdl.c @@ -260,11 +260,11 @@ SDLash_InitCursors */ void SDLash_InitCursors( void ) { +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( cursors.initialized ) SDLash_FreeCursors(); // load up all default cursors -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) cursors.cursors[dc_none] = NULL; cursors.cursors[dc_arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); cursors.cursors[dc_ibeam] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); @@ -278,8 +278,8 @@ void SDLash_InitCursors( void ) cursors.cursors[dc_sizeall] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); cursors.cursors[dc_no] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); cursors.cursors[dc_hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); -#endif cursors.initialized = true; +#endif } /* @@ -314,8 +314,10 @@ void Platform_SetCursorType( VGUI_DefaultCursor type ) { qboolean visible; +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( !cursors.initialized ) return; +#endif if( cls.key_dest != key_game || cl.paused ) return; @@ -331,10 +333,10 @@ void Platform_SetCursorType( VGUI_DefaultCursor type ) break; } -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( CVAR_TO_BOOL( touch_emulate )) return; +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( visible && !host.mouse_visible ) { SDL_SetCursor( cursors.cursors[type] ); @@ -346,8 +348,17 @@ void Platform_SetCursorType( VGUI_DefaultCursor type ) SDL_ShowCursor( false ); Key_EnableTextInput( false, false ); } - host.mouse_visible = visible; +#else + if( visible && !host.mouse_visible ) + { + SDL_ShowCursor( true ); + } + else if( !visible && host.mouse_visible ) + { + SDL_ShowCursor( false ); + } #endif + host.mouse_visible = visible; } /* From e5562a7b61ab270ee72d67ea585e6997d863a6be Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 13 Jun 2022 04:33:12 +0300 Subject: [PATCH 283/548] engine: whereami: fix C89 --- engine/common/whereami.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engine/common/whereami.c b/engine/common/whereami.c index 97f7f858..79b01dcc 100644 --- a/engine/common/whereami.c +++ b/engine/common/whereami.c @@ -255,8 +255,9 @@ int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) { int length = -1; FILE* maps = NULL; + int r; - for (int r = 0; r < WAI_PROC_SELF_MAPS_RETRY; ++r) + for (r = 0; r < WAI_PROC_SELF_MAPS_RETRY; ++r) { maps = fopen(WAI_PROC_SELF_MAPS, "r"); if (!maps) From 568c7fd917013fd43064bb5bb8fdadf6283ae672 Mon Sep 17 00:00:00 2001 From: Velaron Date: Fri, 10 Jun 2022 11:20:58 +0300 Subject: [PATCH 284/548] engine: strip color codes when writing to log --- engine/common/sys_con.c | 15 +++++++++------ public/crtlib.c | 11 +++++++++++ public/crtlib.h | 1 + 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c index 96f4bb98..93c0a4b5 100644 --- a/engine/common/sys_con.c +++ b/engine/common/sys_con.c @@ -230,6 +230,9 @@ void Sys_PrintLog( const char *pMsg ) time( &crt_time ); crt_tm = localtime( &crt_time ); + // strip color codes + Q_cleanstr( pMsg, pMsg ); + // platform-specific output #if XASH_ANDROID && !XASH_DEDICATED __android_log_print( ANDROID_LOG_DEBUG, "Xash", "%s", pMsg ); @@ -248,21 +251,21 @@ void Sys_PrintLog( const char *pMsg ) #ifdef XASH_COLORIZE_CONSOLE Sys_PrintColorized( logtime, pMsg ); #else - printf( "%s %s", logtime, pMsg ); + printf( "%s%s", logtime, pMsg ); #endif Sys_FlushStdout(); #endif - // save last char to detect when line was not ended - lastchar = pMsg[strlen(pMsg)-1]; - if( !s_ld.logfile ) return; if( !lastchar || lastchar == '\n') - strftime( logtime, sizeof( logtime ), "[%Y:%m:%d|%H:%M:%S]", crt_tm ); //full time + strftime( logtime, sizeof( logtime ), "[%Y:%m:%d|%H:%M:%S] ", crt_tm ); //full time + + // save last char to detect when line was not ended + lastchar = pMsg[strlen(pMsg)-1]; - fprintf( s_ld.logfile, "%s %s", logtime, pMsg ); + fprintf( s_ld.logfile, "%s%s", logtime, pMsg ); Sys_FlushLogfile(); } diff --git a/public/crtlib.c b/public/crtlib.c index 73bafdf4..cd3cdb28 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -615,6 +615,17 @@ char *Q_strpbrk(const char *s, const char *accept) return NULL; } +void Q_cleanstr( const char *in, char *out ) +{ + while ( *in ) + { + if ( IsColorString( in ) ) + in += 2; + else *out++ = *in++; + } + *out = '\0'; +} + uint Q_hashkey( const char *string, uint hashSize, qboolean caseinsensitive ) { uint i, hashKey = 0; diff --git a/public/crtlib.h b/public/crtlib.h index 5e23423f..1cccf1e3 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -73,6 +73,7 @@ int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list ar int Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ) _format( 3 ); int Q_sprintf( char *buffer, const char *format, ... ) _format( 2 ); char *Q_strpbrk(const char *s, const char *accept); +void Q_cleanstr( char *in, char *out ); #define Q_memprint( val ) Q_pretifymem( val, 2 ) char *Q_pretifymem( float value, int digitsafterdecimal ); char *va( const char *format, ... ) _format( 1 ); From 15dc25d2cd4df9d123ed7e2d2586792afa5e1be4 Mon Sep 17 00:00:00 2001 From: Velaron Date: Fri, 10 Jun 2022 18:43:04 +0300 Subject: [PATCH 285/548] engine: rename Q_cleanstr to something more meaningful --- engine/common/sys_con.c | 2 +- public/crtlib.c | 2 +- public/crtlib.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c index 93c0a4b5..b79f9e8a 100644 --- a/engine/common/sys_con.c +++ b/engine/common/sys_con.c @@ -231,7 +231,7 @@ void Sys_PrintLog( const char *pMsg ) crt_tm = localtime( &crt_time ); // strip color codes - Q_cleanstr( pMsg, pMsg ); + COM_StripColors( pMsg, pMsg ); // platform-specific output #if XASH_ANDROID && !XASH_DEDICATED diff --git a/public/crtlib.c b/public/crtlib.c index cd3cdb28..cc24a6f3 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -615,7 +615,7 @@ char *Q_strpbrk(const char *s, const char *accept) return NULL; } -void Q_cleanstr( const char *in, char *out ) +void COM_StripColors( const char *in, char *out ) { while ( *in ) { diff --git a/public/crtlib.h b/public/crtlib.h index 1cccf1e3..a50288d8 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -73,7 +73,7 @@ int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list ar int Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ) _format( 3 ); int Q_sprintf( char *buffer, const char *format, ... ) _format( 2 ); char *Q_strpbrk(const char *s, const char *accept); -void Q_cleanstr( char *in, char *out ); +void COM_StripColors( const char *in, char *out ); #define Q_memprint( val ) Q_pretifymem( val, 2 ) char *Q_pretifymem( float value, int digitsafterdecimal ); char *va( const char *format, ... ) _format( 1 ); From 6807cf5849fd9c34aabbad3b08f7865f636eabf5 Mon Sep 17 00:00:00 2001 From: Velaron Date: Fri, 10 Jun 2022 20:29:01 +0300 Subject: [PATCH 286/548] engine: fix build on android --- engine/common/sys_con.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c index b79f9e8a..8dfb9938 100644 --- a/engine/common/sys_con.c +++ b/engine/common/sys_con.c @@ -231,7 +231,7 @@ void Sys_PrintLog( const char *pMsg ) crt_tm = localtime( &crt_time ); // strip color codes - COM_StripColors( pMsg, pMsg ); + COM_StripColors( pMsg, (char *)pMsg ); // platform-specific output #if XASH_ANDROID && !XASH_DEDICATED From 402b38951df1c55e4c04c3103088390021fef189 Mon Sep 17 00:00:00 2001 From: Velaron Date: Mon, 13 Jun 2022 22:36:43 +0300 Subject: [PATCH 287/548] engine: better color code stripping --- engine/common/sys_con.c | 84 +++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c index 8dfb9938..ffa0db2b 100644 --- a/engine/common/sys_con.c +++ b/engine/common/sys_con.c @@ -14,7 +14,10 @@ GNU General Public License for more details. */ #include "common.h" -#if XASH_ANDROID +#if XASH_WIN32 +#define STDOUT_FILENO 1 +#include +#elif XASH_ANDROID #include #endif #include @@ -129,16 +132,18 @@ void Sys_InitLog( void ) if( s_ld.log_active ) { s_ld.logfile = fopen( s_ld.log_path, mode ); - if( !s_ld.logfile ) + + if ( !s_ld.logfile ) { Con_Reportf( S_ERROR "Sys_InitLog: can't create log file %s: %s\n", s_ld.log_path, strerror( errno ) ); + return; } - else - { - fprintf( s_ld.logfile, "=================================================================================\n" ); - fprintf( s_ld.logfile, "\t%s (build %i) started at %s\n", s_ld.title, Q_buildnum(), Q_timestamp( TIME_FULL )); - fprintf( s_ld.logfile, "=================================================================================\n" ); - } + + s_ld.logfileno = fileno( s_ld.logfile ); + + fprintf( s_ld.logfile, "=================================================================================\n" ); + fprintf( s_ld.logfile, "\t%s (build %i) started at %s\n", s_ld.title, Q_buildnum(), Q_timestamp( TIME_FULL ) ); + fprintf( s_ld.logfile, "=================================================================================\n" ); } } @@ -220,9 +225,47 @@ static void Sys_PrintColorized( const char *logtime, const char *msg ) printf( "\033[34m%s\033[0m%s\033[0m", logtime, colored ); } +static void Sys_PrintFile( int fd, const char *logtime, const char *msg ) +{ + write( fd, logtime, Q_strlen( logtime ) ); + + while ( *msg ) + { + const char *p = strchr( msg, '^' ); + + if ( p && IsColorString( p ) ) + { + msg += write( fd, msg, p - msg ); + msg += 2; + } else msg += write( fd, msg, Q_strlen( msg ) ); + } +} + +static void Sys_PrintStdout( const char *logtime, const char *msg ) +{ +#if XASH_MOBILE_PLATFORM + static char buf[MAX_PRINT_MSG]; + + // strip color codes + COM_StripColors( msg, buf ); + + // platform-specific output +#if XASH_ANDROID && !XASH_DEDICATED + __android_log_print( ANDROID_LOG_DEBUG, "Xash", "%s", buf ); +#endif // XASH_ANDROID && !XASH_DEDICATED + +#if TARGET_OS_IOS + void IOS_Log( const char * ); + IOS_Log( buf ); +#endif // TARGET_OS_IOS +#else // XASH_MOBILE_PLATFORM + Sys_PrintFile( STDOUT_FILENO, logtime, msg ); +#endif +} + void Sys_PrintLog( const char *pMsg ) { - time_t crt_time; + time_t crt_time; const struct tm *crt_tm; char logtime[32] = ""; static char lastchar; @@ -230,31 +273,16 @@ void Sys_PrintLog( const char *pMsg ) time( &crt_time ); crt_tm = localtime( &crt_time ); - // strip color codes - COM_StripColors( pMsg, (char *)pMsg ); - - // platform-specific output -#if XASH_ANDROID && !XASH_DEDICATED - __android_log_print( ANDROID_LOG_DEBUG, "Xash", "%s", pMsg ); -#endif - -#if TARGET_OS_IOS - void IOS_Log(const char*); - IOS_Log(pMsg); -#endif - if( !lastchar || lastchar == '\n') strftime( logtime, sizeof( logtime ), "[%H:%M:%S] ", crt_tm ); //short time - // spew to stdout, except mobiles -#if !XASH_MOBILE_PLATFORM + // spew to stdout #ifdef XASH_COLORIZE_CONSOLE Sys_PrintColorized( logtime, pMsg ); #else - printf( "%s%s", logtime, pMsg ); + Sys_PrintStdout( logtime, pMsg ); #endif Sys_FlushStdout(); -#endif if( !s_ld.logfile ) return; @@ -263,9 +291,9 @@ void Sys_PrintLog( const char *pMsg ) strftime( logtime, sizeof( logtime ), "[%Y:%m:%d|%H:%M:%S] ", crt_tm ); //full time // save last char to detect when line was not ended - lastchar = pMsg[strlen(pMsg)-1]; + lastchar = pMsg[Q_strlen( pMsg ) - 1]; - fprintf( s_ld.logfile, "%s%s", logtime, pMsg ); + Sys_PrintFile( s_ld.logfileno, logtime, pMsg ); Sys_FlushLogfile(); } From ab6214142ff17ca29bb2c3c0e9809132904272d1 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 14 Jun 2022 03:23:46 +0300 Subject: [PATCH 288/548] public: remove custom str(r)chr functions --- public/crtlib.c | 26 -------------------------- public/crtlib.h | 4 ++-- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/public/crtlib.c b/public/crtlib.c index cc24a6f3..7260595e 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -317,32 +317,6 @@ void Q_atov( float *vec, const char *str, size_t siz ) } } -char *Q_strchr( const char *s, char c ) -{ - size_t len = Q_strlen( s ); - - while( len-- ) - { - if( *++s == c ) - return (char *)s; - } - return 0; -} - -char *Q_strrchr( const char *s, char c ) -{ - size_t len = Q_strlen( s ); - - s += len; - - while( len-- ) - { - if( *--s == c ) - return (char *)s; - } - return 0; -} - int Q_strnicmp( const char *s1, const char *s2, int n ) { int c1, c2; diff --git a/public/crtlib.h b/public/crtlib.h index a50288d8..b03aa812 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -58,8 +58,8 @@ qboolean Q_isspace( const char *str ); int Q_atoi( const char *str ); float Q_atof( const char *str ); void Q_atov( float *vec, const char *str, size_t siz ); -char *Q_strchr( const char *s, char c ); -char *Q_strrchr( const char *s, char c ); +#define Q_strchr strchr +#define Q_strrchr strrchr #define Q_stricmp( s1, s2 ) Q_strnicmp( s1, s2, 99999 ) int Q_strnicmp( const char *s1, const char *s2, int n ); #define Q_strcmp( s1, s2 ) Q_strncmp( s1, s2, 99999 ) From 347c6d6a919afaa0f026f39d35b774b2c3d08a98 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 14 Jun 2022 03:27:08 +0300 Subject: [PATCH 289/548] engine: common: don't output log to stdout on Win32 where it's done by Wcon. Better colorcode filtration --- engine/common/sys_con.c | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c index ffa0db2b..7cd88bee 100644 --- a/engine/common/sys_con.c +++ b/engine/common/sys_con.c @@ -24,7 +24,7 @@ GNU General Public License for more details. #include #if !XASH_WIN32 && !XASH_MOBILE_PLATFORM -#define XASH_COLORIZE_CONSOLE +// #define XASH_COLORIZE_CONSOLE // use with caution, running engine in Qt Creator may cause a freeze in read() call // I was never encountered this bug anywhere else, so still enable by default // #define XASH_USE_SELECT 1 @@ -227,17 +227,32 @@ static void Sys_PrintColorized( const char *logtime, const char *msg ) static void Sys_PrintFile( int fd, const char *logtime, const char *msg ) { + const char *p = msg; + write( fd, logtime, Q_strlen( logtime ) ); - while ( *msg ) + while( p && *p ) { - const char *p = strchr( msg, '^' ); + p = Q_strchr( msg, '^' ); - if ( p && IsColorString( p ) ) + if( p == NULL ) { - msg += write( fd, msg, p - msg ); - msg += 2; - } else msg += write( fd, msg, Q_strlen( msg ) ); + write( fd, msg, Q_strlen( msg )); + break; + } + else if( IsColorString( p )) + { + if( p != msg ) + { + write( fd, msg, p - msg ); + } + msg = p + 2; + } + else + { + write( fd, msg, p - msg + 1 ); + msg = p + 1; + } } } @@ -279,7 +294,7 @@ void Sys_PrintLog( const char *pMsg ) // spew to stdout #ifdef XASH_COLORIZE_CONSOLE Sys_PrintColorized( logtime, pMsg ); -#else +#elif !XASH_WIN32 // Wcon already does the job Sys_PrintStdout( logtime, pMsg ); #endif Sys_FlushStdout(); From 45bf927c7417d28496f089cee5545cfd74ff9a0b Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 14 Jun 2022 03:27:56 +0300 Subject: [PATCH 290/548] engine: filesystem: avoid FS_SysFolderExists spam if stat returned ENOTDIR --- engine/common/filesystem.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c index 3b036b73..734255e7 100644 --- a/engine/common/filesystem.c +++ b/engine/common/filesystem.c @@ -2536,7 +2536,8 @@ qboolean FS_SysFolderExists( const char *path ) if( stat( path, &buf ) < 0 ) { - Con_Reportf( S_ERROR "FS_SysFolderExists: problem while opening dir: %s\n", strerror( errno )); + if( errno != ENOTDIR ) + Con_Reportf( S_ERROR "FS_SysFolderExists: problem while opening dir: %s\n", strerror( errno )); return false; } From 40298cefb628e0d763d11b76821672ead576c7d5 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 14 Jun 2022 03:30:10 +0300 Subject: [PATCH 291/548] engine: client: don't save configs if shutdown was issued before client was initialized. Remove dead ucmd --- engine/client/cl_main.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 518f4225..225172aa 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -2872,7 +2872,6 @@ void CL_InitLocal( void ) Cmd_AddRestrictedCommand ("kill", NULL, "die instantly" ); Cmd_AddCommand ("god", NULL, "enable godmode" ); Cmd_AddCommand ("fov", NULL, "set client field of view" ); - Cmd_AddCommand ("log", NULL, "logging server events" ); // register our commands Cmd_AddCommand ("pause", NULL, "pause the game (if the server allows pausing)" ); @@ -3091,7 +3090,7 @@ void CL_Shutdown( void ) { Con_Printf( "CL_Shutdown()\n" ); - if( !host.crashed ) + if( !host.crashed && cls.initialized ) { Host_WriteOpenGLConfig (); Host_WriteVideoConfig (); From 2388260848a60a0fe81a1933943f93515e367356 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 14 Jun 2022 04:23:09 +0300 Subject: [PATCH 292/548] engine: optimize colored output * removed unneeded formatting for Android * make generic function to output colorless and colorized strings * disable color output on low memory devices to not waste CPU cycles on it --- engine/common/sys_con.c | 90 ++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 51 deletions(-) diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c index 7cd88bee..32c760da 100644 --- a/engine/common/sys_con.c +++ b/engine/common/sys_con.c @@ -23,11 +23,14 @@ GNU General Public License for more details. #include #include -#if !XASH_WIN32 && !XASH_MOBILE_PLATFORM -// #define XASH_COLORIZE_CONSOLE +// do not waste precious CPU cycles on mobiles or low memory devices +#if !XASH_WIN32 && !XASH_MOBILE_PLATFORM && !XASH_LOW_MEMORY +#define XASH_COLORIZE_CONSOLE true // use with caution, running engine in Qt Creator may cause a freeze in read() call // I was never encountered this bug anywhere else, so still enable by default // #define XASH_USE_SELECT 1 +#else +#define XASH_COLORIZE_CONSOLE false #endif #if XASH_USE_SELECT @@ -181,51 +184,31 @@ void Sys_CloseLog( void ) } } -static void Sys_PrintColorized( const char *logtime, const char *msg ) +#if XASH_COLORIZE_CONSOLE == true +static void Sys_WriteEscapeSequenceForColorcode( int fd, int c ) { - char colored[4096]; - int len = 0; - - while( *msg && ( len < 4090 ) ) + static const char *q3ToAnsi[ 8 ] = { - static char q3ToAnsi[ 8 ] = - { - '0', // COLOR_BLACK - '1', // COLOR_RED - '2', // COLOR_GREEN - '3', // COLOR_YELLOW - '4', // COLOR_BLUE - '6', // COLOR_CYAN - '5', // COLOR_MAGENTA - 0 // COLOR_WHITE - }; + "\033[30m", // COLOR_BLACK + "\033[31m", // COLOR_RED + "\033[32m", // COLOR_GREEN + "\033[33m", // COLOR_YELLOW + "\033[34m", // COLOR_BLUE + "\033[36m", // COLOR_CYAN + "\033[35m", // COLOR_MAGENTA + "\033[0m", // COLOR_WHITE + }; + const char *esc = q3ToAnsi[c]; - if( IsColorString( msg ) ) - { - int color; - - msg++; - color = q3ToAnsi[ *msg++ % 8 ]; - colored[len++] = '\033'; - colored[len++] = '['; - if( color ) - { - colored[len++] = '3'; - colored[len++] = color; - } - else - colored[len++] = '0'; - colored[len++] = 'm'; - } - else - colored[len++] = *msg++; - } - colored[len] = 0; - - printf( "\033[34m%s\033[0m%s\033[0m", logtime, colored ); + if( c == 7 ) + write( fd, esc, 4 ); + else write( fd, esc, 5 ); } +#else +static void Sys_WriteEscapeSequenceForColorcode( int fd, int c ) {} +#endif -static void Sys_PrintFile( int fd, const char *logtime, const char *msg ) +static void Sys_PrintLogfile( const int fd, const char *logtime, const char *msg, const qboolean colorize ) { const char *p = msg; @@ -243,10 +226,11 @@ static void Sys_PrintFile( int fd, const char *logtime, const char *msg ) else if( IsColorString( p )) { if( p != msg ) - { write( fd, msg, p - msg ); - } msg = p + 2; + + if( colorize ) + Sys_WriteEscapeSequenceForColorcode( fd, ColorIndex( p[1] )); } else { @@ -254,6 +238,10 @@ static void Sys_PrintFile( int fd, const char *logtime, const char *msg ) msg = p + 1; } } + + // flush the color + if( colorize ) + Sys_WriteEscapeSequenceForColorcode( fd, 7 ); } static void Sys_PrintStdout( const char *logtime, const char *msg ) @@ -266,7 +254,7 @@ static void Sys_PrintStdout( const char *logtime, const char *msg ) // platform-specific output #if XASH_ANDROID && !XASH_DEDICATED - __android_log_print( ANDROID_LOG_DEBUG, "Xash", "%s", buf ); + __android_log_write( ANDROID_LOG_DEBUG, "Xash", buf ); #endif // XASH_ANDROID && !XASH_DEDICATED #if TARGET_OS_IOS @@ -274,7 +262,7 @@ static void Sys_PrintStdout( const char *logtime, const char *msg ) IOS_Log( buf ); #endif // TARGET_OS_IOS #else // XASH_MOBILE_PLATFORM - Sys_PrintFile( STDOUT_FILENO, logtime, msg ); + Sys_PrintLogfile( STDOUT_FILENO, logtime, msg, XASH_COLORIZE_CONSOLE ); #endif } @@ -292,15 +280,15 @@ void Sys_PrintLog( const char *pMsg ) strftime( logtime, sizeof( logtime ), "[%H:%M:%S] ", crt_tm ); //short time // spew to stdout -#ifdef XASH_COLORIZE_CONSOLE - Sys_PrintColorized( logtime, pMsg ); -#elif !XASH_WIN32 // Wcon already does the job Sys_PrintStdout( logtime, pMsg ); -#endif Sys_FlushStdout(); if( !s_ld.logfile ) + { + // save last char to detect when line was not ended + lastchar = pMsg[Q_strlen( pMsg ) - 1]; return; + } if( !lastchar || lastchar == '\n') strftime( logtime, sizeof( logtime ), "[%Y:%m:%d|%H:%M:%S] ", crt_tm ); //full time @@ -308,7 +296,7 @@ void Sys_PrintLog( const char *pMsg ) // save last char to detect when line was not ended lastchar = pMsg[Q_strlen( pMsg ) - 1]; - Sys_PrintFile( s_ld.logfileno, logtime, pMsg ); + Sys_PrintLogfile( s_ld.logfileno, logtime, pMsg, false ); Sys_FlushLogfile(); } From 2218126c75d2d81afb669e9492ca39e4159f6973 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 14 Jun 2022 04:25:40 +0300 Subject: [PATCH 293/548] engine: disable stdout output and flush on Windows, Wcon already does the job --- engine/common/sys_con.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c index 32c760da..67c5d3bb 100644 --- a/engine/common/sys_con.c +++ b/engine/common/sys_con.c @@ -261,8 +261,9 @@ static void Sys_PrintStdout( const char *logtime, const char *msg ) void IOS_Log( const char * ); IOS_Log( buf ); #endif // TARGET_OS_IOS -#else // XASH_MOBILE_PLATFORM +#elif !XASH_WIN32 // Wcon does the job Sys_PrintLogfile( STDOUT_FILENO, logtime, msg, XASH_COLORIZE_CONSOLE ); + Sys_FlushStdout(); #endif } @@ -281,7 +282,6 @@ void Sys_PrintLog( const char *pMsg ) // spew to stdout Sys_PrintStdout( logtime, pMsg ); - Sys_FlushStdout(); if( !s_ld.logfile ) { From fef0993f54b271c42af23b8250651d0a6daa4d82 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Wed, 15 Jun 2022 02:35:23 +0400 Subject: [PATCH 294/548] engine: client: cl_game: fixed hudGetModelByIndex function in client API (fix #518) --- engine/client/cl_game.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index 66a0fc76..4f5fc5b3 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -3865,7 +3865,7 @@ static cl_enginefunc_t gEngfuncs = (void*)Cmd_GetName, pfnGetClientOldTime, pfnGetGravity, - Mod_Handle, + CL_ModelHandle, pfnEnableTexSort, pfnSetLightmapColor, pfnSetLightmapScale, From 463997da51a0a5a4c3a61996ff230f440c136354 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 15 Jun 2022 13:54:54 +0300 Subject: [PATCH 295/548] engine: common: delete unused Mod_Handle function --- engine/common/mod_local.h | 1 - engine/common/model.c | 17 ----------------- 2 files changed, 18 deletions(-) diff --git a/engine/common/mod_local.h b/engine/common/mod_local.h index 0a794e5f..5c7e579d 100644 --- a/engine/common/mod_local.h +++ b/engine/common/mod_local.h @@ -143,7 +143,6 @@ model_t *Mod_ForName( const char *name, qboolean crash, qboolean trackCRC ); qboolean Mod_ValidateCRC( const char *name, CRC32_t crc ); void Mod_NeedCRC( const char *name, qboolean needCRC ); void Mod_FreeUnused( void ); -model_t *Mod_Handle( int handle ); // // mod_bmodel.c diff --git a/engine/common/model.c b/engine/common/model.c index 1b75a2df..ad8e80ca 100644 --- a/engine/common/model.c +++ b/engine/common/model.c @@ -600,20 +600,3 @@ void Mod_NeedCRC( const char *name, qboolean needCRC ) if( needCRC ) SetBits( p->flags, FCRC_SHOULD_CHECKSUM ); else ClearBits( p->flags, FCRC_SHOULD_CHECKSUM ); } - - -/* -================== -Mod_Handle - -================== -*/ -model_t *GAME_EXPORT Mod_Handle( int handle ) -{ - if( handle < 0 || handle >= MAX_MODELS ) - { - Con_Reportf( "Mod_Handle: bad handle #%i\n", handle ); - return NULL; - } - return &mod_known[handle]; -} From 78d2cd848ae3ba83d27ba2233682f4db3c79e824 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 15 Jun 2022 18:20:19 +0300 Subject: [PATCH 296/548] mainui: update --- mainui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainui b/mainui index 0a28db38..c97245c0 160000 --- a/mainui +++ b/mainui @@ -1 +1 @@ -Subproject commit 0a28db384b9572f2aefbc335fe5255e65f9f7b7f +Subproject commit c97245c0f526880a1b1b7354ce34f92307742d63 From fa09854671a1dc99f3674264396dfd211113e1de Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 15 Jun 2022 18:22:57 +0300 Subject: [PATCH 297/548] engine: client: deprecate and remove ChangeInstance menu call --- engine/client/cl_gameui.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/engine/client/cl_gameui.c b/engine/client/cl_gameui.c index 6308e2e6..f295e701 100644 --- a/engine/client/cl_gameui.c +++ b/engine/client/cl_gameui.c @@ -1060,10 +1060,7 @@ pfnChangeInstance */ static void GAME_EXPORT pfnChangeInstance( const char *newInstance, const char *szFinalMessage ) { - if( !szFinalMessage ) szFinalMessage = ""; - if( !newInstance || !*newInstance ) return; - - Host_NewInstance( newInstance, szFinalMessage ); + Con_Reportf( S_ERROR "ChangeInstance menu call is deprecated!\n" ); } /* From a6f12d30093d32a8c908a1b3fa05ab8750ff4b25 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 15 Jun 2022 18:26:41 +0300 Subject: [PATCH 298/548] ref_gl: gl4es: update --- ref_gl/gl4es | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_gl/gl4es b/ref_gl/gl4es index 36a5cd54..5c28420a 160000 --- a/ref_gl/gl4es +++ b/ref_gl/gl4es @@ -1 +1 @@ -Subproject commit 36a5cd54e7ff6cad15ce6d1924c63bf5cf64c1f1 +Subproject commit 5c28420a384c93345a7a5d060a56a0de5f2ac871 From ad4d0cf00a9e1f6ece8e18aa8e650a9cf3a4f816 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 15 Jun 2022 18:26:51 +0300 Subject: [PATCH 299/548] vgui_support: update --- vgui_support | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vgui_support b/vgui_support index 2c17ffff..99108598 160000 --- a/vgui_support +++ b/vgui_support @@ -1 +1 @@ -Subproject commit 2c17ffffdb9afc282fffe45c863868485064b1fb +Subproject commit 991085982209a1b8eefabae04d842004d4f4fe4f From 0cef18af86451f6b3c00850ab743acc39c5e889a Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Thu, 16 Jun 2022 00:37:38 +0400 Subject: [PATCH 300/548] engine: client: cl_netgraph: fixed uninitialized alpha in netcolors --- engine/client/cl_netgraph.c | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/client/cl_netgraph.c b/engine/client/cl_netgraph.c index a361c9bd..28cc592c 100644 --- a/engine/client/cl_netgraph.c +++ b/engine/client/cl_netgraph.c @@ -158,6 +158,7 @@ static void NetGraph_InitColors( void ) f = (float)(i - hfrac) / (float)(NETGRAPH_LERP_HEIGHT - hfrac ); VectorMA( mincolor[1], f, dc[1], netcolors[NETGRAPH_NET_COLORS + i] ); } + netcolors[NETGRAPH_NET_COLORS + i][3] = 255; } } From ce8e5880e2a5f2287c568310b8a5fdf9d2bb1d38 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Thu, 16 Jun 2022 00:39:48 +0400 Subject: [PATCH 301/548] engine: client: cl_netgraph: added clamp for bars height --- engine/client/cl_netgraph.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/cl_netgraph.c b/engine/client/cl_netgraph.c index 28cc592c..b31cca65 100644 --- a/engine/client/cl_netgraph.c +++ b/engine/client/cl_netgraph.c @@ -260,7 +260,7 @@ static void NetGraph_DrawTimes( wrect_t rect, int x, int w ) for( a = 0; a < w; a++ ) { i = ( cls.netchan.outgoing_sequence - a ) & NET_TIMINGS_MASK; - h = ( netstat_cmdinfo[i].cmd_lerp / 3.0f ) * NETGRAPH_LERP_HEIGHT; + h = Q_min(( netstat_cmdinfo[i].cmd_lerp / 3.0f ) * NETGRAPH_LERP_HEIGHT, net_graphheight->value * 0.7f); fill.left = x + w - a - 1; fill.right = fill.bottom = 1; From d4bb5423aeb6886479af9619c3a12b0921ad324f Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Thu, 16 Jun 2022 00:41:11 +0400 Subject: [PATCH 302/548] engine: client: cl_netgraph: set rendermode to transparent instead additive --- engine/client/cl_netgraph.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/cl_netgraph.c b/engine/client/cl_netgraph.c index b31cca65..7bc6e2a6 100644 --- a/engine/client/cl_netgraph.c +++ b/engine/client/cl_netgraph.c @@ -673,7 +673,7 @@ void SCR_DrawNetGraph( void ) if( graphtype < 3 ) { - ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd ); + ref.dllFuncs.GL_SetRenderMode( kRenderTransColor ); ref.dllFuncs.GL_Bind( XASH_TEXTURE0, R_GetBuiltinTexture( REF_WHITE_TEXTURE ) ); ref.dllFuncs.Begin( TRI_QUADS ); // draw all the fills as a long solid sequence of quads for speedup reasons From a68afcc6726ac903b56f80af514da06fd7e1a3f5 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Thu, 16 Jun 2022 00:42:57 +0400 Subject: [PATCH 303/548] engine: client: cl_netgraph: added kilobytes per seconds unit to in/out fields --- engine/client/cl_netgraph.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/client/cl_netgraph.c b/engine/client/cl_netgraph.c index 7bc6e2a6..e58a255c 100644 --- a/engine/client/cl_netgraph.c +++ b/engine/client/cl_netgraph.c @@ -396,10 +396,10 @@ static void NetGraph_DrawTextFields( int x, int y, int w, wrect_t rect, int coun if( !out ) out = lastout; else lastout = out; - Con_DrawString( x, y, va( "in : %i %.2f k/s", netstat_graph[j].msgbytes, cls.netchan.flow[FLOW_INCOMING].avgkbytespersec ), colors ); + Con_DrawString( x, y, va( "in : %i %.2f kb/s", netstat_graph[j].msgbytes, cls.netchan.flow[FLOW_INCOMING].avgkbytespersec ), colors ); y += 15; - Con_DrawString( x, y, va( "out: %i %.2f k/s", out, cls.netchan.flow[FLOW_OUTGOING].avgkbytespersec ), colors ); + Con_DrawString( x, y, va( "out: %i %.2f kb/s", out, cls.netchan.flow[FLOW_OUTGOING].avgkbytespersec ), colors ); y += 15; if( graphtype > 2 ) From afaabe26a63dcc39223d1c6c3a9c57c03e116592 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Thu, 16 Jun 2022 00:44:23 +0400 Subject: [PATCH 304/548] engine: client: cl_netgraph: fixed netgraph position calculation in center mode --- engine/client/cl_netgraph.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/cl_netgraph.c b/engine/client/cl_netgraph.c index e58a255c..7c9ee99a 100644 --- a/engine/client/cl_netgraph.c +++ b/engine/client/cl_netgraph.c @@ -616,7 +616,7 @@ static void NetGraph_GetScreenPos( wrect_t *rect, int *w, int *x, int *y ) *x = rect->left + rect->right - 5 - *w; break; case 2: // center - *x = rect->left + ( rect->right - 10 - *w ) / 2; + *x = ( rect->left + ( rect->right - 10 - *w )) / 2; break; default: // left sided *x = rect->left + 5; From a1ce5faac20ffc4d9656fdade00b0b3685840d60 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Mon, 2 May 2022 01:22:35 +0200 Subject: [PATCH 305/548] Engine: Keep HTTP from endlessly formatting NaN values For whatever reason, our progress count for HTTP downloads stays at 0. This results in the engine calculating a NaN progress value many times each frame, which results in a significant performance hit. --- engine/common/net_ws.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/common/net_ws.c b/engine/common/net_ws.c index ef403b3c..8b491ba9 100644 --- a/engine/common/net_ws.c +++ b/engine/common/net_ws.c @@ -2280,7 +2280,7 @@ void HTTP_Run( void ) } // update progress - if( !Host_IsDedicated() ) + if( !Host_IsDedicated() && iProgressCount != 0 ) Cvar_SetValue( "scr_download", flProgress/iProgressCount * 100 ); HTTP_AutoClean(); From b23d5ed3547883971d1f3bd7b1cf3e3ceef3251e Mon Sep 17 00:00:00 2001 From: Jesse Buhagiar Date: Sun, 2 Jan 2022 00:39:02 +1100 Subject: [PATCH 306/548] Build: Add SerenityOS to list of compatible systems This is required by the build system to spit out a library with the correct name/platform. --- engine/common/build.c | 2 ++ public/build.h | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/engine/common/build.c b/engine/common/build.c index c4ddaeeb..42ba572c 100644 --- a/engine/common/build.c +++ b/engine/common/build.c @@ -95,6 +95,8 @@ const char *Q_buildos( void ) osname = "DOS4GW"; #elif XASH_HAIKU osname = "haiku"; +#elif XASH_SERENITY + osname = "serenityos"; #else #error "Place your operating system name here! If this is a mistake, try to fix conditions above and report a bug" #endif diff --git a/public/build.h b/public/build.h index 6e1f326d..57a7735f 100644 --- a/public/build.h +++ b/public/build.h @@ -74,6 +74,7 @@ For more information, please refer to #undef XASH_RISCV_DOUBLEFP #undef XASH_RISCV_SINGLEFP #undef XASH_RISCV_SOFTFP +#undef XASH_SERENITY #undef XASH_WIN32 #undef XASH_WIN64 #undef XASH_X86 @@ -125,6 +126,9 @@ For more information, please refer to #elif defined __HAIKU__ #define XASH_HAIKU 1 #define XASH_POSIX 1 +#elif defined __serenity__ + #define XASH_SERENITY 1 + #define XASH_POSIX 1 #else #error "Place your operating system name here! If this is a mistake, try to fix conditions above and report a bug" #endif From 99de598ea46dbb5622d252527bbc356177f21a4f Mon Sep 17 00:00:00 2001 From: Andrey Akhmichin <15944199+nekonomicon@users.noreply.github.com> Date: Fri, 24 Jun 2022 07:50:16 +0500 Subject: [PATCH 307/548] engine: common: imagelib: img_tga.c: fix broken tga flip. --- engine/common/imagelib/img_tga.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/common/imagelib/img_tga.c b/engine/common/imagelib/img_tga.c index 59228bdf..a182075b 100644 --- a/engine/common/imagelib/img_tga.c +++ b/engine/common/imagelib/img_tga.c @@ -127,7 +127,7 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi targa_rgba = image.rgba = Mem_Malloc( host.imagepool, image.size ); // if bit 5 of attributes isn't set, the image has been stored from bottom to top - if( !Image_CheckFlag( IL_DONTFLIP_TGA ) && targa_header.attributes & 0x20 ) + if( Image_CheckFlag( IL_DONTFLIP_TGA ) || targa_header.attributes & 0x20 ) { pixbuf = targa_rgba; row_inc = 0; From 6063ca2ad10f78322ce98136d97fb7c85d94307b Mon Sep 17 00:00:00 2001 From: Andrey Akhmichin <15944199+nekonomicon@users.noreply.github.com> Date: Thu, 23 Jun 2022 07:25:26 +0500 Subject: [PATCH 308/548] Documentation: opensource-mods: add Urbicide and Sewer. --- Documentation/opensource-mods.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/opensource-mods.md b/Documentation/opensource-mods.md index 70e666e8..3f50bb6c 100644 --- a/Documentation/opensource-mods.md +++ b/Documentation/opensource-mods.md @@ -24,6 +24,7 @@ Official github repository - https://github.com/CKFDevPowered/CKF3Alpha ## Cold Ice Version 1.9 is available on ModDB - https://www.moddb.com/mods/cold-ice/downloads/cold-ice-sdk + Version 1.9 mirrored on github - https://github.com/solidi/hl-mods/tree/master/ci ## Cold Ice Ressurection @@ -241,6 +242,9 @@ Spirit of Half Life: Opposing-Force Edition - https://github.com/Hammermaps-DEV/ ## Half-Life: Rebellion Reverse-engineered code, branch **rebellion** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/rebellion +## Half-Life: Urbicide +Branch **hl_urbicide** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/hl_urbicide + ## Half-Life: Visitors malortie's recreation - https://github.com/malortie/hl-visitors @@ -270,6 +274,9 @@ Reverse-engineered code, branch **residual_point** in hlsdk-xash3d - https://git ## Residual Point Reverse-engineered code, branch **residual_point** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/residual_point +## Sewer Beta +Branch **sewer_beta** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/sewer_beta + ## Team Fortress Classic Reverse-engineered code by Velaron - https://github.com/Velaron/tf15-client From e5720cf8b998cee653018583a116ff2f377bc14b Mon Sep 17 00:00:00 2001 From: Andrey Akhmichin <15944199+nekonomicon@users.noreply.github.com> Date: Fri, 24 Jun 2022 16:00:15 +0500 Subject: [PATCH 309/548] engine: common: imagelib: img_tga.c: fix broken tga flip again. --- engine/common/imagelib/img_tga.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/common/imagelib/img_tga.c b/engine/common/imagelib/img_tga.c index a182075b..6ad7ced3 100644 --- a/engine/common/imagelib/img_tga.c +++ b/engine/common/imagelib/img_tga.c @@ -127,7 +127,7 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi targa_rgba = image.rgba = Mem_Malloc( host.imagepool, image.size ); // if bit 5 of attributes isn't set, the image has been stored from bottom to top - if( Image_CheckFlag( IL_DONTFLIP_TGA ) || targa_header.attributes & 0x20 ) + if( Image_CheckFlag( IL_DONTFLIP_TGA ) && targa_header.attributes & 0x20 ) { pixbuf = targa_rgba; row_inc = 0; From 2182ba9630d0d07ab459042bf626bf01de738d2d Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 24 Jun 2022 19:15:28 +0300 Subject: [PATCH 310/548] engine: platform: sdl: try to enable ICO icons for 64-bit Windows, ignore TGA flip attribute for icons --- engine/platform/sdl/vid_sdl.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/engine/platform/sdl/vid_sdl.c b/engine/platform/sdl/vid_sdl.c index 4496c772..f589b53d 100644 --- a/engine/platform/sdl/vid_sdl.c +++ b/engine/platform/sdl/vid_sdl.c @@ -598,7 +598,7 @@ void VID_RestoreScreenResolution( void ) #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } -#if XASH_WIN32 && !XASH_64BIT // ICO support only for Win32 +#if XASH_WIN32 // ICO support only for Win32 #include "SDL_syswm.h" static void WIN_SetWindowIcon( HICON ico ) { @@ -686,7 +686,7 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) VID_RestoreScreenResolution(); } -#if XASH_WIN32 && !XASH_64BIT // ICO support only for Win32 +#if XASH_WIN32 // ICO support only for Win32 if( FS_FileExists( GI->iconpath, true ) ) { HICON ico; @@ -701,7 +701,7 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) WIN_SetWindowIcon( ico ); } } -#endif // _WIN32 && !XASH_64BIT +#endif // _WIN32 if( !iconLoaded ) { @@ -709,13 +709,15 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) COM_StripExtension( iconpath ); COM_DefaultExtension( iconpath, ".tga" ); + Image_SetForceFlags( IL_DONTFLIP_TGA ); icon = FS_LoadImage( iconpath, NULL, 0 ); + Image_ClearForceFlags(); if( icon ) { SDL_Surface *surface = SDL_CreateRGBSurfaceFrom( icon->buffer, icon->width, icon->height, 32, 4 * icon->width, - 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 ); + 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 ); if( surface ) { From 4e295622be449db43a5d1426f7cb16836a0349cb Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 25 Jun 2022 17:03:11 +0300 Subject: [PATCH 311/548] Revert "engine: platform: sdl: try to enable ICO icons for 64-bit Windows, ignore TGA flip attribute for icons" This reverts commit 2182ba9630d0d07ab459042bf626bf01de738d2d. --- engine/platform/sdl/vid_sdl.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/engine/platform/sdl/vid_sdl.c b/engine/platform/sdl/vid_sdl.c index f589b53d..4496c772 100644 --- a/engine/platform/sdl/vid_sdl.c +++ b/engine/platform/sdl/vid_sdl.c @@ -598,7 +598,7 @@ void VID_RestoreScreenResolution( void ) #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } -#if XASH_WIN32 // ICO support only for Win32 +#if XASH_WIN32 && !XASH_64BIT // ICO support only for Win32 #include "SDL_syswm.h" static void WIN_SetWindowIcon( HICON ico ) { @@ -686,7 +686,7 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) VID_RestoreScreenResolution(); } -#if XASH_WIN32 // ICO support only for Win32 +#if XASH_WIN32 && !XASH_64BIT // ICO support only for Win32 if( FS_FileExists( GI->iconpath, true ) ) { HICON ico; @@ -701,7 +701,7 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) WIN_SetWindowIcon( ico ); } } -#endif // _WIN32 +#endif // _WIN32 && !XASH_64BIT if( !iconLoaded ) { @@ -709,15 +709,13 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) COM_StripExtension( iconpath ); COM_DefaultExtension( iconpath, ".tga" ); - Image_SetForceFlags( IL_DONTFLIP_TGA ); icon = FS_LoadImage( iconpath, NULL, 0 ); - Image_ClearForceFlags(); if( icon ) { SDL_Surface *surface = SDL_CreateRGBSurfaceFrom( icon->buffer, icon->width, icon->height, 32, 4 * icon->width, - 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 ); + 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 ); if( surface ) { From 6199426e5ed355f3e4f94cff8b944ec442f5dc3f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 25 Jun 2022 17:03:12 +0300 Subject: [PATCH 312/548] Revert "engine: common: imagelib: img_tga.c: fix broken tga flip again." This reverts commit e5720cf8b998cee653018583a116ff2f377bc14b. --- engine/common/imagelib/img_tga.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/common/imagelib/img_tga.c b/engine/common/imagelib/img_tga.c index 6ad7ced3..a182075b 100644 --- a/engine/common/imagelib/img_tga.c +++ b/engine/common/imagelib/img_tga.c @@ -127,7 +127,7 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi targa_rgba = image.rgba = Mem_Malloc( host.imagepool, image.size ); // if bit 5 of attributes isn't set, the image has been stored from bottom to top - if( Image_CheckFlag( IL_DONTFLIP_TGA ) && targa_header.attributes & 0x20 ) + if( Image_CheckFlag( IL_DONTFLIP_TGA ) || targa_header.attributes & 0x20 ) { pixbuf = targa_rgba; row_inc = 0; From 36cec298c2fdffdff695deb5ea56bf9175bc448a Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 25 Jun 2022 17:03:13 +0300 Subject: [PATCH 313/548] Revert "engine: common: imagelib: img_tga.c: fix broken tga flip." This reverts commit 99de598ea46dbb5622d252527bbc356177f21a4f. --- engine/common/imagelib/img_tga.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/common/imagelib/img_tga.c b/engine/common/imagelib/img_tga.c index a182075b..59228bdf 100644 --- a/engine/common/imagelib/img_tga.c +++ b/engine/common/imagelib/img_tga.c @@ -127,7 +127,7 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi targa_rgba = image.rgba = Mem_Malloc( host.imagepool, image.size ); // if bit 5 of attributes isn't set, the image has been stored from bottom to top - if( Image_CheckFlag( IL_DONTFLIP_TGA ) || targa_header.attributes & 0x20 ) + if( !Image_CheckFlag( IL_DONTFLIP_TGA ) && targa_header.attributes & 0x20 ) { pixbuf = targa_rgba; row_inc = 0; From 3e23634369236fd0dfffb4219f0f1a529b1821d5 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 25 Jun 2022 17:04:02 +0300 Subject: [PATCH 314/548] engine: platform: sdl: enable ICO support on Win64 --- engine/platform/sdl/vid_sdl.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/platform/sdl/vid_sdl.c b/engine/platform/sdl/vid_sdl.c index 4496c772..14785a4c 100644 --- a/engine/platform/sdl/vid_sdl.c +++ b/engine/platform/sdl/vid_sdl.c @@ -598,7 +598,7 @@ void VID_RestoreScreenResolution( void ) #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } -#if XASH_WIN32 && !XASH_64BIT // ICO support only for Win32 +#if XASH_WIN32 // ICO support only for Win32 #include "SDL_syswm.h" static void WIN_SetWindowIcon( HICON ico ) { @@ -686,7 +686,7 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) VID_RestoreScreenResolution(); } -#if XASH_WIN32 && !XASH_64BIT // ICO support only for Win32 +#if XASH_WIN32 // ICO support only for Win32 if( FS_FileExists( GI->iconpath, true ) ) { HICON ico; @@ -728,7 +728,7 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) } } -#if XASH_WIN32 && !XASH_64BIT // ICO support only for Win32 +#if XASH_WIN32 // ICO support only for Win32 if( !iconLoaded ) { WIN_SetWindowIcon( LoadIcon( host.hInst, MAKEINTRESOURCE( 101 ) ) ); From 5285f51a268262f94eae86751a95d6a2cfae2c97 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 25 Jun 2022 17:12:08 +0300 Subject: [PATCH 315/548] engine: platform: sdl: fix loading ICO when it's in RoDir --- engine/platform/sdl/vid_sdl.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/engine/platform/sdl/vid_sdl.c b/engine/platform/sdl/vid_sdl.c index 14785a4c..89461073 100644 --- a/engine/platform/sdl/vid_sdl.c +++ b/engine/platform/sdl/vid_sdl.c @@ -628,6 +628,7 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) qboolean iconLoaded = false; char iconpath[MAX_STRING]; int xpos, ypos; + const char *localIcoPath; if( vid_highdpi->value ) wndFlags |= SDL_WINDOW_ALLOW_HIGHDPI; Q_strncpy( wndname, GI->title, sizeof( wndname )); @@ -687,13 +688,11 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) } #if XASH_WIN32 // ICO support only for Win32 - if( FS_FileExists( GI->iconpath, true ) ) + if(( localIcoPath = FS_GetDiskPath( GI->iconpath, true ))) { HICON ico; - char localPath[MAX_PATH]; - Q_snprintf( localPath, sizeof( localPath ), "%s/%s", GI->gamefolder, GI->iconpath ); - ico = (HICON)LoadImage( NULL, localPath, IMAGE_ICON, 0, 0, LR_LOADFROMFILE|LR_DEFAULTSIZE ); + ico = (HICON)LoadImage( NULL, localIcoPath, IMAGE_ICON, 0, 0, LR_LOADFROMFILE|LR_DEFAULTSIZE ); if( ico ) { From 87ce35b32d1d7101b5322b4b409e0d47c277365f Mon Sep 17 00:00:00 2001 From: Andrey Akhmichin <15944199+nekonomicon@users.noreply.github.com> Date: Sat, 25 Jun 2022 18:25:38 +0500 Subject: [PATCH 316/548] engine: common: imagelib: img_utils.c: change formats priority. --- engine/common/imagelib/img_utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/common/imagelib/img_utils.c b/engine/common/imagelib/img_utils.c index 5ff568cf..ce2e57bd 100644 --- a/engine/common/imagelib/img_utils.c +++ b/engine/common/imagelib/img_utils.c @@ -96,8 +96,8 @@ static const loadpixformat_t load_null[] = static const loadpixformat_t load_game[] = { { "%s%s.%s", "dds", Image_LoadDDS, IL_HINT_NO }, // dds for world and studio models -{ "%s%s.%s", "tga", Image_LoadTGA, IL_HINT_NO }, // hl vgui menus { "%s%s.%s", "bmp", Image_LoadBMP, IL_HINT_NO }, // WON menu images +{ "%s%s.%s", "tga", Image_LoadTGA, IL_HINT_NO }, // hl vgui menus { "%s%s.%s", "png", Image_LoadPNG, IL_HINT_NO }, // NightFire 007 menus { "%s%s.%s", "mip", Image_LoadMIP, IL_HINT_NO }, // hl textures from wad or buffer { "%s%s.%s", "mdl", Image_LoadMDL, IL_HINT_HL }, // hl studio model skins From c076f4ff8e7bc3bdf72a9a5ec290024b14578417 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 26 Jun 2022 04:39:15 +0300 Subject: [PATCH 317/548] engine: common: add generic trace_t initialize function --- engine/common/pm_local.h | 16 ++++++++++++++++ engine/common/pm_trace.c | 15 +++------------ engine/server/sv_world.c | 15 +++------------ 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/engine/common/pm_local.h b/engine/common/pm_local.h index d73dd375..6ad6d8e2 100644 --- a/engine/common/pm_local.h +++ b/engine/common/pm_local.h @@ -40,6 +40,22 @@ int PM_TruePointContents( playermove_t *pmove, const vec3_t p ); int PM_PointContents( playermove_t *pmove, const vec3_t p ); void PM_ConvertTrace( trace_t *out, pmtrace_t *in, edict_t *ent ); +static inline void PM_InitTrace( trace_t *trace, const vec3_t end ) +{ + memset( trace, 0, sizeof( *trace )); + VectorCopy( end, trace->endpos ); + trace->allsolid = true; + trace->fraction = 1.0f; +} + +static inline void PM_InitPMTrace( pmtrace_t *trace, const vec3_t end ) +{ + memset( trace, 0, sizeof( *trace )); + VectorCopy( end, trace->endpos ); + trace->allsolid = true; + trace->fraction = 1.0f; +} + // // pm_surface.c // diff --git a/engine/common/pm_trace.c b/engine/common/pm_trace.c index c60a55eb..0e1ce7c6 100644 --- a/engine/common/pm_trace.c +++ b/engine/common/pm_trace.c @@ -446,10 +446,7 @@ pmtrace_t PM_PlayerTraceExt( playermove_t *pmove, vec3_t start, vec3_t end, int VectorSubtract( end, offset, end_l ); } - memset( &trace_bbox, 0, sizeof( trace_bbox )); - VectorCopy( end, trace_bbox.endpos ); - trace_bbox.allsolid = true; - trace_bbox.fraction = 1.0f; + PM_InitPMTrace( &trace_bbox, end ); if( hullcount < 1 ) { @@ -475,10 +472,7 @@ pmtrace_t PM_PlayerTraceExt( playermove_t *pmove, vec3_t start, vec3_t end, int for( last_hitgroup = 0, j = 0; j < hullcount; j++ ) { - memset( &trace_hitbox, 0, sizeof( trace_hitbox )); - VectorCopy( end, trace_hitbox.endpos ); - trace_hitbox.allsolid = true; - trace_hitbox.fraction = 1.0f; + PM_InitPMTrace( &trace_hitbox, end ); PM_RecursiveHullCheck( &hull[j], hull[j].firstclipnode, 0, 1, start_l, end_l, &trace_hitbox ); @@ -622,10 +616,7 @@ int PM_TestPlayerPosition( playermove_t *pmove, vec3_t pos, pmtrace_t *ptrace, p { pmtrace_t trace; - memset( &trace, 0, sizeof( trace )); - VectorCopy( pos, trace.endpos ); - trace.allsolid = true; - trace.fraction = 1.0f; + PM_InitPMTrace( &trace, pos ); // run custom sweep callback if( pmove->server || Host_IsLocalClient( )) diff --git a/engine/server/sv_world.c b/engine/server/sv_world.c index ea823974..71079e65 100644 --- a/engine/server/sv_world.c +++ b/engine/server/sv_world.c @@ -871,10 +871,7 @@ void SV_ClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t qboolean rotated, transform_bbox; matrix4x4 matrix; - memset( trace, 0, sizeof( trace_t )); - VectorCopy( end, trace->endpos ); - trace->fraction = 1.0f; - trace->allsolid = 1; + PM_InitTrace( trace, end ); model = SV_ModelHandle( ent->v.modelindex ); @@ -945,10 +942,7 @@ void SV_ClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t for( i = 0; i < hullcount; i++ ) { - memset( &trace_hitbox, 0, sizeof( trace_t )); - VectorCopy( end, trace_hitbox.endpos ); - trace_hitbox.fraction = 1.0; - trace_hitbox.allsolid = 1; + PM_InitTrace( &trace_hitbox, end ); PM_RecursiveHullCheck( &hull[i], hull[i].firstclipnode, 0.0f, 1.0f, start_l, end_l, (pmtrace_t *)&trace_hitbox ); @@ -1114,10 +1108,7 @@ or custom physics implementation void SV_CustomClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace ) { // initialize custom trace - memset( trace, 0, sizeof( trace_t )); - VectorCopy( end, trace->endpos ); - trace->allsolid = true; - trace->fraction = 1.0f; + PM_InitTrace( trace, end ); if( svgame.physFuncs.ClipMoveToEntity != NULL ) { From 85895c5311764667300557b53c66407208f5e10a Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 26 Jun 2022 04:39:52 +0300 Subject: [PATCH 318/548] engine: pmove: initialize trace argument in PM_TraceModel --- engine/client/cl_pmove.c | 2 ++ engine/server/sv_pmove.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/engine/client/cl_pmove.c b/engine/client/cl_pmove.c index 3d3a9a98..04dedf1f 100644 --- a/engine/client/cl_pmove.c +++ b/engine/client/cl_pmove.c @@ -794,6 +794,8 @@ static float GAME_EXPORT pfnTraceModel( physent_t *pe, float *start, float *end, matrix4x4 matrix; hull_t *hull; + PM_InitTrace( trace, end ); + old_usehull = clgame.pmove->usehull; clgame.pmove->usehull = 2; diff --git a/engine/server/sv_pmove.c b/engine/server/sv_pmove.c index e8e0ea29..53337e80 100644 --- a/engine/server/sv_pmove.c +++ b/engine/server/sv_pmove.c @@ -455,6 +455,8 @@ static float GAME_EXPORT pfnTraceModel( physent_t *pe, float *start, float *end, matrix4x4 matrix; hull_t *hull; + PM_InitTrace( trace, end ); + old_usehull = svgame.pmove->usehull; svgame.pmove->usehull = 2; From f0a856d9c84b4b99d7228c461ae87a0b64d49ff9 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 26 Jun 2022 15:05:06 +0300 Subject: [PATCH 319/548] engine: server: fix writing message size for engine messages, as it's expected to be 2-bytes --- engine/server/sv_game.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index 588bc7d8..d7343fb0 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -2673,7 +2673,7 @@ void GAME_EXPORT pfnMessageEnd( void ) return; } - sv.multicast.pData[svgame.msg_size_index] = svgame.msg_realsize; + *(word *)&sv.multicast.pData[svgame.msg_size_index] = svgame.msg_realsize; } } else if( svgame.msg[svgame.msg_index].size != -1 ) From af8febabd11735e640ab9466e903ac34d1f00be9 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 26 Jun 2022 15:09:36 +0300 Subject: [PATCH 320/548] engine: client: make temp entity buffer larger in case of long textmessages --- engine/client/cl_tent.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c index 1e9c19e9..36ea6003 100644 --- a/engine/client/cl_tent.c +++ b/engine/client/cl_tent.c @@ -1904,7 +1904,7 @@ handle temp-entity messages void CL_ParseTempEntity( sizebuf_t *msg ) { sizebuf_t buf; - byte pbuf[256]; + byte pbuf[2048]; int iSize; int type, color, count, flags; int decalIndex, modelIndex, entityIndex; @@ -1923,6 +1923,10 @@ void CL_ParseTempEntity( sizebuf_t *msg ) decalIndex = modelIndex = entityIndex = 0; + // this will probably be fatal anyway + if( iSize > sizeof( pbuf )) + Con_Printf( S_ERROR "%s: Temp buffer overflow!\n", __FUNCTION__ ); + // parse user message into buffer MSG_ReadBytes( msg, pbuf, iSize ); From b9b8b0521bb8cd91b708c2ef38ac2480ccac072e Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 29 Jun 2022 02:36:39 +0300 Subject: [PATCH 321/548] common: add unlikely()/likely() macros --- common/xash3d_types.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/common/xash3d_types.h b/common/xash3d_types.h index 71ceca77..ffc3faee 100644 --- a/common/xash3d_types.h +++ b/common/xash3d_types.h @@ -87,6 +87,16 @@ typedef uint64_t longtime_t; #define NORETURN #endif +#if defined(__has_builtin) +#if __has_builtin(__builtin_expect) +#define unlikely(x) __builtin_expect(x, 0) +#define likely(x) __builtin_expect(x, 1) +#else // __has_builtin(__builtin_expect) +#define unlikely(x) (x) +#define likely(x) (x) +#endif // __has_builtin(__builtin_expect) +#endif // defined(__has_builtin) + #ifdef XASH_BIG_ENDIAN #define LittleLong(x) (((int)(((x)&255)<<24)) + ((int)((((x)>>8)&255)<<16)) + ((int)(((x)>>16)&255)<<8) + (((x) >> 24)&255)) From 12815bfbf2778f349e54f975b18af12041acbca9 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 29 Jun 2022 02:39:18 +0300 Subject: [PATCH 322/548] public: remove naive implementations of standard function, add them with standard C with few extensions --- public/crtlib.c | 93 +++---------------------------------------------- public/crtlib.h | 63 +++++++++++++++++++++++++++++---- 2 files changed, 60 insertions(+), 96 deletions(-) diff --git a/public/crtlib.c b/public/crtlib.c index 7260595e..11efccc3 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -317,68 +317,6 @@ void Q_atov( float *vec, const char *str, size_t siz ) } } -int Q_strnicmp( const char *s1, const char *s2, int n ) -{ - int c1, c2; - - if( s1 == NULL ) - { - if( s2 == NULL ) - return 0; - else return -1; - } - else if( s2 == NULL ) - { - return 1; - } - - do { - c1 = *s1++; - c2 = *s2++; - - if( !n-- ) return 0; // strings are equal until end point - - if( c1 != c2 ) - { - if( c1 >= 'a' && c1 <= 'z' ) c1 -= ('a' - 'A'); - if( c2 >= 'a' && c2 <= 'z' ) c2 -= ('a' - 'A'); - if( c1 != c2 ) return c1 < c2 ? -1 : 1; - } - } while( c1 ); - - // strings are equal - return 0; -} - -int Q_strncmp( const char *s1, const char *s2, int n ) -{ - int c1, c2; - - if( s1 == NULL ) - { - if( s2 == NULL ) - return 0; - else return -1; - } - else if( s2 == NULL ) - { - return 1; - } - - do { - c1 = *s1++; - c2 = *s2++; - - // strings are equal until end point - if( !n-- ) return 0; - if( c1 != c2 ) return c1 < c2 ? -1 : 1; - - } while( c1 ); - - // strings are equal - return 0; -} - static qboolean Q_starcmp( const char *pattern, const char *text ) { char c, c1; @@ -470,32 +408,8 @@ const char* Q_timestamp( int format ) return timestamp; } -char *Q_strstr( const char *string, const char *string2 ) -{ - int c; - size_t len; - - if( !string || !string2 ) return NULL; - - c = *string2; - len = Q_strlen( string2 ); - - while( string ) - { - for( ; *string && *string != c; string++ ); - - if( *string ) - { - if( !Q_strncmp( string, string2, len )) - break; - string++; - } - else return NULL; - } - return (char *)string; -} - -char *Q_stristr( const char *string, const char *string2 ) +#if !defined( HAVE_STRCASESTR ) +const char *Q_stristr( const char *string, const char *string2 ) { int c; size_t len; @@ -517,8 +431,9 @@ char *Q_stristr( const char *string, const char *string2 ) } else return NULL; } - return (char *)string; + return string; } +#endif // !defined( HAVE_STRCASESTR ) int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args ) { diff --git a/public/crtlib.h b/public/crtlib.h index b03aa812..0bcf9ce7 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -16,9 +16,10 @@ GNU General Public License for more details. #ifndef STDLIB_H #define STDLIB_H -#include #include +#include #include "build.h" +#include "xash3d_types.h" // timestamp modes enum @@ -60,14 +61,8 @@ float Q_atof( const char *str ); void Q_atov( float *vec, const char *str, size_t siz ); #define Q_strchr strchr #define Q_strrchr strrchr -#define Q_stricmp( s1, s2 ) Q_strnicmp( s1, s2, 99999 ) -int Q_strnicmp( const char *s1, const char *s2, int n ); -#define Q_strcmp( s1, s2 ) Q_strncmp( s1, s2, 99999 ) -int Q_strncmp( const char *s1, const char *s2, int n ); qboolean Q_stricmpext( const char *s1, const char *s2 ); const char *Q_timestamp( int format ); -char *Q_stristr( const char *string, const char *string2 ); -char *Q_strstr( const char *string, const char *string2 ); #define Q_vsprintf( buffer, format, args ) Q_vsnprintf( buffer, 99999, format, args ) int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args ); int Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ) _format( 3 ); @@ -95,4 +90,58 @@ char *_COM_ParseFileSafe( char *data, char *token, const int size, unsigned int int matchpattern( const char *in, const char *pattern, qboolean caseinsensitive ); int matchpattern_with_separator( const char *in, const char *pattern, qboolean caseinsensitive, const char *separators, qboolean wildcard_least_one ); +// libc implementations +static inline int Q_strcmp( const char *s1, const char *s2 ) +{ + return unlikely(!s1) ? + ( !s2 ? 0 : -1 ) : + ( unlikely(!s2) ? 1 : strcmp( s1, s2 )); +} + +static inline int Q_strncmp( const char *s1, const char *s2, size_t n ) +{ + return unlikely(!s1) ? + ( !s2 ? 0 : -1 ) : + ( unlikely(!s2) ? 1 : strncmp( s1, s2, n )); +} + +static inline const char *Q_strstr( const char *s1, const char *s2 ) +{ + return unlikely( !s1 || !s2 ) ? NULL : strstr( s1, s2 ); +} + +// libc extensions, be careful + +#if XASH_WIN32 +#define strcasecmp stricmp +#define strncasecmp strnicmp +#endif // XASH_WIN32 + +static inline int Q_stricmp( const char *s1, const char *s2 ) +{ + return unlikely(!s1) ? + ( !s2 ? 0 : -1 ) : + ( unlikely(!s2) ? 1 : strcasecmp( s1, s2 )); +} + +static inline int Q_strnicmp( const char *s1, const char *s2, size_t n ) +{ + return unlikely(!s1) ? + ( !s2 ? 0 : -1 ) : + ( unlikely(!s2) ? 1 : strncasecmp( s1, s2, n )); +} + +#if defined( HAVE_STRCASESTR ) +#if XASH_WIN32 +#define strcasestr stristr +#endif +static inline const char *Q_stristr( const char *s1, const char *s2 ) +{ + return unlikely( !s1 || !s2 ) ? NULL : strcasestr( s1, s2 ); +} +#else // defined( HAVE_STRCASESTR ) +const char *Q_stristr( const char *s1, const char *s2 ); +#endif // defined( HAVE_STRCASESTR ) + + #endif//STDLIB_H From d52b07beaccc13e8714990d12ba323696f1c5e3e Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 29 Jun 2022 02:43:09 +0300 Subject: [PATCH 323/548] wscript: add stristr/strcasestr presense check --- wscript | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/wscript b/wscript index 378c29b6..f4ffcb20 100644 --- a/wscript +++ b/wscript @@ -317,6 +317,19 @@ int main(void) { return 0; }''', conf.define('_FILE_OFFSET_BITS', 64) else: conf.undefine('_FILE_OFFSET_BITS') + if conf.env.DEST_OS == 'win32': + # msvcrt always has stristr + conf.define('HAVE_STRCASESTR', 1) + else: + strcasestr_frag = '''#include +int main(int argc, char **argv) { strcasestr(argv[1], argv[2]); return 0; }''' + + if conf.check_cc(msg='Checking for strcasestr', mandatory=False, fragment=strcasestr_frag): + conf.define('HAVE_STRCASESTR', 1) + elif conf.check_cc(msg='... with _GNU_SOURCE?', mandatory=False, fragment=strcasestr_frag, defines='_GNU_SOURCE=1'): + conf.define('_GNU_SOURCE', 1) + conf.define('HAVE_STRCASESTR', 1) + # check if we can use alloca.h or malloc.h if conf.check_cc(header_name='alloca.h', mandatory=False): conf.define('ALLOCA_H', 'alloca.h') From 79dc090aae4b27ee2de1f88640cfa26bfc8a15c8 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 29 Jun 2022 02:44:50 +0300 Subject: [PATCH 324/548] engine: platform: sdl: use SetClassLongPtr function to be compatible with 64-bit Windows API --- engine/platform/sdl/vid_sdl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/platform/sdl/vid_sdl.c b/engine/platform/sdl/vid_sdl.c index 89461073..c2453cca 100644 --- a/engine/platform/sdl/vid_sdl.c +++ b/engine/platform/sdl/vid_sdl.c @@ -609,7 +609,7 @@ static void WIN_SetWindowIcon( HICON ico ) if( SDL_GetWindowWMInfo( host.hWnd, &wminfo ) ) { - SetClassLong( wminfo.info.win.window, GCL_HICON, (LONG)ico ); + SetClassLongPtr( wminfo.info.win.window, GCLP_HICON, (LONG)ico ); } } #endif From 2905f951bd5a789677a4228a5c82f086a650cc4c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 29 Jun 2022 03:20:55 +0300 Subject: [PATCH 325/548] engine: menu_int: expose TF_EXPAND_SOURCE imagelib flag --- engine/menu_int.h | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/menu_int.h b/engine/menu_int.h index f8b23614..8a6905d6 100644 --- a/engine/menu_int.h +++ b/engine/menu_int.h @@ -30,6 +30,7 @@ typedef int HIMAGE; // handle to a graphic #define PIC_NEAREST (1<<0) // disable texfilter #define PIC_KEEP_SOURCE (1<<1) // some images keep source #define PIC_NOFLIP_TGA (1<<2) // Steam background completely ignore tga attribute 0x20 +#define PIC_EXPAND_SOURCE (1<<3) // don't keep as 8-bit source, expand to RGBA // flags for COM_ParseFileSafe #define PFILE_IGNOREBRACKET (1<<0) From ac9664c7ae39c4c1b9bb43a41d68aa39d6047d4c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 29 Jun 2022 03:23:51 +0300 Subject: [PATCH 326/548] mainui: update --- mainui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainui b/mainui index c97245c0..2ba099b2 160000 --- a/mainui +++ b/mainui @@ -1 +1 @@ -Subproject commit c97245c0f526880a1b1b7354ce34f92307742d63 +Subproject commit 2ba099b2a1b32dfdb4ecb66b27fc6df095199eb4 From 5d4f8373c44a9fa6b8e96b76bbad46b8f46662f5 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 29 Jun 2022 03:50:50 +0300 Subject: [PATCH 327/548] common: fix usage of __has_builtin for old GCC releases --- common/xash3d_types.h | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/common/xash3d_types.h b/common/xash3d_types.h index ffc3faee..71472d43 100644 --- a/common/xash3d_types.h +++ b/common/xash3d_types.h @@ -87,15 +87,21 @@ typedef uint64_t longtime_t; #define NORETURN #endif -#if defined(__has_builtin) -#if __has_builtin(__builtin_expect) -#define unlikely(x) __builtin_expect(x, 0) -#define likely(x) __builtin_expect(x, 1) -#else // __has_builtin(__builtin_expect) -#define unlikely(x) (x) -#define likely(x) (x) -#endif // __has_builtin(__builtin_expect) -#endif // defined(__has_builtin) +#if ( __GNUC__ >= 3 ) + #define unlikely(x) __builtin_expect(x, 0) + #define likely(x) __builtin_expect(x, 1) +#elif defined( __has_builtin ) + #if __has_builtin( __builtin_expect ) + #define unlikely(x) __builtin_expect(x, 0) + #define likely(x) __builtin_expect(x, 1) + #else + #define unlikely(x) (x) + #define likely(x) (x) + #endif +#else + #define unlikely(x) (x) + #define likely(x) (x) +#endif #ifdef XASH_BIG_ENDIAN From fb43a5590f820f18318fe41068170e819316ec16 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 29 Jun 2022 03:56:42 +0300 Subject: [PATCH 328/548] public: Q_strstr should return pointer to non-const data --- public/crtlib.c | 4 ++-- public/crtlib.h | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/public/crtlib.c b/public/crtlib.c index 11efccc3..2cc3e4e9 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -409,7 +409,7 @@ const char* Q_timestamp( int format ) } #if !defined( HAVE_STRCASESTR ) -const char *Q_stristr( const char *string, const char *string2 ) +char *Q_stristr( const char *string, const char *string2 ) { int c; size_t len; @@ -431,7 +431,7 @@ const char *Q_stristr( const char *string, const char *string2 ) } else return NULL; } - return string; + return (char *)string; } #endif // !defined( HAVE_STRCASESTR ) diff --git a/public/crtlib.h b/public/crtlib.h index 0bcf9ce7..43a0c775 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -105,9 +105,9 @@ static inline int Q_strncmp( const char *s1, const char *s2, size_t n ) ( unlikely(!s2) ? 1 : strncmp( s1, s2, n )); } -static inline const char *Q_strstr( const char *s1, const char *s2 ) +static inline char *Q_strstr( const char *s1, const char *s2 ) { - return unlikely( !s1 || !s2 ) ? NULL : strstr( s1, s2 ); + return unlikely( !s1 || !s2 ) ? NULL : (char*)strstr( s1, s2 ); } // libc extensions, be careful @@ -135,12 +135,12 @@ static inline int Q_strnicmp( const char *s1, const char *s2, size_t n ) #if XASH_WIN32 #define strcasestr stristr #endif -static inline const char *Q_stristr( const char *s1, const char *s2 ) +static inline char *Q_stristr( const char *s1, const char *s2 ) { - return unlikely( !s1 || !s2 ) ? NULL : strcasestr( s1, s2 ); + return unlikely( !s1 || !s2 ) ? NULL : (char *)strcasestr( s1, s2 ); } #else // defined( HAVE_STRCASESTR ) -const char *Q_stristr( const char *s1, const char *s2 ); +char *Q_stristr( const char *s1, const char *s2 ); #endif // defined( HAVE_STRCASESTR ) From 854bfb86730d94cb67160b62eacbf334c2a1f9d5 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 29 Jun 2022 04:00:35 +0300 Subject: [PATCH 329/548] wscript: woops, msvcrt don't actually have stristr --- wscript | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/wscript b/wscript index f4ffcb20..f87fc7ff 100644 --- a/wscript +++ b/wscript @@ -317,10 +317,7 @@ int main(void) { return 0; }''', conf.define('_FILE_OFFSET_BITS', 64) else: conf.undefine('_FILE_OFFSET_BITS') - if conf.env.DEST_OS == 'win32': - # msvcrt always has stristr - conf.define('HAVE_STRCASESTR', 1) - else: + if conf.env.DEST_OS != 'win32': strcasestr_frag = '''#include int main(int argc, char **argv) { strcasestr(argv[1], argv[2]); return 0; }''' From 3fe392b41f17b2a49fdd830ef2aa77e91adf205f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 29 Jun 2022 18:05:51 +0300 Subject: [PATCH 330/548] ref: fix processing indexed textures --- ref_gl/gl_image.c | 5 +++++ ref_soft/r_image.c | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/ref_gl/gl_image.c b/ref_gl/gl_image.c index 1b5ed033..658b0240 100644 --- a/ref_gl/gl_image.c +++ b/ref_gl/gl_image.c @@ -1908,6 +1908,11 @@ void GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor ) // all the operations makes over the image copy not an original pic = gEngfuncs.FS_CopyImage( image->original ); + + // we need to expand image into RGBA buffer + if( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 ) + flags |= IMAGE_FORCE_RGBA; + gEngfuncs.Image_Process( &pic, topColor, bottomColor, flags, 0.0f ); GL_UploadTexture( image, pic ); diff --git a/ref_soft/r_image.c b/ref_soft/r_image.c index a374d401..247e2e1e 100644 --- a/ref_soft/r_image.c +++ b/ref_soft/r_image.c @@ -1186,6 +1186,11 @@ void GAME_EXPORT GL_ProcessTexture( int texnum, float gamma, int topColor, int b // all the operations makes over the image copy not an original pic = gEngfuncs.FS_CopyImage( image->original ); + + // we need to expand image into RGBA buffer + if( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 ) + flags |= IMAGE_FORCE_RGBA; + gEngfuncs.Image_Process( &pic, topColor, bottomColor, flags, 0.0f ); GL_UploadTexture( image, pic ); From 95ed044fee9c450f880f6e5e89f95ffe2f8df350 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 29 Jun 2022 18:14:53 +0300 Subject: [PATCH 331/548] engine: common: disable cl_filterstuffcmd by default --- engine/common/cvar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/common/cvar.c b/engine/common/cvar.c index 108b3956..0e33ef31 100644 --- a/engine/common/cvar.c +++ b/engine/common/cvar.c @@ -21,7 +21,7 @@ GNU General Public License for more details. convar_t *cvar_vars = NULL; // head of list convar_t *cmd_scripting; -CVAR_DEFINE_AUTO( cl_filterstuffcmd, "1", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "filter commands coming from server" ); +CVAR_DEFINE_AUTO( cl_filterstuffcmd, "0", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "filter commands coming from server" ); /* ============ From d46d62bf0325c52191377b76ff4abdbc179a761c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 29 Jun 2022 18:57:10 +0300 Subject: [PATCH 332/548] engine: client: drop loading plaque on second signon, remove servercount check --- engine/client/cl_main.c | 3 +-- engine/client/cl_scrn.c | 1 - engine/client/client.h | 3 --- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 225172aa..46782aef 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -195,8 +195,6 @@ void CL_CheckClientState( void ) Netchan_ReportFlow( &cls.netchan ); Con_DPrintf( "client connected at %.2f sec\n", Sys_DoubleTime() - cls.timestart ); - if(( cls.demoplayback || cls.disable_servercount != cl.servercount ) && cl.video_prepped ) - SCR_EndLoadingPlaque(); // get rid of loading plaque } } @@ -234,6 +232,7 @@ void CL_SignonReply( void ) Mem_PrintStats(); break; case 2: + SCR_EndLoadingPlaque(); if( cl.proxy_redirect && !cls.spectator ) CL_Disconnect(); cl.proxy_redirect = false; diff --git a/engine/client/cl_scrn.c b/engine/client/cl_scrn.c index 62a42047..ee212d46 100644 --- a/engine/client/cl_scrn.c +++ b/engine/client/cl_scrn.c @@ -418,7 +418,6 @@ void SCR_BeginLoadingPlaque( qboolean is_background ) cls.draw_changelevel = !is_background; SCR_UpdateScreen(); cls.disable_screen = host.realtime; - cls.disable_servercount = cl.servercount; cl.background = is_background; // set right state before svc_serverdata is came if( !Host_IsDedicated() ) diff --git a/engine/client/client.h b/engine/client/client.h index 129caca6..07bb0c57 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -527,9 +527,6 @@ typedef struct float disable_screen; // showing loading plaque between levels // or changing rendering dlls // if time gets > 30 seconds ahead, break it - int disable_servercount; // when we receive a frame and cl.servercount - // > cls.disable_servercount, clear disable_screen - qboolean draw_changelevel; // draw changelevel image 'Loading...' keydest_t key_dest; From 64eb0a694d17f9a2f24c42827e802d3be7a1d1cb Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 29 Jun 2022 18:58:01 +0300 Subject: [PATCH 333/548] engine: client: also drop loading plaque on toggleconsole, in case if it's stuck --- engine/client/console.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/client/console.c b/engine/client/console.c index e53aafce..bae1a35d 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -283,6 +283,8 @@ void Con_ToggleConsole_f( void ) if( !host.allow_console || UI_CreditsActive( )) return; // disabled + SCR_EndLoadingPlaque(); + // show console only in game or by special call from menu if( cls.state != ca_active || cls.key_dest == key_menu ) return; From 3ec997721223b10616a0f1135f7166aee2cbf508 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Thu, 30 Jun 2022 03:34:28 +0400 Subject: [PATCH 334/548] wscript: added parallel builds compiler flag for MSVC --- scripts/waifulib/compiler_optimizations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/waifulib/compiler_optimizations.py b/scripts/waifulib/compiler_optimizations.py index 15b70e7a..77eb951a 100644 --- a/scripts/waifulib/compiler_optimizations.py +++ b/scripts/waifulib/compiler_optimizations.py @@ -51,7 +51,7 @@ LINKFLAGS = { CFLAGS = { 'common': { # disable thread-safe local static initialization for C++11 code, as it cause crashes on Windows XP - 'msvc': ['/D_USING_V110_SDK71_', '/FS', '/Zc:threadSafeInit-', '/MT'], + 'msvc': ['/D_USING_V110_SDK71_', '/FS', '/Zc:threadSafeInit-', '/MT', '/MP'], 'clang': ['-g', '-gdwarf-2', '-fvisibility=hidden', '-fno-threadsafe-statics'], 'gcc': ['-g', '-fvisibility=hidden'], 'owcc': ['-fno-short-enum', '-ffloat-store', '-g3'] From c4a25e0d704b6bac0c83dff530b18307adf2bd1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BB=D0=B0=D0=B4=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A1=D1=83=D1=85=D0=BE=D0=B2?= <22411953+Vladislav4KZ@users.noreply.github.com> Date: Thu, 30 Jun 2022 16:00:52 +0600 Subject: [PATCH 335/548] Documentation: supported-mod-list: fix dead links Renamed domains: hl-lab.ru -> gamer-lab.com planetphilip.com -> runthinkshootlive.com --- Documentation/supported-mod-list.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Documentation/supported-mod-list.md b/Documentation/supported-mod-list.md index a155f7ba..ea6345f1 100644 --- a/Documentation/supported-mod-list.md +++ b/Documentation/supported-mod-list.md @@ -46,7 +46,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 16. [Betrayal](http://www.moddb.com/mods/betrayal) (this mod has inner bugs; set **gl_allow_mirrors** to **0** to prevent drops of game's perfomance in some areas) 17. [Between Two Worlds](https://www.runthinkshootlive.com/posts/between-two-worlds/) 18. [Black Mesa Energy Testing Chamber](http://twhl.info/competitions.php?results=17) -19. [Black Mesa Sideline](http://www.isolated-design.de/half-life-mods/black-mesa-sideline/) (set **gl_allow_mirrors** to **0** to prevent drops of game's perfomance in some areas) +19. [Black Mesa Sideline](https://www.moddb.com/mods/black-mesa-sideline) (set **gl_allow_mirrors** to **0** to prevent drops of game's perfomance in some areas) 20. [BlackMesa 2007( Black Mesa Missions 2007 )](http://www.moddb.com/addons/blackmesa-2007) 21. [Blood and Bones](https://www.runthinkshootlive.com/posts/blood-and-bones/) 22. Boom (Huknenn) v1.0 & [HL Boom: Gold Edition v1.1](http://www.moddb.com/mods/boom) @@ -103,8 +103,8 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 72. [Freeman](https://www.runthinkshootlive.com/posts/freeman/) 73. [Freeman's Escape](http://www.moddb.com/mods/freeman-escape-french) (2 maps by *JiggZ*) 74. [Freeman's Fight](https://www.runthinkshootlive.com/posts/freemans-fight/) -75. [Freeman's Return](http://jpmaps.wz.cz/epizody.htm) -76. [Freeman's Return 2](http://jpmaps.wz.cz/epizody.htm) +75. [Freeman's Return](https://www.moddb.com/games/half-life/addons/freemans-return-v12) +76. [Freeman's Return 2](https://www.moddb.com/games/half-life/addons/freemans-return-2-v11) 77. [Freeman's Revenge](https://www.runthinkshootlive.com/posts/freemans-revenge/) 78. [Freeman's Tomb( The Crypt )](http://twhl.info/vault.php?map=3787) 79. [G-Invasion v2.5](http://www.moddb.com/mods/g-man-invasion) aka G-Man Invasion (there is an inner crash bug on final titles: some text lines are too long and cannot be properly displayed) @@ -129,7 +129,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 96. [Haunted](https://www.runthinkshootlive.com/posts/haunted/) (set **sv_validate_changelevel** to **0** to avoid a problem with level change between maps **pagan7** and **pagan8**) 97. [Hazardous Materials: Episode 1 & 2](http://www.moddb.com/mods/half-life-hazardous-materials) 98. [Hazardous-Course 2](http://www.moddb.com/mods/hazardous-course-2) -99. [Help Wanted](http://maps.mrsucko.org/work/) +99. [Help Wanted](https://www.moddb.com/games/half-life/addons/help-wanted) 100. [Hidden Evil v1.01](https://www.runthinkshootlive.com/posts/hidden-evil/) 101. [High Speed](http://www.moddb.com/games/half-life/addons/high-speed-english-version) 102. [hlife_hotdog_compo26](http://twhl.info/vault.php?map=5181) @@ -155,7 +155,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 122. [Mario Keys](http://www.moddb.com/mods/mario-keys) 123. [McBeth](https://www.runthinkshootlive.com/posts/mcbeth/) 124. [Medieval World](http://www.moddb.com/mods/medieval-world) -125. [Mel Soaring 2: Star Rancor](http://www.etherealhell.com/etherealhell/index.php/my-levels/half-life) +125. [Mel Soaring 2: Star Rancor](https://www.moddb.com/addons/mel-soaring-2-star-rancor) 126. [MINIMICUS](http://twhl.info/vault.php?map=163) 127. [Mission Failed](http://www.moddb.com/mods/mission-failed) 128. [Mission MC Poker](http://www.moddb.com/mods/half-life-mission-mc-poker) @@ -176,7 +176,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 143. [Operation: Nova](http://www.moddb.com/mods/half-life-operation-nova) (there is an inner bug with an M60 machinegun on a map with Osprey: it can be activated and used if you are staying in front of it, not behind it) 144. [Optimum Fear](https://www.fileplanet.com/7472/0/fileinfo/Optimum-Fear) 145. [Outrun](http://www.moddb.com/mods/outrun) -146. [Outwards (Day One)](http://www.visgi.info/maps/) +146. [Outwards (Day One)](http://www.visgi.info/maps/) (link dead!) 147. [Overhaul Pack](http://www.moddb.com/mods/half-life-overhaul-pack) (Just HD pack for Half-Life) 148. [P.I.Z.D.E.C.](https://www.runthinkshootlive.com/posts/pizdec/) 149. [pagoda](http://www.moddb.com/mods/pagoda1) @@ -184,7 +184,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 151. [Phobos IV](http://www.moddb.com/mods/phobos-iv) 152. [Pimp My Car](https://www.runthinkshootlive.com/posts/pimp-my-car/) (there is an inner issue with incorrect responses of buttons on combination locks) 153. [Point Blank – Stackdeath Complex](https://www.runthinkshootlive.com/posts/half-life-point-blank-stackdeath-complex/) -154. [Prisoner Escaped](http://four0four.org/downloads/maps/) +154. [Prisoner Escaped](https://www.moddb.com/mods/prisoner-escaped-1-2) 155. [Prisoner of Event](http://wp.vondur.net/?page_id=40) 156. [Prisoner of War](https://www.runthinkshootlive.com/posts/prisoner-of-war/) 157. [Project Quantum Leap](http://www.moddb.com/mods/project-quantum-leap) @@ -469,7 +469,7 @@ Mods: 80. [Extinct Lifeform Hunt](https://www.fileplanet.com/13501/10000/fileinfo/Extinct-Lifeform-Hunt) 81. [Facility](http://twhl.info/vault.php?map=5482) 82. [Facility Escape](http://twhl.info/vault.php?map=3673) -83. [Fallout](http://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html) (map by *simb*) +83. [Fallout](http://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html) (map by *simb*) (link dead!) 84. [Final Assault](http://twhl.info/vault.php?map=4500) 85. [Flat](http://hl.loess.ru/?mods=&search=Flat) 86. [Freeman's Allegiance](https://www.runthinkshootlive.com/posts/freemans-allegiance/) @@ -499,7 +499,7 @@ Mods: 110. [Hostage](https://www.runthinkshootlive.com/posts/hostage-2/) 111. [House](http://www.artpeter.net/Data/HalfLife/Hl.php) 112. [Impulse 101 Fun - The Train](http://web.archive.org/web/20070305075816/http://www.twhl.co.za/mapvault/2817.zip) (map from *TWHL* by *Archie* aka *The Hunter*) -113. [In America](http://www.lambda-force.org/load/half_life/karty/half_life_in_america/18-1-0-476) +113. [In America](http://www.lambda-force.org/load/half_life/karty/half_life_in_america/18-1-0-476) (link dead!) 114. [In the Kitchen](http://twhl.info/vault.php?map=4314) 115. [Infiltration](https://www.fileplanet.com/7467/0/fileinfo/Infiltration) 116. [Interactivity & Lots of Entities](http://twhl.info/vault.php?map=5744) (link dead!) @@ -668,7 +668,7 @@ Mods: 279. [X-treme Violence](http://www.moddb.com/mods/x-treme-violence) 280. [XargoL's Entry for Vassy's Compo](http://twhl.info/vault.php?map=769) 281. [Xen Again](https://www.fileplanet.com/9132/0/fileinfo/XEN-AGAIN) -282. [Xen World](http://www.lambda-force.org/load/half_life/karty/half_life_xen_world/18-1-0-481) +282. [Xen World](http://www.lambda-force.org/load/half_life/karty/half_life_xen_world/18-1-0-481) (link dead!) 283. [XUnil](https://www.fileplanet.com/7883/0/fileinfo/XUNIL) 284. [Zeeba-G's TWHL Compo 26 Entry](http://twhl.info/competitions.php?results=26) 285. [Zombies!](https://gamebanana.com/maps/160812) @@ -694,7 +694,7 @@ For Linux and OS X you must download crossbuild from [here](http://www.moddb.com 2. [Big Scientists](http://www.moddb.com/mods/big-scientists) 3. [Black Silla Assault DEMO v1.2](http://www.moddb.com/mods/black-silla-assault) 4. [Blbej Den](http://www.moddb.com/mods/blbej-den) -5. [Cold Experiment v1](http://jpmaps.wz.cz/epizody.htm) (you will need some files from SoHL to play it properly; download SoHL 1.2, extract files and copy following folders into **coldexv1** folder: cl_dlls, dlls, models, sprites) +5. [Cold Experiment v1](https://www.moddb.com/games/half-life/addons/cold-experiment) (you will need some files from SoHL to play it properly; download SoHL 1.2, extract files and copy following folders into **coldexv1** folder: cl_dlls, dlls, models, sprites) 6. [Dead Sector v1.0a](http://www.moddb.com/mods/dead-sector) 7. [Death Is Dead](http://www.moddb.com/games/half-life/addons/death-is-dead) (there is a fog-related inner bug at the first map) 8. [Escape from Black Mesa Alpha](http://www.moddb.com/mods/escape-from-black-mesa) @@ -712,7 +712,7 @@ For Linux and OS X you must download crossbuild from [here](http://www.moddb.com 20. [Space Prisoner v1.1](http://www.moddb.com/games/half-life/addons/space-prisoner-v11) (after installing of the mod open **liblist.gam** or **gameinfo.txt** file in the mod's folder and correct the line **gamedll "..\prison\dlls\spirit.dll"** for **gamedll "dlls\spirit.dll"**, otherwise you'll not be able to start a game; there is also a scripting error on a third map of the mod, so you'll be forced to use **noclip** to pass around bugged place) 21. [Terrorist Attack 2](http://terroristattack.wz.cz/mody.html) (link dead!) 22. [Timeline III: The Heart of Darkness](http://www.moddb.com/mods/timeline-series) -23. [Underground 2 Demo](http://www.isolated-design.de/half-life-mods/underground-2/) +23. [Underground 2 Demo](https://www.moddb.com/games/half-life/addons/underground-2-demo) ## Mods which based on modified Spirit of Half-Life 1.2 or older(Partially playable with SoHL 1.2 libraries or not playable at all) 1. [Black Death](http://www.moddb.com/mods/half-life-black-death) @@ -825,10 +825,10 @@ Mods: 3. [Bomb Squad](http://www.snarkpit.net/index.php?s=maps&map=3471) 4. [Corruption](http://www.snarkpit.net/index.php?s=maps&map=3154) 5. [Critical Mass](http://www.moddb.com/mods/half-life-critical-mass) [C3M1 Build 1 Demo](https://www.fileplanet.com/125746/120000/fileinfo/C3M1-Build-1) -6. [EPRST!](http://www.planetphillip.com/posts/ep-rst-opposing-force/) (link dead!) +6. [EPRST!](https://www.runthinkshootlive.com/posts/ep-rst/) 7. [Firing Range](https://www.runthinkshootlive.com/posts/firing-range/) 8. [Friendly Fire](https://www.runthinkshootlive.com/posts/friendly-fire/) -9. [Guitar Star Pre-Alpha](http://hl-lab.ru/eng/mods_goldsrc/Guitar_Star_(Prealpha)) (link dead!) +9. [Guitar Star Pre-Alpha](http://gamer-lab.com/rus/mods_goldsrc/Guitar_Star_(Prealpha)) 10. [J2000](https://www.runthinkshootlive.com/posts/j2000/) 11. [Killing House for OF](https://www.runthinkshootlive.com/posts/scientist-rescue-aka-cqb/) 12. [Klabautermann](https://www.runthinkshootlive.com/posts/klabautermann/) (though the map has some inner issues, it can be properly finished, just don't let the welder soldier die and don't press the fifth button until you mount a special gun in its' place) From 8211acf4e05f9a29da4f8f6f6395fd42e8b3e9f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BB=D0=B0=D0=B4=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A1=D1=83=D1=85=D0=BE=D0=B2?= <22411953+Vladislav4KZ@users.noreply.github.com> Date: Thu, 30 Jun 2022 17:04:41 +0600 Subject: [PATCH 336/548] Documentation: supported-mod-list: fix link for Lost in Black Mesa --- Documentation/supported-mod-list.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/supported-mod-list.md b/Documentation/supported-mod-list.md index ea6345f1..1719b520 100644 --- a/Documentation/supported-mod-list.md +++ b/Documentation/supported-mod-list.md @@ -379,7 +379,7 @@ Mods: 5. [Half-Life Baby v1.4](http://www.moddb.com/games/half-life/addons/half-life-baby) (it's an unfinished but playable mod; after installing of the mod open **liblist.gam** or **gameinfo.txt** file in the mod's folder and correct the line **gamedll "..\hlbaby\dlls\hl.dll"** for **gamedll "dlls\hl.dll"**, otherwise you'll not be able to start a game) 6. [Half-Secret](http://www.moddb.com/mods/half-secret) (this mod has custom **weapon_snark** code) 7. [Induction](http://www.moddb.com/mods/half-life-induction) (this mod has new item - **item_flashlight**) -8. [Lost in Black Mesa(first version without HLFX)](http://half-life.ru/forum/showthread.php?threadid=13959) +8. [Lost in Black Mesa(first version without HLFX)](https://drive.google.com/file/d/1bEnm_AxJs-ly8hTEZIQBw_v2BsXdSiGa/view?usp=sharing) 9. [Soldier](http://www.moddb.com/mods/half-life-soldier) 10. [Solo Operations](http://www.moddb.com/mods/solo-operations) 11. [The Blood v1.1](http://hl.loess.ru/?mods=&search=The+Blood) (there are some inner bugs in the mod, but they don't interfere with a game progress) From cbcb90638b5488ffee45a3a4593905421710fc7d Mon Sep 17 00:00:00 2001 From: Andrey Akhmichin <15944199+nekonomicon@users.noreply.github.com> Date: Tue, 21 Jun 2022 11:32:05 +0500 Subject: [PATCH 337/548] utils: mdldec: try to create user defined destination directory. --- utils/mdldec/mdldec.c | 6 +++--- utils/mdldec/utils.c | 16 ++++++++-------- utils/mdldec/utils.h | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/utils/mdldec/mdldec.c b/utils/mdldec/mdldec.c index d902d400..c137be93 100644 --- a/utils/mdldec/mdldec.c +++ b/utils/mdldec/mdldec.c @@ -164,9 +164,9 @@ static qboolean LoadMDL( const char *modelname ) if( destdir[0] != '\0' ) { - if( !IsFileExists( destdir ) ) + if( !MakeDirectory( destdir ) ) { - fprintf( stderr, "ERROR: Couldn't find directory %s\n", destdir ); + fprintf( stderr, "ERROR: Couldn't create directory %s\n", destdir ); return false; } @@ -175,7 +175,7 @@ static qboolean LoadMDL( const char *modelname ) else COM_ExtractFilePath( modelname, destdir ); - len -= 4; // path length without extension + len -= ( sizeof( ".mdl" ) - 1 ); // path length without extension if( !model_hdr->numtextures ) { diff --git a/utils/mdldec/utils.c b/utils/mdldec/utils.c index 739390d9..3e1014cc 100644 --- a/utils/mdldec/utils.c +++ b/utils/mdldec/utils.c @@ -18,22 +18,22 @@ GNU General Public License for more details. #include #include #include "xash3d_types.h" +#include "port.h" #include "crtlib.h" #include "utils.h" /* ============ -IsFileExists +MakeDirectory ============ */ -qboolean IsFileExists( const char *filename ) +qboolean MakeDirectory( const char *path ) { struct stat st; - int ret; - ret = stat( filename, &st ); - - if( ret == -1 ) + if( -1 == _mkdir( path ) + && ( -1 == stat( path, &st ) + || !S_ISDIR(st.st_mode ) ) ) return false; return true; @@ -44,7 +44,7 @@ qboolean IsFileExists( const char *filename ) GetFileSize ============ */ -off_t GetFileSize( FILE *fp ) +off_t GetSizeOfFile( FILE *fp ) { struct stat st; int fd; @@ -71,7 +71,7 @@ byte *LoadFile( const char *filename ) if( !fp ) return NULL; - size = GetFileSize( fp ); + size = GetSizeOfFile( fp ); buf = malloc( size ); diff --git a/utils/mdldec/utils.h b/utils/mdldec/utils.h index b2e9958f..08d07202 100644 --- a/utils/mdldec/utils.h +++ b/utils/mdldec/utils.h @@ -16,8 +16,8 @@ GNU General Public License for more details. #ifndef UTILS_H #define UTILS_H -qboolean IsFileExists( const char *filename ); -off_t GetFileSize( FILE *fp ); +qboolean MakeDirectory( const char *path ); +off_t GetSizeOfFile( FILE *fp ); byte *LoadFile( const char *filename ); #endif // UTILS_H From d562642e269cb2f16b717c81851cbf6c1bca72b4 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 30 Jun 2022 18:32:40 +0300 Subject: [PATCH 338/548] utils: mdldec: fix build on Windows, use GetFileAttributes instead of stat here --- utils/mdldec/utils.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/utils/mdldec/utils.c b/utils/mdldec/utils.c index 3e1014cc..d43fe266 100644 --- a/utils/mdldec/utils.c +++ b/utils/mdldec/utils.c @@ -17,6 +17,7 @@ GNU General Public License for more details. #include #include #include +#include #include "xash3d_types.h" #include "port.h" #include "crtlib.h" @@ -29,12 +30,26 @@ MakeDirectory */ qboolean MakeDirectory( const char *path ) { - struct stat st; + if( -1 == _mkdir( path )) + { + if( errno == EEXIST ) + { + // TODO: when filesystem library will be ready + // use FS_SysFolderExists here or replace this whole function + // with FS_CreatePath +#if XASH_WIN32 + DWORD dwFlags = GetFileAttributes( path ); - if( -1 == _mkdir( path ) - && ( -1 == stat( path, &st ) - || !S_ISDIR(st.st_mode ) ) ) + return ( dwFlags != -1 ) && ( dwFlags & FILE_ATTRIBUTE_DIRECTORY ); +#else + struct stat buf; + + if( !stat( path, &buf )) + return S_ISDIR( buf.st_mode ); +#endif + } return false; + } return true; } From 500dd0a82b53c46c4616f5fa78ef12d6d7ebc6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BB=D0=B0=D0=B4=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=A1=D1=83=D1=85=D0=BE=D0=B2?= <22411953+Vladislav4KZ@users.noreply.github.com> Date: Thu, 30 Jun 2022 22:13:24 +0600 Subject: [PATCH 339/548] Documentation: supported-mod-list: fix dead links Restored some mods at Google Drive --- Documentation/supported-mod-list.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Documentation/supported-mod-list.md b/Documentation/supported-mod-list.md index 1719b520..54f35a3f 100644 --- a/Documentation/supported-mod-list.md +++ b/Documentation/supported-mod-list.md @@ -176,7 +176,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 143. [Operation: Nova](http://www.moddb.com/mods/half-life-operation-nova) (there is an inner bug with an M60 machinegun on a map with Osprey: it can be activated and used if you are staying in front of it, not behind it) 144. [Optimum Fear](https://www.fileplanet.com/7472/0/fileinfo/Optimum-Fear) 145. [Outrun](http://www.moddb.com/mods/outrun) -146. [Outwards (Day One)](http://www.visgi.info/maps/) (link dead!) +146. [Outwards (Day One)](https://drive.google.com/file/d/1mjQ8wUazYbwTq9Ocg0Vs0raq4avbTRne/view?usp=sharing) 147. [Overhaul Pack](http://www.moddb.com/mods/half-life-overhaul-pack) (Just HD pack for Half-Life) 148. [P.I.Z.D.E.C.](https://www.runthinkshootlive.com/posts/pizdec/) 149. [pagoda](http://www.moddb.com/mods/pagoda1) @@ -282,7 +282,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 6. [Catacombs: Part 1](https://www.runthinkshootlive.com/posts/catacombs/) 7. [Cro-man's Office Mappack](http://twhl.info/vault.php?map=5547) 8. [Half-Life Episode Two Demo Alpha v1.0](http://hl.loess.ru/?mod=451) -9. [Hazardous-Course (first version of Hazardous-Course 2)](http://www.richmans-maps.ch.vu/) (link dead!) +9. [Hazardous-Course (first version of Hazardous-Course 2)](https://drive.google.com/file/d/16djJZSpNWKyScSxSyMqX-u_S-2i400rl/view?usp=sharing) 10. [High Tech v0.2](http://hl.loess.ru/?mod=499) 11. [HL Shadows, Part 1](https://www.fileplanet.com/7466/0/fileinfo/'HL-Shadows',-Part-1) 12. [HLNewEnd](http://twhl.info/vault.php?map=2275) @@ -305,11 +305,11 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 29. [Shortcut v1.0 Beta](https://www.runthinkshootlive.com/posts/shortcut/) 30. [Stoka](https://www.ceskemody.cz/mapy.php?lng=1&clanek=222&razeni_hra=1&pn=stoka) 31. [Striker's Compo 26](http://twhl.info/vault.php?map=5190) (buggy) -32. [Technology Test 2](http://www.richmans-maps.ch.vu/) (link dead!) +32. [Technology Test 2](https://drive.google.com/file/d/1LfldzsMIN5j07bgtNkfY5v0Xl0S9de_G/view?usp=sharing) 33. [The Gate Playable Demo](https://www.fileplanet.com/116347/110000/fileinfo/The-Gate-Playable-Demo) 34. [They are Back](https://www.runthinkshootlive.com/posts/they-are-back/) 35. [Tiefseelabor( Deep Sea Laboratory )](https://www.runthinkshootlive.com/posts/deep-sea-laboratory/) -36. [Time-Shift](http://www.richmans-maps.ch.vu/) (link dead!) +36. [Time-Shift](https://drive.google.com/file/d/1pK1s-2SIlSMQHVRPMLdC_94wTa__8_DX/view?usp=sharing) 37. [Train Single Beta](https://cs-mapping.com.ua/forum/showthread.php?t=36394) (Remove **gfx.wad** from **TrainSingle** folder) 38. [WAR: The Killer Beta 0.1](http://www.moddb.com/mods/war) 39. [White Force Beta( Residual Point prototype )](http://www.moddb.com/mods/hl-residual-point/downloads/white-force-beta-2002) @@ -440,7 +440,7 @@ Mods: 51. [Data-base](https://www.runthinkshootlive.com/posts/database/) 52. [de_dust2_azabetfeN](https://cs-mapping.com.ua/forum/showthread.php?t=36394) (remove **cl_dlls** & **dlls** folders from inside of mod's directory before you start the game) 53. [De-railed](http://twhl.info/competitions.php?results=7) -54. [Dead Shift Beta](http://www.moddb.com/mods/dead-shift) - [Demo 1](https://www.gamewatcher.com/mods/half-life-mod/dead-shift-1-0-beta) & [Demo 2](https://www.gamefront.com/files/13532512) (3rd link dead!) +54. [Dead Shift Beta](http://www.moddb.com/mods/dead-shift) - [Demo 1](https://www.gamewatcher.com/mods/half-life-mod/dead-shift-1-0-beta) & [Demo 2](https://drive.google.com/file/d/1uHvr8xONogTTNOy-8X6G4ehFV6Eawvbq/view?usp=sharing) 55. [Deep](http://twhl.info/vault.php?map=1669) 56. [Desert Attack](http://fyzzer.narod.ru/index.html) 57. [Desert Combat Demo](https://www.fileplanet.com/190487/190000/fileinfo/Half-Life---Desert-Combat-Mod) (despite a big file size there's only one small unfinished map) @@ -498,8 +498,8 @@ Mods: 109. [Hospital](https://gamebanana.com/maps/167446) (you need to edit **liblist.gam** file in the mod's folder - delete *gamedll & type* strings from it before you start to play) 110. [Hostage](https://www.runthinkshootlive.com/posts/hostage-2/) 111. [House](http://www.artpeter.net/Data/HalfLife/Hl.php) -112. [Impulse 101 Fun - The Train](http://web.archive.org/web/20070305075816/http://www.twhl.co.za/mapvault/2817.zip) (map from *TWHL* by *Archie* aka *The Hunter*) -113. [In America](http://www.lambda-force.org/load/half_life/karty/half_life_in_america/18-1-0-476) (link dead!) +112. [Impulse 101 Fun - The Train](https://drive.google.com/file/d/1jAuMCCmREH0mfAEAscqaG8FQGa2uNknb/view?usp=sharing) (map from *TWHL* by *Archie* aka *The Hunter*) +113. [In America](https://drive.google.com/file/d/1gOC8zfnUvBxRy8Rr8juNiUNbLYjoZpnn/view?usp=sharing) 114. [In the Kitchen](http://twhl.info/vault.php?map=4314) 115. [Infiltration](https://www.fileplanet.com/7467/0/fileinfo/Infiltration) 116. [Interactivity & Lots of Entities](http://twhl.info/vault.php?map=5744) (link dead!) @@ -710,7 +710,7 @@ For Linux and OS X you must download crossbuild from [here](http://www.moddb.com 18. [Santa's Revenge](http://twhl.info/vault.php?map=4332) (set **fps_max** to **60** to avoid an inner problem of the last map with final scripted sequence, otherwise the mod can not be finished properly) 19. [Sector 6](https://www.moddb.com/mods/sector-6/) 20. [Space Prisoner v1.1](http://www.moddb.com/games/half-life/addons/space-prisoner-v11) (after installing of the mod open **liblist.gam** or **gameinfo.txt** file in the mod's folder and correct the line **gamedll "..\prison\dlls\spirit.dll"** for **gamedll "dlls\spirit.dll"**, otherwise you'll not be able to start a game; there is also a scripting error on a third map of the mod, so you'll be forced to use **noclip** to pass around bugged place) -21. [Terrorist Attack 2](http://terroristattack.wz.cz/mody.html) (link dead!) +21. [Terrorist Attack 2](https://www.moddb.com/games/half-life/addons/terrorist-attack-2) 22. [Timeline III: The Heart of Darkness](http://www.moddb.com/mods/timeline-series) 23. [Underground 2 Demo](https://www.moddb.com/games/half-life/addons/underground-2-demo) From 6e031b518a41e413da21831b9824682556ab7173 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 1 Jul 2022 03:40:24 +0300 Subject: [PATCH 340/548] engine: client: increase limit of client sprites, lower part of it can be used only for HUD sprites higher part used for client sprites map overview sprites are loaded as normal models thus we have equal internal engine and hud sprite indices and it fixes compatibility issues for mods like Half-Rats Parasomnia --- common/com_model.h | 2 +- engine/client/cl_game.c | 31 +++++++++++++++++++++---------- engine/client/client.h | 2 +- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/common/com_model.h b/common/com_model.h index 9f8ceda8..36f2e936 100644 --- a/common/com_model.h +++ b/common/com_model.h @@ -530,7 +530,7 @@ typedef struct #define MAX_DEMOS 32 #define MAX_MOVIES 8 #define MAX_CDTRACKS 32 -#define MAX_CLIENT_SPRITES 256 // SpriteTextures +#define MAX_CLIENT_SPRITES 512 // SpriteTextures (0-256 hud, 256-512 client) #define MAX_EFRAGS 8192 // Arcane Dimensions required #define MAX_REQUESTS 64 diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index 4f5fc5b3..b093c82c 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -1235,6 +1235,10 @@ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFla model_t *mod; int i; + // use high indices for client sprites + // for GoldSrc bug-compatibility + const int start = type != SPR_HUDSPRITE ? MAX_CLIENT_SPRITES / 2 : 0; + if( !COM_CheckString( filename )) { Con_Reportf( S_ERROR "CL_LoadSpriteModel: bad name!\n" ); @@ -1244,8 +1248,7 @@ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFla Q_strncpy( name, filename, sizeof( name )); COM_FixSlashes( name ); - // slot 0 isn't used - for( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ ) + for( i = 0, mod = clgame.sprites + start; i < MAX_CLIENT_SPRITES / 2; i++, mod++ ) { if( !Q_stricmp( mod->name, name )) { @@ -1262,12 +1265,12 @@ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFla } // find a free model slot spot - for( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ ) + for( i = 0, mod = clgame.sprites + start; i < MAX_CLIENT_SPRITES / 2; i++, mod++ ) if( !mod->name[0] ) break; // this is a valid spot - if( i == MAX_CLIENT_SPRITES ) + if( i == MAX_CLIENT_SPRITES / 2 ) { - Con_Printf( S_ERROR "MAX_CLIENT_SPRITES limit exceeded (%d)\n", MAX_CLIENT_SPRITES ); + Con_Printf( S_ERROR "MAX_CLIENT_SPRITES limit exceeded (%d)\n", MAX_CLIENT_SPRITES / 2 ); return NULL; } @@ -1308,7 +1311,7 @@ HSPRITE pfnSPR_LoadExt( const char *szPicName, uint texFlags ) if(( spr = CL_LoadSpriteModel( szPicName, SPR_CLIENT, texFlags )) == NULL ) return 0; - return (spr - clgame.sprites); // return index + return (spr - clgame.sprites) + 1; // return index } /* @@ -1324,7 +1327,7 @@ HSPRITE EXPORT pfnSPR_Load( const char *szPicName ) if(( spr = CL_LoadSpriteModel( szPicName, SPR_HUDSPRITE, 0 )) == NULL ) return 0; - return (spr - clgame.sprites); // return index + return (spr - clgame.sprites) + 1; // return index } /* @@ -1336,10 +1339,11 @@ CL_GetSpritePointer const model_t *CL_GetSpritePointer( HSPRITE hSprite ) { model_t *mod; + int index = hSprite - 1; - if( hSprite <= 0 || hSprite >= MAX_CLIENT_SPRITES ) + if( index < 0 || index >= MAX_CLIENT_SPRITES ) return NULL; // bad image - mod = &clgame.sprites[hSprite]; + mod = &clgame.sprites[index]; if( mod->needload == NL_NEEDS_LOADED ) { @@ -2666,7 +2670,14 @@ pfnLoadMapSprite */ model_t *pfnLoadMapSprite( const char *filename ) { - return CL_LoadSpriteModel( filename, SPR_MAPSPRITE, 0 ); + model_t *mod; + + mod = Mod_FindName( filename, false ); + + if( CL_LoadHudSprite( filename, mod, SPR_MAPSPRITE, 0 )) + return mod; + + return NULL; } /* diff --git a/engine/client/client.h b/engine/client/client.h index 07bb0c57..c757245a 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -465,7 +465,7 @@ typedef struct string cdtracks[MAX_CDTRACKS]; // 32 cd-tracks read from cdaudio.txt - model_t sprites[MAX_CLIENT_SPRITES]; // client spritetextures + model_t sprites[MAX_CLIENT_SPRITES*2]; // hud&client spritetexturesz int viewport[4]; // viewport sizes client_draw_t ds; // draw2d stuff (hud, weaponmenu etc) From 786c408f6e15d097fcca0cdcd9430c5c101cbbd0 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 1 Jul 2022 14:12:32 +0300 Subject: [PATCH 341/548] engine: client: fix sprites array size, it wasn't meant to be increased after previous commit --- engine/client/client.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/client/client.h b/engine/client/client.h index c757245a..cc6683df 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -465,7 +465,7 @@ typedef struct string cdtracks[MAX_CDTRACKS]; // 32 cd-tracks read from cdaudio.txt - model_t sprites[MAX_CLIENT_SPRITES*2]; // hud&client spritetexturesz + model_t sprites[MAX_CLIENT_SPRITES]; // hud&client spritetexturesz int viewport[4]; // viewport sizes client_draw_t ds; // draw2d stuff (hud, weaponmenu etc) From fbdfed84bec30ce5cef28eff6fa16f9aac2774b9 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Sat, 9 Jul 2022 01:21:42 +0400 Subject: [PATCH 342/548] engine: platform: sdl: fixed bug with unhidable mouse cursor in center of screen --- engine/platform/sdl/in_sdl.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/engine/platform/sdl/in_sdl.c b/engine/platform/sdl/in_sdl.c index 129759bb..7f18c8ab 100644 --- a/engine/platform/sdl/in_sdl.c +++ b/engine/platform/sdl/in_sdl.c @@ -333,32 +333,33 @@ void Platform_SetCursorType( VGUI_DefaultCursor type ) break; } + host.mouse_visible = visible; + if( CVAR_TO_BOOL( touch_emulate )) return; #if SDL_VERSION_ATLEAST( 2, 0, 0 ) - if( visible && !host.mouse_visible ) + if( host.mouse_visible ) { SDL_SetCursor( cursors.cursors[type] ); SDL_ShowCursor( true ); Key_EnableTextInput( true, false ); } - else if( !visible && host.mouse_visible ) + else { SDL_ShowCursor( false ); Key_EnableTextInput( false, false ); } #else - if( visible && !host.mouse_visible ) + if( host.mouse_visible ) { SDL_ShowCursor( true ); } - else if( !visible && host.mouse_visible ) + else { SDL_ShowCursor( false ); } #endif - host.mouse_visible = visible; } /* From aca2849c5afb3088b329c07ce0af990b2e790031 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 29 May 2022 00:01:43 -0700 Subject: [PATCH 343/548] rt: make kusochki use double-ended buffers --- ref_vk/TODO.md | 35 +++++++++++++++++++++++++++++++++++ ref_vk/vk_buffer.c | 36 ++++++++++++++++++++++++++++++++++++ ref_vk/vk_buffer.h | 18 ++++++++++++++++++ ref_vk/vk_ray_internal.h | 4 ++-- ref_vk/vk_ray_model.c | 11 ++++++++--- ref_vk/vk_rtx.c | 11 +++++++---- 6 files changed, 106 insertions(+), 9 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index 47e5f14b..6f9b48a2 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -2,8 +2,43 @@ - [ ] allocate for N frames: - [x] geometries - [ ] rt models + - [ ] kusochki + - [ ] same ring buffer alloc as for geometries + - [ ] extract as a unit + - [ ] tlas geom --//-- - [ ] lights +- scratch buffer: + - should be fine (assuming intra-cmdbuf sync), contents lifetime is single frame only +- accels_buffer: + - lifetime: multiple frames; dynamic: some b/tlases get rebuilt every frame + - opt 1: double buffering + - opt 2: intra-cmdbuf sync (can't write unless previous frame is done) +- uniform_buffer: + - lifetime: single frame +- tlas_geom_buffer: + - similar to scratch_buffer + - BUT: filled on CPU, so it's not properly synchronsized + - fix: upload using staging? double/ring buffering? +- light_grid_buffer (+ small lights_buffer): + - lifetime: single frame + - BUT: populated by CPU, needs sync; can't just ring-buffer it + - fixes: double-buffering? + - staging + sync upload? staging needs to be huge or done in chunks. also, cpu needs to wait on staging upload + - 2x size + wait: won't fit into device-local-host-visible mem + - decrease size first? +- kusochki_buffer: + - lifetime: multiple frames + - ? who uploads? + - fix: ring-buffer? +- models_cache + - lifetimes: + - static; entire map + - static; single to multiple frames + - dynamic; multiple frames + - : intra-cmdbuf + + // O. 1 buffer i bardak v nyom // - [SSSSAAS.SBAS....] // - can become extremely fragmented diff --git a/ref_vk/vk_buffer.c b/ref_vk/vk_buffer.c index b85ba75f..cea12f67 100644 --- a/ref_vk/vk_buffer.c +++ b/ref_vk/vk_buffer.c @@ -89,3 +89,39 @@ VkDeviceAddress XVK_BufferGetDeviceAddress(VkBuffer buffer) { const VkBufferDeviceAddressInfo bdai = {.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, .buffer = buffer}; return vkGetBufferDeviceAddress(vk_core.device, &bdai); } + +void R_DEBuffer_Init(r_debuffer_t *debuf, uint32_t static_size, uint32_t dynamic_size) { + aloRingInit(&debuf->static_ring, static_size); + aloRingInit(&debuf->dynamic_ring, dynamic_size); + debuf->static_size = static_size; + debuf->frame_dynamic_offset[0] = debuf->frame_dynamic_offset[1] = ALO_ALLOC_FAILED; +} + +uint32_t R_DEBuffer_Alloc(r_debuffer_t* debuf, r_lifetime_t lifetime, uint32_t size, uint32_t align) { + alo_ring_t * const ring = (lifetime == LifetimeStatic) ? &debuf->static_ring : &debuf->dynamic_ring; + const uint32_t alloc_offset = aloRingAlloc(ring, size, align); + const uint32_t offset = alloc_offset + ((lifetime == LifetimeDynamic) ? debuf->static_size : 0); + + if (alloc_offset == ALO_ALLOC_FAILED) { + gEngine.Con_Printf(S_ERROR "Cannot allocate %d %s bytes\n", + size, + lifetime == LifetimeDynamic ? "dynamic" : "static"); + return ALO_ALLOC_FAILED; + } + + // Store first dynamic allocation this frame + if (lifetime == LifetimeDynamic && debuf->frame_dynamic_offset[1] == ALO_ALLOC_FAILED) { + debuf->frame_dynamic_offset[1] = alloc_offset; + } + + return offset; +} + +void R_DEBuffer_Flip(r_debuffer_t* debuf) { + if (debuf->frame_dynamic_offset[0] != ALO_ALLOC_FAILED) + aloRingFree(&debuf->dynamic_ring, debuf->frame_dynamic_offset[0]); + + debuf->frame_dynamic_offset[0] = debuf->frame_dynamic_offset[1]; + debuf->frame_dynamic_offset[1] = ALO_ALLOC_FAILED; +} + diff --git a/ref_vk/vk_buffer.h b/ref_vk/vk_buffer.h index 15de1198..bcf3addd 100644 --- a/ref_vk/vk_buffer.h +++ b/ref_vk/vk_buffer.h @@ -2,6 +2,7 @@ #include "vk_core.h" #include "vk_devmem.h" +#include "alolcator.h" typedef struct vk_buffer_s { vk_devmem_t devmem; @@ -43,3 +44,20 @@ void VK_RingBuffer_Fix(vk_ring_buffer_t* buf); // Clears non-permantent part of the buffer void VK_RingBuffer_ClearFrame(vk_ring_buffer_t* buf); + + +typedef struct { + alo_ring_t static_ring; + alo_ring_t dynamic_ring; + + uint32_t static_size; + uint32_t frame_dynamic_offset[2]; +} r_debuffer_t; + +typedef enum { + LifetimeStatic, LifetimeDynamic, +} r_lifetime_t; + +void R_DEBuffer_Init(r_debuffer_t *debuf, uint32_t static_size, uint32_t dynamic_size); +uint32_t R_DEBuffer_Alloc(r_debuffer_t* debuf, r_lifetime_t lifetime, uint32_t size, uint32_t align); +void R_DEBuffer_Flip(r_debuffer_t* debuf); diff --git a/ref_vk/vk_ray_internal.h b/ref_vk/vk_ray_internal.h index c01a4648..e693bc03 100644 --- a/ref_vk/vk_ray_internal.h +++ b/ref_vk/vk_ray_internal.h @@ -5,7 +5,7 @@ #include "vk_const.h" #define MAX_ACCELS 1024 -#define MAX_KUSOCHKI 8192 +#define MAX_KUSOCHKI 16384 #define MAX_EMISSIVE_KUSOCHKI 256 #define MODEL_CACHE_SIZE 1024 @@ -59,7 +59,7 @@ typedef struct { // TODO unify with render buffer // Needs: STORAGE_BUFFER vk_buffer_t kusochki_buffer; - vk_ring_buffer_t kusochki_alloc; + r_debuffer_t kusochki_alloc; // TODO this should really be a single uniform buffer for matrices and light data diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index 53b27454..0928a0ff 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -157,7 +157,7 @@ vk_ray_model_t* VK_RayModelCreate( VkCommandBuffer cmdbuf, vk_ray_model_init_t a VkAccelerationStructureBuildRangeInfoKHR *geom_build_ranges; const VkDeviceAddress buffer_addr = getBufferDeviceAddress(args.buffer); vk_kusok_data_t *kusochki; - const uint32_t kusochki_count_offset = VK_RingBuffer_Alloc(&g_ray_model_state.kusochki_alloc, args.model->num_geometries, 1); + const uint32_t kusochki_count_offset = R_DEBuffer_Alloc(&g_ray_model_state.kusochki_alloc, args.model->dynamic ? LifetimeDynamic : LifetimeStatic, args.model->num_geometries, 1); vk_ray_model_t *ray_model; int max_prims = 0; @@ -166,7 +166,7 @@ vk_ray_model_t* VK_RayModelCreate( VkCommandBuffer cmdbuf, vk_ray_model_init_t a if (g_ray_model_state.freeze_models) return args.model->ray_model; - if (kusochki_count_offset == AllocFailed) { + if (kusochki_count_offset == ALO_ALLOC_FAILED) { gEngine.Con_Printf(S_ERROR "Maximum number of kusochki exceeded on model %s\n", args.model->debug_name); return NULL; } @@ -425,6 +425,10 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render } } +void RT_RayModel_Clear(void) { + R_DEBuffer_Init(&g_ray_model_state.kusochki_alloc, MAX_KUSOCHKI / 2, MAX_KUSOCHKI / 2); +} + void XVK_RayModel_ClearForNextFrame( void ) { // FIXME we depend on the fact that only a single frame can be in flight @@ -447,5 +451,6 @@ void XVK_RayModel_ClearForNextFrame( void ) // HACK: blas caching requires persistent memory // proper fix would need some other memory allocation strategy // VK_RingBuffer_ClearFrame(&g_rtx.accels_buffer_alloc); - VK_RingBuffer_ClearFrame(&g_ray_model_state.kusochki_alloc); + //VK_RingBuffer_ClearFrame(&g_ray_model_state.kusochki_alloc); + R_DEBuffer_Flip(&g_ray_model_state.kusochki_alloc); } diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 5df9de2c..e99bbf79 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -164,6 +164,7 @@ static struct { uint32_t scratch_offset; // for building dynamic blases } frame; + // TODO with proper intra-cmdbuf sync we don't really need 2x images unsigned frame_number; xvk_ray_frame_images_t frames[MAX_FRAMES_IN_FLIGHT]; @@ -328,7 +329,7 @@ void VK_RayNewMap( void ) { ASSERT(vk_core.rtx); VK_RingBuffer_Clear(&g_rtx.accels_buffer_alloc); - VK_RingBuffer_Clear(&g_ray_model_state.kusochki_alloc); +// VK_RingBuffer_Clear(&g_ray_model_state.kusochki_alloc); // Clear model cache for (int i = 0; i < ARRAYSIZE(g_ray_model_state.models_cache); ++i) { @@ -346,11 +347,13 @@ void VK_RayNewMap( void ) { createTlas(VK_NULL_HANDLE); } + + RT_RayModel_Clear(); } void VK_RayMapLoadEnd( void ) { VK_RingBuffer_Fix(&g_rtx.accels_buffer_alloc); - VK_RingBuffer_Fix(&g_ray_model_state.kusochki_alloc); + //VK_RingBuffer_Fix(&g_ray_model_state.kusochki_alloc); } void VK_RayFrameBegin( void ) @@ -1293,7 +1296,6 @@ static void reloadLighting( void ) { g_rtx.reload_lighting = true; } - static void freezeModels( void ) { g_ray_model_state.freeze_models = !g_ray_model_state.freeze_models; } @@ -1364,7 +1366,8 @@ qboolean VK_RayInit( void ) // FIXME complain, handle return false; } - g_ray_model_state.kusochki_alloc.size = MAX_KUSOCHKI; + RT_RayModel_Clear(); + //g_ray_model_state.kusochki_alloc.size = MAX_KUSOCHKI; if (!VK_BufferCreate("ray lights_buffer", &g_ray_model_state.lights_buffer, sizeof(struct Lights), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT /* | VK_BUFFER_USAGE_TRANSFER_DST_BIT */, From 31c35a4c823ec9e84bc906a8e4fd38c49444c5ab Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 29 May 2022 14:53:05 -0700 Subject: [PATCH 344/548] rt: ringify tlas geom buffer --- ref_vk/vk_rtx.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index e99bbf79..bc03a592 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -143,6 +143,8 @@ static struct { // TODO: unify them // Needs: SHADER_DEVICE_ADDRESS, STORAGE_BUFFER, AS_BUILD_INPUT_READ_ONLY vk_buffer_t tlas_geom_buffer; + VkDeviceAddress tlas_geom_buffer_addr; + r_debuffer_t tlas_geom_buffer_alloc; // Planned to contain seveal types of data: // - grid structure itself @@ -290,7 +292,7 @@ qboolean createOrUpdateAccelerationStructure(VkCommandBuffer cmdbuf, const as_bu return true; } -static void createTlas( VkCommandBuffer cmdbuf ) { +static void createTlas( VkCommandBuffer cmdbuf, VkDeviceAddress instances_addr ) { const VkAccelerationStructureGeometryKHR tl_geom[] = { { .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR, @@ -299,7 +301,7 @@ static void createTlas( VkCommandBuffer cmdbuf ) { .geometry.instances = (VkAccelerationStructureGeometryInstancesDataKHR){ .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR, - .data.deviceAddress = getBufferDeviceAddress(g_rtx.tlas_geom_buffer.buffer), + .data.deviceAddress = instances_addr, .arrayOfPointers = VK_FALSE, }, }, @@ -345,7 +347,7 @@ void VK_RayNewMap( void ) { g_rtx.tlas = VK_NULL_HANDLE; } - createTlas(VK_NULL_HANDLE); + createTlas(VK_NULL_HANDLE, g_rtx.tlas_geom_buffer_addr); } RT_RayModel_Clear(); @@ -579,12 +581,16 @@ static void createPipeline( void ) static void prepareTlas( VkCommandBuffer cmdbuf ) { ASSERT(g_ray_model_state.frame.num_models > 0); - DEBUG_BEGIN(cmdbuf, "prepare tlas"); + R_DEBuffer_Flip( &g_rtx.tlas_geom_buffer_alloc ); + + const uint32_t instance_offset = R_DEBuffer_Alloc(&g_rtx.tlas_geom_buffer_alloc, LifetimeDynamic, g_ray_model_state.frame.num_models, 1); + ASSERT(instance_offset != ALO_ALLOC_FAILED); + // Upload all blas instances references to GPU mem { - VkAccelerationStructureInstanceKHR* inst = g_rtx.tlas_geom_buffer.mapped; + VkAccelerationStructureInstanceKHR* inst = ((VkAccelerationStructureInstanceKHR*)g_rtx.tlas_geom_buffer.mapped) + instance_offset; for (int i = 0; i < g_ray_model_state.frame.num_models; ++i) { const vk_ray_draw_model_t* const model = g_ray_model_state.frame.models + i; ASSERT(model->model); @@ -628,8 +634,8 @@ static void prepareTlas( VkCommandBuffer cmdbuf ) { .srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR, // | VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR, .buffer = g_rtx.accels_buffer.buffer, - .offset = 0, - .size = VK_WHOLE_SIZE, + .offset = instance_offset * sizeof(VkAccelerationStructureInstanceKHR), + .size = g_ray_model_state.frame.num_models * sizeof(VkAccelerationStructureInstanceKHR), } }; vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, @@ -638,7 +644,7 @@ static void prepareTlas( VkCommandBuffer cmdbuf ) { } // 2. Build TLAS - createTlas(cmdbuf); + createTlas(cmdbuf, g_rtx.tlas_geom_buffer_addr + instance_offset * sizeof(VkAccelerationStructureInstanceKHR)); DEBUG_END(cmdbuf); } @@ -1352,13 +1358,15 @@ qboolean VK_RayInit( void ) } g_rtx.scratch_buffer_addr = getBufferDeviceAddress(g_rtx.scratch_buffer.buffer); - if (!VK_BufferCreate("ray tlas_geom_buffer", &g_rtx.tlas_geom_buffer, sizeof(VkAccelerationStructureInstanceKHR) * MAX_ACCELS, + if (!VK_BufferCreate("ray tlas_geom_buffer", &g_rtx.tlas_geom_buffer, sizeof(VkAccelerationStructureInstanceKHR) * MAX_ACCELS * 2, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { // FIXME complain, handle return false; } + g_rtx.tlas_geom_buffer_addr = getBufferDeviceAddress(g_rtx.tlas_geom_buffer.buffer); + R_DEBuffer_Init(&g_rtx.tlas_geom_buffer_alloc, 0, MAX_ACCELS * 2); if (!VK_BufferCreate("ray kusochki_buffer", &g_ray_model_state.kusochki_buffer, sizeof(vk_kusok_data_t) * MAX_KUSOCHKI, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT /* | VK_BUFFER_USAGE_TRANSFER_DST_BIT */, From 2c667bdc60a25230ff388ef23c10782f6bd17412 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 29 May 2022 15:00:57 -0700 Subject: [PATCH 345/548] fix func declaration --- ref_vk/vk_ray_internal.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ref_vk/vk_ray_internal.h b/ref_vk/vk_ray_internal.h index e693bc03..c2a1ab51 100644 --- a/ref_vk/vk_ray_internal.h +++ b/ref_vk/vk_ray_internal.h @@ -92,3 +92,5 @@ void XVK_RayModel_ClearForNextFrame( void ); void XVK_RayModel_Validate(void); VkDeviceAddress getBufferDeviceAddress(VkBuffer buffer); + +void RT_RayModel_Clear(void); From dc129475bdebf4ae66f5def73855901640a8de07 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Mon, 30 May 2022 10:51:27 -0700 Subject: [PATCH 346/548] vk: fix synchronization on resize/swapchain recreation --- ref_vk/vk_swapchain.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ref_vk/vk_swapchain.c b/ref_vk/vk_swapchain.c index 04ebd4b3..16444609 100644 --- a/ref_vk/vk_swapchain.c +++ b/ref_vk/vk_swapchain.c @@ -46,6 +46,8 @@ static void createDepthImage(int w, int h, VkFormat depth_format) { } static void destroySwapchainAndFramebuffers( VkSwapchainKHR swapchain ) { + XVK_CHECK(vkDeviceWaitIdle( vk_core.device )); + for (uint32_t i = 0; i < g_swapchain.num_images; ++i) { vkDestroyImageView(vk_core.device, g_swapchain.image_views[i], NULL); vkDestroyFramebuffer(vk_core.device, g_swapchain.framebuffers[i], NULL); From b0f1b60f620dc3e2d57d68619d1429692f5edb80 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Mon, 30 May 2022 12:47:20 -0700 Subject: [PATCH 347/548] vk: supposedly fix menu glitches had to ringify 2d buffer too --- ref_vk/vk_2d.c | 117 +++++++++++++++++++++++---------------------- ref_vk/vk_buffer.c | 6 +-- 2 files changed, 64 insertions(+), 59 deletions(-) diff --git a/ref_vk/vk_2d.c b/ref_vk/vk_2d.c index dcd1a6b6..2f790f87 100644 --- a/ref_vk/vk_2d.c +++ b/ref_vk/vk_2d.c @@ -16,6 +16,7 @@ void R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, c { gEngine.Con_Printf(S_WARN "VK FIXME: %s\n", __FUNCTION__); } + void R_DrawTileClear( int texnum, int x, int y, int w, int h ) { gEngine.Con_Printf(S_WARN "VK FIXME: %s\n", __FUNCTION__); @@ -29,55 +30,66 @@ typedef struct vertex_2d_s { // TODO should these be dynamic? #define MAX_PICS 16384 +#define MAX_VERTICES (MAX_PICS * 6) #define MAX_BATCHES 256 +typedef struct { + uint32_t vertex_offset, vertex_count; + int texture; + int blending_mode; +} batch_t; + static struct { VkPipelineLayout pipeline_layout; VkPipeline pipelines[kRenderTransAdd + 1]; - uint32_t max_pics, num_pics; vk_buffer_t pics_buffer; + r_debuffer_t pics_buffer_alloc; + qboolean exhausted_this_frame; - struct { - uint32_t vertex_offset, vertex_count; - int texture; - int blending_mode; - } batch[MAX_BATCHES]; - uint32_t current_batch; + batch_t batch[MAX_BATCHES]; + int batch_count; // TODO texture bindings? } g2d; -static vertex_2d_t* allocQuadVerts(int blending_mode, int texnum) -{ - vertex_2d_t* const ptr = ((vertex_2d_t*)(g2d.pics_buffer.mapped)) + g2d.num_pics; - if (g2d.batch[g2d.current_batch].texture != texnum || g2d.batch[g2d.current_batch].blending_mode != blending_mode) - { - if (g2d.batch[g2d.current_batch].vertex_count != 0) - { - if (g2d.current_batch == MAX_BATCHES - 1) - { - gEngine.Con_Printf(S_ERROR "VK FIXME RAN OUT OF BATCHES"); - return NULL; - } +static vertex_2d_t* allocQuadVerts(int blending_mode, int texnum) { + const uint32_t pics_offset = R_DEBuffer_Alloc(&g2d.pics_buffer_alloc, LifetimeDynamic, 6, 1); + vertex_2d_t* const ptr = ((vertex_2d_t*)(g2d.pics_buffer.mapped)) + pics_offset; + batch_t *batch = g2d.batch + (g2d.batch_count-1); - ++g2d.current_batch; + if (pics_offset == ALO_ALLOC_FAILED) { + if (!g2d.exhausted_this_frame) { + gEngine.Con_Printf(S_ERROR "2d: ran out of vertex memory\n"); + g2d.exhausted_this_frame = true; } - - g2d.batch[g2d.current_batch].texture = texnum; - g2d.batch[g2d.current_batch].blending_mode = vk_renderstate.blending_mode; - g2d.batch[g2d.current_batch].vertex_offset = g2d.num_pics; - g2d.batch[g2d.current_batch].vertex_count = 0; - } - - if (g2d.num_pics + 6 > g2d.max_pics) - { - gEngine.Con_Printf(S_ERROR "VK FIXME RAN OUT OF BUFFER"); return NULL; } - g2d.num_pics += 6; - g2d.batch[g2d.current_batch].vertex_count += 6; + if (batch->texture != texnum + || batch->blending_mode != blending_mode + || batch->vertex_offset > pics_offset) { + if (batch->vertex_count != 0) { + if (g2d.batch_count == MAX_BATCHES) { + if (!g2d.exhausted_this_frame) { + gEngine.Con_Printf(S_ERROR "2d: ran out of batch memory\n"); + g2d.exhausted_this_frame = true; + } + return NULL; + } + + ++g2d.batch_count; + batch++; + } + + batch->vertex_offset = pics_offset; + batch->vertex_count = 0; + batch->texture = texnum; + batch->blending_mode = blending_mode; + } + + batch->vertex_count += 6; + ASSERT(batch->vertex_count + batch->vertex_offset <= MAX_VERTICES); return ptr; } @@ -86,9 +98,8 @@ void R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, f vertex_2d_t *p = allocQuadVerts(vk_renderstate.blending_mode, texnum); if (!p) { - gEngine.Con_Printf(S_ERROR "VK FIXME %s(%f, %f, %f, %f, %f, %f, %f, %f, %d(%s))\n", __FUNCTION__, - x, y, w, h, s1, t1, s2, t2, texnum, findTexture(texnum)->name); - + /* gEngine.Con_Printf(S_ERROR "VK FIXME %s(%f, %f, %f, %f, %f, %f, %f, %f, %d(%s))\n", __FUNCTION__, */ + /* x, y, w, h, s1, t1, s2, t2, texnum, findTexture(texnum)->name); */ return; } @@ -132,32 +143,27 @@ void CL_FillRGBABlend( float x, float y, float w, float h, int r, int g, int b, drawFill(x, y, w, h, r, g, b, a, kRenderTransColor); } -static void XVK_2dClear( void ) +static void clear( void ) { - g2d.num_pics = 0; - g2d.current_batch = 0; + R_DEBuffer_Flip(&g2d.pics_buffer_alloc); + + g2d.batch_count = 1; + g2d.batch[0].texture = -1; + g2d.batch[0].vertex_offset = 0; g2d.batch[0].vertex_count = 0; + g2d.exhausted_this_frame = false; } void vk2dEnd( VkCommandBuffer cmdbuf ) { - const VkDeviceSize offset = 0; + DEBUG_BEGIN(cmdbuf, "2d overlay"); - if (!g2d.num_pics) - return; - - vkCmdBindVertexBuffers(cmdbuf, 0, 1, &g2d.pics_buffer.buffer, &offset); - - if (vk_core.debug) { - VkDebugUtilsLabelEXT label = { - .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, - .pLabelName = "2d overlay", - }; - vkCmdBeginDebugUtilsLabelEXT(cmdbuf, &label); + const VkDeviceSize offset = 0; + vkCmdBindVertexBuffers(cmdbuf, 0, 1, &g2d.pics_buffer.buffer, &offset); } - for (int i = 0; i <= g2d.current_batch; ++i) + for (int i = 0; i < g2d.batch_count && g2d.batch[i].vertex_count > 0; ++i) { vk_texture_t *texture = findTexture(g2d.batch[i].texture); const VkPipeline pipeline = g2d.pipelines[g2d.batch[i].blending_mode]; @@ -169,10 +175,9 @@ void vk2dEnd( VkCommandBuffer cmdbuf ) } // FIXME else what? } - if (vk_core.debug) - vkCmdEndDebugUtilsLabelEXT(cmdbuf); + DEBUG_END(cmdbuf); - XVK_2dClear(); + clear(); } static qboolean createPipelines( void ) @@ -276,12 +281,12 @@ qboolean initVk2d( void ) if (!createPipelines()) return false; - if (!VK_BufferCreate("2d pics_buffer", &g2d.pics_buffer, sizeof(vertex_2d_t) * (MAX_PICS * 6), + if (!VK_BufferCreate("2d pics_buffer", &g2d.pics_buffer, sizeof(vertex_2d_t) * MAX_VERTICES, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT )) // FIXME cleanup return false; - g2d.max_pics = MAX_PICS * 6; + R_DEBuffer_Init(&g2d.pics_buffer_alloc, 0, MAX_VERTICES); return true; } diff --git a/ref_vk/vk_buffer.c b/ref_vk/vk_buffer.c index cea12f67..988d2920 100644 --- a/ref_vk/vk_buffer.c +++ b/ref_vk/vk_buffer.c @@ -103,9 +103,9 @@ uint32_t R_DEBuffer_Alloc(r_debuffer_t* debuf, r_lifetime_t lifetime, uint32_t s const uint32_t offset = alloc_offset + ((lifetime == LifetimeDynamic) ? debuf->static_size : 0); if (alloc_offset == ALO_ALLOC_FAILED) { - gEngine.Con_Printf(S_ERROR "Cannot allocate %d %s bytes\n", - size, - lifetime == LifetimeDynamic ? "dynamic" : "static"); + /* gEngine.Con_Printf(S_ERROR "Cannot allocate %d %s bytes\n", */ + /* size, */ + /* lifetime == LifetimeDynamic ? "dynamic" : "static"); */ return ALO_ALLOC_FAILED; } From 09112bedef22a0f259c04b44efbf5bdeb64b62c1 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Mon, 30 May 2022 13:04:19 -0700 Subject: [PATCH 348/548] vk: slightly refactor s/2d/overlay/ --- ref_vk/vk_core.c | 10 ++- ref_vk/vk_framectl.c | 6 +- ref_vk/{vk_2d.c => vk_overlay.c} | 103 +++++++++++++++---------------- ref_vk/{vk_2d.h => vk_overlay.h} | 6 +- ref_vk/vk_renderstate.c | 1 - ref_vk/vk_rmain.c | 2 +- ref_vk/vk_rtx.c | 6 -- 7 files changed, 64 insertions(+), 70 deletions(-) rename ref_vk/{vk_2d.c => vk_overlay.c} (95%) rename ref_vk/{vk_2d.h => vk_overlay.h} (80%) diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index 67070665..accb363d 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -2,7 +2,7 @@ #include "vk_common.h" #include "vk_textures.h" -#include "vk_2d.h" +#include "vk_overlay.h" #include "vk_renderstate.h" #include "vk_staging.h" #include "vk_framectl.h" @@ -111,6 +111,10 @@ VkBool32 VKAPI_PTR debugCallback( if (Q_strcmp(pCallbackData->pMessageIdName, "VUID-vkMapMemory-memory-00683") == 0) return VK_FALSE; + /* if (messageSeverity != VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { */ + /* gEngine.Con_Printf(S_WARN "Validation: %s\n", pCallbackData->pMessage); */ + /* } */ + // TODO better messages, not only errors, what are other arguments for, ... if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { gEngine.Con_Printf(S_ERROR "Validation: %s\n", pCallbackData->pMessage); @@ -747,7 +751,7 @@ qboolean R_VkInit( void ) // All below need render_pass - if (!initVk2d()) + if (!R_VkOverlay_Init()) return false; if (!VK_BrushInit()) @@ -776,7 +780,7 @@ void R_VkShutdown( void ) { VK_BrushShutdown(); VK_StudioShutdown(); - deinitVk2d(); + R_VkOverlay_Shutdown(); VK_RenderShutdown(); diff --git a/ref_vk/vk_framectl.c b/ref_vk/vk_framectl.c index 154d7fd6..cba93ce3 100644 --- a/ref_vk/vk_framectl.c +++ b/ref_vk/vk_framectl.c @@ -1,6 +1,6 @@ #include "vk_framectl.h" -#include "vk_2d.h" +#include "vk_overlay.h" #include "vk_scene.h" #include "vk_render.h" #include "vk_rtx.h" @@ -148,7 +148,7 @@ static void waitForFrameFence( void ) { loop = false; break; case VK_TIMEOUT: - gEngine.Con_Printf(S_ERROR "Waitinf for frame fence to be signaled timed out after 10 seconds. Wat\n"); + gEngine.Con_Printf(S_ERROR "Waiting for frame fence to be signaled timed out after 10 seconds. Wat\n"); break; default: XVK_CHECK(fence_result); @@ -285,7 +285,7 @@ static void enqueueRendering( VkCommandBuffer cmdbuf ) { if (!g_frame.rtx_enabled) VK_RenderEnd( cmdbuf ); - vk2dEnd( cmdbuf ); + R_VkOverlay_DrawAndFlip( cmdbuf ); vkCmdEndRenderPass(cmdbuf); diff --git a/ref_vk/vk_2d.c b/ref_vk/vk_overlay.c similarity index 95% rename from ref_vk/vk_2d.c rename to ref_vk/vk_overlay.c index 2f790f87..89b34a03 100644 --- a/ref_vk/vk_2d.c +++ b/ref_vk/vk_overlay.c @@ -1,4 +1,4 @@ -#include "vk_2d.h" +#include "vk_overlay.h" #include "vk_buffer.h" #include "vk_core.h" @@ -12,15 +12,6 @@ #include "com_strings.h" #include "eiface.h" -void R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty ) -{ - gEngine.Con_Printf(S_WARN "VK FIXME: %s\n", __FUNCTION__); -} - -void R_DrawTileClear( int texnum, int x, int y, int w, int h ) -{ - gEngine.Con_Printf(S_WARN "VK FIXME: %s\n", __FUNCTION__); -} typedef struct vertex_2d_s { float x, y; @@ -95,7 +86,7 @@ static vertex_2d_t* allocQuadVerts(int blending_mode, int texnum) { void R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum ) { - vertex_2d_t *p = allocQuadVerts(vk_renderstate.blending_mode, texnum); + vertex_2d_t *const p = allocQuadVerts(vk_renderstate.blending_mode, texnum); if (!p) { /* gEngine.Con_Printf(S_ERROR "VK FIXME %s(%f, %f, %f, %f, %f, %f, %f, %f, %d(%s))\n", __FUNCTION__, */ @@ -133,18 +124,7 @@ static void drawFill( float x, float y, float w, float h, int r, int g, int b, i vk_renderstate.blending_mode = prev_blending; } -void CL_FillRGBA( float x, float y, float w, float h, int r, int g, int b, int a ) -{ - drawFill(x, y, w, h, r, g, b, a, kRenderTransAdd); -} - -void CL_FillRGBABlend( float x, float y, float w, float h, int r, int g, int b, int a ) -{ - drawFill(x, y, w, h, r, g, b, a, kRenderTransColor); -} - -static void clear( void ) -{ +static void clearAccumulated( void ) { R_DEBuffer_Flip(&g2d.pics_buffer_alloc); g2d.batch_count = 1; @@ -154,32 +134,6 @@ static void clear( void ) g2d.exhausted_this_frame = false; } -void vk2dEnd( VkCommandBuffer cmdbuf ) -{ - DEBUG_BEGIN(cmdbuf, "2d overlay"); - - { - const VkDeviceSize offset = 0; - vkCmdBindVertexBuffers(cmdbuf, 0, 1, &g2d.pics_buffer.buffer, &offset); - } - - for (int i = 0; i < g2d.batch_count && g2d.batch[i].vertex_count > 0; ++i) - { - vk_texture_t *texture = findTexture(g2d.batch[i].texture); - const VkPipeline pipeline = g2d.pipelines[g2d.batch[i].blending_mode]; - if (texture->vk.descriptor) - { - vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g2d.pipeline_layout, 0, 1, &texture->vk.descriptor, 0, NULL); - vkCmdDraw(cmdbuf, g2d.batch[i].vertex_count, 1, g2d.batch[i].vertex_offset, 0); - } // FIXME else what? - } - - DEBUG_END(cmdbuf); - - clear(); -} - static qboolean createPipelines( void ) { { @@ -276,8 +230,7 @@ static qboolean createPipelines( void ) return true; } -qboolean initVk2d( void ) -{ +qboolean R_VkOverlay_Init( void ) { if (!createPipelines()) return false; @@ -291,11 +244,55 @@ qboolean initVk2d( void ) return true; } -void deinitVk2d( void ) -{ +void R_VkOverlay_Shutdown( void ) { VK_BufferDestroy(&g2d.pics_buffer); for (int i = 0; i < ARRAYSIZE(g2d.pipelines); ++i) vkDestroyPipeline(vk_core.device, g2d.pipelines[i], NULL); vkDestroyPipelineLayout(vk_core.device, g2d.pipeline_layout, NULL); } + +void R_VkOverlay_DrawAndFlip( VkCommandBuffer cmdbuf ) { + DEBUG_BEGIN(cmdbuf, "2d overlay"); + + { + const VkDeviceSize offset = 0; + vkCmdBindVertexBuffers(cmdbuf, 0, 1, &g2d.pics_buffer.buffer, &offset); + } + + for (int i = 0; i < g2d.batch_count && g2d.batch[i].vertex_count > 0; ++i) + { + vk_texture_t *texture = findTexture(g2d.batch[i].texture); + const VkPipeline pipeline = g2d.pipelines[g2d.batch[i].blending_mode]; + if (texture->vk.descriptor) + { + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g2d.pipeline_layout, 0, 1, &texture->vk.descriptor, 0, NULL); + vkCmdDraw(cmdbuf, g2d.batch[i].vertex_count, 1, g2d.batch[i].vertex_offset, 0); + } // FIXME else what? + } + + DEBUG_END(cmdbuf); + + clearAccumulated(); +} + +void R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty ) +{ + PRINT_NOT_IMPLEMENTED(); +} + +void R_DrawTileClear( int texnum, int x, int y, int w, int h ) +{ + PRINT_NOT_IMPLEMENTED_ARGS("%s", findTexture(texnum)->name ); +} + +void CL_FillRGBA( float x, float y, float w, float h, int r, int g, int b, int a ) +{ + drawFill(x, y, w, h, r, g, b, a, kRenderTransAdd); +} + +void CL_FillRGBABlend( float x, float y, float w, float h, int r, int g, int b, int a ) +{ + drawFill(x, y, w, h, r, g, b, a, kRenderTransColor); +} diff --git a/ref_vk/vk_2d.h b/ref_vk/vk_overlay.h similarity index 80% rename from ref_vk/vk_2d.h rename to ref_vk/vk_overlay.h index dcf35d5c..775aa3c6 100644 --- a/ref_vk/vk_2d.h +++ b/ref_vk/vk_overlay.h @@ -9,6 +9,6 @@ void R_DrawTileClear( int texnum, int x, int y, int w, int h ); void CL_FillRGBA( float x, float y, float w, float h, int r, int g, int b, int a ); void CL_FillRGBABlend( float x, float y, float w, float h, int r, int g, int b, int a ); -qboolean initVk2d( void ); -void deinitVk2d( void ); -void vk2dEnd( VkCommandBuffer cmdbuf ); +qboolean R_VkOverlay_Init( void ); +void R_VkOverlay_Shutdown( void ); +void R_VkOverlay_DrawAndFlip( VkCommandBuffer cmdbuf ); diff --git a/ref_vk/vk_renderstate.c b/ref_vk/vk_renderstate.c index e9d2c666..414c9496 100644 --- a/ref_vk/vk_renderstate.c +++ b/ref_vk/vk_renderstate.c @@ -1,6 +1,5 @@ #include "vk_renderstate.h" -#include "vk_2d.h" #include "vk_core.h" #include "cvardef.h" diff --git a/ref_vk/vk_rmain.c b/ref_vk/vk_rmain.c index 3ef89370..611a14b6 100644 --- a/ref_vk/vk_rmain.c +++ b/ref_vk/vk_rmain.c @@ -3,7 +3,7 @@ #include "vk_common.h" #include "vk_textures.h" #include "vk_renderstate.h" -#include "vk_2d.h" +#include "vk_overlay.h" #include "vk_scene.h" #include "vk_framectl.h" #include "vk_lightmap.h" diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index bc03a592..410853c5 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -1105,9 +1105,6 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar DEBUG_END(cmdbuf); } -qboolean initVk2d(void); -void deinitVk2d(void); - static void reloadPass( struct ray_pass_s **slot, struct ray_pass_s *new_pass ) { if (!new_pass) return; @@ -1132,9 +1129,6 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) if (g_rtx.reload_pipeline) { gEngine.Con_Printf(S_WARN "Reloading RTX shaders/pipelines\n"); - // reload 2d - deinitVk2d(); - initVk2d(); // TODO gracefully handle reload errors: need to change createPipeline, loadShader, VK_PipelineCreate... //vkDestroyPipeline(vk_core.device, g_rtx.pipeline, NULL); createPipeline(); From 4ef261a0b07bd32c6543a635b3486a159071cd6f Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 4 Jun 2022 10:48:29 -0700 Subject: [PATCH 349/548] vk: print devmem allocation failure reason --- ref_vk/vk_devmem.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ref_vk/vk_devmem.c b/ref_vk/vk_devmem.c index b225df1e..8690a66e 100644 --- a/ref_vk/vk_devmem.c +++ b/ref_vk/vk_devmem.c @@ -48,8 +48,10 @@ static VkDeviceSize optimalSize(VkDeviceSize size) { } static int allocateDeviceMemory(VkMemoryRequirements req, int type_index, VkMemoryAllocateFlags allocate_flags) { - if (g_vk_devmem.num_allocs == MAX_DEVMEM_ALLOCS) + if (g_vk_devmem.num_allocs == MAX_DEVMEM_ALLOCS) { + gEngine.Host_Error("Ran out of device memory allocation slots\n"); return -1; + } { const VkMemoryAllocateFlagsInfo mafi = { From ebcee75c0b892a886c6668d365b428232638a812 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 4 Jun 2022 10:49:34 -0700 Subject: [PATCH 350/548] vk: silence offset prints in render --- ref_vk/vk_render.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ref_vk/vk_render.c b/ref_vk/vk_render.c index 3b685670..3259508d 100644 --- a/ref_vk/vk_render.c +++ b/ref_vk/vk_render.c @@ -323,7 +323,7 @@ qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int verte // Store first dynamic allocation this frame if (lifetime == LifetimeSingleFrame && g_geom.dynamic_offsets[g_geom.frame_index] == ALO_ALLOC_FAILED) { - gEngine.Con_Reportf("FRAME=%d FIRST_OFFSET=%d\n", g_geom.frame_index, alloc_offset); + //gEngine.Con_Reportf("FRAME=%d FIRST_OFFSET=%d\n", g_geom.frame_index, alloc_offset); g_geom.dynamic_offsets[g_geom.frame_index] = alloc_offset; } @@ -446,7 +446,7 @@ void VK_RenderBegin( qboolean ray_tracing ) { { const int new_frame = (g_geom.frame_index + 1) % COUNTOF(g_geom.dynamic_offsets); if (g_geom.dynamic_offsets[new_frame] != ALO_ALLOC_FAILED) { - gEngine.Con_Reportf("FRAME=%d FREE_OFFSET=%d\n", g_geom.frame_index, g_geom.dynamic_offsets[new_frame]); + //gEngine.Con_Reportf("FRAME=%d FREE_OFFSET=%d\n", g_geom.frame_index, g_geom.dynamic_offsets[new_frame]); aloRingFree(&g_geom.dynamic_ring, g_geom.dynamic_offsets[new_frame]); g_geom.dynamic_offsets[new_frame] = ALO_ALLOC_FAILED; } From eda34ee9db09afabaab73f5829a17aa0363f29ea Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 4 Jun 2022 10:55:26 -0700 Subject: [PATCH 351/548] vk: explicitly synchronize adjacent command buffer submissions --- ref_vk/vk_framectl.c | 49 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/ref_vk/vk_framectl.c b/ref_vk/vk_framectl.c index cba93ce3..3b8f5882 100644 --- a/ref_vk/vk_framectl.c +++ b/ref_vk/vk_framectl.c @@ -31,6 +31,7 @@ static struct { vk_command_pool_t command; VkSemaphore sem_framebuffer_ready[MAX_CONCURRENT_FRAMES]; VkSemaphore sem_done[MAX_CONCURRENT_FRAMES]; + VkSemaphore sem_done2[MAX_CONCURRENT_FRAMES]; VkFence fence_done[MAX_CONCURRENT_FRAMES]; qboolean rtx_enabled; @@ -298,18 +299,30 @@ static void submit( VkCommandBuffer cmdbuf, qboolean wait ) { XVK_CHECK(vkEndCommandBuffer(cmdbuf)); { - const VkPipelineStageFlags stageflags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + const VkPipelineStageFlags stageflags[] = { + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + }; + const VkSemaphore waitophores[] = { + g_frame.sem_framebuffer_ready[g_frame.current.index], + g_frame.sem_done2[(g_frame.current.index + 1) % MAX_CONCURRENT_FRAMES], + }; + const VkSemaphore signalphores[] = { + g_frame.sem_done[g_frame.current.index], + g_frame.sem_done2[g_frame.current.index], + }; const VkSubmitInfo subinfo = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .pNext = NULL, .commandBufferCount = 1, .pCommandBuffers = &cmdbuf, - .waitSemaphoreCount = 1, - .pWaitSemaphores = g_frame.sem_framebuffer_ready + g_frame.current.index, - .signalSemaphoreCount = 1, - .pSignalSemaphores = g_frame.sem_done + g_frame.current.index, - .pWaitDstStageMask = &stageflags, + .waitSemaphoreCount = COUNTOF(waitophores), + .pWaitSemaphores = waitophores, + .pWaitDstStageMask = stageflags, + .signalSemaphoreCount = COUNTOF(signalphores), + .pSignalSemaphores = signalphores, }; + //gEngine.Con_Printf("SYNC: wait for semaphore %d, signal semaphore %d\n", (g_frame.current.index + 1) % MAX_CONCURRENT_FRAMES, g_frame.current.index); XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, g_frame.fence_done[g_frame.current.index])); g_frame.current.phase = Phase_Submitted; } @@ -375,8 +388,31 @@ qboolean VK_FrameCtlInit( void ) for (int i = 0; i < MAX_CONCURRENT_FRAMES; ++i) { g_frame.sem_framebuffer_ready[i] = R_VkSemaphoreCreate(); + SET_DEBUG_NAMEF(g_frame.sem_framebuffer_ready[i], VK_OBJECT_TYPE_SEMAPHORE, "framebuffer_ready[%d]", i); g_frame.sem_done[i] = R_VkSemaphoreCreate(); + SET_DEBUG_NAMEF(g_frame.sem_done[i], VK_OBJECT_TYPE_SEMAPHORE, "done[%d]", i); + g_frame.sem_done2[i] = R_VkSemaphoreCreate(); + SET_DEBUG_NAMEF(g_frame.sem_done2[i], VK_OBJECT_TYPE_SEMAPHORE, "done2[%d]", i); g_frame.fence_done[i] = R_VkFenceCreate(true); + SET_DEBUG_NAMEF(g_frame.fence_done[i], VK_OBJECT_TYPE_FENCE, "done[%d]", i); + } + + // Signal first frame semaphore as done + { + const VkPipelineStageFlags stageflags = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + const VkSubmitInfo subinfo = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = NULL, + .commandBufferCount = 0, + .pCommandBuffers = NULL, + .waitSemaphoreCount = 0, + .pWaitSemaphores = NULL, + .pWaitDstStageMask = &stageflags, + .signalSemaphoreCount = 1, + .pSignalSemaphores = g_frame.sem_done2 + 0, + }; + XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, NULL)); + //gEngine.Con_Printf("SYNC: signal semaphore %d\n", 0); } g_frame.rtx_enabled = vk_core.rtx; @@ -392,6 +428,7 @@ void VK_FrameCtlShutdown( void ) { for (int i = 0; i < MAX_CONCURRENT_FRAMES; ++i) { R_VkSemaphoreDestroy(g_frame.sem_framebuffer_ready[i]); R_VkSemaphoreDestroy(g_frame.sem_done[i]); + R_VkSemaphoreDestroy(g_frame.sem_done2[i]); R_VkFenceDestroy(g_frame.fence_done[i]); } From 1d48982e7b23860e175869b0e384832a72f32118 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 4 Jun 2022 14:04:44 -0700 Subject: [PATCH 352/548] vk: refactor staging api a bit --- ref_vk/vk_render.c | 11 ++- ref_vk/vk_render.h | 1 - ref_vk/vk_staging.c | 170 ++++++++++++++++++++++++++----------------- ref_vk/vk_staging.h | 35 ++++++--- ref_vk/vk_textures.c | 43 ++++++----- 5 files changed, 159 insertions(+), 101 deletions(-) diff --git a/ref_vk/vk_render.c b/ref_vk/vk_render.c index 3259508d..8a5f02ba 100644 --- a/ref_vk/vk_render.c +++ b/ref_vk/vk_render.c @@ -330,8 +330,14 @@ qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int verte { const uint32_t vertices_offset = offset / sizeof(vk_vertex_t); const uint32_t indices_offset = (offset + vertices_size) / sizeof(uint16_t); + const vk_staging_buffer_args_t staging_args = { + .buffer = g_geom.buffer.buffer, + .offset = offset, + .size = total_size, + .alignment = 4, + }; - const vk_staging_region_t staging = R_VkStagingLock(total_size, 4); + const vk_staging_region_t staging = R_VkStagingLockForBuffer(staging_args); ASSERT(staging.ptr); ASSERT( offset % sizeof(vk_vertex_t) == 0 ); @@ -350,7 +356,6 @@ qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int verte }, .impl_ = { .staging_handle = staging.handle, - .offset = offset, }, }; } @@ -359,7 +364,7 @@ qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int verte } void R_GeometryBufferUnlock( const r_geometry_buffer_lock_t *lock ) { - R_VkStagingUnlockToBuffer(lock->impl_.staging_handle, g_geom.buffer.buffer, lock->impl_.offset); + R_VkStagingUnlock(lock->impl_.staging_handle); } void XVK_RenderBufferMapClear( void ) { diff --git a/ref_vk/vk_render.h b/ref_vk/vk_render.h index 8c0d7e81..0aa8848c 100644 --- a/ref_vk/vk_render.h +++ b/ref_vk/vk_render.h @@ -41,7 +41,6 @@ typedef struct { struct { int staging_handle; - uint32_t offset; } impl_; } r_geometry_buffer_lock_t; diff --git a/ref_vk/vk_staging.c b/ref_vk/vk_staging.c index 80582b26..08a5f25d 100644 --- a/ref_vk/vk_staging.c +++ b/ref_vk/vk_staging.c @@ -6,27 +6,31 @@ #define DEFAULT_STAGING_SIZE (16*1024*1024) #define MAX_STAGING_ALLOCS (1024) +#define ALLOC_FAILED 0xffffffffu + typedef struct { - int offset, size; - enum { DestNone, DestBuffer, DestImage } dest_type; - union { - struct { - VkBuffer buffer; - VkDeviceSize offset; - } buffer; - struct { - VkImage image; - VkImageLayout layout; - VkBufferImageCopy region; - } image; - }; -} staging_alloc_t; + VkImage image; + VkImageLayout layout; +} staging_image_t; static struct { vk_buffer_t buffer; - staging_alloc_t allocs[MAX_STAGING_ALLOCS]; - int num_allocs; - int num_committed; + uint32_t offset; + + struct { + VkBuffer dest[MAX_STAGING_ALLOCS]; + VkBufferCopy copy[MAX_STAGING_ALLOCS]; + + int count; + int committed; + } buffers; + + struct { + staging_image_t dest[MAX_STAGING_ALLOCS]; + VkBufferImageCopy copy[MAX_STAGING_ALLOCS]; + int count; + int committed; + } images; } g_staging = {0}; qboolean R_VkStagingInit(void) { @@ -40,76 +44,107 @@ void R_VkStagingShutdown(void) { VK_BufferDestroy(&g_staging.buffer); } -vk_staging_region_t R_VkStagingLock(uint32_t size, uint32_t alignment) { - const int offset = g_staging.num_allocs > 0 - ? ALIGN_UP(g_staging.allocs[g_staging.num_allocs - 1].offset + g_staging.allocs[g_staging.num_allocs - 1].size, alignment) - : 0; - - if ( g_staging.num_allocs >= MAX_STAGING_ALLOCS ) - return (vk_staging_region_t){0}; +static uint32_t stagingAlloc(uint32_t size, uint32_t alignment) { + const uint32_t offset = ALIGN_UP(g_staging.offset, alignment); if ( offset + size > g_staging.buffer.size ) + return ALLOC_FAILED; + + g_staging.offset = offset + size; + return offset; +} + +vk_staging_region_t R_VkStagingLockForBuffer(vk_staging_buffer_args_t args) { + if ( g_staging.buffers.count >= MAX_STAGING_ALLOCS ) return (vk_staging_region_t){0}; - memset(g_staging.allocs + g_staging.num_allocs, 0, sizeof(staging_alloc_t)); - g_staging.allocs[g_staging.num_allocs].offset = offset; - g_staging.allocs[g_staging.num_allocs].size = size; - g_staging.num_allocs++; - return (vk_staging_region_t){(char*)g_staging.buffer.mapped + offset, size, g_staging.num_allocs - 1}; + const int index = g_staging.buffers.count; + + const uint32_t offset = stagingAlloc(args.size, args.alignment); + if (offset == ALLOC_FAILED) + return (vk_staging_region_t){0}; + + g_staging.buffers.dest[index] = args.buffer; + g_staging.buffers.copy[index] = (VkBufferCopy){ + .srcOffset = offset, + .dstOffset = args.offset, + .size = args.size, + }; + + g_staging.buffers.count++; + + return (vk_staging_region_t){ + .ptr = (char*)g_staging.buffer.mapped + offset, + .handle = index, + }; } -void R_VkStagingUnlockToBuffer(staging_handle_t handle, VkBuffer dest, size_t dest_offset) { - staging_alloc_t *alloc; - ASSERT(handle >= 0 && handle < g_staging.num_allocs); - ASSERT(g_staging.allocs[handle].dest_type == DestNone); +vk_staging_region_t R_VkStagingLockForImage(vk_staging_image_args_t args) { + if ( g_staging.images.count >= MAX_STAGING_ALLOCS ) + return (vk_staging_region_t){0}; - alloc = g_staging.allocs + handle; - alloc->dest_type = DestBuffer; + const int index = g_staging.images.count; + staging_image_t *const dest = g_staging.images.dest + index; - alloc->buffer.buffer = dest; - alloc->buffer.offset = dest_offset; + const uint32_t offset = stagingAlloc(args.size, args.alignment); + if (offset == ALLOC_FAILED) + return (vk_staging_region_t){0}; + + dest->image = args.image; + dest->layout = args.layout; + g_staging.images.copy[index] = args.region; + + g_staging.images.count++; + + return (vk_staging_region_t){ + .ptr = (char*)g_staging.buffer.mapped + offset, + .handle = index + MAX_STAGING_ALLOCS, + }; } -void R_VkStagingUnlockToImage(staging_handle_t handle, VkBufferImageCopy* dest_region, VkImageLayout layout, VkImage dest) { - staging_alloc_t *alloc; - ASSERT(handle >= 0 && handle < g_staging.num_allocs); - ASSERT(g_staging.allocs[handle].dest_type == DestNone); +void R_VkStagingUnlock(staging_handle_t handle) { + ASSERT(handle >= 0); + ASSERT(handle < MAX_STAGING_ALLOCS * 2); - alloc = g_staging.allocs + handle; - alloc->dest_type = DestImage; - alloc->image.layout = layout; - alloc->image.image = dest; - alloc->image.region = *dest_region; - alloc->image.region.bufferOffset += alloc->offset; + // FIXME mark and check ready } -void R_VkStagingCommit(VkCommandBuffer cmdbuf) { - for ( int i = g_staging.num_committed; i < g_staging.num_allocs; i++ ) { - staging_alloc_t *const alloc = g_staging.allocs + i; - ASSERT(alloc->dest_type != DestNone); - switch (alloc->dest_type) { - case DestImage: - vkCmdCopyBufferToImage(cmdbuf, g_staging.buffer.buffer, alloc->image.image, alloc->image.layout, 1, &alloc->image.region); - break; - case DestBuffer: - // TODO coalesce staging regions for the same dest buffer - //gEngine.Con_Printf("vkCmdCopyBuffer %d src_offset=%d dst_offset=%d size=%d\n", i, alloc->offset, alloc->buffer.offset, alloc->size); - vkCmdCopyBuffer(cmdbuf, g_staging.buffer.buffer, alloc->buffer.buffer, 1, &(VkBufferCopy){alloc->offset, alloc->buffer.offset, alloc->size}); - break; - } - - alloc->dest_type = DestNone; +static void commitBuffers(VkCommandBuffer cmdbuf) { + for (int i = g_staging.buffers.committed; i < g_staging.buffers.count; i++) { + vkCmdCopyBuffer(cmdbuf, g_staging.buffer.buffer, + g_staging.buffers.dest[i], + 1, g_staging.buffers.copy + i); } - g_staging.num_committed = g_staging.num_allocs; + g_staging.buffers.committed = g_staging.buffers.count; +} + +static void commitImages(VkCommandBuffer cmdbuf) { + for (int i = g_staging.images.committed; i < g_staging.images.count; i++) { + vkCmdCopyBufferToImage(cmdbuf, g_staging.buffer.buffer, + g_staging.images.dest[i].image, + g_staging.images.dest[i].layout, + 1, g_staging.images.copy + i); + } + + g_staging.images.committed = g_staging.images.count; +} + + +void R_VkStagingCommit(VkCommandBuffer cmdbuf) { + commitBuffers(cmdbuf); + commitImages(cmdbuf); } void R_VKStagingMarkEmpty_FIXME(void) { - g_staging.num_committed = g_staging.num_allocs = 0; + g_staging.buffers.committed = g_staging.buffers.count = 0; + g_staging.images.committed = g_staging.images.count = 0; + g_staging.offset = 0; } void R_VkStagingFlushSync(void) { - if ( !g_staging.num_allocs ) + if ( g_staging.buffers.count == g_staging.buffers.committed + && g_staging.images.count == g_staging.images.committed) return; { @@ -129,9 +164,10 @@ void R_VkStagingFlushSync(void) { XVK_CHECK(vkBeginCommandBuffer(cmdbuf, &beginfo)); R_VkStagingCommit(cmdbuf); - R_VKStagingMarkEmpty_FIXME(); XVK_CHECK(vkEndCommandBuffer(cmdbuf)); XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, VK_NULL_HANDLE)); XVK_CHECK(vkQueueWaitIdle(vk_core.queue)); + + R_VKStagingMarkEmpty_FIXME(); } } diff --git a/ref_vk/vk_staging.h b/ref_vk/vk_staging.h index 6922df84..ff3dab19 100644 --- a/ref_vk/vk_staging.h +++ b/ref_vk/vk_staging.h @@ -5,19 +5,37 @@ qboolean R_VkStagingInit(void); void R_VkStagingShutdown(void); -//void *R_VkStagingAlloc(size_t size, VkBuffer dest, size_t dest_offset); - typedef int staging_handle_t; typedef struct { void *ptr; - size_t size; staging_handle_t handle; } vk_staging_region_t; -vk_staging_region_t R_VkStagingLock(uint32_t size, uint32_t alignment); -void R_VkStagingUnlockToBuffer(staging_handle_t handle, VkBuffer dest, size_t dest_offset); -void R_VkStagingUnlockToImage(staging_handle_t handle, VkBufferImageCopy* dest_region, VkImageLayout layout, VkImage dest); +// Allocate region for uploadting to buffer +typedef struct { + VkBuffer buffer; + uint32_t offset; + uint32_t size; + uint32_t alignment; +} vk_staging_buffer_args_t; +vk_staging_region_t R_VkStagingLockForBuffer(vk_staging_buffer_args_t args); + +// Allocate region for uploading to image +typedef struct { + VkImage image; + VkBufferImageCopy region; + VkImageLayout layout; + uint32_t size; + uint32_t alignment; +} vk_staging_image_args_t; +vk_staging_region_t R_VkStagingLockForImage(vk_staging_image_args_t args); + +// Mark allocated region as ready for upload +void R_VkStagingUnlock(staging_handle_t handle); + +// Append copy commands to command buffer and mark staging as empty +// FIXME: it's not empty yet, as it depends on cmdbuf being actually submitted and completed void R_VkStagingCommit(VkCommandBuffer cmdbuf); // FIXME Remove this with proper staging @@ -25,8 +43,3 @@ void R_VKStagingMarkEmpty_FIXME(void); // Force commit synchronously void R_VkStagingFlushSync(void); - -// TODO -// - [x] call init/shutdown from vk_core.ckkjkj -// - [x] use this in vk_texture.c -// - [ ] use this in vk_render.c diff --git a/ref_vk/vk_textures.c b/ref_vk/vk_textures.c index d55c0a4d..bc5a6bde 100644 --- a/ref_vk/vk_textures.c +++ b/ref_vk/vk_textures.c @@ -583,26 +583,31 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, const int width = Q_max( 1, ( pic->width >> mip )); const int height = Q_max( 1, ( pic->height >> mip )); const size_t mip_size = CalcImageSize( pic->type, width, height, 1 ); - - VkBufferImageCopy region = {0}; - region.bufferOffset = 0; - region.bufferRowLength = 0; - region.bufferImageHeight = 0; - region.imageSubresource = (VkImageSubresourceLayers){ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .mipLevel = mip, - .baseArrayLayer = layer, - .layerCount = 1, - }; - region.imageExtent = (VkExtent3D){ - .width = width, - .height = height, - .depth = 1, - }; - const uint32_t texel_block_size = 4; // TODO compressed might be different + const vk_staging_image_args_t staging_args = { + .image = tex->vk.image.image, + .region = (VkBufferImageCopy) { + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = (VkImageSubresourceLayers){ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = mip, + .baseArrayLayer = layer, + .layerCount = 1, + }, + .imageExtent = (VkExtent3D){ + .width = width, + .height = height, + .depth = 1, + }, + }, + .layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .size = mip_size, + .alignment = texel_block_size, + }; - vk_staging_region_t staging = R_VkStagingLock(mip_size, texel_block_size); + const vk_staging_region_t staging = R_VkStagingLockForImage(staging_args); ASSERT(staging.ptr); memcpy(staging.ptr, buf, mip_size); @@ -612,7 +617,7 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, BuildMipMap( buf, width, height, 1, tex->flags ); } - R_VkStagingUnlockToImage(staging.handle, ®ion, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, tex->vk.image.image); + R_VkStagingUnlock(staging.handle); } } From 88f8aabf8ec774ff6a1be51492091efcc12dd808 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 4 Jun 2022 14:26:12 -0700 Subject: [PATCH 353/548] vk: fix linux build --- ref_vk/vk_framectl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_vk/vk_framectl.c b/ref_vk/vk_framectl.c index 3b8f5882..6a35eabe 100644 --- a/ref_vk/vk_framectl.c +++ b/ref_vk/vk_framectl.c @@ -411,7 +411,7 @@ qboolean VK_FrameCtlInit( void ) .signalSemaphoreCount = 1, .pSignalSemaphores = g_frame.sem_done2 + 0, }; - XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, NULL)); + XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, VK_NULL_HANDLE)); //gEngine.Con_Printf("SYNC: signal semaphore %d\n", 0); } From f390591647d026d2410386d200b638f10fb84b9d Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 4 Jun 2022 14:26:35 -0700 Subject: [PATCH 354/548] vk: fix texture mips uploading --- ref_vk/vk_staging.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ref_vk/vk_staging.c b/ref_vk/vk_staging.c index 08a5f25d..9c898b1d 100644 --- a/ref_vk/vk_staging.c +++ b/ref_vk/vk_staging.c @@ -93,6 +93,7 @@ vk_staging_region_t R_VkStagingLockForImage(vk_staging_image_args_t args) { dest->image = args.image; dest->layout = args.layout; g_staging.images.copy[index] = args.region; + g_staging.images.copy[index].bufferOffset += offset; g_staging.images.count++; From be20ae2fb089425a90203244f5f698646a433d6f Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 4 Jun 2022 14:27:12 -0700 Subject: [PATCH 355/548] vk: coalesce same buffer dest staging regions uploading --- ref_vk/vk_staging.c | 24 ++++++++++++++++++++++-- ref_vk/vk_staging.h | 2 +- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/ref_vk/vk_staging.c b/ref_vk/vk_staging.c index 9c898b1d..11bbc3ae 100644 --- a/ref_vk/vk_staging.c +++ b/ref_vk/vk_staging.c @@ -111,10 +111,30 @@ void R_VkStagingUnlock(staging_handle_t handle) { } static void commitBuffers(VkCommandBuffer cmdbuf) { + // TODO better coalescing: + // - upload once per buffer + // - join adjacent regions + + VkBuffer prev_buffer = VK_NULL_HANDLE; + int first_copy = 0; for (int i = g_staging.buffers.committed; i < g_staging.buffers.count; i++) { + if (prev_buffer == g_staging.buffers.dest[i]) + continue; + + if (prev_buffer != VK_NULL_HANDLE) { + vkCmdCopyBuffer(cmdbuf, g_staging.buffer.buffer, + prev_buffer, + i - first_copy, g_staging.buffers.copy + first_copy); + } + + prev_buffer = g_staging.buffers.dest[i]; + first_copy = i; + } + + if (prev_buffer != VK_NULL_HANDLE) { vkCmdCopyBuffer(cmdbuf, g_staging.buffer.buffer, - g_staging.buffers.dest[i], - 1, g_staging.buffers.copy + i); + prev_buffer, + g_staging.buffers.count - first_copy, g_staging.buffers.copy + first_copy); } g_staging.buffers.committed = g_staging.buffers.count; diff --git a/ref_vk/vk_staging.h b/ref_vk/vk_staging.h index ff3dab19..e3bdc31a 100644 --- a/ref_vk/vk_staging.h +++ b/ref_vk/vk_staging.h @@ -24,8 +24,8 @@ vk_staging_region_t R_VkStagingLockForBuffer(vk_staging_buffer_args_t args); // Allocate region for uploading to image typedef struct { VkImage image; - VkBufferImageCopy region; VkImageLayout layout; + VkBufferImageCopy region; uint32_t size; uint32_t alignment; } vk_staging_image_args_t; From f8ea93656f266eba09375a6a84195ae0c6214889 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 25 Jun 2022 11:12:48 -0700 Subject: [PATCH 356/548] do not overwrite staging data until we know it's been uploaded --- ref_vk/vk_framectl.c | 5 ++++- ref_vk/vk_render.c | 20 +++++++++++++++++ ref_vk/vk_render.h | 2 ++ ref_vk/vk_scene.c | 1 - ref_vk/vk_staging.c | 51 +++++++++++++++++++++++++++----------------- ref_vk/vk_staging.h | 1 + ref_vk/vk_textures.c | 3 ++- 7 files changed, 60 insertions(+), 23 deletions(-) diff --git a/ref_vk/vk_framectl.c b/ref_vk/vk_framectl.c index 6a35eabe..3a7e5608 100644 --- a/ref_vk/vk_framectl.c +++ b/ref_vk/vk_framectl.c @@ -217,6 +217,7 @@ void R_BeginFrame( qboolean clearScene ) { ASSERT(!g_frame.current.framebuffer.framebuffer); waitForFrameFence(); + R_VkStagingFrameFlip(); g_frame.current.framebuffer = R_VkSwapchainAcquire( g_frame.sem_framebuffer_ready[g_frame.current.index] ); vk_frame.width = g_frame.current.framebuffer.width; @@ -251,8 +252,10 @@ static void enqueueRendering( VkCommandBuffer cmdbuf ) { ASSERT(g_frame.current.phase == Phase_FrameBegan); + //R_VkStagingFlushSync(); + R_VkStagingCommit(cmdbuf); // FIXME where and when - R_VKStagingMarkEmpty_FIXME(); + VK_Render_FIXME_Barrier(cmdbuf); if (g_frame.rtx_enabled) VK_RenderEndRTX( cmdbuf, g_frame.current.framebuffer.view, g_frame.current.framebuffer.image, g_frame.current.framebuffer.width, g_frame.current.framebuffer.height ); diff --git a/ref_vk/vk_render.c b/ref_vk/vk_render.c index 8a5f02ba..f8cfc71f 100644 --- a/ref_vk/vk_render.c +++ b/ref_vk/vk_render.c @@ -622,6 +622,26 @@ static uint32_t writeDlightsToUBO( void ) return ubo_lights_offset; } +void VK_Render_FIXME_Barrier( VkCommandBuffer cmdbuf ) + // FIXME + { + const VkBufferMemoryBarrier bmb[] = { { + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + //.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR, // FIXME + .dstAccessMask = VK_ACCESS_INDEX_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT , // FIXME + .buffer = g_geom.buffer.buffer, + .offset = 0, // FIXME + .size = VK_WHOLE_SIZE, // FIXME + } }; + vkCmdPipelineBarrier(cmdbuf, + VK_PIPELINE_STAGE_TRANSFER_BIT, + //VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, + //VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, + VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, + 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); + } + void VK_RenderEnd( VkCommandBuffer cmdbuf ) { // TODO we can sort collected draw commands for more efficient and correct rendering diff --git a/ref_vk/vk_render.h b/ref_vk/vk_render.h index 0aa8848c..a6cd6226 100644 --- a/ref_vk/vk_render.h +++ b/ref_vk/vk_render.h @@ -152,3 +152,5 @@ void VK_RenderDebugLabelEnd( void ); void VK_RenderBegin( qboolean ray_tracing ); void VK_RenderEnd( VkCommandBuffer cmdbuf ); void VK_RenderEndRTX( VkCommandBuffer cmdbuf, VkImageView img_dst_view, VkImage img_dst, uint32_t w, uint32_t h ); + +void VK_Render_FIXME_Barrier( VkCommandBuffer cmdbuf ); diff --git a/ref_vk/vk_scene.c b/ref_vk/vk_scene.c index aa57591f..08aa1cb9 100644 --- a/ref_vk/vk_scene.c +++ b/ref_vk/vk_scene.c @@ -167,7 +167,6 @@ void R_NewMap( void ) { if (vk_core.rtx) VK_RayNewMap(); - // Load light entities and patch data prior to loading map brush model XVK_ParseMapEntities(); diff --git a/ref_vk/vk_staging.c b/ref_vk/vk_staging.c index 11bbc3ae..63fba5eb 100644 --- a/ref_vk/vk_staging.c +++ b/ref_vk/vk_staging.c @@ -1,12 +1,11 @@ #include "vk_staging.h" #include "vk_buffer.h" +#include "alolcator.h" #include -#define DEFAULT_STAGING_SIZE (16*1024*1024) -#define MAX_STAGING_ALLOCS (1024) - -#define ALLOC_FAILED 0xffffffffu +#define DEFAULT_STAGING_SIZE (64*1024*1024) +#define MAX_STAGING_ALLOCS (2048) typedef struct { VkImage image; @@ -15,7 +14,7 @@ typedef struct { static struct { vk_buffer_t buffer; - uint32_t offset; + alo_ring_t ring; struct { VkBuffer dest[MAX_STAGING_ALLOCS]; @@ -31,12 +30,18 @@ static struct { int count; int committed; } images; + + struct { + uint32_t offset; + } frames[2]; } g_staging = {0}; qboolean R_VkStagingInit(void) { if (!VK_BufferCreate("staging", &g_staging.buffer, DEFAULT_STAGING_SIZE, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) return false; + aloRingInit(&g_staging.ring, DEFAULT_STAGING_SIZE); + return true; } @@ -44,25 +49,17 @@ void R_VkStagingShutdown(void) { VK_BufferDestroy(&g_staging.buffer); } -static uint32_t stagingAlloc(uint32_t size, uint32_t alignment) { - const uint32_t offset = ALIGN_UP(g_staging.offset, alignment); - - if ( offset + size > g_staging.buffer.size ) - return ALLOC_FAILED; - - g_staging.offset = offset + size; - return offset; -} - vk_staging_region_t R_VkStagingLockForBuffer(vk_staging_buffer_args_t args) { if ( g_staging.buffers.count >= MAX_STAGING_ALLOCS ) return (vk_staging_region_t){0}; const int index = g_staging.buffers.count; - const uint32_t offset = stagingAlloc(args.size, args.alignment); - if (offset == ALLOC_FAILED) + const uint32_t offset = aloRingAlloc(&g_staging.ring, args.size, args.alignment); + if (offset == ALO_ALLOC_FAILED) return (vk_staging_region_t){0}; + if (g_staging.frames[1].offset == ALO_ALLOC_FAILED) + g_staging.frames[1].offset = offset; g_staging.buffers.dest[index] = args.buffer; g_staging.buffers.copy[index] = (VkBufferCopy){ @@ -86,9 +83,11 @@ vk_staging_region_t R_VkStagingLockForImage(vk_staging_image_args_t args) { const int index = g_staging.images.count; staging_image_t *const dest = g_staging.images.dest + index; - const uint32_t offset = stagingAlloc(args.size, args.alignment); - if (offset == ALLOC_FAILED) + const uint32_t offset = aloRingAlloc(&g_staging.ring, args.size, args.alignment); + if (offset == ALO_ALLOC_FAILED) return (vk_staging_region_t){0}; + if (g_staging.frames[1].offset == ALO_ALLOC_FAILED) + g_staging.frames[1].offset = offset; dest->image = args.image; dest->layout = args.layout; @@ -157,10 +156,22 @@ void R_VkStagingCommit(VkCommandBuffer cmdbuf) { commitImages(cmdbuf); } +void R_VkStagingFrameFlip(void) { + if (g_staging.frames[0].offset != ALO_ALLOC_FAILED) + aloRingFree(&g_staging.ring, g_staging.frames[0].offset); + + g_staging.frames[0] = g_staging.frames[1]; + g_staging.frames[1].offset = ALO_ALLOC_FAILED; + + g_staging.buffers.committed = g_staging.buffers.count = 0; + g_staging.images.committed = g_staging.images.count = 0; +} + void R_VKStagingMarkEmpty_FIXME(void) { g_staging.buffers.committed = g_staging.buffers.count = 0; g_staging.images.committed = g_staging.images.count = 0; - g_staging.offset = 0; + g_staging.frames[0].offset = g_staging.frames[1].offset = ALO_ALLOC_FAILED; + aloRingInit(&g_staging.ring, DEFAULT_STAGING_SIZE); } void R_VkStagingFlushSync(void) { diff --git a/ref_vk/vk_staging.h b/ref_vk/vk_staging.h index e3bdc31a..5eef7ecc 100644 --- a/ref_vk/vk_staging.h +++ b/ref_vk/vk_staging.h @@ -37,6 +37,7 @@ void R_VkStagingUnlock(staging_handle_t handle); // Append copy commands to command buffer and mark staging as empty // FIXME: it's not empty yet, as it depends on cmdbuf being actually submitted and completed void R_VkStagingCommit(VkCommandBuffer cmdbuf); +void R_VkStagingFrameFlip(void); // FIXME Remove this with proper staging void R_VKStagingMarkEmpty_FIXME(void); diff --git a/ref_vk/vk_textures.c b/ref_vk/vk_textures.c index bc5a6bde..8b3eab43 100644 --- a/ref_vk/vk_textures.c +++ b/ref_vk/vk_textures.c @@ -622,7 +622,6 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, } R_VkStagingCommit(cmdbuf); - R_VKStagingMarkEmpty_FIXME(); // 5.2 image:layout:DST -> image:layout:SAMPLED // 5.2.1 transitionToLayout(DST -> SHADER_READ_ONLY) @@ -653,6 +652,8 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, XVK_CHECK(vkQueueWaitIdle(vk_core.queue)); } + R_VKStagingMarkEmpty_FIXME(); + // TODO how should we approach this: // - per-texture desc sets can be inconvenient if texture is used in different incompatible contexts // - update descriptor sets in batch? From c299ac9ccde2c2b39374a528220a0868fecd5fd2 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 25 Jun 2022 11:13:56 -0700 Subject: [PATCH 357/548] do not overwrite ubo buffers being used --- ref_vk/vk_buffer.c | 29 ++++++++ ref_vk/vk_buffer.h | 11 +++ ref_vk/vk_render.c | 162 ++++++++++++++++++++++++--------------------- 3 files changed, 125 insertions(+), 77 deletions(-) diff --git a/ref_vk/vk_buffer.c b/ref_vk/vk_buffer.c index 988d2920..de0b5b01 100644 --- a/ref_vk/vk_buffer.c +++ b/ref_vk/vk_buffer.c @@ -125,3 +125,32 @@ void R_DEBuffer_Flip(r_debuffer_t* debuf) { debuf->frame_dynamic_offset[1] = ALO_ALLOC_FAILED; } +void R_FlippingBuffer_Init(r_flipping_buffer_t *flibuf, uint32_t size) { + aloRingInit(&flibuf->ring, size); + R_FlippingBuffer_Clear(flibuf); +} + +void R_FlippingBuffer_Clear(r_flipping_buffer_t *flibuf) { + aloRingInit(&flibuf->ring, flibuf->ring.size); + flibuf->frame_offsets[0] = flibuf->frame_offsets[1] = ALO_ALLOC_FAILED; +} + +uint32_t R_FlippingBuffer_Alloc(r_flipping_buffer_t* flibuf, uint32_t size, uint32_t align) { + const uint32_t offset = aloRingAlloc(&flibuf->ring, size, align); + if (offset == ALO_ALLOC_FAILED) + return ALO_ALLOC_FAILED; + + if (flibuf->frame_offsets[1] == ALO_ALLOC_FAILED) + flibuf->frame_offsets[1] = offset; + + return offset; +} + +void R_FlippingBuffer_Flip(r_flipping_buffer_t* flibuf) { + if (flibuf->frame_offsets[0] != ALO_ALLOC_FAILED) + aloRingFree(&flibuf->ring, flibuf->frame_offsets[0]); + + flibuf->frame_offsets[0] = flibuf->frame_offsets[1]; + flibuf->frame_offsets[1] = ALO_ALLOC_FAILED; +} + diff --git a/ref_vk/vk_buffer.h b/ref_vk/vk_buffer.h index bcf3addd..0f3c222b 100644 --- a/ref_vk/vk_buffer.h +++ b/ref_vk/vk_buffer.h @@ -61,3 +61,14 @@ typedef enum { void R_DEBuffer_Init(r_debuffer_t *debuf, uint32_t static_size, uint32_t dynamic_size); uint32_t R_DEBuffer_Alloc(r_debuffer_t* debuf, r_lifetime_t lifetime, uint32_t size, uint32_t align); void R_DEBuffer_Flip(r_debuffer_t* debuf); + + +typedef struct { + alo_ring_t ring; + uint32_t frame_offsets[2]; +} r_flipping_buffer_t; + +void R_FlippingBuffer_Init(r_flipping_buffer_t *flibuf, uint32_t size); +void R_FlippingBuffer_Clear(r_flipping_buffer_t *flibuf); +uint32_t R_FlippingBuffer_Alloc(r_flipping_buffer_t* flibuf, uint32_t size, uint32_t align); +void R_FlippingBuffer_Flip(r_flipping_buffer_t* flibuf); diff --git a/ref_vk/vk_render.c b/ref_vk/vk_render.c index f8cfc71f..f5d008cc 100644 --- a/ref_vk/vk_render.c +++ b/ref_vk/vk_render.c @@ -25,7 +25,7 @@ #define MAX_BUFFER_INDICES_STATIC (MAX_BUFFER_VERTICES_STATIC * 3) #define GEOMETRY_BUFFER_STATIC_SIZE ALIGN_UP(MAX_BUFFER_VERTICES_STATIC * sizeof(vk_vertex_t) + MAX_BUFFER_INDICES_STATIC * sizeof(uint16_t), sizeof(vk_vertex_t)) -#define MAX_BUFFER_VERTICES_DYNAMIC (128 * 1024) +#define MAX_BUFFER_VERTICES_DYNAMIC (128 * 1024 * 2) #define MAX_BUFFER_INDICES_DYNAMIC (MAX_BUFFER_VERTICES_DYNAMIC * 3) #define GEOMETRY_BUFFER_DYNAMIC_SIZE ALIGN_UP(MAX_BUFFER_VERTICES_DYNAMIC * sizeof(vk_vertex_t) + MAX_BUFFER_INDICES_DYNAMIC * sizeof(uint16_t), sizeof(vk_vertex_t)) @@ -239,11 +239,57 @@ typedef struct { } light[MAX_DLIGHTS]; } vk_ubo_lights_t; -qboolean VK_RenderInit( void ) { - uint32_t uniform_unit_size; +#define MAX_DRAW_COMMANDS 8192 // TODO estimate +#define MAX_DEBUG_NAME_LENGTH 32 +typedef struct render_draw_s { + int lightmap, texture; + int render_mode; + uint32_t element_count; + uint32_t index_offset, vertex_offset; + /* TODO this should be a separate thing? */ struct { float r, g, b; } emissive; +} render_draw_t; + +enum draw_command_type_e { + DrawLabelBegin, + DrawLabelEnd, + DrawDraw +}; + +typedef struct { + enum draw_command_type_e type; + union { + char debug_label[MAX_DEBUG_NAME_LENGTH]; + struct { + render_draw_t draw; + uint32_t ubo_offset; + matrix3x4 transform; + } draw; + }; +} draw_command_t; + +static struct { + int uniform_data_set_mask; + uniform_data_t current_uniform_data; + uniform_data_t dirty_uniform_data; + + r_flipping_buffer_t uniform_alloc; + uint32_t current_ubo_offset_FIXME; + + draw_command_t draw_commands[MAX_DRAW_COMMANDS]; + int num_draw_commands; + + matrix4x4 model, view, projection; + + qboolean current_frame_is_ray_traced; +} g_render_state; + +qboolean VK_RenderInit( void ) { g_render.ubo_align = Q_max(4, vk_core.physical_device.properties.limits.minUniformBufferOffsetAlignment); - uniform_unit_size = ((sizeof(uniform_data_t) + g_render.ubo_align - 1) / g_render.ubo_align) * g_render.ubo_align; + + const uint32_t uniform_unit_size = ((sizeof(uniform_data_t) + g_render.ubo_align - 1) / g_render.ubo_align) * g_render.ubo_align; + const uint32_t uniform_buffer_size = uniform_unit_size * MAX_UNIFORM_SLOTS; + R_FlippingBuffer_Init(&g_render_state.uniform_alloc, uniform_buffer_size); // TODO device memory and friends (e.g. handle mobile memory ...) @@ -252,7 +298,7 @@ qboolean VK_RenderInit( void ) { (vk_core.rtx ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : 0))) // TODO staging buffer? return false; - if (!VK_BufferCreate("render uniform_buffer", &g_render.uniform_buffer, uniform_unit_size * MAX_UNIFORM_SLOTS, + if (!VK_BufferCreate("render uniform_buffer", &g_render.uniform_buffer, uniform_buffer_size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | (vk_core.rtx ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : 0))) // TODO staging buffer? return false; @@ -315,9 +361,9 @@ qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int verte const uint32_t alloc_offset = aloRingAlloc(ring, total_size, sizeof(vk_vertex_t)); const uint32_t offset = alloc_offset + ((lifetime == LifetimeSingleFrame) ? GEOMETRY_BUFFER_STATIC_SIZE : 0); if (alloc_offset == ALO_ALLOC_FAILED) { - gEngine.Con_Printf(S_ERROR "Cannot allocate %s geometry buffer for %d vertices (%d bytes) and %d indices (%d bytes)\n", - lifetime == LifetimeSingleFrame ? "dynamic" : "static", - vertex_count, vertices_size, index_count, indices_size); + /* gEngine.Con_Printf(S_ERROR "Cannot allocate %s geometry buffer for %d vertices (%d bytes) and %d indices (%d bytes)\n", */ + /* lifetime == LifetimeSingleFrame ? "dynamic" : "static", */ + /* vertex_count, vertices_size, index_count, indices_size); */ return false; } @@ -382,51 +428,6 @@ void XVK_RenderBufferPrintStats( void ) { g_geom.static_ring.head / 1024, g_geom.static_ring.size / 1024); } -#define MAX_DRAW_COMMANDS 8192 // TODO estimate -#define MAX_DEBUG_NAME_LENGTH 32 - -typedef struct render_draw_s { - int lightmap, texture; - int render_mode; - uint32_t element_count; - uint32_t index_offset, vertex_offset; - /* TODO this should be a separate thing? */ struct { float r, g, b; } emissive; -} render_draw_t; - -enum draw_command_type_e { - DrawLabelBegin, - DrawLabelEnd, - DrawDraw -}; - -typedef struct { - enum draw_command_type_e type; - union { - char debug_label[MAX_DEBUG_NAME_LENGTH]; - struct { - render_draw_t draw; - uint32_t ubo_offset; - matrix3x4 transform; - } draw; - }; -} draw_command_t; - -static struct { - int uniform_data_set_mask; - uniform_data_t current_uniform_data; - uniform_data_t dirty_uniform_data; - - uint32_t current_ubo_offset; - uint32_t uniform_free_offset; - - draw_command_t draw_commands[MAX_DRAW_COMMANDS]; - int num_draw_commands; - - matrix4x4 model, view, projection; - - qboolean current_frame_is_ray_traced; -} g_render_state; - enum { UNIFORM_UNSET = 0, UNIFORM_SET_COLOR = 1, @@ -438,12 +439,11 @@ enum { }; void VK_RenderBegin( qboolean ray_tracing ) { - g_render_state.uniform_free_offset = 0; // FIXME multiple frames in flight g_render_state.uniform_data_set_mask = UNIFORM_UNSET; - g_render_state.current_ubo_offset = UINT32_MAX; - + g_render_state.current_ubo_offset_FIXME = UINT32_MAX; memset(&g_render_state.current_uniform_data, 0, sizeof(g_render_state.current_uniform_data)); memset(&g_render_state.dirty_uniform_data, 0, sizeof(g_render_state.dirty_uniform_data)); + R_FlippingBuffer_Flip(&g_render_state.uniform_alloc); g_render_state.num_draw_commands = 0; g_render_state.current_frame_is_ray_traced = ray_tracing; @@ -514,11 +514,7 @@ void VK_RenderStateSetMatrixModel( const matrix4x4 model ) static uint32_t allocUniform( uint32_t size, uint32_t alignment ) { // FIXME Q_max is not correct, we need NAIMENSCHEEE OBSCHEEE KRATNOE const uint32_t align = Q_max(alignment, g_render.ubo_align); - const uint32_t offset = (((g_render_state.uniform_free_offset + align - 1) / align) * align); - if (offset + size > g_render.uniform_buffer.size) - return UINT32_MAX; - - g_render_state.uniform_free_offset = offset + size; + const uint32_t offset = R_FlippingBuffer_Alloc(&g_render_state.uniform_alloc, size, align); return offset; } @@ -542,6 +538,27 @@ static void drawCmdPushDebugLabelEnd( void ) { } } +// FIXME get rid of this garbage +static uint32_t getUboOffset_FIXME( void ) { + // Figure out whether we need to update UBO data, and upload new data if we do + // TODO generally it's not safe to do memcmp for structures comparison + if (g_render_state.current_ubo_offset_FIXME == UINT32_MAX + || ((g_render_state.uniform_data_set_mask & UNIFORM_UPLOADED) == 0) + || memcmp(&g_render_state.current_uniform_data, &g_render_state.dirty_uniform_data, sizeof(g_render_state.current_uniform_data)) != 0) { + g_render_state.current_ubo_offset_FIXME = allocUniform(sizeof(uniform_data_t), 16 /* why 16? vec4? */); + + if (g_render_state.current_ubo_offset_FIXME == ALO_ALLOC_FAILED) + return UINT32_MAX; + + uniform_data_t *const ubo = (uniform_data_t*)((byte*)g_render.uniform_buffer.mapped + g_render_state.current_ubo_offset_FIXME); + memcpy(&g_render_state.current_uniform_data, &g_render_state.dirty_uniform_data, sizeof(g_render_state.dirty_uniform_data)); + memcpy(ubo, &g_render_state.current_uniform_data, sizeof(*ubo)); + g_render_state.uniform_data_set_mask |= UNIFORM_UPLOADED; + } + + return g_render_state.current_ubo_offset_FIXME; +} + static void drawCmdPushDraw( const render_draw_t *draw ) { draw_command_t *draw_command; @@ -561,26 +578,16 @@ static void drawCmdPushDraw( const render_draw_t *draw ) return; } - // Figure out whether we need to update UBO data, and upload new data if we do - // TODO generally it's not safe to do memcmp for structures comparison - if (g_render_state.current_ubo_offset == UINT32_MAX || ((g_render_state.uniform_data_set_mask & UNIFORM_UPLOADED) == 0) - || memcmp(&g_render_state.current_uniform_data, &g_render_state.dirty_uniform_data, sizeof(g_render_state.current_uniform_data)) != 0) { - uniform_data_t *ubo; - g_render_state.current_ubo_offset = allocUniform( sizeof(uniform_data_t), 16 ); - if (g_render_state.current_ubo_offset == UINT32_MAX) { - gEngine.Con_Printf( S_ERROR "Ran out of uniform slots\n" ); - return; - } - - ubo = (uniform_data_t*)((byte*)g_render.uniform_buffer.mapped + g_render_state.current_ubo_offset); - memcpy(&g_render_state.current_uniform_data, &g_render_state.dirty_uniform_data, sizeof(g_render_state.dirty_uniform_data)); - memcpy(ubo, &g_render_state.current_uniform_data, sizeof(*ubo)); - g_render_state.uniform_data_set_mask |= UNIFORM_UPLOADED; + const uint32_t ubo_offset = getUboOffset_FIXME(); + if (ubo_offset == ALO_ALLOC_FAILED) { + // TODO stagger this + gEngine.Con_Printf( S_ERROR "Ran out of uniform slots\n" ); + return; } draw_command = drawCmdAlloc(); draw_command->draw.draw = *draw; - draw_command->draw.ubo_offset = g_render_state.current_ubo_offset; + draw_command->draw.ubo_offset = ubo_offset; draw_command->type = DrawDraw; Matrix3x4_Copy(draw_command->draw.transform, g_render_state.model); } @@ -658,6 +665,7 @@ void VK_RenderEnd( VkCommandBuffer cmdbuf ) ASSERT(!g_render_state.current_frame_is_ray_traced); + { const VkDeviceSize offset = 0; vkCmdBindVertexBuffers(cmdbuf, 0, 1, &g_geom.buffer.buffer, &offset); From b634d6251e2c8b8db6ec849bbb9078cfe82d8a80 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 26 Jun 2022 00:52:22 -0700 Subject: [PATCH 358/548] replace debuffer with flipping one where possible --- ref_vk/vk_overlay.c | 8 ++++---- ref_vk/vk_rtx.c | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ref_vk/vk_overlay.c b/ref_vk/vk_overlay.c index 89b34a03..3d1ac330 100644 --- a/ref_vk/vk_overlay.c +++ b/ref_vk/vk_overlay.c @@ -35,7 +35,7 @@ static struct { VkPipeline pipelines[kRenderTransAdd + 1]; vk_buffer_t pics_buffer; - r_debuffer_t pics_buffer_alloc; + r_flipping_buffer_t pics_buffer_alloc; qboolean exhausted_this_frame; batch_t batch[MAX_BATCHES]; @@ -45,7 +45,7 @@ static struct { } g2d; static vertex_2d_t* allocQuadVerts(int blending_mode, int texnum) { - const uint32_t pics_offset = R_DEBuffer_Alloc(&g2d.pics_buffer_alloc, LifetimeDynamic, 6, 1); + const uint32_t pics_offset = R_FlippingBuffer_Alloc(&g2d.pics_buffer_alloc, 6, 1); vertex_2d_t* const ptr = ((vertex_2d_t*)(g2d.pics_buffer.mapped)) + pics_offset; batch_t *batch = g2d.batch + (g2d.batch_count-1); @@ -125,7 +125,7 @@ static void drawFill( float x, float y, float w, float h, int r, int g, int b, i } static void clearAccumulated( void ) { - R_DEBuffer_Flip(&g2d.pics_buffer_alloc); + R_FlippingBuffer_Flip(&g2d.pics_buffer_alloc); g2d.batch_count = 1; g2d.batch[0].texture = -1; @@ -239,7 +239,7 @@ qboolean R_VkOverlay_Init( void ) { // FIXME cleanup return false; - R_DEBuffer_Init(&g2d.pics_buffer_alloc, 0, MAX_VERTICES); + R_FlippingBuffer_Init(&g2d.pics_buffer_alloc, MAX_VERTICES); return true; } diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 410853c5..c9591cf5 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -144,7 +144,7 @@ static struct { // Needs: SHADER_DEVICE_ADDRESS, STORAGE_BUFFER, AS_BUILD_INPUT_READ_ONLY vk_buffer_t tlas_geom_buffer; VkDeviceAddress tlas_geom_buffer_addr; - r_debuffer_t tlas_geom_buffer_alloc; + r_flipping_buffer_t tlas_geom_buffer_alloc; // Planned to contain seveal types of data: // - grid structure itself @@ -583,9 +583,9 @@ static void prepareTlas( VkCommandBuffer cmdbuf ) { ASSERT(g_ray_model_state.frame.num_models > 0); DEBUG_BEGIN(cmdbuf, "prepare tlas"); - R_DEBuffer_Flip( &g_rtx.tlas_geom_buffer_alloc ); + R_FlippingBuffer_Flip( &g_rtx.tlas_geom_buffer_alloc ); - const uint32_t instance_offset = R_DEBuffer_Alloc(&g_rtx.tlas_geom_buffer_alloc, LifetimeDynamic, g_ray_model_state.frame.num_models, 1); + const uint32_t instance_offset = R_FlippingBuffer_Alloc(&g_rtx.tlas_geom_buffer_alloc, g_ray_model_state.frame.num_models, 1); ASSERT(instance_offset != ALO_ALLOC_FAILED); // Upload all blas instances references to GPU mem @@ -1360,7 +1360,7 @@ qboolean VK_RayInit( void ) return false; } g_rtx.tlas_geom_buffer_addr = getBufferDeviceAddress(g_rtx.tlas_geom_buffer.buffer); - R_DEBuffer_Init(&g_rtx.tlas_geom_buffer_alloc, 0, MAX_ACCELS * 2); + R_FlippingBuffer_Init(&g_rtx.tlas_geom_buffer_alloc, MAX_ACCELS * 2); if (!VK_BufferCreate("ray kusochki_buffer", &g_ray_model_state.kusochki_buffer, sizeof(vk_kusok_data_t) * MAX_KUSOCHKI, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT /* | VK_BUFFER_USAGE_TRANSFER_DST_BIT */, From 0fcd05554ac114659fc3279007372c2a379ca8a4 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 26 Jun 2022 01:04:52 -0700 Subject: [PATCH 359/548] implement debuffer on top of flipping buffer --- ref_vk/vk_buffer.c | 68 ++++++++++++++++++++++------------------------ ref_vk/vk_buffer.h | 26 ++++++++---------- 2 files changed, 45 insertions(+), 49 deletions(-) diff --git a/ref_vk/vk_buffer.c b/ref_vk/vk_buffer.c index de0b5b01..cf0d9fca 100644 --- a/ref_vk/vk_buffer.c +++ b/ref_vk/vk_buffer.c @@ -90,41 +90,6 @@ VkDeviceAddress XVK_BufferGetDeviceAddress(VkBuffer buffer) { return vkGetBufferDeviceAddress(vk_core.device, &bdai); } -void R_DEBuffer_Init(r_debuffer_t *debuf, uint32_t static_size, uint32_t dynamic_size) { - aloRingInit(&debuf->static_ring, static_size); - aloRingInit(&debuf->dynamic_ring, dynamic_size); - debuf->static_size = static_size; - debuf->frame_dynamic_offset[0] = debuf->frame_dynamic_offset[1] = ALO_ALLOC_FAILED; -} - -uint32_t R_DEBuffer_Alloc(r_debuffer_t* debuf, r_lifetime_t lifetime, uint32_t size, uint32_t align) { - alo_ring_t * const ring = (lifetime == LifetimeStatic) ? &debuf->static_ring : &debuf->dynamic_ring; - const uint32_t alloc_offset = aloRingAlloc(ring, size, align); - const uint32_t offset = alloc_offset + ((lifetime == LifetimeDynamic) ? debuf->static_size : 0); - - if (alloc_offset == ALO_ALLOC_FAILED) { - /* gEngine.Con_Printf(S_ERROR "Cannot allocate %d %s bytes\n", */ - /* size, */ - /* lifetime == LifetimeDynamic ? "dynamic" : "static"); */ - return ALO_ALLOC_FAILED; - } - - // Store first dynamic allocation this frame - if (lifetime == LifetimeDynamic && debuf->frame_dynamic_offset[1] == ALO_ALLOC_FAILED) { - debuf->frame_dynamic_offset[1] = alloc_offset; - } - - return offset; -} - -void R_DEBuffer_Flip(r_debuffer_t* debuf) { - if (debuf->frame_dynamic_offset[0] != ALO_ALLOC_FAILED) - aloRingFree(&debuf->dynamic_ring, debuf->frame_dynamic_offset[0]); - - debuf->frame_dynamic_offset[0] = debuf->frame_dynamic_offset[1]; - debuf->frame_dynamic_offset[1] = ALO_ALLOC_FAILED; -} - void R_FlippingBuffer_Init(r_flipping_buffer_t *flibuf, uint32_t size) { aloRingInit(&flibuf->ring, size); R_FlippingBuffer_Clear(flibuf); @@ -154,3 +119,36 @@ void R_FlippingBuffer_Flip(r_flipping_buffer_t* flibuf) { flibuf->frame_offsets[1] = ALO_ALLOC_FAILED; } +void R_DEBuffer_Init(r_debuffer_t *debuf, uint32_t static_size, uint32_t dynamic_size) { + R_FlippingBuffer_Init(&debuf->dynamic, dynamic_size); + debuf->static_size = static_size; + debuf->static_offset = 0; +} + +uint32_t R_DEBuffer_Alloc(r_debuffer_t* debuf, r_lifetime_t lifetime, uint32_t size, uint32_t align) { + switch (lifetime) { + case LifetimeDynamic: + { + const uint32_t offset = R_FlippingBuffer_Alloc(&debuf->dynamic, size, align); + if (offset == ALO_ALLOC_FAILED) + return ALO_ALLOC_FAILED; + return offset + debuf->static_offset; + } + case LifetimeStatic: + { + const uint32_t offset = ALIGN_UP(debuf->static_offset, align); + const uint32_t end = offset + size; + if (end > debuf->static_size) + return ALO_ALLOC_FAILED; + + debuf->static_offset = end; + return offset; + } + } + + return ALO_ALLOC_FAILED; +} + +void R_DEBuffer_Flip(r_debuffer_t* debuf) { + R_FlippingBuffer_Flip(&debuf->dynamic); +} diff --git a/ref_vk/vk_buffer.h b/ref_vk/vk_buffer.h index 0f3c222b..f6538479 100644 --- a/ref_vk/vk_buffer.h +++ b/ref_vk/vk_buffer.h @@ -47,11 +47,20 @@ void VK_RingBuffer_ClearFrame(vk_ring_buffer_t* buf); typedef struct { - alo_ring_t static_ring; - alo_ring_t dynamic_ring; + alo_ring_t ring; + uint32_t frame_offsets[2]; +} r_flipping_buffer_t; +void R_FlippingBuffer_Init(r_flipping_buffer_t *flibuf, uint32_t size); +void R_FlippingBuffer_Clear(r_flipping_buffer_t *flibuf); +uint32_t R_FlippingBuffer_Alloc(r_flipping_buffer_t* flibuf, uint32_t size, uint32_t align); +void R_FlippingBuffer_Flip(r_flipping_buffer_t* flibuf); + + +typedef struct { + r_flipping_buffer_t dynamic; uint32_t static_size; - uint32_t frame_dynamic_offset[2]; + uint32_t static_offset; } r_debuffer_t; typedef enum { @@ -61,14 +70,3 @@ typedef enum { void R_DEBuffer_Init(r_debuffer_t *debuf, uint32_t static_size, uint32_t dynamic_size); uint32_t R_DEBuffer_Alloc(r_debuffer_t* debuf, r_lifetime_t lifetime, uint32_t size, uint32_t align); void R_DEBuffer_Flip(r_debuffer_t* debuf); - - -typedef struct { - alo_ring_t ring; - uint32_t frame_offsets[2]; -} r_flipping_buffer_t; - -void R_FlippingBuffer_Init(r_flipping_buffer_t *flibuf, uint32_t size); -void R_FlippingBuffer_Clear(r_flipping_buffer_t *flibuf); -uint32_t R_FlippingBuffer_Alloc(r_flipping_buffer_t* flibuf, uint32_t size, uint32_t align); -void R_FlippingBuffer_Flip(r_flipping_buffer_t* flibuf); From 23ad2096a918f6fd4a92ba26aba938d801ff07e4 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 9 Jul 2022 11:40:26 -0700 Subject: [PATCH 360/548] rt: remove old single-pipeline raytracer --- ref_vk/vk_rtx.c | 534 +----------------------------------------------- 1 file changed, 5 insertions(+), 529 deletions(-) diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index c9591cf5..b4a2795e 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -27,30 +27,16 @@ #define MAX_SCRATCH_BUFFER (32*1024*1024) #define MAX_ACCELS_BUFFER (64*1024*1024) -// TODO actually use it #define MAX_FRAMES_IN_FLIGHT 2 -enum { - ShaderBindingTable_RayGen, - - ShaderBindingTable_Miss, - ShaderBindingTable_Miss_Shadow, - ShaderBindingTable_Miss_Empty, - - ShaderBindingTable_Hit_Base, - ShaderBindingTable_Hit_WithAlphaTest, - ShaderBindingTable_Hit_Additive, - - ShaderBindingTable_Hit_Shadow_Base, - ShaderBindingTable_Hit_Shadow_AlphaTest, - ShaderBindingTable_Hit__END = ShaderBindingTable_Hit_Shadow_AlphaTest, - - ShaderBindingTable_COUNT -}; - // TODO settings/realtime modifiable/adaptive +#if 1 #define FRAME_WIDTH 1280 #define FRAME_HEIGHT 720 +#else +#define FRAME_WIDTH 2560 +#define FRAME_HEIGHT 1440 +#endif // TODO sync with shaders // TODO optimal values @@ -71,29 +57,6 @@ typedef struct { struct LightCluster cells[MAX_LIGHT_CLUSTERS]; } vk_ray_shader_light_grid; -enum { - RayDescBinding_Dest_ImageBaseColor = 0, - RayDescBinding_TLAS = 1, - RayDescBinding_UBOMatrices = 2, - - RayDescBinding_Kusochki = 3, - RayDescBinding_Indices = 4, - RayDescBinding_Vertices = 5, - RayDescBinding_Textures = 6, - - RayDescBinding_Lights = 7, - RayDescBinding_LightClusters = 8, - - RayDescBinding_Dest_ImageDiffuseGI = 9, - RayDescBinding_Dest_ImageSpecular = 10, - RayDescBinding_Dest_ImageAdditive = 11, - RayDescBinding_Dest_ImageNormals = 12, - - RayDescBinding_SkyboxCube = 13, - - RayDescBinding_COUNT -}; - typedef struct { xvk_image_t denoised; @@ -109,21 +72,10 @@ RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) } xvk_ray_frame_images_t; static struct { - vk_descriptors_t descriptors; - VkDescriptorSetLayoutBinding desc_bindings[RayDescBinding_COUNT]; - vk_descriptor_value_t desc_values[RayDescBinding_COUNT]; - VkDescriptorSet desc_sets[1]; - - VkPipeline pipeline; - // Holds UniformBuffer data vk_buffer_t uniform_buffer; uint32_t uniform_unit_size; - // Shader binding table buffer - vk_buffer_t sbt_buffer; - uint32_t sbt_record_size; - // Stores AS built data. Lifetime similar to render buffer: // - some portion lives for entire map lifetime // - some portion lives only for a single frame (may have several frames in flight) @@ -331,7 +283,6 @@ void VK_RayNewMap( void ) { ASSERT(vk_core.rtx); VK_RingBuffer_Clear(&g_rtx.accels_buffer_alloc); -// VK_RingBuffer_Clear(&g_ray_model_state.kusochki_alloc); // Clear model cache for (int i = 0; i < ARRAYSIZE(g_ray_model_state.models_cache); ++i) { @@ -355,7 +306,6 @@ void VK_RayNewMap( void ) { void VK_RayMapLoadEnd( void ) { VK_RingBuffer_Fix(&g_rtx.accels_buffer_alloc); - //VK_RingBuffer_Fix(&g_ray_model_state.kusochki_alloc); } void VK_RayFrameBegin( void ) @@ -379,206 +329,6 @@ void VK_RayFrameBegin( void ) RT_LightsFrameInit(); } -static void createPipeline( void ) -{ - return; // ... FIXME - struct RayShaderSpec { - int max_point_lights; - int max_emissive_kusochki; - uint32_t max_visible_point_lights; - uint32_t max_visible_surface_lights; - float light_grid_cell_size; - int max_light_clusters; - uint32_t max_textures; - uint32_t sbt_record_size; - } spec_data = { - .max_point_lights = MAX_POINT_LIGHTS, - .max_emissive_kusochki = MAX_EMISSIVE_KUSOCHKI, - .max_visible_point_lights = MAX_VISIBLE_POINT_LIGHTS, - .max_visible_surface_lights = MAX_VISIBLE_SURFACE_LIGHTS, - .light_grid_cell_size = LIGHT_GRID_CELL_SIZE, - .max_light_clusters = MAX_LIGHT_CLUSTERS, - .max_textures = MAX_TEXTURES, - .sbt_record_size = g_rtx.sbt_record_size, - }; - const VkSpecializationMapEntry spec_map[] = { - {.constantID = 0, .offset = offsetof(struct RayShaderSpec, max_point_lights), .size = sizeof(int) }, - {.constantID = 1, .offset = offsetof(struct RayShaderSpec, max_emissive_kusochki), .size = sizeof(int) }, - {.constantID = 2, .offset = offsetof(struct RayShaderSpec, max_visible_point_lights), .size = sizeof(uint32_t) }, - {.constantID = 3, .offset = offsetof(struct RayShaderSpec, max_visible_surface_lights), .size = sizeof(uint32_t) }, - {.constantID = 4, .offset = offsetof(struct RayShaderSpec, light_grid_cell_size), .size = sizeof(float) }, - {.constantID = 5, .offset = offsetof(struct RayShaderSpec, max_light_clusters), .size = sizeof(int) }, - {.constantID = 6, .offset = offsetof(struct RayShaderSpec, max_textures), .size = sizeof(uint32_t) }, - {.constantID = 7, .offset = offsetof(struct RayShaderSpec, sbt_record_size), .size = sizeof(uint32_t) }, - }; - - VkSpecializationInfo spec = { - .mapEntryCount = ARRAYSIZE(spec_map), - .pMapEntries = spec_map, - .dataSize = sizeof(spec_data), - .pData = &spec_data, - }; - - enum { - ShaderStageIndex_RayGen, - ShaderStageIndex_Miss, - ShaderStageIndex_Miss_Shadow, - ShaderStageIndex_Miss_Empty, - ShaderStageIndex_ClosestHit, - ShaderStageIndex_ClosestHit_Shadow, - ShaderStageIndex_AnyHit_AlphaTest, - ShaderStageIndex_AnyHit_Additive, - ShaderStageIndex_COUNT, - }; - -#define DEFINE_SHADER(filename, bit, sbt_index) \ - shaders[sbt_index] = (VkPipelineShaderStageCreateInfo){ \ - .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, \ - .stage = VK_SHADER_STAGE_##bit##_BIT_KHR, \ - .module = loadShader(filename), \ - .pName = "main", \ - .pSpecializationInfo = &spec, \ - } - - VkPipelineShaderStageCreateInfo shaders[ShaderStageIndex_COUNT]; - VkRayTracingShaderGroupCreateInfoKHR shader_groups[ShaderBindingTable_COUNT]; - - const VkRayTracingPipelineCreateInfoKHR rtpci = { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR, - //TODO .flags = VK_PIPELINE_CREATE_RAY_TRACING_NO_NULL_ANY_HIT_SHADERS_BIT_KHR .... - .stageCount = ARRAYSIZE(shaders), - .pStages = shaders, - .groupCount = ARRAYSIZE(shader_groups), - .pGroups = shader_groups, - .maxPipelineRayRecursionDepth = 1, - .layout = g_rtx.descriptors.pipeline_layout, - }; - - DEFINE_SHADER("ray.rgen.spv", RAYGEN, ShaderStageIndex_RayGen); - DEFINE_SHADER("ray.rmiss.spv", MISS, ShaderStageIndex_Miss); - DEFINE_SHADER("shadow.rmiss.spv", MISS, ShaderStageIndex_Miss_Shadow); - DEFINE_SHADER("empty.rmiss.spv", MISS, ShaderStageIndex_Miss_Empty); - DEFINE_SHADER("ray.rchit.spv", CLOSEST_HIT, ShaderStageIndex_ClosestHit); - DEFINE_SHADER("shadow.rchit.spv", CLOSEST_HIT, ShaderStageIndex_ClosestHit_Shadow); - DEFINE_SHADER("alphamask.rahit.spv", ANY_HIT, ShaderStageIndex_AnyHit_AlphaTest); - DEFINE_SHADER("additive.rahit.spv", ANY_HIT, ShaderStageIndex_AnyHit_Additive); - - // TODO static assert -#define ASSERT_SHADER_OFFSET(sbt_kind, sbt_index, offset) \ - ASSERT((offset) == (sbt_index - sbt_kind)) - - ASSERT_SHADER_OFFSET(ShaderBindingTable_RayGen, ShaderBindingTable_RayGen, 0); - ASSERT_SHADER_OFFSET(ShaderBindingTable_Miss, ShaderBindingTable_Miss, SHADER_OFFSET_MISS_REGULAR); - ASSERT_SHADER_OFFSET(ShaderBindingTable_Miss, ShaderBindingTable_Miss_Shadow, SHADER_OFFSET_MISS_SHADOW); - - ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_Base, SHADER_OFFSET_HIT_REGULAR_BASE + SHADER_OFFSET_HIT_REGULAR); - ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_WithAlphaTest, SHADER_OFFSET_HIT_REGULAR_BASE + SHADER_OFFSET_HIT_ALPHA_TEST); - ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_Additive, SHADER_OFFSET_HIT_REGULAR_BASE + SHADER_OFFSET_HIT_ADDITIVE); - - ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_Shadow_Base, SHADER_OFFSET_HIT_SHADOW_BASE + 0); - ASSERT_SHADER_OFFSET(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit_Shadow_AlphaTest, SHADER_OFFSET_HIT_SHADOW_BASE + SHADER_OFFSET_HIT_ALPHA_TEST); - - shader_groups[ShaderBindingTable_RayGen] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR, - .anyHitShader = VK_SHADER_UNUSED_KHR, - .closestHitShader = VK_SHADER_UNUSED_KHR, - .generalShader = ShaderStageIndex_RayGen, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - - shader_groups[ShaderBindingTable_Miss] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR, - .anyHitShader = VK_SHADER_UNUSED_KHR, - .closestHitShader = VK_SHADER_UNUSED_KHR, - .generalShader = ShaderStageIndex_Miss, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - - shader_groups[ShaderBindingTable_Miss_Shadow] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR, - .anyHitShader = VK_SHADER_UNUSED_KHR, - .closestHitShader = VK_SHADER_UNUSED_KHR, - .generalShader = ShaderStageIndex_Miss_Shadow, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - - shader_groups[ShaderBindingTable_Miss_Empty] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR, - .anyHitShader = VK_SHADER_UNUSED_KHR, - .closestHitShader = VK_SHADER_UNUSED_KHR, - .generalShader = ShaderBindingTable_Miss_Empty, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - - shader_groups[ShaderBindingTable_Hit_Base] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR, - .anyHitShader = VK_SHADER_UNUSED_KHR, - .closestHitShader = ShaderStageIndex_ClosestHit, - .generalShader = VK_SHADER_UNUSED_KHR, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - - shader_groups[ShaderBindingTable_Hit_WithAlphaTest] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR, - .anyHitShader = ShaderStageIndex_AnyHit_AlphaTest, - .closestHitShader = ShaderStageIndex_ClosestHit, - .generalShader = VK_SHADER_UNUSED_KHR, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - - shader_groups[ShaderBindingTable_Hit_Additive] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR, - .anyHitShader = ShaderStageIndex_AnyHit_Additive, - .closestHitShader = VK_SHADER_UNUSED_KHR, - .generalShader = VK_SHADER_UNUSED_KHR, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - - shader_groups[ShaderBindingTable_Hit_Shadow_Base] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR, - .anyHitShader = VK_SHADER_UNUSED_KHR, - .closestHitShader = ShaderStageIndex_ClosestHit_Shadow, - .generalShader = VK_SHADER_UNUSED_KHR, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - - shader_groups[ShaderBindingTable_Hit_Shadow_AlphaTest] = (VkRayTracingShaderGroupCreateInfoKHR) { - .sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, - .type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR, - .anyHitShader = ShaderStageIndex_AnyHit_AlphaTest, - .closestHitShader = ShaderStageIndex_ClosestHit_Shadow, - .generalShader = VK_SHADER_UNUSED_KHR, - .intersectionShader = VK_SHADER_UNUSED_KHR, - }; - - XVK_CHECK(vkCreateRayTracingPipelinesKHR(vk_core.device, VK_NULL_HANDLE, g_pipeline_cache, 1, &rtpci, NULL, &g_rtx.pipeline)); - ASSERT(g_rtx.pipeline != VK_NULL_HANDLE); - - { - const uint32_t sbt_handle_size = vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupHandleSize; - const uint32_t sbt_handles_buffer_size = ARRAYSIZE(shader_groups) * sbt_handle_size; - uint8_t *sbt_handles = Mem_Malloc(vk_core.pool, sbt_handles_buffer_size); - XVK_CHECK(vkGetRayTracingShaderGroupHandlesKHR(vk_core.device, g_rtx.pipeline, 0, ARRAYSIZE(shader_groups), sbt_handles_buffer_size, sbt_handles)); - for (int i = 0; i < ARRAYSIZE(shader_groups); ++i) - { - uint8_t *sbt_dst = g_rtx.sbt_buffer.mapped; - memcpy(sbt_dst + g_rtx.sbt_record_size * i, sbt_handles + sbt_handle_size * i, sbt_handle_size); - } - Mem_Free(sbt_handles); - } - - for (int i = 0; i < ARRAYSIZE(shaders); ++i) - vkDestroyShaderModule(vk_core.device, shaders[i].module, NULL); -} - static void prepareTlas( VkCommandBuffer cmdbuf ) { ASSERT(g_ray_model_state.frame.num_models > 0); DEBUG_BEGIN(cmdbuf, "prepare tlas"); @@ -648,140 +398,6 @@ static void prepareTlas( VkCommandBuffer cmdbuf ) { DEBUG_END(cmdbuf); } -static void updateDescriptors( const vk_ray_frame_render_args_t *args, int frame_index, const xvk_ray_frame_images_t *frame_dst ) { - // 3. Update descriptor sets (bind dest image, tlas, projection matrix) - VkDescriptorImageInfo dii_all_textures[MAX_TEXTURES]; - - /* g_rtx.desc_values[RayDescBinding_Dest_ImageBaseColor].image = (VkDescriptorImageInfo){ */ - /* .sampler = VK_NULL_HANDLE, */ - /* .imageView = frame_dst->base_color.view, */ - /* .imageLayout = VK_IMAGE_LAYOUT_GENERAL, */ - /* }; */ - - g_rtx.desc_values[RayDescBinding_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){ - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, - .accelerationStructureCount = 1, - .pAccelerationStructures = &g_rtx.tlas, - }; - - g_rtx.desc_values[RayDescBinding_UBOMatrices].buffer = (VkDescriptorBufferInfo){ - .buffer = g_rtx.uniform_buffer.buffer, - .offset = frame_index * g_rtx.uniform_unit_size, - .range = sizeof(struct UniformBuffer), - }; - - g_rtx.desc_values[RayDescBinding_Kusochki].buffer = (VkDescriptorBufferInfo){ - .buffer = g_ray_model_state.kusochki_buffer.buffer, - .offset = 0, - .range = VK_WHOLE_SIZE, // TODO fails validation when empty g_rtx_scene.num_models * sizeof(vk_kusok_data_t), - }; - - g_rtx.desc_values[RayDescBinding_Indices].buffer = (VkDescriptorBufferInfo){ - .buffer = args->geometry_data.buffer, - .offset = 0, - .range = VK_WHOLE_SIZE, // TODO fails validation when empty args->geometry_data.size, - }; - - g_rtx.desc_values[RayDescBinding_Vertices].buffer = (VkDescriptorBufferInfo){ - .buffer = args->geometry_data.buffer, - .offset = 0, - .range = VK_WHOLE_SIZE, // TODO fails validation when empty args->geometry_data.size, - }; - - g_rtx.desc_values[RayDescBinding_Textures].image_array = dii_all_textures; - - g_rtx.desc_values[RayDescBinding_SkyboxCube].image = (VkDescriptorImageInfo){ - .sampler = vk_core.default_sampler, - .imageView = tglob.skybox_cube.vk.image.view ? tglob.skybox_cube.vk.image.view : tglob.cubemap_placeholder.vk.image.view, - .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - }; - - // TODO: move this to vk_texture.c - for (int i = 0; i < MAX_TEXTURES; ++i) { - const vk_texture_t *texture = findTexture(i); - const qboolean exists = texture->vk.image.view != VK_NULL_HANDLE; - dii_all_textures[i].sampler = vk_core.default_sampler; // FIXME on AMD using pImmutableSamplers leads to NEAREST filtering ??. VK_NULL_HANDLE; - dii_all_textures[i].imageView = exists ? texture->vk.image.view : findTexture(tglob.defaultTexture)->vk.image.view; - ASSERT(dii_all_textures[i].imageView != VK_NULL_HANDLE); - dii_all_textures[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - } - - g_rtx.desc_values[RayDescBinding_Lights].buffer = (VkDescriptorBufferInfo){ - .buffer = g_ray_model_state.lights_buffer.buffer, - .offset = 0, - .range = VK_WHOLE_SIZE, - }; - - g_rtx.desc_values[RayDescBinding_LightClusters].buffer = (VkDescriptorBufferInfo){ - .buffer = g_rtx.light_grid_buffer.buffer, - .offset = 0, - .range = VK_WHOLE_SIZE, - }; - - g_rtx.desc_values[RayDescBinding_Dest_ImageDiffuseGI].image = (VkDescriptorImageInfo){ - .sampler = VK_NULL_HANDLE, - .imageView = frame_dst->diffuse_gi.view, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; - - g_rtx.desc_values[RayDescBinding_Dest_ImageSpecular].image = (VkDescriptorImageInfo){ - .sampler = VK_NULL_HANDLE, - .imageView = frame_dst->specular.view, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; - - g_rtx.desc_values[RayDescBinding_Dest_ImageAdditive].image = (VkDescriptorImageInfo){ - .sampler = VK_NULL_HANDLE, - .imageView = frame_dst->additive.view, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; - - g_rtx.desc_values[RayDescBinding_Dest_ImageNormals].image = (VkDescriptorImageInfo){ - .sampler = VK_NULL_HANDLE, - //.imageView = frame_dst->normals.view, - .imageLayout = VK_IMAGE_LAYOUT_GENERAL, - }; - - VK_DescriptorsWrite(&g_rtx.descriptors, 0); -} - -static qboolean rayTrace( VkCommandBuffer cmdbuf, const xvk_ray_frame_images_t *current_frame, float fov_angle_y ) { - - // 4. dispatch ray tracing - vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_rtx.pipeline); - { - vk_rtx_push_constants_t push_constants = { - .time = gpGlobals->time, - .random_seed = (uint32_t)gEngine.COM_RandomLong(0, INT32_MAX), - .bounces = vk_rtx_bounces->value, - .pixel_cone_spread_angle = atanf((2.0f*tanf(DEG2RAD(fov_angle_y) * 0.5f)) / (float)FRAME_HEIGHT), - .debug_light_index_begin = (uint32_t)(vk_rtx_light_begin->value), - .debug_light_index_end = (uint32_t)(vk_rtx_light_end->value), - .flags = r_lightmap->value ? PUSH_FLAG_LIGHTMAP_ONLY : 0, - }; - vkCmdPushConstants(cmdbuf, g_rtx.descriptors.pipeline_layout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, 0, sizeof(push_constants), &push_constants); - } - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_rtx.descriptors.pipeline_layout, 0, 1, g_rtx.descriptors.desc_sets + 0, 0, NULL); - - { - const uint32_t sbt_record_size = g_rtx.sbt_record_size; - //const uint32_t sbt_record_size = vk_core.physical_device.properties_ray_tracing_pipeline.shaderGroupHandleSize; -#define SBT_INDEX(index, count) { \ -.deviceAddress = getBufferDeviceAddress(g_rtx.sbt_buffer.buffer) + g_rtx.sbt_record_size * index, \ -.size = sbt_record_size * (count), \ -.stride = sbt_record_size, \ -} - const VkStridedDeviceAddressRegionKHR sbt_raygen = SBT_INDEX(ShaderBindingTable_RayGen, 1); - const VkStridedDeviceAddressRegionKHR sbt_miss = SBT_INDEX(ShaderBindingTable_Miss, ShaderBindingTable_Miss_Empty - ShaderBindingTable_Miss); - const VkStridedDeviceAddressRegionKHR sbt_hit = SBT_INDEX(ShaderBindingTable_Hit_Base, ShaderBindingTable_Hit__END - ShaderBindingTable_Hit_Base); - const VkStridedDeviceAddressRegionKHR sbt_callable = { 0 }; - - vkCmdTraceRaysKHR(cmdbuf, &sbt_raygen, &sbt_miss, &sbt_hit, &sbt_callable, FRAME_WIDTH, FRAME_HEIGHT, 1 ); - } - - return true; -} - // Finalize and update dynamic lights static void uploadLights( void ) { // Upload light grid @@ -1057,7 +673,6 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar uploadLights(); prepareTlas(cmdbuf); prepareUniformBuffer(args, frame_index, fov_angle_y); - //updateDescriptors(args, frame_index, current_frame); // 4. Barrier for TLAS build and dest image layout transfer { @@ -1129,9 +744,6 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) if (g_rtx.reload_pipeline) { gEngine.Con_Printf(S_WARN "Reloading RTX shaders/pipelines\n"); - // TODO gracefully handle reload errors: need to change createPipeline, loadShader, VK_PipelineCreate... - //vkDestroyPipeline(vk_core.device, g_rtx.pipeline, NULL); - createPipeline(); reloadPass( &g_rtx.pass.primary_ray, R_VkRayPrimaryPassCreate()); reloadPass( &g_rtx.pass.light_direct_poly, R_VkRayLightDirectPolyPassCreate()); @@ -1168,126 +780,6 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) } } -static void createLayouts( void ) { - //VkSampler samplers[MAX_TEXTURES]; - - g_rtx.descriptors.bindings = g_rtx.desc_bindings; - g_rtx.descriptors.num_bindings = ARRAYSIZE(g_rtx.desc_bindings); - g_rtx.descriptors.values = g_rtx.desc_values; - g_rtx.descriptors.num_sets = 1; - g_rtx.descriptors.desc_sets = g_rtx.desc_sets; - g_rtx.descriptors.push_constants = (VkPushConstantRange){ - .offset = 0, - .size = sizeof(vk_rtx_push_constants_t), - .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, - }; - - g_rtx.desc_bindings[RayDescBinding_Dest_ImageDiffuseGI] = (VkDescriptorSetLayoutBinding){ - .binding = RayDescBinding_Dest_ImageDiffuseGI, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR, - }; - g_rtx.desc_bindings[RayDescBinding_Dest_ImageAdditive] = (VkDescriptorSetLayoutBinding){ - .binding = RayDescBinding_Dest_ImageAdditive, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR, - }; - g_rtx.desc_bindings[RayDescBinding_Dest_ImageSpecular] = (VkDescriptorSetLayoutBinding){ - .binding = RayDescBinding_Dest_ImageSpecular, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR, - }; - g_rtx.desc_bindings[RayDescBinding_Dest_ImageNormals] = (VkDescriptorSetLayoutBinding){ - .binding = RayDescBinding_Dest_ImageNormals, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR, - }; - - g_rtx.desc_bindings[RayDescBinding_Dest_ImageBaseColor] = (VkDescriptorSetLayoutBinding){ - .binding = RayDescBinding_Dest_ImageBaseColor, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR, - }; - - g_rtx.desc_bindings[RayDescBinding_TLAS] = (VkDescriptorSetLayoutBinding){ - .binding = RayDescBinding_TLAS, - .descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR, - }; - - g_rtx.desc_bindings[RayDescBinding_UBOMatrices] = (VkDescriptorSetLayoutBinding){ - .binding = RayDescBinding_UBOMatrices, - .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR, - }; - - g_rtx.desc_bindings[RayDescBinding_Kusochki] = (VkDescriptorSetLayoutBinding){ - .binding = RayDescBinding_Kusochki, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, - }; - - g_rtx.desc_bindings[RayDescBinding_Indices] = (VkDescriptorSetLayoutBinding){ - .binding = RayDescBinding_Indices, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, - }; - - g_rtx.desc_bindings[RayDescBinding_Vertices] = (VkDescriptorSetLayoutBinding){ - .binding = RayDescBinding_Vertices, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, - }; - - g_rtx.desc_bindings[RayDescBinding_Textures] = (VkDescriptorSetLayoutBinding){ - .binding = RayDescBinding_Textures, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = MAX_TEXTURES, - .stageFlags = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR | VK_SHADER_STAGE_RAYGEN_BIT_KHR, - // FIXME on AMD using immutable samplers leads to nearest filtering ???! - .pImmutableSamplers = NULL, //samplers, - }; - - g_rtx.desc_bindings[RayDescBinding_SkyboxCube] = (VkDescriptorSetLayoutBinding){ - .binding = RayDescBinding_SkyboxCube, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - // FIXME on AMD using immutable samplers leads to nearest filtering ???! - .pImmutableSamplers = NULL, //samplers, - }; - - - // for (int i = 0; i < ARRAYSIZE(samplers); ++i) - // samplers[i] = vk_core.default_sampler; - - g_rtx.desc_bindings[RayDescBinding_Lights] = (VkDescriptorSetLayoutBinding){ - .binding = RayDescBinding_Lights, - .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - }; - - g_rtx.desc_bindings[RayDescBinding_LightClusters] = (VkDescriptorSetLayoutBinding){ - .binding = RayDescBinding_LightClusters, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR, - }; - - VK_DescriptorsCreate(&g_rtx.descriptors); -} - static void reloadPipeline( void ) { g_rtx.reload_pipeline = true; } @@ -1317,7 +809,6 @@ qboolean VK_RayInit( void ) g_rtx.pass.denoiser = R_VkRayDenoiserCreate(); ASSERT(g_rtx.pass.denoiser); - g_rtx.sbt_record_size = vk_core.physical_device.sbt_record_size; g_rtx.uniform_unit_size = ALIGN_UP(sizeof(struct UniformBuffer), vk_core.physical_device.properties.limits.minUniformBufferOffsetAlignment); if (!VK_BufferCreate("ray uniform_buffer", &g_rtx.uniform_buffer, g_rtx.uniform_unit_size * MAX_FRAMES_IN_FLIGHT, @@ -1327,13 +818,6 @@ qboolean VK_RayInit( void ) return false; } - if (!VK_BufferCreate("ray sbt_buffer", &g_rtx.sbt_buffer, ShaderBindingTable_COUNT * g_rtx.sbt_record_size, - VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) - { - return false; - } - if (!VK_BufferCreate("ray accels_buffer", &g_rtx.accels_buffer, MAX_ACCELS_BUFFER, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT @@ -1369,7 +853,6 @@ qboolean VK_RayInit( void ) return false; } RT_RayModel_Clear(); - //g_ray_model_state.kusochki_alloc.size = MAX_KUSOCHKI; if (!VK_BufferCreate("ray lights_buffer", &g_ray_model_state.lights_buffer, sizeof(struct Lights), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT /* | VK_BUFFER_USAGE_TRANSFER_DST_BIT */, @@ -1385,9 +868,6 @@ qboolean VK_RayInit( void ) return false; } - createLayouts(); - createPipeline(); - for (int i = 0; i < ARRAYSIZE(g_rtx.frames); ++i) { #define CREATE_GBUFFER_IMAGE(name, format_, add_usage_bits) \ do { \ @@ -1456,9 +936,6 @@ void VK_RayShutdown( void ) { XVK_ImageDestroy(&g_rtx.frames[i].additive); } - //vkDestroyPipeline(vk_core.device, g_rtx.pipeline, NULL); - VK_DescriptorsDestroy(&g_rtx.descriptors); - if (g_rtx.tlas != VK_NULL_HANDLE) vkDestroyAccelerationStructureKHR(vk_core.device, g_rtx.tlas, NULL); @@ -1475,6 +952,5 @@ void VK_RayShutdown( void ) { VK_BufferDestroy(&g_ray_model_state.kusochki_buffer); VK_BufferDestroy(&g_ray_model_state.lights_buffer); VK_BufferDestroy(&g_rtx.light_grid_buffer); - VK_BufferDestroy(&g_rtx.sbt_buffer); VK_BufferDestroy(&g_rtx.uniform_buffer); } From da97f664d7e14988a5002335fc29ddbe6e115e08 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 9 Jul 2022 11:59:48 -0700 Subject: [PATCH 361/548] vk: recreate swapchain on exclusive mode lost, fix #365 --- ref_vk/vk_swapchain.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ref_vk/vk_swapchain.c b/ref_vk/vk_swapchain.c index 16444609..2e421583 100644 --- a/ref_vk/vk_swapchain.c +++ b/ref_vk/vk_swapchain.c @@ -202,6 +202,7 @@ r_vk_swapchain_framebuffer_t R_VkSwapchainAcquire( VkSemaphore sem_image_availa switch (acquire_result) { case VK_ERROR_OUT_OF_DATE_KHR: case VK_ERROR_SURFACE_LOST_KHR: + case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: gEngine.Con_Printf(S_WARN "vkAcquireNextImageKHR returned %s, recreating swapchain\n", R_VkResultName(acquire_result)); if (i == 0) { force_recreate = true; From a2b083300cc63b50417ba045dc56f13872446f2f Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 9 Jul 2022 12:31:46 -0700 Subject: [PATCH 362/548] vk: move geometry buffer handling to separate files --- ref_vk/vk_beams.c | 1 + ref_vk/vk_brush.c | 1 + ref_vk/vk_core.c | 5 ++ ref_vk/vk_geometry.c | 129 +++++++++++++++++++++++++++++++++++++++ ref_vk/vk_geometry.h | 65 ++++++++++++++++++++ ref_vk/vk_ray_model.c | 1 + ref_vk/vk_render.c | 139 +++++------------------------------------- ref_vk/vk_render.h | 51 ---------------- ref_vk/vk_sprite.c | 1 + ref_vk/vk_studio.c | 1 + 10 files changed, 220 insertions(+), 174 deletions(-) create mode 100644 ref_vk/vk_geometry.c create mode 100644 ref_vk/vk_geometry.h diff --git a/ref_vk/vk_beams.c b/ref_vk/vk_beams.c index 1072e78b..81440c05 100644 --- a/ref_vk/vk_beams.c +++ b/ref_vk/vk_beams.c @@ -2,6 +2,7 @@ #include "vk_common.h" #include "camera.h" #include "vk_render.h" +#include "vk_geometry.h" #include "vk_textures.h" #include "vk_sprite.h" #include "vk_scene.h" diff --git a/ref_vk/vk_brush.c b/ref_vk/vk_brush.c index d5bf3366..7f24f346 100644 --- a/ref_vk/vk_brush.c +++ b/ref_vk/vk_brush.c @@ -10,6 +10,7 @@ #include "vk_lightmap.h" #include "vk_scene.h" #include "vk_render.h" +#include "vk_geometry.h" #include "vk_light.h" #include "vk_mapents.h" diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index accb363d..00e11d85 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -11,6 +11,7 @@ #include "vk_cvar.h" #include "vk_pipeline.h" #include "vk_render.h" +#include "vk_geometry.h" #include "vk_studio.h" #include "vk_rtx.h" #include "vk_descriptor.h" @@ -740,6 +741,9 @@ qboolean R_VkInit( void ) if (!VK_FrameCtlInit()) return false; + if (!R_GeometryBuffer_Init()) + return false; + if (!VK_RenderInit()) return false; @@ -783,6 +787,7 @@ void R_VkShutdown( void ) { R_VkOverlay_Shutdown(); VK_RenderShutdown(); + R_GeometryBuffer_Shutdown(); VK_FrameCtlShutdown(); diff --git a/ref_vk/vk_geometry.c b/ref_vk/vk_geometry.c new file mode 100644 index 00000000..a7250746 --- /dev/null +++ b/ref_vk/vk_geometry.c @@ -0,0 +1,129 @@ +#include "vk_geometry.h" +#include "vk_buffer.h" +#include "vk_staging.h" +#include "vk_framectl.h" // MAX_CONCURRENT_FRAMES + +#define MAX_BUFFER_VERTICES_STATIC (128 * 1024) +#define MAX_BUFFER_INDICES_STATIC (MAX_BUFFER_VERTICES_STATIC * 3) +#define GEOMETRY_BUFFER_STATIC_SIZE ALIGN_UP(MAX_BUFFER_VERTICES_STATIC * sizeof(vk_vertex_t) + MAX_BUFFER_INDICES_STATIC * sizeof(uint16_t), sizeof(vk_vertex_t)) + +#define MAX_BUFFER_VERTICES_DYNAMIC (128 * 1024 * 2) +#define MAX_BUFFER_INDICES_DYNAMIC (MAX_BUFFER_VERTICES_DYNAMIC * 3) +#define GEOMETRY_BUFFER_DYNAMIC_SIZE ALIGN_UP(MAX_BUFFER_VERTICES_DYNAMIC * sizeof(vk_vertex_t) + MAX_BUFFER_INDICES_DYNAMIC * sizeof(uint16_t), sizeof(vk_vertex_t)) + +#define GEOMETRY_BUFFER_SIZE (GEOMETRY_BUFFER_STATIC_SIZE + GEOMETRY_BUFFER_DYNAMIC_SIZE) + +static struct { + vk_buffer_t buffer; + alo_ring_t static_ring; + alo_ring_t dynamic_ring; + + int frame_index; + uint32_t dynamic_offsets[MAX_CONCURRENT_FRAMES]; +} g_geom; + +qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int vertex_count, int index_count, r_geometry_lifetime_t lifetime ) { + const uint32_t vertices_size = vertex_count * sizeof(vk_vertex_t); + const uint32_t indices_size = index_count * sizeof(uint16_t); + const uint32_t total_size = vertices_size + indices_size; + alo_ring_t * const ring = (lifetime != LifetimeSingleFrame) ? &g_geom.static_ring : &g_geom.dynamic_ring; + + const uint32_t alloc_offset = aloRingAlloc(ring, total_size, sizeof(vk_vertex_t)); + const uint32_t offset = alloc_offset + ((lifetime == LifetimeSingleFrame) ? GEOMETRY_BUFFER_STATIC_SIZE : 0); + if (alloc_offset == ALO_ALLOC_FAILED) { + /* gEngine.Con_Printf(S_ERROR "Cannot allocate %s geometry buffer for %d vertices (%d bytes) and %d indices (%d bytes)\n", */ + /* lifetime == LifetimeSingleFrame ? "dynamic" : "static", */ + /* vertex_count, vertices_size, index_count, indices_size); */ + return false; + } + + // Store first dynamic allocation this frame + if (lifetime == LifetimeSingleFrame && g_geom.dynamic_offsets[g_geom.frame_index] == ALO_ALLOC_FAILED) { + //gEngine.Con_Reportf("FRAME=%d FIRST_OFFSET=%d\n", g_geom.frame_index, alloc_offset); + g_geom.dynamic_offsets[g_geom.frame_index] = alloc_offset; + } + + { + const uint32_t vertices_offset = offset / sizeof(vk_vertex_t); + const uint32_t indices_offset = (offset + vertices_size) / sizeof(uint16_t); + const vk_staging_buffer_args_t staging_args = { + .buffer = g_geom.buffer.buffer, + .offset = offset, + .size = total_size, + .alignment = 4, + }; + + const vk_staging_region_t staging = R_VkStagingLockForBuffer(staging_args); + ASSERT(staging.ptr); + + ASSERT( offset % sizeof(vk_vertex_t) == 0 ); + ASSERT( (offset + vertices_size) % sizeof(uint16_t) == 0 ); + + *lock = (r_geometry_buffer_lock_t) { + .vertices = { + .count = vertex_count, + .ptr = (vk_vertex_t *)staging.ptr, + .unit_offset = vertices_offset, + }, + .indices = { + .count = index_count, + .ptr = (uint16_t *)((char*)staging.ptr + vertices_size), + .unit_offset = indices_offset, + }, + .impl_ = { + .staging_handle = staging.handle, + }, + }; + } + + return true; +} + +void R_GeometryBufferUnlock( const r_geometry_buffer_lock_t *lock ) { + R_VkStagingUnlock(lock->impl_.staging_handle); +} + +void XVK_RenderBufferMapClear( void ) { + aloRingInit(&g_geom.static_ring, GEOMETRY_BUFFER_STATIC_SIZE); + aloRingInit(&g_geom.dynamic_ring, GEOMETRY_BUFFER_DYNAMIC_SIZE); + for (int i = 0; i < COUNTOF(g_geom.dynamic_offsets); ++i) { + g_geom.dynamic_offsets[i] = ALO_ALLOC_FAILED; + } + g_geom.frame_index = 0; +} + +void XVK_RenderBufferPrintStats( void ) { + // TODO get alignment holes size + gEngine.Con_Reportf("Buffer usage: %uKiB of (%uKiB)\n", + g_geom.static_ring.head / 1024, g_geom.static_ring.size / 1024); +} + +qboolean R_GeometryBuffer_Init(void) { + // TODO device memory and friends (e.g. handle mobile memory ...) + + if (!VK_BufferCreate("geometry buffer", &g_geom.buffer, GEOMETRY_BUFFER_SIZE, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | (vk_core.rtx ? VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR : 0), + (vk_core.rtx ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : 0))) // TODO staging buffer? + return false; + + XVK_RenderBufferMapClear(); + return true; +} + +void R_GeometryBuffer_Shutdown(void) { + VK_BufferDestroy( &g_geom.buffer ); +} + +void R_GeometryBuffer_Flip(void) { + const int new_frame = (g_geom.frame_index + 1) % COUNTOF(g_geom.dynamic_offsets); + if (g_geom.dynamic_offsets[new_frame] != ALO_ALLOC_FAILED) { + //gEngine.Con_Reportf("FRAME=%d FREE_OFFSET=%d\n", g_geom.frame_index, g_geom.dynamic_offsets[new_frame]); + aloRingFree(&g_geom.dynamic_ring, g_geom.dynamic_offsets[new_frame]); + g_geom.dynamic_offsets[new_frame] = ALO_ALLOC_FAILED; + } + g_geom.frame_index = new_frame; +} + +VkBuffer R_GeometryBuffer_Get(void) { + return g_geom.buffer.buffer; +} diff --git a/ref_vk/vk_geometry.h b/ref_vk/vk_geometry.h new file mode 100644 index 00000000..c0db22b8 --- /dev/null +++ b/ref_vk/vk_geometry.h @@ -0,0 +1,65 @@ +#pragma once +#include "vk_common.h" +#include "vk_core.h" + +#include + +// General buffer usage pattern +// 1. alloc (allocates buffer mem, stores allocation data) +// 2. (returns void* buf and handle) write to buf +// 3. upload and lock (ensures that all this data is in gpu mem, e.g. uploads from staging) +// 4. ... use it +// 5. free (frame/map end) + +// TODO is this a good place? +typedef struct vk_vertex_s { + // TODO padding needed for storage buffer reading, figure out how to fix in GLSL/SPV side + vec3_t pos; float p0_; + vec3_t normal; uint32_t flags; + vec3_t tangent; uint32_t p1_; + vec2_t gl_tc; //float p2_[2]; + vec2_t lm_tc; //float p3_[2]; + + rgba_t color; // per-vertex (non-rt lighting) color, color[3] == 1(255) => use color, discard lightmap; color[3] == 0 => use lightmap, discard color + float _padding[3]; +} vk_vertex_t; + +typedef struct { + struct { + vk_vertex_t *ptr; + int count; + int unit_offset; + } vertices; + + struct { + uint16_t *ptr; + int count; + int unit_offset; + } indices; + + struct { + int staging_handle; + } impl_; +} r_geometry_buffer_lock_t; + +typedef enum { + LifetimeLong, + LifetimeSingleFrame +} r_geometry_lifetime_t; + +qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int vertex_count, int index_count, r_geometry_lifetime_t lifetime ); +void R_GeometryBufferUnlock( const r_geometry_buffer_lock_t *lock ); +//void R_VkGeometryBufferFree( int handle ); + +void R_GeometryBufferMapClear( void ); // Free the entire buffer for a new map + +void R_GeometryBufferPrintStats( void ); + +qboolean R_GeometryBuffer_Init(void); +void R_GeometryBuffer_Shutdown(void); + +void R_GeometryBuffer_Flip(void); + +// FIXME is there a better way? +VkBuffer R_GeometryBuffer_Get(void); + diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index 0928a0ff..21ce497e 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -3,6 +3,7 @@ #include "vk_rtx.h" #include "vk_textures.h" #include "vk_materials.h" +#include "vk_geometry.h" #include "vk_render.h" #include "vk_light.h" diff --git a/ref_vk/vk_render.c b/ref_vk/vk_render.c index f5d008cc..b84f73a4 100644 --- a/ref_vk/vk_render.c +++ b/ref_vk/vk_render.c @@ -2,6 +2,7 @@ #include "vk_core.h" #include "vk_buffer.h" +#include "vk_geometry.h" #include "vk_staging.h" #include "vk_const.h" #include "vk_common.h" @@ -21,16 +22,6 @@ #define MAX_UNIFORM_SLOTS (MAX_SCENE_ENTITIES * 2 /* solid + trans */ + 1) -#define MAX_BUFFER_VERTICES_STATIC (128 * 1024) -#define MAX_BUFFER_INDICES_STATIC (MAX_BUFFER_VERTICES_STATIC * 3) -#define GEOMETRY_BUFFER_STATIC_SIZE ALIGN_UP(MAX_BUFFER_VERTICES_STATIC * sizeof(vk_vertex_t) + MAX_BUFFER_INDICES_STATIC * sizeof(uint16_t), sizeof(vk_vertex_t)) - -#define MAX_BUFFER_VERTICES_DYNAMIC (128 * 1024 * 2) -#define MAX_BUFFER_INDICES_DYNAMIC (MAX_BUFFER_VERTICES_DYNAMIC * 3) -#define GEOMETRY_BUFFER_DYNAMIC_SIZE ALIGN_UP(MAX_BUFFER_VERTICES_DYNAMIC * sizeof(vk_vertex_t) + MAX_BUFFER_INDICES_DYNAMIC * sizeof(uint16_t), sizeof(vk_vertex_t)) - -#define GEOMETRY_BUFFER_SIZE (GEOMETRY_BUFFER_STATIC_SIZE + GEOMETRY_BUFFER_DYNAMIC_SIZE) - typedef struct { matrix4x4 mvp; vec4_t color; @@ -46,15 +37,6 @@ static struct { float fov_angle_y; } g_render; -struct { - vk_buffer_t buffer; - alo_ring_t static_ring; - alo_ring_t dynamic_ring; - - int frame_index; - uint32_t dynamic_offsets[MAX_CONCURRENT_FRAMES]; -} g_geom; - static qboolean createPipelines( void ) { /* VkPushConstantRange push_const = { */ @@ -291,16 +273,9 @@ qboolean VK_RenderInit( void ) { const uint32_t uniform_buffer_size = uniform_unit_size * MAX_UNIFORM_SLOTS; R_FlippingBuffer_Init(&g_render_state.uniform_alloc, uniform_buffer_size); - // TODO device memory and friends (e.g. handle mobile memory ...) - - if (!VK_BufferCreate("geometry buffer", &g_geom.buffer, GEOMETRY_BUFFER_SIZE, - VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | (vk_core.rtx ? VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR : 0), - (vk_core.rtx ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : 0))) // TODO staging buffer? - return false; - if (!VK_BufferCreate("render uniform_buffer", &g_render.uniform_buffer, uniform_buffer_size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | (vk_core.rtx ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : 0))) // TODO staging buffer? + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | (vk_core.rtx ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : 0))) return false; { @@ -337,8 +312,6 @@ qboolean VK_RenderInit( void ) { if (!createPipelines()) return false; - XVK_RenderBufferMapClear(); - return true; } @@ -348,86 +321,9 @@ void VK_RenderShutdown( void ) vkDestroyPipeline(vk_core.device, g_render.pipelines[i], NULL); vkDestroyPipelineLayout( vk_core.device, g_render.pipeline_layout, NULL ); - VK_BufferDestroy( &g_geom.buffer ); VK_BufferDestroy( &g_render.uniform_buffer ); } -qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int vertex_count, int index_count, r_geometry_lifetime_t lifetime ) { - const uint32_t vertices_size = vertex_count * sizeof(vk_vertex_t); - const uint32_t indices_size = index_count * sizeof(uint16_t); - const uint32_t total_size = vertices_size + indices_size; - alo_ring_t * const ring = (lifetime != LifetimeSingleFrame) ? &g_geom.static_ring : &g_geom.dynamic_ring; - - const uint32_t alloc_offset = aloRingAlloc(ring, total_size, sizeof(vk_vertex_t)); - const uint32_t offset = alloc_offset + ((lifetime == LifetimeSingleFrame) ? GEOMETRY_BUFFER_STATIC_SIZE : 0); - if (alloc_offset == ALO_ALLOC_FAILED) { - /* gEngine.Con_Printf(S_ERROR "Cannot allocate %s geometry buffer for %d vertices (%d bytes) and %d indices (%d bytes)\n", */ - /* lifetime == LifetimeSingleFrame ? "dynamic" : "static", */ - /* vertex_count, vertices_size, index_count, indices_size); */ - return false; - } - - // Store first dynamic allocation this frame - if (lifetime == LifetimeSingleFrame && g_geom.dynamic_offsets[g_geom.frame_index] == ALO_ALLOC_FAILED) { - //gEngine.Con_Reportf("FRAME=%d FIRST_OFFSET=%d\n", g_geom.frame_index, alloc_offset); - g_geom.dynamic_offsets[g_geom.frame_index] = alloc_offset; - } - - { - const uint32_t vertices_offset = offset / sizeof(vk_vertex_t); - const uint32_t indices_offset = (offset + vertices_size) / sizeof(uint16_t); - const vk_staging_buffer_args_t staging_args = { - .buffer = g_geom.buffer.buffer, - .offset = offset, - .size = total_size, - .alignment = 4, - }; - - const vk_staging_region_t staging = R_VkStagingLockForBuffer(staging_args); - ASSERT(staging.ptr); - - ASSERT( offset % sizeof(vk_vertex_t) == 0 ); - ASSERT( (offset + vertices_size) % sizeof(uint16_t) == 0 ); - - *lock = (r_geometry_buffer_lock_t) { - .vertices = { - .count = vertex_count, - .ptr = (vk_vertex_t *)staging.ptr, - .unit_offset = vertices_offset, - }, - .indices = { - .count = index_count, - .ptr = (uint16_t *)((char*)staging.ptr + vertices_size), - .unit_offset = indices_offset, - }, - .impl_ = { - .staging_handle = staging.handle, - }, - }; - } - - return true; -} - -void R_GeometryBufferUnlock( const r_geometry_buffer_lock_t *lock ) { - R_VkStagingUnlock(lock->impl_.staging_handle); -} - -void XVK_RenderBufferMapClear( void ) { - aloRingInit(&g_geom.static_ring, GEOMETRY_BUFFER_STATIC_SIZE); - aloRingInit(&g_geom.dynamic_ring, GEOMETRY_BUFFER_DYNAMIC_SIZE); - for (int i = 0; i < COUNTOF(g_geom.dynamic_offsets); ++i) { - g_geom.dynamic_offsets[i] = ALO_ALLOC_FAILED; - } - g_geom.frame_index = 0; -} - -void XVK_RenderBufferPrintStats( void ) { - // TODO get alignment holes size - gEngine.Con_Reportf("Buffer usage: %uKiB of (%uKiB)\n", - g_geom.static_ring.head / 1024, g_geom.static_ring.size / 1024); -} - enum { UNIFORM_UNSET = 0, UNIFORM_SET_COLOR = 1, @@ -448,15 +344,7 @@ void VK_RenderBegin( qboolean ray_tracing ) { g_render_state.num_draw_commands = 0; g_render_state.current_frame_is_ray_traced = ray_tracing; - { - const int new_frame = (g_geom.frame_index + 1) % COUNTOF(g_geom.dynamic_offsets); - if (g_geom.dynamic_offsets[new_frame] != ALO_ALLOC_FAILED) { - //gEngine.Con_Reportf("FRAME=%d FREE_OFFSET=%d\n", g_geom.frame_index, g_geom.dynamic_offsets[new_frame]); - aloRingFree(&g_geom.dynamic_ring, g_geom.dynamic_offsets[new_frame]); - g_geom.dynamic_offsets[new_frame] = ALO_ALLOC_FAILED; - } - g_geom.frame_index = new_frame; - } + R_GeometryBuffer_Flip(); if (ray_tracing) VK_RayFrameBegin(); @@ -629,7 +517,8 @@ static uint32_t writeDlightsToUBO( void ) return ubo_lights_offset; } -void VK_Render_FIXME_Barrier( VkCommandBuffer cmdbuf ) +void VK_Render_FIXME_Barrier( VkCommandBuffer cmdbuf ) { + const VkBuffer geom_buffer = R_GeometryBuffer_Get(); // FIXME { const VkBufferMemoryBarrier bmb[] = { { @@ -637,7 +526,7 @@ void VK_Render_FIXME_Barrier( VkCommandBuffer cmdbuf ) .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, //.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR, // FIXME .dstAccessMask = VK_ACCESS_INDEX_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT , // FIXME - .buffer = g_geom.buffer.buffer, + .buffer = geom_buffer, .offset = 0, // FIXME .size = VK_WHOLE_SIZE, // FIXME } }; @@ -648,6 +537,7 @@ void VK_Render_FIXME_Barrier( VkCommandBuffer cmdbuf ) VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } +} void VK_RenderEnd( VkCommandBuffer cmdbuf ) { @@ -667,9 +557,10 @@ void VK_RenderEnd( VkCommandBuffer cmdbuf ) { + const VkBuffer geom_buffer = R_GeometryBuffer_Get(); const VkDeviceSize offset = 0; - vkCmdBindVertexBuffers(cmdbuf, 0, 1, &g_geom.buffer.buffer, &offset); - vkCmdBindIndexBuffer(cmdbuf, g_geom.buffer.buffer, 0, VK_INDEX_TYPE_UINT16); + vkCmdBindVertexBuffers(cmdbuf, 0, 1, &geom_buffer, &offset); + vkCmdBindIndexBuffer(cmdbuf, geom_buffer, 0, VK_INDEX_TYPE_UINT16); } vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 3, 1, vk_desc.ubo_sets + 1, 1, &dlights_ubo_offset); @@ -733,6 +624,7 @@ void VK_RenderDebugLabelEnd( void ) void VK_RenderEndRTX( VkCommandBuffer cmdbuf, VkImageView img_dst_view, VkImage img_dst, uint32_t w, uint32_t h ) { + const VkBuffer geom_buffer = R_GeometryBuffer_Get(); ASSERT(vk_core.rtx); { @@ -749,7 +641,7 @@ void VK_RenderEndRTX( VkCommandBuffer cmdbuf, VkImageView img_dst_view, VkImage .view = &g_render_state.view, .geometry_data = { - .buffer = g_geom.buffer.buffer, + .buffer = geom_buffer, .size = VK_WHOLE_SIZE, }, @@ -762,19 +654,20 @@ void VK_RenderEndRTX( VkCommandBuffer cmdbuf, VkImageView img_dst_view, VkImage qboolean VK_RenderModelInit( VkCommandBuffer cmdbuf, vk_render_model_t *model ) { if (vk_core.rtx && (g_render_state.current_frame_is_ray_traced || !model->dynamic)) { + const VkBuffer geom_buffer = R_GeometryBuffer_Get(); // TODO runtime rtx switch: ??? const vk_ray_model_init_t args = { - .buffer = g_geom.buffer.buffer, + .buffer = geom_buffer, .model = model, }; - R_VkStagingCommit(cmdbuf); + R_VkStagingCommit(cmdbuf); // FIXME this is definitely not the right place { const VkBufferMemoryBarrier bmb[] = { { .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, //.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR, // FIXME .dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_SHADER_READ_BIT, // FIXME - .buffer = g_geom.buffer.buffer, + .buffer = geom_buffer, .offset = 0, // FIXME .size = VK_WHOLE_SIZE, // FIXME } }; diff --git a/ref_vk/vk_render.h b/ref_vk/vk_render.h index a6cd6226..ab46688b 100644 --- a/ref_vk/vk_render.h +++ b/ref_vk/vk_render.h @@ -6,57 +6,6 @@ qboolean VK_RenderInit( void ); void VK_RenderShutdown( void ); -// General buffer usage pattern -// 1. alloc (allocates buffer mem, stores allocation data) -// 2. (returns void* buf and handle) write to buf -// 3. upload and lock (ensures that all this data is in gpu mem, e.g. uploads from staging) -// 4. ... use it -// 5. free (frame/map end) - -// TODO is this a good place? -typedef struct vk_vertex_s { - // TODO padding needed for storage buffer reading, figure out how to fix in GLSL/SPV side - vec3_t pos; float p0_; - vec3_t normal; uint32_t flags; - vec3_t tangent; uint32_t p1_; - vec2_t gl_tc; //float p2_[2]; - vec2_t lm_tc; //float p3_[2]; - - rgba_t color; // per-vertex (non-rt lighting) color, color[3] == 1(255) => use color, discard lightmap; color[3] == 0 => use lightmap, discard color - float _padding[3]; -} vk_vertex_t; - -typedef struct { - struct { - vk_vertex_t *ptr; - int count; - int unit_offset; - } vertices; - - struct { - uint16_t *ptr; - int count; - int unit_offset; - } indices; - - struct { - int staging_handle; - } impl_; -} r_geometry_buffer_lock_t; - -typedef enum { - LifetimeLong, - LifetimeSingleFrame -} r_geometry_lifetime_t; - -qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int vertex_count, int index_count, r_geometry_lifetime_t lifetime ); -void R_GeometryBufferUnlock( const r_geometry_buffer_lock_t *lock ); -//void R_VkGeometryBufferFree( int handle ); - -void XVK_RenderBufferMapClear( void ); // Free the entire buffer for a new map - -void XVK_RenderBufferPrintStats( void ); - // Set UBO state for next VK_RenderScheduleDraw calls // Why? Xash Ref code is organized in a way where we can't reliably pass this info with // ScheduleDraw itself, so we need to either set up per-submodule global state, or diff --git a/ref_vk/vk_sprite.c b/ref_vk/vk_sprite.c index 9a95e7e4..daadffb3 100644 --- a/ref_vk/vk_sprite.c +++ b/ref_vk/vk_sprite.c @@ -2,6 +2,7 @@ #include "vk_textures.h" #include "camera.h" #include "vk_render.h" +#include "vk_geometry.h" #include "vk_scene.h" #include "sprite.h" diff --git a/ref_vk/vk_studio.c b/ref_vk/vk_studio.c index 83423746..ecfb4ae5 100644 --- a/ref_vk/vk_studio.c +++ b/ref_vk/vk_studio.c @@ -2,6 +2,7 @@ #include "vk_common.h" #include "vk_textures.h" #include "vk_render.h" +#include "vk_geometry.h" #include "camera.h" #include "xash3d_mathlib.h" From 118bdd9985d35712db582aaabcc3876fb2709c3e Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 9 Jul 2022 12:47:16 -0700 Subject: [PATCH 363/548] vk: use debuffer for geometry mem management --- ref_vk/vk_buffer.c | 2 +- ref_vk/vk_geometry.c | 40 ++++++++-------------------------------- ref_vk/vk_geometry.h | 2 +- ref_vk/vk_scene.c | 3 ++- 4 files changed, 12 insertions(+), 35 deletions(-) diff --git a/ref_vk/vk_buffer.c b/ref_vk/vk_buffer.c index cf0d9fca..20ea9649 100644 --- a/ref_vk/vk_buffer.c +++ b/ref_vk/vk_buffer.c @@ -132,7 +132,7 @@ uint32_t R_DEBuffer_Alloc(r_debuffer_t* debuf, r_lifetime_t lifetime, uint32_t s const uint32_t offset = R_FlippingBuffer_Alloc(&debuf->dynamic, size, align); if (offset == ALO_ALLOC_FAILED) return ALO_ALLOC_FAILED; - return offset + debuf->static_offset; + return offset + debuf->static_size; } case LifetimeStatic: { diff --git a/ref_vk/vk_geometry.c b/ref_vk/vk_geometry.c index a7250746..5d3ab7f6 100644 --- a/ref_vk/vk_geometry.c +++ b/ref_vk/vk_geometry.c @@ -15,34 +15,22 @@ static struct { vk_buffer_t buffer; - alo_ring_t static_ring; - alo_ring_t dynamic_ring; - - int frame_index; - uint32_t dynamic_offsets[MAX_CONCURRENT_FRAMES]; + r_debuffer_t alloc; } g_geom; qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int vertex_count, int index_count, r_geometry_lifetime_t lifetime ) { const uint32_t vertices_size = vertex_count * sizeof(vk_vertex_t); const uint32_t indices_size = index_count * sizeof(uint16_t); const uint32_t total_size = vertices_size + indices_size; - alo_ring_t * const ring = (lifetime != LifetimeSingleFrame) ? &g_geom.static_ring : &g_geom.dynamic_ring; - const uint32_t alloc_offset = aloRingAlloc(ring, total_size, sizeof(vk_vertex_t)); - const uint32_t offset = alloc_offset + ((lifetime == LifetimeSingleFrame) ? GEOMETRY_BUFFER_STATIC_SIZE : 0); - if (alloc_offset == ALO_ALLOC_FAILED) { + const uint32_t offset = R_DEBuffer_Alloc(&g_geom.alloc, (lifetime == LifetimeSingleFrame) ? LifetimeDynamic : LifetimeStatic, total_size, sizeof(vk_vertex_t)); + if (offset == ALO_ALLOC_FAILED) { /* gEngine.Con_Printf(S_ERROR "Cannot allocate %s geometry buffer for %d vertices (%d bytes) and %d indices (%d bytes)\n", */ /* lifetime == LifetimeSingleFrame ? "dynamic" : "static", */ /* vertex_count, vertices_size, index_count, indices_size); */ return false; } - // Store first dynamic allocation this frame - if (lifetime == LifetimeSingleFrame && g_geom.dynamic_offsets[g_geom.frame_index] == ALO_ALLOC_FAILED) { - //gEngine.Con_Reportf("FRAME=%d FIRST_OFFSET=%d\n", g_geom.frame_index, alloc_offset); - g_geom.dynamic_offsets[g_geom.frame_index] = alloc_offset; - } - { const uint32_t vertices_offset = offset / sizeof(vk_vertex_t); const uint32_t indices_offset = (offset + vertices_size) / sizeof(uint16_t); @@ -83,19 +71,13 @@ void R_GeometryBufferUnlock( const r_geometry_buffer_lock_t *lock ) { R_VkStagingUnlock(lock->impl_.staging_handle); } -void XVK_RenderBufferMapClear( void ) { - aloRingInit(&g_geom.static_ring, GEOMETRY_BUFFER_STATIC_SIZE); - aloRingInit(&g_geom.dynamic_ring, GEOMETRY_BUFFER_DYNAMIC_SIZE); - for (int i = 0; i < COUNTOF(g_geom.dynamic_offsets); ++i) { - g_geom.dynamic_offsets[i] = ALO_ALLOC_FAILED; - } - g_geom.frame_index = 0; +void R_GeometryBuffer_MapClear( void ) { + R_DEBuffer_Init(&g_geom.alloc, GEOMETRY_BUFFER_STATIC_SIZE, GEOMETRY_BUFFER_DYNAMIC_SIZE); } void XVK_RenderBufferPrintStats( void ) { // TODO get alignment holes size - gEngine.Con_Reportf("Buffer usage: %uKiB of (%uKiB)\n", - g_geom.static_ring.head / 1024, g_geom.static_ring.size / 1024); + // gEngine.Con_Reportf("Buffer usage: %uKiB of (%uKiB)\n", g_geom.alloc..head / 1024, g_geom.static_ring.size / 1024); } qboolean R_GeometryBuffer_Init(void) { @@ -106,7 +88,7 @@ qboolean R_GeometryBuffer_Init(void) { (vk_core.rtx ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : 0))) // TODO staging buffer? return false; - XVK_RenderBufferMapClear(); + R_GeometryBuffer_MapClear(); return true; } @@ -115,13 +97,7 @@ void R_GeometryBuffer_Shutdown(void) { } void R_GeometryBuffer_Flip(void) { - const int new_frame = (g_geom.frame_index + 1) % COUNTOF(g_geom.dynamic_offsets); - if (g_geom.dynamic_offsets[new_frame] != ALO_ALLOC_FAILED) { - //gEngine.Con_Reportf("FRAME=%d FREE_OFFSET=%d\n", g_geom.frame_index, g_geom.dynamic_offsets[new_frame]); - aloRingFree(&g_geom.dynamic_ring, g_geom.dynamic_offsets[new_frame]); - g_geom.dynamic_offsets[new_frame] = ALO_ALLOC_FAILED; - } - g_geom.frame_index = new_frame; + R_DEBuffer_Flip(&g_geom.alloc); } VkBuffer R_GeometryBuffer_Get(void) { diff --git a/ref_vk/vk_geometry.h b/ref_vk/vk_geometry.h index c0db22b8..eb8394f9 100644 --- a/ref_vk/vk_geometry.h +++ b/ref_vk/vk_geometry.h @@ -51,7 +51,7 @@ qboolean R_GeometryBufferAllocAndLock( r_geometry_buffer_lock_t *lock, int verte void R_GeometryBufferUnlock( const r_geometry_buffer_lock_t *lock ); //void R_VkGeometryBufferFree( int handle ); -void R_GeometryBufferMapClear( void ); // Free the entire buffer for a new map +void R_GeometryBuffer_MapClear( void ); // Free the entire buffer for a new map void R_GeometryBufferPrintStats( void ); diff --git a/ref_vk/vk_scene.c b/ref_vk/vk_scene.c index 08aa1cb9..d638c4e6 100644 --- a/ref_vk/vk_scene.c +++ b/ref_vk/vk_scene.c @@ -5,6 +5,7 @@ #include "vk_lightmap.h" #include "vk_const.h" #include "vk_render.h" +#include "vk_geometry.h" #include "vk_math.h" #include "vk_common.h" #include "vk_core.h" @@ -155,7 +156,7 @@ void R_NewMap( void ) { // TODO should we do something like VK_BrushBeginLoad? VK_BrushStatsClear(); - XVK_RenderBufferMapClear(); + R_GeometryBuffer_MapClear(); VK_ClearLightmap(); From 394587adbfd6b86bd3b0180d8f21c1ccc8a93687 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 9 Jul 2022 13:09:12 -0700 Subject: [PATCH 364/548] vk: remove geometry print stats func --- ref_vk/vk_geometry.c | 5 ----- ref_vk/vk_geometry.h | 2 -- ref_vk/vk_scene.c | 1 - 3 files changed, 8 deletions(-) diff --git a/ref_vk/vk_geometry.c b/ref_vk/vk_geometry.c index 5d3ab7f6..65cf3244 100644 --- a/ref_vk/vk_geometry.c +++ b/ref_vk/vk_geometry.c @@ -75,11 +75,6 @@ void R_GeometryBuffer_MapClear( void ) { R_DEBuffer_Init(&g_geom.alloc, GEOMETRY_BUFFER_STATIC_SIZE, GEOMETRY_BUFFER_DYNAMIC_SIZE); } -void XVK_RenderBufferPrintStats( void ) { - // TODO get alignment holes size - // gEngine.Con_Reportf("Buffer usage: %uKiB of (%uKiB)\n", g_geom.alloc..head / 1024, g_geom.static_ring.size / 1024); -} - qboolean R_GeometryBuffer_Init(void) { // TODO device memory and friends (e.g. handle mobile memory ...) diff --git a/ref_vk/vk_geometry.h b/ref_vk/vk_geometry.h index eb8394f9..41165d2b 100644 --- a/ref_vk/vk_geometry.h +++ b/ref_vk/vk_geometry.h @@ -53,8 +53,6 @@ void R_GeometryBufferUnlock( const r_geometry_buffer_lock_t *lock ); void R_GeometryBuffer_MapClear( void ); // Free the entire buffer for a new map -void R_GeometryBufferPrintStats( void ); - qboolean R_GeometryBuffer_Init(void); void R_GeometryBuffer_Shutdown(void); diff --git a/ref_vk/vk_scene.c b/ref_vk/vk_scene.c index d638c4e6..60a91568 100644 --- a/ref_vk/vk_scene.c +++ b/ref_vk/vk_scene.c @@ -234,7 +234,6 @@ void R_NewMap( void ) { // TODO should we do something like VK_BrushEndLoad? VK_UploadLightmap(); - XVK_RenderBufferPrintStats(); if (vk_core.rtx) VK_RayMapLoadEnd(); } From 8f9183099773ab6cdb44005e78315bdf807fb3f5 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Sun, 10 Jul 2022 02:04:13 +0400 Subject: [PATCH 365/548] engine: added changeport parameter for NET_Config --- engine/client/cl_game.c | 2 +- engine/client/cl_main.c | 8 ++++---- engine/common/net_ws.c | 4 ++-- engine/common/net_ws.h | 2 +- engine/server/sv_init.c | 2 +- engine/server/sv_main.c | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index b093c82c..e516f423 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -3323,7 +3323,7 @@ NetAPI_InitNetworking */ void GAME_EXPORT NetAPI_InitNetworking( void ) { - NET_Config( true ); // allow remote + NET_Config( true, false ); // allow remote } /* diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 46782aef..8c47c1f8 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -1298,7 +1298,7 @@ void CL_Rcon_f( void ) message[3] = (char)255; message[4] = 0; - NET_Config( true ); // allow remote + NET_Config( true, false ); // allow remote Q_strcat( message, "rcon " ); Q_strcat( message, rcon_client_password->string ); @@ -1561,7 +1561,7 @@ void CL_LocalServers_f( void ) netadr_t adr; Con_Printf( "Scanning for servers on the local network area...\n" ); - NET_Config( true ); // allow remote + NET_Config( true, true ); // allow remote // send a broadcast packet adr.type = NA_BROADCAST; @@ -1583,7 +1583,7 @@ void CL_InternetServers_f( void ) char *info = fullquery + sizeof( MS_SCAN_REQUEST ) - 1; const size_t remaining = sizeof( fullquery ) - sizeof( MS_SCAN_REQUEST ); - NET_Config( true ); // allow remote + NET_Config( true, true ); // allow remote Con_Printf( "Scanning for servers on the internet area...\n" ); Info_SetValueForKey( info, "gamedir", GI->gamefolder, remaining ); @@ -2152,7 +2152,7 @@ void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) } else if( clgame.request_type == NET_REQUEST_GAMEUI ) { - NET_Config( true ); // allow remote + NET_Config( true, false ); // allow remote Netchan_OutOfBandPrint( NS_CLIENT, servadr, "info %i", PROTOCOL_VERSION ); } } diff --git a/engine/common/net_ws.c b/engine/common/net_ws.c index 8b491ba9..0727b6dd 100644 --- a/engine/common/net_ws.c +++ b/engine/common/net_ws.c @@ -1570,7 +1570,7 @@ NET_Config A single player game will only use the loopback code ==================== */ -void NET_Config( qboolean multiplayer ) +void NET_Config( qboolean multiplayer, qboolean changeport ) { static qboolean bFirst = true; static qboolean old_config; @@ -1756,7 +1756,7 @@ void NET_Shutdown( void ) NET_ClearLagData( true, true ); - NET_Config( false ); + NET_Config( false, false ); #if XASH_WIN32 WSACleanup(); #endif diff --git a/engine/common/net_ws.h b/engine/common/net_ws.h index ede8f0b1..75329383 100644 --- a/engine/common/net_ws.h +++ b/engine/common/net_ws.h @@ -51,7 +51,7 @@ void NET_Shutdown( void ); void NET_Sleep( int msec ); qboolean NET_IsActive( void ); qboolean NET_IsConfigured( void ); -void NET_Config( qboolean net_enable ); +void NET_Config( qboolean net_enable, qboolean changeport ); qboolean NET_IsLocalAddress( netadr_t adr ); const char *NET_AdrToString( const netadr_t a ); const char *NET_BaseAdrToString( const netadr_t a ); diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index b045fdfd..8f55a25e 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -761,7 +761,7 @@ void SV_SetupClients( void ) Con_Reportf( "%s alloced by server packet entities\n", Q_memprint( sizeof( entity_state_t ) * svs.num_client_entities )); // init network stuff - NET_Config(( svs.maxclients > 1 )); + NET_Config(( svs.maxclients > 1 ), true ); svgame.numEntities = svs.maxclients + 1; // clients + world ClearBits( sv_maxclients->flags, FCVAR_CHANGED ); } diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 52f314c0..6d75b7e6 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -715,7 +715,7 @@ void Master_Add( void ) { netadr_t adr; - NET_Config( true ); // allow remote + NET_Config( true, false ); // allow remote if( !NET_StringToAdr( MASTERSERVER_ADR, &adr )) Con_Printf( "can't resolve adr: %s\n", MASTERSERVER_ADR ); @@ -758,7 +758,7 @@ void Master_Shutdown( void ) { netadr_t adr; - NET_Config( true ); // allow remote + NET_Config( true, false ); // allow remote if( !NET_StringToAdr( MASTERSERVER_ADR, &adr )) Con_Printf( "can't resolve addr: %s\n", MASTERSERVER_ADR ); @@ -1108,7 +1108,7 @@ void SV_Shutdown( const char *finalmsg ) if( public_server->value && svs.maxclients != 1 ) Master_Shutdown(); - NET_Config( false ); + NET_Config( false, false ); SV_UnloadProgs (); CL_Drop(); From 6891ed8064b4660507601ee83ebf1570e3cb200a Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Sun, 10 Jul 2022 02:07:25 +0400 Subject: [PATCH 366/548] engine: common: net_ws: backported NAT bypass feature --- engine/common/net_ws.c | 54 +++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/engine/common/net_ws.c b/engine/common/net_ws.c index 0727b6dd..675ffab0 100644 --- a/engine/common/net_ws.c +++ b/engine/common/net_ws.c @@ -1472,35 +1472,71 @@ static int NET_IPSocket( const char *net_interface, int port, qboolean multicast NET_OpenIP ==================== */ -static void NET_OpenIP( void ) +static void NET_OpenIP( qboolean change_port ) { - int port, sv_port = 0, cl_port = 0; + int port; + qboolean sv_nat = Cvar_VariableInteger("sv_nat"); + qboolean cl_nat = Cvar_VariableInteger("cl_nat"); + + if( change_port && ( FBitSet( net_hostport->flags, FCVAR_CHANGED ) || sv_nat )) + { + // reopen socket to set random port + if( NET_IsSocketValid( net.ip_sockets[NS_SERVER] )) + closesocket( net.ip_sockets[NS_SERVER] ); + + net.ip_sockets[NS_SERVER] = 0; + ClearBits( net_hostport->flags, FCVAR_CHANGED ); + } if( !NET_IsSocketValid( net.ip_sockets[NS_SERVER] ) ) { port = net_iphostport->value; - if( !port ) port = net_hostport->value; - if( !port ) port = PORT_SERVER; // forcing to default + if( !port ) + { + if( sv_nat ) + port = PORT_ANY; + else + port = net_hostport->value; + + if( !port ) + port = PORT_SERVER; // forcing to default + } net.ip_sockets[NS_SERVER] = NET_IPSocket( net_ipname->string, port, false ); if( !NET_IsSocketValid( net.ip_sockets[NS_SERVER] ) && Host_IsDedicated() ) Host_Error( "Couldn't allocate dedicated server IP port %d.\n", port ); - sv_port = port; } // dedicated servers don't need client ports if( Host_IsDedicated() ) return; + if (change_port && ( FBitSet( net_clientport->flags, FCVAR_CHANGED ) || cl_nat )) + { + // reopen socket to set random port + if( NET_IsSocketValid(net.ip_sockets[NS_CLIENT] )) + closesocket( net.ip_sockets[NS_CLIENT] ); + + net.ip_sockets[NS_CLIENT] = 0; + ClearBits( net_clientport->flags, FCVAR_CHANGED ); + } + if( !NET_IsSocketValid( net.ip_sockets[NS_CLIENT] ) ) { port = net_ipclientport->value; - if( !port ) port = net_clientport->value; - if( !port ) port = PORT_ANY; // forcing to default + if( !port ) + { + if( cl_nat ) + port = PORT_ANY; + else + port = net_clientport->value; + + if( !port ) + port = PORT_ANY; // forcing to default + } net.ip_sockets[NS_CLIENT] = NET_IPSocket( net_ipname->string, port, false ); if( !NET_IsSocketValid( net.ip_sockets[NS_CLIENT] ) ) net.ip_sockets[NS_CLIENT] = NET_IPSocket( net_ipname->string, PORT_ANY, false ); - cl_port = port; } } @@ -1586,7 +1622,7 @@ void NET_Config( qboolean multiplayer, qboolean changeport ) if( multiplayer ) { // open sockets - if( net.allow_ip ) NET_OpenIP(); + if( net.allow_ip ) NET_OpenIP( changeport ); // get our local address, if possible if( bFirst ) From ffe7114a47638a5b9ea689ab4169fb9de636a084 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Sun, 10 Jul 2022 02:07:42 +0400 Subject: [PATCH 367/548] engine: client: backported NAT bypass feature --- engine/client/cl_main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 8c47c1f8..e8e9d5f5 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -70,6 +70,7 @@ convar_t *cl_upmax; convar_t *cl_lw; convar_t *cl_charset; convar_t *cl_trace_messages; +convar_t *cl_nat; convar_t *hud_utf8; convar_t *ui_renderworld; @@ -1252,7 +1253,7 @@ void CL_Connect_f( void ) // if running a local server, kill it and reissue if( SV_Active( )) Host_ShutdownServer(); - NET_Config( true ); // allow remote + NET_Config( true, !CVAR_TO_BOOL( cl_nat )); // allow remote Con_Printf( "server %s\n", server ); CL_Disconnect(); @@ -1588,7 +1589,7 @@ void CL_InternetServers_f( void ) Con_Printf( "Scanning for servers on the internet area...\n" ); Info_SetValueForKey( info, "gamedir", GI->gamefolder, remaining ); Info_SetValueForKey( info, "clver", XASH_VERSION, remaining ); // let master know about client version - // Info_SetValueForKey( info, "nat", cl_nat->string, remaining ); + Info_SetValueForKey( info, "nat", cl_nat->string, remaining ); cls.internetservers_wait = NET_SendToMasters( NS_CLIENT, sizeof( MS_SCAN_REQUEST ) + Q_strlen( info ), fullquery ); cls.internetservers_pending = true; @@ -2829,6 +2830,7 @@ void CL_InitLocal( void ) cl_updaterate = Cvar_Get( "cl_updaterate", "20", FCVAR_USERINFO|FCVAR_ARCHIVE, "refresh rate of server messages" ); cl_dlmax = Cvar_Get( "cl_dlmax", "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "max allowed outcoming fragment size" ); cl_upmax = Cvar_Get( "cl_upmax", "1200", FCVAR_ARCHIVE, "max allowed incoming fragment size" ); + cl_nat = Cvar_Get( "cl_nat", "0", 0, "show servers running under NAT" ); rate = Cvar_Get( "rate", "3500", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player network rate" ); topcolor = Cvar_Get( "topcolor", "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "player top color" ); bottomcolor = Cvar_Get( "bottomcolor", "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "player bottom color" ); From cf84ad12f1ea0f2d5d4ed1472a56f3bec995d1b8 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Sun, 10 Jul 2022 02:07:59 +0400 Subject: [PATCH 368/548] engine: server: backported NAT bypass feature --- engine/server/sv_main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 6d75b7e6..514a54d4 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -18,11 +18,12 @@ GNU General Public License for more details. #include "net_encode.h" #include "platform/platform.h" -#define HEARTBEAT_SECONDS 300.0f // 300 seconds +#define HEARTBEAT_SECONDS ((sv_nat.value > 0.0f) ? 60.0f : 300.0f) // 1 or 5 minutes // server cvars CVAR_DEFINE_AUTO( sv_lan, "0", 0, "server is a lan server ( no heartbeat, no authentication, no non-class C addresses, 9999.0 rate, etc." ); CVAR_DEFINE_AUTO( sv_lan_rate, "20000.0", 0, "rate for lan server" ); +CVAR_DEFINE_AUTO( sv_nat, "0", 0, "enable NAT bypass for this server" ); CVAR_DEFINE_AUTO( sv_aim, "1", FCVAR_ARCHIVE|FCVAR_SERVER, "auto aiming option" ); CVAR_DEFINE_AUTO( sv_unlag, "1", 0, "allow lag compensation on server-side" ); CVAR_DEFINE_AUTO( sv_maxunlag, "0.5", 0, "max latency value which can be interpolated (by default ping should not exceed 500 units)" ); @@ -798,6 +799,7 @@ void SV_AddToMaster( netadr_t from, sizebuf_t *msg ) Info_SetValueForKey( s, "version", va( "%s", XASH_VERSION ), len ); // server region. 255 -- all regions Info_SetValueForKey( s, "region", "255", len ); // server region. 255 -- all regions Info_SetValueForKey( s, "product", GI->gamefolder, len ); // product? Where is the difference with gamedir? + Info_SetValueForKey( s, "nat", sv_nat.string, sizeof(s) ); // Server running under NAT, use reverse connection NET_SendPacket( NS_SERVER, Q_strlen( s ), s, from ); } @@ -956,6 +958,7 @@ void SV_Init( void ) sv_hostmap = Cvar_Get( "hostmap", GI->startmap, 0, "keep name of last entered map" ); Cvar_RegisterVariable( &sv_password ); Cvar_RegisterVariable( &sv_lan ); + Cvar_RegisterVariable( &sv_nat ); Cvar_RegisterVariable( &violence_ablood ); Cvar_RegisterVariable( &violence_hblood ); Cvar_RegisterVariable( &violence_agibs ); From 36b0d47f5f113462d853ec4af279a7711ff52454 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Sun, 10 Jul 2022 02:23:24 +0400 Subject: [PATCH 369/548] engine: common: net_ws: fixed sockets reinitialization in NET_OpenIP --- engine/common/net_ws.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/common/net_ws.c b/engine/common/net_ws.c index 675ffab0..2dfcea62 100644 --- a/engine/common/net_ws.c +++ b/engine/common/net_ws.c @@ -1484,7 +1484,7 @@ static void NET_OpenIP( qboolean change_port ) if( NET_IsSocketValid( net.ip_sockets[NS_SERVER] )) closesocket( net.ip_sockets[NS_SERVER] ); - net.ip_sockets[NS_SERVER] = 0; + net.ip_sockets[NS_SERVER] = INVALID_SOCKET; ClearBits( net_hostport->flags, FCVAR_CHANGED ); } @@ -1516,7 +1516,7 @@ static void NET_OpenIP( qboolean change_port ) if( NET_IsSocketValid(net.ip_sockets[NS_CLIENT] )) closesocket( net.ip_sockets[NS_CLIENT] ); - net.ip_sockets[NS_CLIENT] = 0; + net.ip_sockets[NS_CLIENT] = INVALID_SOCKET; ClearBits( net_clientport->flags, FCVAR_CHANGED ); } From fce3959d150b3879049056a7dfb4674df9ebf1d6 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Sun, 10 Jul 2022 02:45:18 +0400 Subject: [PATCH 370/548] engine: server: added "c" command to SV_ConnectionlessPacket --- engine/server/sv_client.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c index e1215491..29b47c15 100644 --- a/engine/server/sv_client.c +++ b/engine/server/sv_client.c @@ -2266,6 +2266,13 @@ void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) else if( !Q_strcmp( pcmd, "s" )) SV_AddToMaster( from, msg ); else if( !Q_strcmp( pcmd, "T" "Source" )) SV_TSourceEngineQuery( from ); else if( !Q_strcmp( pcmd, "i" )) NET_SendPacket( NS_SERVER, 5, "\xFF\xFF\xFF\xFFj", from ); // A2A_PING + else if (!Q_strcmp( pcmd, "c" )) + { + netadr_t to; + + if( NET_StringToAdr( Cmd_Argv( 1 ), &to )) + SV_Info( to, PROTOCOL_VERSION ); + } else if( svgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) { // user out of band message (must be handled in CL_ConnectionlessPacket) From d8724f0be4f35d7bc38032fe42caa67225946951 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Sun, 10 Jul 2022 03:34:14 +0400 Subject: [PATCH 371/548] engine: common: net_ws: disabled read-only flag for ip cvar --- engine/common/net_ws.c | 2 +- engine/server/sv_client.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/common/net_ws.c b/engine/common/net_ws.c index 2dfcea62..999fb705 100644 --- a/engine/common/net_ws.c +++ b/engine/common/net_ws.c @@ -1731,7 +1731,7 @@ void NET_Init( void ) net_clockwindow = Cvar_Get( "clockwindow", "0.5", FCVAR_PRIVILEGED, "timewindow to execute client moves" ); net_address = Cvar_Get( "net_address", "0", FCVAR_READ_ONLY, "contain local address of current client" ); - net_ipname = Cvar_Get( "ip", "localhost", FCVAR_READ_ONLY, "network ip address" ); + net_ipname = Cvar_Get( "ip", "localhost", 0, "network ip address" ); net_iphostport = Cvar_Get( "ip_hostport", "0", FCVAR_READ_ONLY, "network ip host port" ); net_hostport = Cvar_Get( "hostport", va( "%i", PORT_SERVER ), FCVAR_READ_ONLY, "network default host port" ); net_ipclientport = Cvar_Get( "ip_clientport", "0", FCVAR_READ_ONLY, "network ip client port" ); diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c index 29b47c15..93a7ae4a 100644 --- a/engine/server/sv_client.c +++ b/engine/server/sv_client.c @@ -2271,7 +2271,7 @@ void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) netadr_t to; if( NET_StringToAdr( Cmd_Argv( 1 ), &to )) - SV_Info( to, PROTOCOL_VERSION ); + SV_Info( to ); } else if( svgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) { From ab43486ee05f3d601f540e313f0e784538ea82af Mon Sep 17 00:00:00 2001 From: a1batross Date: Mon, 11 Jul 2022 02:43:26 +0300 Subject: [PATCH 372/548] engine: client: don't segfault when client wasn't loaded --- engine/client/input.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/engine/client/input.c b/engine/client/input.c index 83c89a06..69c41ca8 100644 --- a/engine/client/input.c +++ b/engine/client/input.c @@ -276,7 +276,8 @@ void IN_ActivateMouse( void ) return; IN_CheckMouseState( true ); - clgame.dllFuncs.IN_ActivateMouse(); + if( clgame.dllFuncs.IN_ActivateMouse ) + clgame.dllFuncs.IN_ActivateMouse(); in_mouseactive = true; } @@ -293,7 +294,8 @@ void IN_DeactivateMouse( void ) return; IN_CheckMouseState( false ); - clgame.dllFuncs.IN_DeactivateMouse(); + if( clgame.dllFuncs.IN_DeactivateMouse ) + clgame.dllFuncs.IN_DeactivateMouse(); in_mouseactive = false; } From fc84cd2a0ac06f4850de333061cd1226380e8436 Mon Sep 17 00:00:00 2001 From: a1batross Date: Mon, 11 Jul 2022 02:45:40 +0300 Subject: [PATCH 373/548] engine: common: always show message box when we're in normal mode Only dedicated server is expected to throw errors to console --- engine/common/system.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/engine/common/system.c b/engine/common/system.c index bfff6c66..18ba734f 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -421,9 +421,12 @@ void Sys_Error( const char *error, ... ) #if XASH_SDL == 2 if( host.hWnd ) SDL_HideWindow( host.hWnd ); #endif +#if XASH_WIN32 + Wcon_ShowConsole( false ); +#endif + MSGBOX( text ); } - - if( host_developer.value ) + else { #if XASH_WIN32 Wcon_ShowConsole( true ); @@ -432,14 +435,7 @@ void Sys_Error( const char *error, ... ) Sys_Print( text ); // print error message Sys_WaitForQuit(); } - else - { -#if XASH_WIN32 - Wcon_ShowConsole( false ); -#endif - MSGBOX( text ); - } - + Sys_Quit(); } From 5bf6f814ef1b632e64bac6822c5c5b1e589b7efd Mon Sep 17 00:00:00 2001 From: Andrey Akhmichin <15944199+nekonomicon@users.noreply.github.com> Date: Wed, 13 Jul 2022 05:59:21 +0500 Subject: [PATCH 374/548] Documentation: opensource-mods.md: update links. --- Documentation/opensource-mods.md | 89 ++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/Documentation/opensource-mods.md b/Documentation/opensource-mods.md index 3f50bb6c..88676a33 100644 --- a/Documentation/opensource-mods.md +++ b/Documentation/opensource-mods.md @@ -17,7 +17,9 @@ Mirrored on github - https://github.com/JoelTroch/am_src_rebirth Mirrored on github - https://github.com/nekonomicon/BattleGrounds ## Bubblemod -Download page on official site - http://www.bubblemod.org/dl_default.php +Download page on official site - http://www.bubblemod.org/dl_default.php (dead link!) + +Mirrored on github - https://github.com/HLSources/BubbleMod ## Chicken Fortress Official github repository - https://github.com/CKFDevPowered/CKF3Alpha @@ -43,11 +45,13 @@ Official github repository - https://github.com/adslbarxatov/xash3d-for-ESHQ Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/Half-Life_2D_(Flat-Life) ## Gang Wars -Mirrored on github - https://github.com/hammermaps/gw1.45src +Mirrored on github - https://github.com/nekonomicon/gw1.45src ## Go-mod Versions 2.0 and 3.0, available in mod archives on ModDB - https://www.moddb.com/mods/go-mod/downloads +Version 3.0, mirrored on github - https://github.com/nekonomicon/Go-mod30 + ## GT mod Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/GT_mod_(Polnie_ishodniki) @@ -64,10 +68,10 @@ Available on ModDB - https://www.moddb.com/mods/half-life-echoes Available on ModDB - https://www.moddb.com/mods/half-life-expanded-arsenal ## Half-Life: Gravgun mod -Branch **gravgun** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/gravgun +Branch **gravgun** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/gravgun ## Half-Life: Invasion -official github repository - https://github.com/jlecorre/hlinvasion +Official github repository - https://github.com/jlecorre/hlinvasion ## Half-Life: Pong Mirrored on github - https://github.com/solidi/hl-mods/tree/master/pong @@ -106,7 +110,7 @@ Official github repository - https://github.com/desukuran/half-screwed Version 1.3 on ModDB - https://www.moddb.com/mods/headcrab-frenzy/downloads/headcrab-frenzy-13-beta-source-code ## Heart of Evil -Available on ModDB - https://www.moddb.com/mods/heart-of-evil/downloads/heart-of-evil-dlls-source-code +Mirrored on github - https://github.com/nekonomicon/HeartOfEvil ## Ingram Chillin' Mod Official SourceForge repository - https://sourceforge.net/projects/icm-hl/ @@ -169,7 +173,7 @@ Mirrored on github - https://github.com/ZXCmod/ZXCmod # Reimplementation ## Absolute Redemption -Branch **redempt** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/redempt +Branch **redempt** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/redempt ## Adrenaline Gamer OpenAG by YaLTeR - https://github.com/YaLTeR/OpenAG @@ -177,34 +181,34 @@ OpenAG by YaLTeR - https://github.com/YaLTeR/OpenAG ## Afraid of Monsters malortie's recreation - https://github.com/malortie/hl-aom -Branch **aom** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/aom +Branch **aom** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aom ## Afraid of Monsters: Director's cut -Reverse-engineered code, branch **aomdc** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/aomdc +Reverse-engineered code, branch **aomdc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aomdc ## Azure Sheep malortie's recreation - https://github.com/malortie/hl-asheep -Reverse-engineered code, branch **asheep** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/asheep +Reverse-engineered code, branch **asheep** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/asheep ## Big Lolly malortie's recreation - https://github.com/malortie/hl-biglolly -Branch **biglolly** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/biglolly +Branch **biglolly** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/biglolly ## Black Ops malortie's recreation - https://github.com/malortie/hl-blackops -Branch **blackops** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/blackops +Branch **blackops** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/blackops ## Bloody Pizza: Vendetta -Branch **caseclosed** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/caseclosed +Branch **caseclosed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/caseclosed ## Case Closed -Branch **caseclosed** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/caseclosed +Branch **caseclosed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/caseclosed ## Cleaner's Adventures -Branch **CAd** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/CAd +Branch **CAd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/CAd ## Counter Strike Reverse-engineered code of client part by a1batross - https://github.com/FWGS/cs16-client/tree/v1.32 @@ -222,60 +226,60 @@ Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/crack_lif ## Escape from the Darkness malortie's recreation - https://github.com/malortie/hl-eftd -Reverse-engineered code, branch **eftd** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/eftd +Reverse-engineered code, branch **eftd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/eftd ## Half-Life: Blue Shift Unkle Mike's recreation - https://hlfx.ru/forum/showthread.php?s=&threadid=5253 -Reverse-engineered code, branch **bshift** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/bshift +Reverse-engineered code, branch **bshift** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bshift ## Half-Life: Induction -Branch **induction** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/induction +Branch **induction** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/induction ## Half-Life: Opposing Force Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/hlsdk-xash3d -Reverse-engineered code, clean branch **opfor** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/opfor +Reverse-engineered code, clean branch **opfor** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/opfor Spirit of Half Life: Opposing-Force Edition - https://github.com/Hammermaps-DEV/SOHL-V1.9-Opposing-Force-Edition ## Half-Life: Rebellion -Reverse-engineered code, branch **rebellion** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/rebellion +Reverse-engineered code, branch **rebellion** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/rebellion ## Half-Life: Urbicide -Branch **hl_urbicide** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/hl_urbicide +Branch **hl_urbicide** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/hl_urbicide ## Half-Life: Visitors malortie's recreation - https://github.com/malortie/hl-visitors -Reverse-engineered code, branch **visitors** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/visitors +Reverse-engineered code, branch **visitors** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/visitors ## Half-Secret -Branch **half-secret** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/half-secret +Branch **half-secret** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/half-secret ## Night at the Office malortie's recreation - https://github.com/malortie/hl-nato -Branch **noffice** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/noffice +Branch **noffice** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/noffice ## Poke 646 malortie's recreation - https://github.com/malortie/hl-poke646 -Reverse-engineered code, branch **poke646** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/poke646 +Reverse-engineered code, branch **poke646** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646 ## Poke 646: Vendetta malortie's recreation - https://github.com/malortie/hl-poke646-vendetta -Reverse-engineered code, branch **poke646_vendetta** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/poke646_vendetta +Reverse-engineered code, branch **poke646_vendetta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646_vendetta ## Residual Life -Reverse-engineered code, branch **residual_point** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/residual_point +Reverse-engineered code, branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point ## Residual Point -Reverse-engineered code, branch **residual_point** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/residual_point +Reverse-engineered code, branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point ## Sewer Beta -Branch **sewer_beta** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/sewer_beta +Branch **sewer_beta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sewer_beta ## Team Fortress Classic Reverse-engineered code by Velaron - https://github.com/Velaron/tf15-client @@ -283,59 +287,64 @@ Reverse-engineered code by Velaron - https://github.com/Velaron/tf15-client ## The Gate malortie's recreation - https://github.com/malortie/hl-thegate -Reverse-engineered code, branch **thegate** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/thegate +Reverse-engineered code, branch **thegate** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/thegate ## They Hunger malortie's recreation - https://github.com/malortie/hl-theyhunger -Reverse-engineered code, branch **theyhunger** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/theyhunger +Reverse-engineered code, branch **theyhunger** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/theyhunger They Hunger: Tactical - https://www.moddb.com/mods/they-hunger-tactical/downloads/tht-source-code-documentation ## Times of Troubles malortie's recreation - https://github.com/malortie/hl-tot -Branch **tot** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/tot +Branch **tot** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/tot # Derived work ## Adrenaline Gamer -Branch **aghl** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/aghl +Branch **aghl** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aghl ## Bubblemod -Branch **bubblemod** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/bubblemod +Branch **bubblemod** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bubblemod ## Deathmatch Classic Deathmatch Classic: Adrenaline Gamer Edition - https://github.com/martinwebrant/agmod/tree/master/src/dmc Deathmatch Quaked - https://www.moddb.com/games/deathmatch-classic/downloads/dmq2 -Branch **dmc** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/dmc +Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/dmc ## Half-Life: Echoes -Branch **echoes** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/echoes +Branch **echoes** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/echoes ## Half-Life: Invasion Port to HLSDK 2.4 by malortie - https://github.com/malortie/hl-invasion Port to Linux by fmoraw - https://github.com/fmoraw/hlinvasion +Branch **invasion** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/invasion + ## Half-Life: Top-Down -Branch **hltopdown** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/hltopdown +Branch **hltopdown** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/hltopdown ## Half-Screwed -Branch **half-screwed** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/half-screwed +Branch **half-screwed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/half-screwed ## Natural Selection Port to Linux - https://github.com/fmoraw/NS +## Spirit of Half-Life +Version 1.2, branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2 + ## Swiss Cheese Halloween 2002 Just more playable version by malortie - https://github.com/malortie/hl-shall -Branch **halloween** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/halloween +Branch **halloween** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/halloween ## Threewave CTF -Branch **dmc** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/dmc +Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/dmc ## Zombie-X -Branch **zombie-x** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/zombie-x +Branch **zombie-x** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/zombie-x From a2d11f670a10aca5099034bcddc32faa81226a55 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 13 Jul 2022 19:20:43 +0300 Subject: [PATCH 375/548] engine, public: fix float precision issues in mathlib and monster navigation code --- engine/server/sv_move.c | 2 +- public/xash3d_mathlib.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/engine/server/sv_move.c b/engine/server/sv_move.c index 491e0a89..48753f21 100644 --- a/engine/server/sv_move.c +++ b/engine/server/sv_move.c @@ -243,7 +243,7 @@ float SV_VecToYaw( const vec3_t src ) } else { - yaw = (int)( atan2( src[1], src[0] ) * 180.0f / M_PI_F ); + yaw = (int)( atan2( src[1], src[0] ) * 180.0 / M_PI ); if( yaw < 0 ) yaw += 360.0f; } return yaw; diff --git a/public/xash3d_mathlib.h b/public/xash3d_mathlib.h index 985e5d1f..363090d2 100644 --- a/public/xash3d_mathlib.h +++ b/public/xash3d_mathlib.h @@ -34,18 +34,18 @@ GNU General Public License for more details. #define ROLL 2 #ifndef M_PI -#define M_PI (float)3.14159265358979323846 +#define M_PI (double)3.14159265358979323846 #endif #ifndef M_PI2 -#define M_PI2 ((float)(M_PI * 2)) +#define M_PI2 ((double)(M_PI * 2)) #endif #define M_PI_F ((float)(M_PI)) #define M_PI2_F ((float)(M_PI2)) -#define RAD2DEG( x ) ((float)(x) * (float)(180.f / M_PI_F)) -#define DEG2RAD( x ) ((float)(x) * (float)(M_PI_F / 180.f)) +#define RAD2DEG( x ) ((double)(x) * (double)(180.0 / M_PI)) +#define DEG2RAD( x ) ((double)(x) * (double)(M_PI / 180.0)) #define NUMVERTEXNORMALS 162 From 5e1f189db32431f5bef414f7f7c7331e1ccf61ee Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 13 Jul 2022 19:23:45 +0300 Subject: [PATCH 376/548] engine: platform: posix: use RTLD_NOW instead of lazy. It actually was a misconception coming from old engine fork We want to track unresolved symbols before library could be loaded Also, disable "symbol not found" spam in FunctionFromName. Due to how savefile mangling convert works and compatibility with GoldSrc saves, this function is used to bruteforce possible symbol names. --- engine/platform/posix/lib_posix.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/engine/platform/posix/lib_posix.c b/engine/platform/posix/lib_posix.c index c4026706..abfd46ad 100644 --- a/engine/platform/posix/lib_posix.c +++ b/engine/platform/posix/lib_posix.c @@ -102,7 +102,7 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d // try to find by linker(LD_LIBRARY_PATH, DYLD_LIBRARY_PATH, LD_32_LIBRARY_PATH and so on...) if( !pHandle ) { - pHandle = dlopen( dllname, RTLD_LAZY ); + pHandle = dlopen( dllname, RTLD_NOW ); if( pHandle ) return pHandle; @@ -139,7 +139,7 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d else #endif { - if( !( hInst->hInstance = dlopen( hInst->fullPath, RTLD_LAZY ) ) ) + if( !( hInst->hInstance = dlopen( hInst->fullPath, RTLD_NOW ) ) ) { COM_PushLibraryError( dlerror() ); Mem_Free( hInst ); @@ -188,12 +188,7 @@ void *COM_GetProcAddress( void *hInstance, const char *name ) void *COM_FunctionFromName( void *hInstance, const char *pName ) { - void *function; - if( !( function = COM_GetProcAddress( hInstance, pName ) ) ) - { - Con_Reportf( S_ERROR "FunctionFromName: Can't get symbol %s: %s\n", pName, dlerror()); - } - return function; + return COM_GetProcAddress( hInstance, pName ); } #ifdef XASH_DYNAMIC_DLADDR From 657a6af9dcab2bd375534283c6bf59a8c95d0856 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 13 Jul 2022 19:45:42 +0300 Subject: [PATCH 377/548] mainui: update --- mainui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainui b/mainui index 2ba099b2..97fcbf89 160000 --- a/mainui +++ b/mainui @@ -1 +1 @@ -Subproject commit 2ba099b2a1b32dfdb4ecb66b27fc6df095199eb4 +Subproject commit 97fcbf8979f22d774b1cc01cb5553743592d39d0 From 772f4dcb60a8a2594df40195af4a0d4bd8ea7863 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 14 Jul 2022 18:26:47 +0300 Subject: [PATCH 378/548] scripts: gha: win32: fix build type from debug to release --- scripts/gha/build_win32.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gha/build_win32.sh b/scripts/gha/build_win32.sh index 5f36d124..be8bc506 100755 --- a/scripts/gha/build_win32.sh +++ b/scripts/gha/build_win32.sh @@ -11,7 +11,7 @@ fi # NOTE: to build with other version use --msvc_version during configuration # NOTE: sometimes you may need to add WinSDK to %PATH% -./waf.bat configure -s "SDL2_VC" -T "debug" --enable-utils --prefix=`pwd` $AMD64 || die +./waf.bat configure -s "SDL2_VC" -T "release" --enable-utils --prefix=`pwd` $AMD64 || die ./waf.bat build -v || die ./waf.bat install || die From aaab09fd382d87fa306866244fdab0d34c6e3e9d Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Fri, 15 Jul 2022 00:35:19 -0700 Subject: [PATCH 379/548] vk: remove old ring buffer helper --- ref_vk/vk_buffer.c | 43 ------------------------------------------- ref_vk/vk_buffer.h | 28 ---------------------------- ref_vk/vk_rtx.c | 20 ++++++++++++++------ 3 files changed, 14 insertions(+), 77 deletions(-) diff --git a/ref_vk/vk_buffer.c b/ref_vk/vk_buffer.c index 20ea9649..1f10f2d8 100644 --- a/ref_vk/vk_buffer.c +++ b/ref_vk/vk_buffer.c @@ -42,49 +42,6 @@ void VK_BufferDestroy(vk_buffer_t *buf) { } } -void VK_RingBuffer_Clear(vk_ring_buffer_t* buf) { - buf->offset_free = 0; - buf->permanent_size = 0; - buf->free = buf->size; -} - -// < v-> -// |MAP|.........|FRAME|...| -// ^ XXXXX - -uint32_t VK_RingBuffer_Alloc(vk_ring_buffer_t* buf, uint32_t size, uint32_t align) { - uint32_t offset = ALIGN_UP(buf->offset_free, align); - const uint32_t align_diff = offset - buf->offset_free; - uint32_t available = buf->free - align_diff; - const uint32_t tail = buf->size - offset; - - if (available < size) - return AllocFailed; - - if (size > tail) { - offset = ALIGN_UP(buf->permanent_size, align); - available -= (offset - buf->permanent_size) - tail; - } - - if (available < size) - return AllocFailed; - - buf->offset_free = offset + size; - buf->free = available - size; - - return offset; -} - -void VK_RingBuffer_Fix(vk_ring_buffer_t* buf) { - ASSERT(buf->permanent_size == 0); - buf->permanent_size = buf->offset_free; -} - -void VK_RingBuffer_ClearFrame(vk_ring_buffer_t* buf) { - buf->offset_free = buf->permanent_size; - buf->free = buf->size - buf->permanent_size; -} - VkDeviceAddress XVK_BufferGetDeviceAddress(VkBuffer buffer) { const VkBufferDeviceAddressInfo bdai = {.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, .buffer = buffer}; return vkGetBufferDeviceAddress(vk_core.device, &bdai); diff --git a/ref_vk/vk_buffer.h b/ref_vk/vk_buffer.h index f6538479..37c78aaf 100644 --- a/ref_vk/vk_buffer.h +++ b/ref_vk/vk_buffer.h @@ -18,34 +18,6 @@ void VK_BufferDestroy(vk_buffer_t *buf); VkDeviceAddress XVK_BufferGetDeviceAddress(VkBuffer buffer); -// v -- begin of ring buffer|permanent_size -// |XXXMAPLIFETME|<......|FRAME1|FRAME2|FRAMEN|......................>| -// busy pos - ^ ^ ^ ^ -- write pos | offset_free -typedef struct { - uint32_t size; - uint32_t permanent_size; - uint32_t offset_free; - uint32_t free; - - // TODO per-frame offsets for many frames in flight -} vk_ring_buffer_t; - -enum { AllocFailed = 0xffffffffu }; - -// Marks the entire buffer as free -void VK_RingBuffer_Clear(vk_ring_buffer_t* buf); - -// Allocates a new aligned region and returns offset to it (-1 if allocation failed) -uint32_t VK_RingBuffer_Alloc(vk_ring_buffer_t* buf, uint32_t size, uint32_t align); - -// Fixes everything that has been allocated since Clear as permanent, ring buffer will operate on the remainder only -// Can be called only once since Clear -void VK_RingBuffer_Fix(vk_ring_buffer_t* buf); - -// Clears non-permantent part of the buffer -void VK_RingBuffer_ClearFrame(vk_ring_buffer_t* buf); - - typedef struct { alo_ring_t ring; uint32_t frame_offsets[2]; diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index b4a2795e..14b8fce0 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -18,6 +18,8 @@ #include "vk_denoiser.h" #include "vk_math.h" +#include "alolcator.h" + #include "eiface.h" #include "xash3d_mathlib.h" @@ -82,7 +84,7 @@ static struct { // TODO: unify this with render buffer // Needs: AS_STORAGE_BIT, SHADER_DEVICE_ADDRESS_BIT vk_buffer_t accels_buffer; - vk_ring_buffer_t accels_buffer_alloc; + struct alo_pool_s *accels_buffer_alloc; // Temp: lives only during a single frame (may have many in flight) // Used for building ASes; @@ -199,7 +201,8 @@ qboolean createOrUpdateAccelerationStructure(VkCommandBuffer cmdbuf, const as_bu if (should_create) { const uint32_t as_size = build_size.accelerationStructureSize; - const uint32_t buffer_offset = VK_RingBuffer_Alloc(&g_rtx.accels_buffer_alloc, as_size, 256); + const alo_block_t block = aloPoolAllocate(g_rtx.accels_buffer_alloc, as_size, /*TODO why? align=*/256); + const uint32_t buffer_offset = block.offset; const VkAccelerationStructureCreateInfoKHR asci = { .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR, .buffer = g_rtx.accels_buffer.buffer, @@ -208,7 +211,7 @@ qboolean createOrUpdateAccelerationStructure(VkCommandBuffer cmdbuf, const as_bu .size = as_size, }; - if (buffer_offset == AllocFailed) { + if (buffer_offset == ALO_ALLOC_FAILED) { gEngine.Con_Printf(S_ERROR "Failed to allocated %u bytes for accel buffer\n", asci.size); return false; } @@ -280,9 +283,13 @@ static void createTlas( VkCommandBuffer cmdbuf, VkDeviceAddress instances_addr ) } void VK_RayNewMap( void ) { + const int expected_accels = 512; // TODO actually get this from playing the game + const int accels_alignment = 256; // TODO where does this come from? ASSERT(vk_core.rtx); - VK_RingBuffer_Clear(&g_rtx.accels_buffer_alloc); + if (g_rtx.accels_buffer_alloc) + aloPoolDestroy(g_rtx.accels_buffer_alloc); + g_rtx.accels_buffer_alloc = aloPoolCreate(MAX_ACCELS_BUFFER, expected_accels, accels_alignment); // Clear model cache for (int i = 0; i < ARRAYSIZE(g_ray_model_state.models_cache); ++i) { @@ -305,7 +312,6 @@ void VK_RayNewMap( void ) { } void VK_RayMapLoadEnd( void ) { - VK_RingBuffer_Fix(&g_rtx.accels_buffer_alloc); } void VK_RayFrameBegin( void ) @@ -826,7 +832,6 @@ qboolean VK_RayInit( void ) return false; } g_rtx.accels_buffer_addr = getBufferDeviceAddress(g_rtx.accels_buffer.buffer); - g_rtx.accels_buffer_alloc.size = g_rtx.accels_buffer.size; if (!VK_BufferCreate("ray scratch_buffer", &g_rtx.scratch_buffer, MAX_SCRATCH_BUFFER, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, @@ -953,4 +958,7 @@ void VK_RayShutdown( void ) { VK_BufferDestroy(&g_ray_model_state.lights_buffer); VK_BufferDestroy(&g_rtx.light_grid_buffer); VK_BufferDestroy(&g_rtx.uniform_buffer); + + if (g_rtx.accels_buffer_alloc) + aloPoolDestroy(g_rtx.accels_buffer_alloc); } From 3ad60a0fa1af834b13a73051bf5cd535ee83b74a Mon Sep 17 00:00:00 2001 From: Valery Klachkov Date: Sat, 16 Jul 2022 13:59:41 +0000 Subject: [PATCH 380/548] engine: fix uninitialized variable in demo parsing code, fix incorrect size counter in memory allocator --- engine/client/cl_demo.c | 2 +- engine/common/zone.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index 7443d51b..92ebdd2b 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -888,7 +888,7 @@ qboolean CL_DemoReadMessage( byte *buffer, size_t *length ) qboolean swallowmessages = true; static int tdlastdemoframe = 0; byte *userbuf = NULL; - size_t size; + size_t size = 0; byte cmd; if( !cls.demofile ) diff --git a/engine/common/zone.c b/engine/common/zone.c index f3200ed1..4ca83f65 100644 --- a/engine/common/zone.c +++ b/engine/common/zone.c @@ -91,8 +91,8 @@ void *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char pool->totalsize += size; // big allocations are not clumped - pool->realsize += sizeof( memheader_t ) + size + sizeof( int ); - mem = (memheader_t *)Q_malloc( sizeof( memheader_t ) + size + sizeof( int )); + pool->realsize += sizeof( memheader_t ) + size + sizeof( size_t ); + mem = (memheader_t *)Q_malloc( sizeof( memheader_t ) + size + sizeof( size_t )); if( mem == NULL ) Sys_Error( "Mem_Alloc: out of memory (alloc at %s:%i)\n", filename, fileline ); mem->filename = filename; @@ -162,7 +162,7 @@ static void Mem_FreeBlock( memheader_t *mem, const char *filename, int fileline // memheader has been unlinked, do the actual free now pool->totalsize -= mem->size; - pool->realsize -= sizeof( memheader_t ) + mem->size + sizeof( int ); + pool->realsize -= sizeof( memheader_t ) + mem->size + sizeof( size_t ); Q_free( mem ); } From c326853617c2cea82173441b5958d240343a4867 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 21 Jul 2022 01:52:10 +0300 Subject: [PATCH 381/548] engine: server: restore original PEntityOfEntIndex function, but still bug-compatible with GoldSrc --- engine/server/sv_game.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index d7343fb0..f3fe6e6e 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -62,16 +62,23 @@ qboolean SV_CheckEdict( const edict_t *e, const char *file, const int line ) static edict_t *SV_PEntityOfEntIndex( const int iEntIndex, const qboolean allentities ) { - edict_t *pEdict = EDICT_NUM( iEntIndex ); - qboolean player = allentities ? iEntIndex <= svs.maxclients : iEntIndex < svs.maxclients; + if( iEntIndex >= 0 && iEntIndex < GI->max_edicts ) + { + edict_t *pEdict = EDICT_NUM( iEntIndex ); + qboolean player = allentities ? iEntIndex <= svs.maxclients : iEntIndex < svs.maxclients; - if( !SV_IsValidEdict( pEdict )) - return NULL; + if( !iEntIndex || FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + return pEdict; // just get access to array - if( !player && !pEdict->pvPrivateData ) - return NULL; + if( SV_IsValidEdict( pEdict ) && pEdict->pvPrivateData ) + return pEdict; - return pEdict; + // g-cont: world and clients can be accessed even without private data + if( SV_IsValidEdict( pEdict ) && player ) + return pEdict; + } + + return NULL; } From ceea3a614e3afeab50dfa3753159bfff04e1f20e Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 23 Jul 2022 11:18:52 -0700 Subject: [PATCH 382/548] vk: do not forget to upload staging without rays --- ref_vk/vk_rtx.c | 3 --- ref_vk/vk_scene.c | 10 +++++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 14b8fce0..cfe15ce9 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -311,9 +311,6 @@ void VK_RayNewMap( void ) { RT_RayModel_Clear(); } -void VK_RayMapLoadEnd( void ) { -} - void VK_RayFrameBegin( void ) { ASSERT(vk_core.rtx); diff --git a/ref_vk/vk_scene.c b/ref_vk/vk_scene.c index 60a91568..e9d696be 100644 --- a/ref_vk/vk_scene.c +++ b/ref_vk/vk_scene.c @@ -216,7 +216,13 @@ void R_NewMap( void ) { // Reads surfaces from loaded brush models (must happen after all brushes are loaded) RT_LightsNewMapEnd(map); - R_VKStagingMarkEmpty_FIXME(); + if (!vk_core.rtx) { + // FIXME this is a workaround for uploading staging for non-rtx mode. In rtx mode things get naturally uploaded deep in VK_BrushModelLoad. + // FIXME there shouldn't be this difference. Ideally, rtx would only continue with also building BLASes, but uploading part should be the same. + R_VkStagingFlushSync(); + } else { + R_VKStagingMarkEmpty_FIXME(); + } if (vk_core.rtx) { @@ -234,8 +240,6 @@ void R_NewMap( void ) { // TODO should we do something like VK_BrushEndLoad? VK_UploadLightmap(); - if (vk_core.rtx) - VK_RayMapLoadEnd(); } qboolean R_AddEntity( struct cl_entity_s *clent, int type ) From 658a67e09e62c65aa5fc167dcfc6dd9cef46530f Mon Sep 17 00:00:00 2001 From: Valery Klachkov Date: Sat, 16 Jul 2022 01:50:08 +0000 Subject: [PATCH 383/548] Fix #634 --- engine/client/cl_demo.c | 2 +- engine/common/zone.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index 7443d51b..92ebdd2b 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -888,7 +888,7 @@ qboolean CL_DemoReadMessage( byte *buffer, size_t *length ) qboolean swallowmessages = true; static int tdlastdemoframe = 0; byte *userbuf = NULL; - size_t size; + size_t size = 0; byte cmd; if( !cls.demofile ) diff --git a/engine/common/zone.c b/engine/common/zone.c index f3200ed1..4ca83f65 100644 --- a/engine/common/zone.c +++ b/engine/common/zone.c @@ -91,8 +91,8 @@ void *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char pool->totalsize += size; // big allocations are not clumped - pool->realsize += sizeof( memheader_t ) + size + sizeof( int ); - mem = (memheader_t *)Q_malloc( sizeof( memheader_t ) + size + sizeof( int )); + pool->realsize += sizeof( memheader_t ) + size + sizeof( size_t ); + mem = (memheader_t *)Q_malloc( sizeof( memheader_t ) + size + sizeof( size_t )); if( mem == NULL ) Sys_Error( "Mem_Alloc: out of memory (alloc at %s:%i)\n", filename, fileline ); mem->filename = filename; @@ -162,7 +162,7 @@ static void Mem_FreeBlock( memheader_t *mem, const char *filename, int fileline // memheader has been unlinked, do the actual free now pool->totalsize -= mem->size; - pool->realsize -= sizeof( memheader_t ) + mem->size + sizeof( int ); + pool->realsize -= sizeof( memheader_t ) + mem->size + sizeof( size_t ); Q_free( mem ); } From 8baf34768a8a2d0e0abaf997ef11c856a3c7534a Mon Sep 17 00:00:00 2001 From: NightFox <0x4E69676874466F78@users.noreply.github.com> Date: Tue, 31 May 2022 16:32:58 +0300 Subject: [PATCH 384/548] correct environment light power --- ref_vk/shaders/light.glsl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index e06827a4..81d49a45 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -74,6 +74,8 @@ void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec //const float pdf = TWO_PI / asin(radius / dist); const float pdf = 1. / ((1. - sqrt(d2 - r2) / dist) * spot_attenuation); color /= pdf; + } else { + color *= 2; } // if (dot(color,color) < color_culling_threshold) From 9b76f3acb4026adbb5964d58e0eafbe6201c75d1 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 23 Jul 2022 12:06:43 -0700 Subject: [PATCH 385/548] rt: fix missing moving polygon lights --- ref_vk/vk_ray_model.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index 21ce497e..c9719a01 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -420,7 +420,7 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render for (int i = 0; i < render_model->polylights_count; ++i) { rt_light_add_polygon_t *const polylight = render_model->polylights + i; - polylight->transform_row = (const matrix3x4*)model; + polylight->transform_row = (const matrix3x4*)transform_row; polylight->dynamic = true; RT_LightAddPolygon(polylight); } From af594203f8c2025b9e92c4136e9c25e7920f8db1 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 23 Jul 2022 12:43:52 -0700 Subject: [PATCH 386/548] rt: fix point light cluster crash on missing pvs will still crash for polygon lights --- ref_vk/vk_light.c | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 67420abe..615f3b84 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -235,6 +235,7 @@ static void leafAccumFinalize( void ) { static int leafAccumAddPotentiallyVisibleFromLeaf(const model_t *const map, const mleaf_t *leaf, qboolean print_debug) { int pvs_leaf_index = 0; int leafs_added = 0; + ASSERT(leaf->compressed_vis); const byte *pvs = leaf->compressed_vis; for (;pvs_leaf_index < map->numleafs; ++pvs) { uint8_t bits = pvs[0]; @@ -772,9 +773,25 @@ static void addLightIndexToleaf( const mleaf_t *leaf, int index ) { } } -static void addPointLightToClusters( int index ) { - vk_point_light_t *const light = g_lights.point_lights + index; +static void addPointLightToAllClusters( int index ) { const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); + + clusterBitMapClear(); + for (int i = 1; i <= world->numleafs; ++i) { + const mleaf_t *const leaf = world->leafs + i; + addLightIndexToleaf( leaf, index ); + } +} + +static void addPointLightToClusters( int index ) { + const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); + + if (!world->visdata) { + addPointLightToAllClusters( index ); + return; + } + + vk_point_light_t *const light = g_lights.point_lights + index; const mleaf_t* leaf = gEngine.Mod_PointInLeaf(light->origin, world->nodes); const vk_light_leaf_set_t *const leafs = (vk_light_leaf_set_t*)&g_lights_bsp.accum.count; @@ -789,16 +806,6 @@ static void addPointLightToClusters( int index ) { } } -static void addPointLightToAllClusters( int index ) { - const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); - - clusterBitMapClear(); - for (int i = 1; i <= world->numleafs; ++i) { - const mleaf_t *const leaf = world->leafs + i; - addLightIndexToleaf( leaf, index ); - } -} - static int addPointLight( const vec3_t origin, const vec3_t color, float radius, int lightstyle, float hack_attenuation ) { const int index = g_lights.num_point_lights; vk_point_light_t *const plight = g_lights.point_lights + index; From 8291efd08bbc611a516cafa385f3b44ca33620e9 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 23 Jul 2022 13:27:33 -0700 Subject: [PATCH 387/548] rt: fix polygon light clusters on missing pvs --- ref_vk/vk_light.c | 140 +++++++++++++++++++++++++++------------------- 1 file changed, 81 insertions(+), 59 deletions(-) diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 615f3b84..e1916fa4 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -733,7 +733,7 @@ fin: } #endif -static void addLightIndexToleaf( const mleaf_t *leaf, int index ) { +static void addLightIndexToLeaf( const mleaf_t *leaf, int index ) { const int min_x = floorf(leaf->minmaxs[0] / LIGHT_GRID_CELL_SIZE); const int min_y = floorf(leaf->minmaxs[1] / LIGHT_GRID_CELL_SIZE); const int min_z = floorf(leaf->minmaxs[2] / LIGHT_GRID_CELL_SIZE); @@ -776,10 +776,13 @@ static void addLightIndexToleaf( const mleaf_t *leaf, int index ) { static void addPointLightToAllClusters( int index ) { const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); + // TODO there's certainly a better way to do this: just enumerate + // all clusters, not all leafs + clusterBitMapClear(); for (int i = 1; i <= world->numleafs; ++i) { const mleaf_t *const leaf = world->leafs + i; - addLightIndexToleaf( leaf, index ); + addLightIndexToLeaf( leaf, index ); } } @@ -802,7 +805,7 @@ static void addPointLightToClusters( int index ) { clusterBitMapClear(); for (int i = 0; i < leafs->num; ++i) { const mleaf_t *const leaf = world->leafs + leafs->leafs[i]; - addLightIndexToleaf( leaf, index ); + addLightIndexToLeaf( leaf, index ); } } @@ -1175,6 +1178,76 @@ void VK_LightsFrameFinalize( void ) { APROF_SCOPE_END(finalize); } +static void addPolygonLightIndexToLeaf(const mleaf_t* leaf, int poly_index) { + const int min_x = floorf(leaf->minmaxs[0] / LIGHT_GRID_CELL_SIZE); + const int min_y = floorf(leaf->minmaxs[1] / LIGHT_GRID_CELL_SIZE); + const int min_z = floorf(leaf->minmaxs[2] / LIGHT_GRID_CELL_SIZE); + + const int max_x = floorf(leaf->minmaxs[3] / LIGHT_GRID_CELL_SIZE) + 1; + const int max_y = floorf(leaf->minmaxs[4] / LIGHT_GRID_CELL_SIZE) + 1; + const int max_z = floorf(leaf->minmaxs[5] / LIGHT_GRID_CELL_SIZE) + 1; + + const qboolean not_visible = false; //TODO static_map && !canSurfaceLightAffectAABB(world, geom->surf, esurf->emissive, leaf->minmaxs); + + if (debug_dump_lights.enabled) { + gEngine.Con_Reportf(" adding leaf %d min=(%d, %d, %d), max=(%d, %d, %d) total=%d\n", + leaf->cluster, + min_x, min_y, min_z, + max_x, max_y, max_z, + (max_x - min_x) * (max_y - min_y) * (max_z - min_z) + ); + } + + if (not_visible) + return; + + for (int x = min_x; x < max_x; ++x) + for (int y = min_y; y < max_y; ++y) + for (int z = min_z; z < max_z; ++z) { + const int cell[3] = { + x - g_lights.map.grid_min_cell[0], + y - g_lights.map.grid_min_cell[1], + z - g_lights.map.grid_min_cell[2] + }; + + const int cell_index = R_LightCellIndex( cell ); + if (cell_index < 0) + continue; + + if (clusterBitMapCheckOrSet( cell_index )) { + const float minmaxs[6] = { + x * LIGHT_GRID_CELL_SIZE, + y * LIGHT_GRID_CELL_SIZE, + z * LIGHT_GRID_CELL_SIZE, + (x+1) * LIGHT_GRID_CELL_SIZE, + (y+1) * LIGHT_GRID_CELL_SIZE, + (z+1) * LIGHT_GRID_CELL_SIZE, + }; + + /* TODO if (static_map && !canSurfaceLightAffectAABB(world, geom->surf, esurf->emissive, minmaxs)) */ + /* continue; */ + + if (!addSurfaceLightToCell(cell_index, poly_index)) { + ERROR_THROTTLED(10, "Cluster %d,%d,%d(%d) ran out of polygon light slots", + cell[0], cell[1], cell[2], cell_index); + } + } + } +} + +static void addPolygonLightToAllClusters( int poly_index ) { + const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); + + // TODO there's certainly a better way to do this: just enumerate + // all clusters, not all leafs + + clusterBitMapClear(); + for (int i = 1; i <= world->numleafs; ++i) { + const mleaf_t *const leaf = world->leafs + i; + addPolygonLightIndexToLeaf( leaf, poly_index ); + } +} + static void addPolygonLeafSetToClusters(const vk_light_leaf_set_t *leafs, int poly_index) { const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); @@ -1187,61 +1260,7 @@ static void addPolygonLeafSetToClusters(const vk_light_leaf_set_t *leafs, int po // Iterate through each visible/potentially affected leaf to get a range of grid cells for (int i = 0; i < leafs->num; ++i) { const mleaf_t *const leaf = world->leafs + leafs->leafs[i]; - - const int min_x = floorf(leaf->minmaxs[0] / LIGHT_GRID_CELL_SIZE); - const int min_y = floorf(leaf->minmaxs[1] / LIGHT_GRID_CELL_SIZE); - const int min_z = floorf(leaf->minmaxs[2] / LIGHT_GRID_CELL_SIZE); - - const int max_x = floorf(leaf->minmaxs[3] / LIGHT_GRID_CELL_SIZE) + 1; - const int max_y = floorf(leaf->minmaxs[4] / LIGHT_GRID_CELL_SIZE) + 1; - const int max_z = floorf(leaf->minmaxs[5] / LIGHT_GRID_CELL_SIZE) + 1; - - const qboolean not_visible = false; //TODO static_map && !canSurfaceLightAffectAABB(world, geom->surf, esurf->emissive, leaf->minmaxs); - - if (debug_dump_lights.enabled) { - gEngine.Con_Reportf(" adding leaf %d (%d of %d) min=(%d, %d, %d), max=(%d, %d, %d) total=%d\n", - leaf->cluster, i, leafs->num, - min_x, min_y, min_z, - max_x, max_y, max_z, - (max_x - min_x) * (max_y - min_y) * (max_z - min_z) - ); - } - - if (not_visible) - continue; - - for (int x = min_x; x < max_x; ++x) - for (int y = min_y; y < max_y; ++y) - for (int z = min_z; z < max_z; ++z) { - const int cell[3] = { - x - g_lights.map.grid_min_cell[0], - y - g_lights.map.grid_min_cell[1], - z - g_lights.map.grid_min_cell[2] - }; - - const int cell_index = R_LightCellIndex( cell ); - if (cell_index < 0) - continue; - - if (clusterBitMapCheckOrSet( cell_index )) { - const float minmaxs[6] = { - x * LIGHT_GRID_CELL_SIZE, - y * LIGHT_GRID_CELL_SIZE, - z * LIGHT_GRID_CELL_SIZE, - (x+1) * LIGHT_GRID_CELL_SIZE, - (y+1) * LIGHT_GRID_CELL_SIZE, - (z+1) * LIGHT_GRID_CELL_SIZE, - }; - - /* TODO if (static_map && !canSurfaceLightAffectAABB(world, geom->surf, esurf->emissive, minmaxs)) */ - /* continue; */ - - if (!addSurfaceLightToCell(cell_index, poly_index)) { - ERROR_THROTTLED(10, "Cluster %d,%d,%d(%d) ran out of polygon light slots", - cell[0], cell[1], cell[2], cell_index); - } - } - } + addPolygonLightIndexToLeaf(leaf, poly_index); } } @@ -1307,11 +1326,14 @@ int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { ); } - { + const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); + if (world->visdata) { const vk_light_leaf_set_t *const leafs = addpoly->dynamic ? getMapLeafsAffectedByMovingSurface( addpoly->surface, addpoly->transform_row ) : getMapLeafsAffectedByMapSurface( addpoly->surface ); addPolygonLeafSetToClusters(leafs, g_lights.num_polygons); + } else { + addPolygonLightToAllClusters( g_lights.num_polygons ); } g_lights.num_polygon_vertices += addpoly->num_vertices; From 0d0241d088e235a49a6dea4179c51ef82269a26c Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 23 Jul 2022 13:42:07 -0700 Subject: [PATCH 388/548] vk: recreate swapchain on all errors except fatal Unless we know the error is unrecoverable, try to recreate swapchain anyway. --- ref_vk/vk_swapchain.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/ref_vk/vk_swapchain.c b/ref_vk/vk_swapchain.c index 2e421583..dab6d5e9 100644 --- a/ref_vk/vk_swapchain.c +++ b/ref_vk/vk_swapchain.c @@ -200,19 +200,24 @@ r_vk_swapchain_framebuffer_t R_VkSwapchainAcquire( VkSemaphore sem_image_availa const VkResult acquire_result = vkAcquireNextImageKHR(vk_core.device, g_swapchain.swapchain, UINT64_MAX, sem_image_available, VK_NULL_HANDLE, &ret.index); switch (acquire_result) { - case VK_ERROR_OUT_OF_DATE_KHR: - case VK_ERROR_SURFACE_LOST_KHR: - case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: - gEngine.Con_Printf(S_WARN "vkAcquireNextImageKHR returned %s, recreating swapchain\n", R_VkResultName(acquire_result)); + case VK_SUCCESS: + break; + + case VK_ERROR_OUT_OF_HOST_MEMORY: + case VK_ERROR_OUT_OF_DEVICE_MEMORY: + case VK_ERROR_DEVICE_LOST: + gEngine.Host_Error("vkAcquireNextImageKHR returned %s, this is unrecoverable, crashing.\n", R_VkResultName(acquire_result)); + XVK_CHECK(acquire_result); + return ret; + + default: + gEngine.Con_Printf(S_WARN "vkAcquireNextImageKHR returned %s (%0#x), recreating swapchain\n", R_VkResultName(acquire_result), acquire_result); if (i == 0) { force_recreate = true; continue; } gEngine.Con_Printf(S_WARN "second vkAcquireNextImageKHR failed, frame will be lost\n", R_VkResultName(acquire_result)); return ret; - - default: - XVK_CHECK(acquire_result); } break; From fc132e87f48b899141a99bac14c315d526b09185 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 25 Jul 2022 19:56:31 +0300 Subject: [PATCH 389/548] engine, game_launch: fix rpath usage --- engine/wscript | 3 ++- game_launch/wscript | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/engine/wscript b/engine/wscript index c1479c45..4d2c9eed 100644 --- a/engine/wscript +++ b/engine/wscript @@ -184,5 +184,6 @@ def build(bld): includes = includes, use = libs, install_path = install_path, - subsystem = bld.env.MSVC_SUBSYSTEM + subsystem = bld.env.MSVC_SUBSYSTEM, + rpath = '$ORIGIN' ) diff --git a/game_launch/wscript b/game_launch/wscript index 4b9ad831..d4dab31c 100644 --- a/game_launch/wscript +++ b/game_launch/wscript @@ -37,7 +37,7 @@ def build(bld): features = 'c cxx cxxprogram', includes = includes, use = libs, - rpath = '.', + rpath = '$ORIGIN', install_path = bld.env.BINDIR, subsystem = bld.env.MSVC_SUBSYSTEM ) From 5350d88f570597e20b206f35ffcd9ffdfd84dab7 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 11 Jul 2022 00:34:48 +0300 Subject: [PATCH 390/548] public: crtlib: add quotation mark support for ParseFile, required for filesystem_stdio --- engine/client/cl_game.c | 6 +----- engine/client/cl_gameui.c | 7 ++++++- engine/client/cl_mobile.c | 7 ++++++- engine/client/cl_qparse.c | 2 +- engine/common/cmd.c | 2 +- engine/common/common.c | 12 ++++++------ public/crtlib.c | 8 +++++++- public/crtlib.h | 6 ++++-- 8 files changed, 32 insertions(+), 18 deletions(-) diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index e516f423..f7e6d1dc 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -3098,11 +3098,7 @@ handle colon separately */ char *pfnParseFile( char *data, char *token ) { - char *out; - - out = _COM_ParseFileSafe( data, token, PFILE_TOKEN_MAX_LENGTH, PFILE_HANDLECOLON, NULL ); - - return out; + return COM_ParseFileSafe( data, token, PFILE_TOKEN_MAX_LENGTH, PFILE_HANDLECOLON, NULL, NULL ); } /* diff --git a/engine/client/cl_gameui.c b/engine/client/cl_gameui.c index f295e701..bae521d0 100644 --- a/engine/client/cl_gameui.c +++ b/engine/client/cl_gameui.c @@ -1224,6 +1224,11 @@ static int pfnGetRenderers( unsigned int num, char *shortName, size_t size1, cha return 1; } +static char *pfnParseFileSafe( char *data, char *buf, const int size, unsigned int flags, int *len ) +{ + return COM_ParseFileSafe( data, buf, size, flags, len, NULL ); +} + static ui_extendedfuncs_t gExtendedfuncs = { pfnEnableTextInput, @@ -1232,7 +1237,7 @@ static ui_extendedfuncs_t gExtendedfuncs = Con_UtfMoveRight, pfnGetRenderers, Sys_DoubleTime, - _COM_ParseFileSafe, + pfnParseFileSafe, NET_AdrToString }; diff --git a/engine/client/cl_mobile.c b/engine/client/cl_mobile.c index 2dc84d34..3e59b85a 100644 --- a/engine/client/cl_mobile.c +++ b/engine/client/cl_mobile.c @@ -103,6 +103,11 @@ static void pfnTouch_RemoveButton( const char *name ) Touch_RemoveButton( name, true ); } +static char *pfnParseFileSafe( char *data, char *buf, const int size, unsigned int flags, int *len ) +{ + return COM_ParseFileSafe( data, buf, size, flags, len, NULL ); +} + static mobile_engfuncs_t gpMobileEngfuncs = { MOBILITY_API_VERSION, @@ -118,7 +123,7 @@ static mobile_engfuncs_t gpMobileEngfuncs = Sys_Warn, pfnGetNativeObject, ID_SetCustomClientID, - _COM_ParseFileSafe + pfnParseFileSafe }; qboolean Mobile_Init( void ) diff --git a/engine/client/cl_qparse.c b/engine/client/cl_qparse.c index bfdf753e..76865362 100644 --- a/engine/client/cl_qparse.c +++ b/engine/client/cl_qparse.c @@ -864,7 +864,7 @@ void CL_QuakeExecStuff( void ) if( !*text ) break; - text = _COM_ParseFileSafe( text, token, sizeof( token ), PFILE_IGNOREBRACKET, NULL ); + text = COM_ParseFileSafe( text, token, sizeof( token ), PFILE_IGNOREBRACKET, NULL, NULL ); if( !text ) break; diff --git a/engine/common/cmd.c b/engine/common/cmd.c index 3a268e5a..1df7287b 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -647,7 +647,7 @@ void Cmd_TokenizeString( const char *text ) if( cmd_argc == 1 ) cmd_args = text; - text = _COM_ParseFileSafe( (char*)text, cmd_token, sizeof( cmd_token ), PFILE_IGNOREBRACKET, NULL ); + text = COM_ParseFileSafe( (char*)text, cmd_token, sizeof( cmd_token ), PFILE_IGNOREBRACKET, NULL, NULL ); if( !text ) return; diff --git a/engine/common/common.c b/engine/common/common.c index c8baf28b..2e00df6a 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -1182,22 +1182,22 @@ void Test_RunCommon( void ) Msg( "Checking COM_ParseFile...\n" ); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "q" ) && len == 1); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "asdf" ) && len == 4); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "qwer" ) && len == -1); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "f \"f" ) && len == 4); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "meow" ) && len == -1); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "bark" ) && len == 4); } #endif diff --git a/public/crtlib.c b/public/crtlib.c index 2cc3e4e9..d183c7a5 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -889,11 +889,14 @@ COM_ParseFile text parser ============== */ -char *_COM_ParseFileSafe( char *data, char *token, const int size, unsigned int flags, int *plen ) +char *COM_ParseFileSafe( char *data, char *token, const int size, unsigned int flags, int *plen, qboolean *quoted ) { int c, len = 0; qboolean overflow = false; + if( quoted ) + *quoted = false; + if( !token || !size ) { if( plen ) *plen = 0; @@ -927,6 +930,9 @@ skipwhite: // handle quoted strings specially if( c == '\"' ) { + if( quoted ) + *quoted = true; + data++; while( 1 ) { diff --git a/public/crtlib.h b/public/crtlib.h index 43a0c775..6c1ad224 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -37,6 +37,7 @@ enum #define PFILE_IGNOREBRACKET (1<<0) #define PFILE_HANDLECOLON (1<<1) #define PFILE_TOKEN_MAX_LENGTH 1024 +#define PFILE_FS_TOKEN_MAX_LENGTH 512 // // crtlib.c @@ -83,10 +84,11 @@ void COM_RemoveLineFeed( char *str ); void COM_PathSlashFix( char *path ); char COM_Hex2Char( uint8_t hex ); void COM_Hex2String( uint8_t hex, char *str ); +// return 0 on empty or null string, 1 otherwise #define COM_CheckString( string ) ( ( !string || !*string ) ? 0 : 1 ) #define COM_CheckStringEmpty( string ) ( ( !*string ) ? 0 : 1 ) -char *_COM_ParseFileSafe( char *data, char *token, const int size, unsigned int flags, int *len ); -#define COM_ParseFile( data, token, size ) _COM_ParseFileSafe( data, token, size, 0, NULL ) +char *COM_ParseFileSafe( char *data, char *token, const int size, unsigned int flags, int *len, qboolean *quoted ); +#define COM_ParseFile( data, token, size ) COM_ParseFileSafe( data, token, size, 0, NULL, NULL ) int matchpattern( const char *in, const char *pattern, qboolean caseinsensitive ); int matchpattern_with_separator( const char *in, const char *pattern, qboolean caseinsensitive, const char *separators, qboolean wildcard_least_one ); From a9c82dbe211b59fd58f386f85dac8a2c57ca8e97 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 11 Jul 2022 03:59:35 +0300 Subject: [PATCH 391/548] public: make crtlib linkable with C++ --- public/crtlib.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/public/crtlib.h b/public/crtlib.h index 6c1ad224..5b86502a 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -21,6 +21,11 @@ GNU General Public License for more details. #include "build.h" #include "xash3d_types.h" +#ifdef __cplusplus +extern "C" +{ +#endif + // timestamp modes enum { @@ -145,5 +150,8 @@ static inline char *Q_stristr( const char *s1, const char *s2 ) char *Q_stristr( const char *s1, const char *s2 ); #endif // defined( HAVE_STRCASESTR ) +#ifdef __cplusplus +} +#endif #endif//STDLIB_H From a41f8cb01bebae92408da72e3ca031023667172c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 26 Jul 2022 04:07:52 +0300 Subject: [PATCH 392/548] engine: move version strings to com_strings.h file, in preparation of filesystem_stdio branch merge --- engine/common/com_strings.h | 2 ++ engine/common/common.h | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/engine/common/com_strings.h b/engine/common/com_strings.h index fcb40d41..32c5c0a1 100644 --- a/engine/common/com_strings.h +++ b/engine/common/com_strings.h @@ -64,6 +64,8 @@ GNU General Public License for more details. #define DEFAULT_UPDATE_PAGE "https://github.com/FWGS/xash3d-fwgs/releases/latest" #define XASH_ENGINE_NAME "Xash3D FWGS" +#define XASH_VERSION "0.20" // engine current version +#define XASH_COMPAT_VERSION "0.99" // version we are based on // renderers order is important, software is always a last chance fallback #define DEFAULT_RENDERERS { "gl", "gles1", "gles2", "gl4es", "soft" } diff --git a/engine/common/common.h b/engine/common/common.h index 333f31aa..badeeea5 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -119,9 +119,6 @@ typedef enum #include "con_nprint.h" #include "crclib.h" -#define XASH_VERSION "0.20" // engine current version -#define XASH_COMPAT_VERSION "0.99" // version we are based on - // PERFORMANCE INFO #define MIN_FPS 20.0f // host minimum fps value for maxfps. #define MAX_FPS 200.0f // upper limit for maxfps. From 12ea6dcfd7d9242f7e9412e0e4d7ada4184fc256 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 26 Jul 2022 04:10:36 +0300 Subject: [PATCH 393/548] public: move build.c from engine to public library, in preparation of filesystem_stdio merge --- engine/common/common.h | 10 ---------- {engine/common => public}/build.c | 2 +- public/crtlib.h | 9 +++++++++ 3 files changed, 10 insertions(+), 11 deletions(-) rename {engine/common => public}/build.c (99%) diff --git a/engine/common/common.h b/engine/common/common.h index badeeea5..8b112178 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -667,16 +667,6 @@ void FS_FreeStream( stream_t *stream ); qboolean Sound_Process( wavdata_t **wav, int rate, int width, uint flags ); uint Sound_GetApproxWavePlayLen( const char *filepath ); -// -// build.c -// -int Q_buildnum( void ); -int Q_buildnum_compat( void ); -const char *Q_buildos( void ); -const char *Q_buildarch( void ); -const char *Q_buildcommit( void ); - - // // host.c // diff --git a/engine/common/build.c b/public/build.c similarity index 99% rename from engine/common/build.c rename to public/build.c index 42ba572c..b9662c53 100644 --- a/engine/common/build.c +++ b/public/build.c @@ -13,7 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#include "common.h" +#include "crtlib.h" static const char *date = __DATE__ ; static const char *mon[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; diff --git a/public/crtlib.h b/public/crtlib.h index 5b86502a..2d44939f 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -44,6 +44,15 @@ enum #define PFILE_TOKEN_MAX_LENGTH 1024 #define PFILE_FS_TOKEN_MAX_LENGTH 512 +// +// build.c +// +int Q_buildnum( void ); +int Q_buildnum_compat( void ); +const char *Q_buildos( void ); +const char *Q_buildarch( void ); +const char *Q_buildcommit( void ); + // // crtlib.c // From 5e4fc64430ccca8b05df67c6f3c453ff75d029cc Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 1 Jul 2022 19:37:21 +0300 Subject: [PATCH 394/548] filesystem: introduce new module, based on engine filesystem. The goal is to share filesystem code between engine and utilities and provide C++ VFileSystem interface in the future --- engine/client/cl_gameui.c | 36 +- engine/client/ref_common.c | 8 +- engine/common/common.c | 17 - engine/common/common.h | 142 +- engine/common/con_utils.c | 6 +- engine/common/filesystem.h | 200 -- engine/common/filesystem_engine.c | 146 ++ engine/common/host.c | 29 +- engine/common/hpak.c | 4 +- engine/common/hpak.h | 60 + engine/common/imagelib/img_png.c | 1 - engine/common/lib_common.c | 25 + engine/platform/win32/lib_win.c | 4 +- engine/ref_api.h | 16 +- engine/wscript | 2 +- {engine/common => filesystem}/filesystem.c | 2439 +++++--------------- filesystem/filesystem.h | 202 ++ filesystem/filesystem_internal.h | 204 ++ filesystem/fscallback.h | 80 + filesystem/pak.c | 394 ++++ filesystem/wad.c | 634 +++++ filesystem/wscript | 17 + filesystem/zip.c | 678 ++++++ public/crtlib.c | 17 + public/crtlib.h | 1 + {engine/common => public}/miniz.h | 0 ref_gl/gl_alias.c | 2 +- ref_gl/gl_backend.c | 4 +- ref_gl/gl_rmisc.c | 2 +- ref_gl/gl_studio.c | 2 +- ref_gl/gl_warp.c | 4 +- ref_gl/wscript | 1 + ref_soft/r_studio.c | 2 +- ref_soft/wscript | 1 + wscript | 1 + 35 files changed, 3102 insertions(+), 2279 deletions(-) delete mode 100644 engine/common/filesystem.h create mode 100644 engine/common/filesystem_engine.c create mode 100644 engine/common/hpak.h rename {engine/common => filesystem}/filesystem.c (56%) create mode 100644 filesystem/filesystem.h create mode 100644 filesystem/filesystem_internal.h create mode 100644 filesystem/fscallback.h create mode 100644 filesystem/pak.c create mode 100644 filesystem/wad.c create mode 100644 filesystem/wscript create mode 100644 filesystem/zip.c rename {engine/common => public}/miniz.h (100%) diff --git a/engine/client/cl_gameui.c b/engine/client/cl_gameui.c index bae521d0..e5bf2683 100644 --- a/engine/client/cl_gameui.c +++ b/engine/client/cl_gameui.c @@ -980,7 +980,7 @@ pfnGetGamesList */ static GAMEINFO ** GAME_EXPORT pfnGetGamesList( int *numGames ) { - if( numGames ) *numGames = SI.numgames; + if( numGames ) *numGames = FI->numgames; return gameui.modsInfo; } @@ -1117,6 +1117,30 @@ static char *pfnParseFile( char *buf, char *token ) return COM_ParseFile( buf, token, INT_MAX ); } +/* +============= +pfnFileExists + +legacy wrapper +============= +*/ +static int pfnFileExists( const char *path, int gamedironly ) +{ + return FS_FileExists( path, gamedironly ); +} + +/* +============= +pfnDelete + +legacy wrapper +============= +*/ +static int pfnDelete( const char *path ) +{ + return FS_Delete( path ); +} + // engine callbacks static ui_enginefuncs_t gEngfuncs = { @@ -1163,7 +1187,7 @@ static ui_enginefuncs_t gEngfuncs = pfnRenderScene, pfnAddEntity, Host_Error, - FS_FileExists, + pfnFileExists, pfnGetGameDir, Cmd_CheckMapsList, CL_Active, @@ -1202,7 +1226,7 @@ static ui_enginefuncs_t gEngfuncs = COM_CompareFileTime, VID_GetModeString, (void*)COM_SaveFile, - (void*)FS_Delete + pfnDelete }; static void pfnEnableTextInput( int enable ) @@ -1358,13 +1382,13 @@ qboolean UI_LoadProgs( void ) Cvar_FullSet( "host_gameuiloaded", "1", FCVAR_READ_ONLY ); // setup gameinfo - for( i = 0; i < SI.numgames; i++ ) + for( i = 0; i < FI->numgames; i++ ) { gameui.modsInfo[i] = Mem_Calloc( gameui.mempool, sizeof( GAMEINFO )); - UI_ConvertGameInfo( gameui.modsInfo[i], SI.games[i] ); + UI_ConvertGameInfo( gameui.modsInfo[i], FI->games[i] ); } - UI_ConvertGameInfo( &gameui.gameInfo, SI.GameInfo ); // current gameinfo + UI_ConvertGameInfo( &gameui.gameInfo, FI->GameInfo ); // current gameinfo // setup globals gameui.globals->developer = host.allow_console; diff --git a/engine/client/ref_common.c b/engine/client/ref_common.c index 0760d904..57388705 100644 --- a/engine/client/ref_common.c +++ b/engine/client/ref_common.c @@ -334,10 +334,6 @@ static ref_api_t gEngfuncs = COM_FreeLibrary, COM_GetProcAddress, - FS_LoadFile, - FS_FileExists, - FS_AllowDirectPaths, - R_Init_Video_, R_Free_Video, @@ -383,7 +379,9 @@ static ref_api_t gEngfuncs = pfnDrawNormalTriangles, pfnDrawTransparentTriangles, - &clgame.drawFuncs + &clgame.drawFuncs, + + &g_fsapi, }; static void R_UnloadProgs( void ) diff --git a/engine/common/common.c b/engine/common/common.c index 2e00df6a..684718dd 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -585,23 +585,6 @@ void COM_TrimSpace( const char *source, char *dest ) dest[length] = 0; } -/* -============ -COM_FixSlashes - -Changes all '/' characters into '\' characters, in place. -============ -*/ -void COM_FixSlashes( char *pname ) -{ - while( *pname ) - { - if( *pname == '\\' ) - *pname = '/'; - pname++; - } -} - /* ================== COM_Nibble diff --git a/engine/common/common.h b/engine/common/common.h index 8b112178..58741bf3 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -76,13 +76,6 @@ XASH SPECIFIC - sort of hack that works only in Xash3D not in GoldSrc #define HACKS_RELATED_HLMODS // some HL-mods works differently under Xash and can't be fixed without some hacks at least at current time -typedef struct -{ - int numfilenames; - char **filenames; - char *filenamesbuffer; -} search_t; - enum { DEV_NONE = 0, @@ -118,6 +111,8 @@ typedef enum #include "cvar.h" #include "con_nprint.h" #include "crclib.h" +#include "ref_api.h" +#include "fscallback.h" // PERFORMANCE INFO #define MIN_FPS 20.0f // host minimum fps value for maxfps. @@ -147,18 +142,6 @@ typedef enum #define MAX_STATIC_ENTITIES 32 // static entities that moved on the client when level is spawn #endif -// filesystem flags -#define FS_STATIC_PATH ( 1U << 0 ) // FS_ClearSearchPath will be ignore this path -#define FS_NOWRITE_PATH ( 1U << 1 ) // default behavior - last added gamedir set as writedir. This flag disables it -#define FS_GAMEDIR_PATH ( 1U << 2 ) // just a marker for gamedir path -#define FS_CUSTOM_PATH ( 1U << 3 ) // custom directory -#define FS_GAMERODIR_PATH ( 1U << 4 ) // caseinsensitive - -#define FS_GAMEDIRONLY_SEARCH_FLAGS ( FS_GAMEDIR_PATH | FS_CUSTOM_PATH | FS_GAMERODIR_PATH ) - -#define GI SI.GameInfo -#define FS_Gamedir() SI.GameInfo->gamefolder -#define FS_Title() SI.GameInfo->title #define GameState (&host.game) #define FORCE_DRAW_VERSION_TIME 5.0f // draw version for 5 seconds @@ -199,61 +182,6 @@ GAMEINFO stuff internal shared gameinfo structure (readonly for engine parts) ======================================================================== */ -typedef struct gameinfo_s -{ - // filesystem info - char gamefolder[MAX_QPATH]; // used for change game '-game x' - char basedir[MAX_QPATH]; // base game directory (like 'id1' for Quake or 'valve' for Half-Life) - char falldir[MAX_QPATH]; // used as second basedir - char startmap[MAX_QPATH];// map to start singleplayer game - char trainmap[MAX_QPATH];// map to start hazard course (if specified) - char title[64]; // Game Main Title - float version; // game version (optional) - - // .dll pathes - char dll_path[MAX_QPATH]; // e.g. "bin" or "cl_dlls" - char game_dll[MAX_QPATH]; // custom path for game.dll - - // .ico path - char iconpath[MAX_QPATH]; // "game.ico" by default - - // about mod info - string game_url; // link to a developer's site - string update_url; // link to updates page - char type[MAX_QPATH]; // single, toolkit, multiplayer etc - char date[MAX_QPATH]; - size_t size; - - int gamemode; - qboolean secure; // prevent to console acess - qboolean nomodels; // don't let player to choose model (use player.mdl always) - qboolean noskills; // disable skill menu selection - qboolean render_picbutton_text; // use font renderer to render WON buttons - - char sp_entity[32]; // e.g. info_player_start - char mp_entity[32]; // e.g. info_player_deathmatch - char mp_filter[32]; // filtering multiplayer-maps - - char ambientsound[NUM_AMBIENTS][MAX_QPATH]; // quake ambient sounds - - int max_edicts; // min edicts is 600, max edicts is 8196 - int max_tents; // min temp ents is 300, max is 2048 - int max_beams; // min beams is 64, max beams is 512 - int max_particles; // min particles is 4096, max particles is 32768 - - char game_dll_linux[64]; // custom path for game.dll - char game_dll_osx[64]; // custom path for game.dll - - qboolean added; -} gameinfo_t; - -typedef enum -{ - GAME_NORMAL, - GAME_SINGLEPLAYER_ONLY, - GAME_MULTIPLAYER_ONLY -} gametype_t; - typedef struct sysinfo_s { string exeName; // exe.filename @@ -261,9 +189,6 @@ typedef struct sysinfo_s string basedirName; // name of base directory string gamedll; string clientlib; - gameinfo_t *GameInfo; // current GameInfo - gameinfo_t *games[MAX_MODS]; // environment games (founded at each engine start) - int numgames; } sysinfo_t; typedef enum @@ -470,6 +395,14 @@ extern sysinfo_t SI; typedef void (*xcommand_t)( void ); +// +// filesystem_engine.c +// +#define FILESYSTEM_STDIO_DLL "filesystem_stdio." OS_LIB_EXT +qboolean FS_LoadProgs( const char *name ); +void FS_Init( void ); +void FS_Shutdown( void ); + // // cmd.c // @@ -529,56 +462,6 @@ void Mem_PrintStats( void ); #define Mem_IsAllocated( mem ) Mem_IsAllocatedExt( NULL, mem ) #define Mem_Check() _Mem_Check( __FILE__, __LINE__ ) -// -// filesystem.c -// -void FS_Init( void ); -void FS_Path( void ); -void FS_Rescan( void ); -void FS_Shutdown( void ); -void FS_ClearSearchPath( void ); -void FS_AllowDirectPaths( qboolean enable ); -void FS_AddGameDirectory( const char *dir, uint flags ); -void FS_AddGameHierarchy( const char *dir, uint flags ); -void FS_LoadGameInfo( const char *rootfolder ); -const char *FS_GetDiskPath( const char *name, qboolean gamedironly ); -byte *W_LoadLump( wfile_t *wad, const char *lumpname, size_t *lumpsizeptr, const char type ); -void W_Close( wfile_t *wad ); -byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); -qboolean CRC32_File( dword *crcvalue, const char *filename ); -qboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] ); -byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr ); -qboolean FS_WriteFile( const char *filename, const void *data, fs_offset_t len ); -qboolean COM_ParseVector( char **pfile, float *v, size_t size ); -void COM_NormalizeAngles( vec3_t angles ); -int COM_FileSize( const char *filename ); -void COM_FixSlashes( char *pname ); -void COM_FreeFile( void *buffer ); -int COM_CompareFileTime( const char *filename1, const char *filename2, int *iCompare ); -search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ); -file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ); -fs_offset_t FS_Write( file_t *file, const void *data, size_t datasize ); -fs_offset_t FS_Read( file_t *file, void *buffer, size_t buffersize ); -int FS_VPrintf( file_t *file, const char *format, va_list ap ); -int FS_Seek( file_t *file, fs_offset_t offset, int whence ); -int FS_Gets( file_t *file, byte *string, size_t bufsize ); -int FS_Printf( file_t *file, const char *format, ... ) _format( 2 ); -fs_offset_t FS_FileSize( const char *filename, qboolean gamedironly ); -int FS_FileTime( const char *filename, qboolean gamedironly ); -int FS_Print( file_t *file, const char *msg ); -qboolean FS_Rename( const char *oldname, const char *newname ); -int FS_FileExists( const char *filename, int gamedironly ); -int FS_SetCurrentDirectory( const char *path ); -qboolean FS_SysFileExists( const char *path, qboolean casesensitive ); -qboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize ); -qboolean FS_Delete( const char *path ); -int FS_UnGetc( file_t *file, byte c ); -fs_offset_t FS_Tell( file_t *file ); -qboolean FS_Eof( file_t *file ); -int FS_Close( file_t *file ); -int FS_Getc( file_t *file ); -fs_offset_t FS_FileLength( file_t *f ); - // // imagelib // @@ -942,6 +825,11 @@ void UI_SetActiveMenu( qboolean fActive ); void UI_ShowConnectionWarning( void ); void Cmd_Null_f( void ); void Rcon_Print( const char *pMsg ); +qboolean COM_ParseVector( char **pfile, float *v, size_t size ); +void COM_NormalizeAngles( vec3_t angles ); +int COM_FileSize( const char *filename ); +void COM_FreeFile( void *buffer ); +int COM_CompareFileTime( const char *filename1, const char *filename2, int *iCompare ); // soundlib shared exports qboolean S_Init( void ); diff --git a/engine/common/con_utils.c b/engine/common/con_utils.c index b07374e4..8bafcb31 100644 --- a/engine/common/con_utils.c +++ b/engine/common/con_utils.c @@ -761,10 +761,10 @@ qboolean Cmd_GetGamesList( const char *s, char *completedname, int length ) // compare gamelist with current keyword len = Q_strlen( s ); - for( i = 0, numgamedirs = 0; i < SI.numgames; i++ ) + for( i = 0, numgamedirs = 0; i < FI->numgames; i++ ) { - if(( *s == '*' ) || !Q_strnicmp( SI.games[i]->gamefolder, s, len)) - Q_strcpy( gamedirs[numgamedirs++], SI.games[i]->gamefolder ); + if(( *s == '*' ) || !Q_strnicmp( FI->games[i]->gamefolder, s, len)) + Q_strcpy( gamedirs[numgamedirs++], FI->games[i]->gamefolder ); } if( !numgamedirs ) return false; diff --git a/engine/common/filesystem.h b/engine/common/filesystem.h deleted file mode 100644 index 3abba403..00000000 --- a/engine/common/filesystem.h +++ /dev/null @@ -1,200 +0,0 @@ -/* -filesystem.h - engine FS -Copyright (C) 2007 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ - -#ifndef FILESYSTEM_H -#define FILESYSTEM_H - -/* -======================================================================== -PAK FILES - -The .pak files are just a linear collapse of a directory tree -======================================================================== -*/ -// header -#define IDPACKV1HEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') // little-endian "PACK" - -#define MAX_FILES_IN_PACK 65536 // pak - -typedef struct -{ - int ident; - int dirofs; - int dirlen; -} dpackheader_t; - -typedef struct -{ - char name[56]; // total 64 bytes - int filepos; - int filelen; -} dpackfile_t; - -/* -======================================================================== -.WAD archive format (WhereAllData - WAD) - -List of compressed files, that can be identify only by TYPE_* - - -header: dwadinfo_t[dwadinfo_t] -file_1: byte[dwadinfo_t[num]->disksize] -file_2: byte[dwadinfo_t[num]->disksize] -file_3: byte[dwadinfo_t[num]->disksize] -... -file_n: byte[dwadinfo_t[num]->disksize] -infotable dlumpinfo_t[dwadinfo_t->numlumps] -======================================================================== -*/ -#define WAD3_NAMELEN 16 -#define HINT_NAMELEN 5 // e.g. _mask, _norm -#define MAX_FILES_IN_WAD 65535 // real limit as above <2Gb size not a lumpcount - -#include "const.h" - -typedef struct -{ - int ident; // should be WAD3 - int numlumps; // num files - int infotableofs; // LUT offset -} dwadinfo_t; - -typedef struct -{ - int filepos; // file offset in WAD - int disksize; // compressed or uncompressed - int size; // uncompressed - signed char type; // TYP_* - signed char attribs; // file attribs - signed char pad0; - signed char pad1; - char name[WAD3_NAMELEN]; // must be null terminated -} dlumpinfo_t; - -#include "custom.h" - -/* -======================================================================== -.HPK archive format (Hash PAK - HPK) - -List of compressed files, that can be identify only by TYPE_* - - -header: dwadinfo_t[dwadinfo_t] -file_1: byte[dwadinfo_t[num]->disksize] -file_2: byte[dwadinfo_t[num]->disksize] -file_3: byte[dwadinfo_t[num]->disksize] -... -file_n: byte[dwadinfo_t[num]->disksize] -infotable dlumpinfo_t[dwadinfo_t->numlumps] -======================================================================== -*/ - -#define IDHPAKHEADER (('K'<<24)+('A'<<16)+('P'<<8)+'H') // little-endian "HPAK" -#define IDHPAK_VERSION 1 - -typedef struct -{ - int ident; // should be equal HPAK - int version; - int infotableofs; -} hpak_header_t; - -typedef struct -{ - resource_t resource; - int filepos; - int disksize; -} hpak_lump_t; - -typedef struct -{ - int count; - hpak_lump_t *entries; // variable sized. -} hpak_info_t; - -#define ZIP_HEADER_LF (('K'<<8)+('P')+(0x03<<16)+(0x04<<24)) -#define ZIP_HEADER_SPANNED ((0x08<<24)+(0x07<<16)+('K'<<8)+'P') - -#define ZIP_HEADER_CDF ((0x02<<24)+(0x01<<16)+('K'<<8)+'P') -#define ZIP_HEADER_EOCD ((0x06<<24)+(0x05<<16)+('K'<<8)+'P') - -#define ZIP_COMPRESSION_NO_COMPRESSION 0 -#define ZIP_COMPRESSION_DEFLATED 8 - -#define ZIP_ZIP64 0xffffffff - -#pragma pack( push, 1 ) -typedef struct zip_header_s -{ - unsigned int signature; // little endian ZIP_HEADER - unsigned short version; // version of pkzip need to unpack - unsigned short flags; // flags (16 bits == 16 flags) - unsigned short compression_flags; // compression flags (bits) - unsigned int dos_date; // file modification time and file modification date - unsigned int crc32; //crc32 - unsigned int compressed_size; - unsigned int uncompressed_size; - unsigned short filename_len; - unsigned short extrafield_len; -} zip_header_t; - -/* - in zip64 comp and uncompr size == 0xffffffff remeber this - compressed and uncompress filesize stored in extra field -*/ - -typedef struct zip_header_extra_s -{ - unsigned int signature; // ZIP_HEADER_SPANNED - unsigned int crc32; - unsigned int compressed_size; - unsigned int uncompressed_size; -} zip_header_extra_t; - -typedef struct zip_cdf_header_s -{ - unsigned int signature; - unsigned short version; - unsigned short version_need; - unsigned short generalPurposeBitFlag; - unsigned short flags; - unsigned short modification_time; - unsigned short modification_date; - unsigned int crc32; - unsigned int compressed_size; - unsigned int uncompressed_size; - unsigned short filename_len; - unsigned short extrafield_len; - unsigned short file_commentary_len; - unsigned short disk_start; - unsigned short internal_attr; - unsigned int external_attr; - unsigned int local_header_offset; -} zip_cdf_header_t; - -typedef struct zip_header_eocd_s -{ - unsigned short disk_number; - unsigned short start_disk_number; - unsigned short number_central_directory_record; - unsigned short total_central_directory_record; - unsigned int size_of_central_directory; - unsigned int central_directory_offset; - unsigned short commentary_len; -} zip_header_eocd_t; -#pragma pack( pop ) - -#endif//FILESYSTEM_H diff --git a/engine/common/filesystem_engine.c b/engine/common/filesystem_engine.c new file mode 100644 index 00000000..42754134 --- /dev/null +++ b/engine/common/filesystem_engine.c @@ -0,0 +1,146 @@ + /* +filesystem.c - game filesystem based on DP fs +Copyright (C) 2007 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "common.h" +#include "library.h" + +fs_api_t g_fsapi; +fs_globals_t *FI; + +static HINSTANCE fs_hInstance; + +static void FS_Rescan_f( void ) +{ + FS_Rescan(); +} + +static void FS_ClearPaths_f( void ) +{ + FS_ClearSearchPath(); +} + +static void FS_Path_f_( void ) +{ + FS_Path_f(); +} + +static fs_interface_t fs_memfuncs = +{ + Con_Printf, + Con_DPrintf, + Con_Reportf, + Sys_Error, + + _Mem_AllocPool, + _Mem_FreePool, + _Mem_Alloc, + _Mem_Realloc, + _Mem_Free, +}; + +static void FS_UnloadProgs( void ) +{ + COM_FreeLibrary( fs_hInstance ); + fs_hInstance = 0; +} + +qboolean FS_LoadProgs( const char *name ) +{ + FSAPI GetFSAPI; + + fs_hInstance = COM_LoadLibrary( name, false, true ); + + if( !fs_hInstance ) + { + Host_Error( "FS_LoadProgs: can't load filesystem library %s: %s\n", name, COM_GetLibraryError() ); + return false; + } + + if( !( GetFSAPI = (FSAPI)COM_GetProcAddress( fs_hInstance, GET_FS_API ))) + { + FS_UnloadProgs(); + Host_Error( "FS_LoadProgs: can't find GetFSAPI entry point in %s\n", name ); + return false; + } + + if( !GetFSAPI( FS_API_VERSION, &g_fsapi, &FI, &fs_memfuncs )) + { + FS_UnloadProgs(); + Host_Error( "FS_LoadProgs: can't initialize filesystem API: wrong version\n" ); + return false; + } + + Con_DPrintf( "FS_LoadProgs: filesystem_stdio successfully loaded\n" ); + + return true; +} + +/* +================ +FS_Init +================ +*/ +void FS_Init( void ) +{ + qboolean hasBaseDir = false; + qboolean hasGameDir = false; + qboolean caseinsensitive = true; + int i; + string gamedir; + + Cmd_AddRestrictedCommand( "fs_rescan", FS_Rescan_f, "rescan filesystem search pathes" ); + Cmd_AddRestrictedCommand( "fs_path", FS_Path_f_, "show filesystem search pathes" ); + Cmd_AddRestrictedCommand( "fs_clearpaths", FS_ClearPaths_f, "clear filesystem search pathes" ); + +#if !XASH_WIN32 + if( Sys_CheckParm( "-casesensitive" ) ) + caseinsensitive = false; +#endif + + if( !Sys_GetParmFromCmdLine( "-game", gamedir )) + Q_strncpy( gamedir, SI.basedirName, sizeof( gamedir )); // gamedir == basedir + + if( !FS_InitStdio( caseinsensitive, host.rootdir, SI.basedirName, gamedir, host.rodir )) + { + Host_Error( "Can't init filesystem_stdio!\n" ); + return; + } + + if( !Sys_GetParmFromCmdLine( "-dll", SI.gamedll )) + SI.gamedll[0] = 0; + + if( !Sys_GetParmFromCmdLine( "-clientlib", SI.clientlib )) + SI.clientlib[0] = 0; +} + +/* +================ +FS_Shutdown +================ +*/ +void FS_Shutdown( void ) +{ + int i; + + FS_ShutdownStdio(); + + memset( &SI, 0, sizeof( sysinfo_t )); + + FS_UnloadProgs(); +} + + + + diff --git a/engine/common/host.c b/engine/common/host.c index fee751ce..3f89d176 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -328,13 +328,13 @@ void Host_ChangeGame_f( void ) } // validate gamedir - for( i = 0; i < SI.numgames; i++ ) + for( i = 0; i < FI->numgames; i++ ) { - if( !Q_stricmp( SI.games[i]->gamefolder, Cmd_Argv( 1 ))) + if( !Q_stricmp( FI->games[i]->gamefolder, Cmd_Argv( 1 ))) break; } - if( i == SI.numgames ) + if( i == FI->numgames ) { Con_Printf( "%s not exist\n", Cmd_Argv( 1 )); } @@ -345,7 +345,7 @@ void Host_ChangeGame_f( void ) else { const char *arg1 = va( "%s%s", (host.type == HOST_NORMAL) ? "" : "#", Cmd_Argv( 1 )); - const char *arg2 = va( "change game to '%s'", SI.games[i]->title ); + const char *arg2 = va( "change game to '%s'", FI->games[i]->title ); Host_NewInstance( arg1, arg2 ); } @@ -1027,18 +1027,27 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha if( len && host.rodir[len - 1] == '/' ) host.rodir[len - 1] = 0; - if( !COM_CheckStringEmpty( host.rootdir ) || FS_SetCurrentDirectory( host.rootdir ) != 0 ) + if( !COM_CheckStringEmpty( host.rootdir )) + { + Sys_Error( "Changing working directory failed (empty working directory)\n" ); + return; + } + + FS_LoadProgs( FILESYSTEM_STDIO_DLL ); + + if( FS_SetCurrentDirectory( host.rootdir ) != 0 ) Con_Reportf( "%s is working directory now\n", host.rootdir ); else Sys_Error( "Changing working directory to %s failed.\n", host.rootdir ); + FS_Init(); + Sys_InitLog(); Cmd_AddCommand( "exec", Host_Exec_f, "execute a script file" ); Cmd_AddCommand( "memlist", Host_MemStats_f, "prints memory pool information" ); Cmd_AddRestrictedCommand( "userconfigd", Host_Userconfigd_f, "execute all scripts from userconfig.d" ); - FS_Init(); Image_Init(); Sound_Init(); @@ -1048,8 +1057,16 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha #endif FS_LoadGameInfo( NULL ); + + if( FS_FileExists( va( "%s.rc", SI.basedirName ), false )) + Q_strncpy( SI.rcName, SI.basedirName, sizeof( SI.rcName )); // e.g. valve.rc + else Q_strncpy( SI.rcName, SI.exeName, sizeof( SI.rcName )); // e.g. quake.rc + Q_strncpy( host.gamefolder, GI->gamefolder, sizeof( host.gamefolder )); + Image_CheckPaletteQ1 (); + Host_InitDecals (); // reload decals + // DEPRECATED: by FWGS fork #if 0 if( GI->secure ) diff --git a/engine/common/hpak.c b/engine/common/hpak.c index 0da8e8a7..076c3428 100644 --- a/engine/common/hpak.c +++ b/engine/common/hpak.c @@ -14,7 +14,7 @@ GNU General Public License for more details. */ #include "common.h" -#include "filesystem.h" +#include "hpak.h" #define HPAK_MAX_ENTRIES 0x8000 #define HPAK_MIN_SIZE (1 * 1024) @@ -402,7 +402,7 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) FS_Seek( f, hdr.infotableofs, SEEK_SET ); FS_Read( f, &num_lumps, sizeof( num_lumps )); - if( num_lumps < 1 || num_lumps > MAX_FILES_IN_WAD ) + if( num_lumps < 1 || num_lumps > HPAK_MAX_ENTRIES ) { Con_DPrintf( S_ERROR "HPAK_ValidatePak: %s has too many lumps %u.\n", pakname, num_lumps ); FS_Close( f ); diff --git a/engine/common/hpak.h b/engine/common/hpak.h new file mode 100644 index 00000000..4d5c8fde --- /dev/null +++ b/engine/common/hpak.h @@ -0,0 +1,60 @@ +/* +hpak.c - custom user package to send other clients +Copyright (C) 2010 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#ifndef HPAK_H +#define HPAK_H + +#include "custom.h" + +/* +======================================================================== +.HPK archive format (Hash PAK - HPK) + +List of compressed files, that can be identify only by TYPE_* + + +header: dwadinfo_t[dwadinfo_t] +file_1: byte[dwadinfo_t[num]->disksize] +file_2: byte[dwadinfo_t[num]->disksize] +file_3: byte[dwadinfo_t[num]->disksize] +... +file_n: byte[dwadinfo_t[num]->disksize] +infotable dlumpinfo_t[dwadinfo_t->numlumps] +======================================================================== +*/ + +#define IDHPAKHEADER (('K'<<24)+('A'<<16)+('P'<<8)+'H') // little-endian "HPAK" +#define IDHPAK_VERSION 1 + +typedef struct +{ + int ident; // should be equal HPAK + int version; + int infotableofs; +} hpak_header_t; + +typedef struct +{ + resource_t resource; + int filepos; + int disksize; +} hpak_lump_t; + +typedef struct +{ + int count; + hpak_lump_t *entries; // variable sized. +} hpak_info_t; + +#endif // HPAK_H diff --git a/engine/common/imagelib/img_png.c b/engine/common/imagelib/img_png.c index 9569a241..2c129ba1 100644 --- a/engine/common/imagelib/img_png.c +++ b/engine/common/imagelib/img_png.c @@ -13,7 +13,6 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#define MINIZ_HEADER_FILE_ONLY #include "miniz.h" #include "imagelib.h" #include "xash3d_mathlib.h" diff --git a/engine/common/lib_common.c b/engine/common/lib_common.c index e18161db..5b86df77 100644 --- a/engine/common/lib_common.c +++ b/engine/common/lib_common.c @@ -86,6 +86,31 @@ const char *COM_OffsetNameForFunction( void *function ) return sname; } +dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) +{ + dll_user_t *p; + fs_dllinfo_t dllInfo; + + // no fs loaded, can't search + if( !g_fsapi.FindLibrary ) + return NULL; + + // fs can't find library + if( !g_fsapi.FindLibrary( dllname, directpath, &dllInfo )) + return NULL; + + // NOTE: for libraries we not fail even if search is NULL + // let the OS find library himself + p = Mem_Calloc( host.mempool, sizeof( dll_user_t )); + Q_strncpy( p->shortPath, dllInfo.shortPath, sizeof( p->shortPath )); + Q_strncpy( p->fullPath, dllInfo.fullPath, sizeof( p->fullPath )); + Q_strncpy( p->dllName, dllname, sizeof( p->dllName )); + p->custom_loader = dllInfo.custom_loader; + p->encrypted = dllInfo.encrypted; + + return p; +} + /* ============================================================================= diff --git a/engine/platform/win32/lib_win.c b/engine/platform/win32/lib_win.c index 21ee1fab..b6cf9cf8 100644 --- a/engine/platform/win32/lib_win.c +++ b/engine/platform/win32/lib_win.c @@ -391,7 +391,7 @@ qboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname dll_user_t *hInst; qboolean ret = FALSE; - hInst = FS_FindLibrary( name, directpath ); + hInst = COM_FindLibrary( name, directpath ); if ( !hInst ) return FALSE; data = FS_LoadFile( name, NULL, false ); @@ -439,7 +439,7 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d COM_ResetLibraryError(); - hInst = FS_FindLibrary( dllname, directpath ); + hInst = COM_FindLibrary( dllname, directpath ); if( !hInst ) { COM_PushLibraryError( va( "Failed to find library %s", dllname ) ); diff --git a/engine/ref_api.h b/engine/ref_api.h index 3c3119d3..2f78b709 100644 --- a/engine/ref_api.h +++ b/engine/ref_api.h @@ -27,8 +27,12 @@ GNU General Public License for more details. #include "studio.h" #include "r_efx.h" #include "com_image.h" +#include "filesystem.h" -#define REF_API_VERSION 1 +// RefAPI changelog: +// 1. Initial release +// 2. FS functions are removed, instead we have full fs_api_t +#define REF_API_VERSION 2 #define TF_SKY (TF_SKYSIDE|TF_NOMIPMAP) @@ -367,13 +371,6 @@ typedef struct ref_api_s void (*COM_FreeLibrary)( void *handle ); void *(*COM_GetProcAddress)( void *handle, const char *name ); - // filesystem - byte* (*COM_LoadFile)( const char *path, fs_offset_t *pLength, qboolean gamedironly ); - // use Mem_Free instead - // void (*COM_FreeFile)( void *buffer ); - int (*FS_FileExists)( const char *filename, int gamedironly ); - void (*FS_AllowDirectPaths)( qboolean enable ); - // video init // try to create window // will call GL_SetupAttributes in case of REF_GL @@ -430,6 +427,9 @@ typedef struct ref_api_s void (*pfnDrawNormalTriangles)( void ); void (*pfnDrawTransparentTriangles)( void ); render_interface_t *drawFuncs; + + // filesystem exports + fs_api_t *fsapi; } ref_api_t; struct mip_s; diff --git a/engine/wscript b/engine/wscript index 4d2c9eed..972d8804 100644 --- a/engine/wscript +++ b/engine/wscript @@ -166,7 +166,7 @@ def build(bld): 'client/vgui/*.c', 'client/avi/*.c']) - includes = ['common', 'server', 'client', 'client/vgui', 'tests', '.', '../public', '../common', '../pm_shared' ] + includes = ['common', 'server', 'client', 'client/vgui', 'tests', '.', '../public', '../common', '../filesystem', '../pm_shared' ] if bld.env.SINGLE_BINARY: install_path = bld.env.BINDIR diff --git a/engine/common/filesystem.c b/filesystem/filesystem.c similarity index 56% rename from engine/common/filesystem.c rename to filesystem/filesystem.c index 734255e7..6f755d92 100644 --- a/engine/common/filesystem.c +++ b/filesystem/filesystem.c @@ -27,139 +27,39 @@ GNU General Public License for more details. #include #include #endif -#include "miniz.h" // header-only zlib replacement -#include "common.h" -#include "wadfile.h" +#include +#include +#include "port.h" +#include "const.h" +#include "crtlib.h" +#include "crclib.h" #include "filesystem.h" -#include "library.h" +#include "filesystem_internal.h" #include "xash3d_mathlib.h" -#include "protocol.h" +#include "common/com_strings.h" +#include "common/protocol.h" #define FILE_COPY_SIZE (1024 * 1024) -#define FILE_BUFF_SIZE (2048) +qboolean fs_ext_path = false; // attempt to read\write from ./ or ../ pathes +poolhandle_t fs_mempool; +searchpath_t *fs_searchpaths = NULL; // chain +char fs_rodir[MAX_SYSPATH]; +char fs_rootdir[MAX_SYSPATH]; -// PAK errors -#define PAK_LOAD_OK 0 -#define PAK_LOAD_COULDNT_OPEN 1 -#define PAK_LOAD_BAD_HEADER 2 -#define PAK_LOAD_BAD_FOLDERS 3 -#define PAK_LOAD_TOO_MANY_FILES 4 -#define PAK_LOAD_NO_FILES 5 -#define PAK_LOAD_CORRUPTED 6 - -// WAD errors -#define WAD_LOAD_OK 0 -#define WAD_LOAD_COULDNT_OPEN 1 -#define WAD_LOAD_BAD_HEADER 2 -#define WAD_LOAD_BAD_FOLDERS 3 -#define WAD_LOAD_TOO_MANY_FILES 4 -#define WAD_LOAD_NO_FILES 5 -#define WAD_LOAD_CORRUPTED 6 - -// ZIP errors -#define ZIP_LOAD_OK 0 -#define ZIP_LOAD_COULDNT_OPEN 1 -#define ZIP_LOAD_BAD_HEADER 2 -#define ZIP_LOAD_BAD_FOLDERS 3 -#define ZIP_LOAD_NO_FILES 5 -#define ZIP_LOAD_CORRUPTED 6 - -typedef struct stringlist_s -{ - // maxstrings changes as needed, causing reallocation of strings[] array - int maxstrings; - int numstrings; - char **strings; -} stringlist_t; - -typedef struct wadtype_s -{ - const char *ext; - signed char type; -} wadtype_t; - -struct file_s -{ - int handle; // file descriptor - int ungetc; // single stored character from ungetc, cleared to EOF when read - fs_offset_t real_length; // uncompressed file size (for files opened in "read" mode) - fs_offset_t position; // current position in the file - fs_offset_t offset; // offset into the package (0 if external file) - time_t filetime; // pak, wad or real filetime - // contents buffer - fs_offset_t buff_ind, buff_len; // buffer current index and length - byte buff[FILE_BUFF_SIZE]; // intermediate buffer -#ifdef XASH_REDUCE_FD - const char *backup_path; - fs_offset_t backup_position; - uint backup_options; -#endif -}; - -struct wfile_s -{ - string filename; - int infotableofs; - int numlumps; - poolhandle_t mempool; // W_ReadLump temp buffers - file_t *handle; - dlumpinfo_t *lumps; - time_t filetime; -}; - -typedef struct pack_s -{ - string filename; - int handle; - int numfiles; - time_t filetime; // common for all packed files - dpackfile_t *files; -} pack_t; - -typedef struct zipfile_s -{ - char name[MAX_SYSPATH]; - fs_offset_t offset; // offset of local file header - fs_offset_t size; //original file size - fs_offset_t compressed_size; // compressed file size - unsigned short flags; -} zipfile_t; - -typedef struct zip_s -{ - string filename; - int handle; - int numfiles; - time_t filetime; - zipfile_t *files; -} zip_t; - -typedef struct searchpath_s -{ - string filename; - pack_t *pack; - wfile_t *wad; - zip_t *zip; - int flags; - struct searchpath_s *next; -} searchpath_t; - -static poolhandle_t fs_mempool; -static searchpath_t *fs_searchpaths = NULL; // chain static searchpath_t fs_directpath; // static direct path static char fs_basedir[MAX_SYSPATH]; // base game directory static char fs_gamedir[MAX_SYSPATH]; // game current directory static char fs_writedir[MAX_SYSPATH]; // path that game allows to overwrite, delete and rename files (and create new of course) - -static qboolean fs_ext_path = false; // attempt to read\write from ./ or ../ pathes #if !XASH_WIN32 static qboolean fs_caseinsensitive = true; // try to search missing files #endif +static fs_globals_t FI; +#define GI FI.GameInfo + #ifdef XASH_REDUCE_FD static file_t *fs_last_readfile; static zip_t *fs_last_zip; -static pack_t *fs_last_pak; static void FS_EnsureOpenFile( file_t *file ) { @@ -183,21 +83,6 @@ static void FS_EnsureOpenFile( file_t *file ) } } -static void FS_EnsureOpenZip( zip_t *zip ) -{ - if( fs_last_zip == zip ) - return; - - if( fs_last_zip && (fs_last_zip->handle != -1) ) - { - close( fs_last_zip->handle ); - fs_last_zip->handle = -1; - } - fs_last_zip = zip; - if( zip && (zip->handle == -1) ) - zip->handle = open( zip->filename, O_RDONLY|O_BINARY ); -} - static void FS_BackupFileName( file_t *file, const char *path, uint options ) { if( path == NULL ) @@ -217,21 +102,10 @@ static void FS_BackupFileName( file_t *file, const char *path, uint options ) #else static void FS_EnsureOpenFile( file_t *file ) {} -static void FS_EnsureOpenZip( zip_t *zip ) {} static void FS_BackupFileName( file_t *file, const char *path, uint options ) {} #endif static void FS_InitMemory( void ); -static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ); -static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed char matchtype ); -static dpackfile_t *FS_AddFileToPack( const char* name, pack_t *pack, fs_offset_t offset, fs_offset_t size ); -void Zip_Close( zip_t *zip ); -static byte *W_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); -static wfile_t *W_Open( const char *filename, int *errorcode ); -static qboolean FS_SysFolderExists( const char *path ); -static int FS_SysFileTime( const char *filename ); -static signed char W_TypeFromExt( const char *lumpname ); -static const char *W_ExtFromType( signed char lumptype ); static void FS_Purge( file_t* file ); /* @@ -265,7 +139,7 @@ static void stringlistfreecontents( stringlist_t *list ) list->strings = NULL; } -static void stringlistappend( stringlist_t *list, char *text ) +void stringlistappend( stringlist_t *list, char *text ) { size_t textlen; @@ -374,7 +248,7 @@ FS_FixFileCase emulate WIN32 FS behaviour when opening local file ================== */ -static const char *FS_FixFileCase( const char *path ) +const char *FS_FixFileCase( const char *path ) { #if defined __DOS__ & !defined __WATCOM_LFN__ // not fix, but convert to 8.3 CAPS and rotate slashes @@ -428,7 +302,7 @@ static const char *FS_FixFileCase( const char *path ) } /* android has too slow directory scanning, - so drop out some not useful cases */ + so drop out some not useful cases */ if( fname - path2 > 4 ) { char *point; @@ -472,7 +346,7 @@ FS_PathToWideChar Converts input UTF-8 string to wide char string. ==================== */ -const wchar_t *FS_PathToWideChar( const char *path ) +static const wchar_t *FS_PathToWideChar( const char *path ) { static wchar_t pathBuffer[MAX_PATH]; MultiByteToWideChar( CP_UTF8, 0, path, -1, pathBuffer, MAX_PATH ); @@ -480,49 +354,6 @@ const wchar_t *FS_PathToWideChar( const char *path ) } #endif -/* -==================== -FS_AddFileToPack - -Add a file to the list of files contained into a package -==================== -*/ -static dpackfile_t *FS_AddFileToPack( const char *name, pack_t *pack, fs_offset_t offset, fs_offset_t size ) -{ - int left, right, middle; - dpackfile_t *pfile; - - // look for the slot we should put that file into (binary search) - left = 0; - right = pack->numfiles - 1; - - while( left <= right ) - { - int diff; - - middle = (left + right) / 2; - diff = Q_stricmp( pack->files[middle].name, name ); - - // If we found the file, there's a problem - if( !diff ) Con_Reportf( S_WARN "package %s contains the file %s several times\n", pack->filename, name ); - - // If we're too far in the list - if( diff > 0 ) right = middle - 1; - else left = middle + 1; - } - - // We have to move the right of the list by one slot to free the one we need - pfile = &pack->files[left]; - memmove( pfile + 1, pfile, (pack->numfiles - left) * sizeof( *pfile )); - pack->numfiles++; - - Q_strncpy( pfile->name, name, sizeof( pfile->name )); - pfile->filepos = offset; - pfile->filelen = size; - - return pfile; -} - /* ============ FS_CreatePath @@ -547,703 +378,7 @@ void FS_CreatePath( char *path ) } } -/* -============ -FS_Path_f -debug info -============ -*/ -void FS_Path_f( void ) -{ - searchpath_t *s; - - Con_Printf( "Current search path:\n" ); - - for( s = fs_searchpaths; s; s = s->next ) - { - if( s->pack ) Con_Printf( "%s (%i files)", s->pack->filename, s->pack->numfiles ); - else if( s->wad ) Con_Printf( "%s (%i files)", s->wad->filename, s->wad->numlumps ); - else if( s->zip ) Con_Printf( "%s (%i files)", s->zip->filename, s->zip->numfiles ); - else Con_Printf( "%s", s->filename ); - - if( s->flags & FS_GAMERODIR_PATH ) Con_Printf( " ^2rodir^7" ); - if( s->flags & FS_GAMEDIR_PATH ) Con_Printf( " ^2gamedir^7" ); - if( s->flags & FS_CUSTOM_PATH ) Con_Printf( " ^2custom^7" ); - if( s->flags & FS_NOWRITE_PATH ) Con_Printf( " ^2nowrite^7" ); - if( s->flags & FS_STATIC_PATH ) Con_Printf( " ^2static^7" ); - - Con_Printf( "\n" ); - } -} - -/* -============ -FS_ClearPath_f - -only for debug targets -============ -*/ -void FS_ClearPaths_f( void ) -{ - FS_ClearSearchPath(); -} - -/* -================= -FS_LoadPackPAK - -Takes an explicit (not game tree related) path to a pak file. - -Loads the header and directory, adding the files at the beginning -of the list so they override previous pack files. -================= -*/ -pack_t *FS_LoadPackPAK( const char *packfile, int *error ) -{ - dpackheader_t header; - int packhandle; - int i, numpackfiles; - pack_t *pack; - dpackfile_t *info; - fs_size_t c; - - packhandle = open( packfile, O_RDONLY|O_BINARY ); - -#if !XASH_WIN32 - if( packhandle < 0 ) - { - const char *fpackfile = FS_FixFileCase( packfile ); - if( fpackfile != packfile ) - packhandle = open( fpackfile, O_RDONLY|O_BINARY ); - } -#endif - - if( packhandle < 0 ) - { - Con_Reportf( "%s couldn't open: %s\n", packfile, strerror( errno )); - if( error ) *error = PAK_LOAD_COULDNT_OPEN; - return NULL; - } - - c = read( packhandle, (void *)&header, sizeof( header )); - - if( c != sizeof( header ) || header.ident != IDPACKV1HEADER ) - { - Con_Reportf( "%s is not a packfile. Ignored.\n", packfile ); - if( error ) *error = PAK_LOAD_BAD_HEADER; - close( packhandle ); - return NULL; - } - - if( header.dirlen % sizeof( dpackfile_t )) - { - Con_Reportf( S_ERROR "%s has an invalid directory size. Ignored.\n", packfile ); - if( error ) *error = PAK_LOAD_BAD_FOLDERS; - close( packhandle ); - return NULL; - } - - numpackfiles = header.dirlen / sizeof( dpackfile_t ); - - if( numpackfiles > MAX_FILES_IN_PACK ) - { - Con_Reportf( S_ERROR "%s has too many files ( %i ). Ignored.\n", packfile, numpackfiles ); - if( error ) *error = PAK_LOAD_TOO_MANY_FILES; - close( packhandle ); - return NULL; - } - - if( numpackfiles <= 0 ) - { - Con_Reportf( "%s has no files. Ignored.\n", packfile ); - if( error ) *error = PAK_LOAD_NO_FILES; - close( packhandle ); - return NULL; - } - - info = (dpackfile_t *)Mem_Malloc( fs_mempool, sizeof( *info ) * numpackfiles ); - lseek( packhandle, header.dirofs, SEEK_SET ); - - if( header.dirlen != read( packhandle, (void *)info, header.dirlen )) - { - Con_Reportf( "%s is an incomplete PAK, not loading\n", packfile ); - if( error ) *error = PAK_LOAD_CORRUPTED; - close( packhandle ); - Mem_Free( info ); - return NULL; - } - - pack = (pack_t *)Mem_Calloc( fs_mempool, sizeof( pack_t )); - Q_strncpy( pack->filename, packfile, sizeof( pack->filename )); - pack->files = (dpackfile_t *)Mem_Calloc( fs_mempool, numpackfiles * sizeof( dpackfile_t )); - pack->filetime = FS_SysFileTime( packfile ); - pack->handle = packhandle; - pack->numfiles = 0; - - // parse the directory - for( i = 0; i < numpackfiles; i++ ) - FS_AddFileToPack( info[i].name, pack, info[i].filepos, info[i].filelen ); - -#ifdef XASH_REDUCE_FD - // will reopen when needed - close( pack->handle ); - pack->handle = -1; -#endif - - if( error ) *error = PAK_LOAD_OK; - Mem_Free( info ); - - return pack; -} - -/* -============ -FS_SortZip -============ -*/ -static int FS_SortZip( const void *a, const void *b ) -{ - return Q_stricmp( ( ( zipfile_t* )a )->name, ( ( zipfile_t* )b )->name ); -} - -/* -============ -FS_LoadZip -============ -*/ -static zip_t *FS_LoadZip( const char *zipfile, int *error ) -{ - int numpackfiles = 0, i; - zip_cdf_header_t header_cdf; - zip_header_eocd_t header_eocd; - uint32_t signature; - fs_offset_t filepos = 0, length; - zipfile_t *info = NULL; - char filename_buffer[MAX_SYSPATH]; - zip_t *zip = (zip_t *)Mem_Calloc( fs_mempool, sizeof( *zip )); - fs_size_t c; - - zip->handle = open( zipfile, O_RDONLY|O_BINARY ); - -#if !XASH_WIN32 - if( zip->handle < 0 ) - { - const char *fzipfile = FS_FixFileCase( zipfile ); - if( fzipfile != zipfile ) - zip->handle = open( fzipfile, O_RDONLY|O_BINARY ); - } -#endif - - if( zip->handle < 0 ) - { - Con_Reportf( S_ERROR "%s couldn't open\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_COULDNT_OPEN; - - Zip_Close( zip ); - return NULL; - } - - length = lseek( zip->handle, 0, SEEK_END ); - - if( length > UINT_MAX ) - { - Con_Reportf( S_ERROR "%s bigger than 4GB.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_COULDNT_OPEN; - - Zip_Close( zip ); - return NULL; - } - - lseek( zip->handle, 0, SEEK_SET ); - - c = read( zip->handle, &signature, sizeof( signature ) ); - - if( c != sizeof( signature ) || signature == ZIP_HEADER_EOCD ) - { - Con_Reportf( S_WARN "%s has no files. Ignored.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_NO_FILES; - - Zip_Close( zip ); - return NULL; - } - - if( signature != ZIP_HEADER_LF ) - { - Con_Reportf( S_ERROR "%s is not a zip file. Ignored.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_BAD_HEADER; - - Zip_Close( zip ); - return NULL; - } - - // Find oecd - lseek( zip->handle, 0, SEEK_SET ); - filepos = length; - - while ( filepos > 0 ) - { - lseek( zip->handle, filepos, SEEK_SET ); - c = read( zip->handle, &signature, sizeof( signature ) ); - - if( c == sizeof( signature ) && signature == ZIP_HEADER_EOCD ) - break; - - filepos -= sizeof( char ); // step back one byte - } - - if( ZIP_HEADER_EOCD != signature ) - { - Con_Reportf( S_ERROR "cannot find EOCD in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_BAD_HEADER; - - Zip_Close( zip ); - return NULL; - } - - c = read( zip->handle, &header_eocd, sizeof( header_eocd ) ); - - if( c != sizeof( header_eocd )) - { - Con_Reportf( S_ERROR "invalid EOCD header in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_BAD_HEADER; - - Zip_Close( zip ); - return NULL; - } - - // Move to CDF start - lseek( zip->handle, header_eocd.central_directory_offset, SEEK_SET ); - - // Calc count of files in archive - info = (zipfile_t *)Mem_Calloc( fs_mempool, sizeof( *info ) * header_eocd.total_central_directory_record ); - - for( i = 0; i < header_eocd.total_central_directory_record; i++ ) - { - c = read( zip->handle, &header_cdf, sizeof( header_cdf ) ); - - if( c != sizeof( header_cdf ) || header_cdf.signature != ZIP_HEADER_CDF ) - { - Con_Reportf( S_ERROR "CDF signature mismatch in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_BAD_HEADER; - - Mem_Free( info ); - Zip_Close( zip ); - return NULL; - } - - if( header_cdf.uncompressed_size && header_cdf.filename_len && ( header_cdf.filename_len < MAX_SYSPATH ) ) - { - memset( &filename_buffer, '\0', MAX_SYSPATH ); - c = read( zip->handle, &filename_buffer, header_cdf.filename_len ); - - if( c != header_cdf.filename_len ) - { - Con_Reportf( S_ERROR "filename length mismatch in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_CORRUPTED; - - Mem_Free( info ); - Zip_Close( zip ); - return NULL; - } - - Q_strncpy( info[numpackfiles].name, filename_buffer, MAX_SYSPATH ); - - info[numpackfiles].size = header_cdf.uncompressed_size; - info[numpackfiles].compressed_size = header_cdf.compressed_size; - info[numpackfiles].offset = header_cdf.local_header_offset; - numpackfiles++; - } - else - lseek( zip->handle, header_cdf.filename_len, SEEK_CUR ); - - if( header_cdf.extrafield_len ) - lseek( zip->handle, header_cdf.extrafield_len, SEEK_CUR ); - - if( header_cdf.file_commentary_len ) - lseek( zip->handle, header_cdf.file_commentary_len, SEEK_CUR ); - } - - // recalculate offsets - for( i = 0; i < numpackfiles; i++ ) - { - zip_header_t header; - - lseek( zip->handle, info[i].offset, SEEK_SET ); - c = read( zip->handle, &header, sizeof( header ) ); - - if( c != sizeof( header )) - { - Con_Reportf( S_ERROR "header length mismatch in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_CORRUPTED; - - Mem_Free( info ); - Zip_Close( zip ); - return NULL; - } - - info[i].flags = header.compression_flags; - info[i].offset = info[i].offset + header.filename_len + header.extrafield_len + sizeof( header ); - } - - Q_strncpy( zip->filename, zipfile, sizeof( zip->filename ) ); - zip->filetime = FS_SysFileTime( zipfile ); - zip->numfiles = numpackfiles; - zip->files = info; - - qsort( zip->files, zip->numfiles, sizeof( *zip->files ), FS_SortZip ); - -#ifdef XASH_REDUCE_FD - // will reopen when needed - close(zip->handle); - zip->handle = -1; -#endif - - if( error ) - *error = ZIP_LOAD_OK; - - return zip; -} - -void Zip_Close( zip_t *zip ) -{ - if( !zip ) - return; - - if( zip->files ) - Mem_Free( zip->files ); - - FS_EnsureOpenZip( NULL ); - - if( zip->handle >= 0 ) - close( zip->handle ); - - Mem_Free( zip ); -} - -static byte *Zip_LoadFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ) -{ - searchpath_t *search; - int index; - zipfile_t *file = NULL; - byte *compressed_buffer = NULL, *decompressed_buffer = NULL; - int zlib_result = 0; - dword test_crc, final_crc; - z_stream decompress_stream; - size_t c; - - if( sizeptr ) *sizeptr = 0; - - search = FS_FindFile( path, &index, gamedironly ); - - if( !search || !search->zip ) - return NULL; - - file = &search->zip->files[index]; - - FS_EnsureOpenZip( search->zip ); - - if( lseek( search->zip->handle, file->offset, SEEK_SET ) == -1 ) - return NULL; - - /*if( read( search->zip->handle, &header, sizeof( header ) ) < 0 ) - return NULL; - - if( header.signature != ZIP_HEADER_LF ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s signature error\n", file->name ); - return NULL; - }*/ - - if( file->flags == ZIP_COMPRESSION_NO_COMPRESSION ) - { - decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); - decompressed_buffer[file->size] = '\0'; - - c = read( search->zip->handle, decompressed_buffer, file->size ); - if( c != file->size ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s size doesn't match\n", file->name ); - return NULL; - } - -#if 0 - CRC32_Init( &test_crc ); - CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); - - final_crc = CRC32_Final( test_crc ); - - if( final_crc != file->crc32 ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); - Mem_Free( decompressed_buffer ); - return NULL; - } -#endif - if( sizeptr ) *sizeptr = file->size; - - FS_EnsureOpenZip( NULL ); - return decompressed_buffer; - } - else if( file->flags == ZIP_COMPRESSION_DEFLATED ) - { - compressed_buffer = Mem_Malloc( fs_mempool, file->compressed_size + 1 ); - decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); - decompressed_buffer[file->size] = '\0'; - - c = read( search->zip->handle, compressed_buffer, file->compressed_size ); - if( c != file->compressed_size ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s compressed size doesn't match\n", file->name ); - return NULL; - } - - memset( &decompress_stream, 0, sizeof( decompress_stream ) ); - - decompress_stream.total_in = decompress_stream.avail_in = file->compressed_size; - decompress_stream.next_in = (Bytef *)compressed_buffer; - decompress_stream.total_out = decompress_stream.avail_out = file->size; - decompress_stream.next_out = (Bytef *)decompressed_buffer; - - decompress_stream.zalloc = Z_NULL; - decompress_stream.zfree = Z_NULL; - decompress_stream.opaque = Z_NULL; - - if( inflateInit2( &decompress_stream, -MAX_WBITS ) != Z_OK ) - { - Con_Printf( S_ERROR "Zip_LoadFile: inflateInit2 failed\n" ); - Mem_Free( compressed_buffer ); - Mem_Free( decompressed_buffer ); - return NULL; - } - - zlib_result = inflate( &decompress_stream, Z_NO_FLUSH ); - inflateEnd( &decompress_stream ); - - if( zlib_result == Z_OK || zlib_result == Z_STREAM_END ) - { - Mem_Free( compressed_buffer ); // finaly free compressed buffer -#if 0 - CRC32_Init( &test_crc ); - CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); - - final_crc = CRC32_Final( test_crc ); - - if( final_crc != file->crc32 ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); - Mem_Free( decompressed_buffer ); - return NULL; - } -#endif - if( sizeptr ) *sizeptr = file->size; - - FS_EnsureOpenZip( NULL ); - return decompressed_buffer; - } - else - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s : error while file decompressing. Zlib return code %d.\n", file->name, zlib_result ); - Mem_Free( compressed_buffer ); - Mem_Free( decompressed_buffer ); - return NULL; - } - - } - else - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s : file compressed with unknown algorithm.\n", file->name ); - return NULL; - } - - FS_EnsureOpenZip( NULL ); - return NULL; -} - -/* -==================== -FS_AddWad_Fullpath -==================== -*/ -static qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ) -{ - searchpath_t *search; - wfile_t *wad = NULL; - const char *ext = COM_FileExtension( wadfile ); - int errorcode = WAD_LOAD_COULDNT_OPEN; - - for( search = fs_searchpaths; search; search = search->next ) - { - if( search->wad && !Q_stricmp( search->wad->filename, wadfile )) - { - if( already_loaded ) *already_loaded = true; - return true; // already loaded - } - } - - if( already_loaded ) - *already_loaded = false; - - if( !Q_stricmp( ext, "wad" )) - wad = W_Open( wadfile, &errorcode ); - - if( wad ) - { - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); - search->wad = wad; - search->next = fs_searchpaths; - search->flags |= flags; - fs_searchpaths = search; - - Con_Reportf( "Adding wadfile: %s (%i files)\n", wadfile, wad->numlumps ); - return true; - } - else - { - if( errorcode != WAD_LOAD_NO_FILES ) - Con_Reportf( S_ERROR "FS_AddWad_Fullpath: unable to load wad \"%s\"\n", wadfile ); - return false; - } -} - -/* -================ -FS_AddPak_Fullpath - -Adds the given pack to the search path. -The pack type is autodetected by the file extension. - -Returns true if the file was successfully added to the -search path or if it was already included. - -If keep_plain_dirs is set, the pack will be added AFTER the first sequence of -plain directories. -================ -*/ -static qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ) -{ - searchpath_t *search; - pack_t *pak = NULL; - const char *ext = COM_FileExtension( pakfile ); - int i, errorcode = PAK_LOAD_COULDNT_OPEN; - - for( search = fs_searchpaths; search; search = search->next ) - { - if( search->pack && !Q_stricmp( search->pack->filename, pakfile )) - { - if( already_loaded ) *already_loaded = true; - return true; // already loaded - } - } - - if( already_loaded ) - *already_loaded = false; - - if( !Q_stricmp( ext, "pak" )) - pak = FS_LoadPackPAK( pakfile, &errorcode ); - - if( pak ) - { - string fullpath; - - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); - search->pack = pak; - search->next = fs_searchpaths; - search->flags |= flags; - fs_searchpaths = search; - - Con_Reportf( "Adding pakfile: %s (%i files)\n", pakfile, pak->numfiles ); - - // time to add in search list all the wads that contains in current pakfile (if do) - for( i = 0; i < pak->numfiles; i++ ) - { - if( !Q_stricmp( COM_FileExtension( pak->files[i].name ), "wad" )) - { - Q_snprintf( fullpath, MAX_STRING, "%s/%s", pakfile, pak->files[i].name ); - FS_AddWad_Fullpath( fullpath, NULL, flags ); - } - } - - return true; - } - else - { - if( errorcode != PAK_LOAD_NO_FILES ) - Con_Reportf( S_ERROR "FS_AddPak_Fullpath: unable to load pak \"%s\"\n", pakfile ); - return false; - } -} - -static qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ) -{ - searchpath_t *search; - zip_t *zip = NULL; - const char *ext = COM_FileExtension( zipfile ); - int errorcode = ZIP_LOAD_COULDNT_OPEN; - - for( search = fs_searchpaths; search; search = search->next ) - { - if( search->pack && !Q_stricmp( search->pack->filename, zipfile )) - { - if( already_loaded ) *already_loaded = true; - return true; // already loaded - } - } - - if( already_loaded ) *already_loaded = false; - - if( !Q_stricmp( ext, "pk3" ) ) - zip = FS_LoadZip( zipfile, &errorcode ); - - if( zip ) - { - string fullpath; - int i; - - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ) ); - search->zip = zip; - search->next = fs_searchpaths; - search->flags |= flags; - fs_searchpaths = search; - - Con_Reportf( "Adding zipfile: %s (%i files)\n", zipfile, zip->numfiles ); - - // time to add in search list all the wads that contains in current pakfile (if do) - for( i = 0; i < zip->numfiles; i++ ) - { - if( !Q_stricmp( COM_FileExtension( zip->files[i].name ), "wad" )) - { - Q_snprintf( fullpath, MAX_STRING, "%s/%s", zipfile, zip->files[i].name ); - FS_AddWad_Fullpath( fullpath, NULL, flags ); - } - } - return true; - } - else - { - if( errorcode != ZIP_LOAD_NO_FILES ) - Con_Reportf( S_ERROR "FS_AddZip_Fullpath: unable to load zip \"%s\"\n", zipfile ); - return false; - } -} /* ================ @@ -1324,62 +459,11 @@ void FS_AddGameDirectory( const char *dir, uint flags ) search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); Q_strncpy( search->filename, dir, sizeof ( search->filename )); search->next = fs_searchpaths; + search->type = SEARCHPATH_PLAIN; search->flags = flags; fs_searchpaths = search; } -/* -================ -FS_AddGameHierarchy -================ -*/ -void FS_AddGameHierarchy( const char *dir, uint flags ) -{ - int i; - qboolean isGameDir = flags & FS_GAMEDIR_PATH; - - GI->added = true; - - if( !COM_CheckString( dir )) - return; - - // add the common game directory - - // recursive gamedirs - // for example, czeror->czero->cstrike->valve - for( i = 0; i < SI.numgames; i++ ) - { - if( !Q_strnicmp( SI.games[i]->gamefolder, dir, 64 )) - { - Con_Reportf( "FS_AddGameHierarchy: %d %s %s\n", i, SI.games[i]->gamefolder, SI.games[i]->basedir ); - if( !SI.games[i]->added && Q_stricmp( SI.games[i]->gamefolder, SI.games[i]->basedir ) ) - { - SI.games[i]->added = true; - FS_AddGameHierarchy( SI.games[i]->basedir, flags & (~FS_GAMEDIR_PATH) ); - } - break; - } - } - - if( COM_CheckStringEmpty( host.rodir ) ) - { - // append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH - uint newFlags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH)); - if( isGameDir ) - newFlags |= FS_GAMERODIR_PATH; - - FS_AllowDirectPaths( true ); - FS_AddGameDirectory( va( "%s/%s/", host.rodir, dir ), newFlags ); - FS_AllowDirectPaths( false ); - } - - if( isGameDir ) - FS_AddGameDirectory( va( "%s/downloaded/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); - FS_AddGameDirectory( va( "%s/", dir ), flags ); - if( isGameDir ) - FS_AddGameDirectory( va( "%s/custom/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); -} - /* ================ FS_ClearSearchPath @@ -1402,23 +486,19 @@ void FS_ClearSearchPath( void ) } else fs_searchpaths = search->next; - if( search->pack ) + switch( search->type ) { - if( search->pack->files ) - Mem_Free( search->pack->files ); - if( search->pack->handle >= 0 ) - close( search->pack->handle ); - Mem_Free( search->pack ); - } - - if( search->wad ) - { - W_Close( search->wad ); - } - - if( search->zip ) - { - Zip_Close(search->zip); + case SEARCHPATH_PAK: + FS_ClosePAK( search->pack ); + break; + case SEARCHPATH_WAD: + FS_CloseWAD( search->wad ); + break; + case SEARCHPATH_ZIP: + FS_CloseZIP( search->zip ); + break; + default: + break; } Mem_Free( search ); @@ -1428,19 +508,18 @@ void FS_ClearSearchPath( void ) /* ==================== FS_CheckNastyPath - Return true if the path should be rejected due to one of the following: 1: path elements that are non-portable 2: path elements that would allow access to files outside the game directory, - or are just not a good idea for a mod to be using. + or are just not a good idea for a mod to be using. ==================== */ -int FS_CheckNastyPath( const char *path, qboolean isgamedir ) +int FS_CheckNastyPath (const char *path, qboolean isgamedir) { // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless if( !COM_CheckString( path )) return 2; - if( fs_ext_path ) return 0; // allow any path + if( fs_ext_path ) return 0; // allow any path // Mac: don't allow Mac-only filenames - : is a directory separator // instead of /, but we rely on / working already, so there's no reason to @@ -1449,72 +528,23 @@ int FS_CheckNastyPath( const char *path, qboolean isgamedir ) if( Q_strchr( path, ':' )) return 1; // non-portable attempt to go to root of drive // Amiga: // is parent directory - if( Q_strstr( path, "//" )) return 1; // non-portable attempt to go to parent directory + if( Q_strstr( path, "//")) return 1; // non-portable attempt to go to parent directory // all: don't allow going to parent directory (../ or /../) - if( Q_strstr( path, ".." )) return 2; // attempt to go outside the game directory + if( Q_strstr( path, "..")) return 2; // attempt to go outside the game directory // Windows and UNIXes: don't allow absolute paths - if( path[0] == '/' ) return 2; // attempt to go outside the game directory + if( path[0] == '/') return 2; // attempt to go outside the game directory // all: forbid trailing slash on gamedir - if( isgamedir && path[Q_strlen( path )-1] == '/' ) return 2; + if( isgamedir && path[Q_strlen(path)-1] == '/' ) return 2; // all: forbid leading dot on any filename for any reason - if( Q_strstr( path, "/." )) return 2; // attempt to go outside the game directory + if( Q_strstr(path, "/.")) return 2; // attempt to go outside the game directory // after all these checks we're pretty sure it's a / separated filename // and won't do much if any harm - return 0; -} - -/* -================ -FS_Rescan -================ -*/ -void FS_Rescan( void ) -{ - const char *str; - const int extrasFlags = FS_NOWRITE_PATH | FS_CUSTOM_PATH; - Con_Reportf( "FS_Rescan( %s )\n", GI->title ); - - FS_ClearSearchPath(); - -#if XASH_IOS - { - FS_AddPak_Fullpath( va( "%sextras.pak", SDL_GetBasePath() ), NULL, extrasFlags ); - FS_AddPak_Fullpath( va( "%sextras_%s.pak", SDL_GetBasePath(), GI->gamefolder ), NULL, extrasFlags ); - } -#else - str = getenv( "XASH3D_EXTRAS_PAK1" ); - if( COM_CheckString( str )) - FS_AddArchive_Fullpath( str, NULL, extrasFlags ); - - str = getenv( "XASH3D_EXTRAS_PAK2" ); - if( COM_CheckString( str )) - FS_AddArchive_Fullpath( str, NULL, extrasFlags ); -#endif - - if( Q_stricmp( GI->basedir, GI->gamefolder )) - FS_AddGameHierarchy( GI->basedir, 0 ); - if( Q_stricmp( GI->basedir, GI->falldir ) && Q_stricmp( GI->gamefolder, GI->falldir )) - FS_AddGameHierarchy( GI->falldir, 0 ); - FS_AddGameHierarchy( GI->gamefolder, FS_GAMEDIR_PATH ); - - if( FS_FileExists( va( "%s.rc", SI.basedirName ), false )) - Q_strncpy( SI.rcName, SI.basedirName, sizeof( SI.rcName )); // e.g. valve.rc - else Q_strncpy( SI.rcName, SI.exeName, sizeof( SI.rcName )); // e.g. quake.rc -} - -/* -================ -FS_Rescan_f -================ -*/ -void FS_Rescan_f( void ) -{ - FS_Rescan(); + return false; } /* @@ -1886,7 +916,7 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool } // make sure what gamedir is really exist - if( !FS_SysFolderExists( va( "%s"PATH_SPLITTER"%s", host.rootdir, GameInfo->falldir ))) + if( !FS_SysFolderExists( va( "%s"PATH_SPLITTER"%s", fs_rootdir, GameInfo->falldir ))) GameInfo->falldir[0] = '\0'; } @@ -2011,15 +1041,15 @@ static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo ) Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir ); // here goes some RoDir magic... - if( COM_CheckStringEmpty( host.rodir ) ) + if( COM_CheckStringEmpty( fs_rodir ) ) { string filepath_ro, liblist_ro; fs_offset_t roLibListTime, roGameInfoTime, rwGameInfoTime; FS_AllowDirectPaths( true ); - Q_snprintf( filepath_ro, sizeof( filepath_ro ), "%s/%s/gameinfo.txt", host.rodir, gamedir ); - Q_snprintf( liblist_ro, sizeof( liblist_ro ), "%s/%s/liblist.gam", host.rodir, gamedir ); + Q_snprintf( filepath_ro, sizeof( filepath_ro ), "%s/%s/gameinfo.txt", fs_rodir, gamedir ); + Q_snprintf( liblist_ro, sizeof( liblist_ro ), "%s/%s/liblist.gam", fs_rodir, gamedir ); roLibListTime = FS_SysFileTime( liblist_ro ); roGameInfoTime = FS_SysFileTime( filepath_ro ); @@ -2076,6 +1106,93 @@ static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo ) return false; } +/* +================ +FS_AddGameHierarchy +================ +*/ +void FS_AddGameHierarchy( const char *dir, uint flags ) +{ + int i; + qboolean isGameDir = flags & FS_GAMEDIR_PATH; + + GI->added = true; + + if( !COM_CheckString( dir )) + return; + + // add the common game directory + + // recursive gamedirs + // for example, czeror->czero->cstrike->valve + for( i = 0; i < FI.numgames; i++ ) + { + if( !Q_strnicmp( FI.games[i]->gamefolder, dir, 64 )) + { + Con_Reportf( "FS_AddGameHierarchy: %d %s %s\n", i, FI.games[i]->gamefolder, FI.games[i]->basedir ); + if( !FI.games[i]->added && Q_stricmp( FI.games[i]->gamefolder, FI.games[i]->basedir )) + { + FI.games[i]->added = true; + FS_AddGameHierarchy( FI.games[i]->basedir, flags & (~FS_GAMEDIR_PATH) ); + } + break; + } + } + + if( COM_CheckStringEmpty( fs_rodir ) ) + { + // append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH + uint newFlags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH)); + if( isGameDir ) + newFlags |= FS_GAMERODIR_PATH; + + FS_AllowDirectPaths( true ); + FS_AddGameDirectory( va( "%s/%s/", fs_rodir, dir ), newFlags ); + FS_AllowDirectPaths( false ); + } + + if( isGameDir ) + FS_AddGameDirectory( va( "%s/downloaded/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + FS_AddGameDirectory( va( "%s/", dir ), flags ); + if( isGameDir ) + FS_AddGameDirectory( va( "%s/custom/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); +} + +/* +================ +FS_Rescan +================ +*/ +void FS_Rescan( void ) +{ + const char *str; + const int extrasFlags = FS_NOWRITE_PATH | FS_CUSTOM_PATH; + Con_Reportf( "FS_Rescan( %s )\n", GI->title ); + + FS_ClearSearchPath(); + +#if XASH_IOS + { + FS_AddPak_Fullpath( va( "%sextras.pak", SDL_GetBasePath() ), NULL, extrasFlags ); + FS_AddPak_Fullpath( va( "%sextras_%s.pak", SDL_GetBasePath(), GI->gamefolder ), NULL, extrasFlags ); + } +#else + str = getenv( "XASH3D_EXTRAS_PAK1" ); + if( COM_CheckString( str )) + FS_AddArchive_Fullpath( str, NULL, extrasFlags ); + + str = getenv( "XASH3D_EXTRAS_PAK2" ); + if( COM_CheckString( str )) + FS_AddArchive_Fullpath( str, NULL, extrasFlags ); +#endif + + if( Q_stricmp( GI->basedir, GI->gamefolder )) + FS_AddGameHierarchy( GI->basedir, 0 ); + if( Q_stricmp( GI->basedir, GI->falldir ) && Q_stricmp( GI->gamefolder, GI->falldir )) + FS_AddGameHierarchy( GI->falldir, 0 ); + FS_AddGameHierarchy( GI->gamefolder, FS_GAMEDIR_PATH ); +} + /* ================ FS_LoadGameInfo @@ -2097,39 +1214,176 @@ void FS_LoadGameInfo( const char *rootfolder ) FS_ClearSearchPath(); // validate gamedir - for( i = 0; i < SI.numgames; i++ ) + for( i = 0; i < FI.numgames; i++ ) { - if( !Q_stricmp( SI.games[i]->gamefolder, fs_gamedir )) + if( !Q_stricmp( FI.games[i]->gamefolder, fs_gamedir )) break; } - if( i == SI.numgames ) + if( i == FI.numgames ) Sys_Error( "Couldn't find game directory '%s'\n", fs_gamedir ); - SI.GameInfo = SI.games[i]; - - if( !Sys_GetParmFromCmdLine( "-dll", SI.gamedll ) ) - { - SI.gamedll[0] = 0; - } - - if( !Sys_GetParmFromCmdLine( "-clientlib", SI.clientlib ) ) - { - SI.clientlib[0] = 0; - } + FI.GameInfo = FI.games[i]; FS_Rescan(); // create new filesystem - - Image_CheckPaletteQ1 (); - Host_InitDecals (); // reload decals } +/* +================== +FS_CheckForCrypt + +return true if library is crypted +================== +*/ +static qboolean FS_CheckForCrypt( const char *dllname ) +{ + file_t *f; + int key; + + f = FS_Open( dllname, "rb", false ); + if( !f ) return false; + + FS_Seek( f, 64, SEEK_SET ); // skip first 64 bytes + FS_Read( f, &key, sizeof( key )); + FS_Close( f ); + + return ( key == 0x12345678 ) ? true : false; +} + +/* +================== +FS_FindLibrary + +search for library, assume index is valid +================== +*/ +static qboolean FS_FindLibrary( const char *dllname, qboolean directpath, fs_dllinfo_t *dllInfo ) +{ + searchpath_t *search; + int index, start = 0, i, len; + + fs_ext_path = directpath; + + // check for bad exports + if( !COM_CheckString( dllname )) + return false; + + // HACKHACK remove absoulte path to valve folder + if( !Q_strnicmp( dllname, "..\\valve\\", 9 ) || !Q_strnicmp( dllname, "../valve/", 9 )) + start += 9; + + // replace all backward slashes + len = Q_strlen( dllname ); + + for( i = 0; i < len; i++ ) + { + if( dllname[i+start] == '\\' ) dllInfo->shortPath[i] = '/'; + else dllInfo->shortPath[i] = Q_tolower( dllname[i+start] ); + } + dllInfo->shortPath[i] = '\0'; + + COM_DefaultExtension( dllInfo->shortPath, "."OS_LIB_EXT ); // apply ext if forget + + search = FS_FindFile( dllInfo->shortPath, &index, false ); + + if( !search && !directpath ) + { + fs_ext_path = false; + + // trying check also 'bin' folder for indirect paths + Q_strncpy( dllInfo->shortPath, dllname, sizeof( dllInfo->shortPath )); + search = FS_FindFile( dllInfo->shortPath, &index, false ); + if( !search ) return false; // unable to find + } + + dllInfo->encrypted = FS_CheckForCrypt( dllInfo->shortPath ); + + if( index < 0 && !dllInfo->encrypted && search ) + { + Q_snprintf( dllInfo->fullPath, sizeof( dllInfo->fullPath ), + "%s%s", search->filename, dllInfo->shortPath ); + dllInfo->custom_loader = false; // we can loading from disk and use normal debugging + } + else + { + // NOTE: if search is NULL let the OS found library himself + Q_strncpy( dllInfo->fullPath, dllInfo->shortPath, sizeof( dllInfo->fullPath )); + + if( search && search->type != SEARCHPATH_PLAIN ) + { +#if XASH_WIN32 && XASH_X86 // a1ba: custom loader is non-portable (I just don't want to touch it) + Con_Printf( S_WARN "%s: loading libraries from packs is deprecated " + "and will be removed in the future\n", __FUNCTION__ ); + *custom_loader = true; +#else + Con_Printf( S_WARN "%s: loading libraries from packs is unsupported on " + "this platform\n", __FUNCTION__ ); + dllInfo->custom_loader = false; +#endif + } + else + { + dllInfo->custom_loader = false; + } + } + fs_ext_path = false; // always reset direct paths + + return true; +} + +poolhandle_t _Mem_AllocPool( const char *name, const char *filename, int fileline ) +{ + return 0xDEADC0DE; +} + +void _Mem_FreePool( poolhandle_t *poolptr, const char *filename, int fileline ) +{ + // stub +} + +void* _Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) +{ + if( clear ) return calloc( 1, size ); + return malloc( size ); +} + +void* _Mem_Realloc( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ) +{ + return realloc( memptr, size ); +} + +void _Mem_Free( void *data, const char *filename, int fileline ) +{ + free( data ); +} + +void _Con_Printf( const char *fmt, ... ) +{ + va_list ap; + + va_start( ap, fmt ); + vprintf( fmt, ap ); + va_end( ap ); +} + +void _Sys_Error( const char *fmt, ... ) +{ + va_list ap; + + va_start( ap, fmt ); + vfprintf( stderr, fmt, ap ); + va_end( ap ); + + exit( 1 ); +} + + /* ================ FS_Init ================ */ -void FS_Init( void ) +qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ) { stringlist_t dirs; qboolean hasBaseDir = false; @@ -2138,61 +1392,30 @@ void FS_Init( void ) FS_InitMemory(); - Cmd_AddRestrictedCommand( "fs_rescan", FS_Rescan_f, "rescan filesystem search pathes" ); - Cmd_AddRestrictedCommand( "fs_path", FS_Path_f, "show filesystem search pathes" ); - Cmd_AddRestrictedCommand( "fs_clearpaths", FS_ClearPaths_f, "clear filesystem search pathes" ); + fs_caseinsensitive = caseinsensitive; -#if !XASH_WIN32 - if( Sys_CheckParm( "-casesensitive" ) ) - fs_caseinsensitive = false; - - if( !fs_caseinsensitive ) - { - if( COM_CheckStringEmpty( host.rodir ) && !Q_strcmp( host.rodir, host.rootdir ) ) - { - Sys_Error( "RoDir and default rootdir can't point to same directory!" ); - } - } - else -#endif - { - if( COM_CheckStringEmpty( host.rodir ) && !Q_stricmp( host.rodir, host.rootdir ) ) - { - Sys_Error( "RoDir and default rootdir can't point to same directory!" ); - } - } - - // ignore commandlineoption "-game" for other stuff - SI.numgames = 0; - - Q_strncpy( fs_basedir, SI.basedirName, sizeof( fs_basedir )); // default dir - - if( !Sys_GetParmFromCmdLine( "-game", fs_gamedir )) - Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); // gamedir == basedir - - if( FS_CheckNastyPath( fs_basedir, true )) - { - // this is completely fatal... - Sys_Error( "invalid base directory \"%s\"\n", fs_basedir ); - } - - if( FS_CheckNastyPath( fs_gamedir, true )) - { - Con_Printf( S_ERROR "invalid game directory \"%s\"\n", fs_gamedir ); - Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); // default dir - } + Q_strncpy( fs_rootdir, rootdir, sizeof( fs_rootdir )); + Q_strncpy( fs_gamedir, gamedir, sizeof( fs_gamedir )); + Q_strncpy( fs_basedir, basedir, sizeof( fs_basedir )); + Q_strncpy( fs_rodir, rodir, sizeof( fs_rodir )); // add readonly directories first - if( COM_CheckStringEmpty( host.rodir ) ) + if( COM_CheckStringEmpty( fs_rodir )) { + if( !Q_stricmp( fs_rodir, fs_rootdir )) + { + Sys_Error( "RoDir and default rootdir can't point to same directory!" ); + return false; + } + stringlistinit( &dirs ); - listdirectory( &dirs, host.rodir, false ); + listdirectory( &dirs, fs_rodir, false ); stringlistsort( &dirs ); for( i = 0; i < dirs.numstrings; i++ ) { - char *roPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, host.rodir, dirs.strings[i] ); - char *rwPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, host.rootdir, dirs.strings[i] ); + char *roPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, fs_rodir, dirs.strings[i] ); + char *rwPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, fs_rootdir, dirs.strings[i] ); // check if it's a directory if( !FS_SysFolderExists( roPath )) @@ -2233,15 +1456,17 @@ void FS_Init( void ) if( !FS_SysFolderExists( dirs.strings[i] ) || ( !Q_strcmp( dirs.strings[i], ".." ) && !fs_ext_path )) continue; - if( SI.games[SI.numgames] == NULL ) - SI.games[SI.numgames] = (gameinfo_t *)Mem_Calloc( fs_mempool, sizeof( gameinfo_t )); + if( FI.games[FI.numgames] == NULL ) + FI.games[FI.numgames] = (gameinfo_t *)Mem_Calloc( fs_mempool, sizeof( gameinfo_t )); - if( FS_ParseGameInfo( dirs.strings[i], SI.games[SI.numgames] )) - SI.numgames++; // added + if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames] )) + FI.numgames++; // added } stringlistfreecontents( &dirs ); Con_Reportf( "FS_Init: done\n" ); + + return true; } void FS_AllowDirectPaths( qboolean enable ) @@ -2254,20 +1479,62 @@ void FS_AllowDirectPaths( qboolean enable ) FS_Shutdown ================ */ -void FS_Shutdown( void ) +void FS_ShutdownStdio( void ) { - int i; - + int i; // release gamedirs - for( i = 0; i < SI.numgames; i++ ) - if( SI.games[i] ) Mem_Free( SI.games[i] ); - - memset( &SI, 0, sizeof( sysinfo_t )); + for( i = 0; i < FI.numgames; i++ ) + if( FI.games[i] ) Mem_Free( FI.games[i] ); FS_ClearSearchPath(); // release all wad files too Mem_FreePool( &fs_mempool ); } +/* +============ +FS_Path_f + +debug info +============ +*/ +void FS_Path_f( void ) +{ + searchpath_t *s; + + Con_Printf( "Current search path:\n" ); + + for( s = fs_searchpaths; s; s = s->next ) + { + string info; + + switch( s->type ) + { + case SEARCHPATH_PAK: + FS_PrintPAKInfo( info, sizeof( info ), s->pack ); + break; + case SEARCHPATH_WAD: + FS_PrintWADInfo( info, sizeof( info ), s->wad ); + break; + case SEARCHPATH_ZIP: + FS_PrintZIPInfo( info, sizeof( info ), s->zip ); + break; + case SEARCHPATH_PLAIN: + Q_strncpy( info, s->filename, sizeof( info )); + break; + } + + Con_Printf( "%s", info ); + + if( s->flags & FS_GAMERODIR_PATH ) Con_Printf( " ^2rodir^7" ); + if( s->flags & FS_GAMEDIR_PATH ) Con_Printf( " ^2gamedir^7" ); + if( s->flags & FS_CUSTOM_PATH ) Con_Printf( " ^2custom^7" ); + if( s->flags & FS_NOWRITE_PATH ) Con_Printf( " ^2nowrite^7" ); + if( s->flags & FS_STATIC_PATH ) Con_Printf( " ^2static^7" ); + + Con_Printf( "\n" ); + } +} + /* ==================== FS_SysFileTime @@ -2275,7 +1542,7 @@ FS_SysFileTime Internal function used to determine filetime ==================== */ -static int FS_SysFileTime( const char *filename ) +int FS_SysFileTime( const char *filename ) { struct stat buf; @@ -2292,7 +1559,7 @@ FS_SysOpen Internal function used to create a file_t and open the relevant non-packed file on disk ==================== */ -static file_t *FS_SysOpen( const char *filepath, const char *mode ) +file_t *FS_SysOpen( const char *filepath, const char *mode ) { file_t *file; int mod, opt; @@ -2391,7 +1658,7 @@ static int FS_DuplicateHandle( const char *filename, int handle, fs_offset_t pos } */ -static file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_offset_t len ) +file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_offset_t len ) { file_t *file = (file_t *)Mem_Calloc( fs_mempool, sizeof( file_t )); #ifndef XASH_REDUCE_FD @@ -2422,44 +1689,6 @@ static file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offse return file; } -/* -=========== -FS_OpenPackedFile - -Open a packed file using its package file descriptor -=========== -*/ -file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ) -{ - dpackfile_t *pfile; - - pfile = &pack->files[pack_ind]; - - return FS_OpenHandle( pack->filename, pack->handle, pfile->filepos, pfile->filelen ); -} - -/* -=========== -FS_OpenZipFile - -Open a packed file using its package file descriptor -=========== -*/ -file_t *FS_OpenZipFile( zip_t *zip, int pack_ind ) -{ - zipfile_t *pfile; - pfile = &zip->files[pack_ind]; - - // compressed files handled in Zip_LoadFile - if( pfile->flags != ZIP_COMPRESSION_NO_COMPRESSION ) - { - Con_Printf( S_ERROR "%s: can't open compressed file %s\n", __FUNCTION__, pfile->name ); - return NULL; - } - - return FS_OpenHandle( zip->filename, zip->handle, pfile->offset, pfile->size ); -} - /* ================== FS_SysFileExists @@ -2535,11 +1764,7 @@ qboolean FS_SysFolderExists( const char *path ) struct stat buf; if( stat( path, &buf ) < 0 ) - { - if( errno != ENOTDIR ) - Con_Reportf( S_ERROR "FS_SysFolderExists: problem while opening dir: %s\n", strerror( errno )); return false; - } return S_ISDIR( buf.st_mode ); #else @@ -2557,7 +1782,7 @@ Return the searchpath where the file was found (or NULL) and the file index in the package if relevant ==================== */ -static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ) +searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ) { searchpath_t *search; char *pEnvPath; @@ -2569,107 +1794,31 @@ static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedir continue; // is the element a pak file? - if( search->pack ) + if( search->type == SEARCHPATH_PAK ) { - int left, right, middle; - pack_t *pak; - - pak = search->pack; - - // look for the file (binary search) - left = 0; - right = pak->numfiles - 1; - while( left <= right ) + int pack_ind = FS_FindFilePAK( search->pack, name ); + if( pack_ind >= 0 ) { - int diff; - - middle = (left + right) / 2; - diff = Q_stricmp( pak->files[middle].name, name ); - - // Found it - if( !diff ) - { - if( index ) *index = middle; - return search; - } - - // if we're too far in the list - if( diff > 0 ) - right = middle - 1; - else left = middle + 1; - } - } - else if( search->wad ) - { - dlumpinfo_t *lump; - signed char type = W_TypeFromExt( name ); - qboolean anywadname = true; - string wadname, wadfolder; - string shortname; - - // quick reject by filetype - if( type == TYP_NONE ) continue; - COM_ExtractFilePath( name, wadname ); - wadfolder[0] = '\0'; - - if( COM_CheckStringEmpty( wadname ) ) - { - COM_FileBase( wadname, wadname ); - Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); - COM_DefaultExtension( wadname, ".wad" ); - anywadname = false; - } - - // make wadname from wad fullpath - COM_FileBase( search->wad->filename, shortname ); - COM_DefaultExtension( shortname, ".wad" ); - - // quick reject by wadname - if( !anywadname && Q_stricmp( wadname, shortname )) - continue; - - // NOTE: we can't using long names for wad, - // because we using original wad names[16]; - COM_FileBase( name, shortname ); - - lump = W_FindLump( search->wad, shortname, type ); - - if( lump ) - { - if( index ) - *index = lump - search->wad->lumps; + if( index ) *index = pack_ind; return search; } } - else if( search->zip ) + else if( search->type == SEARCHPATH_WAD ) { - int left, right, middle; - zip_t *zip; - - zip = search->zip; - - // look for the file (binary search) - left = 0; - right = zip->numfiles - 1; - - while( left <= right ) + int pack_ind = FS_FindFileWAD( search->wad, name ); + if( pack_ind >= 0 ) { - int diff; - - middle = (left + right) / 2; - diff = Q_stricmp( zip->files[middle].name, name ); - - // Found it - if( !diff ) - { - if( index ) *index = middle; - return search; - } - - // if we're too far in the list - if( diff > 0 ) - right = middle - 1; - else left = middle + 1; + if( index ) *index = pack_ind; + return search; + } + } + else if( search->type == SEARCHPATH_ZIP ) + { + int pack_ind = FS_FindFileZIP( search->zip, name ); + if( pack_ind >= 0 ) + { + if( index ) *index = pack_ind; + return search; } } else @@ -2695,7 +1844,7 @@ static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedir memset( search, 0, sizeof( searchpath_t )); // root folder has a more priority than netpath - Q_strncpy( search->filename, host.rootdir, sizeof( search->filename )); + Q_strncpy( search->filename, fs_rootdir, sizeof( search->filename )); Q_strcat( search->filename, PATH_SPLITTER ); Q_snprintf( netpath, MAX_SYSPATH, "%s%s", search->filename, name ); @@ -2705,26 +1854,6 @@ static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedir *index = -1; return search; } - -#if 0 - // search for environment path - while( ( pEnvPath = getenv( "Path" ) ) ) - { - char *end = Q_strchr( pEnvPath, ';' ); - if( !end ) break; - Q_strncpy( search->filename, pEnvPath, (end - pEnvPath) + 1 ); - Q_strcat( search->filename, PATH_SPLITTER ); - Q_snprintf( netpath, MAX_SYSPATH, "%s%s", search->filename, name ); - - if( FS_SysFileExists( netpath, !( search->flags & FS_CUSTOM_PATH ) )) - { - if( index != NULL ) - *index = -1; - return search; - } - pEnvPath += (end - pEnvPath) + 1; // move pointer - } -#endif // 0 } if( index != NULL ) @@ -2752,19 +1881,23 @@ file_t *FS_OpenReadFile( const char *filename, const char *mode, qboolean gamedi if( search == NULL ) return NULL; - if( search->pack ) - return FS_OpenPackedFile( search->pack, pack_ind ); - else if( search->wad ) - return NULL; // let W_LoadFile get lump correctly - else if( search->zip ) - return FS_OpenZipFile( search->zip, pack_ind ); - else if( pack_ind < 0 ) + switch( search->type ) { - char path [MAX_SYSPATH]; + case SEARCHPATH_PAK: + return FS_OpenPackedFile( search->pack, pack_ind ); + case SEARCHPATH_WAD: + return NULL; // let W_LoadFile get lump correctly + case SEARCHPATH_ZIP: + return FS_OpenZipFile( search->zip, pack_ind ); + default: + if( pack_ind < 0 ) + { + char path [MAX_SYSPATH]; - // found in the filesystem? - Q_sprintf( path, "%s%s", search->filename, filename ); - return FS_SysOpen( path, mode ); + // found in the filesystem? + Q_sprintf( path, "%s%s", search->filename, filename ); + return FS_SysOpen( path, mode ); + } } return NULL; @@ -2832,6 +1965,27 @@ int FS_Close( file_t *file ) return 0; } +/* +==================== +FS_Flush + +flushes written data to disk +==================== +*/ +int FS_Flush( file_t *file ) +{ + if( !file ) return 0; + + // purge cached data + FS_Purge( file ); + + // sync + if( fsync( file->handle ) < 0 ) + return EOF; + + return 0; +} + /* ==================== FS_Write @@ -3159,7 +2313,7 @@ FS_Purge Erases any buffered input or output data ==================== */ -void FS_Purge( file_t *file ) +static void FS_Purge( file_t *file ) { file->buff_len = 0; file->buff_ind = 0; @@ -3193,10 +2347,10 @@ byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamediro } else { - buf = W_LoadFile( path, &filesize, gamedironly ); + buf = FS_LoadWADFile( path, &filesize, gamedironly ); if( !buf ) - buf = Zip_LoadFile( path, &filesize, gamedironly ); + buf = FS_LoadZIPFile( path, &filesize, gamedironly ); } @@ -3215,7 +2369,6 @@ qboolean CRC32_File( dword *crcvalue, const char *filename ) f = FS_Open( filename, "rb", false ); if( !f ) return false; - Assert( crcvalue != NULL ); CRC32_Init( crcvalue ); while( 1 ) @@ -3374,122 +2527,6 @@ const char *FS_GetDiskPath( const char *name, qboolean gamedironly ) return NULL; } -/* -================== -FS_CheckForCrypt - -return true if library is crypted -================== -*/ -qboolean FS_CheckForCrypt( const char *dllname ) -{ - file_t *f; - int key; - - f = FS_Open( dllname, "rb", false ); - if( !f ) return false; - - FS_Seek( f, 64, SEEK_SET ); // skip first 64 bytes - FS_Read( f, &key, sizeof( key )); - FS_Close( f ); - - return ( key == 0x12345678 ) ? true : false; -} - -/* -================== -FS_FindLibrary - -search for library, assume index is valid -only for internal use -================== -*/ -dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) -{ - string dllpath; - searchpath_t *search; - dll_user_t *hInst; - int i, index; - int start = 0; - int len; - - // check for bad exports - if( !COM_CheckString( dllname )) - return NULL; - - fs_ext_path = directpath; - - // HACKHACK remove absoulte path to valve folder - if( !Q_strnicmp( dllname, "..\\valve\\", 9 ) || !Q_strnicmp( dllname, "../valve/", 9 )) - start += 9; - - // replace all backward slashes - len = Q_strlen( dllname ); - - for( i = 0; i < len; i++ ) - { - if( dllname[i+start] == '\\' ) dllpath[i] = '/'; - else dllpath[i] = Q_tolower( dllname[i+start] ); - } - dllpath[i] = '\0'; - - COM_DefaultExtension( dllpath, "."OS_LIB_EXT ); // apply ext if forget - search = FS_FindFile( dllpath, &index, false ); - - if( !search && !directpath ) - { - fs_ext_path = false; - - // trying check also 'bin' folder for indirect paths - Q_strncpy( dllpath, dllname, sizeof( dllpath )); - search = FS_FindFile( dllpath, &index, false ); - if( !search ) return NULL; // unable to find - } - - // NOTE: for libraries we not fail even if search is NULL - // let the OS find library himself - hInst = Mem_Calloc( host.mempool, sizeof( dll_user_t )); - - // save dllname for debug purposes - Q_strncpy( hInst->dllName, dllname, sizeof( hInst->dllName )); - - // shortPath is used for LibraryLoadSymbols only - Q_strncpy( hInst->shortPath, dllpath, sizeof( hInst->shortPath )); - - hInst->encrypted = FS_CheckForCrypt( dllpath ); - - if( index < 0 && !hInst->encrypted && search ) - { - Q_snprintf( hInst->fullPath, sizeof( hInst->fullPath ), "%s%s", search->filename, dllpath ); - hInst->custom_loader = false; // we can loading from disk and use normal debugging - } - else - { - // NOTE: if search is NULL let the OS found library himself - Q_strncpy( hInst->fullPath, dllpath, sizeof( hInst->fullPath )); - - if( search && ( search->wad || search->pack || search->zip ) ) - { -#if XASH_WIN32 && XASH_X86 // a1ba: custom loader is non-portable (I just don't want to touch it) - Con_Printf( S_WARN "%s: loading libraries from packs is deprecated " - "and will be removed in the future\n", __FUNCTION__ ); - hInst->custom_loader = true; -#else - Con_Printf( S_WARN "%s: loading libraries from packs is unsupported on " - "this platform\n", __FUNCTION__ ); - hInst->custom_loader = false; -#endif - } - else - { - hInst->custom_loader = false; - } - } - fs_ext_path = false; // always reset direct paths - - return hInst; -} - /* ================== FS_FileSize @@ -3543,19 +2580,23 @@ int FS_FileTime( const char *filename, qboolean gamedironly ) search = FS_FindFile( filename, &pack_ind, gamedironly ); if( !search ) return -1; // doesn't exist - if( search->pack ) // grab pack filetime - return search->pack->filetime; - else if( search->wad ) // grab wad filetime - return search->wad->filetime; - else if( search->zip ) - return search->zip->filetime; - else if( pack_ind < 0 ) + switch( search->type ) { - // found in the filesystem? - char path [MAX_SYSPATH]; + case SEARCHPATH_PAK: + return FS_FileTimePAK( search->pack ); + case SEARCHPATH_WAD: + return FS_FileTimeWAD( search->wad ); + case SEARCHPATH_ZIP: + return FS_FileTimeZIP( search->zip ); + default: + if( pack_ind < 0 ) + { + char path [MAX_SYSPATH]; - Q_sprintf( path, "%s%s", search->filename, filename ); - return FS_SysFileTime( path ); + // found in the filesystem? + Q_sprintf( path, "%s%s", search->filename, filename ); + return FS_SysFileTime( path ); + } } return -1; // doesn't exist @@ -3687,154 +2728,18 @@ search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) continue; // is the element a pak file? - if( searchpath->pack ) + if( searchpath->type == SEARCHPATH_PAK ) { // look through all the pak file elements - pak = searchpath->pack; - for( i = 0; i < pak->numfiles; i++ ) - { - Q_strncpy( temp, pak->files[i].name, sizeof( temp )); - while( temp[0] ) - { - if( matchpattern( temp, (char *)pattern, true )) - { - for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) - { - if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) - break; - } - - if( resultlistindex == resultlist.numstrings ) - stringlistappend( &resultlist, temp ); - } - - // strip off one path element at a time until empty - // this way directories are added to the listing if they match the pattern - slash = Q_strrchr( temp, '/' ); - backslash = Q_strrchr( temp, '\\' ); - colon = Q_strrchr( temp, ':' ); - separator = temp; - if( separator < slash ) - separator = slash; - if( separator < backslash ) - separator = backslash; - if( separator < colon ) - separator = colon; - *((char *)separator) = 0; - } - } + FS_SearchPAK( &resultlist, searchpath->pack, pattern ); } - else if( searchpath->zip ) + else if( searchpath->type == SEARCHPATH_ZIP ) { - zip = searchpath->zip; - for( i = 0; i < zip->numfiles; i++ ) - { - Q_strncpy( temp, zip->files[i].name, sizeof(temp) ); - while( temp[0] ) - { - if( matchpattern( temp, (char *)pattern, true )) - { - for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) - { - if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) - break; - } - - if( resultlistindex == resultlist.numstrings ) - stringlistappend( &resultlist, temp ); - } - - // strip off one path element at a time until empty - // this way directories are added to the listing if they match the pattern - slash = Q_strrchr( temp, '/' ); - backslash = Q_strrchr( temp, '\\' ); - colon = Q_strrchr( temp, ':' ); - separator = temp; - if( separator < slash ) - separator = slash; - if( separator < backslash ) - separator = backslash; - if( separator < colon ) - separator = colon; - *((char *)separator) = 0; - } - } + FS_SearchZIP( &resultlist, searchpath->zip, pattern ); } - else if( searchpath->wad ) + else if( searchpath->type == SEARCHPATH_WAD ) { - string wadpattern, wadname, temp2; - signed char type = W_TypeFromExt( pattern ); - qboolean anywadname = true; - string wadfolder; - - // quick reject by filetype - if( type == TYP_NONE ) continue; - COM_ExtractFilePath( pattern, wadname ); - COM_FileBase( pattern, wadpattern ); - wadfolder[0] = '\0'; - - if( COM_CheckStringEmpty( wadname )) - { - COM_FileBase( wadname, wadname ); - Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); - COM_DefaultExtension( wadname, ".wad" ); - anywadname = false; - } - - // make wadname from wad fullpath - COM_FileBase( searchpath->wad->filename, temp2 ); - COM_DefaultExtension( temp2, ".wad" ); - - // quick reject by wadname - if( !anywadname && Q_stricmp( wadname, temp2 )) - continue; - - // look through all the wad file elements - wad = searchpath->wad; - - for( i = 0; i < wad->numlumps; i++ ) - { - // if type not matching, we already have no chance ... - if( type != TYP_ANY && wad->lumps[i].type != type ) - continue; - - // build the lumpname with image suffix (if present) - Q_strncpy( temp, wad->lumps[i].name, sizeof( temp )); - - while( temp[0] ) - { - if( matchpattern( temp, wadpattern, true )) - { - for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) - { - if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) - break; - } - - if( resultlistindex == resultlist.numstrings ) - { - // build path: wadname/lumpname.ext - Q_snprintf( temp2, sizeof(temp2), "%s/%s", wadfolder, temp ); - COM_DefaultExtension( temp2, va(".%s", W_ExtFromType( wad->lumps[i].type ))); - stringlistappend( &resultlist, temp2 ); - } - } - - // strip off one path element at a time until empty - // this way directories are added to the listing if they match the pattern - slash = Q_strrchr( temp, '/' ); - backslash = Q_strrchr( temp, '\\' ); - colon = Q_strrchr( temp, ':' ); - separator = temp; - if( separator < slash ) - separator = slash; - if( separator < backslash ) - separator = backslash; - if( separator < colon ) - separator = colon; - *((char *)separator) = 0; - } - } + FS_SearchWAD( &resultlist, searchpath->wad, pattern ); } else { @@ -3903,376 +2808,124 @@ void FS_InitMemory( void ) fs_searchpaths = NULL; } -/* -============================================================================= - -WADSYSTEM PRIVATE ROUTINES - -============================================================================= -*/ -// associate extension with wad type -static const wadtype_t wad_types[7] = +fs_interface_t g_engfuncs = { -{ "pal", TYP_PALETTE }, // palette -{ "dds", TYP_DDSTEX }, // DDS image -{ "lmp", TYP_GFXPIC }, // quake1, hl pic -{ "fnt", TYP_QFONT }, // hl qfonts -{ "mip", TYP_MIPTEX }, // hl/q1 mip -{ "txt", TYP_SCRIPT }, // scripts -{ NULL, TYP_NONE } + _Con_Printf, + _Con_Printf, + _Con_Printf, + _Sys_Error, + _Mem_AllocPool, + _Mem_FreePool, + _Mem_Alloc, + _Mem_Realloc, + _Mem_Free }; -/* -=========== -W_TypeFromExt - -Extracts file type from extension -=========== -*/ -static signed char W_TypeFromExt( const char *lumpname ) +static qboolean FS_InitInterface( int version, fs_interface_t *engfuncs ) { - const char *ext = COM_FileExtension( lumpname ); - const wadtype_t *type; - - // we not known about filetype, so match only by filename - if( !Q_strcmp( ext, "*" ) || !Q_strcmp( ext, "" )) - return TYP_ANY; - - for( type = wad_types; type->ext; type++ ) + // to be extended in future interface revisions + if( version != FS_API_VERSION ) { - if( !Q_stricmp( ext, type->ext )) - return type->type; + Con_Printf( S_ERROR "filesystem optional interface version mismatch: expected %d, got %d\n", + FS_API_VERSION, version ); + return false; } - return TYP_NONE; + + if( engfuncs->_Con_Printf ) + g_engfuncs._Con_Printf = engfuncs->_Con_Printf; + + if( engfuncs->_Con_DPrintf ) + g_engfuncs._Con_DPrintf = engfuncs->_Con_DPrintf; + + if( engfuncs->_Con_Reportf ) + g_engfuncs._Con_Reportf = engfuncs->_Con_Reportf; + + if( engfuncs->_Sys_Error ) + g_engfuncs._Sys_Error = engfuncs->_Sys_Error; + + if( engfuncs->_Mem_AllocPool && engfuncs->_Mem_FreePool ) + { + g_engfuncs._Mem_AllocPool = engfuncs->_Mem_AllocPool; + g_engfuncs._Mem_FreePool = engfuncs->_Mem_FreePool; + + Con_Reportf( "filesystem_stdio: custom pool allocation functions found\n" ); + } + + if( engfuncs->_Mem_Alloc && engfuncs->_Mem_Realloc && engfuncs->_Mem_Free ) + { + g_engfuncs._Mem_Alloc = engfuncs->_Mem_Alloc; + g_engfuncs._Mem_Realloc = engfuncs->_Mem_Realloc; + g_engfuncs._Mem_Free = engfuncs->_Mem_Free; + + Con_Reportf( "filesystem_stdio: custom memory allocation functions found\n" ); + } + + return true; } -/* -=========== -W_ExtFromType - -Convert type to extension -=========== -*/ -static const char *W_ExtFromType( signed char lumptype ) +static fs_api_t g_api = { - const wadtype_t *type; + FS_InitStdio, + FS_ShutdownStdio, - // we not known aboyt filetype, so match only by filename - if( lumptype == TYP_NONE || lumptype == TYP_ANY ) - return ""; + // search path utils + FS_Rescan, + FS_ClearSearchPath, + FS_AllowDirectPaths, + FS_AddGameDirectory, + FS_AddGameHierarchy, + FS_Search, + FS_SetCurrentDirectory, + FS_FindLibrary, + FS_Path_f, - for( type = wad_types; type->ext; type++ ) - { - if( lumptype == type->type ) - return type->ext; - } - return ""; -} + // gameinfo utils + FS_LoadGameInfo, -/* -=========== -W_FindLump + // file ops + FS_Open, + FS_Write, + FS_Read, + FS_Seek, + FS_Tell, + FS_Eof, + FS_Flush, + FS_Close, + FS_Gets, + FS_UnGetc, + FS_Getc, + FS_VPrintf, + FS_Printf, + FS_Print, + FS_FileLength, + FS_FileCopy, -Serach for already existed lump -=========== -*/ -static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed char matchtype ) + // file buffer ops + FS_LoadFile, + FS_LoadDirectFile, + FS_WriteFile, + + // file hashing + CRC32_File, + MD5_HashFile, + + // filesystem ops + FS_FileExists, + FS_FileTime, + FS_FileSize, + FS_Rename, + FS_Delete, + FS_SysFileExists, + FS_GetDiskPath, +}; + +int EXPORT GetFSAPI( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *engfuncs ) { - int left, right; + if( !FS_InitInterface( version, engfuncs )) + return 0; - if( !wad || !wad->lumps || matchtype == TYP_NONE ) - return NULL; + memcpy( api, &g_api, sizeof( *api )); + *globals = &FI; - // look for the file (binary search) - left = 0; - right = wad->numlumps - 1; - - while( left <= right ) - { - int middle = (left + right) / 2; - int diff = Q_stricmp( wad->lumps[middle].name, name ); - - if( !diff ) - { - if(( matchtype == TYP_ANY ) || ( matchtype == wad->lumps[middle].type )) - return &wad->lumps[middle]; // found - else if( wad->lumps[middle].type < matchtype ) - diff = 1; - else if( wad->lumps[middle].type > matchtype ) - diff = -1; - else break; // not found - } - - // if we're too far in the list - if( diff > 0 ) right = middle - 1; - else left = middle + 1; - } - - return NULL; -} - -/* -==================== -W_AddFileToWad - -Add a file to the list of files contained into a package -and sort LAT in alpha-bethical order -==================== -*/ -static dlumpinfo_t *W_AddFileToWad( const char *name, wfile_t *wad, dlumpinfo_t *newlump ) -{ - int left, right; - dlumpinfo_t *plump; - - // look for the slot we should put that file into (binary search) - left = 0; - right = wad->numlumps - 1; - - while( left <= right ) - { - int middle = ( left + right ) / 2; - int diff = Q_stricmp( wad->lumps[middle].name, name ); - - if( !diff ) - { - if( wad->lumps[middle].type < newlump->type ) - diff = 1; - else if( wad->lumps[middle].type > newlump->type ) - diff = -1; - else Con_Reportf( S_WARN "Wad %s contains the file %s several times\n", wad->filename, name ); - } - - // If we're too far in the list - if( diff > 0 ) right = middle - 1; - else left = middle + 1; - } - - // we have to move the right of the list by one slot to free the one we need - plump = &wad->lumps[left]; - memmove( plump + 1, plump, ( wad->numlumps - left ) * sizeof( *plump )); - wad->numlumps++; - - *plump = *newlump; - memcpy( plump->name, name, sizeof( plump->name )); - - return plump; -} - -/* -=========== -W_ReadLump - -reading lump into temp buffer -=========== -*/ -byte *W_ReadLump( wfile_t *wad, dlumpinfo_t *lump, fs_offset_t *lumpsizeptr ) -{ - size_t oldpos, size = 0; - byte *buf; - - // assume error - if( lumpsizeptr ) *lumpsizeptr = 0; - - // no wads loaded - if( !wad || !lump ) return NULL; - - oldpos = FS_Tell( wad->handle ); // don't forget restore original position - - if( FS_Seek( wad->handle, lump->filepos, SEEK_SET ) == -1 ) - { - Con_Reportf( S_ERROR "W_ReadLump: %s is corrupted\n", lump->name ); - FS_Seek( wad->handle, oldpos, SEEK_SET ); - return NULL; - } - - buf = (byte *)Mem_Malloc( wad->mempool, lump->disksize ); - size = FS_Read( wad->handle, buf, lump->disksize ); - - if( size < lump->disksize ) - { - Con_Reportf( S_WARN "W_ReadLump: %s is probably corrupted\n", lump->name ); - FS_Seek( wad->handle, oldpos, SEEK_SET ); - Mem_Free( buf ); - return NULL; - } - - if( lumpsizeptr ) *lumpsizeptr = lump->disksize; - FS_Seek( wad->handle, oldpos, SEEK_SET ); - - return buf; -} - -/* -============================================================================= - -WADSYSTEM PUBLIC BASE FUNCTIONS - -============================================================================= -*/ -/* -=========== -W_Open - -open the wad for reading & writing -=========== -*/ -wfile_t *W_Open( const char *filename, int *error ) -{ - wfile_t *wad = (wfile_t *)Mem_Calloc( fs_mempool, sizeof( wfile_t )); - const char *basename; - int i, lumpcount; - dlumpinfo_t *srclumps; - size_t lat_size; - dwadinfo_t header; - - // NOTE: FS_Open is load wad file from the first pak in the list (while fs_ext_path is false) - if( fs_ext_path ) basename = filename; - else basename = COM_FileWithoutPath( filename ); - - wad->handle = FS_Open( basename, "rb", false ); - - // HACKHACK: try to open WAD by full path for RoDir, when searchpaths are not ready - if( COM_CheckStringEmpty( host.rodir ) && fs_ext_path && wad->handle == NULL ) - wad->handle = FS_SysOpen( filename, "rb" ); - - if( wad->handle == NULL ) - { - Con_Reportf( S_ERROR "W_Open: couldn't open %s\n", filename ); - if( error ) *error = WAD_LOAD_COULDNT_OPEN; - W_Close( wad ); - return NULL; - } - - // copy wad name - Q_strncpy( wad->filename, filename, sizeof( wad->filename )); - wad->filetime = FS_SysFileTime( filename ); - wad->mempool = Mem_AllocPool( filename ); - - if( FS_Read( wad->handle, &header, sizeof( dwadinfo_t )) != sizeof( dwadinfo_t )) - { - Con_Reportf( S_ERROR "W_Open: %s can't read header\n", filename ); - if( error ) *error = WAD_LOAD_BAD_HEADER; - W_Close( wad ); - return NULL; - } - - if( header.ident != IDWAD2HEADER && header.ident != IDWAD3HEADER ) - { - Con_Reportf( S_ERROR "W_Open: %s is not a WAD2 or WAD3 file\n", filename ); - if( error ) *error = WAD_LOAD_BAD_HEADER; - W_Close( wad ); - return NULL; - } - - lumpcount = header.numlumps; - - if( lumpcount >= MAX_FILES_IN_WAD ) - { - Con_Reportf( S_WARN "W_Open: %s is full (%i lumps)\n", filename, lumpcount ); - if( error ) *error = WAD_LOAD_TOO_MANY_FILES; - } - else if( lumpcount <= 0 ) - { - Con_Reportf( S_ERROR "W_Open: %s has no lumps\n", filename ); - if( error ) *error = WAD_LOAD_NO_FILES; - W_Close( wad ); - return NULL; - } - else if( error ) *error = WAD_LOAD_OK; - - wad->infotableofs = header.infotableofs; // save infotableofs position - - if( FS_Seek( wad->handle, wad->infotableofs, SEEK_SET ) == -1 ) - { - Con_Reportf( S_ERROR "W_Open: %s can't find lump allocation table\n", filename ); - if( error ) *error = WAD_LOAD_BAD_FOLDERS; - W_Close( wad ); - return NULL; - } - - lat_size = lumpcount * sizeof( dlumpinfo_t ); - - // NOTE: lumps table can be reallocated for O_APPEND mode - srclumps = (dlumpinfo_t *)Mem_Malloc( wad->mempool, lat_size ); - - if( FS_Read( wad->handle, srclumps, lat_size ) != lat_size ) - { - Con_Reportf( S_ERROR "W_ReadLumpTable: %s has corrupted lump allocation table\n", wad->filename ); - if( error ) *error = WAD_LOAD_CORRUPTED; - Mem_Free( srclumps ); - W_Close( wad ); - return NULL; - } - - // starting to add lumps - wad->lumps = (dlumpinfo_t *)Mem_Calloc( wad->mempool, lat_size ); - wad->numlumps = 0; - - // sort lumps for binary search - for( i = 0; i < lumpcount; i++ ) - { - char name[16]; - int k; - - // cleanup lumpname - Q_strnlwr( srclumps[i].name, name, sizeof( srclumps[i].name )); - - // check for '*' symbol issues (quake1) - k = Q_strlen( Q_strrchr( name, '*' )); - if( k ) name[Q_strlen( name ) - k] = '!'; - - // check for Quake 'conchars' issues (only lmp loader really allows to read this lame pic) - if( srclumps[i].type == 68 && !Q_stricmp( srclumps[i].name, "conchars" )) - srclumps[i].type = TYP_GFXPIC; - - W_AddFileToWad( name, wad, &srclumps[i] ); - } - - // release source lumps - Mem_Free( srclumps ); - - // and leave the file open - return wad; -} - -/* -=========== -W_Close - -finalize wad or just close -=========== -*/ -void W_Close( wfile_t *wad ) -{ - if( !wad ) return; - - Mem_FreePool( &wad->mempool ); - if( wad->handle != NULL ) - FS_Close( wad->handle ); - Mem_Free( wad ); // free himself -} - -/* -============================================================================= - -FILESYSTEM IMPLEMENTATION - -============================================================================= -*/ -/* -=========== -W_LoadFile - -loading lump into the tmp buffer -=========== -*/ -static byte *W_LoadFile( const char *path, fs_offset_t *lumpsizeptr, qboolean gamedironly ) -{ - searchpath_t *search; - int index; - - search = FS_FindFile( path, &index, gamedironly ); - if( search && search->wad ) - return W_ReadLump( search->wad, &search->wad->lumps[index], lumpsizeptr ); - return NULL; + return FS_API_VERSION; } diff --git a/filesystem/filesystem.h b/filesystem/filesystem.h new file mode 100644 index 00000000..6a7a1f4c --- /dev/null +++ b/filesystem/filesystem.h @@ -0,0 +1,202 @@ +/* +filesystem.h - engine FS +Copyright (C) 2007 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +#include +#include +#include +#include "xash3d_types.h" +#include "const.h" +#include "com_model.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +#define FS_API_VERSION 1 // not stable yet! + +// search path flags +enum +{ + FS_STATIC_PATH = BIT( 0 ), // FS_ClearSearchPath will be ignore this path + FS_NOWRITE_PATH = BIT( 1 ), // default behavior - last added gamedir set as writedir. This flag disables it + FS_GAMEDIR_PATH = BIT( 2 ), // just a marker for gamedir path + FS_CUSTOM_PATH = BIT( 3 ), // gamedir but with custom/mod data + FS_GAMERODIR_PATH = BIT( 4 ), // gamedir but read-only + + FS_GAMEDIRONLY_SEARCH_FLAGS = FS_GAMEDIR_PATH | FS_CUSTOM_PATH | FS_GAMERODIR_PATH +}; + +typedef struct +{ + int numfilenames; + char **filenames; + char *filenamesbuffer; +} search_t; + +typedef struct gameinfo_s +{ + // filesystem info + char gamefolder[MAX_QPATH]; // used for change game '-game x' + char basedir[MAX_QPATH]; // base game directory (like 'id1' for Quake or 'valve' for Half-Life) + char falldir[MAX_QPATH]; // used as second basedir + char startmap[MAX_QPATH];// map to start singleplayer game + char trainmap[MAX_QPATH];// map to start hazard course (if specified) + char title[64]; // Game Main Title + float version; // game version (optional) + + // .dll pathes + char dll_path[MAX_QPATH]; // e.g. "bin" or "cl_dlls" + char game_dll[MAX_QPATH]; // custom path for game.dll + + // .ico path + char iconpath[MAX_QPATH]; // "game.ico" by default + + // about mod info + string game_url; // link to a developer's site + string update_url; // link to updates page + char type[MAX_QPATH]; // single, toolkit, multiplayer etc + char date[MAX_QPATH]; + size_t size; + + int gamemode; + qboolean secure; // prevent to console acess + qboolean nomodels; // don't let player to choose model (use player.mdl always) + qboolean noskills; // disable skill menu selection + qboolean render_picbutton_text; // use font renderer to render WON buttons + + char sp_entity[32]; // e.g. info_player_start + char mp_entity[32]; // e.g. info_player_deathmatch + char mp_filter[32]; // filtering multiplayer-maps + + char ambientsound[NUM_AMBIENTS][MAX_QPATH]; // quake ambient sounds + + int max_edicts; // min edicts is 600, max edicts is 8196 + int max_tents; // min temp ents is 300, max is 2048 + int max_beams; // min beams is 64, max beams is 512 + int max_particles; // min particles is 4096, max particles is 32768 + + char game_dll_linux[64]; // custom path for game.dll + char game_dll_osx[64]; // custom path for game.dll + + qboolean added; +} gameinfo_t; + +typedef enum +{ + GAME_NORMAL, + GAME_SINGLEPLAYER_ONLY, + GAME_MULTIPLAYER_ONLY +} gametype_t; + +typedef struct fs_dllinfo_t +{ + string fullPath; + string shortPath; + qboolean encrypted; + qboolean custom_loader; +} fs_dllinfo_t; + +typedef struct fs_globals_t +{ + gameinfo_t *GameInfo; // current GameInfo + gameinfo_t *games[MAX_MODS]; // environment games (founded at each engine start) + int numgames; +} fs_globals_t; + +typedef struct fs_api_t +{ + qboolean (*InitStdio)( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ); + void (*ShutdownStdio)( void ); + + // search path utils + void (*Rescan)( void ); + void (*ClearSearchPath)( void ); + void (*AllowDirectPaths)( qboolean enable ); + void (*AddGameDirectory)( const char *dir, uint flags ); + void (*AddGameHierarchy)( const char *dir, uint flags ); + search_t *(*Search)( const char *pattern, int caseinsensitive, int gamedironly ); + int (*SetCurrentDirectory)( const char *path ); + qboolean (*FindLibrary)( const char *dllname, qboolean directpath, fs_dllinfo_t *dllinfo ); + void (*Path_f)( void ); + + // gameinfo utils + void (*LoadGameInfo)( const char *rootfolder ); + + // file ops + file_t *(*Open)( const char *filepath, const char *mode, qboolean gamedironly ); + fs_offset_t (*Write)( file_t *file, const void *data, size_t datasize ); + fs_offset_t (*Read)( file_t *file, void *buffer, size_t buffersize ); + int (*Seek)( file_t *file, fs_offset_t offset, int whence ); + fs_offset_t (*Tell)( file_t *file ); + qboolean (*Eof)( file_t *file ); + int (*Flush)( file_t *file ); + int (*Close)( file_t *file ); + int (*Gets)( file_t *file, byte *string, size_t bufsize ); + int (*UnGetc)( file_t *file, byte c ); + int (*Getc)( file_t *file ); + int (*VPrintf)( file_t *file, const char *format, va_list ap ); + int (*Printf)( file_t *file, const char *format, ... ) _format( 2 ); + int (*Print)( file_t *file, const char *msg ); + fs_offset_t (*FileLength)( file_t *f ); + qboolean (*FileCopy)( file_t *pOutput, file_t *pInput, int fileSize ); + + // file buffer ops + byte *(*LoadFile)( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); + byte *(*LoadDirectFile)( const char *path, fs_offset_t *filesizeptr ); + qboolean (*WriteFile)( const char *filename, const void *data, fs_offset_t len ); + + // file hashing + qboolean (*CRC32_File)( dword *crcvalue, const char *filename ); + qboolean (*MD5_HashFile)( byte digest[16], const char *pszFileName, uint seed[4] ); + + // filesystem ops + int (*FileExists)( const char *filename, int gamedironly ); + int (*FileTime)( const char *filename, qboolean gamedironly ); + fs_offset_t (*FileSize)( const char *filename, qboolean gamedironly ); + qboolean (*Rename)( const char *oldname, const char *newname ); + qboolean (*Delete)( const char *path ); + qboolean (*SysFileExists)( const char *path, qboolean casesensitive ); + const char *(*GetDiskPath)( const char *name, qboolean gamedironly ); +} fs_api_t; + +typedef struct fs_interface_t +{ + // logging + void (*_Con_Printf)( const char *fmt, ... ) _format( 1 ); // typical console allowed messages + void (*_Con_DPrintf)( const char *fmt, ... ) _format( 1 ); // -dev 1 + void (*_Con_Reportf)( const char *fmt, ... ) _format( 1 ); // -dev 2 + + void (*_Sys_Error)( const char *fmt, ... ) _format( 1 ); + + // memory + poolhandle_t (*_Mem_AllocPool)( const char *name, const char *filename, int fileline ); + void (*_Mem_FreePool)( poolhandle_t *poolptr, const char *filename, int fileline ); + void *(*_Mem_Alloc)( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ); + void *(*_Mem_Realloc)( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ); + void (*_Mem_Free)( void *data, const char *filename, int fileline ); +} fs_interface_t; + +typedef int (*FSAPI)( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *interface ); +#define GET_FS_API "GetFSAPI" + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif//FILESYSTEM_H diff --git a/filesystem/filesystem_internal.h b/filesystem/filesystem_internal.h new file mode 100644 index 00000000..c466b1db --- /dev/null +++ b/filesystem/filesystem_internal.h @@ -0,0 +1,204 @@ +/* +filesystem.h - engine FS +Copyright (C) 2007 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef FILESYSTEM_INTERNAL_H +#define FILESYSTEM_INTERNAL_H + +#include "xash3d_types.h" +#include "filesystem.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct zip_s zip_t; +typedef struct pack_s pack_t; +typedef struct wfile_s wfile_t; + + +#define FILE_BUFF_SIZE (2048) + +struct file_s +{ + int handle; // file descriptor + int ungetc; // single stored character from ungetc, cleared to EOF when read + fs_offset_t real_length; // uncompressed file size (for files opened in "read" mode) + fs_offset_t position; // current position in the file + fs_offset_t offset; // offset into the package (0 if external file) + time_t filetime; // pak, wad or real filetime + // contents buffer + fs_offset_t buff_ind, buff_len; // buffer current index and length + byte buff[FILE_BUFF_SIZE]; // intermediate buffer +#ifdef XASH_REDUCE_FD + const char *backup_path; + fs_offset_t backup_position; + uint backup_options; +#endif +}; + +enum +{ + SEARCHPATH_PLAIN = 0, + SEARCHPATH_PAK, + SEARCHPATH_WAD, + SEARCHPATH_ZIP +}; + +typedef struct stringlist_s +{ + // maxstrings changes as needed, causing reallocation of strings[] array + int maxstrings; + int numstrings; + char **strings; +} stringlist_t; + +typedef struct searchpath_s +{ + string filename; + int type; + int flags; + union + { + pack_t *pack; + wfile_t *wad; + zip_t *zip; + }; + struct searchpath_s *next; +} searchpath_t; + +extern searchpath_t *fs_searchpaths; +extern poolhandle_t fs_mempool; +extern fs_interface_t g_engfuncs; +extern qboolean fs_ext_path; +extern char fs_rodir[MAX_SYSPATH]; +extern char fs_rootdir[MAX_SYSPATH]; + +#define Mem_Malloc( pool, size ) g_engfuncs._Mem_Alloc( pool, size, false, __FILE__, __LINE__ ) +#define Mem_Calloc( pool, size ) g_engfuncs._Mem_Alloc( pool, size, true, __FILE__, __LINE__ ) +#define Mem_Realloc( pool, ptr, size ) g_engfuncs._Mem_Realloc( pool, ptr, size, true, __FILE__, __LINE__ ) +#define Mem_Free( mem ) g_engfuncs._Mem_Free( mem, __FILE__, __LINE__ ) +#define Mem_AllocPool( name ) g_engfuncs._Mem_AllocPool( name, __FILE__, __LINE__ ) +#define Mem_FreePool( pool ) g_engfuncs._Mem_FreePool( pool, __FILE__, __LINE__ ) + +#define Con_Printf (*g_engfuncs._Con_Printf) +#define Con_DPrintf (*g_engfuncs._Con_DPrintf) +#define Con_Reportf (*g_engfuncs._Con_Reportf) +#define Sys_Error (*g_engfuncs._Sys_Error) + +// +// filesystem.c +// +qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ); +void FS_ShutdownStdio( void ); + +// search path utils +void FS_Rescan( void ); +void FS_ClearSearchPath( void ); +void FS_AllowDirectPaths( qboolean enable ); +void FS_AddGameDirectory( const char *dir, uint flags ); +void FS_AddGameHierarchy( const char *dir, uint flags ); +search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ); +int FS_SetCurrentDirectory( const char *path ); +void FS_Path_f( void ); + +// gameinfo utils +void FS_LoadGameInfo( const char *rootfolder ); + +// file ops +file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ); +fs_offset_t FS_Write( file_t *file, const void *data, size_t datasize ); +fs_offset_t FS_Read( file_t *file, void *buffer, size_t buffersize ); +int FS_Seek( file_t *file, fs_offset_t offset, int whence ); +fs_offset_t FS_Tell( file_t *file ); +qboolean FS_Eof( file_t *file ); +int FS_Flush( file_t *file ); +int FS_Close( file_t *file ); +int FS_Gets( file_t *file, byte *string, size_t bufsize ); +int FS_UnGetc( file_t *file, byte c ); +int FS_Getc( file_t *file ); +int FS_VPrintf( file_t *file, const char *format, va_list ap ); +int FS_Printf( file_t *file, const char *format, ... ) _format( 2 ); +int FS_Print( file_t *file, const char *msg ); +fs_offset_t FS_FileLength( file_t *f ); +qboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize ); + +// file buffer ops +byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); +byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr ); +qboolean FS_WriteFile( const char *filename, const void *data, fs_offset_t len ); + +// file hashing +qboolean CRC32_File( dword *crcvalue, const char *filename ); +qboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] ); + +// filesystem ops +int FS_FileExists( const char *filename, int gamedironly ); +int FS_FileTime( const char *filename, qboolean gamedironly ); +fs_offset_t FS_FileSize( const char *filename, qboolean gamedironly ); +qboolean FS_Rename( const char *oldname, const char *newname ); +qboolean FS_Delete( const char *path ); +qboolean FS_SysFileExists( const char *path, qboolean casesensitive ); +const char *FS_GetDiskPath( const char *name, qboolean gamedironly ); +void stringlistappend( stringlist_t *list, char *text ); +void FS_CreatePath( char *path ); +qboolean FS_SysFolderExists( const char *path ); +file_t *FS_OpenReadFile( const char *filename, const char *mode, qboolean gamedironly ); + +int FS_SysFileTime( const char *filename ); +file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_offset_t len ); +file_t *FS_SysOpen( const char *filepath, const char *mode ); +const char *FS_FixFileCase( const char *path ); +searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ); + +// +// pak.c +// +int FS_FileTimePAK( pack_t *pack ); +int FS_FindFilePAK( pack_t *pack, const char *name ); +void FS_PrintPAKInfo( char *dst, size_t size, pack_t *pack ); +void FS_ClosePAK( pack_t *pack ); +void FS_SearchPAK( stringlist_t *list, pack_t *pack, const char *pattern ); +file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ); +qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ); + +// +// wad.c +// +int FS_FileTimeWAD( wfile_t *wad ); +int FS_FindFileWAD( wfile_t *wad, const char *name ); +void FS_PrintWADInfo( char *dst, size_t size, wfile_t *wad ); +void FS_CloseWAD( wfile_t *wad ); +void FS_SearchWAD( stringlist_t *list, wfile_t *wad, const char *pattern ); +byte *FS_LoadWADFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ); +qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ); + +// +// zip.c +// +int FS_FileTimeZIP( zip_t *zip ); +int FS_FindFileZIP( zip_t *zip, const char *name ); +void FS_PrintZIPInfo( char *dst, size_t size, zip_t *zip ); +void FS_CloseZIP( zip_t *zip ); +void FS_SearchZIP( stringlist_t *list, zip_t *zip, const char *pattern ); +byte *FS_LoadZIPFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ); +file_t *FS_OpenZipFile( zip_t *zip, int pack_ind ); +qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ); + +#ifdef __cplusplus +} +#endif + +#endif // FILESYSTEM_INTERNAL_H diff --git a/filesystem/fscallback.h b/filesystem/fscallback.h new file mode 100644 index 00000000..bebfdd29 --- /dev/null +++ b/filesystem/fscallback.h @@ -0,0 +1,80 @@ +/* +fscallback.h - common filesystem callbacks +Copyright (C) 2022 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#ifndef FSCALLBACK_H +#define FSCALLBACK_H + +#include "filesystem.h" + +extern fs_api_t g_fsapi; +extern fs_globals_t *FI; + +#define GI FI->GameInfo +#define FS_Gamedir() GI->gamefolder +#define FS_Title() GI->title + +#define FS_InitStdio (*g_fsapi.InitStdio) +#define FS_ShutdownStdio (*g_fsapi.ShutdownStdio) + +// search path utils +#define FS_Rescan (*g_fsapi.Rescan) +#define FS_ClearSearchPath (*g_fsapi.ClearSearchPath) +#define FS_AllowDirectPaths (*g_fsapi.AllowDirectPaths) +#define FS_AddGameDirectory (*g_fsapi.AddGameDirectory) +#define FS_AddGameHierarchy (*g_fsapi.AddGameHierarchy) +#define FS_Search (*g_fsapi.Search) +#define FS_SetCurrentDirectory (*g_fsapi.SetCurrentDirectory) +#define FS_Path_f (*g_fsapi.Path_f) + +// gameinfo utils +#define FS_LoadGameInfo (*g_fsapi.LoadGameInfo) + +// file ops +#define FS_Open (*g_fsapi.Open) +#define FS_Write (*g_fsapi.Write) +#define FS_Read (*g_fsapi.Read) +#define FS_Seek (*g_fsapi.Seek) +#define FS_Tell (*g_fsapi.Tell) +#define FS_Eof (*g_fsapi.Eof) +#define FS_Flush (*g_fsapi.Flush) +#define FS_Close (*g_fsapi.Close) +#define FS_Gets (*g_fsapi.Gets) +#define FS_UnGetc (*g_fsapi.UnGetc) +#define FS_Getc (*g_fsapi.Getc) +#define FS_VPrintf (*g_fsapi.VPrintf) +#define FS_Printf (*g_fsapi.Printf) +#define FS_Print (*g_fsapi.Print) +#define FS_FileLength (*g_fsapi.FileLength) +#define FS_FileCopy (*g_fsapi.FileCopy) + +// file buffer ops +#define FS_LoadFile (*g_fsapi.LoadFile) +#define FS_LoadDirectFile (*g_fsapi.LoadDirectFile) +#define FS_WriteFile (*g_fsapi.WriteFile) + +// file hashing +#define CRC32_File (*g_fsapi.CRC32_File) +#define MD5_HashFile (*g_fsapi.MD5_HashFile) + +// filesystem ops +#define FS_FileExists (*g_fsapi.FileExists) +#define FS_FileTime (*g_fsapi.FileTime) +#define FS_FileSize (*g_fsapi.FileSize) +#define FS_Rename (*g_fsapi.Rename) +#define FS_Delete (*g_fsapi.Delete) +#define FS_SysFileExists (*g_fsapi.SysFileExists) +#define FS_GetDiskPath (*g_fsapi.GetDiskPath) + + +#endif // FSCALLBACK_H diff --git a/filesystem/pak.c b/filesystem/pak.c new file mode 100644 index 00000000..7dac5b8f --- /dev/null +++ b/filesystem/pak.c @@ -0,0 +1,394 @@ +/* +pak.c - PAK support for filesystem +Copyright (C) 2007 Uncle Mike +Copyright (C) 2022 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include +#include +#include +#include +#include +#include "port.h" +#include "filesystem_internal.h" +#include "crtlib.h" +#include "common/com_strings.h" + +/* +======================================================================== +PAK FILES + +The .pak files are just a linear collapse of a directory tree +======================================================================== +*/ +// header +#define IDPACKV1HEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') // little-endian "PACK" + +#define MAX_FILES_IN_PACK 65536 // pak + +typedef struct +{ + int ident; + int dirofs; + int dirlen; +} dpackheader_t; + +typedef struct +{ + char name[56]; // total 64 bytes + int filepos; + int filelen; +} dpackfile_t; + +// PAK errors +#define PAK_LOAD_OK 0 +#define PAK_LOAD_COULDNT_OPEN 1 +#define PAK_LOAD_BAD_HEADER 2 +#define PAK_LOAD_BAD_FOLDERS 3 +#define PAK_LOAD_TOO_MANY_FILES 4 +#define PAK_LOAD_NO_FILES 5 +#define PAK_LOAD_CORRUPTED 6 + +typedef struct pack_s +{ + string filename; + int handle; + int numfiles; + time_t filetime; // common for all packed files + dpackfile_t *files; +} pack_t; + +/* +==================== +FS_AddFileToPack + +Add a file to the list of files contained into a package +==================== +*/ +static dpackfile_t *FS_AddFileToPack( const char *name, pack_t *pack, fs_offset_t offset, fs_offset_t size ) +{ + int left, right, middle; + dpackfile_t *pfile; + + // look for the slot we should put that file into (binary search) + left = 0; + right = pack->numfiles - 1; + + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( pack->files[middle].name, name ); + + // If we found the file, there's a problem + if( !diff ) Con_Reportf( S_WARN "package %s contains the file %s several times\n", pack->filename, name ); + + // If we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + // We have to move the right of the list by one slot to free the one we need + pfile = &pack->files[left]; + memmove( pfile + 1, pfile, (pack->numfiles - left) * sizeof( *pfile )); + pack->numfiles++; + + Q_strncpy( pfile->name, name, sizeof( pfile->name )); + pfile->filepos = offset; + pfile->filelen = size; + + return pfile; +} + +/* +================= +FS_LoadPackPAK + +Takes an explicit (not game tree related) path to a pak file. + +Loads the header and directory, adding the files at the beginning +of the list so they override previous pack files. +================= +*/ +static pack_t *FS_LoadPackPAK( const char *packfile, int *error ) +{ + dpackheader_t header; + int packhandle; + int i, numpackfiles; + pack_t *pack; + dpackfile_t *info; + fs_size_t c; + + packhandle = open( packfile, O_RDONLY|O_BINARY ); + +#if !XASH_WIN32 + if( packhandle < 0 ) + { + const char *fpackfile = FS_FixFileCase( packfile ); + if( fpackfile != packfile ) + packhandle = open( fpackfile, O_RDONLY|O_BINARY ); + } +#endif + + if( packhandle < 0 ) + { + Con_Reportf( "%s couldn't open: %s\n", packfile, strerror( errno )); + if( error ) *error = PAK_LOAD_COULDNT_OPEN; + return NULL; + } + + c = read( packhandle, (void *)&header, sizeof( header )); + + if( c != sizeof( header ) || header.ident != IDPACKV1HEADER ) + { + Con_Reportf( "%s is not a packfile. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_BAD_HEADER; + close( packhandle ); + return NULL; + } + + if( header.dirlen % sizeof( dpackfile_t )) + { + Con_Reportf( S_ERROR "%s has an invalid directory size. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_BAD_FOLDERS; + close( packhandle ); + return NULL; + } + + numpackfiles = header.dirlen / sizeof( dpackfile_t ); + + if( numpackfiles > MAX_FILES_IN_PACK ) + { + Con_Reportf( S_ERROR "%s has too many files ( %i ). Ignored.\n", packfile, numpackfiles ); + if( error ) *error = PAK_LOAD_TOO_MANY_FILES; + close( packhandle ); + return NULL; + } + + if( numpackfiles <= 0 ) + { + Con_Reportf( "%s has no files. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_NO_FILES; + close( packhandle ); + return NULL; + } + + info = (dpackfile_t *)Mem_Malloc( fs_mempool, sizeof( *info ) * numpackfiles ); + lseek( packhandle, header.dirofs, SEEK_SET ); + + if( header.dirlen != read( packhandle, (void *)info, header.dirlen )) + { + Con_Reportf( "%s is an incomplete PAK, not loading\n", packfile ); + if( error ) *error = PAK_LOAD_CORRUPTED; + close( packhandle ); + Mem_Free( info ); + return NULL; + } + + pack = (pack_t *)Mem_Calloc( fs_mempool, sizeof( pack_t )); + Q_strncpy( pack->filename, packfile, sizeof( pack->filename )); + pack->files = (dpackfile_t *)Mem_Calloc( fs_mempool, numpackfiles * sizeof( dpackfile_t )); + pack->filetime = FS_SysFileTime( packfile ); + pack->handle = packhandle; + pack->numfiles = 0; + + // parse the directory + for( i = 0; i < numpackfiles; i++ ) + FS_AddFileToPack( info[i].name, pack, info[i].filepos, info[i].filelen ); + +#ifdef XASH_REDUCE_FD + // will reopen when needed + close( pack->handle ); + pack->handle = -1; +#endif + + if( error ) *error = PAK_LOAD_OK; + Mem_Free( info ); + + return pack; +} + +/* +=========== +FS_OpenPackedFile + +Open a packed file using its package file descriptor +=========== +*/ +file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ) +{ + dpackfile_t *pfile; + + pfile = &pack->files[pack_ind]; + + return FS_OpenHandle( pack->filename, pack->handle, pfile->filepos, pfile->filelen ); +} + +/* +================ +FS_AddPak_Fullpath + +Adds the given pack to the search path. +The pack type is autodetected by the file extension. + +Returns true if the file was successfully added to the +search path or if it was already included. + +If keep_plain_dirs is set, the pack will be added AFTER the first sequence of +plain directories. +================ +*/ +qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + pack_t *pak = NULL; + const char *ext = COM_FileExtension( pakfile ); + int i, errorcode = PAK_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->type == SEARCHPATH_PAK && !Q_stricmp( search->pack->filename, pakfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) + *already_loaded = false; + + if( !Q_stricmp( ext, "pak" )) + pak = FS_LoadPackPAK( pakfile, &errorcode ); + + if( pak ) + { + string fullpath; + + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); + search->pack = pak; + search->type = SEARCHPATH_PAK; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + Con_Reportf( "Adding pakfile: %s (%i files)\n", pakfile, pak->numfiles ); + + // time to add in search list all the wads that contains in current pakfile (if do) + for( i = 0; i < pak->numfiles; i++ ) + { + if( !Q_stricmp( COM_FileExtension( pak->files[i].name ), "wad" )) + { + Q_snprintf( fullpath, MAX_STRING, "%s/%s", pakfile, pak->files[i].name ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } + } + + return true; + } + else + { + if( errorcode != PAK_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "FS_AddPak_Fullpath: unable to load pak \"%s\"\n", pakfile ); + return false; + } +} + +int FS_FindFilePAK( pack_t *pack, const char *name ) +{ + int left, right, middle; + + // look for the file (binary search) + left = 0; + right = pack->numfiles - 1; + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( pack->files[middle].name, name ); + + // Found it + if( !diff ) + { + return middle; + } + + // if we're too far in the list + if( diff > 0 ) + right = middle - 1; + else left = middle + 1; + } + + return -1; +} + +void FS_SearchPAK( stringlist_t *list, pack_t *pack, const char *pattern ) +{ + string temp; + const char *slash, *backslash, *colon, *separator; + int j, i; + + for( i = 0; i < pack->numfiles; i++ ) + { + Q_strncpy( temp, pack->files[i].name, sizeof( temp )); + while( temp[0] ) + { + if( matchpattern( temp, pattern, true )) + { + for( j = 0; j < list->numstrings; j++ ) + { + if( !Q_strcmp( list->strings[j], temp )) + break; + } + + if( j == list->numstrings ) + stringlistappend( list, temp ); + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } +} + +int FS_FileTimePAK( pack_t *pack ) +{ + return pack->filetime; +} + +void FS_PrintPAKInfo( char *dst, size_t size, pack_t *pack ) +{ + Q_snprintf( dst, size, "%s (%i files)", pack->filename, pack->numfiles ); +} + +void FS_ClosePAK( pack_t *pack ) +{ + if( pack->files ) + Mem_Free( pack->files ); + if( pack->handle >= 0 ) + close( pack->handle ); + Mem_Free( pack ); +} diff --git a/filesystem/wad.c b/filesystem/wad.c new file mode 100644 index 00000000..9cbfa886 --- /dev/null +++ b/filesystem/wad.c @@ -0,0 +1,634 @@ +/* +wad.c - WAD support for filesystem +Copyright (C) 2007 Uncle Mike +Copyright (C) 2022 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include +#include +#include +#include +#include +#include "port.h" +#include "filesystem_internal.h" +#include "crtlib.h" +#include "common/com_strings.h" +#include "wadfile.h" + +/* +======================================================================== +.WAD archive format (WhereAllData - WAD) + +List of compressed files, that can be identify only by TYPE_* + + +header: dwadinfo_t[dwadinfo_t] +file_1: byte[dwadinfo_t[num]->disksize] +file_2: byte[dwadinfo_t[num]->disksize] +file_3: byte[dwadinfo_t[num]->disksize] +... +file_n: byte[dwadinfo_t[num]->disksize] +infotable dlumpinfo_t[dwadinfo_t->numlumps] +======================================================================== +*/ +#define WAD3_NAMELEN 16 +#define HINT_NAMELEN 5 // e.g. _mask, _norm +#define MAX_FILES_IN_WAD 65535 // real limit as above <2Gb size not a lumpcount + +#include "const.h" + +typedef struct +{ + int ident; // should be WAD3 + int numlumps; // num files + int infotableofs; // LUT offset +} dwadinfo_t; + +typedef struct +{ + int filepos; // file offset in WAD + int disksize; // compressed or uncompressed + int size; // uncompressed + signed char type; // TYP_* + signed char attribs; // file attribs + signed char pad0; + signed char pad1; + char name[WAD3_NAMELEN]; // must be null terminated +} dlumpinfo_t; + +typedef struct wfile_s +{ + string filename; + int infotableofs; + int numlumps; + poolhandle_t mempool; // W_ReadLump temp buffers + file_t *handle; + dlumpinfo_t *lumps; + time_t filetime; +} wfile_t; + +// WAD errors +#define WAD_LOAD_OK 0 +#define WAD_LOAD_COULDNT_OPEN 1 +#define WAD_LOAD_BAD_HEADER 2 +#define WAD_LOAD_BAD_FOLDERS 3 +#define WAD_LOAD_TOO_MANY_FILES 4 +#define WAD_LOAD_NO_FILES 5 +#define WAD_LOAD_CORRUPTED 6 + +typedef struct wadtype_s +{ + const char *ext; + signed char type; +} wadtype_t; + +// associate extension with wad type +static const wadtype_t wad_types[7] = +{ +{ "pal", TYP_PALETTE }, // palette +{ "dds", TYP_DDSTEX }, // DDS image +{ "lmp", TYP_GFXPIC }, // quake1, hl pic +{ "fnt", TYP_QFONT }, // hl qfonts +{ "mip", TYP_MIPTEX }, // hl/q1 mip +{ "txt", TYP_SCRIPT }, // scripts +{ NULL, TYP_NONE } +}; + +/* +=========== +W_TypeFromExt + +Extracts file type from extension +=========== +*/ +static signed char W_TypeFromExt( const char *lumpname ) +{ + const char *ext = COM_FileExtension( lumpname ); + const wadtype_t *type; + + // we not known about filetype, so match only by filename + if( !Q_strcmp( ext, "*" ) || !Q_strcmp( ext, "" )) + return TYP_ANY; + + for( type = wad_types; type->ext; type++ ) + { + if( !Q_stricmp( ext, type->ext )) + return type->type; + } + return TYP_NONE; +} + +/* +=========== +W_ExtFromType + +Convert type to extension +=========== +*/ +static const char *W_ExtFromType( signed char lumptype ) +{ + const wadtype_t *type; + + // we not known aboyt filetype, so match only by filename + if( lumptype == TYP_NONE || lumptype == TYP_ANY ) + return ""; + + for( type = wad_types; type->ext; type++ ) + { + if( lumptype == type->type ) + return type->ext; + } + return ""; +} + +/* +==================== +W_AddFileToWad + +Add a file to the list of files contained into a package +and sort LAT in alpha-bethical order +==================== +*/ +static dlumpinfo_t *W_AddFileToWad( const char *name, wfile_t *wad, dlumpinfo_t *newlump ) +{ + int left, right; + dlumpinfo_t *plump; + + // look for the slot we should put that file into (binary search) + left = 0; + right = wad->numlumps - 1; + + while( left <= right ) + { + int middle = ( left + right ) / 2; + int diff = Q_stricmp( wad->lumps[middle].name, name ); + + if( !diff ) + { + if( wad->lumps[middle].type < newlump->type ) + diff = 1; + else if( wad->lumps[middle].type > newlump->type ) + diff = -1; + else Con_Reportf( S_WARN "Wad %s contains the file %s several times\n", wad->filename, name ); + } + + // If we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + // we have to move the right of the list by one slot to free the one we need + plump = &wad->lumps[left]; + memmove( plump + 1, plump, ( wad->numlumps - left ) * sizeof( *plump )); + wad->numlumps++; + + *plump = *newlump; + memcpy( plump->name, name, sizeof( plump->name )); + + return plump; +} + +/* +=========== +FS_CloseWAD + +finalize wad or just close +=========== +*/ +void FS_CloseWAD( wfile_t *wad ) +{ + Mem_FreePool( &wad->mempool ); + if( wad->handle != NULL ) + FS_Close( wad->handle ); + Mem_Free( wad ); // free himself +} + +/* +=========== +W_Open + +open the wad for reading & writing +=========== +*/ +static wfile_t *W_Open( const char *filename, int *error ) +{ + wfile_t *wad = (wfile_t *)Mem_Calloc( fs_mempool, sizeof( wfile_t )); + const char *basename; + int i, lumpcount; + dlumpinfo_t *srclumps; + size_t lat_size; + dwadinfo_t header; + + // NOTE: FS_Open is load wad file from the first pak in the list (while fs_ext_path is false) + if( fs_ext_path ) basename = filename; + else basename = COM_FileWithoutPath( filename ); + + wad->handle = FS_Open( basename, "rb", false ); + + // HACKHACK: try to open WAD by full path for RoDir, when searchpaths are not ready + if( COM_CheckStringEmpty( fs_rodir ) && fs_ext_path && wad->handle == NULL ) + wad->handle = FS_SysOpen( filename, "rb" ); + + if( wad->handle == NULL ) + { + Con_Reportf( S_ERROR "W_Open: couldn't open %s\n", filename ); + if( error ) *error = WAD_LOAD_COULDNT_OPEN; + FS_CloseWAD( wad ); + return NULL; + } + + // copy wad name + Q_strncpy( wad->filename, filename, sizeof( wad->filename )); + wad->filetime = FS_SysFileTime( filename ); + wad->mempool = Mem_AllocPool( filename ); + + if( FS_Read( wad->handle, &header, sizeof( dwadinfo_t )) != sizeof( dwadinfo_t )) + { + Con_Reportf( S_ERROR "W_Open: %s can't read header\n", filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + FS_CloseWAD( wad ); + return NULL; + } + + if( header.ident != IDWAD2HEADER && header.ident != IDWAD3HEADER ) + { + Con_Reportf( S_ERROR "W_Open: %s is not a WAD2 or WAD3 file\n", filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + FS_CloseWAD( wad ); + return NULL; + } + + lumpcount = header.numlumps; + + if( lumpcount >= MAX_FILES_IN_WAD ) + { + Con_Reportf( S_WARN "W_Open: %s is full (%i lumps)\n", filename, lumpcount ); + if( error ) *error = WAD_LOAD_TOO_MANY_FILES; + } + else if( lumpcount <= 0 ) + { + Con_Reportf( S_ERROR "W_Open: %s has no lumps\n", filename ); + if( error ) *error = WAD_LOAD_NO_FILES; + FS_CloseWAD( wad ); + return NULL; + } + else if( error ) *error = WAD_LOAD_OK; + + wad->infotableofs = header.infotableofs; // save infotableofs position + + if( FS_Seek( wad->handle, wad->infotableofs, SEEK_SET ) == -1 ) + { + Con_Reportf( S_ERROR "W_Open: %s can't find lump allocation table\n", filename ); + if( error ) *error = WAD_LOAD_BAD_FOLDERS; + FS_CloseWAD( wad ); + return NULL; + } + + lat_size = lumpcount * sizeof( dlumpinfo_t ); + + // NOTE: lumps table can be reallocated for O_APPEND mode + srclumps = (dlumpinfo_t *)Mem_Malloc( wad->mempool, lat_size ); + + if( FS_Read( wad->handle, srclumps, lat_size ) != lat_size ) + { + Con_Reportf( S_ERROR "W_ReadLumpTable: %s has corrupted lump allocation table\n", wad->filename ); + if( error ) *error = WAD_LOAD_CORRUPTED; + Mem_Free( srclumps ); + FS_CloseWAD( wad ); + return NULL; + } + + // starting to add lumps + wad->lumps = (dlumpinfo_t *)Mem_Calloc( wad->mempool, lat_size ); + wad->numlumps = 0; + + // sort lumps for binary search + for( i = 0; i < lumpcount; i++ ) + { + char name[16]; + int k; + + // cleanup lumpname + Q_strnlwr( srclumps[i].name, name, sizeof( srclumps[i].name )); + + // check for '*' symbol issues (quake1) + k = Q_strlen( Q_strrchr( name, '*' )); + if( k ) name[Q_strlen( name ) - k] = '!'; + + // check for Quake 'conchars' issues (only lmp loader really allows to read this lame pic) + if( srclumps[i].type == 68 && !Q_stricmp( srclumps[i].name, "conchars" )) + srclumps[i].type = TYP_GFXPIC; + + W_AddFileToWad( name, wad, &srclumps[i] ); + } + + // release source lumps + Mem_Free( srclumps ); + + // and leave the file open + return wad; +} + +/* +==================== +FS_AddWad_Fullpath +==================== +*/ +qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + wfile_t *wad = NULL; + const char *ext = COM_FileExtension( wadfile ); + int errorcode = WAD_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->type == SEARCHPATH_WAD && !Q_stricmp( search->wad->filename, wadfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) + *already_loaded = false; + + if( !Q_stricmp( ext, "wad" )) + wad = W_Open( wadfile, &errorcode ); + + if( wad ) + { + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); + search->wad = wad; + search->type = SEARCHPATH_WAD; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + Con_Reportf( "Adding wadfile: %s (%i files)\n", wadfile, wad->numlumps ); + return true; + } + else + { + if( errorcode != WAD_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "FS_AddWad_Fullpath: unable to load wad \"%s\"\n", wadfile ); + return false; + } +} + +/* +============================================================================= + +WADSYSTEM PRIVATE ROUTINES + +============================================================================= +*/ + +/* +=========== +W_FindLump + +Serach for already existed lump +=========== +*/ +static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed char matchtype ) +{ + int left, right; + + if( !wad || !wad->lumps || matchtype == TYP_NONE ) + return NULL; + + // look for the file (binary search) + left = 0; + right = wad->numlumps - 1; + + while( left <= right ) + { + int middle = (left + right) / 2; + int diff = Q_stricmp( wad->lumps[middle].name, name ); + + if( !diff ) + { + if(( matchtype == TYP_ANY ) || ( matchtype == wad->lumps[middle].type )) + return &wad->lumps[middle]; // found + else if( wad->lumps[middle].type < matchtype ) + diff = 1; + else if( wad->lumps[middle].type > matchtype ) + diff = -1; + else break; // not found + } + + // if we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + return NULL; +} + +/* +=========== +W_ReadLump + +reading lump into temp buffer +=========== +*/ +static byte *W_ReadLump( wfile_t *wad, dlumpinfo_t *lump, fs_offset_t *lumpsizeptr ) +{ + size_t oldpos, size = 0; + byte *buf; + + // assume error + if( lumpsizeptr ) *lumpsizeptr = 0; + + // no wads loaded + if( !wad || !lump ) return NULL; + + oldpos = FS_Tell( wad->handle ); // don't forget restore original position + + if( FS_Seek( wad->handle, lump->filepos, SEEK_SET ) == -1 ) + { + Con_Reportf( S_ERROR "W_ReadLump: %s is corrupted\n", lump->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + return NULL; + } + + buf = (byte *)Mem_Malloc( wad->mempool, lump->disksize ); + size = FS_Read( wad->handle, buf, lump->disksize ); + + if( size < lump->disksize ) + { + Con_Reportf( S_WARN "W_ReadLump: %s is probably corrupted\n", lump->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + Mem_Free( buf ); + return NULL; + } + + if( lumpsizeptr ) *lumpsizeptr = lump->disksize; + FS_Seek( wad->handle, oldpos, SEEK_SET ); + + return buf; +} + +/* +=========== +FS_LoadWADFile + +loading lump into the tmp buffer +=========== +*/ +byte *FS_LoadWADFile( const char *path, fs_offset_t *lumpsizeptr, qboolean gamedironly ) +{ + searchpath_t *search; + int index; + + search = FS_FindFile( path, &index, gamedironly ); + if( search && search->type == SEARCHPATH_WAD ) + return W_ReadLump( search->wad, &search->wad->lumps[index], lumpsizeptr ); + return NULL; +} + +int FS_FileTimeWAD( wfile_t *wad ) +{ + return wad->filetime; +} + +void FS_PrintWADInfo( char *dst, size_t size, wfile_t *wad ) +{ + Q_snprintf( dst, size, "%s (%i files)", wad->filename, wad->numlumps ); +} + +int FS_FindFileWAD( wfile_t *wad, const char *name ) +{ + dlumpinfo_t *lump; + signed char type = W_TypeFromExt( name ); + qboolean anywadname = true; + string wadname, wadfolder; + string shortname; + + // quick reject by filetype + if( type == TYP_NONE ) + return -1; + + COM_ExtractFilePath( name, wadname ); + wadfolder[0] = '\0'; + + if( COM_CheckStringEmpty( wadname ) ) + { + COM_FileBase( wadname, wadname ); + Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); + COM_DefaultExtension( wadname, ".wad" ); + anywadname = false; + } + + // make wadname from wad fullpath + COM_FileBase( wad->filename, shortname ); + COM_DefaultExtension( shortname, ".wad" ); + + // quick reject by wadname + if( !anywadname && Q_stricmp( wadname, shortname )) + return -1; + + // NOTE: we can't using long names for wad, + // because we using original wad names[16]; + COM_FileBase( name, shortname ); + + lump = W_FindLump( wad, shortname, type ); + + if( lump ) + { + return lump - wad->lumps; + } + + return -1; + +} + +void FS_SearchWAD( stringlist_t *list, wfile_t *wad, const char *pattern ) +{ + string wadpattern, wadname, temp2; + signed char type = W_TypeFromExt( pattern ); + qboolean anywadname = true; + string wadfolder, temp; + int j, i; + const char *slash, *backslash, *colon, *separator; + + // quick reject by filetype + if( type == TYP_NONE ) + return; + + COM_ExtractFilePath( pattern, wadname ); + COM_FileBase( pattern, wadpattern ); + wadfolder[0] = '\0'; + + if( COM_CheckStringEmpty( wadname )) + { + COM_FileBase( wadname, wadname ); + Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); + COM_DefaultExtension( wadname, ".wad" ); + anywadname = false; + } + + // make wadname from wad fullpath + COM_FileBase( wad->filename, temp2 ); + COM_DefaultExtension( temp2, ".wad" ); + + // quick reject by wadname + if( !anywadname && Q_stricmp( wadname, temp2 )) + return; + + for( i = 0; i < wad->numlumps; i++ ) + { + // if type not matching, we already have no chance ... + if( type != TYP_ANY && wad->lumps[i].type != type ) + continue; + + // build the lumpname with image suffix (if present) + Q_strncpy( temp, wad->lumps[i].name, sizeof( temp )); + + while( temp[0] ) + { + if( matchpattern( temp, wadpattern, true )) + { + for( j = 0; j < list->numstrings; j++ ) + { + if( !Q_strcmp( list->strings[j], temp )) + break; + } + + if( j == list->numstrings ) + { + // build path: wadname/lumpname.ext + Q_snprintf( temp2, sizeof(temp2), "%s/%s", wadfolder, temp ); + COM_DefaultExtension( temp2, va(".%s", W_ExtFromType( wad->lumps[i].type ))); + stringlistappend( list, temp2 ); + } + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } +} diff --git a/filesystem/wscript b/filesystem/wscript new file mode 100644 index 00000000..bfcf35c0 --- /dev/null +++ b/filesystem/wscript @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +def options(opt): + pass + +def configure(conf): + if conf.env.cxxshlib_PATTERN.startswith('lib'): + conf.env.cxxshlib_PATTERN = conf.env.cxxshlib_PATTERN[3:] + +def build(bld): + bld.shlib(target = 'filesystem_stdio', + features = 'c', + source = bld.path.ant_glob(['*.c']), + includes = ['.', '../common', '../public', '../engine'], + use = ['public'], + install_path = bld.env.LIBDIR, + subsystem = bld.env.MSVC_SUBSYSTEM) diff --git a/filesystem/zip.c b/filesystem/zip.c new file mode 100644 index 00000000..dcad291b --- /dev/null +++ b/filesystem/zip.c @@ -0,0 +1,678 @@ +/* +zip.c - ZIP support for filesystem +Copyright (C) 2019 Mr0maks +Copyright (C) 2022 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include +#include +#include +#include +#include +#include STDINT_H +#include "port.h" +#include "filesystem_internal.h" +#include "crtlib.h" +#include "common/com_strings.h" +#include "miniz.h" + +#define ZIP_HEADER_LF (('K'<<8)+('P')+(0x03<<16)+(0x04<<24)) +#define ZIP_HEADER_SPANNED ((0x08<<24)+(0x07<<16)+('K'<<8)+'P') + +#define ZIP_HEADER_CDF ((0x02<<24)+(0x01<<16)+('K'<<8)+'P') +#define ZIP_HEADER_EOCD ((0x06<<24)+(0x05<<16)+('K'<<8)+'P') + +#define ZIP_COMPRESSION_NO_COMPRESSION 0 +#define ZIP_COMPRESSION_DEFLATED 8 + +#define ZIP_ZIP64 0xffffffff + +#pragma pack( push, 1 ) +typedef struct zip_header_s +{ + unsigned int signature; // little endian ZIP_HEADER + unsigned short version; // version of pkzip need to unpack + unsigned short flags; // flags (16 bits == 16 flags) + unsigned short compression_flags; // compression flags (bits) + unsigned int dos_date; // file modification time and file modification date + unsigned int crc32; //crc32 + unsigned int compressed_size; + unsigned int uncompressed_size; + unsigned short filename_len; + unsigned short extrafield_len; +} zip_header_t; + +/* + in zip64 comp and uncompr size == 0xffffffff remeber this + compressed and uncompress filesize stored in extra field +*/ + +typedef struct zip_header_extra_s +{ + unsigned int signature; // ZIP_HEADER_SPANNED + unsigned int crc32; + unsigned int compressed_size; + unsigned int uncompressed_size; +} zip_header_extra_t; + +typedef struct zip_cdf_header_s +{ + unsigned int signature; + unsigned short version; + unsigned short version_need; + unsigned short generalPurposeBitFlag; + unsigned short flags; + unsigned short modification_time; + unsigned short modification_date; + unsigned int crc32; + unsigned int compressed_size; + unsigned int uncompressed_size; + unsigned short filename_len; + unsigned short extrafield_len; + unsigned short file_commentary_len; + unsigned short disk_start; + unsigned short internal_attr; + unsigned int external_attr; + unsigned int local_header_offset; +} zip_cdf_header_t; + +typedef struct zip_header_eocd_s +{ + unsigned short disk_number; + unsigned short start_disk_number; + unsigned short number_central_directory_record; + unsigned short total_central_directory_record; + unsigned int size_of_central_directory; + unsigned int central_directory_offset; + unsigned short commentary_len; +} zip_header_eocd_t; +#pragma pack( pop ) + +// ZIP errors +#define ZIP_LOAD_OK 0 +#define ZIP_LOAD_COULDNT_OPEN 1 +#define ZIP_LOAD_BAD_HEADER 2 +#define ZIP_LOAD_BAD_FOLDERS 3 +#define ZIP_LOAD_NO_FILES 5 +#define ZIP_LOAD_CORRUPTED 6 + +typedef struct zipfile_s +{ + char name[MAX_SYSPATH]; + fs_offset_t offset; // offset of local file header + fs_offset_t size; //original file size + fs_offset_t compressed_size; // compressed file size + unsigned short flags; +} zipfile_t; + +typedef struct zip_s +{ + string filename; + int handle; + int numfiles; + time_t filetime; + zipfile_t *files; +} zip_t; + +#ifdef XASH_REDUCE_FD +static void FS_EnsureOpenZip( zip_t *zip ) +{ + if( fs_last_zip == zip ) + return; + + if( fs_last_zip && (fs_last_zip->handle != -1) ) + { + close( fs_last_zip->handle ); + fs_last_zip->handle = -1; + } + fs_last_zip = zip; + if( zip && (zip->handle == -1) ) + zip->handle = open( zip->filename, O_RDONLY|O_BINARY ); +} +#else +static void FS_EnsureOpenZip( zip_t *zip ) {} +#endif + +void FS_CloseZIP( zip_t *zip ) +{ + if( zip->files ) + Mem_Free( zip->files ); + + FS_EnsureOpenZip( NULL ); + + if( zip->handle >= 0 ) + close( zip->handle ); + + Mem_Free( zip ); +} + +/* +============ +FS_SortZip +============ +*/ +static int FS_SortZip( const void *a, const void *b ) +{ + return Q_stricmp( ( ( zipfile_t* )a )->name, ( ( zipfile_t* )b )->name ); +} + +/* +============ +FS_LoadZip +============ +*/ +static zip_t *FS_LoadZip( const char *zipfile, int *error ) +{ + int numpackfiles = 0, i; + zip_cdf_header_t header_cdf; + zip_header_eocd_t header_eocd; + uint32_t signature; + fs_offset_t filepos = 0, length; + zipfile_t *info = NULL; + char filename_buffer[MAX_SYSPATH]; + zip_t *zip = (zip_t *)Mem_Calloc( fs_mempool, sizeof( *zip )); + fs_size_t c; + + zip->handle = open( zipfile, O_RDONLY|O_BINARY ); + +#if !XASH_WIN32 + if( zip->handle < 0 ) + { + const char *fzipfile = FS_FixFileCase( zipfile ); + if( fzipfile != zipfile ) + zip->handle = open( fzipfile, O_RDONLY|O_BINARY ); + } +#endif + + if( zip->handle < 0 ) + { + Con_Reportf( S_ERROR "%s couldn't open\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_COULDNT_OPEN; + + FS_CloseZIP( zip ); + return NULL; + } + + length = lseek( zip->handle, 0, SEEK_END ); + + if( length > UINT_MAX ) + { + Con_Reportf( S_ERROR "%s bigger than 4GB.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_COULDNT_OPEN; + + FS_CloseZIP( zip ); + return NULL; + } + + lseek( zip->handle, 0, SEEK_SET ); + + c = read( zip->handle, &signature, sizeof( signature ) ); + + if( c != sizeof( signature ) || signature == ZIP_HEADER_EOCD ) + { + Con_Reportf( S_WARN "%s has no files. Ignored.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_NO_FILES; + + FS_CloseZIP( zip ); + return NULL; + } + + if( signature != ZIP_HEADER_LF ) + { + Con_Reportf( S_ERROR "%s is not a zip file. Ignored.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + FS_CloseZIP( zip ); + return NULL; + } + + // Find oecd + lseek( zip->handle, 0, SEEK_SET ); + filepos = length; + + while ( filepos > 0 ) + { + lseek( zip->handle, filepos, SEEK_SET ); + c = read( zip->handle, &signature, sizeof( signature ) ); + + if( c == sizeof( signature ) && signature == ZIP_HEADER_EOCD ) + break; + + filepos -= sizeof( char ); // step back one byte + } + + if( ZIP_HEADER_EOCD != signature ) + { + Con_Reportf( S_ERROR "cannot find EOCD in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + FS_CloseZIP( zip ); + return NULL; + } + + c = read( zip->handle, &header_eocd, sizeof( header_eocd ) ); + + if( c != sizeof( header_eocd )) + { + Con_Reportf( S_ERROR "invalid EOCD header in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + FS_CloseZIP( zip ); + return NULL; + } + + // Move to CDF start + lseek( zip->handle, header_eocd.central_directory_offset, SEEK_SET ); + + // Calc count of files in archive + info = (zipfile_t *)Mem_Calloc( fs_mempool, sizeof( *info ) * header_eocd.total_central_directory_record ); + + for( i = 0; i < header_eocd.total_central_directory_record; i++ ) + { + c = read( zip->handle, &header_cdf, sizeof( header_cdf ) ); + + if( c != sizeof( header_cdf ) || header_cdf.signature != ZIP_HEADER_CDF ) + { + Con_Reportf( S_ERROR "CDF signature mismatch in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + Mem_Free( info ); + FS_CloseZIP( zip ); + return NULL; + } + + if( header_cdf.uncompressed_size && header_cdf.filename_len && ( header_cdf.filename_len < MAX_SYSPATH ) ) + { + memset( &filename_buffer, '\0', MAX_SYSPATH ); + c = read( zip->handle, &filename_buffer, header_cdf.filename_len ); + + if( c != header_cdf.filename_len ) + { + Con_Reportf( S_ERROR "filename length mismatch in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_CORRUPTED; + + Mem_Free( info ); + FS_CloseZIP( zip ); + return NULL; + } + + Q_strncpy( info[numpackfiles].name, filename_buffer, MAX_SYSPATH ); + + info[numpackfiles].size = header_cdf.uncompressed_size; + info[numpackfiles].compressed_size = header_cdf.compressed_size; + info[numpackfiles].offset = header_cdf.local_header_offset; + numpackfiles++; + } + else + lseek( zip->handle, header_cdf.filename_len, SEEK_CUR ); + + if( header_cdf.extrafield_len ) + lseek( zip->handle, header_cdf.extrafield_len, SEEK_CUR ); + + if( header_cdf.file_commentary_len ) + lseek( zip->handle, header_cdf.file_commentary_len, SEEK_CUR ); + } + + // recalculate offsets + for( i = 0; i < numpackfiles; i++ ) + { + zip_header_t header; + + lseek( zip->handle, info[i].offset, SEEK_SET ); + c = read( zip->handle, &header, sizeof( header ) ); + + if( c != sizeof( header )) + { + Con_Reportf( S_ERROR "header length mismatch in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_CORRUPTED; + + Mem_Free( info ); + FS_CloseZIP( zip ); + return NULL; + } + + info[i].flags = header.compression_flags; + info[i].offset = info[i].offset + header.filename_len + header.extrafield_len + sizeof( header ); + } + + Q_strncpy( zip->filename, zipfile, sizeof( zip->filename ) ); + zip->filetime = FS_SysFileTime( zipfile ); + zip->numfiles = numpackfiles; + zip->files = info; + + qsort( zip->files, zip->numfiles, sizeof( *zip->files ), FS_SortZip ); + +#ifdef XASH_REDUCE_FD + // will reopen when needed + close(zip->handle); + zip->handle = -1; +#endif + + if( error ) + *error = ZIP_LOAD_OK; + + return zip; +} + +/* +=========== +FS_OpenZipFile + +Open a packed file using its package file descriptor +=========== +*/ +file_t *FS_OpenZipFile( zip_t *zip, int pack_ind ) +{ + zipfile_t *pfile; + pfile = &zip->files[pack_ind]; + + // compressed files handled in Zip_LoadFile + if( pfile->flags != ZIP_COMPRESSION_NO_COMPRESSION ) + { + Con_Printf( S_ERROR "%s: can't open compressed file %s\n", __FUNCTION__, pfile->name ); + return NULL; + } + + return FS_OpenHandle( zip->filename, zip->handle, pfile->offset, pfile->size ); +} + +byte *FS_LoadZIPFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ) +{ + searchpath_t *search; + int index; + zipfile_t *file = NULL; + byte *compressed_buffer = NULL, *decompressed_buffer = NULL; + int zlib_result = 0; + dword test_crc, final_crc; + z_stream decompress_stream; + size_t c; + + if( sizeptr ) *sizeptr = 0; + + search = FS_FindFile( path, &index, gamedironly ); + + if( !search || search->type != SEARCHPATH_ZIP ) + return NULL; + + file = &search->zip->files[index]; + + FS_EnsureOpenZip( search->zip ); + + if( lseek( search->zip->handle, file->offset, SEEK_SET ) == -1 ) + return NULL; + + /*if( read( search->zip->handle, &header, sizeof( header ) ) < 0 ) + return NULL; + + if( header.signature != ZIP_HEADER_LF ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s signature error\n", file->name ); + return NULL; + }*/ + + if( file->flags == ZIP_COMPRESSION_NO_COMPRESSION ) + { + decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); + decompressed_buffer[file->size] = '\0'; + + c = read( search->zip->handle, decompressed_buffer, file->size ); + if( c != file->size ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s size doesn't match\n", file->name ); + return NULL; + } + +#if 0 + CRC32_Init( &test_crc ); + CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); + + final_crc = CRC32_Final( test_crc ); + + if( final_crc != file->crc32 ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); + Mem_Free( decompressed_buffer ); + return NULL; + } +#endif + if( sizeptr ) *sizeptr = file->size; + + FS_EnsureOpenZip( NULL ); + return decompressed_buffer; + } + else if( file->flags == ZIP_COMPRESSION_DEFLATED ) + { + compressed_buffer = Mem_Malloc( fs_mempool, file->compressed_size + 1 ); + decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); + decompressed_buffer[file->size] = '\0'; + + c = read( search->zip->handle, compressed_buffer, file->compressed_size ); + if( c != file->compressed_size ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s compressed size doesn't match\n", file->name ); + return NULL; + } + + memset( &decompress_stream, 0, sizeof( decompress_stream ) ); + + decompress_stream.total_in = decompress_stream.avail_in = file->compressed_size; + decompress_stream.next_in = (Bytef *)compressed_buffer; + decompress_stream.total_out = decompress_stream.avail_out = file->size; + decompress_stream.next_out = (Bytef *)decompressed_buffer; + + decompress_stream.zalloc = Z_NULL; + decompress_stream.zfree = Z_NULL; + decompress_stream.opaque = Z_NULL; + + if( inflateInit2( &decompress_stream, -MAX_WBITS ) != Z_OK ) + { + Con_Printf( S_ERROR "Zip_LoadFile: inflateInit2 failed\n" ); + Mem_Free( compressed_buffer ); + Mem_Free( decompressed_buffer ); + return NULL; + } + + zlib_result = inflate( &decompress_stream, Z_NO_FLUSH ); + inflateEnd( &decompress_stream ); + + if( zlib_result == Z_OK || zlib_result == Z_STREAM_END ) + { + Mem_Free( compressed_buffer ); // finaly free compressed buffer +#if 0 + CRC32_Init( &test_crc ); + CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); + + final_crc = CRC32_Final( test_crc ); + + if( final_crc != file->crc32 ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); + Mem_Free( decompressed_buffer ); + return NULL; + } +#endif + if( sizeptr ) *sizeptr = file->size; + + FS_EnsureOpenZip( NULL ); + return decompressed_buffer; + } + else + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s : error while file decompressing. Zlib return code %d.\n", file->name, zlib_result ); + Mem_Free( compressed_buffer ); + Mem_Free( decompressed_buffer ); + return NULL; + } + + } + else + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s : file compressed with unknown algorithm.\n", file->name ); + return NULL; + } + + FS_EnsureOpenZip( NULL ); + return NULL; +} + + +qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + zip_t *zip = NULL; + const char *ext = COM_FileExtension( zipfile ); + int errorcode = ZIP_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->type == SEARCHPATH_ZIP && !Q_stricmp( search->zip->filename, zipfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) *already_loaded = false; + + if( !Q_stricmp( ext, "pk3" ) ) + zip = FS_LoadZip( zipfile, &errorcode ); + + if( zip ) + { + string fullpath; + int i; + + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ) ); + search->zip = zip; + search->type = SEARCHPATH_ZIP; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + Con_Reportf( "Adding zipfile: %s (%i files)\n", zipfile, zip->numfiles ); + + // time to add in search list all the wads that contains in current pakfile (if do) + for( i = 0; i < zip->numfiles; i++ ) + { + if( !Q_stricmp( COM_FileExtension( zip->files[i].name ), "wad" )) + { + Q_snprintf( fullpath, MAX_STRING, "%s/%s", zipfile, zip->files[i].name ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } + } + return true; + } + else + { + if( errorcode != ZIP_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "FS_AddZip_Fullpath: unable to load zip \"%s\"\n", zipfile ); + return false; + } +} + +int FS_FileTimeZIP( zip_t *zip ) +{ + return zip->filetime; +} + +void FS_PrintZIPInfo( char *dst, size_t size, zip_t *zip ) +{ + Q_snprintf( dst, size, "%s (%i files)", zip->filename, zip->numfiles ); +} + +int FS_FindFileZIP( zip_t *zip, const char *name ) +{ + int left, right, middle; + + // look for the file (binary search) + left = 0; + right = zip->numfiles - 1; + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( zip->files[middle].name, name ); + + // Found it + if( !diff ) + return middle; + + // if we're too far in the list + if( diff > 0 ) + right = middle - 1; + else left = middle + 1; + } + + return -1; +} + +void FS_SearchZIP( stringlist_t *list, zip_t *zip, const char *pattern ) +{ + string temp; + const char *slash, *backslash, *colon, *separator; + int j, i; + + for( i = 0; i < zip->numfiles; i++ ) + { + Q_strncpy( temp, zip->files[i].name, sizeof( temp )); + while( temp[0] ) + { + if( matchpattern( temp, pattern, true )) + { + for( j = 0; j < list->numstrings; j++ ) + { + if( !Q_strcmp( list->strings[j], temp )) + break; + } + + if( j == list->numstrings ) + stringlistappend( list, temp ); + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } +} + diff --git a/public/crtlib.c b/public/crtlib.c index d183c7a5..652ae917 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -819,6 +819,23 @@ void COM_RemoveLineFeed( char *str ) } } +/* +============ +COM_FixSlashes + +Changes all '/' characters into '\' characters, in place. +============ +*/ +void COM_FixSlashes( char *pname ) +{ + while( *pname ) + { + if( *pname == '\\' ) + *pname = '/'; + pname++; + } +} + /* ============ COM_PathSlashFix diff --git a/public/crtlib.h b/public/crtlib.h index 2d44939f..84027ea6 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -95,6 +95,7 @@ void COM_ExtractFilePath( const char *path, char *dest ); const char *COM_FileWithoutPath( const char *in ); void COM_StripExtension( char *path ); void COM_RemoveLineFeed( char *str ); +void COM_FixSlashes( char *pname ); void COM_PathSlashFix( char *path ); char COM_Hex2Char( uint8_t hex ); void COM_Hex2String( uint8_t hex, char *str ); diff --git a/engine/common/miniz.h b/public/miniz.h similarity index 100% rename from engine/common/miniz.h rename to public/miniz.h diff --git a/ref_gl/gl_alias.c b/ref_gl/gl_alias.c index 77b27b77..d9766755 100644 --- a/ref_gl/gl_alias.c +++ b/ref_gl/gl_alias.c @@ -486,7 +486,7 @@ void *Mod_LoadSingleSkin( daliasskintype_t *pskintype, int skinnum, int size ) Q_snprintf( name, sizeof( name ), "%s:frame%i", loadmodel->name, skinnum ); Q_snprintf( lumaname, sizeof( lumaname ), "%s:luma%i", loadmodel->name, skinnum ); Q_snprintf( checkname, sizeof( checkname ), "%s_%i.tga", loadmodel->name, skinnum ); - if( !gEngfuncs.FS_FileExists( checkname, false ) || ( pic = gEngfuncs.FS_LoadImage( checkname, NULL, 0 )) == NULL ) + if( !gEngfuncs.fsapi->FileExists( checkname, false ) || ( pic = gEngfuncs.FS_LoadImage( checkname, NULL, 0 )) == NULL ) pic = Mod_CreateSkinData( loadmodel, (byte *)(pskintype + 1), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); m_pAliasHeader->gl_texturenum[skinnum][0] = diff --git a/ref_gl/gl_backend.c b/ref_gl/gl_backend.c index 88881f9f..da530405 100644 --- a/ref_gl/gl_backend.c +++ b/ref_gl/gl_backend.c @@ -478,7 +478,7 @@ qboolean VID_ScreenShot( const char *filename, int shot_type ) case VID_SCREENSHOT: break; case VID_SNAPSHOT: - gEngfuncs.FS_AllowDirectPaths( true ); + gEngfuncs.fsapi->AllowDirectPaths( true ); break; case VID_LEVELSHOT: flags |= IMAGE_RESAMPLE; @@ -509,7 +509,7 @@ qboolean VID_ScreenShot( const char *filename, int shot_type ) // write image result = gEngfuncs.FS_SaveImage( filename, r_shot ); - gEngfuncs.FS_AllowDirectPaths( false ); // always reset after store screenshot + gEngfuncs.fsapi->AllowDirectPaths( false ); // always reset after store screenshot gEngfuncs.FS_FreeImage( r_shot ); return result; diff --git a/ref_gl/gl_rmisc.c b/ref_gl/gl_rmisc.c index c402e337..230e66b9 100644 --- a/ref_gl/gl_rmisc.c +++ b/ref_gl/gl_rmisc.c @@ -29,7 +29,7 @@ static void R_ParseDetailTextures( const char *filename ) texture_t *tex; int i; - afile = gEngfuncs.COM_LoadFile( filename, NULL, false ); + afile = gEngfuncs.fsapi->LoadFile( filename, NULL, false ); if( !afile ) return; pfile = (char *)afile; diff --git a/ref_gl/gl_studio.c b/ref_gl/gl_studio.c index bb52c262..c71630d9 100644 --- a/ref_gl/gl_studio.c +++ b/ref_gl/gl_studio.c @@ -2699,7 +2699,7 @@ static model_t *R_StudioSetupPlayerModel( int index ) Q_snprintf( state->modelname, sizeof( state->modelname ), "models/player/%s/%s.mdl", info->model, info->model ); - if( gEngfuncs.FS_FileExists( state->modelname, false )) + if( gEngfuncs.fsapi->FileExists( state->modelname, false )) state->model = gEngfuncs.Mod_ForName( state->modelname, false, true ); else state->model = NULL; diff --git a/ref_gl/gl_warp.c b/ref_gl/gl_warp.c index 32359d89..72e77c1c 100644 --- a/ref_gl/gl_warp.c +++ b/ref_gl/gl_warp.c @@ -78,7 +78,7 @@ static int CheckSkybox( const char *name ) { // build side name sidename = va( "%s%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); - if( gEngfuncs.FS_FileExists( sidename, false )) + if( gEngfuncs.fsapi->FileExists( sidename, false )) num_checked_sides++; } @@ -90,7 +90,7 @@ static int CheckSkybox( const char *name ) { // build side name sidename = va( "%s_%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); - if( gEngfuncs.FS_FileExists( sidename, false )) + if( gEngfuncs.fsapi->FileExists( sidename, false )) num_checked_sides++; } diff --git a/ref_gl/wscript b/ref_gl/wscript index af60caea..207e7ec6 100644 --- a/ref_gl/wscript +++ b/ref_gl/wscript @@ -59,6 +59,7 @@ def build(bld): source = bld.path.ant_glob(['*.c']) includes = ['.', + '../filesystem', '../engine', '../engine/common', '../engine/server', diff --git a/ref_soft/r_studio.c b/ref_soft/r_studio.c index 3dad15b6..0a68035a 100644 --- a/ref_soft/r_studio.c +++ b/ref_soft/r_studio.c @@ -2462,7 +2462,7 @@ static model_t *R_StudioSetupPlayerModel( int index ) Q_snprintf( state->modelname, sizeof( state->modelname ), "models/player/%s/%s.mdl", info->model, info->model ); - if( gEngfuncs.FS_FileExists( state->modelname, false )) + if( gEngfuncs.fsapi->FileExists( state->modelname, false )) state->model = gEngfuncs.Mod_ForName( state->modelname, false, true ); else state->model = NULL; diff --git a/ref_soft/wscript b/ref_soft/wscript index 93933140..9504cd84 100644 --- a/ref_soft/wscript +++ b/ref_soft/wscript @@ -30,6 +30,7 @@ def build(bld): source = bld.path.ant_glob(['*.c']) includes = ['.', + '../filesystem', '../engine', '../engine/common', '../engine/server', diff --git a/wscript b/wscript index f87fc7ff..6565e53c 100644 --- a/wscript +++ b/wscript @@ -55,6 +55,7 @@ class Subproject: SUBDIRS = [ Subproject('public', dedicated=False, mandatory = True), + Subproject('filesystem', dedicated=False, mandatory = True), Subproject('game_launch', singlebin=True), Subproject('ref_gl',), Subproject('ref_soft'), From 56d7c05b5180347dcd9ad6050f676a4de43708e7 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 4 Jul 2022 18:30:10 +0300 Subject: [PATCH 395/548] filesystem: generated VFileSystem009 from pdwtags output, removed unneeded in C++ this first argument --- filesystem/VFileSystem009.h | 153 ++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 filesystem/VFileSystem009.h diff --git a/filesystem/VFileSystem009.h b/filesystem/VFileSystem009.h new file mode 100644 index 00000000..a0b73123 --- /dev/null +++ b/filesystem/VFileSystem009.h @@ -0,0 +1,153 @@ +/* +VFileSystem009.h - C++ interface for filesystem_stdio +Copyright (C) 2022 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef VFILESYSTEM009_H +#define VFILESYSTEM009_H + +// exported from dwarf +typedef enum { + FILESYSTEM_SEEK_HEAD = 0, + FILESYSTEM_SEEK_CURRENT = 1, + FILESYSTEM_SEEK_TAIL = 2, +} FileSystemSeek_t; /* size: 4 */ + +typedef enum { + FILESYSTEM_WARNING_QUIET = 0, + FILESYSTEM_WARNING_REPORTUNCLOSED = 1, + FILESYSTEM_WARNING_REPORTUSAGE = 2, + FILESYSTEM_WARNING_REPORTALLACCESSES = 3, +} FileWarningLevel_t; /* size: 4 */ + +typedef void * FileHandle_t; /* size: 4 */ +typedef int FileFindHandle_t; /* size: 4 */ +typedef int WaitForResourcesHandle_t; /* size: 4 */ + +class IBaseInterface +{ +public: + virtual ~IBaseInterface() {} +}; + +class IVFileSystem009 : public IBaseInterface +{ +public: + virtual void Mount() = 0; /* linkage=_ZN11IFileSystem5MountEv */ + + virtual void Unmount() = 0; /* linkage=_ZN11IFileSystem7UnmountEv */ + + virtual void RemoveAllSearchPaths() = 0; /* linkage=_ZN11IFileSystem20RemoveAllSearchPathsEv */ + + virtual void AddSearchPath(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem13AddSearchPathEPKcS1_ */ + + virtual bool RemoveSearchPath(const char *) = 0; /* linkage=_ZN11IFileSystem16RemoveSearchPathEPKc */ + + virtual void RemoveFile(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem10RemoveFileEPKcS1_ */ + + virtual void CreateDirHierarchy(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem18CreateDirHierarchyEPKcS1_ */ + + virtual bool FileExists(const char *) = 0; /* linkage=_ZN11IFileSystem10FileExistsEPKc */ + + virtual bool IsDirectory(const char *) = 0; /* linkage=_ZN11IFileSystem11IsDirectoryEPKc */ + + virtual FileHandle_t Open(const char *, const char *, const char *) = 0; /* linkage=_ZN11IFileSystem4OpenEPKcS1_S1_ */ + + virtual void Close(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem5CloseEPv */ + + virtual void Seek(FileHandle_t, int, FileSystemSeek_t) = 0; /* linkage=_ZN11IFileSystem4SeekEPvi16FileSystemSeek_t */ + + virtual unsigned int Tell(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4TellEPv */ + + virtual unsigned int Size(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4SizeEPv */ + + virtual unsigned int Size(const char *) = 0; /* linkage=_ZN11IFileSystem4SizeEPKc */ + + virtual long int GetFileTime(const char *) = 0; /* linkage=_ZN11IFileSystem11GetFileTimeEPKc */ + + virtual void FileTimeToString(char *, int, long int) = 0; /* linkage=_ZN11IFileSystem16FileTimeToStringEPcil */ + + virtual bool IsOk(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4IsOkEPv */ + + virtual void Flush(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem5FlushEPv */ + + virtual bool EndOfFile(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem9EndOfFileEPv */ + + virtual int Read(void *, int, FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4ReadEPviS0_ */ + + virtual int Write(const void *, int, FileHandle_t) = 0; /* linkage=_ZN11IFileSystem5WriteEPKviPv */ + + virtual char * ReadLine(char *, int, FileHandle_t) = 0; /* linkage=_ZN11IFileSystem8ReadLineEPciPv */ + + virtual int FPrintf(FileHandle_t, char *, ...) = 0; /* linkage=_ZN11IFileSystem7FPrintfEPvPcz */ + + virtual void * GetReadBuffer(FileHandle_t, int *, bool) = 0; /* linkage=_ZN11IFileSystem13GetReadBufferEPvPib */ + + virtual void ReleaseReadBuffer(FileHandle_t, void *) = 0; /* linkage=_ZN11IFileSystem17ReleaseReadBufferEPvS0_ */ + + virtual const char * FindFirst(const char *, FileFindHandle_t *, const char *) = 0; /* linkage=_ZN11IFileSystem9FindFirstEPKcPiS1_ */ + + virtual const char * FindNext(FileFindHandle_t) = 0; /* linkage=_ZN11IFileSystem8FindNextEi */ + + virtual bool FindIsDirectory(FileFindHandle_t) = 0; /* linkage=_ZN11IFileSystem15FindIsDirectoryEi */ + + virtual void FindClose(FileFindHandle_t) = 0; /* linkage=_ZN11IFileSystem9FindCloseEi */ + + virtual void GetLocalCopy(const char *) = 0; /* linkage=_ZN11IFileSystem12GetLocalCopyEPKc */ + + virtual const char * GetLocalPath(const char *, char *, int) = 0; /* linkage=_ZN11IFileSystem12GetLocalPathEPKcPci */ + + virtual char * ParseFile(char *, char *, bool *) = 0; /* linkage=_ZN11IFileSystem9ParseFileEPcS0_Pb */ + + virtual bool FullPathToRelativePath(const char *, char *) = 0; /* linkage=_ZN11IFileSystem22FullPathToRelativePathEPKcPc */ + + virtual bool GetCurrentDirectory(char *, int) = 0; /* linkage=_ZN11IFileSystem19GetCurrentDirectoryEPci */ + + virtual void PrintOpenedFiles() = 0; /* linkage=_ZN11IFileSystem16PrintOpenedFilesEv */ + + virtual void SetWarningFunc(void (*)(const char *, ...)) = 0; /* linkage=_ZN11IFileSystem14SetWarningFuncEPFvPKczE */ + + virtual void SetWarningLevel(FileWarningLevel_t) = 0; /* linkage=_ZN11IFileSystem15SetWarningLevelE18FileWarningLevel_t */ + + virtual void LogLevelLoadStarted(const char *) = 0; /* linkage=_ZN11IFileSystem19LogLevelLoadStartedEPKc */ + + virtual void LogLevelLoadFinished(const char *) = 0; /* linkage=_ZN11IFileSystem20LogLevelLoadFinishedEPKc */ + + virtual int HintResourceNeed(const char *, int) = 0; /* linkage=_ZN11IFileSystem16HintResourceNeedEPKci */ + + virtual int PauseResourcePreloading() = 0; /* linkage=_ZN11IFileSystem23PauseResourcePreloadingEv */ + + virtual int ResumeResourcePreloading() = 0; /* linkage=_ZN11IFileSystem24ResumeResourcePreloadingEv */ + + virtual int SetVBuf(FileHandle_t, char *, int, long int) = 0; /* linkage=_ZN11IFileSystem7SetVBufEPvPcil */ + + virtual void GetInterfaceVersion(char *, int) = 0; /* linkage=_ZN11IFileSystem19GetInterfaceVersionEPci */ + + virtual bool IsFileImmediatelyAvailable(const char *) = 0; /* linkage=_ZN11IFileSystem26IsFileImmediatelyAvailableEPKc */ + + virtual WaitForResourcesHandle_t WaitForResources(const char *) = 0; /* linkage=_ZN11IFileSystem16WaitForResourcesEPKc */ + + virtual bool GetWaitForResourcesProgress(WaitForResourcesHandle_t, float *, bool *) = 0; /* linkage=_ZN11IFileSystem27GetWaitForResourcesProgressEiPfPb */ + + virtual void CancelWaitForResources(WaitForResourcesHandle_t) = 0; /* linkage=_ZN11IFileSystem22CancelWaitForResourcesEi */ + + virtual bool IsAppReadyForOfflinePlay(int) = 0; /* linkage=_ZN11IFileSystem24IsAppReadyForOfflinePlayEi */ + + virtual bool AddPackFile(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem11AddPackFileEPKcS1_ */ + + virtual FileHandle_t OpenFromCacheForRead(const char *, const char *, const char *) = 0; /* linkage=_ZN11IFileSystem20OpenFromCacheForReadEPKcS1_S1_ */ + + virtual void AddSearchPathNoWrite(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem20AddSearchPathNoWriteEPKcS1_ */ +}; + +#endif // VFILESYSTEM009_H From 55a29e6e6bdc0e861f5e585745a1e7b52a290602 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 11 Jul 2022 03:58:59 +0300 Subject: [PATCH 396/548] filesystem: implement VFileSystem009 interface --- filesystem/VFileSystem009.cpp | 497 +++++++++++++++++++++++++++++++ filesystem/filesystem.c | 7 +- filesystem/filesystem_internal.h | 4 + filesystem/wscript | 4 +- 4 files changed, 506 insertions(+), 6 deletions(-) create mode 100644 filesystem/VFileSystem009.cpp diff --git a/filesystem/VFileSystem009.cpp b/filesystem/VFileSystem009.cpp new file mode 100644 index 00000000..adac91bc --- /dev/null +++ b/filesystem/VFileSystem009.cpp @@ -0,0 +1,497 @@ +/* +VFileSystem009.h - C++ interface for filesystem_stdio +Copyright (C) 2022 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include +#include +#include +#include +#include ALLOCA_H +#include "crtlib.h" +#include "filesystem.h" +#include "filesystem_internal.h" +#include "VFileSystem009.h" + +#if __cplusplus < 201103L +#define override +#endif + +// GoldSrc Directories and ID +// GAME gamedir +// GAMECONFIG gamedir (rodir integration?) +// GAMEDOWNLOAD gamedir_downloads (gamedir/downloads for us) +// GAME_FALLBACK liblist.gam's fallback_dir +// ROOT and BASE rootdir +// PLATFORM platform +// CONFIG platform/config + +static inline qboolean IsIdGamedir( const char *id ) +{ + return !Q_strcmp( id, "GAME" ) || + !Q_strcmp( id, "GAMECONFIG" ) || + !Q_strcmp( id, "GAMEDOWNLOAD" ); +} + +static inline const char* IdToDir( const char *id ) +{ + if( !Q_strcmp( id, "GAME" )) + return GI->gamefolder; + else if( !Q_strcmp( id, "GAMEDOWNLOAD" )) + return va( "%s/downloaded", GI->gamefolder ); + else if( !Q_strcmp( id, "GAMECONFIG" )) + return fs_writedir; // full path here so it's totally our write allowed directory + else if( !Q_strcmp( id, "PLATFORM" )) + return "platform"; // stub + else if( !Q_strcmp( id, "CONFIG" )) + return "platform/config"; // stub + else // ROOT || BASE + return fs_rootdir; // give at least root directory +} + +static inline void CopyAndFixSlashes( char *p, const char *in ) +{ + Q_strcpy( p, in ); + COM_FixSlashes( p ); +} + +class CXashFS : public IVFileSystem009 +{ +private: + class CSearchState + { + public: + CSearchState( CSearchState **head, search_t *search ) : + next( *head ), search( search ), index( 0 ) + { + if( *head ) + handle = (*head)->handle + 1; + else handle = 0; + + *head = this; + } + ~CSearchState() + { + Mem_Free( search ); + } + + CSearchState *next; + search_t *search; + int index; + FileFindHandle_t handle; + }; + + CSearchState *searchHead; + + CSearchState *GetSearchStateByHandle( FileFindHandle_t handle ) + { + for( CSearchState *state = searchHead; state; state = state->next ) + { + if( state->handle == handle ) + { + return state; + } + } + + Con_DPrintf( "Can't find search state by handle %d\n", handle ); + return NULL; + } + +public: + CXashFS() : searchHead( NULL ) + { + } + + void RemoveAllSearchPaths() override + { + FS_ClearSearchPath(); + } + + void AddSearchPath( const char *path, const char *id ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + FS_AddGameDirectory( p, FS_CUSTOM_PATH ); + } + + void AddSearchPathNoWrite( const char *path, const char *id ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + FS_AddGameDirectory( p, FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + } + + bool RemoveSearchPath( const char *id ) override + { + // TODO: + return true; + } + + void RemoveFile( const char *path, const char *id ) override + { + FS_Delete( path ); // FS_Delete is aware of slashes + } + + void CreateDirHierarchy( const char *path, const char *id ) override + { + FS_CreatePath( va( "%s/%s", IdToDir( id ), path )); // FS_CreatePath is aware of slashes + } + + bool FileExists( const char *path ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + return FS_FileExists( p, false ); + } + + bool IsDirectory( const char *path ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + return FS_SysFolderExists( p ); + } + + FileHandle_t Open( const char *path, const char *mode, const char *id ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + file_t *fd = FS_Open( p, mode, IsIdGamedir( id ) ); + + return fd; + } + + void Close( FileHandle_t handle ) override + { + FS_Close( (file_t *)handle ); + } + + void Seek( FileHandle_t handle, int offset, FileSystemSeek_t whence ) override + { + int whence_ = SEEK_SET; + switch( whence ) + { + case FILESYSTEM_SEEK_HEAD: whence_ = SEEK_SET; break; + case FILESYSTEM_SEEK_CURRENT: whence_ = SEEK_CUR; break; + case FILESYSTEM_SEEK_TAIL: whence_ = SEEK_END; break; + } + + FS_Seek( (file_t *)handle, offset, whence_ ); + } + + unsigned int Tell( FileHandle_t handle ) override + { + return FS_Tell( (file_t *)handle ); + } + + unsigned int Size( FileHandle_t handle ) override + { + file_t *fd = (file_t *)handle; + return fd->real_length; + } + + unsigned int Size( const char *path ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + return FS_FileSize( p, false ); + } + + long int GetFileTime( const char *path ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + return FS_FileTime( p, false ); + } + + void FileTimeToString( char *p, int size, long int time ) override + { + char *buf = ctime( &time ); + + Q_strncpy( p, buf, size ); + } + + bool IsOk( FileHandle_t handle ) override + { + return !FS_Eof( (file_t *)handle ); + } + + void Flush( FileHandle_t handle ) override + { + FS_Flush( (file_t *)handle ); + } + + bool EndOfFile( FileHandle_t handle ) override + { + return FS_Eof( (file_t *)handle ); + } + + int Read( void *buf, int size, FileHandle_t handle ) override + { + return FS_Read( (file_t *)handle, buf, size ); + } + + int Write( const void *buf, int size, FileHandle_t handle ) override + { + return FS_Write( (file_t *)handle, buf, size ); + } + + char *ReadLine( char *buf, int size, FileHandle_t handle ) override + { + int c = FS_Gets( (file_t *)handle, (byte*)buf, size ); + + return c >= 0 ? buf : NULL; + } + + int FPrintf( FileHandle_t handle, char *fmt, ... ) override + { + va_list ap; + int ret; + + va_start( ap, fmt ); + ret = FS_VPrintf( (file_t *)handle, fmt, ap ); + va_end( ap ); + + return ret; + } + + void * GetReadBuffer(FileHandle_t, int *size, bool) override + { + // deprecated by Valve + *size = 0; + return NULL; + } + + void ReleaseReadBuffer(FileHandle_t, void *) override + { + // deprecated by Valve + return; + } + + const char *FindFirst(const char *pattern, FileFindHandle_t *handle, const char *id) override + { + if( !handle || !pattern ) + return NULL; + + char *p = (char *)alloca( Q_strlen( pattern ) + 1 ); + CopyAndFixSlashes( p, pattern ); + search_t *search = FS_Search( p, true, IsIdGamedir( id )); + + if( !search ) + return NULL; + + CSearchState *state = new CSearchState( &searchHead, search ); + + *handle = state->handle; + + return state->search->filenames[0]; + } + + const char *FindNext( FileFindHandle_t handle ) override + { + CSearchState *state = GetSearchStateByHandle( handle ); + + if( !state ) return NULL; + + if( state->index + 1 >= state->search->numfilenames ) + return NULL; + + return state->search->filenames[++state->index]; + } + + bool FindIsDirectory( FileFindHandle_t handle ) override + { + CSearchState *state = GetSearchStateByHandle( handle ); + + if( !state ) + return false; + + if( state->index >= state->search->numfilenames ) + return false; + + return IsDirectory( state->search->filenames[state->index] ); + } + + void FindClose( FileFindHandle_t handle ) override + { + for( CSearchState *state = searchHead, **prev = NULL; + state; + *prev = state, state = state->next ) + { + if( state->handle == handle ) + { + if( prev ) + (*prev)->next = state->next; + else searchHead = state->next; + + delete state; + + return; + } + } + + Con_DPrintf( "FindClose: Can't find search state by handle %d\n", handle ); + return; + } + + const char * GetLocalPath( const char *name, char *buf, int size ) override + { + if( !name ) return NULL; + + char *p = (char *)alloca( Q_strlen( name ) + 1 ); + CopyAndFixSlashes( p, name ); + +#if !XASH_WIN32 + if( p[0] == '/' ) +#else + if( Q_strchr( p, ':' )) +#endif + { + Q_strncpy( buf, p, size ); + + return buf; + } + + + const char *fullpath = FS_GetDiskPath( p, false ); + if( !fullpath ) + return NULL; + + Q_strncpy( buf, fullpath, size ); + return buf; + } + + char *ParseFile( char *buf, char *token, bool *quoted ) override + { + qboolean qquoted; + + char *p = COM_ParseFileSafe( buf, token, PFILE_FS_TOKEN_MAX_LENGTH, 0, NULL, &qquoted ); + if( quoted ) *quoted = qquoted; + + return p; + } + + bool FullPathToRelativePath( const char *path, char *out ) override + { + if( !COM_CheckString( path )) + { + *out = 0; + return false; + } + + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + searchpath_t *sp; + + for( sp = fs_searchpaths; sp; sp = sp->next ) + { + size_t splen = Q_strlen( sp->filename ); + + if( !Q_strnicmp( sp->filename, p, splen )) + { + Q_strcpy( out, p + splen + 1 ); + return true; + } + } + + Q_strcpy( out, p ); + return false; + } + + bool GetCurrentDirectory( char *p, int size ) override + { + Q_strncpy( p, fs_rootdir, size ); + + return true; + } + + void PrintOpenedFiles() override + { + // we don't track this yet + return; + } + + void SetWarningFunc(void (*)(const char *, ...)) override + { + // TODO: + return; + } + + void SetWarningLevel(FileWarningLevel_t) override + { + // TODO: + return; + } + + int SetVBuf( FileHandle_t handle, char *buf, int mode, long int size ) override + { + // TODO: + return 0; + } + + void GetInterfaceVersion(char *p, int size) override + { + Q_strncpy( p, "Stdio", size ); + } + + bool AddPackFile( const char *path, const char *id ) override + { + char *p = va( "%s/%s", IdToDir( id ), path ); + CopyAndFixSlashes( p, path ); + + return !!FS_AddPak_Fullpath( p, NULL, FS_CUSTOM_PATH ); + } + + FileHandle_t OpenFromCacheForRead( const char *path , const char *mode, const char *id ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + return FS_OpenReadFile( p, mode, IsIdGamedir( id )); + } + + // stubs + void Mount() override {} + void Unmount() override {} + void GetLocalCopy(const char *) override {} + void LogLevelLoadStarted(const char *) override {} + void LogLevelLoadFinished(const char *) override {} + void CancelWaitForResources(WaitForResourcesHandle_t) override {} + int HintResourceNeed(const char *, int) override { return 0; } + WaitForResourcesHandle_t WaitForResources(const char *) override { return 0; } + int PauseResourcePreloading() override { return 0; } + int ResumeResourcePreloading() override { return 0; } + bool IsAppReadyForOfflinePlay(int) override { return true; } + bool IsFileImmediatelyAvailable(const char *) override { return true; } + bool GetWaitForResourcesProgress(WaitForResourcesHandle_t, float *progress, bool *override) override + { + if( progress ) *progress = 0; + if( override ) *override = true; + return false; + } +} g_VFileSystem009; + +extern "C" void EXPORT *CreateInterface( const char *interface, int *retval ) +{ + if( !Q_strcmp( interface, "VFileSystem009" )) + { + if( retval ) *retval = 0; + return &g_VFileSystem009; + } + + if( retval ) *retval = 1; + return NULL; +} diff --git a/filesystem/filesystem.c b/filesystem/filesystem.c index 6f755d92..55298c6c 100644 --- a/filesystem/filesystem.c +++ b/filesystem/filesystem.c @@ -40,23 +40,22 @@ GNU General Public License for more details. #include "common/protocol.h" #define FILE_COPY_SIZE (1024 * 1024) + +fs_globals_t FI; qboolean fs_ext_path = false; // attempt to read\write from ./ or ../ pathes poolhandle_t fs_mempool; searchpath_t *fs_searchpaths = NULL; // chain char fs_rodir[MAX_SYSPATH]; char fs_rootdir[MAX_SYSPATH]; +char fs_writedir[MAX_SYSPATH]; // path that game allows to overwrite, delete and rename files (and create new of course) static searchpath_t fs_directpath; // static direct path static char fs_basedir[MAX_SYSPATH]; // base game directory static char fs_gamedir[MAX_SYSPATH]; // game current directory -static char fs_writedir[MAX_SYSPATH]; // path that game allows to overwrite, delete and rename files (and create new of course) #if !XASH_WIN32 static qboolean fs_caseinsensitive = true; // try to search missing files #endif -static fs_globals_t FI; -#define GI FI.GameInfo - #ifdef XASH_REDUCE_FD static file_t *fs_last_readfile; static zip_t *fs_last_zip; diff --git a/filesystem/filesystem_internal.h b/filesystem/filesystem_internal.h index c466b1db..b70cb77f 100644 --- a/filesystem/filesystem_internal.h +++ b/filesystem/filesystem_internal.h @@ -79,12 +79,16 @@ typedef struct searchpath_s struct searchpath_s *next; } searchpath_t; +extern fs_globals_t FI; extern searchpath_t *fs_searchpaths; extern poolhandle_t fs_mempool; extern fs_interface_t g_engfuncs; extern qboolean fs_ext_path; extern char fs_rodir[MAX_SYSPATH]; extern char fs_rootdir[MAX_SYSPATH]; +extern char fs_writedir[MAX_SYSPATH]; + +#define GI FI.GameInfo #define Mem_Malloc( pool, size ) g_engfuncs._Mem_Alloc( pool, size, false, __FILE__, __LINE__ ) #define Mem_Calloc( pool, size ) g_engfuncs._Mem_Alloc( pool, size, true, __FILE__, __LINE__ ) diff --git a/filesystem/wscript b/filesystem/wscript index bfcf35c0..b96d00d8 100644 --- a/filesystem/wscript +++ b/filesystem/wscript @@ -9,8 +9,8 @@ def configure(conf): def build(bld): bld.shlib(target = 'filesystem_stdio', - features = 'c', - source = bld.path.ant_glob(['*.c']), + features = 'cxx c', + source = bld.path.ant_glob(['*.c', '*.cpp']), includes = ['.', '../common', '../public', '../engine'], use = ['public'], install_path = bld.env.LIBDIR, From b26cd6cc94aaad82682a74dceb29d76b370d5fb3 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Mon, 1 Aug 2022 04:11:13 +0400 Subject: [PATCH 397/548] wscript: added /Zc:__cplusplus compiler flag for MSVC --- scripts/waifulib/compiler_optimizations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/waifulib/compiler_optimizations.py b/scripts/waifulib/compiler_optimizations.py index 77eb951a..3d653770 100644 --- a/scripts/waifulib/compiler_optimizations.py +++ b/scripts/waifulib/compiler_optimizations.py @@ -51,7 +51,7 @@ LINKFLAGS = { CFLAGS = { 'common': { # disable thread-safe local static initialization for C++11 code, as it cause crashes on Windows XP - 'msvc': ['/D_USING_V110_SDK71_', '/FS', '/Zc:threadSafeInit-', '/MT', '/MP'], + 'msvc': ['/D_USING_V110_SDK71_', '/FS', '/Zc:threadSafeInit-', '/MT', '/MP', '/Zc:__cplusplus'], 'clang': ['-g', '-gdwarf-2', '-fvisibility=hidden', '-fno-threadsafe-statics'], 'gcc': ['-g', '-fvisibility=hidden'], 'owcc': ['-fno-short-enum', '-ffloat-store', '-g3'] From a8674c18df242145f6fe9be5856a8442be2e65c3 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Mon, 1 Aug 2022 04:11:36 +0400 Subject: [PATCH 398/548] engine: platform: win32: fixed build for Windows --- engine/platform/win32/lib_win.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/platform/win32/lib_win.c b/engine/platform/win32/lib_win.c index b6cf9cf8..21ee1fab 100644 --- a/engine/platform/win32/lib_win.c +++ b/engine/platform/win32/lib_win.c @@ -391,7 +391,7 @@ qboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname dll_user_t *hInst; qboolean ret = FALSE; - hInst = COM_FindLibrary( name, directpath ); + hInst = FS_FindLibrary( name, directpath ); if ( !hInst ) return FALSE; data = FS_LoadFile( name, NULL, false ); @@ -439,7 +439,7 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d COM_ResetLibraryError(); - hInst = COM_FindLibrary( dllname, directpath ); + hInst = FS_FindLibrary( dllname, directpath ); if( !hInst ) { COM_PushLibraryError( va( "Failed to find library %s", dllname ) ); From fd7dba74cd54fd943850b720f9aba0d5cc82fd21 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Mon, 1 Aug 2022 04:12:35 +0400 Subject: [PATCH 399/548] filesystem: fixed build for Windows --- filesystem/VFileSystem009.cpp | 4 ++-- filesystem/filesystem.c | 10 ++++++++-- filesystem/pak.c | 3 +++ filesystem/wad.c | 2 ++ filesystem/zip.c | 2 ++ 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/filesystem/VFileSystem009.cpp b/filesystem/VFileSystem009.cpp index adac91bc..ad2a72c0 100644 --- a/filesystem/VFileSystem009.cpp +++ b/filesystem/VFileSystem009.cpp @@ -219,8 +219,8 @@ public: void FileTimeToString( char *p, int size, long int time ) override { - char *buf = ctime( &time ); - + time_t curtime = time; + char *buf = ctime( &curtime ); Q_strncpy( p, buf, size ); } diff --git a/filesystem/filesystem.c b/filesystem/filesystem.c index 55298c6c..ebe9c87e 100644 --- a/filesystem/filesystem.c +++ b/filesystem/filesystem.c @@ -1313,7 +1313,7 @@ static qboolean FS_FindLibrary( const char *dllname, qboolean directpath, fs_dll #if XASH_WIN32 && XASH_X86 // a1ba: custom loader is non-portable (I just don't want to touch it) Con_Printf( S_WARN "%s: loading libraries from packs is deprecated " "and will be removed in the future\n", __FUNCTION__ ); - *custom_loader = true; + dllInfo->custom_loader = true; #else Con_Printf( S_WARN "%s: loading libraries from packs is unsupported on " "this platform\n", __FUNCTION__ ); @@ -1390,8 +1390,9 @@ qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char int i; FS_InitMemory(); - +#if !XASH_WIN32 fs_caseinsensitive = caseinsensitive; +#endif Q_strncpy( fs_rootdir, rootdir, sizeof( fs_rootdir )); Q_strncpy( fs_gamedir, gamedir, sizeof( fs_gamedir )); @@ -1979,8 +1980,13 @@ int FS_Flush( file_t *file ) FS_Purge( file ); // sync +#if XASH_POSIX if( fsync( file->handle ) < 0 ) return EOF; +#else + if( fflush( file->handle ) < 0 ) + return EOF; +#endif return 0; } diff --git a/filesystem/pak.c b/filesystem/pak.c index 7dac5b8f..c0a8a911 100644 --- a/filesystem/pak.c +++ b/filesystem/pak.c @@ -14,10 +14,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +#include "build.h" #include #include #include +#if XASH_POSIX #include +#endif #include #include #include "port.h" diff --git a/filesystem/wad.c b/filesystem/wad.c index 9cbfa886..f741ff68 100644 --- a/filesystem/wad.c +++ b/filesystem/wad.c @@ -17,7 +17,9 @@ GNU General Public License for more details. #include #include #include +#if XASH_POSIX #include +#endif #include #include #include "port.h" diff --git a/filesystem/zip.c b/filesystem/zip.c index dcad291b..838928e0 100644 --- a/filesystem/zip.c +++ b/filesystem/zip.c @@ -17,7 +17,9 @@ GNU General Public License for more details. #include #include #include +#if XASH_POSIX #include +#endif #include #include #include STDINT_H From 3c4eec62aed833c79ed46e4f2e9c8ab116ec8d94 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 1 Aug 2022 13:25:51 +0300 Subject: [PATCH 400/548] engine: let engine find the filesystem library, if it wasn't loaded yet --- engine/common/lib_common.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/engine/common/lib_common.c b/engine/common/lib_common.c index 5b86df77..cdd3f8b6 100644 --- a/engine/common/lib_common.c +++ b/engine/common/lib_common.c @@ -91,9 +91,16 @@ dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) dll_user_t *p; fs_dllinfo_t dllInfo; - // no fs loaded, can't search + // no fs loaded yet, but let engine find fs if( !g_fsapi.FindLibrary ) - return NULL; + { + p = Mem_Calloc( host.mempool, sizeof( dll_user_t )); + Q_strncpy( p->shortPath, dllname, sizeof( p->shortPath )); + Q_strncpy( p->fullPath, dllname, sizeof( p->fullPath )); + Q_strncpy( p->dllName, dllname, sizeof( p->dllName )); + + return p; + } // fs can't find library if( !g_fsapi.FindLibrary( dllname, directpath, &dllInfo )) From b7b9c611cfc2e3ae38f807df20632d6d07c5dfbd Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 1 Aug 2022 13:34:44 +0300 Subject: [PATCH 401/548] filesystem: wscript: disable RTTI and exceptions(useful on Android), we don't need it here. Link as C++ library. --- filesystem/wscript | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/filesystem/wscript b/filesystem/wscript index b96d00d8..730facd2 100644 --- a/filesystem/wscript +++ b/filesystem/wscript @@ -4,12 +4,17 @@ def options(opt): pass def configure(conf): + nortti = { + 'msvc': ['/GR-'], + 'default': ['-fno-rtti', '-fno-exceptions'] + } + conf.env.append_unique('CXXFLAGS', conf.get_flags_by_compiler(nortti, conf.env.COMPILER_CC)) if conf.env.cxxshlib_PATTERN.startswith('lib'): conf.env.cxxshlib_PATTERN = conf.env.cxxshlib_PATTERN[3:] def build(bld): bld.shlib(target = 'filesystem_stdio', - features = 'cxx c', + features = 'cxx', source = bld.path.ant_glob(['*.c', '*.cpp']), includes = ['.', '../common', '../public', '../engine'], use = ['public'], From 067e9be37afaead171608b6727e511f6c0f16e9f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 1 Aug 2022 14:25:44 +0300 Subject: [PATCH 402/548] scripts: upgrade to Android NDK 25 --- scripts/gha/deps_android.sh | 4 ++-- scripts/waifulib/xcompile.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/gha/deps_android.sh b/scripts/gha/deps_android.sh index 75e70979..59b5c549 100755 --- a/scripts/gha/deps_android.sh +++ b/scripts/gha/deps_android.sh @@ -15,9 +15,9 @@ popd echo "Download all needed tools and NDK" yes | sdk/tools/bin/sdkmanager --licenses > /dev/null 2>/dev/null # who even reads licenses? :) sdk/tools/bin/sdkmanager --install build-tools\;29.0.1 platform-tools platforms\;android-29 > /dev/null 2>/dev/null -wget https://dl.google.com/android/repository/android-ndk-r23b-linux.zip -qO ndk.zip > /dev/null || exit 1 +wget https://dl.google.com/android/repository/android-ndk-r25-linux.zip -qO ndk.zip > /dev/null || exit 1 unzip -q ndk.zip || exit 1 -mv android-ndk-r23b sdk/ndk-bundle || exit 1 +mv android-ndk-r25 sdk/ndk-bundle || exit 1 echo "Download Xash3D FWGS Android source" git clone --depth 1 https://github.com/FWGS/xash3d-android-project -b waf android || exit 1 diff --git a/scripts/waifulib/xcompile.py b/scripts/waifulib/xcompile.py index 0abf53f1..b9ce398f 100644 --- a/scripts/waifulib/xcompile.py +++ b/scripts/waifulib/xcompile.py @@ -20,12 +20,12 @@ import os import sys ANDROID_NDK_ENVVARS = ['ANDROID_NDK_HOME', 'ANDROID_NDK'] -ANDROID_NDK_SUPPORTED = [10, 19, 20, 23] +ANDROID_NDK_SUPPORTED = [10, 19, 20, 23, 25] ANDROID_NDK_HARDFP_MAX = 11 # latest version that supports hardfp ANDROID_NDK_GCC_MAX = 17 # latest NDK that ships with GCC ANDROID_NDK_UNIFIED_SYSROOT_MIN = 15 ANDROID_NDK_SYSROOT_FLAG_MAX = 19 # latest NDK that need --sysroot flag -ANDROID_NDK_API_MIN = { 10: 3, 19: 16, 20: 16, 23: 16 } # minimal API level ndk revision supports +ANDROID_NDK_API_MIN = { 10: 3, 19: 16, 20: 16, 23: 16, 25: 19 } # minimal API level ndk revision supports ANDROID_STPCPY_API_MIN = 21 # stpcpy() introduced in SDK 21 ANDROID_64BIT_API_MIN = 21 # minimal API level that supports 64-bit targets From c025606739553f4e37af3f13c8e177e1eca9646f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 1 Aug 2022 14:33:36 +0300 Subject: [PATCH 403/548] engine: whereami: fix C89 in Android detection --- engine/common/whereami.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/engine/common/whereami.c b/engine/common/whereami.c index 79b01dcc..4d3401a1 100644 --- a/engine/common/whereami.c +++ b/engine/common/whereami.c @@ -295,6 +295,7 @@ int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) &&buffer[length - 3] == 'a' &&buffer[length - 4] == '.') { + char *begin, *p; int fd = open(path, O_RDONLY); if (fd == -1) { @@ -302,7 +303,7 @@ int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) break; } - char* begin = (char*)mmap(0, offset, PROT_READ, MAP_SHARED, fd, 0); + begin = (char*)mmap(0, offset, PROT_READ, MAP_SHARED, fd, 0); if (begin == MAP_FAILED) { close(fd); @@ -310,7 +311,7 @@ int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) break; } - char* p = begin + offset - 30; // minimum size of local file header + p = begin + offset - 30; // minimum size of local file header while (p >= begin) // scan backwards { if (*((uint32_t*)p) == 0x04034b50UL) // local file header signature found From 3eb4862794a0bcecdfb041ec04377cdad0b6ddcf Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 1 Aug 2022 15:06:54 +0300 Subject: [PATCH 404/548] common: remove unneeded typedef for wad file type --- common/xash3d_types.h | 1 - 1 file changed, 1 deletion(-) diff --git a/common/xash3d_types.h b/common/xash3d_types.h index 71472d43..33f82bb1 100644 --- a/common/xash3d_types.h +++ b/common/xash3d_types.h @@ -138,7 +138,6 @@ typedef unsigned int dword; typedef unsigned int uint; typedef char string[MAX_STRING]; typedef struct file_s file_t; // normal file -typedef struct wfile_s wfile_t; // wad file typedef struct stream_s stream_t; // sound stream for background music playing typedef off_t fs_offset_t; #if XASH_WIN32 From a5ba43ea39e980def67873eab6108e763b91cd30 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Tue, 2 Aug 2022 18:57:09 +0400 Subject: [PATCH 405/548] engine: fixed vulnerability in NAT bypass mechanism --- engine/server/sv_client.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c index 93a7ae4a..97fd122f 100644 --- a/engine/server/sv_client.c +++ b/engine/server/sv_client.c @@ -2268,10 +2268,14 @@ void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) else if( !Q_strcmp( pcmd, "i" )) NET_SendPacket( NS_SERVER, 5, "\xFF\xFF\xFF\xFFj", from ); // A2A_PING else if (!Q_strcmp( pcmd, "c" )) { - netadr_t to; + qboolean sv_nat = Cvar_VariableInteger( "sv_nat" ); + if( sv_nat ) + { + netadr_t to; - if( NET_StringToAdr( Cmd_Argv( 1 ), &to )) - SV_Info( to ); + if( NET_StringToAdr( Cmd_Argv( 1 ), &to ) && !NET_IsReservedAdr( to )) + SV_Info( to ); + } } else if( svgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) { From 3d5aa7c20ca3e50bffd399c14a5a001544d608a6 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 5 Aug 2022 18:05:15 +0300 Subject: [PATCH 406/548] engine: remove mistakingly placed NORETURN attributes --- engine/client/client.h | 2 +- engine/common/common.h | 2 +- engine/common/system.h | 2 +- engine/menu_int.h | 7 +------ engine/ref_api.h | 2 +- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/engine/client/client.h b/engine/client/client.h index cc6683df..fc4f9a9c 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -702,7 +702,7 @@ dlight_t *CL_GetEntityLight( int number ); // // cl_cmds.c // -void CL_Quit_f( void ) NORETURN; +void CL_Quit_f( void ); void CL_ScreenShot_f( void ); void CL_SnapShot_f( void ); void CL_PlayCDTrack_f( void ); diff --git a/engine/common/common.h b/engine/common/common.h index 58741bf3..b954dbd3 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -569,7 +569,7 @@ void Host_WriteConfig( void ); qboolean Host_IsLocalGame( void ); qboolean Host_IsLocalClient( void ); void Host_ShutdownServer( void ); -void Host_Error( const char *error, ... ) _format( 1 ) NORETURN; +void Host_Error( const char *error, ... ) _format( 1 ); void Host_PrintEngineFeatures( void ); void Host_Frame( float time ); void Host_InitDecals( void ); diff --git a/engine/common/system.h b/engine/common/system.h index 6a186602..c6d8956d 100644 --- a/engine/common/system.h +++ b/engine/common/system.h @@ -51,7 +51,7 @@ char *Sys_GetClipboardData( void ); const char *Sys_GetCurrentUser( void ); int Sys_CheckParm( const char *parm ); void Sys_Warn( const char *format, ... ) _format( 1 ); -void Sys_Error( const char *error, ... ) _format( 1 ) NORETURN; +void Sys_Error( const char *error, ... ) _format( 1 ); qboolean Sys_LoadLibrary( dll_info_t *dll ); void* Sys_GetProcAddress( dll_info_t *dll, const char* name ); qboolean Sys_FreeLibrary( dll_info_t *dll ); diff --git a/engine/menu_int.h b/engine/menu_int.h index 8a6905d6..d273d8f6 100644 --- a/engine/menu_int.h +++ b/engine/menu_int.h @@ -54,11 +54,6 @@ typedef struct ui_globalvars_s struct ref_viewpass_s; -#if __GNUC__ == 3 -#undef NORETURN -#define NORETURN -#endif // GCC 3.x have problems with noreturn attribute on function pointer - typedef struct ui_enginefuncs_s { // image handlers @@ -122,7 +117,7 @@ typedef struct ui_enginefuncs_s int (*CL_CreateVisibleEntity)( int type, struct cl_entity_s *ent ); // misc handlers - void (*pfnHostError)( const char *szFmt, ... ) _format( 1 ) NORETURN; + void (*pfnHostError)( const char *szFmt, ... ) _format( 1 ); int (*pfnFileExists)( const char *filename, int gamedironly ); void (*pfnGetGameDir)( char *szGetGameDir ); diff --git a/engine/ref_api.h b/engine/ref_api.h index 2f78b709..2b2e4ca1 100644 --- a/engine/ref_api.h +++ b/engine/ref_api.h @@ -339,7 +339,7 @@ typedef struct ref_api_s // utils void (*CL_ExtraUpdate)( void ); - void (*Host_Error)( const char *fmt, ... ) _format( 1 ) NORETURN; + void (*Host_Error)( const char *fmt, ... ) _format( 1 ); void (*COM_SetRandomSeed)( int lSeed ); float (*COM_RandomFloat)( float rmin, float rmax ); int (*COM_RandomLong)( int rmin, int rmax ); From bc00c6c54b4922066c21cfb2ed46d5c0ed35dcbd Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 26 Jul 2022 04:51:32 +0300 Subject: [PATCH 407/548] vgui_support: update --- vgui_support | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vgui_support b/vgui_support index 99108598..697de1ef 160000 --- a/vgui_support +++ b/vgui_support @@ -1 +1 @@ -Subproject commit 991085982209a1b8eefabae04d842004d4f4fe4f +Subproject commit 697de1efe2eea79d48118968d9e6a5a7e1117700 From d6dfeaeba7a8c2a436a8a9cad30335fef0f27145 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 6 Aug 2022 20:15:18 +0300 Subject: [PATCH 408/548] engine: fix searching filesystem_stdio on mobile platforms --- engine/common/common.h | 3 +-- engine/common/filesystem_engine.c | 9 ++++++++- engine/common/host.c | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/engine/common/common.h b/engine/common/common.h index b954dbd3..f70337e2 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -398,8 +398,7 @@ typedef void (*xcommand_t)( void ); // // filesystem_engine.c // -#define FILESYSTEM_STDIO_DLL "filesystem_stdio." OS_LIB_EXT -qboolean FS_LoadProgs( const char *name ); +qboolean FS_LoadProgs( void ); void FS_Init( void ); void FS_Shutdown( void ); diff --git a/engine/common/filesystem_engine.c b/engine/common/filesystem_engine.c index 42754134..6d3ff03e 100644 --- a/engine/common/filesystem_engine.c +++ b/engine/common/filesystem_engine.c @@ -56,8 +56,15 @@ static void FS_UnloadProgs( void ) fs_hInstance = 0; } -qboolean FS_LoadProgs( const char *name ) +#ifdef XASH_INTERNAL_GAMELIBS +#define FILESYSTEM_STDIO_DLL "filesystem_stdio" +#else +#define FILESYSTEM_STDIO_DLL "filesystem_stdio." OS_LIB_EXT +#endif + +qboolean FS_LoadProgs( void ) { + const char *name = FILESYSTEM_STDIO_DLL; FSAPI GetFSAPI; fs_hInstance = COM_LoadLibrary( name, false, true ); diff --git a/engine/common/host.c b/engine/common/host.c index 3f89d176..f259cdca 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -1033,7 +1033,7 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha return; } - FS_LoadProgs( FILESYSTEM_STDIO_DLL ); + FS_LoadProgs(); if( FS_SetCurrentDirectory( host.rootdir ) != 0 ) Con_Reportf( "%s is working directory now\n", host.rootdir ); From 33cbead4a46ae54f5bd0b844df7a57c46689c958 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 6 Aug 2022 20:16:04 +0300 Subject: [PATCH 409/548] filesystem: wscript: fix library name on Android --- filesystem/wscript | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/filesystem/wscript b/filesystem/wscript index 730facd2..8a4e421a 100644 --- a/filesystem/wscript +++ b/filesystem/wscript @@ -9,8 +9,10 @@ def configure(conf): 'default': ['-fno-rtti', '-fno-exceptions'] } conf.env.append_unique('CXXFLAGS', conf.get_flags_by_compiler(nortti, conf.env.COMPILER_CC)) - if conf.env.cxxshlib_PATTERN.startswith('lib'): - conf.env.cxxshlib_PATTERN = conf.env.cxxshlib_PATTERN[3:] + + if conf.env.DEST_OS != 'android': + if conf.env.cxxshlib_PATTERN.startswith('lib'): + conf.env.cxxshlib_PATTERN = conf.env.cxxshlib_PATTERN[3:] def build(bld): bld.shlib(target = 'filesystem_stdio', From 90c566dde3a8ec023eb31f7c2b5c01d74377ed8c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 6 Aug 2022 20:16:27 +0300 Subject: [PATCH 410/548] wscript: enable GCC's -Wmisleading-indentation --- wscript | 1 + 1 file changed, 1 insertion(+) diff --git a/wscript b/wscript index 6565e53c..07e49635 100644 --- a/wscript +++ b/wscript @@ -209,6 +209,7 @@ def configure(conf): # '-Werror=format=2', # '-Wdouble-promotion', # disable warning flood '-Wstrict-aliasing', + '-Wmisleading-indentation', ] c_compiler_optional_flags = [ From 10ad1c3b380b53aeb7cab341c423ff0410c4a43f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 6 Aug 2022 20:19:42 +0300 Subject: [PATCH 411/548] filesystem: use correct flushing function for file descriptors on Win32 --- filesystem/filesystem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filesystem/filesystem.c b/filesystem/filesystem.c index ebe9c87e..66a9b5ae 100644 --- a/filesystem/filesystem.c +++ b/filesystem/filesystem.c @@ -1984,7 +1984,7 @@ int FS_Flush( file_t *file ) if( fsync( file->handle ) < 0 ) return EOF; #else - if( fflush( file->handle ) < 0 ) + if( _commit( file->handle ) < 0 ) return EOF; #endif From 0d449370e0fee49bcf9df6684ea320eb764d6680 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 6 Aug 2022 20:33:01 +0300 Subject: [PATCH 412/548] filesystem: fixes for GCC 3 --- filesystem/VFileSystem009.cpp | 6 +++--- filesystem/filesystem_internal.h | 1 - filesystem/pak.c | 4 ++-- filesystem/wad.c | 4 ++-- filesystem/zip.c | 4 ++-- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/filesystem/VFileSystem009.cpp b/filesystem/VFileSystem009.cpp index ad2a72c0..ffcffe40 100644 --- a/filesystem/VFileSystem009.cpp +++ b/filesystem/VFileSystem009.cpp @@ -476,10 +476,10 @@ public: int ResumeResourcePreloading() override { return 0; } bool IsAppReadyForOfflinePlay(int) override { return true; } bool IsFileImmediatelyAvailable(const char *) override { return true; } - bool GetWaitForResourcesProgress(WaitForResourcesHandle_t, float *progress, bool *override) override + bool GetWaitForResourcesProgress(WaitForResourcesHandle_t, float *pProgress, bool *pOverride) override { - if( progress ) *progress = 0; - if( override ) *override = true; + if( pProgress ) *pProgress = 0; + if( pOverride ) *pOverride = true; return false; } } g_VFileSystem009; diff --git a/filesystem/filesystem_internal.h b/filesystem/filesystem_internal.h index b70cb77f..3cf5a462 100644 --- a/filesystem/filesystem_internal.h +++ b/filesystem/filesystem_internal.h @@ -28,7 +28,6 @@ typedef struct zip_s zip_t; typedef struct pack_s pack_t; typedef struct wfile_s wfile_t; - #define FILE_BUFF_SIZE (2048) struct file_s diff --git a/filesystem/pak.c b/filesystem/pak.c index c0a8a911..1cc0037a 100644 --- a/filesystem/pak.c +++ b/filesystem/pak.c @@ -63,14 +63,14 @@ typedef struct #define PAK_LOAD_NO_FILES 5 #define PAK_LOAD_CORRUPTED 6 -typedef struct pack_s +struct pack_s { string filename; int handle; int numfiles; time_t filetime; // common for all packed files dpackfile_t *files; -} pack_t; +}; /* ==================== diff --git a/filesystem/wad.c b/filesystem/wad.c index f741ff68..646476d3 100644 --- a/filesystem/wad.c +++ b/filesystem/wad.c @@ -69,7 +69,7 @@ typedef struct char name[WAD3_NAMELEN]; // must be null terminated } dlumpinfo_t; -typedef struct wfile_s +struct wfile_s { string filename; int infotableofs; @@ -78,7 +78,7 @@ typedef struct wfile_s file_t *handle; dlumpinfo_t *lumps; time_t filetime; -} wfile_t; +}; // WAD errors #define WAD_LOAD_OK 0 diff --git a/filesystem/zip.c b/filesystem/zip.c index 838928e0..829d1553 100644 --- a/filesystem/zip.c +++ b/filesystem/zip.c @@ -118,14 +118,14 @@ typedef struct zipfile_s unsigned short flags; } zipfile_t; -typedef struct zip_s +struct zip_s { string filename; int handle; int numfiles; time_t filetime; zipfile_t *files; -} zip_t; +}; #ifdef XASH_REDUCE_FD static void FS_EnsureOpenZip( zip_t *zip ) From 28d7f2eaa2f80856c876a40c3a06a49811cfafc1 Mon Sep 17 00:00:00 2001 From: Andrey Akhmichin <15944199+nekonomicon@users.noreply.github.com> Date: Sun, 7 Aug 2022 23:44:11 +0500 Subject: [PATCH 413/548] Documentation: not-supported-mod-list-and-reasons-why.md: update. --- Documentation/not-supported-mod-list-and-reasons-why.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Documentation/not-supported-mod-list-and-reasons-why.md b/Documentation/not-supported-mod-list-and-reasons-why.md index 89f834cc..f2ec68a2 100644 --- a/Documentation/not-supported-mod-list-and-reasons-why.md +++ b/Documentation/not-supported-mod-list-and-reasons-why.md @@ -7,6 +7,5 @@ |Counter Strike: Condition Zero |The latest steam release |Uses vgui2 library which xash3d does not support |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface |Counter Strike: Condition Zero - Deleted scenes |The latest steam release |Uses vgui2 library which xash3d does not support |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface |Day of Defeat |The latest steam release |Uses vgui2 library which xash3d does not support |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface -|Sven-Coop |5.0+ |Uses filesystem_stdio library which xash3d does not support |filesystem_stdio replacement already was made: https://github.com/FWGS/filesystem_stdio_xash -|XDM |3.0.4.0 | | +|Sven-Coop |5.0+ |Uses custom GoldSrc engine | From 85c55a7fc9dda57f05369b7791c84b9330b625d1 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Mon, 8 Aug 2022 00:23:38 -0700 Subject: [PATCH 414/548] update after merge, make it build and run with rasterizer have not checked ray tracing, or playing longer than a couple of minutes --- engine/client/cl_gameui.c | 2 +- engine/common/pm_local.h | 3 +++ ref_vk/vk_core.c | 2 +- ref_vk/vk_framectl.c | 4 ++-- ref_vk/vk_light.c | 2 +- ref_vk/vk_mapents.c | 2 +- ref_vk/vk_materials.c | 2 +- ref_vk/vk_studio.c | 2 +- ref_vk/vk_textures.c | 4 ++-- ref_vk/wscript | 1 + 10 files changed, 14 insertions(+), 10 deletions(-) diff --git a/engine/client/cl_gameui.c b/engine/client/cl_gameui.c index b5c56ac6..c4f51faf 100644 --- a/engine/client/cl_gameui.c +++ b/engine/client/cl_gameui.c @@ -1263,7 +1263,7 @@ static ui_extendedfuncs_t gExtendedfuncs = pfnGetRenderers, Sys_DoubleTime, pfnParseFileSafe, - NET_AdrToString + NET_AdrToString, R_GetRenderDevice }; diff --git a/engine/common/pm_local.h b/engine/common/pm_local.h index 6ad6d8e2..14a3129e 100644 --- a/engine/common/pm_local.h +++ b/engine/common/pm_local.h @@ -17,6 +17,9 @@ GNU General Public License for more details. #define PM_LOCAL_H #include "pm_defs.h" +#include "xash3d_mathlib.h" + +#include // memset typedef int (*pfnIgnore)( physent_t *pe ); // custom trace filter diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index 00e11d85..ef1d3c10 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -830,7 +830,7 @@ VkShaderModule loadShader(const char *filename) { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, }; VkShaderModule shader; - byte* buf = gEngine.COM_LoadFile( filename, &size, false); + byte* buf = gEngine.fsapi->LoadFile( filename, &size, false); uint32_t *pcode; if (!buf) diff --git a/ref_vk/vk_framectl.c b/ref_vk/vk_framectl.c index 3a7e5608..e48f4d89 100644 --- a/ref_vk/vk_framectl.c +++ b/ref_vk/vk_framectl.c @@ -672,7 +672,7 @@ qboolean VID_ScreenShot( const char *filename, int shot_type ) case VID_SCREENSHOT: break; case VID_SNAPSHOT: - gEngine.FS_AllowDirectPaths( true ); + gEngine.fsapi->AllowDirectPaths( true ); break; case VID_LEVELSHOT: flags |= IMAGE_RESAMPLE; @@ -703,7 +703,7 @@ qboolean VID_ScreenShot( const char *filename, int shot_type ) // write image result = gEngine.FS_SaveImage( filename, r_shot ); - gEngine.FS_AllowDirectPaths( false ); // always reset after store screenshot + gEngine.fsapi->AllowDirectPaths( false ); // always reset after store screenshot gEngine.FS_FreeImage( r_shot ); gEngine.Con_Printf("Wrote screenshot %s\n", filename); diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index e1916fa4..84576173 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -98,7 +98,7 @@ static void loadRadData( const model_t *map, const char *fmt, ... ) { vsnprintf( filename, sizeof filename, fmt, argptr ); va_end( argptr ); - buffer = gEngine.COM_LoadFile( filename, &size, false); + buffer = gEngine.fsapi->LoadFile( filename, &size, false); if (!buffer) { gEngine.Con_Printf(S_ERROR "Couldn't load RAD data from file %s, the map will be completely black\n", filename); diff --git a/ref_vk/vk_mapents.c b/ref_vk/vk_mapents.c index 38f4db8c..f7240c87 100644 --- a/ref_vk/vk_mapents.c +++ b/ref_vk/vk_mapents.c @@ -513,7 +513,7 @@ static void parsePatches( const model_t *const map ) { Q_snprintf(filename, sizeof(filename), "luchiki/%s.patch", map->name); gEngine.Con_Reportf("Loading patches from file \"%s\"\n", filename); - data = gEngine.COM_LoadFile( filename, 0, false ); + data = gEngine.fsapi->LoadFile( filename, 0, false ); if (!data) { gEngine.Con_Reportf("No patch file \"%s\"\n", filename); return; diff --git a/ref_vk/vk_materials.c b/ref_vk/vk_materials.c index d9308872..75ea261b 100644 --- a/ref_vk/vk_materials.c +++ b/ref_vk/vk_materials.c @@ -47,7 +47,7 @@ static void loadMaterialsFromFile( const char *filename, int depth ) { fs_offset_t size; const char *const path_begin = filename; const char *path_end = Q_strrchr(filename, '/'); - byte *data = gEngine.COM_LoadFile( filename, 0, false ); + byte *data = gEngine.fsapi->LoadFile( filename, 0, false ); char *pos = (char*)data; xvk_material_t current_material = { .base_color = -1, diff --git a/ref_vk/vk_studio.c b/ref_vk/vk_studio.c index ecfb4ae5..10865960 100644 --- a/ref_vk/vk_studio.c +++ b/ref_vk/vk_studio.c @@ -2314,7 +2314,7 @@ static model_t *R_StudioSetupPlayerModel( int index ) Q_snprintf( state->modelname, sizeof( state->modelname ), "models/player/%s/%s.mdl", info->model, info->model ); - if( gEngine.FS_FileExists( state->modelname, false )) + if( gEngine.fsapi->FileExists( state->modelname, false )) state->model = gEngine.Mod_ForName( state->modelname, false, true ); else state->model = NULL; diff --git a/ref_vk/vk_textures.c b/ref_vk/vk_textures.c index 8b3eab43..3ef58fe3 100644 --- a/ref_vk/vk_textures.c +++ b/ref_vk/vk_textures.c @@ -960,7 +960,7 @@ static int CheckSkybox( const char *name ) { // build side name sidename = va( "%s%s.%s", name, g_skybox_info[j].suffix, skybox_ext[i] ); - if( gEngine.FS_FileExists( sidename, false )) + if( gEngine.fsapi->FileExists( sidename, false )) num_checked_sides++; } @@ -972,7 +972,7 @@ static int CheckSkybox( const char *name ) { // build side name sidename = va( "%s_%s.%s", name, g_skybox_info[j].suffix, skybox_ext[i] ); - if( gEngine.FS_FileExists( sidename, false )) + if( gEngine.fsapi->FileExists( sidename, false )) num_checked_sides++; } diff --git a/ref_vk/wscript b/ref_vk/wscript index 272e06c9..9da62050 100644 --- a/ref_vk/wscript +++ b/ref_vk/wscript @@ -89,6 +89,7 @@ def build(bld): rtx_glsl_source = bld.path.ant_glob(['shaders/*.rgen', 'shaders/*.rchit', 'shaders/*.rmiss', 'shaders/*.rahit']) includes = ['.', + '../filesystem', '../engine', '../engine/common', '../engine/server', From 557d6c1ab6a85c867515a5b512c61c4d45368c46 Mon Sep 17 00:00:00 2001 From: NightFox <0x4E69676874466F78@users.noreply.github.com> Date: Fri, 12 Aug 2022 20:22:54 +0300 Subject: [PATCH 415/548] fix incorrect display device id in r_show_devices (R_GetRenderDevices_f) (#383) fixed #381 --- engine/client/ref_common.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/client/ref_common.c b/engine/client/ref_common.c index 22a3e364..16a8dc60 100644 --- a/engine/client/ref_common.c +++ b/engine/client/ref_common.c @@ -689,8 +689,8 @@ static void R_GetRenderDevices_f( void ) if( !device ) break; - Con_Printf( "%-3i %-4x:%-4x %-10s %s\n", - i, device->deviceID, device->vendorID, + Con_Printf( "%-3i %04x:%04x %-10s %s\n", + i, device->vendorID, device->deviceID, R_DeviceTypeToString( device->deviceType ), device->deviceName ); } From 91df1179980501df995f3468b247c39f1e4acc17 Mon Sep 17 00:00:00 2001 From: NightFox <0x4E69676874466F78@users.noreply.github.com> Date: Fri, 12 Aug 2022 20:27:41 +0300 Subject: [PATCH 416/548] new material paths (#382) --- ref_vk/vk_materials.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ref_vk/vk_materials.c b/ref_vk/vk_materials.c index 75ea261b..ed403cee 100644 --- a/ref_vk/vk_materials.c +++ b/ref_vk/vk_materials.c @@ -183,21 +183,21 @@ void XVK_ReloadMaterials( void ) { } loadMaterialsFromFile( "pbr/materials.mat", MAX_INCLUDE_DEPTH ); - loadMaterialsFromFile( "pbr/models/materials.mat", MAX_INCLUDE_DEPTH ); - loadMaterialsFromFile( "pbr/sprites/materials.mat", MAX_INCLUDE_DEPTH ); + loadMaterialsFromFile( "pbr/models/models.mat", MAX_INCLUDE_DEPTH ); + loadMaterialsFromFile( "pbr/sprites/sprites.mat", MAX_INCLUDE_DEPTH ); { const char *wad = g_map_entities.wadlist; for (; *wad;) { const char *const wad_end = Q_strchr(wad, ';'); - loadMaterialsFromFileF("pbr/%.*s/materials.mat", wad_end - wad, wad); + loadMaterialsFromFileF("pbr/%.*s/%.*s.mat", wad_end - wad, wad, wad_end - wad, wad); wad = wad_end + 1; } } { const model_t *map = gEngine.pfnGetModelByIndex( 1 ); - loadMaterialsFromFileF("pbr/%s/materials.mat", map->name); + loadMaterialsFromFileF("pbr/%s/%s.mat", map->name, COM_FileWithoutPath(map->name)); } } From 8ee68935d092edc7824a1202de1b9fde283295bc Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 13 Aug 2022 12:36:46 -0700 Subject: [PATCH 417/548] rt: do not print each material texture lookup --- ref_vk/vk_textures.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_vk/vk_textures.c b/ref_vk/vk_textures.c index 3ef58fe3..7a830af8 100644 --- a/ref_vk/vk_textures.c +++ b/ref_vk/vk_textures.c @@ -917,7 +917,7 @@ int XVK_TextureLookupF( const char *fmt, ...) { va_end( argptr ); tex_id = VK_FindTexture(buffer); - gEngine.Con_Reportf("Looked up texture %s -> %d\n", buffer, tex_id); + //gEngine.Con_Reportf("Looked up texture %s -> %d\n", buffer, tex_id); return tex_id; } From 202f2b846225649f150489ead56061f8c14eedc1 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 13 Aug 2022 13:15:17 -0700 Subject: [PATCH 418/548] rt: print material load stats --- ref_vk/profiler.h | 7 ++++--- ref_vk/vk_materials.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/ref_vk/profiler.h b/ref_vk/profiler.h index 19d8d1d9..e3d1a8b8 100644 --- a/ref_vk/profiler.h +++ b/ref_vk/profiler.h @@ -29,6 +29,7 @@ typedef int aprof_scope_id_t; aprof_scope_id_t aprof_scope_init(const char *scope_name); void aprof_scope_event(aprof_scope_id_t, int begin); void aprof_scope_frame( void ); +uint64_t aprof_time_now_ns( void ); typedef struct { const char *name; @@ -64,7 +65,7 @@ extern aprof_state_t g_aprof; #ifdef __linux__ #include -static uint64_t _aprof_time_now( void ) { +uint64_t aprof_time_now_ns( void ) { struct timespec tp; clock_gettime(CLOCK_MONOTONIC, &tp); return tp.tv_nsec + tp.tv_sec * 1000000000ull; @@ -74,7 +75,7 @@ static uint64_t _aprof_time_now( void ) { #define WIN32_EXTRA_LEAN #include static LARGE_INTEGER _aprof_frequency; -static uint64_t _aprof_time_now( void ) { +uint64_t aprof_time_now_ns( void ) { LARGE_INTEGER pc; QueryPerformanceCounter(&pc); return pc.QuadPart * 1000000000ull / _aprof_frequency.QuadPart; @@ -99,7 +100,7 @@ aprof_scope_id_t aprof_scope_init(const char *scope_name) { } void aprof_scope_event(aprof_scope_id_t scope_id, int begin) { - const uint64_t now = _aprof_time_now(); + const uint64_t now = aprof_time_now_ns(); if (scope_id < 0 || scope_id >= g_aprof.num_scopes) return; diff --git a/ref_vk/vk_materials.c b/ref_vk/vk_materials.c index ed403cee..eb3ae67d 100644 --- a/ref_vk/vk_materials.c +++ b/ref_vk/vk_materials.c @@ -2,6 +2,7 @@ #include "vk_textures.h" #include "vk_mapents.h" #include "vk_const.h" +#include "profiler.h" #include @@ -43,6 +44,14 @@ static void makePath(char *out, size_t out_size, const char *value, const char * #define MAKE_PATH(out, value) \ makePath(out, sizeof(out), value, path_begin, path_end) +static struct { + int mat_files_read; + int texture_lookups; + int texture_loads; + int texture_lookup_duration_ns; + int texture_load_duration_ns; +} g_stats; + static void loadMaterialsFromFile( const char *filename, int depth ) { fs_offset_t size; const char *const path_begin = filename; @@ -107,7 +116,10 @@ static void loadMaterialsFromFile( const char *filename, int depth ) { break; if (Q_stricmp(key, "for") == 0) { + const uint64_t lookup_begin_ns = aprof_time_now_ns(); current_material_index = XVK_FindTextureNamedLike(value); + g_stats.texture_lookup_duration_ns += aprof_time_now_ns() - lookup_begin_ns; + g_stats.texture_lookups++; create = false; } else if (Q_stricmp(key, "new") == 0) { current_material_index = XVK_CreateDummyTexture(value); @@ -147,18 +159,23 @@ static void loadMaterialsFromFile( const char *filename, int depth ) { MAKE_PATH(texture_path, value); if (tex_id_dest) { + const uint64_t load_begin_ns = aprof_time_now_ns(); const int tex_id = loadTexture(texture_path, force_reload); + g_stats.texture_load_duration_ns += aprof_time_now_ns() - load_begin_ns; + if (tex_id < 0) { gEngine.Con_Printf(S_ERROR "Failed to load texture \"%s\" for key \"%s\"\n", value, key); continue; } *tex_id_dest = tex_id; + g_stats.texture_loads++; } } } Mem_Free( data ); + g_stats.mat_files_read++; } static void loadMaterialsFromFileF( const char *fmt, ... ) { @@ -173,6 +190,9 @@ static void loadMaterialsFromFileF( const char *fmt, ... ) { } void XVK_ReloadMaterials( void ) { + memset(&g_stats, 0, sizeof(g_stats)); + const uint64_t begin_time_ns = aprof_time_now_ns(); + for (int i = 0; i < MAX_TEXTURES; ++i) { xvk_material_t *const mat = g_materials.materials + i; const vk_texture_t *const tex = findTexture( i ); @@ -199,6 +219,19 @@ void XVK_ReloadMaterials( void ) { const model_t *map = gEngine.pfnGetModelByIndex( 1 ); loadMaterialsFromFileF("pbr/%s/%s.mat", map->name, COM_FileWithoutPath(map->name)); } + + // Print out statistics + { + const int duration_ms = (aprof_time_now_ns() - begin_time_ns) / 1000000ull; + gEngine.Con_Printf("Loading materials took %dms, .mat files parsed: %d. Texture lookups: %d (%dms). Texture loads: %d (%dms).\n", + duration_ms, + g_stats.mat_files_read, + g_stats.texture_lookups, + (int)(g_stats.texture_lookup_duration_ns / 1000000ull), + g_stats.texture_loads, + (int)(g_stats.texture_load_duration_ns / 1000000ull) + ); + } } xvk_material_t* XVK_GetMaterialForTextureIndex( int tex_index ) { From a5efa71ecb3864f98f68325191d32a2057be9d57 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 30 Jul 2022 11:37:06 -0700 Subject: [PATCH 419/548] rt: clean vk_light a bit --- ref_vk/infotool.c | 2 +- ref_vk/vk_light.c | 165 ++-------------------------------------------- ref_vk/vk_light.h | 15 ++--- ref_vk/vk_rtx.c | 9 ++- ref_vk/vk_scene.c | 7 +- 5 files changed, 18 insertions(+), 180 deletions(-) diff --git a/ref_vk/infotool.c b/ref_vk/infotool.c index a7d61683..e5176e18 100644 --- a/ref_vk/infotool.c +++ b/ref_vk/infotool.c @@ -98,7 +98,7 @@ void XVK_CameraDebugPrintCenterEntity( void ) { cell_raw[1] - g_lights.map.grid_min_cell[1], cell_raw[2] - g_lights.map.grid_min_cell[2], }; - const int cell_index = R_LightCellIndex( light_cell ); + const int cell_index = RT_LightCellIndex( light_cell ); const vk_lights_cell_t *cell = (cell_index >= 0 && cell_index < MAX_LIGHT_CLUSTERS) ? g_lights.cells + cell_index : NULL; p += Q_snprintf(p, end - p, diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 84576173..f44d9417 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -20,7 +20,7 @@ #include "pmtrace.h" #define PROFILER_SCOPES(X) \ - X(finalize , "VK_LightsFrameFinalize"); \ + X(finalize , "RT_LightsFrameEnd"); \ X(emissive_surface, "VK_LightsAddEmissiveSurface"); \ X(static_lights, "add static lights"); \ X(dlights, "add dlights"); \ @@ -365,7 +365,7 @@ static void clusterBitMapShutdown( void ) { g_lights_tmp.clusters_bit_map = NULL; } -int R_LightCellIndex( const int light_cell[3] ) { +int RT_LightCellIndex( const int light_cell[3] ) { if (light_cell[0] < 0 || light_cell[1] < 0 || light_cell[2] < 0 || (light_cell[0] >= g_lights.map.grid_size[0]) || (light_cell[1] >= g_lights.map.grid_size[1]) @@ -522,7 +522,7 @@ void RT_LightsNewMapBegin( const struct model_s *map ) { } } -void RT_LightsFrameInit( void ) { +void RT_LightsFrameBegin( void ) { g_lights.num_polygons = g_lights.num_static.polygons; g_lights.num_point_lights = g_lights.num_static.point_lights; g_lights.num_polygon_vertices = g_lights.num_static.polygon_vertices; @@ -598,141 +598,6 @@ static qboolean canSurfaceLightAffectAABB(const model_t *mod, const msurface_t * return retval; } -#if 0 -void VK_LightsAddEmissiveSurface( const struct vk_render_geometry_s *geom, const matrix3x4 *transform_row, qboolean static_map ) { - APROF_SCOPE_BEGIN_EARLY(emissive_surface); - const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); - const int texture_num = geom->texture; // Animated texture - vk_emissive_surface_t *retval = NULL; - vec3_t emissive_color = {0}; - - ASSERT(texture_num >= 0); - ASSERT(texture_num < MAX_TEXTURES); - - // Only brush model surfaces are supported to be emissive. This is not _strictly_ necessary, but is a bit simpler. - if (!geom->surf) - goto fin; // TODO break? no surface means that model is not brush - - // Find out whether this surface is emissive - { - const int surface_index = geom->surf - world->surfaces; - const xvk_patch_surface_t *psurf = g_map_entities.patch.surfaces ? g_map_entities.patch.surfaces + surface_index : NULL; - - ASSERT(surface_index >= 0); - ASSERT(surface_index < world->numsurfaces); - - if (psurf && psurf->flags & Patch_Surface_Emissive) { - VectorCopy(psurf->emissive, emissive_color); - } else if (geom->material == kXVkMaterialEmissive) { - VectorCopy(geom->emissive, emissive_color); - } else if (g_lights.map.emissive_textures[texture_num].set) { - VectorCopy(g_lights.map.emissive_textures[texture_num].emissive, emissive_color); - } else { - goto fin; - } - - if (emissive_color[0] == 0 && emissive_color[1] == 0 && emissive_color[2] == 0) { - if (static_map) { - gEngine.Con_Reportf("Surface %d got zero emissive color, not adding as a light source\n", surface_index); - } - goto fin; - } - } - - if (g_lights.num_polygons >= 256) - goto fin; - - if (debug_dump_lights.enabled) { - const vk_texture_t *tex = findTexture(texture_num); - ASSERT(tex); - gEngine.Con_Reportf("surface light %d: %s (%f %f %f)\n", g_lights.num_polygons, tex->name, - emissive_color[0], - emissive_color[1], - emissive_color[2]); - } - - { - const vk_light_leaf_set_t *const leafs = static_map - ? getMapLeafsAffectedByMapSurface( geom->surf ) - : getMapLeafsAffectedByMovingSurface( geom->surf, transform_row ); - vk_emissive_surface_t *esurf = g_lights.polygons + g_lights.num_polygons; - - // Insert into emissive surfaces - esurf->kusok_index = geom->kusok_index; - VectorCopy(emissive_color, esurf->emissive); - Matrix3x4_Copy(esurf->transform, *transform_row); - - clusterBitMapClear(); - - // Iterate through each visible/potentially affected leaf to get a range of grid cells - for (int i = 0; i < leafs->num; ++i) { - const mleaf_t *const leaf = world->leafs + leafs->leafs[i]; - - const int min_x = floorf(leaf->minmaxs[0] / LIGHT_GRID_CELL_SIZE); - const int min_y = floorf(leaf->minmaxs[1] / LIGHT_GRID_CELL_SIZE); - const int min_z = floorf(leaf->minmaxs[2] / LIGHT_GRID_CELL_SIZE); - - const int max_x = floorf(leaf->minmaxs[3] / LIGHT_GRID_CELL_SIZE) + 1; - const int max_y = floorf(leaf->minmaxs[4] / LIGHT_GRID_CELL_SIZE) + 1; - const int max_z = floorf(leaf->minmaxs[5] / LIGHT_GRID_CELL_SIZE) + 1; - - const qboolean not_visible = static_map && !canSurfaceLightAffectAABB(world, geom->surf, esurf->emissive, leaf->minmaxs); - - if (debug_dump_lights.enabled) { - gEngine.Con_Reportf(" adding leaf %d (%d of %d) min=(%d, %d, %d), max=(%d, %d, %d) total=%d\n", - leaf->cluster, i, leafs->num, - min_x, min_y, min_z, - max_x, max_y, max_z, - (max_x - min_x) * (max_y - min_y) * (max_z - min_z) - ); - } - - if (not_visible) - continue; - - for (int x = min_x; x < max_x; ++x) - for (int y = min_y; y < max_y; ++y) - for (int z = min_z; z < max_z; ++z) { - const int cell[3] = { - x - g_lights.map.grid_min_cell[0], - y - g_lights.map.grid_min_cell[1], - z - g_lights.map.grid_min_cell[2] - }; - - const int cell_index = R_LightCellIndex( cell ); - if (cell_index < 0) - continue; - - if (clusterBitMapCheckOrSet( cell_index )) { - const float minmaxs[6] = { - x * LIGHT_GRID_CELL_SIZE, - y * LIGHT_GRID_CELL_SIZE, - z * LIGHT_GRID_CELL_SIZE, - (x+1) * LIGHT_GRID_CELL_SIZE, - (y+1) * LIGHT_GRID_CELL_SIZE, - (z+1) * LIGHT_GRID_CELL_SIZE, - }; - - if (static_map && !canSurfaceLightAffectAABB(world, geom->surf, esurf->emissive, minmaxs)) - continue; - - if (!addSurfaceLightToCell(cell_index, g_lights.num_polygons)) { - ERROR_THROTTLED(10, "Cluster %d,%d,%d(%d) ran out of emissive surfaces slots", - cell[0], cell[1], cell[2], cell_index); - } - } - } - } - - ++g_lights.num_polygons; - retval = esurf; - } - -fin: - APROF_SCOPE_END(emissive_surface); -} -#endif - static void addLightIndexToLeaf( const mleaf_t *leaf, int index ) { const int min_x = floorf(leaf->minmaxs[0] / LIGHT_GRID_CELL_SIZE); const int min_y = floorf(leaf->minmaxs[1] / LIGHT_GRID_CELL_SIZE); @@ -760,7 +625,7 @@ static void addLightIndexToLeaf( const mleaf_t *leaf, int index ) { z - g_lights.map.grid_min_cell[2] }; - const int cell_index = R_LightCellIndex( cell ); + const int cell_index = RT_LightCellIndex( cell ); if (cell_index < 0) continue; @@ -883,7 +748,7 @@ static int addSpotLight( const vk_light_entity_t *le, float radius, int lightsty return index; } -void R_LightAddFlashlight(const struct cl_entity_s *ent, qboolean local_player ) { +void RT_LightAddFlashlight(const struct cl_entity_s *ent, qboolean local_player ) { // parameters const float hack_attenuation = 0.1; float radius = 1.0; @@ -1048,22 +913,6 @@ void RT_LightsNewMapEnd( const struct model_s *map ) { processStaticPointLights(); -#if 0 - // Load static map model - { - matrix3x4 xform; - const vk_brush_model_t *const bmodel = map->cache.data; - ASSERT(bmodel); - Matrix3x4_LoadIdentity(xform); - - for (int i = 0; i < bmodel->render_model.num_geometries; ++i) { - const vk_render_geometry_t *geom = bmodel->render_model.geometries + i; - VK_LightsAddEmissiveSurface( geom, &xform, true ); - // TODO how to differentiate between this and non-emissive gEngine.Con_Printf(S_ERROR "Ran out of surface light slots, geom %d of %d\n", i, bmodel->render_model.num_geometries); - } - } -#endif - // Fix static counts { g_lights.num_static.polygons = g_lights.num_polygons; @@ -1093,7 +942,7 @@ qboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id ) { } } -void VK_LightsFrameFinalize( void ) { +void RT_LightsFrameEnd( void ) { APROF_SCOPE_BEGIN_EARLY(finalize); const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); @@ -1210,7 +1059,7 @@ static void addPolygonLightIndexToLeaf(const mleaf_t* leaf, int poly_index) { z - g_lights.map.grid_min_cell[2] }; - const int cell_index = R_LightCellIndex( cell ); + const int cell_index = RT_LightCellIndex( cell ); if (cell_index < 0) continue; diff --git a/ref_vk/vk_light.h b/ref_vk/vk_light.h index 545f3b97..14ffc7e8 100644 --- a/ref_vk/vk_light.h +++ b/ref_vk/vk_light.h @@ -93,22 +93,15 @@ struct model_s; void RT_LightsNewMapBegin( const struct model_s *map ); void RT_LightsNewMapEnd( const struct model_s *map ); -void RT_LightsFrameInit( void ); // TODO begin +void RT_LightsFrameBegin( void ); +void RT_LightsFrameEnd( void ); -// TODO there is an arguably better way to organize this. -// a. this only belongs to ray tracing mode -// b. kusochki now have emissive color, so it probably makes more sense to not store emissive -// separately in emissive surfaces. -struct vk_render_geometry_s; -void VK_LightsAddEmissiveSurface( const struct vk_render_geometry_s *geom, const matrix3x4 *transform_row, qboolean static_map ); qboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id ); -void VK_LightsFrameFinalize( void ); // TODO end - -int R_LightCellIndex( const int light_cell[3] ); +int RT_LightCellIndex( const int light_cell[3] ); struct cl_entity_s; -void R_LightAddFlashlight( const struct cl_entity_s *ent, qboolean local_player ); +void RT_LightAddFlashlight( const struct cl_entity_s *ent, qboolean local_player ); struct msurface_s; typedef struct rt_light_add_polygon_s { diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index cfe15ce9..9fbf3a4f 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -329,7 +329,7 @@ void VK_RayFrameBegin( void ) } // TODO shouldn't we do this in freeze models mode anyway? - RT_LightsFrameInit(); + RT_LightsFrameBegin(); } static void prepareTlas( VkCommandBuffer cmdbuf ) { @@ -421,7 +421,7 @@ static void uploadLights( void ) { } } - // Upload dynamic emissive kusochki + // Upload polygon lights { struct Lights *lights = g_ray_model_state.lights_buffer.mapped; ASSERT(g_lights.num_polygons <= MAX_EMISSIVE_KUSOCHKI); @@ -430,9 +430,6 @@ static void uploadLights( void ) { const rt_light_polygon_t *const src_poly = g_lights.polygons + i; struct PolygonLight *const dst_poly = lights->polygons + i; - //dst_ekusok->kusok_index = src_esurf->kusok_index; - //Matrix3x4_Copy(dst_ekusok->tx_row_x, src_esurf->transform); - Vector4Copy(src_poly->plane, dst_poly->plane); VectorCopy(src_poly->center, dst_poly->center); dst_poly->area = src_poly->area; @@ -740,6 +737,8 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) // ubo should contain two matrices // FIXME pass these matrices explicitly to let RTX module handle ubo itself + RT_LightsFrameEnd(); + g_rtx.frame_number++; // if (vk_core.debug) diff --git a/ref_vk/vk_scene.c b/ref_vk/vk_scene.c index e9d696be..e2d784f7 100644 --- a/ref_vk/vk_scene.c +++ b/ref_vk/vk_scene.c @@ -665,7 +665,7 @@ void VK_SceneRender( const ref_viewpass_t *rvp ) { { // Draw flashlight for local player if( FBitSet( local_player->curstate.effects, EF_DIMLIGHT )) { - R_LightAddFlashlight(local_player, true); + RT_LightAddFlashlight(local_player, true); } } @@ -678,7 +678,7 @@ void VK_SceneRender( const ref_viewpass_t *rvp ) { // Draw flashlight for other players if( FBitSet( ent->curstate.effects, EF_DIMLIGHT ) && ent != local_player) { - R_LightAddFlashlight(ent, false); + RT_LightAddFlashlight(ent, false); } } APROF_SCOPE_END(draw_opaques); @@ -713,9 +713,6 @@ void VK_SceneRender( const ref_viewpass_t *rvp ) { VK_RenderDebugLabelEnd(); - if (vk_core.rtx) - VK_LightsFrameFinalize(); - if (ui_infotool->value > 0) XVK_CameraDebugPrintCenterEntity(); From 1f741a48a3d8eb61611cdc2e1e553ed7e2df76e8 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 30 Jul 2022 11:51:32 -0700 Subject: [PATCH 420/548] rt: hide emissive_textures info --- ref_vk/vk_light.c | 20 +++++++++++++++++--- ref_vk/vk_light.h | 10 ---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index f44d9417..704c6452 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -30,6 +30,20 @@ PROFILER_SCOPES(SCOPE_DECLARE) #undef SCOPE_DECLARE +typedef struct { + vec3_t emissive; + qboolean set; +} vk_emissive_texture_t; + +static struct { + struct { + vk_emissive_texture_t emissive_textures[MAX_TEXTURES]; + } map; + + // vk_buffer_t staging; + // vk_buffer_t device; +} g_lights_; + static struct { qboolean enabled; char name_filter[256]; @@ -184,7 +198,7 @@ static void loadRadData( const model_t *map, const char *fmt, ... ) { } if (tex_id) { - vk_emissive_texture_t *const etex = g_lights.map.emissive_textures + tex_id; + vk_emissive_texture_t *const etex = g_lights_.map.emissive_textures + tex_id; ASSERT(tex_id < MAX_TEXTURES); etex->emissive[0] = r; @@ -495,7 +509,7 @@ void RT_LightsNewMapBegin( const struct model_s *map ) { prepareSurfacesLeafVisibilityCache( map ); // Load RAD data based on map name - memset(g_lights.map.emissive_textures, 0, sizeof(g_lights.map.emissive_textures)); + memset(g_lights_.map.emissive_textures, 0, sizeof(g_lights_.map.emissive_textures)); loadRadData( map, "maps/lights.rad" ); { @@ -932,7 +946,7 @@ qboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id ) { ASSERT(texture_id < MAX_TEXTURES); { - vk_emissive_texture_t *const etex = g_lights.map.emissive_textures + texture_id; + vk_emissive_texture_t *const etex = g_lights_.map.emissive_textures + texture_id; if (etex->set) { VectorCopy(etex->emissive, out); return true; diff --git a/ref_vk/vk_light.h b/ref_vk/vk_light.h index 14ffc7e8..3e3ccac7 100644 --- a/ref_vk/vk_light.h +++ b/ref_vk/vk_light.h @@ -3,14 +3,6 @@ #include "vk_const.h" #include "xash3d_types.h" -//#include "protocol.h" -//#include "const.h" -//#include "bspfile.h" - -typedef struct { - vec3_t emissive; - qboolean set; -} vk_emissive_texture_t; typedef struct { uint8_t num_point_lights; @@ -62,8 +54,6 @@ typedef struct { int grid_min_cell[3]; int grid_size[3]; int grid_cells; - - vk_emissive_texture_t emissive_textures[MAX_TEXTURES]; } map; int num_polygons; From 608bf020c4d7c63c631aaa6bdd3796b832a9d80d Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 30 Jul 2022 12:38:34 -0700 Subject: [PATCH 421/548] rt: move light metadata uploading to vk_light.c --- ref_vk/TODO.md | 12 +- ref_vk/shaders/light.glsl | 2 +- ref_vk/vk_light.c | 239 ++++++++++++++++++++++------------- ref_vk/vk_light.h | 6 +- ref_vk/vk_ray_internal.h | 13 -- ref_vk/vk_ray_light_direct.c | 2 +- ref_vk/vk_rtx.c | 65 +--------- 7 files changed, 171 insertions(+), 168 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index 6f9b48a2..de2b2227 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,12 +1,16 @@ # Parallel frames - [ ] allocate for N frames: - [x] geometries - - [ ] rt models - - [ ] kusochki - - [ ] same ring buffer alloc as for geometries - - [ ] extract as a unit + - [-] rt models + - [-] kusochki + - [x] same ring buffer alloc as for geometries + - [x] extract as a unit - [ ] tlas geom --//-- - [ ] lights + - [x] make metadata buffer in lights + - [ ] join lights grid+meta into a single buffer + - [ ] put lights data into a cpu-side vk buffer + - [ ] sync+barrier upload - scratch buffer: - should be fine (assuming intra-cmdbuf sync), contents lifetime is single frame only diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index 81d49a45..fac94c5e 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -1,4 +1,4 @@ -layout (set = 0, binding = BINDING_LIGHTS) uniform UBOLights { Lights lights; }; // TODO this is pretty much static and should be a buffer, not UBO +layout (set = 0, binding = BINDING_LIGHTS) readonly buffer UBOLights { Lights lights; }; // TODO this is pretty much static and should be a buffer, not UBO layout (set = 0, binding = BINDING_LIGHT_CLUSTERS, align = 1) readonly buffer UBOLightClusters { ivec3 grid_min, grid_size; //uint8_t clusters_data[MAX_LIGHT_CLUSTERS * LIGHT_CLUSTER_SIZE + HACK_OFFSET]; diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 704c6452..849bb540 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -1,10 +1,11 @@ #include "vk_light.h" +#include "vk_buffer.h" #include "vk_mapents.h" #include "vk_textures.h" -#include "vk_brush.h" #include "vk_lightmap.h" #include "vk_cvar.h" #include "vk_common.h" +#include "shaders/ray_interop.h" #include "profiler.h" #include "mod_local.h" @@ -40,8 +41,8 @@ static struct { vk_emissive_texture_t emissive_textures[MAX_TEXTURES]; } map; - // vk_buffer_t staging; - // vk_buffer_t device; + // TODO vk_buffer_t staging; + vk_buffer_t buffer; } g_lights_; static struct { @@ -60,15 +61,26 @@ static void debugDumpLights( void ) { vk_lights_t g_lights = {0}; -void VK_LightsInit( void ) { +qboolean VK_LightsInit( void ) { PROFILER_SCOPES(APROF_SCOPE_INIT); gEngine.Cmd_AddCommand("vk_lights_dump", debugDumpLights, "Dump all light sources for next frame"); + + if (!VK_BufferCreate("rt lights buffer", &g_lights_.buffer, sizeof(struct Lights), + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT /* | VK_BUFFER_USAGE_TRANSFER_DST_BIT */, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { + // FIXME complain, handle + return false; + } + + return true; } static void clusterBitMapShutdown( void ); void VK_LightsShutdown( void ) { + VK_BufferDestroy(&g_lights_.buffer); + gEngine.Cmd_RemoveCommand("vk_lights_dump"); clusterBitMapShutdown(); } @@ -956,90 +968,6 @@ qboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id ) { } } -void RT_LightsFrameEnd( void ) { - APROF_SCOPE_BEGIN_EARLY(finalize); - const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); - - if (g_lights.num_polygons > UINT8_MAX) { - ERROR_THROTTLED(10, "Too many emissive surfaces found: %d; some areas will be dark", g_lights.num_polygons); - g_lights.num_polygons = UINT8_MAX; - } - - /* for (int i = 0; i < MAX_ELIGHTS; ++i) { */ - /* const dlight_t *dlight = gEngine.GetEntityLight(i); */ - /* if (!addDlight(dlight)) { */ - /* ERROR_THROTTLED(10,"Too many elights, MAX_POINT_LIGHTS=%d", MAX_POINT_LIGHTS); */ - /* break; */ - /* } */ - /* } */ - - for (int i = 0; i < g_lights.num_point_lights; ++i) { - vk_point_light_t *const light = g_lights.point_lights + i; - if (light->lightstyle < 0 || light->lightstyle >= MAX_LIGHTSTYLES) - continue; - - { - const float scale = g_lightmap.lightstylevalue[light->lightstyle] / 255.f; - VectorScale(light->base_color, scale, light->color); - } - } - - APROF_SCOPE_BEGIN(dlights); - for (int i = 0; i < MAX_DLIGHTS; ++i) { - const dlight_t *dlight = gEngine.GetDynamicLight(i); - if( !dlight || dlight->die < gpGlobals->time || !dlight->radius ) - continue; - addDlight(dlight); - } - APROF_SCOPE_END(dlights); - - if (debug_dump_lights.enabled) { -#if 0 - // Print light grid stats - gEngine.Con_Reportf("Emissive surfaces found: %d\n", g_lights.num_polygons); - - { - #define GROUPSIZE 4 - int histogram[1 + (MAX_VISIBLE_SURFACE_LIGHTS + GROUPSIZE - 1) / GROUPSIZE] = {0}; - for (int i = 0; i < g_lights.map.grid_cells; ++i) { - const vk_lights_cell_t *cluster = g_lights.cells + i; - const int hist_index = cluster->num_polygons ? 1 + cluster->num_polygons / GROUPSIZE : 0; - histogram[hist_index]++; - } - - gEngine.Con_Reportf("Built %d light clusters. Stats:\n", g_lights.map.grid_cells); - gEngine.Con_Reportf(" 0: %d\n", histogram[0]); - for (int i = 1; i < ARRAYSIZE(histogram); ++i) - gEngine.Con_Reportf(" %d-%d: %d\n", - (i - 1) * GROUPSIZE, - i * GROUPSIZE - 1, - histogram[i]); - } - - { - int num_clusters_with_lights_in_range = 0; - for (int i = 0; i < g_lights.map.grid_cells; ++i) { - const vk_lights_cell_t *cluster = g_lights.cells + i; - if (cluster->num_polygons > 0) { - gEngine.Con_Reportf(" cluster %d: polygons=%d\n", i, cluster->num_polygons); - } - - for (int j = 0; j < cluster->num_polygons; ++j) { - const int index = cluster->polygons[j]; - if (index >= vk_rtx_light_begin->value && index < vk_rtx_light_end->value) { - ++num_clusters_with_lights_in_range; - } - } - } - - gEngine.Con_Reportf("Clusters with filtered lights: %d\n", num_clusters_with_lights_in_range); - } -#endif - } - - debug_dump_lights.enabled = false; - APROF_SCOPE_END(finalize); -} static void addPolygonLightIndexToLeaf(const mleaf_t* leaf, int poly_index) { const int min_x = floorf(leaf->minmaxs[0] / LIGHT_GRID_CELL_SIZE); @@ -1203,3 +1131,138 @@ int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { return g_lights.num_polygons++; } } + +const struct vk_buffer_s* VK_LightsUpload( VkCommandBuffer cmdbuf ) { + // Upload polygon lights + struct Lights *lights = g_lights_.buffer.mapped; + ASSERT(g_lights.num_polygons <= MAX_EMISSIVE_KUSOCHKI); + lights->num_polygons = g_lights.num_polygons; + for (int i = 0; i < g_lights.num_polygons; ++i) { + const rt_light_polygon_t *const src_poly = g_lights.polygons + i; + struct PolygonLight *const dst_poly = lights->polygons + i; + + Vector4Copy(src_poly->plane, dst_poly->plane); + VectorCopy(src_poly->center, dst_poly->center); + dst_poly->area = src_poly->area; + VectorCopy(src_poly->emissive, dst_poly->emissive); + + // TODO DEBUG_ASSERT + ASSERT(src_poly->vertices.count > 2); + ASSERT(src_poly->vertices.offset < 0xffffu); + ASSERT(src_poly->vertices.count < 0xffffu); + + ASSERT(src_poly->vertices.offset + src_poly->vertices.count < COUNTOF(lights->polygon_vertices)); + + dst_poly->vertices_count_offset = (src_poly->vertices.count << 16) | (src_poly->vertices.offset); + } + + lights->num_point_lights = g_lights.num_point_lights; + for (int i = 0; i < g_lights.num_point_lights; ++i) { + vk_point_light_t *const src = g_lights.point_lights + i; + struct PointLight *const dst = lights->point_lights + i; + + VectorCopy(src->origin, dst->origin_r); + dst->origin_r[3] = src->radius; + + VectorCopy(src->color, dst->color_stopdot); + dst->color_stopdot[3] = src->stopdot; + + VectorCopy(src->dir, dst->dir_stopdot2); + dst->dir_stopdot2[3] = src->stopdot2; + + dst->environment = !!(src->flags & LightFlag_Environment); + } + + // TODO static assert + ASSERT(sizeof(lights->polygon_vertices) >= sizeof(g_lights.polygon_vertices)); + for (int i = 0; i < g_lights.num_polygon_vertices; ++i) { + VectorCopy(g_lights.polygon_vertices[i], lights->polygon_vertices[i]); + } + + return &g_lights_.buffer; +} + +void RT_LightsFrameEnd( void ) { + APROF_SCOPE_BEGIN_EARLY(finalize); + const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); + + if (g_lights.num_polygons > UINT8_MAX) { + ERROR_THROTTLED(10, "Too many emissive surfaces found: %d; some areas will be dark", g_lights.num_polygons); + g_lights.num_polygons = UINT8_MAX; + } + + /* for (int i = 0; i < MAX_ELIGHTS; ++i) { */ + /* const dlight_t *dlight = gEngine.GetEntityLight(i); */ + /* if (!addDlight(dlight)) { */ + /* ERROR_THROTTLED(10,"Too many elights, MAX_POINT_LIGHTS=%d", MAX_POINT_LIGHTS); */ + /* break; */ + /* } */ + /* } */ + + for (int i = 0; i < g_lights.num_point_lights; ++i) { + vk_point_light_t *const light = g_lights.point_lights + i; + if (light->lightstyle < 0 || light->lightstyle >= MAX_LIGHTSTYLES) + continue; + + { + const float scale = g_lightmap.lightstylevalue[light->lightstyle] / 255.f; + VectorScale(light->base_color, scale, light->color); + } + } + + APROF_SCOPE_BEGIN(dlights); + for (int i = 0; i < MAX_DLIGHTS; ++i) { + const dlight_t *dlight = gEngine.GetDynamicLight(i); + if( !dlight || dlight->die < gpGlobals->time || !dlight->radius ) + continue; + addDlight(dlight); + } + APROF_SCOPE_END(dlights); + + if (debug_dump_lights.enabled) { +#if 0 + // Print light grid stats + gEngine.Con_Reportf("Emissive surfaces found: %d\n", g_lights.num_polygons); + + { + #define GROUPSIZE 4 + int histogram[1 + (MAX_VISIBLE_SURFACE_LIGHTS + GROUPSIZE - 1) / GROUPSIZE] = {0}; + for (int i = 0; i < g_lights.map.grid_cells; ++i) { + const vk_lights_cell_t *cluster = g_lights.cells + i; + const int hist_index = cluster->num_polygons ? 1 + cluster->num_polygons / GROUPSIZE : 0; + histogram[hist_index]++; + } + + gEngine.Con_Reportf("Built %d light clusters. Stats:\n", g_lights.map.grid_cells); + gEngine.Con_Reportf(" 0: %d\n", histogram[0]); + for (int i = 1; i < ARRAYSIZE(histogram); ++i) + gEngine.Con_Reportf(" %d-%d: %d\n", + (i - 1) * GROUPSIZE, + i * GROUPSIZE - 1, + histogram[i]); + } + + { + int num_clusters_with_lights_in_range = 0; + for (int i = 0; i < g_lights.map.grid_cells; ++i) { + const vk_lights_cell_t *cluster = g_lights.cells + i; + if (cluster->num_polygons > 0) { + gEngine.Con_Reportf(" cluster %d: polygons=%d\n", i, cluster->num_polygons); + } + + for (int j = 0; j < cluster->num_polygons; ++j) { + const int index = cluster->polygons[j]; + if (index >= vk_rtx_light_begin->value && index < vk_rtx_light_end->value) { + ++num_clusters_with_lights_in_range; + } + } + } + + gEngine.Con_Reportf("Clusters with filtered lights: %d\n", num_clusters_with_lights_in_range); + } +#endif + } + + debug_dump_lights.enabled = false; + APROF_SCOPE_END(finalize); +} diff --git a/ref_vk/vk_light.h b/ref_vk/vk_light.h index 3e3ccac7..2bd78a85 100644 --- a/ref_vk/vk_light.h +++ b/ref_vk/vk_light.h @@ -1,6 +1,7 @@ #pragma once #include "vk_const.h" +#include "vk_core.h" #include "xash3d_types.h" @@ -76,7 +77,7 @@ typedef struct { extern vk_lights_t g_lights; -void VK_LightsInit( void ); +qboolean VK_LightsInit( void ); void VK_LightsShutdown( void ); struct model_s; @@ -86,6 +87,9 @@ void RT_LightsNewMapEnd( const struct model_s *map ); void RT_LightsFrameBegin( void ); void RT_LightsFrameEnd( void ); +struct vk_buffer_s; +const struct vk_buffer_s* VK_LightsUpload( VkCommandBuffer ); + qboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id ); int RT_LightCellIndex( const int light_cell[3] ); diff --git a/ref_vk/vk_ray_internal.h b/ref_vk/vk_ray_internal.h index c2a1ab51..9bbf8ade 100644 --- a/ref_vk/vk_ray_internal.h +++ b/ref_vk/vk_ray_internal.h @@ -6,7 +6,6 @@ #define MAX_ACCELS 1024 #define MAX_KUSOCHKI 16384 -#define MAX_EMISSIVE_KUSOCHKI 256 #define MODEL_CACHE_SIZE 1024 #include "shaders/ray_interop.h" @@ -61,18 +60,6 @@ typedef struct { vk_buffer_t kusochki_buffer; r_debuffer_t kusochki_alloc; - // TODO this should really be a single uniform buffer for matrices and light data - - // Expected to be small (qualifies for uniform buffer) - // Two distinct modes: (TODO which?) - // - static map-only lighting: constant for the entire map lifetime. - // Could be joined with render buffer, if not for possible uniform buffer binding optimization. - // This is how it operates now. - // - fully dynamic lights: re-built each frame, so becomes similar to scratch_buffer and could be unified (same about uniform binding opt) - // This allows studio and other non-brush model to be emissive. - // Needs: STORAGE/UNIFORM_BUFFER - vk_buffer_t lights_buffer; - // Per-frame data that is accumulated between RayFrameBegin and End calls struct { int num_models; diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 5cc4cdcb..2dd3bbb8 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -10,7 +10,7 @@ X(4, indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ X(5, vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ X(6, all_textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES) \ - X(7, lights, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1) \ + X(7, lights, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ X(8, light_clusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ #define LIST_COMMON_BINDINGS(X) \ diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 9fbf3a4f..5c7411fa 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -420,54 +420,6 @@ static void uploadLights( void ) { memcpy(dst->polygons, src->polygons, sizeof(uint8_t) * src->num_polygons); } } - - // Upload polygon lights - { - struct Lights *lights = g_ray_model_state.lights_buffer.mapped; - ASSERT(g_lights.num_polygons <= MAX_EMISSIVE_KUSOCHKI); - lights->num_polygons = g_lights.num_polygons; - for (int i = 0; i < g_lights.num_polygons; ++i) { - const rt_light_polygon_t *const src_poly = g_lights.polygons + i; - struct PolygonLight *const dst_poly = lights->polygons + i; - - Vector4Copy(src_poly->plane, dst_poly->plane); - VectorCopy(src_poly->center, dst_poly->center); - dst_poly->area = src_poly->area; - VectorCopy(src_poly->emissive, dst_poly->emissive); - - // TODO DEBUG_ASSERT - ASSERT(src_poly->vertices.count > 2); - ASSERT(src_poly->vertices.offset < 0xffffu); - ASSERT(src_poly->vertices.count < 0xffffu); - - ASSERT(src_poly->vertices.offset + src_poly->vertices.count < COUNTOF(lights->polygon_vertices)); - - dst_poly->vertices_count_offset = (src_poly->vertices.count << 16) | (src_poly->vertices.offset); - } - - lights->num_point_lights = g_lights.num_point_lights; - for (int i = 0; i < g_lights.num_point_lights; ++i) { - vk_point_light_t *const src = g_lights.point_lights + i; - struct PointLight *const dst = lights->point_lights + i; - - VectorCopy(src->origin, dst->origin_r); - dst->origin_r[3] = src->radius; - - VectorCopy(src->color, dst->color_stopdot); - dst->color_stopdot[3] = src->stopdot; - - VectorCopy(src->dir, dst->dir_stopdot2); - dst->dir_stopdot2[3] = src->stopdot2; - - dst->environment = !!(src->flags & LightFlag_Environment); - } - - // TODO static assert - ASSERT(sizeof(lights->polygon_vertices) >= sizeof(g_lights.polygon_vertices)); - for (int i = 0; i < g_lights.num_polygon_vertices; ++i) { - VectorCopy(g_lights.polygon_vertices[i], lights->polygon_vertices[i]); - } - } } static void clearVkImage( VkCommandBuffer cmdbuf, VkImage image ) { @@ -604,7 +556,7 @@ static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int fr ubo->random_seed = (uint32_t)gEngine.COM_RandomLong(0, INT32_MAX); } -static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_args_t* args, int frame_index, const xvk_ray_frame_images_t *current_frame, float fov_angle_y) { +static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_args_t* args, int frame_index, const xvk_ray_frame_images_t *current_frame, float fov_angle_y, const vk_buffer_t* fixme_lights_buffer) { vk_ray_resources_t res = { .width = FRAME_WIDTH, .height = FRAME_HEIGHT, @@ -634,7 +586,7 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar RES_SET_SBUFFER_FULL(kusochki, g_ray_model_state.kusochki_buffer), RES_SET_SBUFFER_FULL(indices, args->geometry_data), RES_SET_SBUFFER_FULL(vertices, args->geometry_data), - RES_SET_SBUFFER_FULL(lights, g_ray_model_state.lights_buffer), + RES_SET_SBUFFER_FULL(lights, (*fixme_lights_buffer)), RES_SET_SBUFFER_FULL(light_clusters, g_rtx.light_grid_buffer), #undef RES_SET_SBUFFER_FULL #undef RES_SET_BUFFER @@ -670,7 +622,6 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar DEBUG_BEGIN(cmdbuf, "yay tracing"); - uploadLights(); prepareTlas(cmdbuf); prepareUniformBuffer(args, frame_index, fov_angle_y); @@ -738,6 +689,8 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) // FIXME pass these matrices explicitly to let RTX module handle ubo itself RT_LightsFrameEnd(); + const vk_buffer_t* const fixme_lights_buffer = VK_LightsUpload(cmdbuf); + uploadLights(); g_rtx.frame_number++; @@ -778,7 +731,7 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) clearVkImage( cmdbuf, current_frame->denoised.image ); blitImage( &blit_args ); } else { - performTracing( cmdbuf, args, (g_rtx.frame_number % 2), current_frame, args->fov_angle_y ); + performTracing( cmdbuf, args, (g_rtx.frame_number % 2), current_frame, args->fov_angle_y, fixme_lights_buffer); } } @@ -855,13 +808,6 @@ qboolean VK_RayInit( void ) } RT_RayModel_Clear(); - if (!VK_BufferCreate("ray lights_buffer", &g_ray_model_state.lights_buffer, sizeof(struct Lights), - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT /* | VK_BUFFER_USAGE_TRANSFER_DST_BIT */, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { - // FIXME complain, handle - return false; - } - if (!VK_BufferCreate("ray light_grid_buffer", &g_rtx.light_grid_buffer, sizeof(vk_ray_shader_light_grid), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT /* | VK_BUFFER_USAGE_TRANSFER_DST_BIT */, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { @@ -951,7 +897,6 @@ void VK_RayShutdown( void ) { VK_BufferDestroy(&g_rtx.accels_buffer); VK_BufferDestroy(&g_rtx.tlas_geom_buffer); VK_BufferDestroy(&g_ray_model_state.kusochki_buffer); - VK_BufferDestroy(&g_ray_model_state.lights_buffer); VK_BufferDestroy(&g_rtx.light_grid_buffer); VK_BufferDestroy(&g_rtx.uniform_buffer); From 1b834f37be6dd53304affe423fbe6208db085081 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 30 Jul 2022 14:16:24 -0700 Subject: [PATCH 422/548] rt: join metadata and grid buffers together (breaks validation) --- ref_vk/shaders/light.glsl | 35 ++- ref_vk/shaders/light_polygon.glsl | 42 +-- ref_vk/shaders/ray.rchit | 180 ------------- ref_vk/shaders/ray.rgen | 373 --------------------------- ref_vk/shaders/ray.rmiss | 18 -- ref_vk/shaders/ray_interop.h | 11 +- ref_vk/shaders/ray_light_direct.glsl | 1 - ref_vk/shaders/shadow.rchit | 15 -- ref_vk/vk_core.c | 2 + ref_vk/vk_light.c | 48 +++- ref_vk/vk_ray_light_direct.c | 1 - ref_vk/vk_rtx.c | 48 ---- 12 files changed, 82 insertions(+), 692 deletions(-) delete mode 100644 ref_vk/shaders/ray.rchit delete mode 100644 ref_vk/shaders/ray.rgen delete mode 100644 ref_vk/shaders/ray.rmiss delete mode 100644 ref_vk/shaders/shadow.rchit diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index fac94c5e..ec779483 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -1,9 +1,8 @@ -layout (set = 0, binding = BINDING_LIGHTS) readonly buffer UBOLights { Lights lights; }; // TODO this is pretty much static and should be a buffer, not UBO -layout (set = 0, binding = BINDING_LIGHT_CLUSTERS, align = 1) readonly buffer UBOLightClusters { +layout (set = 0, binding = BINDING_LIGHTS, align = 1) readonly buffer LightBuffer { + LightsMetadata metadata; ivec3 grid_min, grid_size; - //uint8_t clusters_data[MAX_LIGHT_CLUSTERS * LIGHT_CLUSTER_SIZE + HACK_OFFSET]; LightCluster clusters[MAX_LIGHT_CLUSTERS]; -} light_grid; +} lights; const float color_culling_threshold = 0;//600./color_factor; const float shadow_offset_fudge = .1; @@ -18,19 +17,19 @@ const float shadow_offset_fudge = .1; #if LIGHT_POINT void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { diffuse = specular = vec3(0.); - const uint num_point_lights = uint(light_grid.clusters[cluster_index].num_point_lights); + const uint num_point_lights = uint(lights.clusters[cluster_index].num_point_lights); for (uint j = 0; j < num_point_lights; ++j) { - const uint i = uint(light_grid.clusters[cluster_index].point_lights[j]); + const uint i = uint(lights.clusters[cluster_index].point_lights[j]); - vec3 color = lights.point_lights[i].color_stopdot.rgb * throughput; + vec3 color = lights.metadata.point_lights[i].color_stopdot.rgb * throughput; if (dot(color,color) < color_culling_threshold) continue; - const vec4 origin_r = lights.point_lights[i].origin_r; - const float stopdot = lights.point_lights[i].color_stopdot.a; - const vec3 dir = lights.point_lights[i].dir_stopdot2.xyz; - const float stopdot2 = lights.point_lights[i].dir_stopdot2.a; - const bool not_environment = (lights.point_lights[i].environment == 0); + const vec4 origin_r = lights.metadata.point_lights[i].origin_r; + const float stopdot = lights.metadata.point_lights[i].color_stopdot.a; + const vec3 dir = lights.metadata.point_lights[i].dir_stopdot2.xyz; + const float stopdot2 = lights.metadata.point_lights[i].dir_stopdot2.a; + const bool not_environment = (lights.metadata.point_lights[i].environment == 0); const vec3 light_dir = not_environment ? (origin_r.xyz - P) : -dir; // TODO need to randomize sampling direction for environment soft shadow const float radius = origin_r.w; @@ -109,19 +108,19 @@ void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec void computeLighting(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { diffuse = specular = vec3(0.); - const ivec3 light_cell = ivec3(floor(P / LIGHT_GRID_CELL_SIZE)) - light_grid.grid_min; - const uint cluster_index = uint(dot(light_cell, ivec3(1, light_grid.grid_size.x, light_grid.grid_size.x * light_grid.grid_size.y))); + const ivec3 light_cell = ivec3(floor(P / LIGHT_GRID_CELL_SIZE)) - lights.grid_min; + const uint cluster_index = uint(dot(light_cell, ivec3(1, lights.grid_size.x, lights.grid_size.x * lights.grid_size.y))); - if (any(greaterThanEqual(light_cell, light_grid.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) + if (any(greaterThanEqual(light_cell, lights.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) return; // throughput * vec3(1., 0., 0.); // const uint cluster_offset = cluster_index * LIGHT_CLUSTER_SIZE + HACK_OFFSET; - // const int num_dlights = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_DLIGHTS_OFFSET]); - // const int num_emissive_surfaces = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_EMISSIVE_SURFACES_OFFSET]); + // const int num_dlights = int(lights.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_DLIGHTS_OFFSET]); + // const int num_emissive_surfaces = int(lights.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_EMISSIVE_SURFACES_OFFSET]); // const uint emissive_surfaces_offset = cluster_offset + LIGHT_CLUSTER_EMISSIVE_SURFACES_DATA_OFFSET; //C = vec3(float(num_emissive_surfaces)); - //C = vec3(float(int(light_grid.clusters[cluster_index].num_emissive_surfaces))); + //C = vec3(float(int(lights.clusters[cluster_index].num_emissive_surfaces))); //C += .3 * fract(vec3(light_cell) / 4.); #if LIGHT_POLYGON diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index c4933724..2d21f718 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -28,7 +28,7 @@ vec4 getPolygonLightSampleSimple(vec3 P, vec3 view_dir, const PolygonLight poly) vertices_count = 3; // FIXME for (uint i = 0; i < vertices_count; ++i) { - v[i] = lights.polygon_vertices[vertices_offset + i].xyz; + v[i] = lights.metadata.polygon_vertices[vertices_offset + i].xyz; } vec2 rnd = vec2(sqrt(rand01()), rand01()); @@ -49,12 +49,12 @@ vec4 getPolygonLightSampleSimpleSolid(vec3 P, vec3 view_dir, const PolygonLight float total_contrib = 0.; float eps1 = rand01(); vec3 v[3]; - v[0] = normalize(lights.polygon_vertices[vertices_offset + 0].xyz - P); - v[1] = normalize(lights.polygon_vertices[vertices_offset + 1].xyz - P); + v[0] = normalize(lights.metadata.polygon_vertices[vertices_offset + 0].xyz - P); + v[1] = normalize(lights.metadata.polygon_vertices[vertices_offset + 1].xyz - P); const float householder_sign = (v[0].x > 0.0f) ? -1.0f : 1.0f; const vec2 householder_yz = v[0].yz * (1.0f / (abs(v[0].x) + 1.0f)); for (uint i = 2; i < vertices_count; ++i) { - v[2] = normalize(lights.polygon_vertices[vertices_offset + i].xyz - P); + v[2] = normalize(lights.metadata.polygon_vertices[vertices_offset + i].xyz - P); // effectively mindlessly copypasted from polygon_sampling.glsl, Peters 2021 // https://github.com/MomentsInGraphics/vulkan_renderer/blob/main/src/shaders/polygon_sampling.glsl @@ -98,9 +98,9 @@ vec4 getPolygonLightSampleSimpleSolid(vec3 P, vec3 view_dir, const PolygonLight rnd.x = 1.f - rnd.x; const vec3 light_dir = baryMix( - lights.polygon_vertices[vertices_offset + 0].xyz, - lights.polygon_vertices[vertices_offset + selected - 1].xyz, - lights.polygon_vertices[vertices_offset + selected].xyz, + lights.metadata.polygon_vertices[vertices_offset + 0].xyz, + lights.metadata.polygon_vertices[vertices_offset + selected - 1].xyz, + lights.metadata.polygon_vertices[vertices_offset + selected].xyz, rnd) - P; const vec3 light_dir_n = normalize(light_dir); return vec4(light_dir_n, total_contrib); @@ -113,7 +113,7 @@ vec4 getPolygonLightSampleProjected(vec3 view_dir, SampleContext ctx, const Poly uint vertices_count = poly.vertices_count_offset >> 16; for (uint i = 0; i < vertices_count; ++i) { - clipped[i] = ctx.world_to_shading * vec4(lights.polygon_vertices[vertices_offset + i].xyz, 1.); + clipped[i] = ctx.world_to_shading * vec4(lights.metadata.polygon_vertices[vertices_offset + i].xyz, 1.); } vertices_count = clip_polygon(vertices_count, clipped); @@ -138,7 +138,7 @@ vec4 getPolygonLightSampleSolid(vec3 P, vec3 view_dir, SampleContext ctx, const uint vertices_count = poly.vertices_count_offset >> 16; for (uint i = 0; i < vertices_count; ++i) { - clipped[i] = lights.polygon_vertices[vertices_offset + i].xyz; + clipped[i] = lights.metadata.polygon_vertices[vertices_offset + i].xyz; } #define DONT_CLIP @@ -191,14 +191,14 @@ void sampleSinglePolygonLight(in vec3 P, in vec3 N, in vec3 view_dir, in SampleC #if 0 // Sample random one void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { - const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); + const uint num_polygons = uint(lights.clusters[cluster_index].num_polygons); if (num_polygons == 0) return; - const uint selected = uint(light_grid.clusters[cluster_index].polygons[rand_range(num_polygons)]); + const uint selected = uint(lights.clusters[cluster_index].polygons[rand_range(num_polygons)]); - const PolygonLight poly = lights.polygons[selected]; + const PolygonLight poly = lights.metadata.polygons[selected]; const SampleContext ctx = buildSampleContext(P, N, view_dir); sampleSinglePolygonLight(P, N, view_dir, ctx, material, poly, diffuse, specular); @@ -211,10 +211,10 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { #if DO_ALL_IN_CLUSTER const SampleContext ctx = buildSampleContext(P, N, view_dir); - const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); + const uint num_polygons = uint(lights.clusters[cluster_index].num_polygons); for (uint i = 0; i < num_polygons; ++i) { - const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); - const PolygonLight poly = lights.polygons[index]; + const uint index = uint(lights.clusters[cluster_index].polygons[i]); + const PolygonLight poly = lights.metadata.polygons[index]; const float plane_dist = dot(poly.plane, vec4(P, 1.f)); @@ -251,9 +251,9 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate #define USE_CLUSTERS #ifdef USE_CLUSTERS // TODO move this to pickPolygonLight function - const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); + const uint num_polygons = uint(lights.clusters[cluster_index].num_polygons); #else - const uint num_polygons = lights.num_polygons; + const uint num_polygons = lights.metadata.num_polygons; #endif uint selected = 0; @@ -261,12 +261,12 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate float eps1 = rand01(); for (uint i = 0; i < num_polygons; ++i) { #ifdef USE_CLUSTERS - const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); + const uint index = uint(lights.clusters[cluster_index].polygons[i]); #else const uint index = i; #endif - const PolygonLight poly = lights.polygons[index]; + const PolygonLight poly = lights.metadata.polygons[index]; const vec3 dir = poly.center - P; const vec3 light_dir = normalize(dir); @@ -293,7 +293,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate } #if 0 - const PolygonLight poly = lights.polygons[selected - 1]; + const PolygonLight poly = lights.metadata.polygons[selected - 1]; const vec3 emissive = poly.emissive; vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); evalSplitBRDF(N, normalize(poly.center-P), view_dir, material, poly_diffuse, poly_specular); @@ -301,7 +301,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate specular += throughput * emissive * total_contrib; #else const SampleContext ctx = buildSampleContext(P, N, view_dir); - const PolygonLight poly = lights.polygons[selected - 1]; + const PolygonLight poly = lights.metadata.polygons[selected - 1]; #ifdef PROJECTED const vec4 light_sample_dir = getPolygonLightSampleProjected(view_dir, ctx, poly); #else diff --git a/ref_vk/shaders/ray.rchit b/ref_vk/shaders/ray.rchit deleted file mode 100644 index e87837b5..00000000 --- a/ref_vk/shaders/ray.rchit +++ /dev/null @@ -1,180 +0,0 @@ -#version 460 core -#extension GL_EXT_nonuniform_qualifier : enable -#extension GL_GOOGLE_include_directive : require - -#include "ray_kusochki.glsl" -#include "ray_common.glsl" - -layout (set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; -layout (set = 0, binding = 13) uniform samplerCube skybox; - -layout(location = PAYLOAD_LOCATION_OPAQUE) rayPayloadInEXT RayPayloadOpaque payload; - -layout (push_constant) uniform PC_ { - PushConstants push_constants; -}; - -hitAttributeEXT vec2 bary; - -// FIXME implement more robust self-intersection avoidance (see chap 6 of "Ray Tracing Gems") -const float normal_offset_fudge = .001; - -// Taken from Journal of Computer Graphics Techniques, Vol. 10, No. 1, 2021. -// Improved Shader and Texture Level of Detail Using Ray Cones, -// by T. Akenine-Moller, C. Crassin, J. Boksansky, L. Belcour, A. Panteleev, and O. Wright -// https://jcgt.org/published/0010/01/01/ -// P is the intersection point -// f is the triangle normal -// d is the ray cone direction -void computeAnisotropicEllipseAxes(in vec3 P, in vec3 f, - in vec3 d, in float rayConeRadiusAtIntersection, - in vec3 positions[3], in vec2 txcoords[3], - in vec2 interpolatedTexCoordsAtIntersection, - out vec2 texGradient1, out vec2 texGradient2) -{ - // Compute ellipse axes. - vec3 a1 = d - dot(f, d) * f; - vec3 p1 = a1 - dot(d, a1) * d; - a1 *= rayConeRadiusAtIntersection / max(0.0001, length(p1)); - vec3 a2 = cross(f, a1); - vec3 p2 = a2 - dot(d, a2) * d; - a2 *= rayConeRadiusAtIntersection / max(0.0001, length(p2)); - // Compute texture coordinate gradients. - vec3 eP, delta = P - positions[0]; - vec3 e1 = positions[1] - positions[0]; - vec3 e2 = positions[2] - positions[0]; - float oneOverAreaTriangle = 1.0 / dot(f, cross(e1, e2)); - eP = delta + a1; - float u1 = dot(f, cross(eP, e2)) * oneOverAreaTriangle; - float v1 = dot(f, cross(e1, eP)) * oneOverAreaTriangle; - texGradient1 = (1.0-u1-v1) * txcoords[0] + u1 * txcoords[1] + - v1 * txcoords[2] - interpolatedTexCoordsAtIntersection; - eP = delta + a2; - float u2 = dot(f, cross(eP, e2)) * oneOverAreaTriangle; - float v2 = dot(f, cross(e1, eP)) * oneOverAreaTriangle; - texGradient2 = (1.0-u2-v2) * txcoords[0] + u2 * txcoords[1] + - v2 * txcoords[2] - interpolatedTexCoordsAtIntersection; -} - -vec4 sampleTexture(uint tex_index, vec2 uv, vec4 uv_lods) { - return textureGrad(textures[nonuniformEXT(tex_index)], uv, uv_lods.xy, uv_lods.zw); -} - -vec3 baryMix(vec3 v1, vec3 v2, vec3 v3, vec2 bary) { - return v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y; -} - -void main() { - payload.t_offset += gl_HitTEXT; - - const int instance_kusochki_offset = gl_InstanceCustomIndexEXT; - const int kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT; - const Kusok kusok = kusochki[kusok_index]; - - const uint first_index_offset = kusok.index_offset + gl_PrimitiveID * 3; - - const uint vi1 = uint(indices[first_index_offset+0]) + kusok.vertex_offset; - const uint vi2 = uint(indices[first_index_offset+1]) + kusok.vertex_offset; - const uint vi3 = uint(indices[first_index_offset+2]) + kusok.vertex_offset; - - const vec3 pos[3] = { - gl_ObjectToWorldEXT * vec4(vertices[vi1].pos, 1.), - gl_ObjectToWorldEXT * vec4(vertices[vi2].pos, 1.), - gl_ObjectToWorldEXT * vec4(vertices[vi3].pos, 1.), - }; - // This one is supposed to be numerically better, but I can't see why - const vec3 hit_pos = pos[0] * (1. - bary.x - bary.y) + pos[1] * bary.x + pos[2] * bary.y; - //const vec3 hit_pos = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT - - const uint tex_base_color = kusok.tex_base_color; - if ((tex_base_color & KUSOK_MATERIAL_FLAG_SKYBOX) != 0) { - payload.hit_pos_t = vec4(hit_pos, gl_HitTEXT); - payload.base_color = vec3(0.); - payload.transmissiveness = 0.; - payload.normal = vec3(0.); - payload.geometry_normal = vec3(0.); - payload.kusok_index = -1; - payload.roughness = 0.; - payload.metalness = 0.; - payload.material_index = tex_base_color; - - // HACK: skyboxes are LDR now. They will look really dull after tonemapping - // We need to remaster them into HDR. While that is not done, we just tune them with pow(x, 2.2) which looks okay-ish - // See #230 - payload.emissive = pow(texture(skybox, gl_WorldRayDirectionEXT).rgb, vec3(2.2)); - return; - } - - const vec3 n1 = vertices[vi1].normal; - const vec3 n2 = vertices[vi2].normal; - const vec3 n3 = vertices[vi3].normal; - - // TODO use already inverse gl_WorldToObject ? - const mat3 matWorldRotation = mat3(gl_ObjectToWorldEXT); - const mat3 normalTransformMat = transpose(inverse(matWorldRotation)); - vec3 normal = normalize(normalTransformMat * (n1 * (1. - bary.x - bary.y) + n2 * bary.x + n3 * bary.y)); - - const vec2 uvs[3] = { - vertices[vi1].gl_tc, - vertices[vi2].gl_tc, - vertices[vi3].gl_tc, - }; - - const vec2 texture_uv_stationary = vertices[vi1].gl_tc * (1. - bary.x - bary.y) + vertices[vi2].gl_tc * bary.x + vertices[vi3].gl_tc * bary.y; - const vec2 texture_uv = texture_uv_stationary + push_constants.time * kusok.uv_speed; - - const vec3 real_geom_normal = normalize(cross(pos[2]-pos[0], pos[1]-pos[0])); - const float geom_normal_sign = sign(dot(real_geom_normal, -gl_WorldRayDirectionEXT)); - const vec3 geom_normal = geom_normal_sign * real_geom_normal; - - const float ray_cone_width = payload.pixel_cone_spread_angle * payload.t_offset; - vec4 uv_lods; - computeAnisotropicEllipseAxes(hit_pos, /* TODO geom_?*/ normal, gl_WorldRayDirectionEXT, ray_cone_width, pos, uvs, texture_uv_stationary, uv_lods.xy, uv_lods.zw); - - const uint tex_index = tex_base_color; - const vec4 tex_color = sampleTexture(tex_index, texture_uv, uv_lods); - //const vec3 base_color = pow(tex_color.rgb, vec3(2.)); - const vec3 base_color = ((push_constants.flags & PUSH_FLAG_LIGHTMAP_ONLY) != 0) ? vec3(1.) : tex_color.rgb;// pow(tex_color.rgb, vec3(2.)); - /* tex_color = pow(tex_color, vec4(2.)); */ - /* const vec3 base_color = tex_color.rgb; */ - - normal *= geom_normal_sign; - const uint tex_normal = kusok.tex_normalmap; - vec3 T = baryMix(vertices[vi1].tangent, vertices[vi2].tangent, vertices[vi3].tangent, bary); - if (tex_normal > 0 && dot(T,T) > .5) { - T = normalize(normalTransformMat * T); - T = normalize(T - dot(T, normal) * normal); - const vec3 B = normalize(cross(normal, T)); - const mat3 TBN = mat3(T, B, normal); - const vec3 tnorm = sampleTexture(tex_normal, texture_uv, uv_lods).xyz * 2. - 1.; // TODO is this sampling correct for normal data? - normal = normalize(TBN * tnorm); - } - - // FIXME read alpha from texture - - payload.hit_pos_t = vec4(hit_pos + geom_normal * normal_offset_fudge, gl_HitTEXT); - payload.base_color = base_color * kusok.color.rgb; - payload.transmissiveness = (1. - tex_color.a * kusok.color.a); - payload.normal = normal; - payload.geometry_normal = geom_normal; - - payload.emissive = vec3(0.); - if (any(greaterThan(kusok.emissive, vec3(0.)))) { - //const vec3 emissive_color = base_color; - //const vec3 emissive_color = pow(base_color, vec3(2.2)); - //const float max_color = max(max(emissive_color.r, emissive_color.g), emissive_color.b); - //payload.emissive = normalize(kusok.emissive) * emissive_color;// * mix(vec3(1.), kusok.emissive, smoothstep(.3, .6, max_color)); - payload.emissive = kusok.emissive * base_color; - } - - payload.kusok_index = kusok_index; - - payload.roughness = (kusok.tex_roughness > 0) ? sampleTexture(kusok.tex_roughness, texture_uv, uv_lods).r : kusok.roughness; - payload.metalness = (kusok.tex_metalness > 0) ? sampleTexture(kusok.tex_metalness, texture_uv, uv_lods).r : kusok.metalness; - payload.material_index = tex_index; - //payload.debug = vec4(texture_uv, uv_lods); - - T = baryMix(vertices[vi1].tangent, vertices[vi2].tangent, vertices[vi3].tangent, bary); - T = normalize(normalTransformMat * T); - payload.debug = vec4(bary, 0., 0.); -} diff --git a/ref_vk/shaders/ray.rgen b/ref_vk/shaders/ray.rgen deleted file mode 100644 index a8f49db2..00000000 --- a/ref_vk/shaders/ray.rgen +++ /dev/null @@ -1,373 +0,0 @@ -#version 460 core -#extension GL_EXT_nonuniform_qualifier : enable -#extension GL_GOOGLE_include_directive : require -#extension GL_EXT_control_flow_attributes : require - -#define RAY_TRACE - -#include "ray_common.glsl" -#include "ray_kusochki.glsl" -#include "noise.glsl" -#include "brdf.h" - -//#define DEBUG_LIGHT_CULLING - -// FIXME what should these be? -const float shadow_offset_fudge = .1; -const float pdf_culling_threshold = 1e6;//100.; -const float color_factor = 1.; -const float color_culling_threshold = 0;//600./color_factor; -const float throughput_threshold = 1e-3; - -layout(set = 0, binding = 0, rgba8) uniform image2D out_image_base_color; -layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; -layout(set = 0, binding = 9, rgba16f) uniform image2D out_image_diffuse_gi; -layout(set = 0, binding = 10, rgba16f) uniform image2D out_image_specular; -layout(set = 0, binding = 11, rgba16f) uniform image2D out_image_additive; -layout(set = 0, binding = 12, rgba16f) uniform image2D out_image_normals; - -layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; -layout(set = 0, binding = 2) uniform UBO { - mat4 inv_proj, inv_view; -} ubo; - -layout (set = 0, binding = 7/*, align=4*/) uniform UBOLights { Lights lights; }; - -layout (set = 0, binding = 8, align = 1) readonly buffer UBOLightClusters { - ivec3 grid_min, grid_size; - //uint8_t clusters_data[MAX_LIGHT_CLUSTERS * LIGHT_CLUSTER_SIZE + HACK_OFFSET]; - LightCluster clusters[MAX_LIGHT_CLUSTERS]; -} light_grid; - -layout (push_constant) uniform PC_ { - PushConstants push_constants; -}; - -layout(location = PAYLOAD_LOCATION_OPAQUE) rayPayloadEXT RayPayloadOpaque payload_opaque; -layout(location = PAYLOAD_LOCATION_SHADOW) rayPayloadEXT RayPayloadShadow payload_shadow; -layout(location = PAYLOAD_LOCATION_ADDITIVE) rayPayloadEXT RayPayloadAdditive payload_additive; - -#include "light_common.glsl" -#include "light_polygon.glsl" - -void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { - diffuse = specular = vec3(0.); - const uint num_point_lights = uint(light_grid.clusters[cluster_index].num_point_lights); - for (uint j = 0; j < num_point_lights; ++j) { - const uint i = uint(light_grid.clusters[cluster_index].point_lights[j]); - - vec3 color = lights.point_lights[i].color_stopdot.rgb * throughput; - if (dot(color,color) < color_culling_threshold) - continue; - - const vec4 origin_r = lights.point_lights[i].origin_r; - const float stopdot = lights.point_lights[i].color_stopdot.a; - const vec3 dir = lights.point_lights[i].dir_stopdot2.xyz; - const float stopdot2 = lights.point_lights[i].dir_stopdot2.a; - const bool not_environment = (lights.point_lights[i].environment == 0); - - const vec3 light_dir = not_environment ? (origin_r.xyz - P) : -dir; // TODO need to randomize sampling direction for environment soft shadow - const float radius = origin_r.w; - - const vec3 light_dir_norm = normalize(light_dir); - const float light_dot = dot(light_dir_norm, N); - if (light_dot < 1e-5) - continue; - - const float spot_dot = -dot(light_dir_norm, dir); - if (spot_dot < stopdot2) - continue; - - float spot_attenuation = 1.f; - if (spot_dot < stopdot) - spot_attenuation = (spot_dot - stopdot2) / (stopdot - stopdot2); - - //float fdist = 1.f; - float light_dist = 1e5; // TODO this is supposedly not the right way to do shadows for environment lights. qrad checks for hitting SURF_SKY, and maybe we should too? - const float d2 = dot(light_dir, light_dir); - const float r2 = origin_r.w * origin_r.w; - if (not_environment) { - if (radius < 1e-3) - continue; - - const float dist = length(light_dir); - if (radius > dist) - continue; -#if 1 - //light_dist = sqrt(d2); - light_dist = dist - radius; - //fdist = 2.f / (r2 + d2 + light_dist * sqrt(d2 + r2)); -#else - light_dist = dist; - //const float fdist = 2.f / (r2 + d2 + light_dist * sqrt(d2 + r2)); - //const float fdist = 2.f / (r2 + d2 + light_dist * sqrt(d2 + r2)); - //fdist = (light_dist > 1.) ? 1.f / d2 : 1.f; // qrad workaround -#endif - - //const float pdf = 1.f / (fdist * light_dot * spot_attenuation); - //const float pdf = TWO_PI / asin(radius / dist); - const float pdf = 1. / ((1. - sqrt(d2 - r2) / dist) * spot_attenuation); - color /= pdf; - } - - // if (dot(color,color) < color_culling_threshold) - // continue; - - vec3 ldiffuse, lspecular; - evalSplitBRDF(N, light_dir_norm, view_dir, material, ldiffuse, lspecular); - ldiffuse *= color; - lspecular *= color; - - vec3 combined = ldiffuse + lspecular; - - if (dot(combined,combined) < color_culling_threshold) - continue; - - if (not_environment) { - if (shadowed(P, light_dir_norm, light_dist + shadow_offset_fudge)) - continue; - } else { - // for environment light check that we've hit SURF_SKY - if (shadowedSky(P, light_dir_norm, light_dist + shadow_offset_fudge)) - continue; - } - - diffuse += ldiffuse; - specular += lspecular; - } // for all lights -} - -void computeLighting(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { - diffuse = specular = vec3(0.); - const ivec3 light_cell = ivec3(floor(P / LIGHT_GRID_CELL_SIZE)) - light_grid.grid_min; - const uint cluster_index = uint(dot(light_cell, ivec3(1, light_grid.grid_size.x, light_grid.grid_size.x * light_grid.grid_size.y))); - - if (any(greaterThanEqual(light_cell, light_grid.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) - return; // throughput * vec3(1., 0., 0.); - - // const uint cluster_offset = cluster_index * LIGHT_CLUSTER_SIZE + HACK_OFFSET; - // const int num_dlights = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_DLIGHTS_OFFSET]); - // const int num_emissive_surfaces = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_EMISSIVE_SURFACES_OFFSET]); - // const uint emissive_surfaces_offset = cluster_offset + LIGHT_CLUSTER_EMISSIVE_SURFACES_DATA_OFFSET; - //C = vec3(float(num_emissive_surfaces)); - - //C = vec3(float(int(light_grid.clusters[cluster_index].num_emissive_surfaces))); - //C += .3 * fract(vec3(light_cell) / 4.); - - sampleEmissiveSurfaces(P, N, throughput, view_dir, material, cluster_index, diffuse, specular); - - vec3 ldiffuse = vec3(0.), lspecular = vec3(0.); - computePointLights(P, N, cluster_index, throughput, view_dir, material, ldiffuse, lspecular); - diffuse += ldiffuse; - specular += lspecular; -} - -// Additive translucency -vec3 traceAdditive(vec3 origin, vec3 direction, float ray_distance) { - const uint flags = 0 - /* TODO try without*/ | gl_RayFlagsCullFrontFacingTrianglesEXT - //| gl_RayFlagsOpaqueEXT - | gl_RayFlagsSkipClosestHitShaderEXT - ; - const uint sbt_offset = 0; - const uint sbt_stride = 0; - - payload_additive.color = vec3(0.); - payload_additive.ray_distance = ray_distance; - traceRayEXT(tlas, flags, GEOMETRY_BIT_ADDITIVE, - sbt_offset, sbt_stride, SHADER_OFFSET_MISS_EMPTY, - origin, 0., direction, ray_distance + additive_soft_overshoot, - PAYLOAD_LOCATION_ADDITIVE); - return payload_additive.color * color_factor; -} - -void main() { - rand01_state = push_constants.random_seed + gl_LaunchIDEXT.x * 1833 + gl_LaunchIDEXT.y * 31337; - vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.; - - vec3 origin = (ubo.inv_view * vec4(0, 0, 0, 1)).xyz; - vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); - vec3 direction = (ubo.inv_view * vec4(normalize(target.xyz), 0)).xyz; - - payload_opaque.material_index = 0; - payload_opaque.t_offset = .0; - payload_opaque.pixel_cone_spread_angle = push_constants.pixel_cone_spread_angle; - - float out_material_index = 0.; - vec3 out_additive = vec3(0.); - vec3 out_diffuse_gi = vec3(0.); - vec3 out_specular = vec3(0.); - vec3 out_base_color = vec3(0.); - vec4 out_normals = vec4(0.); - - // Can be specular or diffuse_gi based on first bounce - vec3 out_accumulated = vec3(0.); - - int first_bounce_brdf_type = 0; - int brdfType = SPECULAR_TYPE; - vec3 throughput = vec3(1.); - for (int bounce = 0; bounce < push_constants.bounces; ++bounce) { - const uint flags = gl_RayFlagsCullFrontFacingTrianglesEXT; - const uint sbt_offset = 0; - const uint sbt_stride = 0; - const float L = 10000.; // Why 10k? - traceRayEXT(tlas, flags, GEOMETRY_BIT_OPAQUE | GEOMETRY_BIT_REFRACTIVE, - sbt_offset, sbt_stride, SHADER_OFFSET_MISS_REGULAR, - origin, 0., direction, L, - PAYLOAD_LOCATION_OPAQUE); - -#if 0 - imageStore(out_image_base_color, ivec2(gl_LaunchIDEXT.xy), vec4(fract(payload_opaque.debug.xy), 0., 0.)); - //imageStore(out_image_base_color, ivec2(gl_LaunchIDEXT.xy), vec4(payload_opaque.kusok_index)); - //imageStore(out_image_base_color, ivec2(gl_LaunchIDEXT.xy), vec4(payload_opaque.roughness)); - imageStore(out_image_diffuse_gi, ivec2(gl_LaunchIDEXT.xy), vec4(0)); - imageStore(out_image_specular, ivec2(gl_LaunchIDEXT.xy), vec4(0.)); - imageStore(out_image_additive, ivec2(gl_LaunchIDEXT.xy), vec4(clamp(payload_opaque.normal, vec3(0.), vec3(1.)), 0.)); - return; -#endif - - vec3 additive = traceAdditive(origin, direction, payload_opaque.hit_pos_t.w <= 0. ? L : payload_opaque.hit_pos_t.w); - - // Sky/envmap/emissive - if ((payload_opaque.kusok_index < 0) || any(greaterThan(payload_opaque.emissive, vec3(0.)))) { - if (bounce == 0) { - out_additive += payload_opaque.emissive * color_factor + additive; - } else { - out_accumulated += throughput * (/*payload_opaque.emissive * color_factor +*/ additive); - } - break; - } - -#if 0 //def DEBUG_LIGHT_CULLING - // light clusters debugging - { - const ivec3 light_cell = ivec3(floor(payload_opaque.hit_pos_t.xyz / LIGHT_GRID_CELL_SIZE)) - light_grid.grid_min; - const uint cluster_index = uint(dot(light_cell, ivec3(1, light_grid.grid_size.x, light_grid.grid_size.x * light_grid.grid_size.y))); - if (any(greaterThanEqual(light_cell, light_grid.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) { - out_additive = vec3(1., 0., 0.) * color_factor; - break; - } - - const uint num_emissive_kusochki = uint(light_grid.clusters[cluster_index].num_emissive_surfaces); - for (uint i = 0; i < num_emissive_kusochki; ++i) { - const uint index_into_emissive_kusochki = uint(light_grid.clusters[cluster_index].emissive_surfaces[i]); - - if (push_constants.debug_light_index_begin < push_constants.debug_light_index_end) { - if (index_into_emissive_kusochki < push_constants.debug_light_index_begin || index_into_emissive_kusochki >= push_constants.debug_light_index_end) - continue; - } - - out_additive = vec3(0., 0., 1.) * color_factor; - } - - const uvec3 cellrand = pcg3d(uvec3(light_cell)); - out_additive = .2 * color_factor * vec3( - uintToFloat01(cellrand.r), - uintToFloat01(cellrand.g), - uintToFloat01(cellrand.b)); - break; - } -#endif - - MaterialProperties material; - material.baseColor = payload_opaque.base_color; - material.metalness = payload_opaque.metalness; - material.emissive = payload_opaque.emissive; - material.roughness = payload_opaque.roughness; - - // material.roughness = uintToFloat01(xxhash32(uvec3(abs(floor(payload_opaque.hit_pos_t.xyz/64.))))); - // material.metalness = step(.5, xxhash32(uvec3(abs(floor(payload_opaque.hit_pos_t.xyz/32.))))); - - const vec3 shadingNormal = payload_opaque.normal; - const vec3 geometryNormal = payload_opaque.geometry_normal; - - if (bounce == 0) { //brdfType == SPECULAR_TYPE) - out_additive = payload_opaque.emissive + additive; - additive = vec3(0.); - - out_material_index = float(payload_opaque.material_index); - out_base_color = payload_opaque.base_color; - out_normals = vec4(geometryNormal.xy, shadingNormal.xy); - payload_opaque.base_color = vec3(1.); - - //out_material_index = float(kusochki[payload_opaque.kusok_index].tex_roughness); - } - - // TODO should we do this after reflect/transmit decision? -#define SKIP_TRASMITTED_LIGHT -#ifndef SKIP_TRASMITTED_LIGHT - C += computeLighting(throughput, -direction, material); - - if (bounce == push_constants.bounces - 1) - break; -#else - vec3 prev_throughput = throughput; -#endif - - const vec3 V = -direction; - if (material.metalness == 1.0f && material.roughness == 0.0f) { - // Fast path for mirrors - brdfType = SPECULAR_TYPE; - } else { - // Decide whether to sample diffuse or specular BRDF (based on Fresnel term) - float brdfProbability = getBrdfProbability(material, V, shadingNormal); - if (rand01() < brdfProbability) { - brdfType = SPECULAR_TYPE; - throughput /= brdfProbability; - } else { - // Refraction - if (rand01() < payload_opaque.transmissiveness) { - direction = refract(direction, payload_opaque.geometry_normal, .95); - origin = payload_opaque.hit_pos_t.xyz - payload_opaque.geometry_normal * shadow_offset_fudge; - continue; - } - - brdfType = DIFFUSE_TYPE; - throughput /= (1.0f - brdfProbability); - } - } - -#ifdef SKIP_TRASMITTED_LIGHT - vec3 diffuse, specular; - computeLighting(payload_opaque.hit_pos_t.xyz, payload_opaque.normal, prev_throughput, -direction, material, diffuse, specular); - - if (bounce == 0) { - out_diffuse_gi += diffuse; - out_specular += specular; - } else { - out_accumulated += payload_opaque.base_color * (diffuse + specular); - out_accumulated += prev_throughput * additive; - } - - if (bounce == push_constants.bounces - 1) - break; -#endif - - vec2 u = vec2(rand01(), rand01()); - vec3 brdfWeight; - if (!evalIndirectCombinedBRDF(u, shadingNormal, geometryNormal, V, material, brdfType, direction, brdfWeight)) { - break; // Ray was eaten by the surface :( - } - - throughput *= brdfWeight; - if (dot(throughput, throughput) < throughput_threshold) - break; - - origin = payload_opaque.hit_pos_t.xyz; - - if (bounce == 0) - first_bounce_brdf_type = brdfType; - } // for all bounces - - if (first_bounce_brdf_type == DIFFUSE_TYPE) { - out_diffuse_gi += out_accumulated; - } else { - out_specular += out_accumulated; - } - - imageStore(out_image_base_color, ivec2(gl_LaunchIDEXT.xy), vec4(out_base_color, 0.)); - imageStore(out_image_normals, ivec2(gl_LaunchIDEXT.xy), out_normals); - imageStore(out_image_diffuse_gi, ivec2(gl_LaunchIDEXT.xy), vec4(out_diffuse_gi / color_factor, out_material_index)); - imageStore(out_image_specular, ivec2(gl_LaunchIDEXT.xy), vec4(out_specular / color_factor, 0.)); - imageStore(out_image_additive, ivec2(gl_LaunchIDEXT.xy), vec4(out_additive / color_factor, 0.)); -} diff --git a/ref_vk/shaders/ray.rmiss b/ref_vk/shaders/ray.rmiss deleted file mode 100644 index af385020..00000000 --- a/ref_vk/shaders/ray.rmiss +++ /dev/null @@ -1,18 +0,0 @@ -#version 460 core -#extension GL_GOOGLE_include_directive : require - -#include "ray_common.glsl" -#include "ray_kusochki.glsl" - -layout(location = PAYLOAD_LOCATION_OPAQUE) rayPayloadInEXT RayPayloadOpaque payload; - -void main() { - payload.hit_pos_t = vec4(-1.); - payload.geometry_normal = payload.normal = vec3(0., 1., 0.); - payload.transmissiveness = 0.; - payload.roughness = 0.; - payload.base_color = vec3(1., 0., 1.); - payload.kusok_index = -1; - payload.material_index = 0; - payload.emissive = vec3(1., 0., 1.); -} diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index c8c1fdab..f0cd2622 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -113,14 +113,6 @@ struct PointLight { PAD(3) }; -struct EmissiveKusok { - uint kusok_index; - PAD(3) - vec3 emissive; - PAD(1) - vec4 tx_row_x, tx_row_y, tx_row_z; -}; - struct PolygonLight { vec4 plane; @@ -131,11 +123,10 @@ struct PolygonLight { uint vertices_count_offset; }; -struct Lights { +struct LightsMetadata { uint num_polygons; uint num_point_lights; PAD(2) - //STRUCT EmissiveKusok kusochki[MAX_EMISSIVE_KUSOCHKI]; STRUCT PointLight point_lights[MAX_POINT_LIGHTS]; STRUCT PolygonLight polygons[MAX_EMISSIVE_KUSOCHKI]; vec4 polygon_vertices[MAX_EMISSIVE_KUSOCHKI * 7]; // vec3 but aligned diff --git a/ref_vk/shaders/ray_light_direct.glsl b/ref_vk/shaders/ray_light_direct.glsl index 195248c2..534b6e07 100644 --- a/ref_vk/shaders/ray_light_direct.glsl +++ b/ref_vk/shaders/ray_light_direct.glsl @@ -30,7 +30,6 @@ layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; #define PAYLOAD_LOCATION_SHADOW 0 #define BINDING_LIGHTS 7 -#define BINDING_LIGHT_CLUSTERS 8 #include "light.glsl" void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { diff --git a/ref_vk/shaders/shadow.rchit b/ref_vk/shaders/shadow.rchit deleted file mode 100644 index f759d2f6..00000000 --- a/ref_vk/shaders/shadow.rchit +++ /dev/null @@ -1,15 +0,0 @@ -#version 460 core -#extension GL_EXT_ray_tracing: require - -#include "ray_kusochki.glsl" -#include "ray_common.glsl" - -layout(location = PAYLOAD_LOCATION_SHADOW) rayPayloadInEXT RayPayloadShadow payload_shadow; - -void main() { - const int instance_kusochki_offset = gl_InstanceCustomIndexEXT; - const int kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT; - const uint tex_base_color = kusochki[kusok_index].tex_base_color; - - payload_shadow.hit_type = ((tex_base_color & KUSOK_MATERIAL_FLAG_SKYBOX) == 0) ? SHADOW_HIT : SHADOW_SKY ; -} diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index ef1d3c10..c3efe7a8 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -849,6 +849,8 @@ VkShaderModule loadShader(const char *filename) { smci.pCode = pcode; XVK_CHECK(vkCreateShaderModule(vk_core.device, &smci, NULL, &shader)); + SET_DEBUG_NAME(shader, VK_OBJECT_TYPE_SHADER_MODULE, filename); + Mem_Free(buf); return shader; } diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 849bb540..9efec663 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -36,6 +36,16 @@ typedef struct { qboolean set; } vk_emissive_texture_t; +typedef struct { + int min_cell[4], size[3]; // 4th element is padding + struct LightCluster cells[MAX_LIGHT_CLUSTERS]; +} vk_ray_shader_light_grid; + +struct Lights { + struct LightsMetadata metadata; + vk_ray_shader_light_grid grid; +}; + static struct { struct { vk_emissive_texture_t emissive_textures[MAX_TEXTURES]; @@ -1132,14 +1142,38 @@ int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { } } +static void uploadGrid( void ) { + struct Lights *lights = g_lights_.buffer.mapped; + vk_ray_shader_light_grid *grid = &lights->grid; + + ASSERT(g_lights.map.grid_cells <= MAX_LIGHT_CLUSTERS); + + VectorCopy(g_lights.map.grid_min_cell, grid->min_cell); + VectorCopy(g_lights.map.grid_size, grid->size); + + for (int i = 0; i < g_lights.map.grid_cells; ++i) { + const vk_lights_cell_t *const src = g_lights.cells + i; + struct LightCluster *const dst = grid->cells + i; + + dst->num_point_lights = src->num_point_lights; + dst->num_polygons = src->num_polygons; + memcpy(dst->point_lights, src->point_lights, sizeof(uint8_t) * src->num_point_lights); + memcpy(dst->polygons, src->polygons, sizeof(uint8_t) * src->num_polygons); + } +} + const struct vk_buffer_s* VK_LightsUpload( VkCommandBuffer cmdbuf ) { + uploadGrid(); + // Upload polygon lights struct Lights *lights = g_lights_.buffer.mapped; + struct LightsMetadata *metadata = &lights->metadata; + ASSERT(g_lights.num_polygons <= MAX_EMISSIVE_KUSOCHKI); - lights->num_polygons = g_lights.num_polygons; + metadata->num_polygons = g_lights.num_polygons; for (int i = 0; i < g_lights.num_polygons; ++i) { const rt_light_polygon_t *const src_poly = g_lights.polygons + i; - struct PolygonLight *const dst_poly = lights->polygons + i; + struct PolygonLight *const dst_poly = metadata->polygons + i; Vector4Copy(src_poly->plane, dst_poly->plane); VectorCopy(src_poly->center, dst_poly->center); @@ -1151,15 +1185,15 @@ const struct vk_buffer_s* VK_LightsUpload( VkCommandBuffer cmdbuf ) { ASSERT(src_poly->vertices.offset < 0xffffu); ASSERT(src_poly->vertices.count < 0xffffu); - ASSERT(src_poly->vertices.offset + src_poly->vertices.count < COUNTOF(lights->polygon_vertices)); + ASSERT(src_poly->vertices.offset + src_poly->vertices.count < COUNTOF(metadata->polygon_vertices)); dst_poly->vertices_count_offset = (src_poly->vertices.count << 16) | (src_poly->vertices.offset); } - lights->num_point_lights = g_lights.num_point_lights; + metadata->num_point_lights = g_lights.num_point_lights; for (int i = 0; i < g_lights.num_point_lights; ++i) { vk_point_light_t *const src = g_lights.point_lights + i; - struct PointLight *const dst = lights->point_lights + i; + struct PointLight *const dst = metadata->point_lights + i; VectorCopy(src->origin, dst->origin_r); dst->origin_r[3] = src->radius; @@ -1174,9 +1208,9 @@ const struct vk_buffer_s* VK_LightsUpload( VkCommandBuffer cmdbuf ) { } // TODO static assert - ASSERT(sizeof(lights->polygon_vertices) >= sizeof(g_lights.polygon_vertices)); + ASSERT(sizeof(metadata->polygon_vertices) >= sizeof(g_lights.polygon_vertices)); for (int i = 0; i < g_lights.num_polygon_vertices; ++i) { - VectorCopy(g_lights.polygon_vertices[i], lights->polygon_vertices[i]); + VectorCopy(g_lights.polygon_vertices[i], metadata->polygon_vertices[i]); } return &g_lights_.buffer; diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 2dd3bbb8..f2838d95 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -11,7 +11,6 @@ X(5, vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ X(6, all_textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES) \ X(7, lights, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ - X(8, light_clusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ #define LIST_COMMON_BINDINGS(X) \ LIST_SCENE_BINDINGS(X) \ diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 5c7411fa..de809291 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -54,11 +54,6 @@ typedef struct { typedef struct PushConstants vk_rtx_push_constants_t; -typedef struct { - int min_cell[4], size[3]; // 4th element is padding - struct LightCluster cells[MAX_LIGHT_CLUSTERS]; -} vk_ray_shader_light_grid; - typedef struct { xvk_image_t denoised; @@ -100,18 +95,6 @@ static struct { VkDeviceAddress tlas_geom_buffer_addr; r_flipping_buffer_t tlas_geom_buffer_alloc; - // Planned to contain seveal types of data: - // - grid structure itself - // - lights data: - // - dlights (fully dynamic) - // - entity lights (can be dynamic with light styles) - // - surface lights (map geometry is static, however brush models can have them too and move around (e.g. wagonchik and elevators)) - // Therefore, this is also dynamic and lifetime is per-frame - // TODO: unify with scratch buffer - // Needs: STORAGE_BUFFER - // Can be potentially crated using compute shader (would need shader write bit) - vk_buffer_t light_grid_buffer; - // TODO need several TLASes for N frames in flight VkAccelerationStructureKHR tlas; @@ -401,27 +384,6 @@ static void prepareTlas( VkCommandBuffer cmdbuf ) { DEBUG_END(cmdbuf); } -// Finalize and update dynamic lights -static void uploadLights( void ) { - // Upload light grid - { - vk_ray_shader_light_grid *grid = g_rtx.light_grid_buffer.mapped; - ASSERT(g_lights.map.grid_cells <= MAX_LIGHT_CLUSTERS); - VectorCopy(g_lights.map.grid_min_cell, grid->min_cell); - VectorCopy(g_lights.map.grid_size, grid->size); - - for (int i = 0; i < g_lights.map.grid_cells; ++i) { - const vk_lights_cell_t *const src = g_lights.cells + i; - struct LightCluster *const dst = grid->cells + i; - - dst->num_point_lights = src->num_point_lights; - dst->num_polygons = src->num_polygons; - memcpy(dst->point_lights, src->point_lights, sizeof(uint8_t) * src->num_point_lights); - memcpy(dst->polygons, src->polygons, sizeof(uint8_t) * src->num_polygons); - } - } -} - static void clearVkImage( VkCommandBuffer cmdbuf, VkImage image ) { const VkImageMemoryBarrier image_barriers[] = { { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, @@ -587,7 +549,6 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar RES_SET_SBUFFER_FULL(indices, args->geometry_data), RES_SET_SBUFFER_FULL(vertices, args->geometry_data), RES_SET_SBUFFER_FULL(lights, (*fixme_lights_buffer)), - RES_SET_SBUFFER_FULL(light_clusters, g_rtx.light_grid_buffer), #undef RES_SET_SBUFFER_FULL #undef RES_SET_BUFFER @@ -690,7 +651,6 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) RT_LightsFrameEnd(); const vk_buffer_t* const fixme_lights_buffer = VK_LightsUpload(cmdbuf); - uploadLights(); g_rtx.frame_number++; @@ -808,13 +768,6 @@ qboolean VK_RayInit( void ) } RT_RayModel_Clear(); - if (!VK_BufferCreate("ray light_grid_buffer", &g_rtx.light_grid_buffer, sizeof(vk_ray_shader_light_grid), - VK_BUFFER_USAGE_STORAGE_BUFFER_BIT /* | VK_BUFFER_USAGE_TRANSFER_DST_BIT */, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { - // FIXME complain, handle - return false; - } - for (int i = 0; i < ARRAYSIZE(g_rtx.frames); ++i) { #define CREATE_GBUFFER_IMAGE(name, format_, add_usage_bits) \ do { \ @@ -897,7 +850,6 @@ void VK_RayShutdown( void ) { VK_BufferDestroy(&g_rtx.accels_buffer); VK_BufferDestroy(&g_rtx.tlas_geom_buffer); VK_BufferDestroy(&g_ray_model_state.kusochki_buffer); - VK_BufferDestroy(&g_rtx.light_grid_buffer); VK_BufferDestroy(&g_rtx.uniform_buffer); if (g_rtx.accels_buffer_alloc) From 01d4764cf4f3b91ce3ea443dcdd4e3a7a2a728e2 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 30 Jul 2022 14:18:37 -0700 Subject: [PATCH 423/548] vk: do not compile dumbspter --- ref_vk/dumbspter.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ref_vk/dumbspter.c b/ref_vk/dumbspter.c index ca21f817..f8650f23 100644 --- a/ref_vk/dumbspter.c +++ b/ref_vk/dumbspter.c @@ -1,3 +1,4 @@ +#if 0 #include "vk_common.h" #include "xash3d_types.h" #include "protocol.h" @@ -165,3 +166,4 @@ void traverseBSP( void ) { fclose(ctx.f); //exit(0); } +#endif From 1d25b718dd3d33f14b593514ae2450304a2c7c1d Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 7 Aug 2022 09:45:54 -0700 Subject: [PATCH 424/548] rt: split light metadata/grid bindings back in shader for some reason joining them leads to "invalid spirv" validation errors (and broken lights). split the bindings back making shaders essentially unchanged, while still keeping a single physical buffer --- ref_vk/shaders/light.glsl | 35 ++++++++--------- ref_vk/shaders/light_polygon.glsl | 42 ++++++++++----------- ref_vk/shaders/ray_light_direct.glsl | 1 + ref_vk/vk_light.c | 20 +++++++--- ref_vk/vk_light.h | 9 ++++- ref_vk/vk_ray_light_direct.c | 1 + ref_vk/vk_rtx.c | 56 ++++++++++++++++++---------- 7 files changed, 99 insertions(+), 65 deletions(-) diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index ec779483..8bef97a6 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -1,8 +1,9 @@ -layout (set = 0, binding = BINDING_LIGHTS, align = 1) readonly buffer LightBuffer { - LightsMetadata metadata; +layout (set = 0, binding = BINDING_LIGHTS) readonly buffer UBOLights { LightsMetadata lights; }; // TODO this is pretty much static and should be a buffer, not UBO +layout (set = 0, binding = BINDING_LIGHT_CLUSTERS, align = 1) readonly buffer UBOLightClusters { ivec3 grid_min, grid_size; + //uint8_t clusters_data[MAX_LIGHT_CLUSTERS * LIGHT_CLUSTER_SIZE + HACK_OFFSET]; LightCluster clusters[MAX_LIGHT_CLUSTERS]; -} lights; +} light_grid; const float color_culling_threshold = 0;//600./color_factor; const float shadow_offset_fudge = .1; @@ -17,19 +18,19 @@ const float shadow_offset_fudge = .1; #if LIGHT_POINT void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { diffuse = specular = vec3(0.); - const uint num_point_lights = uint(lights.clusters[cluster_index].num_point_lights); + const uint num_point_lights = uint(light_grid.clusters[cluster_index].num_point_lights); for (uint j = 0; j < num_point_lights; ++j) { - const uint i = uint(lights.clusters[cluster_index].point_lights[j]); + const uint i = uint(light_grid.clusters[cluster_index].point_lights[j]); - vec3 color = lights.metadata.point_lights[i].color_stopdot.rgb * throughput; + vec3 color = lights.point_lights[i].color_stopdot.rgb * throughput; if (dot(color,color) < color_culling_threshold) continue; - const vec4 origin_r = lights.metadata.point_lights[i].origin_r; - const float stopdot = lights.metadata.point_lights[i].color_stopdot.a; - const vec3 dir = lights.metadata.point_lights[i].dir_stopdot2.xyz; - const float stopdot2 = lights.metadata.point_lights[i].dir_stopdot2.a; - const bool not_environment = (lights.metadata.point_lights[i].environment == 0); + const vec4 origin_r = lights.point_lights[i].origin_r; + const float stopdot = lights.point_lights[i].color_stopdot.a; + const vec3 dir = lights.point_lights[i].dir_stopdot2.xyz; + const float stopdot2 = lights.point_lights[i].dir_stopdot2.a; + const bool not_environment = (lights.point_lights[i].environment == 0); const vec3 light_dir = not_environment ? (origin_r.xyz - P) : -dir; // TODO need to randomize sampling direction for environment soft shadow const float radius = origin_r.w; @@ -108,19 +109,19 @@ void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec void computeLighting(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { diffuse = specular = vec3(0.); - const ivec3 light_cell = ivec3(floor(P / LIGHT_GRID_CELL_SIZE)) - lights.grid_min; - const uint cluster_index = uint(dot(light_cell, ivec3(1, lights.grid_size.x, lights.grid_size.x * lights.grid_size.y))); + const ivec3 light_cell = ivec3(floor(P / LIGHT_GRID_CELL_SIZE)) - light_grid.grid_min; + const uint cluster_index = uint(dot(light_cell, ivec3(1, light_grid.grid_size.x, light_grid.grid_size.x * light_grid.grid_size.y))); - if (any(greaterThanEqual(light_cell, lights.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) + if (any(greaterThanEqual(light_cell, light_grid.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) return; // throughput * vec3(1., 0., 0.); // const uint cluster_offset = cluster_index * LIGHT_CLUSTER_SIZE + HACK_OFFSET; - // const int num_dlights = int(lights.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_DLIGHTS_OFFSET]); - // const int num_emissive_surfaces = int(lights.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_EMISSIVE_SURFACES_OFFSET]); + // const int num_dlights = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_DLIGHTS_OFFSET]); + // const int num_emissive_surfaces = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_EMISSIVE_SURFACES_OFFSET]); // const uint emissive_surfaces_offset = cluster_offset + LIGHT_CLUSTER_EMISSIVE_SURFACES_DATA_OFFSET; //C = vec3(float(num_emissive_surfaces)); - //C = vec3(float(int(lights.clusters[cluster_index].num_emissive_surfaces))); + //C = vec3(float(int(light_grid.clusters[cluster_index].num_emissive_surfaces))); //C += .3 * fract(vec3(light_cell) / 4.); #if LIGHT_POLYGON diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 2d21f718..c4933724 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -28,7 +28,7 @@ vec4 getPolygonLightSampleSimple(vec3 P, vec3 view_dir, const PolygonLight poly) vertices_count = 3; // FIXME for (uint i = 0; i < vertices_count; ++i) { - v[i] = lights.metadata.polygon_vertices[vertices_offset + i].xyz; + v[i] = lights.polygon_vertices[vertices_offset + i].xyz; } vec2 rnd = vec2(sqrt(rand01()), rand01()); @@ -49,12 +49,12 @@ vec4 getPolygonLightSampleSimpleSolid(vec3 P, vec3 view_dir, const PolygonLight float total_contrib = 0.; float eps1 = rand01(); vec3 v[3]; - v[0] = normalize(lights.metadata.polygon_vertices[vertices_offset + 0].xyz - P); - v[1] = normalize(lights.metadata.polygon_vertices[vertices_offset + 1].xyz - P); + v[0] = normalize(lights.polygon_vertices[vertices_offset + 0].xyz - P); + v[1] = normalize(lights.polygon_vertices[vertices_offset + 1].xyz - P); const float householder_sign = (v[0].x > 0.0f) ? -1.0f : 1.0f; const vec2 householder_yz = v[0].yz * (1.0f / (abs(v[0].x) + 1.0f)); for (uint i = 2; i < vertices_count; ++i) { - v[2] = normalize(lights.metadata.polygon_vertices[vertices_offset + i].xyz - P); + v[2] = normalize(lights.polygon_vertices[vertices_offset + i].xyz - P); // effectively mindlessly copypasted from polygon_sampling.glsl, Peters 2021 // https://github.com/MomentsInGraphics/vulkan_renderer/blob/main/src/shaders/polygon_sampling.glsl @@ -98,9 +98,9 @@ vec4 getPolygonLightSampleSimpleSolid(vec3 P, vec3 view_dir, const PolygonLight rnd.x = 1.f - rnd.x; const vec3 light_dir = baryMix( - lights.metadata.polygon_vertices[vertices_offset + 0].xyz, - lights.metadata.polygon_vertices[vertices_offset + selected - 1].xyz, - lights.metadata.polygon_vertices[vertices_offset + selected].xyz, + lights.polygon_vertices[vertices_offset + 0].xyz, + lights.polygon_vertices[vertices_offset + selected - 1].xyz, + lights.polygon_vertices[vertices_offset + selected].xyz, rnd) - P; const vec3 light_dir_n = normalize(light_dir); return vec4(light_dir_n, total_contrib); @@ -113,7 +113,7 @@ vec4 getPolygonLightSampleProjected(vec3 view_dir, SampleContext ctx, const Poly uint vertices_count = poly.vertices_count_offset >> 16; for (uint i = 0; i < vertices_count; ++i) { - clipped[i] = ctx.world_to_shading * vec4(lights.metadata.polygon_vertices[vertices_offset + i].xyz, 1.); + clipped[i] = ctx.world_to_shading * vec4(lights.polygon_vertices[vertices_offset + i].xyz, 1.); } vertices_count = clip_polygon(vertices_count, clipped); @@ -138,7 +138,7 @@ vec4 getPolygonLightSampleSolid(vec3 P, vec3 view_dir, SampleContext ctx, const uint vertices_count = poly.vertices_count_offset >> 16; for (uint i = 0; i < vertices_count; ++i) { - clipped[i] = lights.metadata.polygon_vertices[vertices_offset + i].xyz; + clipped[i] = lights.polygon_vertices[vertices_offset + i].xyz; } #define DONT_CLIP @@ -191,14 +191,14 @@ void sampleSinglePolygonLight(in vec3 P, in vec3 N, in vec3 view_dir, in SampleC #if 0 // Sample random one void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { - const uint num_polygons = uint(lights.clusters[cluster_index].num_polygons); + const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); if (num_polygons == 0) return; - const uint selected = uint(lights.clusters[cluster_index].polygons[rand_range(num_polygons)]); + const uint selected = uint(light_grid.clusters[cluster_index].polygons[rand_range(num_polygons)]); - const PolygonLight poly = lights.metadata.polygons[selected]; + const PolygonLight poly = lights.polygons[selected]; const SampleContext ctx = buildSampleContext(P, N, view_dir); sampleSinglePolygonLight(P, N, view_dir, ctx, material, poly, diffuse, specular); @@ -211,10 +211,10 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { #if DO_ALL_IN_CLUSTER const SampleContext ctx = buildSampleContext(P, N, view_dir); - const uint num_polygons = uint(lights.clusters[cluster_index].num_polygons); + const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); for (uint i = 0; i < num_polygons; ++i) { - const uint index = uint(lights.clusters[cluster_index].polygons[i]); - const PolygonLight poly = lights.metadata.polygons[index]; + const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); + const PolygonLight poly = lights.polygons[index]; const float plane_dist = dot(poly.plane, vec4(P, 1.f)); @@ -251,9 +251,9 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate #define USE_CLUSTERS #ifdef USE_CLUSTERS // TODO move this to pickPolygonLight function - const uint num_polygons = uint(lights.clusters[cluster_index].num_polygons); + const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); #else - const uint num_polygons = lights.metadata.num_polygons; + const uint num_polygons = lights.num_polygons; #endif uint selected = 0; @@ -261,12 +261,12 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate float eps1 = rand01(); for (uint i = 0; i < num_polygons; ++i) { #ifdef USE_CLUSTERS - const uint index = uint(lights.clusters[cluster_index].polygons[i]); + const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); #else const uint index = i; #endif - const PolygonLight poly = lights.metadata.polygons[index]; + const PolygonLight poly = lights.polygons[index]; const vec3 dir = poly.center - P; const vec3 light_dir = normalize(dir); @@ -293,7 +293,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate } #if 0 - const PolygonLight poly = lights.metadata.polygons[selected - 1]; + const PolygonLight poly = lights.polygons[selected - 1]; const vec3 emissive = poly.emissive; vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); evalSplitBRDF(N, normalize(poly.center-P), view_dir, material, poly_diffuse, poly_specular); @@ -301,7 +301,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate specular += throughput * emissive * total_contrib; #else const SampleContext ctx = buildSampleContext(P, N, view_dir); - const PolygonLight poly = lights.metadata.polygons[selected - 1]; + const PolygonLight poly = lights.polygons[selected - 1]; #ifdef PROJECTED const vec4 light_sample_dir = getPolygonLightSampleProjected(view_dir, ctx, poly); #else diff --git a/ref_vk/shaders/ray_light_direct.glsl b/ref_vk/shaders/ray_light_direct.glsl index 534b6e07..195248c2 100644 --- a/ref_vk/shaders/ray_light_direct.glsl +++ b/ref_vk/shaders/ray_light_direct.glsl @@ -30,6 +30,7 @@ layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; #define PAYLOAD_LOCATION_SHADOW 0 #define BINDING_LIGHTS 7 +#define BINDING_LIGHT_CLUSTERS 8 #include "light.glsl" void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 9efec663..c9303eb1 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -39,11 +39,11 @@ typedef struct { typedef struct { int min_cell[4], size[3]; // 4th element is padding struct LightCluster cells[MAX_LIGHT_CLUSTERS]; -} vk_ray_shader_light_grid; +} vk_ray_shader_light_grid_t; struct Lights { struct LightsMetadata metadata; - vk_ray_shader_light_grid grid; + vk_ray_shader_light_grid_t grid; }; static struct { @@ -1144,7 +1144,7 @@ int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { static void uploadGrid( void ) { struct Lights *lights = g_lights_.buffer.mapped; - vk_ray_shader_light_grid *grid = &lights->grid; + vk_ray_shader_light_grid_t *grid = &lights->grid; ASSERT(g_lights.map.grid_cells <= MAX_LIGHT_CLUSTERS); @@ -1162,7 +1162,7 @@ static void uploadGrid( void ) { } } -const struct vk_buffer_s* VK_LightsUpload( VkCommandBuffer cmdbuf ) { +vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { uploadGrid(); // Upload polygon lights @@ -1213,7 +1213,17 @@ const struct vk_buffer_s* VK_LightsUpload( VkCommandBuffer cmdbuf ) { VectorCopy(g_lights.polygon_vertices[i], metadata->polygon_vertices[i]); } - return &g_lights_.buffer; + return (vk_lights_bindings_t){ + .buffer = g_lights_.buffer.buffer, + .metadata = { + .offset = 0, + .size = sizeof(struct LightsMetadata), + }, + .grid = { + .offset = sizeof(struct LightsMetadata), + .size = sizeof(vk_ray_shader_light_grid_t), + }, + }; } void RT_LightsFrameEnd( void ) { diff --git a/ref_vk/vk_light.h b/ref_vk/vk_light.h index 2bd78a85..d117ba98 100644 --- a/ref_vk/vk_light.h +++ b/ref_vk/vk_light.h @@ -87,8 +87,13 @@ void RT_LightsNewMapEnd( const struct model_s *map ); void RT_LightsFrameBegin( void ); void RT_LightsFrameEnd( void ); -struct vk_buffer_s; -const struct vk_buffer_s* VK_LightsUpload( VkCommandBuffer ); +typedef struct { + VkBuffer buffer; + struct { + uint32_t offset, size; + } metadata, grid; +} vk_lights_bindings_t; +vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer ); qboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id ); diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index f2838d95..2dd3bbb8 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -11,6 +11,7 @@ X(5, vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ X(6, all_textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES) \ X(7, lights, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ + X(8, light_clusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ #define LIST_COMMON_BINDINGS(X) \ LIST_SCENE_BINDINGS(X) \ diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index de809291..50985a59 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -518,7 +518,15 @@ static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int fr ubo->random_seed = (uint32_t)gEngine.COM_RandomLong(0, INT32_MAX); } -static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_args_t* args, int frame_index, const xvk_ray_frame_images_t *current_frame, float fov_angle_y, const vk_buffer_t* fixme_lights_buffer) { +typedef struct { + const vk_ray_frame_render_args_t* render_args; + int frame_index; + const xvk_ray_frame_images_t *current_frame; + float fov_angle_y; + const vk_lights_bindings_t *light_bindings; +} perform_tracing_args_t; + +static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* args) { vk_ray_resources_t res = { .width = FRAME_WIDTH, .height = FRAME_HEIGHT, @@ -536,19 +544,20 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar [RayResource_##name] = { \ .type = type_, \ .value.buffer = (VkDescriptorBufferInfo) { \ - .buffer = source_.buffer, \ + .buffer = (source_), \ .offset = (offset_), \ .range = (size_), \ } \ } - RES_SET_BUFFER(ubo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, g_rtx.uniform_buffer, frame_index * g_rtx.uniform_unit_size, sizeof(struct UniformBuffer)), + RES_SET_BUFFER(ubo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, g_rtx.uniform_buffer.buffer, args->frame_index * g_rtx.uniform_unit_size, sizeof(struct UniformBuffer)), #define RES_SET_SBUFFER_FULL(name, source_) \ - RES_SET_BUFFER(name, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, source_, 0, source_.size) + RES_SET_BUFFER(name, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, source_.buffer, 0, source_.size) RES_SET_SBUFFER_FULL(kusochki, g_ray_model_state.kusochki_buffer), - RES_SET_SBUFFER_FULL(indices, args->geometry_data), - RES_SET_SBUFFER_FULL(vertices, args->geometry_data), - RES_SET_SBUFFER_FULL(lights, (*fixme_lights_buffer)), + RES_SET_SBUFFER_FULL(indices, args->render_args->geometry_data), + RES_SET_SBUFFER_FULL(vertices, args->render_args->geometry_data), + RES_SET_BUFFER(lights, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, args->light_bindings->buffer, args->light_bindings->metadata.offset, args->light_bindings->metadata.size), + RES_SET_BUFFER(light_clusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, args->light_bindings->buffer, args->light_bindings->grid.offset, args->light_bindings->grid.size), #undef RES_SET_SBUFFER_FULL #undef RES_SET_BUFFER @@ -571,7 +580,7 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, \ .write = {0}, \ .read = {0}, \ - .image = ¤t_frame->name, \ + .image = &args->current_frame->name, \ }, RAY_PRIMARY_OUTPUTS(RES_SET_IMAGE) RAY_LIGHT_DIRECT_POLY_OUTPUTS(RES_SET_IMAGE) @@ -584,7 +593,7 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar DEBUG_BEGIN(cmdbuf, "yay tracing"); prepareTlas(cmdbuf); - prepareUniformBuffer(args, frame_index, fov_angle_y); + prepareUniformBuffer(args->render_args, args->frame_index, args->fov_angle_y); // 4. Barrier for TLAS build and dest image layout transfer { @@ -602,26 +611,26 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } - RayPassPerform( cmdbuf, frame_index, g_rtx.pass.primary_ray, &res ); - RayPassPerform( cmdbuf, frame_index, g_rtx.pass.light_direct_poly, &res ); - RayPassPerform( cmdbuf, frame_index, g_rtx.pass.light_direct_point, &res ); - RayPassPerform( cmdbuf, frame_index, g_rtx.pass.denoiser, &res ); + RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.primary_ray, &res ); + RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.light_direct_poly, &res ); + RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.light_direct_point, &res ); + RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.denoiser, &res ); { const xvk_blit_args blit_args = { - .cmdbuf = args->cmdbuf, + .cmdbuf = cmdbuf, .in_stage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, .src = { - .image = current_frame->denoised.image, + .image = args->current_frame->denoised.image, .width = FRAME_WIDTH, .height = FRAME_HEIGHT, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, }, .dst = { - .image = args->dst.image, - .width = args->dst.width, - .height = args->dst.height, + .image = args->render_args->dst.image, + .width = args->render_args->dst.width, + .height = args->render_args->dst.height, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .srcAccessMask = 0, }, @@ -650,7 +659,7 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) // FIXME pass these matrices explicitly to let RTX module handle ubo itself RT_LightsFrameEnd(); - const vk_buffer_t* const fixme_lights_buffer = VK_LightsUpload(cmdbuf); + const vk_lights_bindings_t light_bindings = VK_LightsUpload(cmdbuf); g_rtx.frame_number++; @@ -691,7 +700,14 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) clearVkImage( cmdbuf, current_frame->denoised.image ); blitImage( &blit_args ); } else { - performTracing( cmdbuf, args, (g_rtx.frame_number % 2), current_frame, args->fov_angle_y, fixme_lights_buffer); + const perform_tracing_args_t trace_args = { + .render_args = args, + .frame_index = (g_rtx.frame_number % 2), + .current_frame = current_frame, + .fov_angle_y = args->fov_angle_y, + .light_bindings = &light_bindings, + }; + performTracing( cmdbuf, &trace_args ); } } From 1d2e763baf9aed14ba01f49e2e9cd173ac0bf0ae Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 7 Aug 2022 10:14:52 -0700 Subject: [PATCH 425/548] rt: add staging buffer for light grid, fix #368 --- ref_vk/vk_light.c | 48 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index c9303eb1..3a0c20f3 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -51,7 +51,7 @@ static struct { vk_emissive_texture_t emissive_textures[MAX_TEXTURES]; } map; - // TODO vk_buffer_t staging; + vk_buffer_t staging; vk_buffer_t buffer; } g_lights_; @@ -77,8 +77,14 @@ qboolean VK_LightsInit( void ) { gEngine.Cmd_AddCommand("vk_lights_dump", debugDumpLights, "Dump all light sources for next frame"); if (!VK_BufferCreate("rt lights buffer", &g_lights_.buffer, sizeof(struct Lights), - VK_BUFFER_USAGE_STORAGE_BUFFER_BIT /* | VK_BUFFER_USAGE_TRANSFER_DST_BIT */, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { + // FIXME complain, handle + return false; + } + + if (!VK_BufferCreate("rt lights staging buffer", &g_lights_.staging, sizeof(struct Lights), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { // FIXME complain, handle return false; } @@ -89,6 +95,7 @@ qboolean VK_LightsInit( void ) { static void clusterBitMapShutdown( void ); void VK_LightsShutdown( void ) { + VK_BufferDestroy(&g_lights_.staging); VK_BufferDestroy(&g_lights_.buffer); gEngine.Cmd_RemoveCommand("vk_lights_dump"); @@ -1142,10 +1149,7 @@ int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { } } -static void uploadGrid( void ) { - struct Lights *lights = g_lights_.buffer.mapped; - vk_ray_shader_light_grid_t *grid = &lights->grid; - +static void uploadGrid( vk_ray_shader_light_grid_t *grid ) { ASSERT(g_lights.map.grid_cells <= MAX_LIGHT_CLUSTERS); VectorCopy(g_lights.map.grid_min_cell, grid->min_cell); @@ -1163,10 +1167,8 @@ static void uploadGrid( void ) { } vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { - uploadGrid(); - // Upload polygon lights - struct Lights *lights = g_lights_.buffer.mapped; + struct Lights *lights = g_lights_.staging.mapped; struct LightsMetadata *metadata = &lights->metadata; ASSERT(g_lights.num_polygons <= MAX_EMISSIVE_KUSOCHKI); @@ -1207,12 +1209,38 @@ vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { dst->environment = !!(src->flags & LightFlag_Environment); } + uploadGrid( &lights->grid ); + // TODO static assert ASSERT(sizeof(metadata->polygon_vertices) >= sizeof(g_lights.polygon_vertices)); for (int i = 0; i < g_lights.num_polygon_vertices; ++i) { VectorCopy(g_lights.polygon_vertices[i], metadata->polygon_vertices[i]); } + { + const VkBufferCopy regions[] = { + { + .srcOffset = 0, + .dstOffset = 0, + .size = sizeof(struct Lights), + }, + }; + vkCmdCopyBuffer(cmdbuf, g_lights_.staging.buffer, g_lights_.buffer.buffer, COUNTOF(regions), regions); + + const VkBufferMemoryBarrier bmb[] = {{ + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .buffer = g_lights_.buffer.buffer, + .offset = 0, + .size = VK_WHOLE_SIZE, + }}; + vkCmdPipelineBarrier(cmdbuf, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, + 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); + } + return (vk_lights_bindings_t){ .buffer = g_lights_.buffer.buffer, .metadata = { From 0f0a58d1aaabfd155c5a018370061db3318f4138 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 7 Aug 2022 10:32:46 -0700 Subject: [PATCH 426/548] rt: add debug markers to see that uploading lights takes >35ms --- ref_vk/vk_light.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 3a0c20f3..e2d8916a 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -1218,6 +1218,7 @@ vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { } { + DEBUG_BEGIN(cmdbuf, "upload lights"); const VkBufferCopy regions[] = { { .srcOffset = 0, @@ -1239,6 +1240,7 @@ vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); + DEBUG_END(cmdbuf); } return (vk_lights_bindings_t){ From 9f6fa0a8cbf98337ba043dde0b1f0d6360515f22 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Fri, 12 Aug 2022 10:11:00 -0700 Subject: [PATCH 427/548] rt: make lights data more private --- ref_vk/vk_light.c | 106 ++++++++++++++++++++++++++-------------------- ref_vk/vk_light.h | 18 +------- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index e2d8916a..13e67b9c 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -53,6 +53,22 @@ static struct { vk_buffer_t staging; vk_buffer_t buffer; + + int num_polygons; + rt_light_polygon_t polygons[MAX_SURFACE_LIGHTS]; + + int num_point_lights; + vk_point_light_t point_lights[MAX_POINT_LIGHTS]; + + int num_polygon_vertices; + vec3_t polygon_vertices[MAX_SURFACE_LIGHTS * 7]; + + struct { + int point_lights; + int polygons; + int polygon_vertices; + } num_static; + } g_lights_; static struct { @@ -553,9 +569,9 @@ void RT_LightsNewMapBegin( const struct model_s *map ) { // Clear static lights counts { - g_lights.num_polygons = g_lights.num_static.polygons = 0; - g_lights.num_point_lights = g_lights.num_static.point_lights = 0; - g_lights.num_polygon_vertices = g_lights.num_static.polygon_vertices = 0; + g_lights_.num_polygons = g_lights_.num_static.polygons = 0; + g_lights_.num_point_lights = g_lights_.num_static.point_lights = 0; + g_lights_.num_polygon_vertices = g_lights_.num_static.polygon_vertices = 0; for (int i = 0; i < g_lights.map.grid_cells; ++i) { vk_lights_cell_t *const cell = g_lights.cells + i; @@ -566,9 +582,9 @@ void RT_LightsNewMapBegin( const struct model_s *map ) { } void RT_LightsFrameBegin( void ) { - g_lights.num_polygons = g_lights.num_static.polygons; - g_lights.num_point_lights = g_lights.num_static.point_lights; - g_lights.num_polygon_vertices = g_lights.num_static.polygon_vertices; + g_lights_.num_polygons = g_lights_.num_static.polygons; + g_lights_.num_point_lights = g_lights_.num_static.point_lights; + g_lights_.num_polygon_vertices = g_lights_.num_static.polygon_vertices; for (int i = 0; i < g_lights.map.grid_cells; ++i) { vk_lights_cell_t *const cell = g_lights.cells + i; @@ -702,7 +718,7 @@ static void addPointLightToClusters( int index ) { return; } - vk_point_light_t *const light = g_lights.point_lights + index; + vk_point_light_t *const light = g_lights_.point_lights + index; const mleaf_t* leaf = gEngine.Mod_PointInLeaf(light->origin, world->nodes); const vk_light_leaf_set_t *const leafs = (vk_light_leaf_set_t*)&g_lights_bsp.accum.count; @@ -718,10 +734,10 @@ static void addPointLightToClusters( int index ) { } static int addPointLight( const vec3_t origin, const vec3_t color, float radius, int lightstyle, float hack_attenuation ) { - const int index = g_lights.num_point_lights; - vk_point_light_t *const plight = g_lights.point_lights + index; + const int index = g_lights_.num_point_lights; + vk_point_light_t *const plight = g_lights_.point_lights + index; - if (g_lights.num_point_lights >= MAX_POINT_LIGHTS) { + if (g_lights_.num_point_lights >= MAX_POINT_LIGHTS) { ERROR_THROTTLED(10, "Too many lights, MAX_POINT_LIGHTS=%d", MAX_POINT_LIGHTS); return -1; } @@ -745,15 +761,15 @@ static int addPointLight( const vec3_t origin, const vec3_t color, float radius, VectorSet(plight->dir, 0, 0, 0); addPointLightToClusters( index ); - g_lights.num_point_lights++; + g_lights_.num_point_lights++; return index; } static int addSpotLight( const vk_light_entity_t *le, float radius, int lightstyle, float hack_attenuation, qboolean all_clusters ) { - const int index = g_lights.num_point_lights; - vk_point_light_t *const plight = g_lights.point_lights + index; + const int index = g_lights_.num_point_lights; + vk_point_light_t *const plight = g_lights_.point_lights + index; - if (g_lights.num_point_lights >= MAX_POINT_LIGHTS) { + if (g_lights_.num_point_lights >= MAX_POINT_LIGHTS) { ERROR_THROTTLED(10, "Too many lights, MAX_POINT_LIGHTS=%d", MAX_POINT_LIGHTS); return -1; } @@ -787,7 +803,7 @@ static int addSpotLight( const vk_light_entity_t *le, float radius, int lightsty else addPointLightToClusters( index ); - g_lights.num_point_lights++; + g_lights_.num_point_lights++; return index; } @@ -921,7 +937,7 @@ static void processStaticPointLights( void ) { const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); ASSERT(world); - g_lights.num_point_lights = 0; + g_lights_.num_point_lights = 0; for (int i = 0; i < g_map_entities.num_lights; ++i) { const vk_light_entity_t *le = g_map_entities.lights + i; const float default_radius = 2.f; // FIXME tune @@ -958,9 +974,9 @@ void RT_LightsNewMapEnd( const struct model_s *map ) { // Fix static counts { - g_lights.num_static.polygons = g_lights.num_polygons; - g_lights.num_static.point_lights = g_lights.num_point_lights; - g_lights.num_static.polygon_vertices = g_lights.num_polygon_vertices; + g_lights_.num_static.polygons = g_lights_.num_polygons; + g_lights_.num_static.point_lights = g_lights_.num_point_lights; + g_lights_.num_static.polygon_vertices = g_lights_.num_polygon_vertices; for (int i = 0; i < g_lights.map.grid_cells; ++i) { vk_lights_cell_t *const cell = g_lights.cells + i; @@ -1073,21 +1089,21 @@ static void addPolygonLeafSetToClusters(const vk_light_leaf_set_t *leafs, int po } int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { - if (g_lights.num_polygons == MAX_SURFACE_LIGHTS) { + if (g_lights_.num_polygons == MAX_SURFACE_LIGHTS) { gEngine.Con_Printf(S_ERROR "Max number of polygon lights %d reached\n", MAX_SURFACE_LIGHTS); return -1; } ASSERT(addpoly->num_vertices > 2); ASSERT(addpoly->num_vertices < 8); - ASSERT(g_lights.num_polygon_vertices + addpoly->num_vertices <= COUNTOF(g_lights.polygon_vertices)); + ASSERT(g_lights_.num_polygon_vertices + addpoly->num_vertices <= COUNTOF(g_lights_.polygon_vertices)); { - rt_light_polygon_t *const poly = g_lights.polygons + g_lights.num_polygons; - vec3_t *vertices = g_lights.polygon_vertices + g_lights.num_polygon_vertices; + rt_light_polygon_t *const poly = g_lights_.polygons + g_lights_.num_polygons; + vec3_t *vertices = g_lights_.polygon_vertices + g_lights_.num_polygon_vertices; vec3_t normal; - poly->vertices.offset = g_lights.num_polygon_vertices; + poly->vertices.offset = g_lights_.num_polygon_vertices; poly->vertices.count = addpoly->num_vertices; VectorCopy(addpoly->emissive, poly->emissive); @@ -1118,7 +1134,7 @@ int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { if (!addpoly->dynamic || debug_dump_lights.enabled) { gEngine.Con_Reportf("added polygon light index=%d color=(%f, %f, %f) center=(%f, %f, %f) plane=(%f, %f, %f, %f) area=%f num_vertices=%d\n", - g_lights.num_polygons, + g_lights_.num_polygons, poly->emissive[0], poly->emissive[1], poly->emissive[2], @@ -1139,13 +1155,13 @@ int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { const vk_light_leaf_set_t *const leafs = addpoly->dynamic ? getMapLeafsAffectedByMovingSurface( addpoly->surface, addpoly->transform_row ) : getMapLeafsAffectedByMapSurface( addpoly->surface ); - addPolygonLeafSetToClusters(leafs, g_lights.num_polygons); + addPolygonLeafSetToClusters(leafs, g_lights_.num_polygons); } else { - addPolygonLightToAllClusters( g_lights.num_polygons ); + addPolygonLightToAllClusters( g_lights_.num_polygons ); } - g_lights.num_polygon_vertices += addpoly->num_vertices; - return g_lights.num_polygons++; + g_lights_.num_polygon_vertices += addpoly->num_vertices; + return g_lights_.num_polygons++; } } @@ -1171,10 +1187,10 @@ vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { struct Lights *lights = g_lights_.staging.mapped; struct LightsMetadata *metadata = &lights->metadata; - ASSERT(g_lights.num_polygons <= MAX_EMISSIVE_KUSOCHKI); - metadata->num_polygons = g_lights.num_polygons; - for (int i = 0; i < g_lights.num_polygons; ++i) { - const rt_light_polygon_t *const src_poly = g_lights.polygons + i; + ASSERT(g_lights_.num_polygons <= MAX_EMISSIVE_KUSOCHKI); + metadata->num_polygons = g_lights_.num_polygons; + for (int i = 0; i < g_lights_.num_polygons; ++i) { + const rt_light_polygon_t *const src_poly = g_lights_.polygons + i; struct PolygonLight *const dst_poly = metadata->polygons + i; Vector4Copy(src_poly->plane, dst_poly->plane); @@ -1192,9 +1208,9 @@ vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { dst_poly->vertices_count_offset = (src_poly->vertices.count << 16) | (src_poly->vertices.offset); } - metadata->num_point_lights = g_lights.num_point_lights; - for (int i = 0; i < g_lights.num_point_lights; ++i) { - vk_point_light_t *const src = g_lights.point_lights + i; + metadata->num_point_lights = g_lights_.num_point_lights; + for (int i = 0; i < g_lights_.num_point_lights; ++i) { + vk_point_light_t *const src = g_lights_.point_lights + i; struct PointLight *const dst = metadata->point_lights + i; VectorCopy(src->origin, dst->origin_r); @@ -1212,9 +1228,9 @@ vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { uploadGrid( &lights->grid ); // TODO static assert - ASSERT(sizeof(metadata->polygon_vertices) >= sizeof(g_lights.polygon_vertices)); - for (int i = 0; i < g_lights.num_polygon_vertices; ++i) { - VectorCopy(g_lights.polygon_vertices[i], metadata->polygon_vertices[i]); + ASSERT(sizeof(metadata->polygon_vertices) >= sizeof(g_lights_.polygon_vertices)); + for (int i = 0; i < g_lights_.num_polygon_vertices; ++i) { + VectorCopy(g_lights_.polygon_vertices[i], metadata->polygon_vertices[i]); } { @@ -1260,9 +1276,9 @@ void RT_LightsFrameEnd( void ) { APROF_SCOPE_BEGIN_EARLY(finalize); const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); - if (g_lights.num_polygons > UINT8_MAX) { - ERROR_THROTTLED(10, "Too many emissive surfaces found: %d; some areas will be dark", g_lights.num_polygons); - g_lights.num_polygons = UINT8_MAX; + if (g_lights_.num_polygons > UINT8_MAX) { + ERROR_THROTTLED(10, "Too many emissive surfaces found: %d; some areas will be dark", g_lights_.num_polygons); + g_lights_.num_polygons = UINT8_MAX; } /* for (int i = 0; i < MAX_ELIGHTS; ++i) { */ @@ -1273,8 +1289,8 @@ void RT_LightsFrameEnd( void ) { /* } */ /* } */ - for (int i = 0; i < g_lights.num_point_lights; ++i) { - vk_point_light_t *const light = g_lights.point_lights + i; + for (int i = 0; i < g_lights_.num_point_lights; ++i) { + vk_point_light_t *const light = g_lights_.point_lights + i; if (light->lightstyle < 0 || light->lightstyle >= MAX_LIGHTSTYLES) continue; @@ -1296,7 +1312,7 @@ void RT_LightsFrameEnd( void ) { if (debug_dump_lights.enabled) { #if 0 // Print light grid stats - gEngine.Con_Reportf("Emissive surfaces found: %d\n", g_lights.num_polygons); + gEngine.Con_Reportf("Emissive surfaces found: %d\n", g_lights_.num_polygons); { #define GROUPSIZE 4 diff --git a/ref_vk/vk_light.h b/ref_vk/vk_light.h index d117ba98..0aee4eee 100644 --- a/ref_vk/vk_light.h +++ b/ref_vk/vk_light.h @@ -48,8 +48,7 @@ typedef struct { vec3_t base_color; } vk_point_light_t; -// TODO spotlight - +// Used by infotool typedef struct { struct { int grid_min_cell[3]; @@ -57,21 +56,6 @@ typedef struct { int grid_cells; } map; - int num_polygons; - rt_light_polygon_t polygons[MAX_SURFACE_LIGHTS]; - - int num_point_lights; - vk_point_light_t point_lights[MAX_POINT_LIGHTS]; - - int num_polygon_vertices; - vec3_t polygon_vertices[MAX_SURFACE_LIGHTS * 7]; - - struct { - int point_lights; - int polygons; - int polygon_vertices; - } num_static; - vk_lights_cell_t cells[MAX_LIGHT_CLUSTERS]; } vk_lights_t; From c53f22d028b1924482d07d04e193d2c36a597e44 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Fri, 12 Aug 2022 12:31:58 -0700 Subject: [PATCH 428/548] rt: extract bit array --- ref_vk/bitarray.c | 38 ++++++++++++++++++++++++++++ ref_vk/bitarray.h | 15 +++++++++++ ref_vk/vk_light.c | 63 +++++++++++------------------------------------ 3 files changed, 67 insertions(+), 49 deletions(-) create mode 100644 ref_vk/bitarray.c create mode 100644 ref_vk/bitarray.h diff --git a/ref_vk/bitarray.c b/ref_vk/bitarray.c new file mode 100644 index 00000000..a4cd6525 --- /dev/null +++ b/ref_vk/bitarray.c @@ -0,0 +1,38 @@ +#include "bitarray.h" + +#include "vk_core.h" + +bit_array_t bitArrayCreate(uint32_t size) { + size = (size + 31) / 32; + bit_array_t ret = { + .size = size, + .bits = Mem_Malloc(vk_core.pool, size * sizeof(uint32_t)) + }; + bitArrayClear(&ret); + return ret; +} + +void bitArrayDestroy(bit_array_t *ba) { + if (ba->bits) + Mem_Free(ba->bits); + ba->bits = NULL; + ba->size = 0; +} + +void bitArrayClear(bit_array_t *ba) { + memset(ba->bits, 0, ba->size * sizeof(uint32_t)); +} + +qboolean bitArrayCheckOrSet(bit_array_t *ba, uint32_t index) { + const uint32_t offset = index / 32; + ASSERT(offset < ba->size); + + uint32_t* bits = ba->bits + offset; + + const uint32_t bit = 1u << (index % 32); + if ((*bits) & bit) + return false; + + (*bits) |= bit; + return true; +} diff --git a/ref_vk/bitarray.h b/ref_vk/bitarray.h new file mode 100644 index 00000000..433bbcfc --- /dev/null +++ b/ref_vk/bitarray.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +typedef struct { + uint32_t size; + uint32_t *bits; +} bit_array_t; + +bit_array_t bitArrayCreate(uint32_t size); +void bitArrayDestroy(bit_array_t *ba); +void bitArrayClear(bit_array_t *ba); + +// Returns true if wasn't set +qboolean bitArrayCheckOrSet(bit_array_t *ba, uint32_t index); diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 13e67b9c..f0b7f6e7 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -6,6 +6,7 @@ #include "vk_cvar.h" #include "vk_common.h" #include "shaders/ray_interop.h" +#include "bitarray.h" #include "profiler.h" #include "mod_local.h" @@ -69,6 +70,8 @@ static struct { int polygon_vertices; } num_static; + bit_array_t visited_cells; + } g_lights_; static struct { @@ -108,14 +111,12 @@ qboolean VK_LightsInit( void ) { return true; } -static void clusterBitMapShutdown( void ); - void VK_LightsShutdown( void ) { VK_BufferDestroy(&g_lights_.staging); VK_BufferDestroy(&g_lights_.buffer); gEngine.Cmd_RemoveCommand("vk_lights_dump"); - clusterBitMapShutdown(); + bitArrayDestroy(&g_lights_.visited_cells); } typedef struct { @@ -389,41 +390,6 @@ vk_light_leaf_set_t *getMapLeafsAffectedByMapSurface( const msurface_t *surf ) { return smeta->potentially_visible_leafs; } - -static struct { -#define CLUSTERS_BIT_MAP_SIZE_UINT ((g_lights.map.grid_cells + 31) / 32) - uint32_t *clusters_bit_map; -} g_lights_tmp; - -static void clusterBitMapClear( void ) { - memset(g_lights_tmp.clusters_bit_map, 0, CLUSTERS_BIT_MAP_SIZE_UINT * sizeof(uint32_t)); -} - -// Returns true if wasn't set -static qboolean clusterBitMapCheckOrSet( int cell_index ) { - uint32_t *const bits = g_lights_tmp.clusters_bit_map + (cell_index / 32); - const uint32_t bit = 1u << (cell_index % 32); - - if ((*bits) & bit) - return false; - - (*bits) |= bit; - return true; -} - -static void clusterBitMapInit( void ) { - ASSERT(!g_lights_tmp.clusters_bit_map); - - g_lights_tmp.clusters_bit_map = Mem_Malloc(vk_core.pool, CLUSTERS_BIT_MAP_SIZE_UINT * sizeof(uint32_t)); - clusterBitMapClear(); -} - -static void clusterBitMapShutdown( void ) { - if (g_lights_tmp.clusters_bit_map) - Mem_Free(g_lights_tmp.clusters_bit_map); - g_lights_tmp.clusters_bit_map = NULL; -} - int RT_LightCellIndex( const int light_cell[3] ) { if (light_cell[0] < 0 || light_cell[1] < 0 || light_cell[2] < 0 || (light_cell[0] >= g_lights.map.grid_size[0]) @@ -548,8 +514,8 @@ void RT_LightsNewMapBegin( const struct model_s *map ) { g_lights.map.grid_cells ); - clusterBitMapShutdown(); - clusterBitMapInit(); + bitArrayDestroy(&g_lights_.visited_cells); + g_lights_.visited_cells = bitArrayCreate(g_lights.map.grid_cells); prepareSurfacesLeafVisibilityCache( map ); @@ -688,7 +654,7 @@ static void addLightIndexToLeaf( const mleaf_t *leaf, int index ) { if (cell_index < 0) continue; - if (clusterBitMapCheckOrSet( cell_index )) { + if (bitArrayCheckOrSet(&g_lights_.visited_cells, cell_index)) { if (!addLightToCell(cell_index, index)) { ERROR_THROTTLED(10, "Cluster %d,%d,%d(%d) ran out of light slots", cell[0], cell[1], cell[2], cell_index); @@ -700,10 +666,10 @@ static void addLightIndexToLeaf( const mleaf_t *leaf, int index ) { static void addPointLightToAllClusters( int index ) { const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); - // TODO there's certainly a better way to do this: just enumerate + // FIXME there's certainly a better way to do this: just enumerate // all clusters, not all leafs - clusterBitMapClear(); + bitArrayClear(&g_lights_.visited_cells); for (int i = 1; i <= world->numleafs; ++i) { const mleaf_t *const leaf = world->leafs + i; addLightIndexToLeaf( leaf, index ); @@ -726,7 +692,7 @@ static void addPointLightToClusters( int index ) { leafAccumAddPotentiallyVisibleFromLeaf( world, leaf, false); leafAccumFinalize(); - clusterBitMapClear(); + bitArrayClear(&g_lights_.visited_cells); for (int i = 0; i < leafs->num; ++i) { const mleaf_t *const leaf = world->leafs + leafs->leafs[i]; addLightIndexToLeaf( leaf, index ); @@ -1001,7 +967,6 @@ qboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id ) { } } - static void addPolygonLightIndexToLeaf(const mleaf_t* leaf, int poly_index) { const int min_x = floorf(leaf->minmaxs[0] / LIGHT_GRID_CELL_SIZE); const int min_y = floorf(leaf->minmaxs[1] / LIGHT_GRID_CELL_SIZE); @@ -1038,7 +1003,7 @@ static void addPolygonLightIndexToLeaf(const mleaf_t* leaf, int poly_index) { if (cell_index < 0) continue; - if (clusterBitMapCheckOrSet( cell_index )) { + if (bitArrayCheckOrSet(&g_lights_.visited_cells, cell_index)) { const float minmaxs[6] = { x * LIGHT_GRID_CELL_SIZE, y * LIGHT_GRID_CELL_SIZE, @@ -1062,10 +1027,10 @@ static void addPolygonLightIndexToLeaf(const mleaf_t* leaf, int poly_index) { static void addPolygonLightToAllClusters( int poly_index ) { const model_t* const world = gEngine.pfnGetModelByIndex( 1 ); - // TODO there's certainly a better way to do this: just enumerate + // FIXME there's certainly a better way to do this: just enumerate // all clusters, not all leafs - clusterBitMapClear(); + bitArrayClear(&g_lights_.visited_cells); for (int i = 1; i <= world->numleafs; ++i) { const mleaf_t *const leaf = world->leafs + i; addPolygonLightIndexToLeaf( leaf, poly_index ); @@ -1079,7 +1044,7 @@ static void addPolygonLeafSetToClusters(const vk_light_leaf_set_t *leafs, int po if (!leafs) return; - clusterBitMapClear(); + bitArrayClear(&g_lights_.visited_cells); // Iterate through each visible/potentially affected leaf to get a range of grid cells for (int i = 0; i < leafs->num; ++i) { From 188d02c4aa7472013fc6e9c0862f92b428bd1665 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Fri, 12 Aug 2022 12:32:52 -0700 Subject: [PATCH 429/548] rt: fix shader reloading validation crashes wait for gpu to become idle before trying to recreate pipelines --- ref_vk/vk_rtx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 50985a59..2347d821 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -668,6 +668,7 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) if (g_rtx.reload_pipeline) { gEngine.Con_Printf(S_WARN "Reloading RTX shaders/pipelines\n"); + XVK_CHECK(vkDeviceWaitIdle(vk_core.device)); reloadPass( &g_rtx.pass.primary_ray, R_VkRayPrimaryPassCreate()); reloadPass( &g_rtx.pass.light_direct_poly, R_VkRayLightDirectPolyPassCreate()); From 41e5757c0a080991eb4288731375b173cd443ecd Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Fri, 12 Aug 2022 12:34:37 -0700 Subject: [PATCH 430/548] FIXME: rt: try disabling light grid entirely this skips 40ms+ grid upload cost, and seems generally just a few ms more costly than sampling clusters. however, there are sync glitches for reasons unknown. --- ref_vk/shaders/light.glsl | 28 +++++++++++++++++++++++++++- ref_vk/shaders/light_polygon.glsl | 10 ++++++++-- ref_vk/vk_light.c | 6 ++++-- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index 8bef97a6..b1da0dba 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -2,7 +2,7 @@ layout (set = 0, binding = BINDING_LIGHTS) readonly buffer UBOLights { LightsMet layout (set = 0, binding = BINDING_LIGHT_CLUSTERS, align = 1) readonly buffer UBOLightClusters { ivec3 grid_min, grid_size; //uint8_t clusters_data[MAX_LIGHT_CLUSTERS * LIGHT_CLUSTER_SIZE + HACK_OFFSET]; - LightCluster clusters[MAX_LIGHT_CLUSTERS]; + LightCluster clusters_[MAX_LIGHT_CLUSTERS]; } light_grid; const float color_culling_threshold = 0;//600./color_factor; @@ -18,9 +18,16 @@ const float shadow_offset_fudge = .1; #if LIGHT_POINT void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { diffuse = specular = vec3(0.); + + //diffuse = vec3(1.);//float(lights.num_point_lights) / 64.); +//#define USE_CLUSTERS +#ifdef USE_CLUSTERS const uint num_point_lights = uint(light_grid.clusters[cluster_index].num_point_lights); for (uint j = 0; j < num_point_lights; ++j) { const uint i = uint(light_grid.clusters[cluster_index].point_lights[j]); +#else + for (uint i = 0; i < lights.num_point_lights; ++i) { +#endif vec3 color = lights.point_lights[i].color_stopdot.rgb * throughput; if (dot(color,color) < color_culling_threshold) @@ -115,6 +122,9 @@ void computeLighting(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialPro if (any(greaterThanEqual(light_cell, light_grid.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) return; // throughput * vec3(1., 0., 0.); + //diffuse = specular = vec3(1.); + //return; + // const uint cluster_offset = cluster_index * LIGHT_CLUSTER_SIZE + HACK_OFFSET; // const int num_dlights = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_DLIGHTS_OFFSET]); // const int num_emissive_surfaces = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_EMISSIVE_SURFACES_OFFSET]); @@ -128,10 +138,26 @@ void computeLighting(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialPro sampleEmissiveSurfaces(P, N, throughput, view_dir, material, cluster_index, diffuse, specular); #endif + #if LIGHT_POINT vec3 ldiffuse = vec3(0.), lspecular = vec3(0.); computePointLights(P, N, cluster_index, throughput, view_dir, material, ldiffuse, lspecular); diffuse += ldiffuse; specular += lspecular; #endif + + if (any(isnan(diffuse))) + diffuse = vec3(1.,0.,0.); + + if (any(isnan(specular))) + specular = vec3(0.,1.,0.); + + if (any(lessThan(diffuse,vec3(0.)))) + diffuse = vec3(1., 0., 1.); + + if (any(lessThan(specular,vec3(0.)))) + specular = vec3(0., 1., 1.); + + //specular = vec3(0.,1.,0.); + //diffuse = vec3(0.); } diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index c4933724..1ba25c34 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -211,9 +211,16 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { #if DO_ALL_IN_CLUSTER const SampleContext ctx = buildSampleContext(P, N, view_dir); + +//#define USE_CLUSTERS +#ifdef USE_CLUSTERS const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); for (uint i = 0; i < num_polygons; ++i) { const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); +#else + for (uint index = 0; index < lights.num_polygons; ++index) { +#endif + const PolygonLight poly = lights.polygons[index]; const float plane_dist = dot(poly.plane, vec4(P, 1.f)); @@ -246,9 +253,8 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate specular += throughput * emissive * estimate * poly_specular; } } -#else +#else // DO_ALL_IN_CLUSTERS -#define USE_CLUSTERS #ifdef USE_CLUSTERS // TODO move this to pickPolygonLight function const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index f0b7f6e7..4ae60dd6 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -1200,11 +1200,13 @@ vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { { DEBUG_BEGIN(cmdbuf, "upload lights"); + const uint32_t size = sizeof(struct LightsMetadata) + 8 * sizeof(uint32_t); // .... + //const uint32_t size = sizeof(struct Lights); const VkBufferCopy regions[] = { { .srcOffset = 0, .dstOffset = 0, - .size = sizeof(struct Lights), + .size = size, }, }; vkCmdCopyBuffer(cmdbuf, g_lights_.staging.buffer, g_lights_.buffer.buffer, COUNTOF(regions), regions); @@ -1215,7 +1217,7 @@ vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, .buffer = g_lights_.buffer.buffer, .offset = 0, - .size = VK_WHOLE_SIZE, + .size = size, }}; vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TRANSFER_BIT, From 7f3dca099324d742ccf348a6080e7a259a2bb6b6 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Mon, 15 Aug 2022 09:20:42 -0700 Subject: [PATCH 431/548] rt: use proper staging for light uploading also don't upload clusters as we're thinking about sunsetting them in their current form. --- ref_vk/shaders/light.glsl | 3 ++ ref_vk/vk_light.c | 75 ++++++++++++++++----------------------- ref_vk/vk_rtx.c | 18 ++++++++++ ref_vk/vk_staging.c | 2 +- 4 files changed, 52 insertions(+), 46 deletions(-) diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index b1da0dba..d9b715e3 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -116,11 +116,14 @@ void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec void computeLighting(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { diffuse = specular = vec3(0.); + const ivec3 light_cell = ivec3(floor(P / LIGHT_GRID_CELL_SIZE)) - light_grid.grid_min; const uint cluster_index = uint(dot(light_cell, ivec3(1, light_grid.grid_size.x, light_grid.grid_size.x * light_grid.grid_size.y))); +#ifdef USE_CLUSTERS if (any(greaterThanEqual(light_cell, light_grid.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) return; // throughput * vec3(1., 0., 0.); +#endif //diffuse = specular = vec3(1.); //return; diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 4ae60dd6..c9b8c5ec 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -8,6 +8,7 @@ #include "shaders/ray_interop.h" #include "bitarray.h" #include "profiler.h" +#include "vk_staging.h" #include "mod_local.h" #include "xash3d_mathlib.h" @@ -52,7 +53,6 @@ static struct { vk_emissive_texture_t emissive_textures[MAX_TEXTURES]; } map; - vk_buffer_t staging; vk_buffer_t buffer; int num_polygons; @@ -102,17 +102,10 @@ qboolean VK_LightsInit( void ) { return false; } - if (!VK_BufferCreate("rt lights staging buffer", &g_lights_.staging, sizeof(struct Lights), - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { - // FIXME complain, handle - return false; - } - return true; } void VK_LightsShutdown( void ) { - VK_BufferDestroy(&g_lights_.staging); VK_BufferDestroy(&g_lights_.buffer); gEngine.Cmd_RemoveCommand("vk_lights_dump"); @@ -1147,11 +1140,7 @@ static void uploadGrid( vk_ray_shader_light_grid_t *grid ) { } } -vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { - // Upload polygon lights - struct Lights *lights = g_lights_.staging.mapped; - struct LightsMetadata *metadata = &lights->metadata; - +static void uploadPolygonLights( struct LightsMetadata *metadata ) { ASSERT(g_lights_.num_polygons <= MAX_EMISSIVE_KUSOCHKI); metadata->num_polygons = g_lights_.num_polygons; for (int i = 0; i < g_lights_.num_polygons; ++i) { @@ -1173,6 +1162,14 @@ vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { dst_poly->vertices_count_offset = (src_poly->vertices.count << 16) | (src_poly->vertices.offset); } + // TODO static assert + ASSERT(sizeof(metadata->polygon_vertices) >= sizeof(g_lights_.polygon_vertices)); + for (int i = 0; i < g_lights_.num_polygon_vertices; ++i) { + VectorCopy(g_lights_.polygon_vertices[i], metadata->polygon_vertices[i]); + } +} + +static void uploadPointLights( struct LightsMetadata *metadata ) { metadata->num_point_lights = g_lights_.num_point_lights; for (int i = 0; i < g_lights_.num_point_lights; ++i) { vk_point_light_t *const src = g_lights_.point_lights + i; @@ -1189,42 +1186,30 @@ vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { dst->environment = !!(src->flags & LightFlag_Environment); } +} - uploadGrid( &lights->grid ); +vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { - // TODO static assert - ASSERT(sizeof(metadata->polygon_vertices) >= sizeof(g_lights_.polygon_vertices)); - for (int i = 0; i < g_lights_.num_polygon_vertices; ++i) { - VectorCopy(g_lights_.polygon_vertices[i], metadata->polygon_vertices[i]); - } + const vk_staging_region_t locked = R_VkStagingLockForBuffer( (vk_staging_buffer_args_t) { + .buffer = g_lights_.buffer.buffer, + .offset = 0, + .size = sizeof(struct LightsMetadata), + .alignment = 16, + } ); - { - DEBUG_BEGIN(cmdbuf, "upload lights"); - const uint32_t size = sizeof(struct LightsMetadata) + 8 * sizeof(uint32_t); // .... - //const uint32_t size = sizeof(struct Lights); - const VkBufferCopy regions[] = { - { - .srcOffset = 0, - .dstOffset = 0, - .size = size, - }, - }; - vkCmdCopyBuffer(cmdbuf, g_lights_.staging.buffer, g_lights_.buffer.buffer, COUNTOF(regions), regions); + ASSERT(locked.ptr); - const VkBufferMemoryBarrier bmb[] = {{ - .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, - .buffer = g_lights_.buffer.buffer, - .offset = 0, - .size = size, - }}; - vkCmdPipelineBarrier(cmdbuf, - VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, - 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); - DEBUG_END(cmdbuf); - } + struct LightsMetadata *metadata = locked.ptr; + + uploadPolygonLights( metadata ); + uploadPointLights( metadata ); + + // FIXME uploadGrid( &lights->grid ); + + R_VkStagingUnlock( locked.handle ); + + // TODO probably should do this somewhere else + R_VkStagingCommit( cmdbuf ); return (vk_lights_bindings_t){ .buffer = g_lights_.buffer.buffer, diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 2347d821..2111834b 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -612,6 +612,24 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* } RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.primary_ray, &res ); + + { + //const uint32_t size = sizeof(struct Lights); + //const uint32_t size = sizeof(struct LightsMetadata); // + 8 * sizeof(uint32_t); + const VkBufferMemoryBarrier bmb[] = {{ + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .buffer = args->light_bindings->buffer, + .offset = 0, + .size = VK_WHOLE_SIZE, + }}; + vkCmdPipelineBarrier(cmdbuf, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, + 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); + } + RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.light_direct_poly, &res ); RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.light_direct_point, &res ); RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.denoiser, &res ); diff --git a/ref_vk/vk_staging.c b/ref_vk/vk_staging.c index 63fba5eb..288edf69 100644 --- a/ref_vk/vk_staging.c +++ b/ref_vk/vk_staging.c @@ -55,7 +55,7 @@ vk_staging_region_t R_VkStagingLockForBuffer(vk_staging_buffer_args_t args) { const int index = g_staging.buffers.count; - const uint32_t offset = aloRingAlloc(&g_staging.ring, args.size, args.alignment); + const uint32_t offset = aloRingAlloc(&g_staging.ring, args.size, args.alignment < 1 ? 1 : args.alignment ); if (offset == ALO_ALLOC_FAILED) return (vk_staging_region_t){0}; if (g_staging.frames[1].offset == ALO_ALLOC_FAILED) From 501c3d15add3bea3d85edcf49a299d2fd26816be Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 20 Aug 2022 12:13:03 -0700 Subject: [PATCH 432/548] rt: use staging to upload kusochki fixes more weird square glitches breaks animated textures, colors and conveyors --- ref_vk/vk_geometry.c | 2 +- ref_vk/vk_ray_model.c | 106 +++++++++++++++++++++++++++++++++++++++++- ref_vk/vk_render.c | 17 ------- ref_vk/vk_rtx.c | 4 +- 4 files changed, 107 insertions(+), 22 deletions(-) diff --git a/ref_vk/vk_geometry.c b/ref_vk/vk_geometry.c index 65cf3244..fd6609c1 100644 --- a/ref_vk/vk_geometry.c +++ b/ref_vk/vk_geometry.c @@ -80,7 +80,7 @@ qboolean R_GeometryBuffer_Init(void) { if (!VK_BufferCreate("geometry buffer", &g_geom.buffer, GEOMETRY_BUFFER_SIZE, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | (vk_core.rtx ? VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR : 0), - (vk_core.rtx ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : 0))) // TODO staging buffer? + (vk_core.rtx ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : 0))) return false; R_GeometryBuffer_MapClear(); diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index c9719a01..617ef2da 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -5,6 +5,7 @@ #include "vk_materials.h" #include "vk_geometry.h" #include "vk_render.h" +#include "vk_staging.h" #include "vk_light.h" #include "eiface.h" @@ -152,12 +153,62 @@ void XVK_RayModel_Validate( void ) { } } +static void applyMaterialToKusok( vk_kusok_data_t* kusok, const vk_render_geometry_t *geom, const vec3_t color) { + const xvk_material_t *const mat = XVK_GetMaterialForTextureIndex( geom->texture ); + ASSERT(mat); + + /* if (!render_model->static_map) */ + /* VK_LightsAddEmissiveSurface( geom, transform_row, false ); */ + + kusok->tex_base_color = mat->tex_base_color; + kusok->tex_roughness = mat->tex_roughness; + kusok->tex_metalness = mat->tex_metalness; + kusok->tex_normalmap = mat->tex_normalmap; + + kusok->roughness = mat->roughness; + kusok->metalness = mat->metalness; + +/* FIXME + // HACK until there is a proper mechanism for patching materials, see https://github.com/w23/xash3d-fwgs/issues/213 + // FIXME also this erases previous roughness unconditionally + if (HACK_reflective) { + kusok->tex_roughness = tglob.blackTexture; + } else if (!mat->set && geom->material == kXVkMaterialChrome) { + kusok->tex_roughness = tglob.grayTexture; + } +*/ + + if (geom->material == kXVkMaterialSky) + kusok->tex_base_color |= KUSOK_MATERIAL_FLAG_SKYBOX; + + { + vec4_t gcolor; + gcolor[0] = color[0] * mat->base_color[0]; + gcolor[1] = color[1] * mat->base_color[1]; + gcolor[2] = color[2] * mat->base_color[2]; + gcolor[3] = color[3]; + Vector4Copy(gcolor, kusok->color); + } + + if (geom->material == kXVkMaterialEmissive) { + VectorCopy( geom->emissive, kusok->emissive ); + } else { + RT_GetEmissiveForTexture( kusok->emissive, geom->texture ); + } + +/* FIXME + if (geom->material == kXVkMaterialConveyor) { + computeConveyorSpeed( entcolor, geom->texture, kusok->uv_speed ); + } else */ { + kusok->uv_speed[0] = kusok->uv_speed[1] = 0.f; + } +} + vk_ray_model_t* VK_RayModelCreate( VkCommandBuffer cmdbuf, vk_ray_model_init_t args ) { VkAccelerationStructureGeometryKHR *geoms; uint32_t *geom_max_prim_counts; VkAccelerationStructureBuildRangeInfoKHR *geom_build_ranges; const VkDeviceAddress buffer_addr = getBufferDeviceAddress(args.buffer); - vk_kusok_data_t *kusochki; const uint32_t kusochki_count_offset = R_DEBuffer_Alloc(&g_ray_model_state.kusochki_alloc, args.model->dynamic ? LifetimeDynamic : LifetimeStatic, args.model->num_geometries, 1); vk_ray_model_t *ray_model; int max_prims = 0; @@ -172,12 +223,26 @@ vk_ray_model_t* VK_RayModelCreate( VkCommandBuffer cmdbuf, vk_ray_model_init_t a return NULL; } + const vk_staging_buffer_args_t staging_args = { + .buffer = g_ray_model_state.kusochki_buffer.buffer, + .offset = kusochki_count_offset * sizeof(vk_kusok_data_t), + .size = args.model->num_geometries * sizeof(vk_kusok_data_t), + .alignment = 16, + }; + const vk_staging_region_t kusok_staging = R_VkStagingLockForBuffer(staging_args); + + if (!kusok_staging.ptr) { + gEngine.Con_Printf(S_ERROR "Couldn't allocate staging for %d kusochkov for model %s\n", args.model->num_geometries, args.model->debug_name); + return NULL; + } + + vk_kusok_data_t *const kusochki = kusok_staging.ptr; + // FIXME don't touch allocator each frame many times pls geoms = Mem_Calloc(vk_core.pool, args.model->num_geometries * sizeof(*geoms)); geom_max_prim_counts = Mem_Malloc(vk_core.pool, args.model->num_geometries * sizeof(*geom_max_prim_counts)); geom_build_ranges = Mem_Calloc(vk_core.pool, args.model->num_geometries * sizeof(*geom_build_ranges)); - kusochki = (vk_kusok_data_t*)(g_ray_model_state.kusochki_buffer.mapped) + kusochki_count_offset; for (int i = 0; i < args.model->num_geometries; ++i) { vk_render_geometry_t *mg = args.model->geometries + i; @@ -228,9 +293,41 @@ vk_ray_model_t* VK_RayModelCreate( VkCommandBuffer cmdbuf, vk_ray_model_init_t a //kusochki[i].roughness = mg->material == kXVkMaterialWater ? 0. : 1.; // FIXME VectorSet(kusochki[i].emissive, 0, 0, 0 ); + vec3_t color = {1, 1, 1}; + applyMaterialToKusok(kusochki + i, mg, color); + mg->kusok_index = i + kusochki_count_offset; } + R_VkStagingUnlock(kusok_staging.handle); + + // FIXME this is definitely not the right place. We should upload everything in bulk, and only then build blases in bulk too + R_VkStagingCommit(cmdbuf); + { + const VkBufferMemoryBarrier bmb[] = { { + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + //.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR, // FIXME + .dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_SHADER_READ_BIT, // FIXME + .buffer = args.buffer, + .offset = 0, // FIXME + .size = VK_WHOLE_SIZE, // FIXME + }, { + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + //.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR, // FIXME + .dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_SHADER_READ_BIT, // FIXME + .buffer = staging_args.buffer, + .offset = staging_args.offset, + .size = staging_args.size, + } }; + vkCmdPipelineBarrier(cmdbuf, + VK_PIPELINE_STAGE_TRANSFER_BIT, + //VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, + VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, + 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); + } + { as_build_args_t asrgs = { .geoms = geoms, @@ -367,6 +464,10 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render gEngine.Host_Error("Unexpected render mode %d\n", render_model->render_mode); } +#if 0 // FIXME implement staging: +// - collect list of geoms for which we could update anything (animated textues, uvs, etc) +// - update only those through staging +// - also consider tracking whether the main model color has changed (that'd need to update everything yay) for (int i = 0; i < render_model->num_geometries; ++i) { const vk_render_geometry_t *geom = render_model->geometries + i; vk_kusok_data_t *kusok = (vk_kusok_data_t*)(g_ray_model_state.kusochki_buffer.mapped) + geom->kusok_index; @@ -417,6 +518,7 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render kusok->uv_speed[0] = kusok->uv_speed[1] = 0.f; } } +#endif for (int i = 0; i < render_model->polylights_count; ++i) { rt_light_add_polygon_t *const polylight = render_model->polylights + i; diff --git a/ref_vk/vk_render.c b/ref_vk/vk_render.c index b84f73a4..12437aec 100644 --- a/ref_vk/vk_render.c +++ b/ref_vk/vk_render.c @@ -660,23 +660,6 @@ qboolean VK_RenderModelInit( VkCommandBuffer cmdbuf, vk_render_model_t *model ) .buffer = geom_buffer, .model = model, }; - R_VkStagingCommit(cmdbuf); // FIXME this is definitely not the right place - { - const VkBufferMemoryBarrier bmb[] = { { - .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - //.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR, // FIXME - .dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_SHADER_READ_BIT, // FIXME - .buffer = geom_buffer, - .offset = 0, // FIXME - .size = VK_WHOLE_SIZE, // FIXME - } }; - vkCmdPipelineBarrier(cmdbuf, - VK_PIPELINE_STAGE_TRANSFER_BIT, - //VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, - VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, - 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); - } model->ray_model = VK_RayModelCreate(cmdbuf, args); return !!model->ray_model; } diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 2111834b..b374d1b4 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -796,8 +796,8 @@ qboolean VK_RayInit( void ) R_FlippingBuffer_Init(&g_rtx.tlas_geom_buffer_alloc, MAX_ACCELS * 2); if (!VK_BufferCreate("ray kusochki_buffer", &g_ray_model_state.kusochki_buffer, sizeof(vk_kusok_data_t) * MAX_KUSOCHKI, - VK_BUFFER_USAGE_STORAGE_BUFFER_BIT /* | VK_BUFFER_USAGE_TRANSFER_DST_BIT */, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { // FIXME complain, handle return false; } From 9a335a1e5631de7945ab4a1a9f77ad641b43db81 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 20 Aug 2022 13:56:29 -0700 Subject: [PATCH 433/548] (wip) rt: add uploading kusochki on every frame disabled due to severe glitches --- ref_vk/vk_ray_model.c | 85 ++++++++++++++++--------------------------- ref_vk/vk_render.h | 3 -- ref_vk/vk_rtx.c | 27 ++++++++++++-- 3 files changed, 56 insertions(+), 59 deletions(-) diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index 617ef2da..c4123ed4 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -153,7 +153,7 @@ void XVK_RayModel_Validate( void ) { } } -static void applyMaterialToKusok( vk_kusok_data_t* kusok, const vk_render_geometry_t *geom, const vec3_t color) { +static void applyMaterialToKusok( vk_kusok_data_t* kusok, const vk_render_geometry_t *geom, const vec3_t color, qboolean HACK_reflective) { const xvk_material_t *const mat = XVK_GetMaterialForTextureIndex( geom->texture ); ASSERT(mat); @@ -168,7 +168,6 @@ static void applyMaterialToKusok( vk_kusok_data_t* kusok, const vk_render_geomet kusok->roughness = mat->roughness; kusok->metalness = mat->metalness; -/* FIXME // HACK until there is a proper mechanism for patching materials, see https://github.com/w23/xash3d-fwgs/issues/213 // FIXME also this erases previous roughness unconditionally if (HACK_reflective) { @@ -176,7 +175,6 @@ static void applyMaterialToKusok( vk_kusok_data_t* kusok, const vk_render_geomet } else if (!mat->set && geom->material == kXVkMaterialChrome) { kusok->tex_roughness = tglob.grayTexture; } -*/ if (geom->material == kXVkMaterialSky) kusok->tex_base_color |= KUSOK_MATERIAL_FLAG_SKYBOX; @@ -196,7 +194,7 @@ static void applyMaterialToKusok( vk_kusok_data_t* kusok, const vk_render_geomet RT_GetEmissiveForTexture( kusok->emissive, geom->texture ); } -/* FIXME +/* FIXME these should be done in a different way if (geom->material == kXVkMaterialConveyor) { computeConveyorSpeed( entcolor, geom->texture, kusok->uv_speed ); } else */ { @@ -294,9 +292,7 @@ vk_ray_model_t* VK_RayModelCreate( VkCommandBuffer cmdbuf, vk_ray_model_init_t a VectorSet(kusochki[i].emissive, 0, 0, 0 ); vec3_t color = {1, 1, 1}; - applyMaterialToKusok(kusochki + i, mg, color); - - mg->kusok_index = i + kusochki_count_offset; + applyMaterialToKusok(kusochki + i, mg, color, false); } R_VkStagingUnlock(kusok_staging.handle); @@ -421,6 +417,7 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render ASSERT(vk_core.rtx); ASSERT(g_ray_model_state.frame.num_models <= ARRAYSIZE(g_ray_model_state.frame.models)); + ASSERT(model->num_geoms == render_model->num_geometries); if (g_ray_model_state.freeze_models) return; @@ -434,7 +431,6 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render ASSERT(model->as != VK_NULL_HANDLE); draw_model->model = model; memcpy(draw_model->transform_row, *transform_row, sizeof(draw_model->transform_row)); - g_ray_model_state.frame.num_models++; } switch (render_model->render_mode) { @@ -464,61 +460,42 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render gEngine.Host_Error("Unexpected render mode %d\n", render_model->render_mode); } -#if 0 // FIXME implement staging: +// TODO optimize: // - collect list of geoms for which we could update anything (animated textues, uvs, etc) // - update only those through staging // - also consider tracking whether the main model color has changed (that'd need to update everything yay) - for (int i = 0; i < render_model->num_geometries; ++i) { - const vk_render_geometry_t *geom = render_model->geometries + i; - vk_kusok_data_t *kusok = (vk_kusok_data_t*)(g_ray_model_state.kusochki_buffer.mapped) + geom->kusok_index; - const xvk_material_t *const mat = XVK_GetMaterialForTextureIndex( geom->texture ); - ASSERT(mat); + if (0) + { + const vk_staging_buffer_args_t staging_args = { + .buffer = g_ray_model_state.kusochki_buffer.buffer, + .offset = model->kusochki_offset * sizeof(vk_kusok_data_t), + .size = render_model->num_geometries * sizeof(vk_kusok_data_t), + .alignment = 16, + }; + const vk_staging_region_t kusok_staging = R_VkStagingLockForBuffer(staging_args); - /* if (!render_model->static_map) */ - /* VK_LightsAddEmissiveSurface( geom, transform_row, false ); */ - - kusok->tex_base_color = mat->tex_base_color; - kusok->tex_roughness = mat->tex_roughness; - kusok->tex_metalness = mat->tex_metalness; - kusok->tex_normalmap = mat->tex_normalmap; - - kusok->roughness = mat->roughness; - kusok->metalness = mat->metalness; - - // HACK until there is a proper mechanism for patching materials, see https://github.com/w23/xash3d-fwgs/issues/213 - // FIXME also this erases previous roughness unconditionally - if (HACK_reflective) { - kusok->tex_roughness = tglob.blackTexture; - } else if (!mat->set && geom->material == kXVkMaterialChrome) { - kusok->tex_roughness = tglob.grayTexture; + if (!kusok_staging.ptr) { + gEngine.Con_Printf(S_ERROR "Couldn't allocate staging for %d kusochkov for model %s\n", model->num_geoms, render_model->debug_name); + return; } - if (geom->material == kXVkMaterialSky) { - kusok->tex_base_color |= KUSOK_MATERIAL_FLAG_SKYBOX; + vk_kusok_data_t *const kusochki = kusok_staging.ptr; + + for (int i = 0; i < render_model->num_geometries; ++i) { + const vk_render_geometry_t *geom = render_model->geometries + i; + applyMaterialToKusok( kusochki + i, geom, color, HACK_reflective ); } - { - vec4_t gcolor; - gcolor[0] = color[0] * mat->base_color[0]; - gcolor[1] = color[1] * mat->base_color[1]; - gcolor[2] = color[2] * mat->base_color[2]; - gcolor[3] = color[3]; - Vector4Copy(gcolor, kusok->color); - } + gEngine.Con_Reportf("model %s: geom=%d kind=%d ko=%d ks=%d handle=%d\n", + render_model->debug_name, + render_model->num_geometries, + model->kusochki_offset, + staging_args.offset, staging_args.size, + kusok_staging.handle + ); - if (geom->material == kXVkMaterialEmissive) { - VectorCopy( geom->emissive, kusok->emissive ); - } else { - RT_GetEmissiveForTexture( kusok->emissive, geom->texture ); - } - - if (geom->material == kXVkMaterialConveyor) { - computeConveyorSpeed( entcolor, geom->texture, kusok->uv_speed ); - } else { - kusok->uv_speed[0] = kusok->uv_speed[1] = 0.f; - } + R_VkStagingUnlock(kusok_staging.handle); } -#endif for (int i = 0; i < render_model->polylights_count; ++i) { rt_light_add_polygon_t *const polylight = render_model->polylights + i; @@ -526,6 +503,8 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render polylight->dynamic = true; RT_LightAddPolygon(polylight); } + + g_ray_model_state.frame.num_models++; } void RT_RayModel_Clear(void) { diff --git a/ref_vk/vk_render.h b/ref_vk/vk_render.h index ab46688b..bc74d27b 100644 --- a/ref_vk/vk_render.h +++ b/ref_vk/vk_render.h @@ -57,9 +57,6 @@ typedef struct vk_render_geometry_s { // - updating dynamic lights (TODO: can decouple from surface/brush models by providing texture_id and aabb directly here) const struct msurface_s *surf; - // Index into kusochki buffer for current frame - uint32_t kusok_index; - // for kXVkMaterialEmissive vec3_t emissive; } vk_render_geometry_t; diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index b374d1b4..0a5d2136 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -10,6 +10,7 @@ #include "vk_common.h" #include "vk_buffer.h" #include "vk_pipeline.h" +#include "vk_staging.h" #include "vk_cvar.h" #include "vk_textures.h" #include "vk_light.h" @@ -35,9 +36,12 @@ #if 1 #define FRAME_WIDTH 1280 #define FRAME_HEIGHT 720 -#else +#elif 0 #define FRAME_WIDTH 2560 #define FRAME_HEIGHT 1440 +#else +#define FRAME_WIDTH 1920 +#define FRAME_HEIGHT 1080 #endif // TODO sync with shaders @@ -590,14 +594,31 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* }, }; + // Upload kusochki updates + { + R_VkStagingCommit(cmdbuf); + const VkBufferMemoryBarrier bmb[] = { { + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR, + .buffer = g_ray_model_state.kusochki_buffer.buffer, + .offset = 0, + .size = VK_WHOLE_SIZE, + } }; + + vkCmdPipelineBarrier(cmdbuf, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR | VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, + 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); + } DEBUG_BEGIN(cmdbuf, "yay tracing"); prepareTlas(cmdbuf); prepareUniformBuffer(args->render_args, args->frame_index, args->fov_angle_y); - // 4. Barrier for TLAS build and dest image layout transfer + // 4. Barrier for TLAS build { - VkBufferMemoryBarrier bmb[] = { { + const VkBufferMemoryBarrier bmb[] = { { .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, .srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR, .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, From 77b05af59aa2cbdfb05ff499bde5a442f7bc75d1 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 27 Aug 2022 11:25:09 -0700 Subject: [PATCH 434/548] rt: don't forget to fill kusochki with static geometry data on update this also makes dynamic model gpu crashes more salient, investigating --- ref_vk/vk_ray_model.c | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index c4123ed4..d73e6b7e 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -153,10 +153,16 @@ void XVK_RayModel_Validate( void ) { } } -static void applyMaterialToKusok( vk_kusok_data_t* kusok, const vk_render_geometry_t *geom, const vec3_t color, qboolean HACK_reflective) { +static void applyMaterialToKusok(vk_kusok_data_t* kusok, const vk_render_geometry_t *geom, const vec3_t color, qboolean HACK_reflective) { const xvk_material_t *const mat = XVK_GetMaterialForTextureIndex( geom->texture ); ASSERT(mat); + // TODO split kusochki into static geometry data and potentially dynamic material data + // This data is static, should never change + kusok->vertex_offset = geom->vertex_offset; + kusok->index_offset = geom->index_offset; + kusok->triangles = geom->element_count / 3; + /* if (!render_model->static_map) */ /* VK_LightsAddEmissiveSurface( geom, transform_row, false ); */ @@ -277,21 +283,13 @@ vk_ray_model_t* VK_RayModelCreate( VkCommandBuffer cmdbuf, vk_ray_model_init_t a .firstVertex = mg->vertex_offset, }; - kusochki[i].vertex_offset = mg->vertex_offset; - kusochki[i].index_offset = mg->index_offset; - kusochki[i].triangles = prim_count; - if (mg->material == kXVkMaterialSky) { kusochki[i].tex_base_color |= KUSOK_MATERIAL_FLAG_SKYBOX; } else { kusochki[i].tex_base_color &= (~KUSOK_MATERIAL_FLAG_SKYBOX); } - //kusochki[i].texture = mg->texture; - //kusochki[i].roughness = mg->material == kXVkMaterialWater ? 0. : 1.; // FIXME - VectorSet(kusochki[i].emissive, 0, 0, 0 ); - - vec3_t color = {1, 1, 1}; + const vec3_t color = {1, 1, 1}; applyMaterialToKusok(kusochki + i, mg, color, false); } @@ -464,7 +462,7 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render // - collect list of geoms for which we could update anything (animated textues, uvs, etc) // - update only those through staging // - also consider tracking whether the main model color has changed (that'd need to update everything yay) - if (0) + if (0) // FIXME enabling this makes dynamic models crash the gpu (?!) { const vk_staging_buffer_args_t staging_args = { .buffer = g_ray_model_state.kusochki_buffer.buffer, @@ -483,10 +481,10 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render for (int i = 0; i < render_model->num_geometries; ++i) { const vk_render_geometry_t *geom = render_model->geometries + i; - applyMaterialToKusok( kusochki + i, geom, color, HACK_reflective ); + applyMaterialToKusok(kusochki + i, geom, color, HACK_reflective); } - gEngine.Con_Reportf("model %s: geom=%d kind=%d ko=%d ks=%d handle=%d\n", + gEngine.Con_Reportf("model %s: geom=%d kuoffs=%d kustoff=%d kustsz=%d sthndl=%d\n", render_model->debug_name, render_model->num_geometries, model->kusochki_offset, From 275417772e00d6cb1ee9b24c357bee1717c21b4b Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 27 Aug 2022 11:39:29 -0700 Subject: [PATCH 435/548] rt: move image-related funcs from vk_rtx to vk_image --- ref_vk/vk_image.c | 106 +++++++++++++++++++++++++++++++++++++ ref_vk/vk_image.h | 15 ++++++ ref_vk/vk_rtx.c | 130 ++-------------------------------------------- 3 files changed, 126 insertions(+), 125 deletions(-) diff --git a/ref_vk/vk_image.c b/ref_vk/vk_image.c index a20da26b..b0db40b8 100644 --- a/ref_vk/vk_image.c +++ b/ref_vk/vk_image.c @@ -79,3 +79,109 @@ void XVK_ImageDestroy(xvk_image_t *img) { VK_DevMemFree(&img->devmem); *img = (xvk_image_t){0}; } + +void R_VkImageClear(VkCommandBuffer cmdbuf, VkImage image) { + const VkImageMemoryBarrier image_barriers[] = { { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .image = image, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .subresourceRange = (VkImageSubresourceRange) { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }} }; + + const VkClearColorValue clear_value = {0}; + + vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + 0, NULL, 0, NULL, COUNTOF(image_barriers), image_barriers); + + vkCmdClearColorImage(cmdbuf, image, VK_IMAGE_LAYOUT_GENERAL, &clear_value, 1, &image_barriers->subresourceRange); +} + +void R_VkImageBlit(VkCommandBuffer cmdbuf, const r_vkimage_blit_args *blit_args) { + { + const VkImageMemoryBarrier image_barriers[] = { { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .image = blit_args->src.image, + .srcAccessMask = blit_args->src.srcAccessMask, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = blit_args->src.oldLayout, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .subresourceRange = + (VkImageSubresourceRange){ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }, { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .image = blit_args->dst.image, + .srcAccessMask = blit_args->dst.srcAccessMask, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = blit_args->dst.oldLayout, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .subresourceRange = + (VkImageSubresourceRange){ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + } }; + + vkCmdPipelineBarrier(cmdbuf, + blit_args->in_stage, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, NULL, 0, NULL, COUNTOF(image_barriers), image_barriers); + } + + { + VkImageBlit region = {0}; + region.srcOffsets[1].x = blit_args->src.width; + region.srcOffsets[1].y = blit_args->src.height; + region.srcOffsets[1].z = 1; + region.dstOffsets[1].x = blit_args->dst.width; + region.dstOffsets[1].y = blit_args->dst.height; + region.dstOffsets[1].z = 1; + region.srcSubresource.aspectMask = region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.srcSubresource.layerCount = region.dstSubresource.layerCount = 1; + vkCmdBlitImage(cmdbuf, + blit_args->src.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + blit_args->dst.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, ®ion, + VK_FILTER_NEAREST); + } + + { + VkImageMemoryBarrier image_barriers[] = { + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .image = blit_args->dst.image, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .subresourceRange = + (VkImageSubresourceRange){ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }}; + vkCmdPipelineBarrier(cmdbuf, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, 0, NULL, 0, NULL, COUNTOF(image_barriers), image_barriers); + } +} diff --git a/ref_vk/vk_image.h b/ref_vk/vk_image.h index e46d781c..27709d53 100644 --- a/ref_vk/vk_image.h +++ b/ref_vk/vk_image.h @@ -25,3 +25,18 @@ typedef struct { xvk_image_t XVK_ImageCreate(const xvk_image_create_t *create); void XVK_ImageDestroy(xvk_image_t *img); + +void R_VkImageClear(VkCommandBuffer cmdbuf, VkImage image); + +typedef struct { + VkPipelineStageFlags in_stage; + struct { + VkImage image; + int width, height; + VkImageLayout oldLayout; + VkAccessFlags srcAccessMask; + } src, dst; +} r_vkimage_blit_args; + +void R_VkImageBlit( VkCommandBuffer cmdbuf, const r_vkimage_blit_args *blit_args ); + diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 0a5d2136..c7bbf6da 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -388,124 +388,6 @@ static void prepareTlas( VkCommandBuffer cmdbuf ) { DEBUG_END(cmdbuf); } -static void clearVkImage( VkCommandBuffer cmdbuf, VkImage image ) { - const VkImageMemoryBarrier image_barriers[] = { { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .image = image, - .srcAccessMask = 0, - .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .subresourceRange = (VkImageSubresourceRange) { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }} }; - - const VkClearColorValue clear_value = {0}; - - vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, - 0, NULL, 0, NULL, ARRAYSIZE(image_barriers), image_barriers); - - vkCmdClearColorImage(cmdbuf, image, VK_IMAGE_LAYOUT_GENERAL, &clear_value, 1, &image_barriers->subresourceRange); -} - -typedef struct { - VkCommandBuffer cmdbuf; - - VkPipelineStageFlags in_stage; - struct { - VkImage image; - int width, height; - VkImageLayout oldLayout; - VkAccessFlags srcAccessMask; - } src, dst; -} xvk_blit_args; - -static void blitImage( const xvk_blit_args *blit_args ) { - { - const VkImageMemoryBarrier image_barriers[] = { { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .image = blit_args->src.image, - .srcAccessMask = blit_args->src.srcAccessMask, - .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, - .oldLayout = blit_args->src.oldLayout, - .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - .subresourceRange = - (VkImageSubresourceRange){ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - }, { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .image = blit_args->dst.image, - .srcAccessMask = blit_args->dst.srcAccessMask, - .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .oldLayout = blit_args->dst.oldLayout, - .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - .subresourceRange = - (VkImageSubresourceRange){ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - } }; - - vkCmdPipelineBarrier(blit_args->cmdbuf, - blit_args->in_stage, - VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, 0, NULL, 0, NULL, ARRAYSIZE(image_barriers), image_barriers); - } - - { - VkImageBlit region = {0}; - region.srcOffsets[1].x = blit_args->src.width; - region.srcOffsets[1].y = blit_args->src.height; - region.srcOffsets[1].z = 1; - region.dstOffsets[1].x = blit_args->dst.width; - region.dstOffsets[1].y = blit_args->dst.height; - region.dstOffsets[1].z = 1; - region.srcSubresource.aspectMask = region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.srcSubresource.layerCount = region.dstSubresource.layerCount = 1; - vkCmdBlitImage(blit_args->cmdbuf, - blit_args->src.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - blit_args->dst.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, ®ion, - VK_FILTER_NEAREST); - } - - { - VkImageMemoryBarrier image_barriers[] = { - { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .image = blit_args->dst.image, - .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - .subresourceRange = - (VkImageSubresourceRange){ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - }}; - vkCmdPipelineBarrier(blit_args->cmdbuf, - VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - 0, 0, NULL, 0, NULL, ARRAYSIZE(image_barriers), image_barriers); - } -} - static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int frame_index, float fov_angle_y ) { struct UniformBuffer *ubo = (struct UniformBuffer*)((char*)g_rtx.uniform_buffer.mapped + frame_index * g_rtx.uniform_unit_size); @@ -656,8 +538,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.denoiser, &res ); { - const xvk_blit_args blit_args = { - .cmdbuf = cmdbuf, + const r_vkimage_blit_args blit_args = { .in_stage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, .src = { .image = args->current_frame->denoised.image, @@ -675,7 +556,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* }, }; - blitImage( &blit_args ); + R_VkImageBlit( cmdbuf, &blit_args ); } DEBUG_END(cmdbuf); } @@ -718,8 +599,7 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) } if (g_ray_model_state.frame.num_models == 0) { - const xvk_blit_args blit_args = { - .cmdbuf = args->cmdbuf, + const r_vkimage_blit_args blit_args = { .in_stage = VK_PIPELINE_STAGE_TRANSFER_BIT, .src = { .image = current_frame->denoised.image, @@ -737,8 +617,8 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) }, }; - clearVkImage( cmdbuf, current_frame->denoised.image ); - blitImage( &blit_args ); + R_VkImageClear( cmdbuf, current_frame->denoised.image ); + R_VkImageBlit( cmdbuf, &blit_args ); } else { const perform_tracing_args_t trace_args = { .render_args = args, From df09a4e63750c01895644319d35747de6cebad57 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 27 Aug 2022 12:30:53 -0700 Subject: [PATCH 436/548] rt: move AS management from vk_rtx to vk_ray_accel no functional changes yet. --- ref_vk/vk_buffer.c | 2 +- ref_vk/vk_buffer.h | 2 +- ref_vk/vk_overlay.c | 1 + ref_vk/vk_pipeline.c | 2 +- ref_vk/vk_ray_accel.c | 302 +++++++++++++++++++++++++++++++++++ ref_vk/vk_ray_accel.h | 44 ++++++ ref_vk/vk_ray_internal.h | 2 - ref_vk/vk_ray_model.c | 2 +- ref_vk/vk_rtx.c | 329 ++------------------------------------- 9 files changed, 361 insertions(+), 325 deletions(-) create mode 100644 ref_vk/vk_ray_accel.c create mode 100644 ref_vk/vk_ray_accel.h diff --git a/ref_vk/vk_buffer.c b/ref_vk/vk_buffer.c index 1f10f2d8..ac4dfc72 100644 --- a/ref_vk/vk_buffer.c +++ b/ref_vk/vk_buffer.c @@ -42,7 +42,7 @@ void VK_BufferDestroy(vk_buffer_t *buf) { } } -VkDeviceAddress XVK_BufferGetDeviceAddress(VkBuffer buffer) { +VkDeviceAddress R_VkBufferGetDeviceAddress(VkBuffer buffer) { const VkBufferDeviceAddressInfo bdai = {.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, .buffer = buffer}; return vkGetBufferDeviceAddress(vk_core.device, &bdai); } diff --git a/ref_vk/vk_buffer.h b/ref_vk/vk_buffer.h index 37c78aaf..a9a54683 100644 --- a/ref_vk/vk_buffer.h +++ b/ref_vk/vk_buffer.h @@ -15,7 +15,7 @@ typedef struct vk_buffer_s { qboolean VK_BufferCreate(const char *debug_name, vk_buffer_t *buf, uint32_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags flags); void VK_BufferDestroy(vk_buffer_t *buf); -VkDeviceAddress XVK_BufferGetDeviceAddress(VkBuffer buffer); +VkDeviceAddress R_VkBufferGetDeviceAddress(VkBuffer buffer); typedef struct { diff --git a/ref_vk/vk_overlay.c b/ref_vk/vk_overlay.c index 3d1ac330..627f5aef 100644 --- a/ref_vk/vk_overlay.c +++ b/ref_vk/vk_overlay.c @@ -234,6 +234,7 @@ qboolean R_VkOverlay_Init( void ) { if (!createPipelines()) return false; + // TODO this doesn't need to be host visible, could use staging too if (!VK_BufferCreate("2d pics_buffer", &g2d.pics_buffer, sizeof(vertex_2d_t) * MAX_VERTICES, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT )) // FIXME cleanup diff --git a/ref_vk/vk_pipeline.c b/ref_vk/vk_pipeline.c index fb032da2..c42dd4bf 100644 --- a/ref_vk/vk_pipeline.c +++ b/ref_vk/vk_pipeline.c @@ -293,7 +293,7 @@ vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_ } { - const VkDeviceAddress sbt_addr = XVK_BufferGetDeviceAddress(ret.sbt_buffer.buffer); + const VkDeviceAddress sbt_addr = R_VkBufferGetDeviceAddress(ret.sbt_buffer.buffer); const uint32_t sbt_record_size = vk_core.physical_device.sbt_record_size; uint32_t index = 0; diff --git a/ref_vk/vk_ray_accel.c b/ref_vk/vk_ray_accel.c new file mode 100644 index 00000000..2ab5772b --- /dev/null +++ b/ref_vk/vk_ray_accel.c @@ -0,0 +1,302 @@ +#include "vk_ray_accel.h" + +#include "vk_core.h" +#include "vk_rtx.h" +#include "vk_ray_internal.h" + +#define MAX_SCRATCH_BUFFER (32*1024*1024) +#define MAX_ACCELS_BUFFER (64*1024*1024) + +struct rt_vk_ray_accel_s g_accel = {0}; + +static VkDeviceAddress getASAddress(VkAccelerationStructureKHR as) { + VkAccelerationStructureDeviceAddressInfoKHR asdai = { + .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR, + .accelerationStructure = as, + }; + return vkGetAccelerationStructureDeviceAddressKHR(vk_core.device, &asdai); +} + +// TODO split this into smaller building blocks in a separate module +qboolean createOrUpdateAccelerationStructure(VkCommandBuffer cmdbuf, const as_build_args_t *args, vk_ray_model_t *model) { + qboolean should_create = *args->p_accel == VK_NULL_HANDLE; +#if 1 // update does not work at all on AMD gpus + qboolean is_update = false; // FIXME this crashes for some reason !should_create && args->dynamic; +#else + qboolean is_update = !should_create && args->dynamic; +#endif + + VkAccelerationStructureBuildGeometryInfoKHR build_info = { + .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR, + .type = args->type, + .flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR | ( args->dynamic ? VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR : 0), + .mode = is_update ? VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR : VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR, + .geometryCount = args->n_geoms, + .pGeometries = args->geoms, + .srcAccelerationStructure = is_update ? *args->p_accel : VK_NULL_HANDLE, + }; + + VkAccelerationStructureBuildSizesInfoKHR build_size = { + .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR + }; + + uint32_t scratch_buffer_size = 0; + + ASSERT(args->geoms); + ASSERT(args->n_geoms > 0); + ASSERT(args->p_accel); + + vkGetAccelerationStructureBuildSizesKHR( + vk_core.device, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &build_info, args->max_prim_counts, &build_size); + + scratch_buffer_size = is_update ? build_size.updateScratchSize : build_size.buildScratchSize; + +#if 0 + { + uint32_t max_prims = 0; + for (int i = 0; i < args->n_geoms; ++i) + max_prims += args->max_prim_counts[i]; + gEngine.Con_Reportf( + "AS max_prims=%u, n_geoms=%u, build size: %d, scratch size: %d\n", max_prims, args->n_geoms, build_size.accelerationStructureSize, build_size.buildScratchSize); + } +#endif + + if (MAX_SCRATCH_BUFFER < g_accel.frame.scratch_offset + scratch_buffer_size) { + gEngine.Con_Printf(S_ERROR "Scratch buffer overflow: left %u bytes, but need %u\n", + MAX_SCRATCH_BUFFER - g_accel.frame.scratch_offset, + scratch_buffer_size); + return false; + } + + if (should_create) { + const uint32_t as_size = build_size.accelerationStructureSize; + const alo_block_t block = aloPoolAllocate(g_accel.accels_buffer_alloc, as_size, /*TODO why? align=*/256); + const uint32_t buffer_offset = block.offset; + const VkAccelerationStructureCreateInfoKHR asci = { + .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR, + .buffer = g_accel.accels_buffer.buffer, + .offset = buffer_offset, + .type = args->type, + .size = as_size, + }; + + if (buffer_offset == ALO_ALLOC_FAILED) { + gEngine.Con_Printf(S_ERROR "Failed to allocated %u bytes for accel buffer\n", asci.size); + return false; + } + + XVK_CHECK(vkCreateAccelerationStructureKHR(vk_core.device, &asci, NULL, args->p_accel)); + SET_DEBUG_NAME(*args->p_accel, VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR, args->debug_name); + + if (model) { + model->size = asci.size; + model->debug.as_offset = buffer_offset; + } + + // gEngine.Con_Reportf("AS=%p, n_geoms=%u, build: %#x %d %#x\n", *args->p_accel, args->n_geoms, buffer_offset, asci.size, buffer_offset + asci.size); + } + + // If not enough data for building, just create + if (!cmdbuf || !args->build_ranges) + return true; + + if (model) { + ASSERT(model->size >= build_size.accelerationStructureSize); + } + + build_info.dstAccelerationStructure = *args->p_accel; + build_info.scratchData.deviceAddress = g_accel.scratch_buffer_addr + g_accel.frame.scratch_offset; + //uint32_t scratch_offset_initial = g_accel.frame.scratch_offset; + g_accel.frame.scratch_offset += scratch_buffer_size; + g_accel.frame.scratch_offset = ALIGN_UP(g_accel.frame.scratch_offset, vk_core.physical_device.properties_accel.minAccelerationStructureScratchOffsetAlignment); + + //gEngine.Con_Reportf("AS=%p, n_geoms=%u, scratch: %#x %d %#x\n", *args->p_accel, args->n_geoms, scratch_offset_initial, scratch_buffer_size, scratch_offset_initial + scratch_buffer_size); + + vkCmdBuildAccelerationStructuresKHR(cmdbuf, 1, &build_info, &args->build_ranges); + return true; +} + +static void createTlas( VkCommandBuffer cmdbuf, VkDeviceAddress instances_addr ) { + const VkAccelerationStructureGeometryKHR tl_geom[] = { + { + .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR, + //.flags = VK_GEOMETRY_OPAQUE_BIT, + .geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR, + .geometry.instances = + (VkAccelerationStructureGeometryInstancesDataKHR){ + .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR, + .data.deviceAddress = instances_addr, + .arrayOfPointers = VK_FALSE, + }, + }, + }; + const uint32_t tl_max_prim_counts[COUNTOF(tl_geom)] = { MAX_ACCELS }; //cmdbuf == VK_NULL_HANDLE ? MAX_ACCELS : g_ray_model_state.frame.num_models }; + const VkAccelerationStructureBuildRangeInfoKHR tl_build_range = { + .primitiveCount = g_ray_model_state.frame.num_models, + }; + const as_build_args_t asrgs = { + .geoms = tl_geom, + .max_prim_counts = tl_max_prim_counts, + .build_ranges = cmdbuf == VK_NULL_HANDLE ? NULL : &tl_build_range, + .n_geoms = COUNTOF(tl_geom), + .type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, + // we can't really rebuild TLAS because instance count changes are not allowed .dynamic = true, + .dynamic = false, + .p_accel = &g_accel.tlas, + .debug_name = "TLAS", + }; + if (!createOrUpdateAccelerationStructure(cmdbuf, &asrgs, NULL)) { + gEngine.Host_Error("Could not create/update TLAS\n"); + return; + } +} + +void RT_VkAccelPrepareTlas(VkCommandBuffer cmdbuf) { + ASSERT(g_ray_model_state.frame.num_models > 0); + DEBUG_BEGIN(cmdbuf, "prepare tlas"); + + R_FlippingBuffer_Flip( &g_accel.tlas_geom_buffer_alloc ); + + const uint32_t instance_offset = R_FlippingBuffer_Alloc(&g_accel.tlas_geom_buffer_alloc, g_ray_model_state.frame.num_models, 1); + ASSERT(instance_offset != ALO_ALLOC_FAILED); + + // Upload all blas instances references to GPU mem + { + VkAccelerationStructureInstanceKHR* inst = ((VkAccelerationStructureInstanceKHR*)g_accel.tlas_geom_buffer.mapped) + instance_offset; + for (int i = 0; i < g_ray_model_state.frame.num_models; ++i) { + const vk_ray_draw_model_t* const model = g_ray_model_state.frame.models + i; + ASSERT(model->model); + ASSERT(model->model->as != VK_NULL_HANDLE); + inst[i] = (VkAccelerationStructureInstanceKHR){ + .instanceCustomIndex = model->model->kusochki_offset, + .instanceShaderBindingTableRecordOffset = 0, + .accelerationStructureReference = getASAddress(model->model->as), // TODO cache this addr + }; + switch (model->material_mode) { + case MaterialMode_Opaque: + inst[i].mask = GEOMETRY_BIT_OPAQUE; + inst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_REGULAR, + inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR; + break; + case MaterialMode_Opaque_AlphaTest: + inst[i].mask = GEOMETRY_BIT_OPAQUE; + inst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_ALPHA_TEST, + inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR; + break; + case MaterialMode_Refractive: + inst[i].mask = GEOMETRY_BIT_REFRACTIVE; + inst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_REGULAR, + inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR; + break; + case MaterialMode_Additive: + inst[i].mask = GEOMETRY_BIT_ADDITIVE; + inst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_ADDITIVE, + inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR; + break; + } + memcpy(&inst[i].transform, model->transform_row, sizeof(VkTransformMatrixKHR)); + } + } + + // Barrier for building all BLASes + // BLAS building is now in cmdbuf, need to synchronize with results + { + VkBufferMemoryBarrier bmb[] = { { + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR, // | VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR, + .buffer = g_accel.accels_buffer.buffer, + .offset = instance_offset * sizeof(VkAccelerationStructureInstanceKHR), + .size = g_ray_model_state.frame.num_models * sizeof(VkAccelerationStructureInstanceKHR), + } }; + vkCmdPipelineBarrier(cmdbuf, + VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, + VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, + 0, 0, NULL, COUNTOF(bmb), bmb, 0, NULL); + } + + // 2. Build TLAS + createTlas(cmdbuf, g_accel.tlas_geom_buffer_addr + instance_offset * sizeof(VkAccelerationStructureInstanceKHR)); + DEBUG_END(cmdbuf); +} + +qboolean RT_VkAccelInit(void) { + if (!VK_BufferCreate("ray accels_buffer", &g_accel.accels_buffer, MAX_ACCELS_BUFFER, + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT + )) + { + return false; + } + g_accel.accels_buffer_addr = R_VkBufferGetDeviceAddress(g_accel.accels_buffer.buffer); + + if (!VK_BufferCreate("ray scratch_buffer", &g_accel.scratch_buffer, MAX_SCRATCH_BUFFER, + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT + )) { + return false; + } + g_accel.scratch_buffer_addr = R_VkBufferGetDeviceAddress(g_accel.scratch_buffer.buffer); + + // TODO this doesn't really need to be host visible, use staging + if (!VK_BufferCreate("ray tlas_geom_buffer", &g_accel.tlas_geom_buffer, sizeof(VkAccelerationStructureInstanceKHR) * MAX_ACCELS * 2, + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { + // FIXME complain, handle + return false; + } + g_accel.tlas_geom_buffer_addr = R_VkBufferGetDeviceAddress(g_accel.tlas_geom_buffer.buffer); + R_FlippingBuffer_Init(&g_accel.tlas_geom_buffer_alloc, MAX_ACCELS * 2); + + return true; +} + +void RT_VkAccelShutdown(void) { + if (g_accel.tlas != VK_NULL_HANDLE) + vkDestroyAccelerationStructureKHR(vk_core.device, g_accel.tlas, NULL); + + for (int i = 0; i < COUNTOF(g_ray_model_state.models_cache); ++i) { + vk_ray_model_t *model = g_ray_model_state.models_cache + i; + if (model->as != VK_NULL_HANDLE) + vkDestroyAccelerationStructureKHR(vk_core.device, model->as, NULL); + model->as = VK_NULL_HANDLE; + } + + VK_BufferDestroy(&g_accel.scratch_buffer); + VK_BufferDestroy(&g_accel.accels_buffer); + VK_BufferDestroy(&g_accel.tlas_geom_buffer); + if (g_accel.accels_buffer_alloc) + aloPoolDestroy(g_accel.accels_buffer_alloc); +} + +void RT_VkAccelNewMap(void) { + const int expected_accels = 512; // TODO actually get this from playing the game + const int accels_alignment = 256; // TODO where does this come from? + ASSERT(vk_core.rtx); + + if (g_accel.accels_buffer_alloc) + aloPoolDestroy(g_accel.accels_buffer_alloc); + g_accel.accels_buffer_alloc = aloPoolCreate(MAX_ACCELS_BUFFER, expected_accels, accels_alignment); + + // Clear model cache + for (int i = 0; i < COUNTOF(g_ray_model_state.models_cache); ++i) { + vk_ray_model_t *model = g_ray_model_state.models_cache + i; + VK_RayModelDestroy(model); + } + + // Recreate tlas + // Why here and not in init: to make sure that its memory is preserved. Map init will clear all memory regions. + { + if (g_accel.tlas != VK_NULL_HANDLE) { + vkDestroyAccelerationStructureKHR(vk_core.device, g_accel.tlas, NULL); + g_accel.tlas = VK_NULL_HANDLE; + } + + createTlas(VK_NULL_HANDLE, g_accel.tlas_geom_buffer_addr); + } +} + +void RT_VkAccelFrameBegin(void) { + g_accel.frame.scratch_offset = 0; +} diff --git a/ref_vk/vk_ray_accel.h b/ref_vk/vk_ray_accel.h new file mode 100644 index 00000000..0c7af8ab --- /dev/null +++ b/ref_vk/vk_ray_accel.h @@ -0,0 +1,44 @@ +#pragma once + +#include "vk_core.h" +#include "vk_buffer.h" + +struct rt_vk_ray_accel_s { + // Stores AS built data. Lifetime similar to render buffer: + // - some portion lives for entire map lifetime + // - some portion lives only for a single frame (may have several frames in flight) + // TODO: unify this with render buffer + // Needs: AS_STORAGE_BIT, SHADER_DEVICE_ADDRESS_BIT + vk_buffer_t accels_buffer; + struct alo_pool_s *accels_buffer_alloc; + + // Temp: lives only during a single frame (may have many in flight) + // Used for building ASes; + // Needs: AS_STORAGE_BIT, SHADER_DEVICE_ADDRESS_BIT + vk_buffer_t scratch_buffer; + VkDeviceAddress accels_buffer_addr, scratch_buffer_addr; + + // Temp-ish: used for making TLAS, contains addressed to all used BLASes + // Lifetime and nature of usage similar to scratch_buffer + // TODO: unify them + // Needs: SHADER_DEVICE_ADDRESS, STORAGE_BUFFER, AS_BUILD_INPUT_READ_ONLY + vk_buffer_t tlas_geom_buffer; + VkDeviceAddress tlas_geom_buffer_addr; + r_flipping_buffer_t tlas_geom_buffer_alloc; + + // TODO need several TLASes for N frames in flight + VkAccelerationStructureKHR tlas; + + // Per-frame data that is accumulated between RayFrameBegin and End calls + struct { + uint32_t scratch_offset; // for building dynamic blases + } frame; +}; + +extern struct rt_vk_ray_accel_s g_accel; + +qboolean RT_VkAccelInit(void); +void RT_VkAccelShutdown(void); +void RT_VkAccelNewMap(void); +void RT_VkAccelFrameBegin(void); +void RT_VkAccelPrepareTlas(VkCommandBuffer cmdbuf); diff --git a/ref_vk/vk_ray_internal.h b/ref_vk/vk_ray_internal.h index 9bbf8ade..cc81189d 100644 --- a/ref_vk/vk_ray_internal.h +++ b/ref_vk/vk_ray_internal.h @@ -78,6 +78,4 @@ extern xvk_ray_model_state_t g_ray_model_state; void XVK_RayModel_ClearForNextFrame( void ); void XVK_RayModel_Validate(void); -VkDeviceAddress getBufferDeviceAddress(VkBuffer buffer); - void RT_RayModel_Clear(void); diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index d73e6b7e..f5bbf2da 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -212,7 +212,7 @@ vk_ray_model_t* VK_RayModelCreate( VkCommandBuffer cmdbuf, vk_ray_model_init_t a VkAccelerationStructureGeometryKHR *geoms; uint32_t *geom_max_prim_counts; VkAccelerationStructureBuildRangeInfoKHR *geom_build_ranges; - const VkDeviceAddress buffer_addr = getBufferDeviceAddress(args.buffer); + const VkDeviceAddress buffer_addr = R_VkBufferGetDeviceAddress(args.buffer); // TODO pass in args/have in buffer itself const uint32_t kusochki_count_offset = R_DEBuffer_Alloc(&g_ray_model_state.kusochki_alloc, args.model->dynamic ? LifetimeDynamic : LifetimeStatic, args.model->num_geometries, 1); vk_ray_model_t *ray_model; int max_prims = 0; diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index c7bbf6da..cd5a538e 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -2,6 +2,7 @@ #include "ray_pass.h" #include "ray_resources.h" +#include "vk_ray_accel.h" #include "vk_ray_primary.h" #include "vk_ray_light_direct.h" @@ -27,9 +28,6 @@ #include -#define MAX_SCRATCH_BUFFER (32*1024*1024) -#define MAX_ACCELS_BUFFER (64*1024*1024) - #define MAX_FRAMES_IN_FLIGHT 2 // TODO settings/realtime modifiable/adaptive @@ -77,36 +75,6 @@ static struct { vk_buffer_t uniform_buffer; uint32_t uniform_unit_size; - // Stores AS built data. Lifetime similar to render buffer: - // - some portion lives for entire map lifetime - // - some portion lives only for a single frame (may have several frames in flight) - // TODO: unify this with render buffer - // Needs: AS_STORAGE_BIT, SHADER_DEVICE_ADDRESS_BIT - vk_buffer_t accels_buffer; - struct alo_pool_s *accels_buffer_alloc; - - // Temp: lives only during a single frame (may have many in flight) - // Used for building ASes; - // Needs: AS_STORAGE_BIT, SHADER_DEVICE_ADDRESS_BIT - vk_buffer_t scratch_buffer; - VkDeviceAddress accels_buffer_addr, scratch_buffer_addr; - - // Temp-ish: used for making TLAS, contains addressed to all used BLASes - // Lifetime and nature of usage similar to scratch_buffer - // TODO: unify them - // Needs: SHADER_DEVICE_ADDRESS, STORAGE_BUFFER, AS_BUILD_INPUT_READ_ONLY - vk_buffer_t tlas_geom_buffer; - VkDeviceAddress tlas_geom_buffer_addr; - r_flipping_buffer_t tlas_geom_buffer_alloc; - - // TODO need several TLASes for N frames in flight - VkAccelerationStructureKHR tlas; - - // Per-frame data that is accumulated between RayFrameBegin and End calls - struct { - uint32_t scratch_offset; // for building dynamic blases - } frame; - // TODO with proper intra-cmdbuf sync we don't really need 2x images unsigned frame_number; xvk_ray_frame_images_t frames[MAX_FRAMES_IN_FLIGHT]; @@ -122,179 +90,9 @@ static struct { qboolean reload_lighting; } g_rtx = {0}; -VkDeviceAddress getBufferDeviceAddress(VkBuffer buffer) { - const VkBufferDeviceAddressInfo bdai = {.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, .buffer = buffer}; - return vkGetBufferDeviceAddress(vk_core.device, &bdai); -} - -static VkDeviceAddress getASAddress(VkAccelerationStructureKHR as) { - VkAccelerationStructureDeviceAddressInfoKHR asdai = { - .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR, - .accelerationStructure = as, - }; - return vkGetAccelerationStructureDeviceAddressKHR(vk_core.device, &asdai); -} - -// TODO split this into smaller building blocks in a separate module -qboolean createOrUpdateAccelerationStructure(VkCommandBuffer cmdbuf, const as_build_args_t *args, vk_ray_model_t *model) { - qboolean should_create = *args->p_accel == VK_NULL_HANDLE; -#if 1 // update does not work at all on AMD gpus - qboolean is_update = false; // FIXME this crashes for some reason !should_create && args->dynamic; -#else - qboolean is_update = !should_create && args->dynamic; -#endif - - VkAccelerationStructureBuildGeometryInfoKHR build_info = { - .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR, - .type = args->type, - .flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR | ( args->dynamic ? VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR : 0), - .mode = is_update ? VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR : VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR, - .geometryCount = args->n_geoms, - .pGeometries = args->geoms, - .srcAccelerationStructure = is_update ? *args->p_accel : VK_NULL_HANDLE, - }; - - VkAccelerationStructureBuildSizesInfoKHR build_size = { - .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR - }; - - uint32_t scratch_buffer_size = 0; - - ASSERT(args->geoms); - ASSERT(args->n_geoms > 0); - ASSERT(args->p_accel); - - vkGetAccelerationStructureBuildSizesKHR( - vk_core.device, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &build_info, args->max_prim_counts, &build_size); - - scratch_buffer_size = is_update ? build_size.updateScratchSize : build_size.buildScratchSize; - -#if 0 - { - uint32_t max_prims = 0; - for (int i = 0; i < args->n_geoms; ++i) - max_prims += args->max_prim_counts[i]; - gEngine.Con_Reportf( - "AS max_prims=%u, n_geoms=%u, build size: %d, scratch size: %d\n", max_prims, args->n_geoms, build_size.accelerationStructureSize, build_size.buildScratchSize); - } -#endif - - if (MAX_SCRATCH_BUFFER < g_rtx.frame.scratch_offset + scratch_buffer_size) { - gEngine.Con_Printf(S_ERROR "Scratch buffer overflow: left %u bytes, but need %u\n", - MAX_SCRATCH_BUFFER - g_rtx.frame.scratch_offset, - scratch_buffer_size); - return false; - } - - if (should_create) { - const uint32_t as_size = build_size.accelerationStructureSize; - const alo_block_t block = aloPoolAllocate(g_rtx.accels_buffer_alloc, as_size, /*TODO why? align=*/256); - const uint32_t buffer_offset = block.offset; - const VkAccelerationStructureCreateInfoKHR asci = { - .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR, - .buffer = g_rtx.accels_buffer.buffer, - .offset = buffer_offset, - .type = args->type, - .size = as_size, - }; - - if (buffer_offset == ALO_ALLOC_FAILED) { - gEngine.Con_Printf(S_ERROR "Failed to allocated %u bytes for accel buffer\n", asci.size); - return false; - } - - XVK_CHECK(vkCreateAccelerationStructureKHR(vk_core.device, &asci, NULL, args->p_accel)); - SET_DEBUG_NAME(*args->p_accel, VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR, args->debug_name); - - if (model) { - model->size = asci.size; - model->debug.as_offset = buffer_offset; - } - - // gEngine.Con_Reportf("AS=%p, n_geoms=%u, build: %#x %d %#x\n", *args->p_accel, args->n_geoms, buffer_offset, asci.size, buffer_offset + asci.size); - } - - // If not enough data for building, just create - if (!cmdbuf || !args->build_ranges) - return true; - - if (model) { - ASSERT(model->size >= build_size.accelerationStructureSize); - } - - build_info.dstAccelerationStructure = *args->p_accel; - build_info.scratchData.deviceAddress = g_rtx.scratch_buffer_addr + g_rtx.frame.scratch_offset; - //uint32_t scratch_offset_initial = g_rtx.frame.scratch_offset; - g_rtx.frame.scratch_offset += scratch_buffer_size; - g_rtx.frame.scratch_offset = ALIGN_UP(g_rtx.frame.scratch_offset, vk_core.physical_device.properties_accel.minAccelerationStructureScratchOffsetAlignment); - - //gEngine.Con_Reportf("AS=%p, n_geoms=%u, scratch: %#x %d %#x\n", *args->p_accel, args->n_geoms, scratch_offset_initial, scratch_buffer_size, scratch_offset_initial + scratch_buffer_size); - - vkCmdBuildAccelerationStructuresKHR(cmdbuf, 1, &build_info, &args->build_ranges); - return true; -} - -static void createTlas( VkCommandBuffer cmdbuf, VkDeviceAddress instances_addr ) { - const VkAccelerationStructureGeometryKHR tl_geom[] = { - { - .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR, - //.flags = VK_GEOMETRY_OPAQUE_BIT, - .geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR, - .geometry.instances = - (VkAccelerationStructureGeometryInstancesDataKHR){ - .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR, - .data.deviceAddress = instances_addr, - .arrayOfPointers = VK_FALSE, - }, - }, - }; - const uint32_t tl_max_prim_counts[ARRAYSIZE(tl_geom)] = { MAX_ACCELS }; //cmdbuf == VK_NULL_HANDLE ? MAX_ACCELS : g_ray_model_state.frame.num_models }; - const VkAccelerationStructureBuildRangeInfoKHR tl_build_range = { - .primitiveCount = g_ray_model_state.frame.num_models, - }; - const as_build_args_t asrgs = { - .geoms = tl_geom, - .max_prim_counts = tl_max_prim_counts, - .build_ranges = cmdbuf == VK_NULL_HANDLE ? NULL : &tl_build_range, - .n_geoms = ARRAYSIZE(tl_geom), - .type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, - // we can't really rebuild TLAS because instance count changes are not allowed .dynamic = true, - .dynamic = false, - .p_accel = &g_rtx.tlas, - .debug_name = "TLAS", - }; - if (!createOrUpdateAccelerationStructure(cmdbuf, &asrgs, NULL)) { - gEngine.Host_Error("Could not create/update TLAS\n"); - return; - } -} void VK_RayNewMap( void ) { - const int expected_accels = 512; // TODO actually get this from playing the game - const int accels_alignment = 256; // TODO where does this come from? - ASSERT(vk_core.rtx); - - if (g_rtx.accels_buffer_alloc) - aloPoolDestroy(g_rtx.accels_buffer_alloc); - g_rtx.accels_buffer_alloc = aloPoolCreate(MAX_ACCELS_BUFFER, expected_accels, accels_alignment); - - // Clear model cache - for (int i = 0; i < ARRAYSIZE(g_ray_model_state.models_cache); ++i) { - vk_ray_model_t *model = g_ray_model_state.models_cache + i; - VK_RayModelDestroy(model); - } - - // Recreate tlas - // Why here and not in init: to make sure that its memory is preserved. Map init will clear all memory regions. - { - if (g_rtx.tlas != VK_NULL_HANDLE) { - vkDestroyAccelerationStructureKHR(vk_core.device, g_rtx.tlas, NULL); - g_rtx.tlas = VK_NULL_HANDLE; - } - - createTlas(VK_NULL_HANDLE, g_rtx.tlas_geom_buffer_addr); - } - + RT_VkAccelNewMap(); RT_RayModel_Clear(); } @@ -302,7 +100,7 @@ void VK_RayFrameBegin( void ) { ASSERT(vk_core.rtx); - g_rtx.frame.scratch_offset = 0; + RT_VkAccelFrameBegin(); if (g_ray_model_state.freeze_models) return; @@ -319,75 +117,6 @@ void VK_RayFrameBegin( void ) RT_LightsFrameBegin(); } -static void prepareTlas( VkCommandBuffer cmdbuf ) { - ASSERT(g_ray_model_state.frame.num_models > 0); - DEBUG_BEGIN(cmdbuf, "prepare tlas"); - - R_FlippingBuffer_Flip( &g_rtx.tlas_geom_buffer_alloc ); - - const uint32_t instance_offset = R_FlippingBuffer_Alloc(&g_rtx.tlas_geom_buffer_alloc, g_ray_model_state.frame.num_models, 1); - ASSERT(instance_offset != ALO_ALLOC_FAILED); - - // Upload all blas instances references to GPU mem - { - VkAccelerationStructureInstanceKHR* inst = ((VkAccelerationStructureInstanceKHR*)g_rtx.tlas_geom_buffer.mapped) + instance_offset; - for (int i = 0; i < g_ray_model_state.frame.num_models; ++i) { - const vk_ray_draw_model_t* const model = g_ray_model_state.frame.models + i; - ASSERT(model->model); - ASSERT(model->model->as != VK_NULL_HANDLE); - inst[i] = (VkAccelerationStructureInstanceKHR){ - .instanceCustomIndex = model->model->kusochki_offset, - .instanceShaderBindingTableRecordOffset = 0, - .accelerationStructureReference = getASAddress(model->model->as), // TODO cache this addr - }; - switch (model->material_mode) { - case MaterialMode_Opaque: - inst[i].mask = GEOMETRY_BIT_OPAQUE; - inst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_REGULAR, - inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR; - break; - case MaterialMode_Opaque_AlphaTest: - inst[i].mask = GEOMETRY_BIT_OPAQUE; - inst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_ALPHA_TEST, - inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR; - break; - case MaterialMode_Refractive: - inst[i].mask = GEOMETRY_BIT_REFRACTIVE; - inst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_REGULAR, - inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR; - break; - case MaterialMode_Additive: - inst[i].mask = GEOMETRY_BIT_ADDITIVE; - inst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_ADDITIVE, - inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR; - break; - } - memcpy(&inst[i].transform, model->transform_row, sizeof(VkTransformMatrixKHR)); - } - } - - // Barrier for building all BLASes - // BLAS building is now in cmdbuf, need to synchronize with results - { - VkBufferMemoryBarrier bmb[] = { { - .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR, // | VK_ACCESS_TRANSFER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR, - .buffer = g_rtx.accels_buffer.buffer, - .offset = instance_offset * sizeof(VkAccelerationStructureInstanceKHR), - .size = g_ray_model_state.frame.num_models * sizeof(VkAccelerationStructureInstanceKHR), - } }; - vkCmdPipelineBarrier(cmdbuf, - VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, - VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, - 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); - } - - // 2. Build TLAS - createTlas(cmdbuf, g_rtx.tlas_geom_buffer_addr + instance_offset * sizeof(VkAccelerationStructureInstanceKHR)); - DEBUG_END(cmdbuf); -} - static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int frame_index, float fov_angle_y ) { struct UniformBuffer *ubo = (struct UniformBuffer*)((char*)g_rtx.uniform_buffer.mapped + frame_index * g_rtx.uniform_unit_size); @@ -422,7 +151,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* .value.accel = (VkWriteDescriptorSetAccelerationStructureKHR){ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, .accelerationStructureCount = 1, - .pAccelerationStructures = &g_rtx.tlas, + .pAccelerationStructures = &g_accel.tlas, .pNext = NULL, }, }, @@ -495,7 +224,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* } DEBUG_BEGIN(cmdbuf, "yay tracing"); - prepareTlas(cmdbuf); + RT_VkAccelPrepareTlas(cmdbuf); prepareUniformBuffer(args->render_args, args->frame_index, args->fov_angle_y); // 4. Barrier for TLAS build @@ -504,7 +233,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, .srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR, .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, - .buffer = g_rtx.accels_buffer.buffer, + .buffer = g_accel.accels_buffer.buffer, .offset = 0, .size = VK_WHOLE_SIZE, } }; @@ -648,6 +377,9 @@ qboolean VK_RayInit( void ) ASSERT(vk_core.rtx); // TODO complain and cleanup on failure + if (!RT_VkAccelInit()) + return false; + g_rtx.pass.primary_ray = R_VkRayPrimaryPassCreate(); ASSERT(g_rtx.pass.primary_ray); @@ -669,33 +401,6 @@ qboolean VK_RayInit( void ) return false; } - if (!VK_BufferCreate("ray accels_buffer", &g_rtx.accels_buffer, MAX_ACCELS_BUFFER, - VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT - )) - { - return false; - } - g_rtx.accels_buffer_addr = getBufferDeviceAddress(g_rtx.accels_buffer.buffer); - - if (!VK_BufferCreate("ray scratch_buffer", &g_rtx.scratch_buffer, MAX_SCRATCH_BUFFER, - VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT - )) { - return false; - } - g_rtx.scratch_buffer_addr = getBufferDeviceAddress(g_rtx.scratch_buffer.buffer); - - if (!VK_BufferCreate("ray tlas_geom_buffer", &g_rtx.tlas_geom_buffer, sizeof(VkAccelerationStructureInstanceKHR) * MAX_ACCELS * 2, - VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | - VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { - // FIXME complain, handle - return false; - } - g_rtx.tlas_geom_buffer_addr = getBufferDeviceAddress(g_rtx.tlas_geom_buffer.buffer); - R_FlippingBuffer_Init(&g_rtx.tlas_geom_buffer_alloc, MAX_ACCELS * 2); - if (!VK_BufferCreate("ray kusochki_buffer", &g_ray_model_state.kusochki_buffer, sizeof(vk_kusok_data_t) * MAX_KUSOCHKI, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { @@ -772,22 +477,8 @@ void VK_RayShutdown( void ) { XVK_ImageDestroy(&g_rtx.frames[i].additive); } - if (g_rtx.tlas != VK_NULL_HANDLE) - vkDestroyAccelerationStructureKHR(vk_core.device, g_rtx.tlas, NULL); - - for (int i = 0; i < ARRAYSIZE(g_ray_model_state.models_cache); ++i) { - vk_ray_model_t *model = g_ray_model_state.models_cache + i; - if (model->as != VK_NULL_HANDLE) - vkDestroyAccelerationStructureKHR(vk_core.device, model->as, NULL); - model->as = VK_NULL_HANDLE; - } - - VK_BufferDestroy(&g_rtx.scratch_buffer); - VK_BufferDestroy(&g_rtx.accels_buffer); - VK_BufferDestroy(&g_rtx.tlas_geom_buffer); VK_BufferDestroy(&g_ray_model_state.kusochki_buffer); VK_BufferDestroy(&g_rtx.uniform_buffer); - if (g_rtx.accels_buffer_alloc) - aloPoolDestroy(g_rtx.accels_buffer_alloc); + RT_VkAccelShutdown(); } From d34bf3aa8d305f419c5910cccebc850a86d421d8 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 3 Sep 2022 11:25:33 -0700 Subject: [PATCH 437/548] rt: fix studio models colorful flickering --- ref_vk/vk_light.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index c9b8c5ec..60a04e50 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -955,6 +955,7 @@ qboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id ) { VectorCopy(etex->emissive, out); return true; } else { + VectorClear(out); return false; } } From 0d349848abe6252aae9b7578bb1fde5b273dd0cb Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 3 Sep 2022 12:08:46 -0700 Subject: [PATCH 438/548] vk: ask validation for sync and best practices --- ref_vk/vk_core.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index c3efe7a8..c33762d5 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -158,7 +158,7 @@ static qboolean createInstance( void ) { const char ** instance_extensions = NULL; unsigned int num_instance_extensions = vk_core.debug ? 1 : 0; - VkApplicationInfo app_info = { + const VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, // TODO support versions 1.0 and 1.1 for simple traditional rendering // This would require using older physical device features and props query structures @@ -169,9 +169,19 @@ static qboolean createInstance( void ) .pApplicationName = "", .pEngineName = "xash3d-fwgs", }; + const VkValidationFeatureEnableEXT validation_features[] = { + VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT, + VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT, + }; + const VkValidationFeaturesEXT validation_ext = { + .sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT, + .pEnabledValidationFeatures = validation_features, + .enabledValidationFeatureCount = COUNTOF(validation_features), + }; VkInstanceCreateInfo create_info = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app_info, + .pNext = vk_core.validate ? &validation_ext : NULL, }; int vid_extensions = gEngine.XVK_GetInstanceExtensions(0, NULL); From 61b2ced49f6d650983e07720dcb703174d23450f Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 3 Sep 2022 14:04:50 -0700 Subject: [PATCH 439/548] vk: add experimental nv checkpoints support doesn't really work reliably --- ref_vk/vk_core.c | 2 +- ref_vk/vk_core.h | 21 +++++++++++++++ ref_vk/vk_nv_aftermath.c | 55 ++++++++++++++++++++++++++++++++++++++++ ref_vk/vk_staging.c | 1 + 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index c33762d5..b3da53c9 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -510,7 +510,7 @@ static qboolean createDevice( void ) { VkDeviceDiagnosticsConfigCreateInfoNV diag_config_nv = { .sType = VK_STRUCTURE_TYPE_DEVICE_DIAGNOSTICS_CONFIG_CREATE_INFO_NV, .pNext = head, - .flags = VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_AUTOMATIC_CHECKPOINTS_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_RESOURCE_TRACKING_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_DEBUG_INFO_BIT_NV, + .flags = VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_AUTOMATIC_CHECKPOINTS_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_RESOURCE_TRACKING_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_DEBUG_INFO_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_ERROR_REPORTING_BIT_NV }; head = &diag_config_nv; #endif diff --git a/ref_vk/vk_core.h b/ref_vk/vk_core.h index 2b527276..6e5c9940 100644 --- a/ref_vk/vk_core.h +++ b/ref_vk/vk_core.h @@ -107,6 +107,20 @@ do { \ } \ } while (0) +#if USE_AFTERMATH +void R_VK_NV_CheckpointF(VkCommandBuffer cmdbuf, const char *fmt, ...); +void R_Vk_NV_Checkpoint_Dump(void); +#define DEBUG_NV_CHECKPOINTF(cmdbuf, fmt, ...) \ + do { \ + if (vk_core.debug) { \ + R_Vk_NV_CheckpointF(cmdbuf, fmt, ##__VA_ARGS__); \ + } \ + } while(0) +#else +#define DEBUG_CHECKPOINTF(...) +#define R_Vk_NV_Checkpoint_Dump() +#endif + #define DEBUG_BEGIN(cmdbuf, msg) \ do { \ if (vk_core.debug) { \ @@ -115,6 +129,7 @@ do { \ .pLabelName = msg, \ }; \ vkCmdBeginDebugUtilsLabelEXT(cmdbuf, &label); \ + DEBUG_NV_CHECKPOINTF(cmdbuf, "begin %s", msg); \ } \ } while(0) @@ -128,6 +143,7 @@ do { \ .pLabelName = buf, \ }; \ vkCmdBeginDebugUtilsLabelEXT(cmdbuf, &label); \ + DEBUG_NV_CHECKPOINTF(cmdbuf, "begin " fmt, ##__VA_ARGS__); \ } \ } while(0) @@ -135,6 +151,7 @@ do { \ do { \ if (vk_core.debug) { \ vkCmdEndDebugUtilsLabelEXT(cmdbuf); \ + DEBUG_NV_CHECKPOINTF(cmdbuf, "end "); /* TODO: find corresponding begin */ \ } \ } while(0) @@ -145,6 +162,8 @@ do { \ if (result != VK_SUCCESS) { \ gEngine.Con_Printf( S_ERROR "%s:%d " #f " failed (%d): %s\n", \ __FILE__, __LINE__, result, R_VkResultName(result)); \ + if (vk_core.debug) \ + R_Vk_NV_Checkpoint_Dump(); \ gEngine.Host_Error( S_ERROR "%s:%d " #f " failed (%d): %s\n", \ __FILE__, __LINE__, result, R_VkResultName(result)); \ } \ @@ -254,6 +273,8 @@ do { \ X(vkCmdClearColorImage) \ X(vkCmdCopyImage) \ X(vkGetImageSubresourceLayout) \ + X(vkCmdSetCheckpointNV) \ + X(vkGetQueueCheckpointDataNV) \ #define DEVICE_FUNCS_RTX(X) \ X(vkGetAccelerationStructureBuildSizesKHR) \ diff --git a/ref_vk/vk_nv_aftermath.c b/ref_vk/vk_nv_aftermath.c index 320b6772..3c2effab 100644 --- a/ref_vk/vk_nv_aftermath.c +++ b/ref_vk/vk_nv_aftermath.c @@ -2,6 +2,7 @@ #include "vk_nv_aftermath.h" #include "vk_common.h" +#include "vk_core.h" #include "xash3d_types.h" @@ -66,6 +67,7 @@ static qboolean writeFile(const char *filename, const void *data, size_t size) { static void callbackGpuCrashDump(const void* pGpuCrashDump, const uint32_t gpuCrashDumpSize, void* pUserData) { gEngine.Con_Printf(S_ERROR "AFTERMATH GPU CRASH DUMP: %p, size=%d\n", pGpuCrashDump, gpuCrashDumpSize); writeFile("ref_vk.nv-gpudmp", pGpuCrashDump, gpuCrashDumpSize); + R_Vk_NV_Checkpoint_Dump(); } static void callbackShaderDebugInfo(const void* pShaderDebugInfo, const uint32_t shaderDebugInfoSize, void* pUserData) { @@ -109,4 +111,57 @@ void VK_AftermathShutdown() { GFSDK_Aftermath_DisableGpuCrashDumps(); } } + +#define MAX_NV_CHECKPOINTS 2048 + +typedef struct { + unsigned sequence; + char message[256]; +} vk_nv_checkpoint_entry_t; + +static struct { + unsigned sequence; + vk_nv_checkpoint_entry_t entries[MAX_NV_CHECKPOINTS]; +} g_nv_checkpoint = {0}; + +void R_Vk_NV_CheckpointF(VkCommandBuffer cmdbuf, const char *fmt, ...) { + va_list argptr; + + ++g_nv_checkpoint.sequence; + + vk_nv_checkpoint_entry_t *entry = g_nv_checkpoint.entries + (g_nv_checkpoint.sequence % MAX_NV_CHECKPOINTS); + entry->sequence = g_nv_checkpoint.sequence; + + va_start( argptr, fmt ); + vsnprintf( entry->message, sizeof entry->message, fmt, argptr ); + va_end( argptr ); + + const uintptr_t marker = entry->sequence; + vkCmdSetCheckpointNV(cmdbuf, (const void*)marker); +} + +void R_Vk_NV_Checkpoint_Dump(void) { + uint32_t checkpoints_count = 0; + vkGetQueueCheckpointDataNV(vk_core.queue, &checkpoints_count, NULL); + + VkCheckpointDataNV checkpoints[32]; + if (checkpoints_count > COUNTOF(checkpoints)) + checkpoints_count = COUNTOF(checkpoints); + + for (int i = 0; i < checkpoints_count; ++i) { + checkpoints[i].pNext = NULL; + checkpoints[i].sType = VK_STRUCTURE_TYPE_CHECKPOINT_DATA_NV; + } + + vkGetQueueCheckpointDataNV(vk_core.queue, &checkpoints_count, checkpoints); + + gEngine.Con_Reportf(S_ERROR "Checkpoints: %d\n", checkpoints_count); + for (int i = 0; i < checkpoints_count; ++i) { + const VkCheckpointDataNV *const checkpoint = checkpoints + i; + const unsigned sequence = (uintptr_t)checkpoint->pCheckpointMarker; + const vk_nv_checkpoint_entry_t *const entry = g_nv_checkpoint.entries + (sequence % MAX_NV_CHECKPOINTS); + gEngine.Con_Reportf(S_ERROR "\t%u: stage=%04x msg: %s\n", sequence, checkpoint->stage, entry->sequence == sequence ? entry->message : "[OBSOLETE]"); + } +} + #endif //ifdef USE_AFTERMATH diff --git a/ref_vk/vk_staging.c b/ref_vk/vk_staging.c index 288edf69..e0c45e06 100644 --- a/ref_vk/vk_staging.c +++ b/ref_vk/vk_staging.c @@ -121,6 +121,7 @@ static void commitBuffers(VkCommandBuffer cmdbuf) { continue; if (prev_buffer != VK_NULL_HANDLE) { + DEBUG_NV_CHECKPOINTF(cmdbuf, "staging dst_buffer=%p count=%d", prev_buffer, i-first_copy); vkCmdCopyBuffer(cmdbuf, g_staging.buffer.buffer, prev_buffer, i - first_copy, g_staging.buffers.copy + first_copy); From e1b570fc8fc850ddeea9708d79f82443d21a37c1 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 3 Sep 2022 14:24:19 -0700 Subject: [PATCH 440/548] vk: fix no-aftermatch build --- ref_vk/vk_core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_vk/vk_core.h b/ref_vk/vk_core.h index 6e5c9940..b1f7d201 100644 --- a/ref_vk/vk_core.h +++ b/ref_vk/vk_core.h @@ -117,7 +117,7 @@ void R_Vk_NV_Checkpoint_Dump(void); } \ } while(0) #else -#define DEBUG_CHECKPOINTF(...) +#define DEBUG_NV_CHECKPOINTF(...) #define R_Vk_NV_Checkpoint_Dump() #endif From f93ae5de80bb721318b2ca1ca29d77bc41a73b43 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Mon, 5 Sep 2022 11:23:37 -0700 Subject: [PATCH 441/548] vk: update aftermath to 2022.2; auto install its libraries --- ref_vk/vk_nv_aftermath.c | 69 ++++++++++++++++++++++++---------------- ref_vk/wscript | 4 +++ 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/ref_vk/vk_nv_aftermath.c b/ref_vk/vk_nv_aftermath.c index 3c2effab..7d452d0b 100644 --- a/ref_vk/vk_nv_aftermath.c +++ b/ref_vk/vk_nv_aftermath.c @@ -67,18 +67,17 @@ static qboolean writeFile(const char *filename, const void *data, size_t size) { static void callbackGpuCrashDump(const void* pGpuCrashDump, const uint32_t gpuCrashDumpSize, void* pUserData) { gEngine.Con_Printf(S_ERROR "AFTERMATH GPU CRASH DUMP: %p, size=%d\n", pGpuCrashDump, gpuCrashDumpSize); writeFile("ref_vk.nv-gpudmp", pGpuCrashDump, gpuCrashDumpSize); - R_Vk_NV_Checkpoint_Dump(); } static void callbackShaderDebugInfo(const void* pShaderDebugInfo, const uint32_t shaderDebugInfoSize, void* pUserData) { - GFSDK_Aftermath_ShaderDebugInfoIdentifier identifier = {0}; + GFSDK_Aftermath_ShaderDebugInfoIdentifier identifier = {0}; gEngine.Con_Printf(S_ERROR "AFTERMATH Shader Debug Info: %p, size=%d\n", pShaderDebugInfo, shaderDebugInfoSize); - AM_CHECK(GFSDK_Aftermath_GetShaderDebugInfoIdentifier( - GFSDK_Aftermath_Version_API, - pShaderDebugInfo, - shaderDebugInfoSize, - &identifier)); + AM_CHECK(GFSDK_Aftermath_GetShaderDebugInfoIdentifier( + GFSDK_Aftermath_Version_API, + pShaderDebugInfo, + shaderDebugInfoSize, + &identifier)); char filename[64]; Q_snprintf(filename, sizeof(filename), "shader-%016llX-%016llX.nvdbg", identifier.id[0], identifier.id[1]); @@ -91,27 +90,6 @@ static void callbackGpuCrashDumpDescription(PFN_GFSDK_Aftermath_AddGpuCrashDumpD addValue(GFSDK_Aftermath_GpuCrashDumpDescriptionKey_ApplicationVersion, "v0.0.1"); } -static qboolean initialized = false; -qboolean VK_AftermathInit() { - AM_CHECK(GFSDK_Aftermath_EnableGpuCrashDumps( - GFSDK_Aftermath_Version_API, - GFSDK_Aftermath_GpuCrashDumpWatchedApiFlags_Vulkan, - GFSDK_Aftermath_GpuCrashDumpFeatureFlags_DeferDebugInfoCallbacks, - callbackGpuCrashDump, - callbackShaderDebugInfo, - callbackGpuCrashDumpDescription, - NULL)); - - initialized = true; - return true; -} - -void VK_AftermathShutdown() { - if (initialized) { - GFSDK_Aftermath_DisableGpuCrashDumps(); - } -} - #define MAX_NV_CHECKPOINTS 2048 typedef struct { @@ -124,6 +102,41 @@ static struct { vk_nv_checkpoint_entry_t entries[MAX_NV_CHECKPOINTS]; } g_nv_checkpoint = {0}; +static const char obsolete[] = "[OBSOLETE]"; + +static void callbackResolveMarkers(const void* pMarker, void* pUserData, void** resolvedMarkerData, uint32_t* markerSize) { + const unsigned sequence = (uintptr_t)pMarker; + const vk_nv_checkpoint_entry_t *const entry = g_nv_checkpoint.entries + (sequence % MAX_NV_CHECKPOINTS); + + const char *msg = entry->sequence == sequence ? entry->message : obsolete; + gEngine.Con_Reportf(S_ERROR "resolved marker %u: msg: %s\n", sequence, msg); + + *resolvedMarkerData = (void*)msg; + *markerSize = strlen(msg); +} + +static qboolean initialized = false; +qboolean VK_AftermathInit() { + AM_CHECK(GFSDK_Aftermath_EnableGpuCrashDumps( + GFSDK_Aftermath_Version_API, + GFSDK_Aftermath_GpuCrashDumpWatchedApiFlags_Vulkan, + GFSDK_Aftermath_GpuCrashDumpFeatureFlags_DeferDebugInfoCallbacks, + callbackGpuCrashDump, + callbackShaderDebugInfo, + callbackGpuCrashDumpDescription, + callbackResolveMarkers, + NULL)); + + initialized = true; + return true; +} + +void VK_AftermathShutdown() { + if (initialized) { + GFSDK_Aftermath_DisableGpuCrashDumps(); + } +} + void R_Vk_NV_CheckpointF(VkCommandBuffer cmdbuf, const char *fmt, ...) { va_list argptr; diff --git a/ref_vk/wscript b/ref_vk/wscript index 9da62050..08fe3a3f 100644 --- a/ref_vk/wscript +++ b/ref_vk/wscript @@ -104,6 +104,10 @@ def build(bld): if bld.env.HAVE_AFTERMATH: defines.append('USE_AFTERMATH') libs.append('AFTERMATH') + for file in ['GFSDK_Aftermath_Lib.x64.dll', 'llvm_7_0_1.dll']: + bld.install_files( + bld.env.LIBDIR, + bld.root.find_dir(bld.env.LIBPATH_AFTERMATH[0]).find_node(file)) if bld.env.COMPILER_CC == 'msvc': bld.env.CFLAGS += ['/WX'] From 2c4d0846d42c5696ee4ded1828b6d849f8263547 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 11 Sep 2022 11:38:51 -0700 Subject: [PATCH 442/548] rt: reimagine staging - make staging/prep phase use separate command buffer - flush this command buffer early if needed - move command pool to its own module - move shader loading stuff to pipeline module - cleanup lots of command buffer passing for model loading, it can use staging explicitly now --- ref_vk/vk_brush.c | 6 +- ref_vk/vk_brush.h | 6 +- ref_vk/vk_commandpool.c | 32 +++++++++ ref_vk/vk_commandpool.h | 10 +++ ref_vk/vk_core.c | 67 +----------------- ref_vk/vk_core.h | 17 +---- ref_vk/vk_framectl.c | 18 +++-- ref_vk/vk_light.c | 3 - ref_vk/vk_pipeline.c | 32 +++++++++ ref_vk/vk_ray_model.c | 14 +++- ref_vk/vk_render.c | 6 +- ref_vk/vk_render.h | 2 +- ref_vk/vk_rtx.c | 1 - ref_vk/vk_rtx.h | 2 +- ref_vk/vk_scene.c | 36 +--------- ref_vk/vk_staging.c | 146 +++++++++++++++++++++++++++------------- ref_vk/vk_staging.h | 19 +++--- ref_vk/vk_textures.c | 33 +++------ 18 files changed, 231 insertions(+), 219 deletions(-) create mode 100644 ref_vk/vk_commandpool.c create mode 100644 ref_vk/vk_commandpool.h diff --git a/ref_vk/vk_brush.c b/ref_vk/vk_brush.c index 7f24f346..43d9ed0a 100644 --- a/ref_vk/vk_brush.c +++ b/ref_vk/vk_brush.c @@ -511,7 +511,7 @@ static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { // FIXME move this to rt_light_bsp and static loading { qboolean is_emissive = false; - vec3_t emissive; + vec3_t emissive = {0}; rt_light_add_polygon_t polylight; if (psurf && (psurf->flags & Patch_Surface_Emissive)) { @@ -637,7 +637,7 @@ static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { return true; } -qboolean VK_BrushModelLoad( VkCommandBuffer cmdbuf, model_t *mod, qboolean map ) +qboolean VK_BrushModelLoad( model_t *mod, qboolean map ) { if (mod->cache.data) { @@ -667,7 +667,7 @@ qboolean VK_BrushModelLoad( VkCommandBuffer cmdbuf, model_t *mod, qboolean map ) if (!map && sizes.emissive_surfaces) bmodel->render_model.polylights = Mem_Malloc(vk_core.pool, sizeof(bmodel->render_model.polylights[0]) * sizes.emissive_surfaces); - if (!loadBrushSurfaces(sizes, mod) || !VK_RenderModelInit(cmdbuf, &bmodel->render_model)) { + if (!loadBrushSurfaces(sizes, mod) || !VK_RenderModelInit(&bmodel->render_model)) { gEngine.Con_Printf(S_ERROR "Could not load model %s\n", mod->name); Mem_Free(bmodel); return false; diff --git a/ref_vk/vk_brush.h b/ref_vk/vk_brush.h index 9c950fd2..ae25cd58 100644 --- a/ref_vk/vk_brush.h +++ b/ref_vk/vk_brush.h @@ -1,7 +1,7 @@ #pragma once #include "xash3d_types.h" -#include "vk_render.h" +#include "vk_render.h" // cl_entity_t struct ref_viewpass_s; struct draw_list_s; @@ -11,8 +11,8 @@ struct cl_entity_s; qboolean VK_BrushInit( void ); void VK_BrushShutdown( void ); -qboolean VK_BrushModelLoad( VkCommandBuffer cmdbuf, struct model_s *mod, qboolean map); -void VK_BrushModelDestroy( struct model_s *mod ); +qboolean VK_BrushModelLoad(struct model_s *mod, qboolean map); +void VK_BrushModelDestroy(struct model_s *mod); void VK_BrushModelDraw( const cl_entity_t *ent, int render_mode, const matrix4x4 model ); void VK_BrushStatsClear( void ); diff --git a/ref_vk/vk_commandpool.c b/ref_vk/vk_commandpool.c new file mode 100644 index 00000000..5f66d0bd --- /dev/null +++ b/ref_vk/vk_commandpool.c @@ -0,0 +1,32 @@ +#include "vk_commandpool.h" + +vk_command_pool_t R_VkCommandPoolCreate( int count ) { + vk_command_pool_t ret = {0}; + + const VkCommandPoolCreateInfo cpci = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .queueFamilyIndex = 0, + .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + }; + + VkCommandBufferAllocateInfo cbai = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandBufferCount = count, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + }; + + XVK_CHECK(vkCreateCommandPool(vk_core.device, &cpci, NULL, &ret.pool)); + + cbai.commandPool = ret.pool; + ret.buffers = Mem_Malloc(vk_core.pool, sizeof(VkCommandBuffer) * count); + ret.buffers_count = count; + XVK_CHECK(vkAllocateCommandBuffers(vk_core.device, &cbai, ret.buffers)); + + return ret; +} + +void R_VkCommandPoolDestroy( vk_command_pool_t *pool ) { + ASSERT(pool->buffers); + vkDestroyCommandPool(vk_core.device, pool->pool, NULL); + Mem_Free(pool->buffers); +} diff --git a/ref_vk/vk_commandpool.h b/ref_vk/vk_commandpool.h new file mode 100644 index 00000000..f1281ca3 --- /dev/null +++ b/ref_vk/vk_commandpool.h @@ -0,0 +1,10 @@ +#include "vk_core.h" + +typedef struct { + VkCommandPool pool; + VkCommandBuffer *buffers; + int buffers_count; +} vk_command_pool_t; + +vk_command_pool_t R_VkCommandPoolCreate( int count ); +void R_VkCommandPoolDestroy( vk_command_pool_t *pool ); diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index b3da53c9..89d4b46f 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -17,6 +17,7 @@ #include "vk_descriptor.h" #include "vk_nv_aftermath.h" #include "vk_devmem.h" +#include "vk_commandpool.h" // FIXME move this rt-specific stuff out #include "vk_light.h" @@ -621,37 +622,6 @@ static qboolean initSurface( void ) return true; } -vk_command_pool_t R_VkCommandPoolCreate( int count ) { - vk_command_pool_t ret = {0}; - - const VkCommandPoolCreateInfo cpci = { - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .queueFamilyIndex = 0, - .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, - }; - - VkCommandBufferAllocateInfo cbai = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - .commandBufferCount = count, - .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, - }; - - XVK_CHECK(vkCreateCommandPool(vk_core.device, &cpci, NULL, &ret.pool)); - - cbai.commandPool = ret.pool; - ret.buffers = Mem_Malloc(vk_core.pool, sizeof(VkCommandBuffer) * count); - ret.buffers_count = count; - XVK_CHECK(vkAllocateCommandBuffers(vk_core.device, &cbai, ret.buffers)); - - return ret; -} - -void R_VkCommandPoolDestroy( vk_command_pool_t *pool ) { - ASSERT(pool->buffers); - vkDestroyCommandPool(vk_core.device, pool->pool, NULL); - Mem_Free(pool->buffers); -} - qboolean R_VkInit( void ) { // FIXME !!!! handle initialization errors properly: destroy what has already been created @@ -713,8 +683,6 @@ qboolean R_VkInit( void ) if (!initSurface()) return false; - vk_core.upload_pool = R_VkCommandPoolCreate( 1 ); - if (!VK_DevMemInit()) return false; @@ -812,8 +780,6 @@ void R_VkShutdown( void ) { VK_DevMemDestroy(); - R_VkCommandPoolDestroy( &vk_core.upload_pool ); - vkDestroyDevice(vk_core.device, NULL); #if USE_AFTERMATH @@ -834,37 +800,6 @@ void R_VkShutdown( void ) { gEngine.R_Free_Video(); } -VkShaderModule loadShader(const char *filename) { - fs_offset_t size = 0; - VkShaderModuleCreateInfo smci = { - .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, - }; - VkShaderModule shader; - byte* buf = gEngine.fsapi->LoadFile( filename, &size, false); - uint32_t *pcode; - - if (!buf) - { - gEngine.Host_Error( S_ERROR "Cannot open shader file \"%s\"\n", filename); - } - - if ((size % 4 != 0) || (((uintptr_t)buf & 3) != 0)) { - gEngine.Host_Error( S_ERROR "size %zu or buf %p is not aligned to 4 bytes as required by SPIR-V/Vulkan spec", size, buf); - } - - smci.codeSize = size; - //smci.pCode = (const uint32_t*)buf; - //memcpy(&smci.pCode, &buf, sizeof(void*)); - memcpy(&pcode, &buf, sizeof(pcode)); - smci.pCode = pcode; - - XVK_CHECK(vkCreateShaderModule(vk_core.device, &smci, NULL, &shader)); - SET_DEBUG_NAME(shader, VK_OBJECT_TYPE_SHADER_MODULE, filename); - - Mem_Free(buf); - return shader; -} - VkSemaphore R_VkSemaphoreCreate( void ) { VkSemaphore sema; VkSemaphoreCreateInfo sci = { diff --git a/ref_vk/vk_core.h b/ref_vk/vk_core.h index b1f7d201..5a9fcd38 100644 --- a/ref_vk/vk_core.h +++ b/ref_vk/vk_core.h @@ -10,18 +10,6 @@ qboolean R_VkInit( void ); void R_VkShutdown( void ); -typedef struct { - VkCommandPool pool; - VkCommandBuffer *buffers; - int buffers_count; -} vk_command_pool_t; - -vk_command_pool_t R_VkCommandPoolCreate( int count ); -void R_VkCommandPoolDestroy( vk_command_pool_t *pool ); - -// TODO load from embedded static structs -VkShaderModule loadShader(const char *filename); - VkSemaphore R_VkSemaphoreCreate( void ); void R_VkSemaphoreDestroy(VkSemaphore sema); @@ -64,8 +52,6 @@ typedef struct vulkan_core_s { VkDevice device; VkQueue queue; - vk_command_pool_t upload_pool; - VkSampler default_sampler; unsigned int num_devices; @@ -108,12 +94,13 @@ do { \ } while (0) #if USE_AFTERMATH -void R_VK_NV_CheckpointF(VkCommandBuffer cmdbuf, const char *fmt, ...); +void R_Vk_NV_CheckpointF(VkCommandBuffer cmdbuf, const char *fmt, ...); void R_Vk_NV_Checkpoint_Dump(void); #define DEBUG_NV_CHECKPOINTF(cmdbuf, fmt, ...) \ do { \ if (vk_core.debug) { \ R_Vk_NV_CheckpointF(cmdbuf, fmt, ##__VA_ARGS__); \ + if (0) gEngine.Con_Reportf(fmt "\n", ##__VA_ARGS__); \ } \ } while(0) #else diff --git a/ref_vk/vk_framectl.c b/ref_vk/vk_framectl.c index e48f4d89..033f91c4 100644 --- a/ref_vk/vk_framectl.c +++ b/ref_vk/vk_framectl.c @@ -9,6 +9,7 @@ #include "vk_swapchain.h" #include "vk_image.h" #include "vk_staging.h" +#include "vk_commandpool.h" #include "profiler.h" @@ -217,7 +218,7 @@ void R_BeginFrame( qboolean clearScene ) { ASSERT(!g_frame.current.framebuffer.framebuffer); waitForFrameFence(); - R_VkStagingFrameFlip(); + R_VkStagingFrameBegin(); g_frame.current.framebuffer = R_VkSwapchainAcquire( g_frame.sem_framebuffer_ready[g_frame.current.index] ); vk_frame.width = g_frame.current.framebuffer.width; @@ -252,9 +253,6 @@ static void enqueueRendering( VkCommandBuffer cmdbuf ) { ASSERT(g_frame.current.phase == Phase_FrameBegan); - //R_VkStagingFlushSync(); - - R_VkStagingCommit(cmdbuf); // FIXME where and when VK_Render_FIXME_Barrier(cmdbuf); if (g_frame.rtx_enabled) @@ -301,6 +299,11 @@ static void submit( VkCommandBuffer cmdbuf, qboolean wait ) { XVK_CHECK(vkEndCommandBuffer(cmdbuf)); + const VkCommandBuffer cmdbufs[] = { + R_VkStagingFrameEnd(), + cmdbuf, + }; + { const VkPipelineStageFlags stageflags[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, @@ -317,8 +320,8 @@ static void submit( VkCommandBuffer cmdbuf, qboolean wait ) { const VkSubmitInfo subinfo = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .pNext = NULL, - .commandBufferCount = 1, - .pCommandBuffers = &cmdbuf, + .commandBufferCount = cmdbufs[0] ? 2 : 1, + .pCommandBuffers = cmdbufs[0] ? cmdbufs : cmdbufs + 1, .waitSemaphoreCount = COUNTOF(waitophores), .pWaitSemaphores = waitophores, .pWaitDstStageMask = stageflags, @@ -357,7 +360,8 @@ void R_EndFrame( void ) if (g_frame.current.phase == Phase_FrameBegan) { const VkCommandBuffer cmdbuf = currentCommandBuffer(); enqueueRendering( cmdbuf ); - submit( cmdbuf, false ); + //submit( cmdbuf, false ); + submit( cmdbuf, true ); vk_frame.cmdbuf = VK_NULL_HANDLE; } diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 60a04e50..413bde42 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -1209,9 +1209,6 @@ vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { R_VkStagingUnlock( locked.handle ); - // TODO probably should do this somewhere else - R_VkStagingCommit( cmdbuf ); - return (vk_lights_bindings_t){ .buffer = g_lights_.buffer.buffer, .metadata = { diff --git a/ref_vk/vk_pipeline.c b/ref_vk/vk_pipeline.c index c42dd4bf..a5adc721 100644 --- a/ref_vk/vk_pipeline.c +++ b/ref_vk/vk_pipeline.c @@ -25,6 +25,38 @@ void VK_PipelineShutdown( void ) vkDestroyPipelineCache(vk_core.device, g_pipeline_cache, NULL); } +// TODO load from embedded static structs +static VkShaderModule loadShader(const char *filename) { + fs_offset_t size = 0; + VkShaderModuleCreateInfo smci = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + }; + VkShaderModule shader; + byte* buf = gEngine.fsapi->LoadFile( filename, &size, false); + uint32_t *pcode; + + if (!buf) + { + gEngine.Host_Error( S_ERROR "Cannot open shader file \"%s\"\n", filename); + } + + if ((size % 4 != 0) || (((uintptr_t)buf & 3) != 0)) { + gEngine.Host_Error( S_ERROR "size %zu or buf %p is not aligned to 4 bytes as required by SPIR-V/Vulkan spec", size, buf); + } + + smci.codeSize = size; + //smci.pCode = (const uint32_t*)buf; + //memcpy(&smci.pCode, &buf, sizeof(void*)); + memcpy(&pcode, &buf, sizeof(pcode)); + smci.pCode = pcode; + + XVK_CHECK(vkCreateShaderModule(vk_core.device, &smci, NULL, &shader)); + SET_DEBUG_NAME(shader, VK_OBJECT_TYPE_SHADER_MODULE, filename); + + Mem_Free(buf); + return shader; +} + VkPipeline VK_PipelineGraphicsCreate(const vk_pipeline_graphics_create_info_t *ci) { VkPipeline pipeline; diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index f5bbf2da..c31f6d0a 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -208,7 +208,7 @@ static void applyMaterialToKusok(vk_kusok_data_t* kusok, const vk_render_geometr } } -vk_ray_model_t* VK_RayModelCreate( VkCommandBuffer cmdbuf, vk_ray_model_init_t args ) { +vk_ray_model_t* VK_RayModelCreate( vk_ray_model_init_t args ) { VkAccelerationStructureGeometryKHR *geoms; uint32_t *geom_max_prim_counts; VkAccelerationStructureBuildRangeInfoKHR *geom_build_ranges; @@ -247,6 +247,7 @@ vk_ray_model_t* VK_RayModelCreate( VkCommandBuffer cmdbuf, vk_ray_model_init_t a geom_max_prim_counts = Mem_Malloc(vk_core.pool, args.model->num_geometries * sizeof(*geom_max_prim_counts)); geom_build_ranges = Mem_Calloc(vk_core.pool, args.model->num_geometries * sizeof(*geom_build_ranges)); + /* gEngine.Con_Reportf("Loading model %s, geoms: %d\n", args.model->debug_name, args.model->num_geometries); */ for (int i = 0; i < args.model->num_geometries; ++i) { vk_render_geometry_t *mg = args.model->geometries + i; @@ -283,6 +284,15 @@ vk_ray_model_t* VK_RayModelCreate( VkCommandBuffer cmdbuf, vk_ray_model_init_t a .firstVertex = mg->vertex_offset, }; + /* { */ + /* const uint32_t index_offset = mg->index_offset * sizeof(uint16_t); */ + /* gEngine.Con_Reportf(" g%d: vertices:[%08x, %08x) indices:[%08x, %08x)\n", */ + /* i, */ + /* mg->vertex_offset * sizeof(vk_vertex_t), (mg->vertex_offset + mg->max_vertex) * sizeof(vk_vertex_t), */ + /* index_offset, index_offset + mg->element_count * sizeof(uint16_t) */ + /* ); */ + /* } */ + if (mg->material == kXVkMaterialSky) { kusochki[i].tex_base_color |= KUSOK_MATERIAL_FLAG_SKYBOX; } else { @@ -296,7 +306,7 @@ vk_ray_model_t* VK_RayModelCreate( VkCommandBuffer cmdbuf, vk_ray_model_init_t a R_VkStagingUnlock(kusok_staging.handle); // FIXME this is definitely not the right place. We should upload everything in bulk, and only then build blases in bulk too - R_VkStagingCommit(cmdbuf); + const VkCommandBuffer cmdbuf = R_VkStagingCommit(); { const VkBufferMemoryBarrier bmb[] = { { .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, diff --git a/ref_vk/vk_render.c b/ref_vk/vk_render.c index 12437aec..ac7df43e 100644 --- a/ref_vk/vk_render.c +++ b/ref_vk/vk_render.c @@ -652,7 +652,7 @@ void VK_RenderEndRTX( VkCommandBuffer cmdbuf, VkImageView img_dst_view, VkImage } } -qboolean VK_RenderModelInit( VkCommandBuffer cmdbuf, vk_render_model_t *model ) { +qboolean VK_RenderModelInit( vk_render_model_t *model ) { if (vk_core.rtx && (g_render_state.current_frame_is_ray_traced || !model->dynamic)) { const VkBuffer geom_buffer = R_GeometryBuffer_Get(); // TODO runtime rtx switch: ??? @@ -660,7 +660,7 @@ qboolean VK_RenderModelInit( VkCommandBuffer cmdbuf, vk_render_model_t *model ) .buffer = geom_buffer, .model = model, }; - model->ray_model = VK_RayModelCreate(cmdbuf, args); + model->ray_model = VK_RayModelCreate(args); return !!model->ray_model; } @@ -772,7 +772,7 @@ void VK_RenderModelDynamicCommit( void ) { if (g_dynamic_model.model.num_geometries > 0) { g_dynamic_model.model.dynamic = true; - VK_RenderModelInit( vk_frame.cmdbuf, &g_dynamic_model.model ); + VK_RenderModelInit( &g_dynamic_model.model ); VK_RenderModelDraw( NULL, &g_dynamic_model.model ); } diff --git a/ref_vk/vk_render.h b/ref_vk/vk_render.h index bc74d27b..5f3323fb 100644 --- a/ref_vk/vk_render.h +++ b/ref_vk/vk_render.h @@ -84,7 +84,7 @@ typedef struct vk_render_model_s { int polylights_count; } vk_render_model_t; -qboolean VK_RenderModelInit( VkCommandBuffer cmdbuf, vk_render_model_t* model ); +qboolean VK_RenderModelInit( vk_render_model_t* model ); void VK_RenderModelDestroy( vk_render_model_t* model ); void VK_RenderModelDraw( const cl_entity_t *ent, vk_render_model_t* model ); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index cd5a538e..2bd40522 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -207,7 +207,6 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* // Upload kusochki updates { - R_VkStagingCommit(cmdbuf); const VkBufferMemoryBarrier bmb[] = { { .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, diff --git a/ref_vk/vk_rtx.h b/ref_vk/vk_rtx.h index 2e2ca3aa..04960198 100644 --- a/ref_vk/vk_rtx.h +++ b/ref_vk/vk_rtx.h @@ -11,7 +11,7 @@ typedef struct { VkBuffer buffer; // TODO must be uniform for all models. Shall we read it directly from vk_render? } vk_ray_model_init_t; -struct vk_ray_model_s *VK_RayModelCreate( VkCommandBuffer cmdbuf, vk_ray_model_init_t model_init ); +struct vk_ray_model_s *VK_RayModelCreate( vk_ray_model_init_t model_init ); void VK_RayModelDestroy( struct vk_ray_model_s *model ); void VK_RayFrameBegin( void ); diff --git a/ref_vk/vk_scene.c b/ref_vk/vk_scene.c index e2d784f7..818f8e71 100644 --- a/ref_vk/vk_scene.c +++ b/ref_vk/vk_scene.c @@ -181,18 +181,6 @@ void R_NewMap( void ) { // Need parsed map entities, and also should happen before brush model loading RT_LightsNewMapBegin(map); - // RTX map loading requires command buffer for building blases - if (vk_core.rtx) - { - //ASSERT(!"Not implemented"); - const VkCommandBufferBeginInfo beginfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, - }; - - XVK_CHECK(vkBeginCommandBuffer(vk_core.upload_pool.buffers[0], &beginfo)); - } - // Load all models at once gEngine.Con_Reportf( "Num models: %d:\n", num_models ); for( int i = 0; i < num_models; i++ ) @@ -206,7 +194,7 @@ void R_NewMap( void ) { if( m->type != mod_brush ) continue; - if (!VK_BrushModelLoad( vk_core.upload_pool.buffers[0], m, i == 0 )) + if (!VK_BrushModelLoad(m, i == 0)) { gEngine.Con_Printf( S_ERROR "Couldn't load model %s\n", m->name ); } @@ -216,28 +204,6 @@ void R_NewMap( void ) { // Reads surfaces from loaded brush models (must happen after all brushes are loaded) RT_LightsNewMapEnd(map); - if (!vk_core.rtx) { - // FIXME this is a workaround for uploading staging for non-rtx mode. In rtx mode things get naturally uploaded deep in VK_BrushModelLoad. - // FIXME there shouldn't be this difference. Ideally, rtx would only continue with also building BLASes, but uploading part should be the same. - R_VkStagingFlushSync(); - } else { - R_VKStagingMarkEmpty_FIXME(); - } - - if (vk_core.rtx) - { - //ASSERT(!"Not implemented"); - const VkSubmitInfo subinfo = { - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .commandBufferCount = 1, - .pCommandBuffers = &vk_core.upload_pool.buffers[0], - }; - - XVK_CHECK(vkEndCommandBuffer(vk_core.upload_pool.buffers[0])); - XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, VK_NULL_HANDLE)); - XVK_CHECK(vkQueueWaitIdle(vk_core.queue)); - } - // TODO should we do something like VK_BrushEndLoad? VK_UploadLightmap(); } diff --git a/ref_vk/vk_staging.c b/ref_vk/vk_staging.c index e0c45e06..1eae2c78 100644 --- a/ref_vk/vk_staging.c +++ b/ref_vk/vk_staging.c @@ -1,11 +1,13 @@ #include "vk_staging.h" #include "vk_buffer.h" #include "alolcator.h" +#include "vk_commandpool.h" #include #define DEFAULT_STAGING_SIZE (64*1024*1024) #define MAX_STAGING_ALLOCS (2048) +#define MAX_CONCURRENT_FRAMES 2 typedef struct { VkImage image; @@ -33,13 +35,18 @@ static struct { struct { uint32_t offset; - } frames[2]; + } frames[MAX_CONCURRENT_FRAMES]; + + vk_command_pool_t upload_pool; + VkCommandBuffer cmdbuf; } g_staging = {0}; qboolean R_VkStagingInit(void) { if (!VK_BufferCreate("staging", &g_staging.buffer, DEFAULT_STAGING_SIZE, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) return false; + g_staging.upload_pool = R_VkCommandPoolCreate( MAX_CONCURRENT_FRAMES ); + aloRingInit(&g_staging.ring, DEFAULT_STAGING_SIZE); return true; @@ -47,20 +54,58 @@ qboolean R_VkStagingInit(void) { void R_VkStagingShutdown(void) { VK_BufferDestroy(&g_staging.buffer); + R_VkCommandPoolDestroy( &g_staging.upload_pool ); +} + +static void flushStagingBufferSync(void) { + const VkCommandBuffer cmdbuf = R_VkStagingCommit(); + if (!cmdbuf) + return; + + XVK_CHECK(vkEndCommandBuffer(cmdbuf)); + g_staging.cmdbuf = VK_NULL_HANDLE; + + gEngine.Con_Reportf(S_WARN "flushing staging buffer img committed=%d count=%d\n", g_staging.images.committed, g_staging.images.count); + + const VkSubmitInfo subinfo = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &cmdbuf, + }; + + XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, VK_NULL_HANDLE)); + XVK_CHECK(vkQueueWaitIdle(vk_core.queue)); + + g_staging.buffers.committed = g_staging.buffers.count = 0; + g_staging.images.committed = g_staging.images.count = 0; + g_staging.frames[0].offset = g_staging.frames[1].offset = ALO_ALLOC_FAILED; + aloRingInit(&g_staging.ring, DEFAULT_STAGING_SIZE); +}; + +static uint32_t allocateInRing(uint32_t size, uint32_t alignment) { + alignment = alignment < 1 ? 1 : alignment; + + const uint32_t offset = aloRingAlloc(&g_staging.ring, size, alignment ); + if (offset != ALO_ALLOC_FAILED) + return offset; + + flushStagingBufferSync(); + + return aloRingAlloc(&g_staging.ring, size, alignment ); } vk_staging_region_t R_VkStagingLockForBuffer(vk_staging_buffer_args_t args) { if ( g_staging.buffers.count >= MAX_STAGING_ALLOCS ) - return (vk_staging_region_t){0}; + flushStagingBufferSync(); - const int index = g_staging.buffers.count; - - const uint32_t offset = aloRingAlloc(&g_staging.ring, args.size, args.alignment < 1 ? 1 : args.alignment ); + const uint32_t offset = allocateInRing(args.size, args.alignment); if (offset == ALO_ALLOC_FAILED) return (vk_staging_region_t){0}; if (g_staging.frames[1].offset == ALO_ALLOC_FAILED) g_staging.frames[1].offset = offset; + const int index = g_staging.buffers.count; + g_staging.buffers.dest[index] = args.buffer; g_staging.buffers.copy[index] = (VkBufferCopy){ .srcOffset = offset, @@ -78,17 +123,17 @@ vk_staging_region_t R_VkStagingLockForBuffer(vk_staging_buffer_args_t args) { vk_staging_region_t R_VkStagingLockForImage(vk_staging_image_args_t args) { if ( g_staging.images.count >= MAX_STAGING_ALLOCS ) - return (vk_staging_region_t){0}; + flushStagingBufferSync(); - const int index = g_staging.images.count; - staging_image_t *const dest = g_staging.images.dest + index; - - const uint32_t offset = aloRingAlloc(&g_staging.ring, args.size, args.alignment); + const uint32_t offset = allocateInRing(args.size, args.alignment); if (offset == ALO_ALLOC_FAILED) return (vk_staging_region_t){0}; if (g_staging.frames[1].offset == ALO_ALLOC_FAILED) g_staging.frames[1].offset = offset; + const int index = g_staging.images.count; + staging_image_t *const dest = g_staging.images.dest + index; + dest->image = args.image; dest->layout = args.layout; g_staging.images.copy[index] = args.region; @@ -117,6 +162,11 @@ static void commitBuffers(VkCommandBuffer cmdbuf) { VkBuffer prev_buffer = VK_NULL_HANDLE; int first_copy = 0; for (int i = g_staging.buffers.committed; i < g_staging.buffers.count; i++) { + /* { */ + /* const VkBufferCopy *const copy = g_staging.buffers.copy + i; */ + /* gEngine.Con_Reportf(" %d: [%08llx, %08llx) => [%08llx, %08llx)\n", i, copy->srcOffset, copy->srcOffset + copy->size, copy->dstOffset, copy->dstOffset + copy->size); */ + /* } */ + if (prev_buffer == g_staging.buffers.dest[i]) continue; @@ -132,6 +182,7 @@ static void commitBuffers(VkCommandBuffer cmdbuf) { } if (prev_buffer != VK_NULL_HANDLE) { + DEBUG_NV_CHECKPOINTF(cmdbuf, "staging dst_buffer=%p count=%d", prev_buffer, g_staging.buffers.count-first_copy); vkCmdCopyBuffer(cmdbuf, g_staging.buffer.buffer, prev_buffer, g_staging.buffers.count - first_copy, g_staging.buffers.copy + first_copy); @@ -142,6 +193,11 @@ static void commitBuffers(VkCommandBuffer cmdbuf) { static void commitImages(VkCommandBuffer cmdbuf) { for (int i = g_staging.images.committed; i < g_staging.images.count; i++) { + /* { */ + /* const VkBufferImageCopy *const copy = g_staging.images.copy + i; */ + /* gEngine.Con_Reportf(" i%d: [%08llx, ?) => %p\n", i, copy->bufferOffset, g_staging.images.dest[i].image); */ + /* } */ + vkCmdCopyBufferToImage(cmdbuf, g_staging.buffer.buffer, g_staging.images.dest[i].image, g_staging.images.dest[i].layout, @@ -151,13 +207,32 @@ static void commitImages(VkCommandBuffer cmdbuf) { g_staging.images.committed = g_staging.images.count; } +VkCommandBuffer R_VkStagingGetCommandBuffer(void) { + if (g_staging.cmdbuf) + return g_staging.cmdbuf; -void R_VkStagingCommit(VkCommandBuffer cmdbuf) { - commitBuffers(cmdbuf); - commitImages(cmdbuf); + g_staging.cmdbuf = g_staging.upload_pool.buffers[0]; + + const VkCommandBufferBeginInfo beginfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + }; + XVK_CHECK(vkBeginCommandBuffer(g_staging.cmdbuf, &beginfo)); + + return g_staging.cmdbuf; } -void R_VkStagingFrameFlip(void) { +VkCommandBuffer R_VkStagingCommit(void) { + if (!g_staging.images.count && !g_staging.buffers.count && !g_staging.cmdbuf) + return VK_NULL_HANDLE; + + const VkCommandBuffer cmdbuf = R_VkStagingGetCommandBuffer(); + commitBuffers(cmdbuf); + commitImages(cmdbuf); + return cmdbuf; +} + +void R_VkStagingFrameBegin(void) { if (g_staging.frames[0].offset != ALO_ALLOC_FAILED) aloRingFree(&g_staging.ring, g_staging.frames[0].offset); @@ -168,39 +243,16 @@ void R_VkStagingFrameFlip(void) { g_staging.images.committed = g_staging.images.count = 0; } -void R_VKStagingMarkEmpty_FIXME(void) { - g_staging.buffers.committed = g_staging.buffers.count = 0; - g_staging.images.committed = g_staging.images.count = 0; - g_staging.frames[0].offset = g_staging.frames[1].offset = ALO_ALLOC_FAILED; - aloRingInit(&g_staging.ring, DEFAULT_STAGING_SIZE); -} - -void R_VkStagingFlushSync(void) { - if ( g_staging.buffers.count == g_staging.buffers.committed - && g_staging.images.count == g_staging.images.committed) - return; - - { - // FIXME get the right one - const VkCommandBuffer cmdbuf = vk_core.upload_pool.buffers[0]; - - const VkCommandBufferBeginInfo beginfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, - }; - - const VkSubmitInfo subinfo = { - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .commandBufferCount = 1, - .pCommandBuffers = &cmdbuf, - }; - - XVK_CHECK(vkBeginCommandBuffer(cmdbuf, &beginfo)); - R_VkStagingCommit(cmdbuf); +VkCommandBuffer R_VkStagingFrameEnd(void) { + const VkCommandBuffer cmdbuf = R_VkStagingCommit(); + if (cmdbuf) XVK_CHECK(vkEndCommandBuffer(cmdbuf)); - XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, VK_NULL_HANDLE)); - XVK_CHECK(vkQueueWaitIdle(vk_core.queue)); - R_VKStagingMarkEmpty_FIXME(); - } + g_staging.cmdbuf = VK_NULL_HANDLE; + + const VkCommandBuffer tmp = g_staging.upload_pool.buffers[0]; + g_staging.upload_pool.buffers[0] = g_staging.upload_pool.buffers[1]; + g_staging.upload_pool.buffers[1] = tmp; + + return cmdbuf; } diff --git a/ref_vk/vk_staging.h b/ref_vk/vk_staging.h index 5eef7ecc..39117ea2 100644 --- a/ref_vk/vk_staging.h +++ b/ref_vk/vk_staging.h @@ -34,13 +34,16 @@ vk_staging_region_t R_VkStagingLockForImage(vk_staging_image_args_t args); // Mark allocated region as ready for upload void R_VkStagingUnlock(staging_handle_t handle); -// Append copy commands to command buffer and mark staging as empty -// FIXME: it's not empty yet, as it depends on cmdbuf being actually submitted and completed -void R_VkStagingCommit(VkCommandBuffer cmdbuf); -void R_VkStagingFrameFlip(void); +// Append copy commands to command buffer. +VkCommandBuffer R_VkStagingCommit(void); -// FIXME Remove this with proper staging -void R_VKStagingMarkEmpty_FIXME(void); +// Mark previous frame data as uploaded and safe to use. +void R_VkStagingFrameBegin(void); -// Force commit synchronously -void R_VkStagingFlushSync(void); +// Uploads staging contents and returns the command buffer ready to be submitted. +// Can return NULL if there's nothing to upload. +VkCommandBuffer R_VkStagingFrameEnd(void); + +// Gets the current command buffer. +// WARNING: Can be invalidated by any of the Lock calls +VkCommandBuffer R_VkStagingGetCommandBuffer(void); diff --git a/ref_vk/vk_textures.c b/ref_vk/vk_textures.c index 7a830af8..cfabbb51 100644 --- a/ref_vk/vk_textures.c +++ b/ref_vk/vk_textures.c @@ -482,7 +482,6 @@ static void BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, in static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, int num_layers, qboolean cubemap) { const VkFormat format = VK_GetFormat(layers[0]->type); int mipCount = 0; - const VkCommandBuffer cmdbuf = vk_core.upload_pool.buffers[0]; // TODO non-rbga textures @@ -546,12 +545,6 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, } { - // 5. Create/get cmdbuf for transitions - VkCommandBufferBeginInfo beginfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, - }; - // 5.1 upload buf -> image:layout:DST // 5.1.1 transitionToLayout(UNDEFINED -> DST) VkImageMemoryBarrier image_barrier = { @@ -569,11 +562,14 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, .layerCount = num_layers, }}; - XVK_CHECK(vkBeginCommandBuffer(cmdbuf, &beginfo)); - vkCmdPipelineBarrier(cmdbuf, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, 0, NULL, 0, NULL, 1, &image_barrier); + { + // cmdbuf may become invalidated in locks in the loops below + const VkCommandBuffer cmdbuf = R_VkStagingGetCommandBuffer(); + vkCmdPipelineBarrier(cmdbuf, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, NULL, 0, NULL, 1, &image_barrier); + } // 5.1.2 copyBufferToImage for all mip levels for (int layer = 0; layer < num_layers; ++layer) { @@ -621,7 +617,7 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, } } - R_VkStagingCommit(cmdbuf); + const VkCommandBuffer cmdbuf = R_VkStagingCommit(); // 5.2 image:layout:DST -> image:layout:SAMPLED // 5.2.1 transitionToLayout(DST -> SHADER_READ_ONLY) @@ -641,19 +637,8 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, &image_barrier); - XVK_CHECK(vkEndCommandBuffer(cmdbuf)); } - { - VkSubmitInfo subinfo = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO}; - subinfo.commandBufferCount = 1; - subinfo.pCommandBuffers = &cmdbuf; - XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, VK_NULL_HANDLE)); - XVK_CHECK(vkQueueWaitIdle(vk_core.queue)); - } - - R_VKStagingMarkEmpty_FIXME(); - // TODO how should we approach this: // - per-texture desc sets can be inconvenient if texture is used in different incompatible contexts // - update descriptor sets in batch? From 85899993fe7dec2d86018bfa4d837d0b2464a9de Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 11 Sep 2022 12:09:47 -0700 Subject: [PATCH 443/548] rt: use flipping buffer for staging mem alloc --- ref_vk/vk_staging.c | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/ref_vk/vk_staging.c b/ref_vk/vk_staging.c index 1eae2c78..61625214 100644 --- a/ref_vk/vk_staging.c +++ b/ref_vk/vk_staging.c @@ -16,7 +16,7 @@ typedef struct { static struct { vk_buffer_t buffer; - alo_ring_t ring; + r_flipping_buffer_t buffer_alloc; struct { VkBuffer dest[MAX_STAGING_ALLOCS]; @@ -33,10 +33,6 @@ static struct { int committed; } images; - struct { - uint32_t offset; - } frames[MAX_CONCURRENT_FRAMES]; - vk_command_pool_t upload_pool; VkCommandBuffer cmdbuf; } g_staging = {0}; @@ -47,7 +43,7 @@ qboolean R_VkStagingInit(void) { g_staging.upload_pool = R_VkCommandPoolCreate( MAX_CONCURRENT_FRAMES ); - aloRingInit(&g_staging.ring, DEFAULT_STAGING_SIZE); + R_FlippingBuffer_Init(&g_staging.buffer_alloc, DEFAULT_STAGING_SIZE); return true; } @@ -73,25 +69,26 @@ static void flushStagingBufferSync(void) { .pCommandBuffers = &cmdbuf, }; + // TODO wait for previous command buffer completion. Why: we might end up writing into the same dst + XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, VK_NULL_HANDLE)); XVK_CHECK(vkQueueWaitIdle(vk_core.queue)); g_staging.buffers.committed = g_staging.buffers.count = 0; g_staging.images.committed = g_staging.images.count = 0; - g_staging.frames[0].offset = g_staging.frames[1].offset = ALO_ALLOC_FAILED; - aloRingInit(&g_staging.ring, DEFAULT_STAGING_SIZE); + R_FlippingBuffer_Clear(&g_staging.buffer_alloc); }; static uint32_t allocateInRing(uint32_t size, uint32_t alignment) { alignment = alignment < 1 ? 1 : alignment; - const uint32_t offset = aloRingAlloc(&g_staging.ring, size, alignment ); + const uint32_t offset = R_FlippingBuffer_Alloc(&g_staging.buffer_alloc, size, alignment ); if (offset != ALO_ALLOC_FAILED) return offset; flushStagingBufferSync(); - return aloRingAlloc(&g_staging.ring, size, alignment ); + return R_FlippingBuffer_Alloc(&g_staging.buffer_alloc, size, alignment ); } vk_staging_region_t R_VkStagingLockForBuffer(vk_staging_buffer_args_t args) { @@ -101,8 +98,6 @@ vk_staging_region_t R_VkStagingLockForBuffer(vk_staging_buffer_args_t args) { const uint32_t offset = allocateInRing(args.size, args.alignment); if (offset == ALO_ALLOC_FAILED) return (vk_staging_region_t){0}; - if (g_staging.frames[1].offset == ALO_ALLOC_FAILED) - g_staging.frames[1].offset = offset; const int index = g_staging.buffers.count; @@ -128,8 +123,6 @@ vk_staging_region_t R_VkStagingLockForImage(vk_staging_image_args_t args) { const uint32_t offset = allocateInRing(args.size, args.alignment); if (offset == ALO_ALLOC_FAILED) return (vk_staging_region_t){0}; - if (g_staging.frames[1].offset == ALO_ALLOC_FAILED) - g_staging.frames[1].offset = offset; const int index = g_staging.images.count; staging_image_t *const dest = g_staging.images.dest + index; @@ -233,11 +226,7 @@ VkCommandBuffer R_VkStagingCommit(void) { } void R_VkStagingFrameBegin(void) { - if (g_staging.frames[0].offset != ALO_ALLOC_FAILED) - aloRingFree(&g_staging.ring, g_staging.frames[0].offset); - - g_staging.frames[0] = g_staging.frames[1]; - g_staging.frames[1].offset = ALO_ALLOC_FAILED; + R_FlippingBuffer_Flip(&g_staging.buffer_alloc); g_staging.buffers.committed = g_staging.buffers.count = 0; g_staging.images.committed = g_staging.images.count = 0; From 9712ae9c13e0bc356948cbd63346ef3652a71b09 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 17 Sep 2022 10:54:18 -0700 Subject: [PATCH 444/548] rt: fix staging command buffer overlap 1. Add one more command buffer to accommodate uploading stuff inbetween frames when there are already 2 frames in flight on GPU. 2. Remove committed from to-upload buffer/image tracking, as it is completely unnecessary. --- ref_vk/vk_framectl.c | 4 ++-- ref_vk/vk_staging.c | 27 +++++++++++++-------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/ref_vk/vk_framectl.c b/ref_vk/vk_framectl.c index 033f91c4..beb2148e 100644 --- a/ref_vk/vk_framectl.c +++ b/ref_vk/vk_framectl.c @@ -360,8 +360,8 @@ void R_EndFrame( void ) if (g_frame.current.phase == Phase_FrameBegan) { const VkCommandBuffer cmdbuf = currentCommandBuffer(); enqueueRendering( cmdbuf ); - //submit( cmdbuf, false ); - submit( cmdbuf, true ); + submit( cmdbuf, false ); + //submit( cmdbuf, true ); vk_frame.cmdbuf = VK_NULL_HANDLE; } diff --git a/ref_vk/vk_staging.c b/ref_vk/vk_staging.c index 61625214..f04007d9 100644 --- a/ref_vk/vk_staging.c +++ b/ref_vk/vk_staging.c @@ -8,6 +8,7 @@ #define DEFAULT_STAGING_SIZE (64*1024*1024) #define MAX_STAGING_ALLOCS (2048) #define MAX_CONCURRENT_FRAMES 2 +#define COMMAND_BUFFER_COUNT (MAX_CONCURRENT_FRAMES + 1) // to accommodate two frames in flight plus something trying to upload data before waiting for the next frame to complete typedef struct { VkImage image; @@ -21,16 +22,13 @@ static struct { struct { VkBuffer dest[MAX_STAGING_ALLOCS]; VkBufferCopy copy[MAX_STAGING_ALLOCS]; - int count; - int committed; } buffers; struct { staging_image_t dest[MAX_STAGING_ALLOCS]; VkBufferImageCopy copy[MAX_STAGING_ALLOCS]; int count; - int committed; } images; vk_command_pool_t upload_pool; @@ -41,7 +39,7 @@ qboolean R_VkStagingInit(void) { if (!VK_BufferCreate("staging", &g_staging.buffer, DEFAULT_STAGING_SIZE, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) return false; - g_staging.upload_pool = R_VkCommandPoolCreate( MAX_CONCURRENT_FRAMES ); + g_staging.upload_pool = R_VkCommandPoolCreate( COMMAND_BUFFER_COUNT ); R_FlippingBuffer_Init(&g_staging.buffer_alloc, DEFAULT_STAGING_SIZE); @@ -61,7 +59,7 @@ static void flushStagingBufferSync(void) { XVK_CHECK(vkEndCommandBuffer(cmdbuf)); g_staging.cmdbuf = VK_NULL_HANDLE; - gEngine.Con_Reportf(S_WARN "flushing staging buffer img committed=%d count=%d\n", g_staging.images.committed, g_staging.images.count); + gEngine.Con_Reportf(S_WARN "flushing staging buffer img count=%d\n", g_staging.images.count); const VkSubmitInfo subinfo = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, @@ -74,8 +72,8 @@ static void flushStagingBufferSync(void) { XVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, VK_NULL_HANDLE)); XVK_CHECK(vkQueueWaitIdle(vk_core.queue)); - g_staging.buffers.committed = g_staging.buffers.count = 0; - g_staging.images.committed = g_staging.images.count = 0; + g_staging.buffers.count = 0; + g_staging.images.count = 0; R_FlippingBuffer_Clear(&g_staging.buffer_alloc); }; @@ -154,7 +152,7 @@ static void commitBuffers(VkCommandBuffer cmdbuf) { VkBuffer prev_buffer = VK_NULL_HANDLE; int first_copy = 0; - for (int i = g_staging.buffers.committed; i < g_staging.buffers.count; i++) { + for (int i = 0; i < g_staging.buffers.count; i++) { /* { */ /* const VkBufferCopy *const copy = g_staging.buffers.copy + i; */ /* gEngine.Con_Reportf(" %d: [%08llx, %08llx) => [%08llx, %08llx)\n", i, copy->srcOffset, copy->srcOffset + copy->size, copy->dstOffset, copy->dstOffset + copy->size); */ @@ -181,11 +179,11 @@ static void commitBuffers(VkCommandBuffer cmdbuf) { g_staging.buffers.count - first_copy, g_staging.buffers.copy + first_copy); } - g_staging.buffers.committed = g_staging.buffers.count; + g_staging.buffers.count = 0; } static void commitImages(VkCommandBuffer cmdbuf) { - for (int i = g_staging.images.committed; i < g_staging.images.count; i++) { + for (int i = 0; i < g_staging.images.count; i++) { /* { */ /* const VkBufferImageCopy *const copy = g_staging.images.copy + i; */ /* gEngine.Con_Reportf(" i%d: [%08llx, ?) => %p\n", i, copy->bufferOffset, g_staging.images.dest[i].image); */ @@ -197,7 +195,7 @@ static void commitImages(VkCommandBuffer cmdbuf) { 1, g_staging.images.copy + i); } - g_staging.images.committed = g_staging.images.count; + g_staging.images.count = 0; } VkCommandBuffer R_VkStagingGetCommandBuffer(void) { @@ -228,8 +226,8 @@ VkCommandBuffer R_VkStagingCommit(void) { void R_VkStagingFrameBegin(void) { R_FlippingBuffer_Flip(&g_staging.buffer_alloc); - g_staging.buffers.committed = g_staging.buffers.count = 0; - g_staging.images.committed = g_staging.images.count = 0; + g_staging.buffers.count = 0; + g_staging.images.count = 0; } VkCommandBuffer R_VkStagingFrameEnd(void) { @@ -241,7 +239,8 @@ VkCommandBuffer R_VkStagingFrameEnd(void) { const VkCommandBuffer tmp = g_staging.upload_pool.buffers[0]; g_staging.upload_pool.buffers[0] = g_staging.upload_pool.buffers[1]; - g_staging.upload_pool.buffers[1] = tmp; + g_staging.upload_pool.buffers[1] = g_staging.upload_pool.buffers[2]; + g_staging.upload_pool.buffers[2] = tmp; return cmdbuf; } From 86ad30c1258ada35d4f286027744679695a1771b Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 17 Sep 2022 11:37:52 -0700 Subject: [PATCH 445/548] rt: fix dynamic/animated textures --- ref_vk/vk_ray_internal.h | 1 + ref_vk/vk_ray_model.c | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/ref_vk/vk_ray_internal.h b/ref_vk/vk_ray_internal.h index cc81189d..ed15c2cc 100644 --- a/ref_vk/vk_ray_internal.h +++ b/ref_vk/vk_ray_internal.h @@ -19,6 +19,7 @@ typedef struct vk_ray_model_s { uint32_t kusochki_offset; qboolean dynamic; qboolean taken; + qboolean kusochki_updated_this_frame; struct { uint32_t as_offset; diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index c31f6d0a..e360d986 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -361,6 +361,7 @@ vk_ray_model_t* VK_RayModelCreate( vk_ray_model_init_t args ) { } else { ray_model->kusochki_offset = kusochki_count_offset; ray_model->dynamic = args.model->dynamic; + ray_model->kusochki_updated_this_frame = true; if (vk_core.debug) validateModel(ray_model); @@ -472,7 +473,7 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render // - collect list of geoms for which we could update anything (animated textues, uvs, etc) // - update only those through staging // - also consider tracking whether the main model color has changed (that'd need to update everything yay) - if (0) // FIXME enabling this makes dynamic models crash the gpu (?!) + if (!model->kusochki_updated_this_frame) // FIXME enabling this makes dynamic models crash the gpu (?!) { const vk_staging_buffer_args_t staging_args = { .buffer = g_ray_model_state.kusochki_buffer.buffer, @@ -494,15 +495,16 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render applyMaterialToKusok(kusochki + i, geom, color, HACK_reflective); } - gEngine.Con_Reportf("model %s: geom=%d kuoffs=%d kustoff=%d kustsz=%d sthndl=%d\n", - render_model->debug_name, - render_model->num_geometries, - model->kusochki_offset, - staging_args.offset, staging_args.size, - kusok_staging.handle - ); + /* gEngine.Con_Reportf("model %s: geom=%d kuoffs=%d kustoff=%d kustsz=%d sthndl=%d\n", */ + /* render_model->debug_name, */ + /* render_model->num_geometries, */ + /* model->kusochki_offset, */ + /* staging_args.offset, staging_args.size, */ + /* kusok_staging.handle */ + /* ); */ R_VkStagingUnlock(kusok_staging.handle); + model->kusochki_updated_this_frame = true; } for (int i = 0; i < render_model->polylights_count; ++i) { @@ -528,6 +530,8 @@ void XVK_RayModel_ClearForNextFrame( void ) for (int i = 0; i < g_ray_model_state.frame.num_models; ++i) { vk_ray_draw_model_t *model = g_ray_model_state.frame.models + i; ASSERT(model->model); + model->model->kusochki_updated_this_frame = false; + if (!model->model->dynamic) continue; From 3f52f46f7db15b21c0c63611fa5d240e980522ed Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Fri, 30 Sep 2022 01:05:07 -0700 Subject: [PATCH 446/548] update TODO with random notes --- ref_vk/TODO.md | 93 ++++++++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 48 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index de2b2227..b3a8eb11 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,64 +1,34 @@ -# Parallel frames -- [ ] allocate for N frames: - - [x] geometries - - [-] rt models - - [-] kusochki - - [x] same ring buffer alloc as for geometries - - [x] extract as a unit - - [ ] tlas geom --//-- - - [ ] lights - - [x] make metadata buffer in lights - - [ ] join lights grid+meta into a single buffer - - [ ] put lights data into a cpu-side vk buffer - - [ ] sync+barrier upload +# Programmable render +- [ ] parse spirv -> get bindings with names + - [ ] spirv needs to be compiled with -g, otherwise there are no OpName entries. Need a custom strip util that strips the rest? + - [ ] unnamed uniform blocks are uncomfortable to parse. +- [ ] passes "export" their bindings as detailed resource descriptions: + - [ ] images: name, r/w, format, resolution (? not found in spv, needs to be externally supplied) + - [ ] buffers: name, r/w, size, type name (?) +- [ ] name -> index resolver (hashmap kekw) +- [ ] automatic creation of resources + - [ ] images + - [ ] buffers +- [ ] implicit dependency tracking. pass defines: + - [ ] imports: list of things it needs + - [ ] exports: list of things it produces. those get created and registered with this pass as a producer -- scratch buffer: - - should be fine (assuming intra-cmdbuf sync), contents lifetime is single frame only -- accels_buffer: - - lifetime: multiple frames; dynamic: some b/tlases get rebuilt every frame - - opt 1: double buffering - - opt 2: intra-cmdbuf sync (can't write unless previous frame is done) -- uniform_buffer: - - lifetime: single frame -- tlas_geom_buffer: - - similar to scratch_buffer - - BUT: filled on CPU, so it's not properly synchronsized - - fix: upload using staging? double/ring buffering? -- light_grid_buffer (+ small lights_buffer): +# Parallel frames sync +- [ ] light_grid_buffer (+ small lights_buffer): - lifetime: single frame - BUT: populated by CPU, needs sync; can't just ring-buffer it - fixes: double-buffering? - staging + sync upload? staging needs to be huge or done in chunks. also, cpu needs to wait on staging upload - 2x size + wait: won't fit into device-local-host-visible mem - decrease size first? -- kusochki_buffer: - - lifetime: multiple frames - - ? who uploads? - - fix: ring-buffer? -- models_cache +- [ ] models_cache - lifetimes: - static; entire map - static; single to multiple frames - dynamic; multiple frames - : intra-cmdbuf - -// O. 1 buffer i bardak v nyom -// - [SSSSAAS.SBAS....] -// - can become extremely fragmented - -// I. 2 buffer + bit indirection -// - lives longer than 2 frames [SSS.SSS..SS.....] -// - dynamic [AAAAA->.....<-BBBBBB] -// - high bits in shader point to buffer - -// II. 1 buffer bi-directional -// - [SSS.SS..S...|AAAABBBB->...] -// ^ - long-living "static" stuff -// ^ - dynamic ring buffer -// - [SSS.SS..S.|.....<-AAAABBBB] (dynamic split) - -# Passes +# Multipass + Sampling - [ ] better simple sampling - [x] all triangles - [x] area based on triangles @@ -466,3 +436,30 @@ - [x] rtx: cull light sources (dlights and light textures) using bsp - [-] crash in PM_RecursiveHullCheck. havent seen this in a while - [x] rtx: remove lbsp + +## 2022-09-17 E207 Parallel frames +- [x] allocate for N frames: + - [x] geometries + - [x] rt models + - [x] kusochki + - [x] same ring buffer alloc as for geometries + - [x] extract as a unit + - [x] tlas geom --//-- + - [-] lights + - [x] make metadata buffer in lights + - [-] join lights grid+meta into a single buffer => pipeline loading issues + - [x] put lights data into a cpu-side vk buffer + - [-] sync+barrier upload => TOO BIG AND TOO SLOW, need to e.g. track dirty regions, compactify stuff (many clusters are the same), etc +- [x] scratch buffer: + - should be fine (assuming intra-cmdbuf sync), contents lifetime is single frame only +- [x] accels_buffer: + - ~~[ ] lifetime: multiple frames; dynamic: some b/tlases get rebuilt every frame~~ + - ~~[ ] opt 1: double buffering~~ + - [x] opt 2: intra-cmdbuf sync (can't write unless previous frame is done) +- [x] uniform_buffer: + - lifetime: single frame +- [x] tlas_geom_buffer: + - similar to scratch_buffer + - BUT: filled on CPU, so it's not properly synchronsized + - fix: upload using staging? + - [x] double/ring buffering From 5d37985ec5db0cf5bde07b4714819d0d9c3a0234 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Wed, 5 Oct 2022 21:39:02 -0700 Subject: [PATCH 447/548] vk: begin parsing spir-v to find out about shader bindings --- ref_vk/vk_pipeline.c | 77 +++++--- ref_vk/vk_spirv.c | 438 +++++++++++++++++++++++++++++++++++++++++++ ref_vk/vk_spirv.h | 31 +++ 3 files changed, 520 insertions(+), 26 deletions(-) create mode 100644 ref_vk/vk_spirv.c create mode 100644 ref_vk/vk_spirv.h diff --git a/ref_vk/vk_pipeline.c b/ref_vk/vk_pipeline.c index a5adc721..e5ba4bb5 100644 --- a/ref_vk/vk_pipeline.c +++ b/ref_vk/vk_pipeline.c @@ -1,4 +1,5 @@ #include "vk_pipeline.h" +#include "vk_spirv.h" #include "vk_framectl.h" // VkRenderPass @@ -25,36 +26,48 @@ void VK_PipelineShutdown( void ) vkDestroyPipelineCache(vk_core.device, g_pipeline_cache, NULL); } -// TODO load from embedded static structs -static VkShaderModule loadShader(const char *filename) { - fs_offset_t size = 0; - VkShaderModuleCreateInfo smci = { - .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, - }; - VkShaderModule shader; - byte* buf = gEngine.fsapi->LoadFile( filename, &size, false); - uint32_t *pcode; +typedef struct { + VkShaderModule module; - if (!buf) - { - gEngine.Host_Error( S_ERROR "Cannot open shader file \"%s\"\n", filename); + vk_spirv_t spirv; +} r_vk_shader_t; + +static qboolean R_VkShaderLoad(r_vk_shader_t *shader, const char *filename) { + *shader = (r_vk_shader_t){0}; + + fs_offset_t size = 0; + byte* const buf = gEngine.fsapi->LoadFile(filename, &size, false); + + if (!buf) { + gEngine.Con_Printf( S_ERROR "Cannot open shader file \"%s\"\n", filename); + return false; } if ((size % 4 != 0) || (((uintptr_t)buf & 3) != 0)) { - gEngine.Host_Error( S_ERROR "size %zu or buf %p is not aligned to 4 bytes as required by SPIR-V/Vulkan spec", size, buf); + gEngine.Con_Printf(S_ERROR "size %zu or buf %p is not aligned to 4 bytes as required by SPIR-V/Vulkan spec", size, buf); + return false; } - smci.codeSize = size; - //smci.pCode = (const uint32_t*)buf; - //memcpy(&smci.pCode, &buf, sizeof(void*)); - memcpy(&pcode, &buf, sizeof(pcode)); - smci.pCode = pcode; + const VkShaderModuleCreateInfo smci = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = size, + .pCode = (const uint32_t*)(void*)buf, + }; - XVK_CHECK(vkCreateShaderModule(vk_core.device, &smci, NULL, &shader)); - SET_DEBUG_NAME(shader, VK_OBJECT_TYPE_SHADER_MODULE, filename); + XVK_CHECK(vkCreateShaderModule(vk_core.device, &smci, NULL, &shader->module)); + SET_DEBUG_NAME(shader->module, VK_OBJECT_TYPE_SHADER_MODULE, filename); + + if (!R_VkSpirvParse(&shader->spirv, smci.pCode, size / 4)) { + gEngine.Con_Printf(S_ERROR "Error parsing SPIR-V for %s\n", filename); + } Mem_Free(buf); - return shader; + return true; +} + +static void R_VkShaderDestroy(r_vk_shader_t *shader) { + R_VkSpirvFree(&shader->spirv); + vkDestroyShaderModule(vk_core.device, shader->module, NULL); } VkPipeline VK_PipelineGraphicsCreate(const vk_pipeline_graphics_create_info_t *ci) @@ -155,20 +168,32 @@ VkPipeline VK_PipelineGraphicsCreate(const vk_pipeline_graphics_create_info_t *c if (ci->num_stages > MAX_STAGES) return VK_NULL_HANDLE; + r_vk_shader_t shaders[MAX_STAGES] = {0}; + for (int i = 0; i < ci->num_stages; ++i) { + if (!R_VkShaderLoad(shaders + i, ci->stages[i].filename)) + goto finalize; + + gEngine.Con_Reportf("Got %d bindings for shader %s\n", shaders[i].spirv.bindings_count, ci->stages[i].filename); + + for (int j = 0; j < shaders[i].spirv.bindings_count; ++j) { + const vk_binding_t *binding = shaders[i].spirv.bindings + j; + gEngine.Con_Reportf(" %02d [%d:%d] name=%s\n", j, binding->descriptor_set, binding->binding, binding->name); + } + stage_create_infos[i] = (VkPipelineShaderStageCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = ci->stages[i].stage, - .module = loadShader(ci->stages[i].filename), + .module = shaders[i].module, .pSpecializationInfo = ci->stages[i].specialization_info, .pName = "main", }; } XVK_CHECK(vkCreateGraphicsPipelines(vk_core.device, g_pipeline_cache, 1, &gpci, NULL, &pipeline)); - +finalize: for (int i = 0; i < ci->num_stages; ++i) { - vkDestroyShaderModule(vk_core.device, stage_create_infos[i].module, NULL); + R_VkShaderDestroy(shaders + i); } return pipeline; @@ -181,7 +206,7 @@ VkPipeline VK_PipelineComputeCreate(const vk_pipeline_compute_create_info_t *ci) .stage = (VkPipelineShaderStageCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_COMPUTE_BIT, - .module = loadShader(ci->shader_filename), + // FIXME .module = loadShader(ci->shader_filename), .pName = "main", .pSpecializationInfo = ci->specialization_info, }, @@ -229,7 +254,7 @@ vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_ stages[i] = (VkPipelineShaderStageCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = stage->stage, - .module = loadShader(stage->filename), + // FIXME .module = loadShader(stage->filename), .pName = "main", .pSpecializationInfo = stage->specialization_info, }; diff --git a/ref_vk/vk_spirv.c b/ref_vk/vk_spirv.c new file mode 100644 index 00000000..9d2c859a --- /dev/null +++ b/ref_vk/vk_spirv.c @@ -0,0 +1,438 @@ +#include "vk_spirv.h" +#include "vk_common.h" + +#include +#include +#include +#include +#include + +//#define L(msg, ...) fprintf(stderr, msg "\n",##__VA_ARGS__) +#define L(msg, ...) gEngine.Con_Reportf(msg "\n",##__VA_ARGS__) +#define LE(msg, ...) gEngine.Con_Printf(S_ERROR msg "\n",##__VA_ARGS__) + +#define MALLOC malloc +#define FREE free + +#define CHECK(cond) assert(cond) + +static const char *opTypeName(uint16_t op) { + switch(op) { + case SpvOpVariable: return "SpvOpVariable"; + case SpvOpTypeVoid: return "SpvOpTypeVoid"; + case SpvOpTypeBool: return "SpvOpTypeBool"; + case SpvOpTypeInt: return "SpvOpTypeInt"; + case SpvOpTypeFloat: return "SpvOpTypeFloat"; + case SpvOpTypeVector: return "SpvOpTypeVector"; + case SpvOpTypeMatrix: return "SpvOpTypeMatrix"; + case SpvOpTypeImage: return "SpvOpTypeImage"; + case SpvOpTypeSampler: return "SpvOpTypeSampler"; + case SpvOpTypeSampledImage: return "SpvOpTypeSampledImage"; + case SpvOpTypeArray: return "SpvOpTypeArray"; + case SpvOpTypeRuntimeArray: return "SpvOpTypeRuntimeArray"; + case SpvOpTypeStruct: return "SpvOpTypeStruct"; + case SpvOpTypeOpaque: return "SpvOpTypeOpaque"; + case SpvOpTypePointer: return "SpvOpTypePointer"; + case SpvOpTypeFunction: return "SpvOpTypeFunction"; + case SpvOpTypeEvent: return "SpvOpTypeEvent"; + case SpvOpTypeDeviceEvent: return "SpvOpTypeDeviceEvent"; + case SpvOpTypeReserveId: return "SpvOpTypeReserveId"; + case SpvOpTypeQueue: return "SpvOpTypeQueue"; + case SpvOpTypePipe: return "SpvOpTypePipe"; + case SpvOpTypeForwardPointer: return "SpvOpTypeForwardPointer"; + case SpvOpTypePipeStorage: return "SpvOpTypePipeStorage"; + case SpvOpTypeNamedBarrier: return "SpvOpTypeNamedBarrier"; + case SpvOpTypeRayQueryKHR: return "SpvOpTypeRayQueryKHR"; + case SpvOpTypeAccelerationStructureKHR: return "SpvOpTypeAccelerationStructureKHR"; + case SpvOpTypeCooperativeMatrixNV: return "SpvOpTypeCooperativeMatrixNV"; + default: return "UNKNOWN"; + } +} + +static const char *storageClassName(SpvStorageClass storage_class) { + switch(storage_class) { + case SpvStorageClassUniformConstant: return "SpvStorageClassUniformConstant"; + case SpvStorageClassInput: return "SpvStorageClassInput"; + case SpvStorageClassUniform: return "SpvStorageClassUniform"; + case SpvStorageClassOutput: return "SpvStorageClassOutput"; + case SpvStorageClassWorkgroup: return "SpvStorageClassWorkgroup"; + case SpvStorageClassCrossWorkgroup: return "SpvStorageClassCrossWorkgroup"; + case SpvStorageClassPrivate: return "SpvStorageClassPrivate"; + case SpvStorageClassFunction: return "SpvStorageClassFunction"; + case SpvStorageClassGeneric: return "SpvStorageClassGeneric"; + case SpvStorageClassPushConstant: return "SpvStorageClassPushConstant"; + case SpvStorageClassAtomicCounter: return "SpvStorageClassAtomicCounter"; + case SpvStorageClassImage: return "SpvStorageClassImage"; + case SpvStorageClassStorageBuffer: return "SpvStorageClassStorageBuffer"; + case SpvStorageClassCallableDataKHR: return "SpvStorageClassCallableDataKHR"; + case SpvStorageClassIncomingCallableDataKHR: return "SpvStorageClassIncomingCallableDataKHR"; + case SpvStorageClassRayPayloadKHR: return "SpvStorageClassRayPayloadKHR"; + case SpvStorageClassHitAttributeKHR: return "SpvStorageClassHitAttributeKHR"; + case SpvStorageClassIncomingRayPayloadKHR: return "SpvStorageClassIncomingRayPayloadKHR"; + case SpvStorageClassShaderRecordBufferKHR: return "SpvStorageClassShaderRecordBufferKHR"; + case SpvStorageClassPhysicalStorageBuffer: return "SpvStorageClassPhysicalStorageBuffer"; + case SpvStorageClassCodeSectionINTEL: return "SpvStorageClassCodeSectionINTEL"; + case SpvStorageClassDeviceOnlyINTEL: return "SpvStorageClassDeviceOnlyINTEL"; + case SpvStorageClassHostOnlyINTEL: return "SpvStorageClassHostOnlyINTEL"; + case SpvStorageClassMax: return "SpvStorageClassMax"; + } + return "UNKNOWN"; +} + +static const char *imageFormatName(SpvImageFormat format) { + switch(format) { + case SpvImageFormatUnknown: return "SpvImageFormatUnknown"; + case SpvImageFormatRgba32f: return "SpvImageFormatRgba32f"; + case SpvImageFormatRgba16f: return "SpvImageFormatRgba16f"; + case SpvImageFormatR32f: return "SpvImageFormatR32f"; + case SpvImageFormatRgba8: return "SpvImageFormatRgba8"; + case SpvImageFormatRgba8Snorm: return "SpvImageFormatRgba8Snorm"; + case SpvImageFormatRg32f: return "SpvImageFormatRg32f"; + case SpvImageFormatRg16f: return "SpvImageFormatRg16f"; + case SpvImageFormatR11fG11fB10f: return "SpvImageFormatR11fG11fB10f"; + case SpvImageFormatR16f: return "SpvImageFormatR16f"; + case SpvImageFormatRgba16: return "SpvImageFormatRgba16"; + case SpvImageFormatRgb10A2: return "SpvImageFormatRgb10A2"; + case SpvImageFormatRg16: return "SpvImageFormatRg16"; + case SpvImageFormatRg8: return "SpvImageFormatRg8"; + case SpvImageFormatR16: return "SpvImageFormatR16"; + case SpvImageFormatR8: return "SpvImageFormatR8"; + case SpvImageFormatRgba16Snorm: return "SpvImageFormatRgba16Snorm"; + case SpvImageFormatRg16Snorm: return "SpvImageFormatRg16Snorm"; + case SpvImageFormatRg8Snorm: return "SpvImageFormatRg8Snorm"; + case SpvImageFormatR16Snorm: return "SpvImageFormatR16Snorm"; + case SpvImageFormatR8Snorm: return "SpvImageFormatR8Snorm"; + case SpvImageFormatRgba32i: return "SpvImageFormatRgba32i"; + case SpvImageFormatRgba16i: return "SpvImageFormatRgba16i"; + case SpvImageFormatRgba8i: return "SpvImageFormatRgba8i"; + case SpvImageFormatR32i: return "SpvImageFormatR32i"; + case SpvImageFormatRg32i: return "SpvImageFormatRg32i"; + case SpvImageFormatRg16i: return "SpvImageFormatRg16i"; + case SpvImageFormatRg8i: return "SpvImageFormatRg8i"; + case SpvImageFormatR16i: return "SpvImageFormatR16i"; + case SpvImageFormatR8i: return "SpvImageFormatR8i"; + case SpvImageFormatRgba32ui: return "SpvImageFormatRgba32ui"; + case SpvImageFormatRgba16ui: return "SpvImageFormatRgba16ui"; + case SpvImageFormatRgba8ui: return "SpvImageFormatRgba8ui"; + case SpvImageFormatR32ui: return "SpvImageFormatR32ui"; + case SpvImageFormatRgb10a2ui: return "SpvImageFormatRgb10a2ui"; + case SpvImageFormatRg32ui: return "SpvImageFormatRg32ui"; + case SpvImageFormatRg16ui: return "SpvImageFormatRg16ui"; + case SpvImageFormatRg8ui: return "SpvImageFormatRg8ui"; + case SpvImageFormatR16ui: return "SpvImageFormatR16ui"; + case SpvImageFormatR8ui: return "SpvImageFormatR8ui"; + case SpvImageFormatR64ui: return "SpvImageFormatR64ui"; + case SpvImageFormatR64i: return "SpvImageFormatR64i"; + case SpvImageFormatMax: return "SpvImageFormatMax"; + } + return "UNKNOWN"; +} + +typedef struct { + int id; + const char *name; + int descriptor_set; + int binding; + + // TODO: in-out, type, image format, etc + //uint32_t flags; +} binding_t; + +#define MAX_BINDINGS 32 +typedef struct node_t { + SpvOp op; + int binding; + const char *name; + uint32_t type_id; + uint32_t storage_class; + uint32_t flags; +} node_t; + +typedef struct { + int nodes_count; + node_t *nodes; + + int bindings_count; + binding_t bindings[MAX_BINDINGS]; +} context_t; + +binding_t *getBinding(context_t *ctx, int id) { + if(id < 0) { + LE("id %d < 0", id); + return NULL; + } + + if (id >= ctx->nodes_count) { + LE("id %d > %d", id, ctx->nodes_count); + return NULL; + } + + node_t *sid = ctx->nodes + id; + + if (sid->binding < 0) { + if (ctx->bindings_count >= MAX_BINDINGS) { + LE("too many bindings %d", MAX_BINDINGS); + return NULL; + } + + sid->binding = ctx->bindings_count++; + ctx->bindings[sid->binding].id = id; + } + + return ctx->bindings + sid->binding; +} + +static qboolean spvParseOp(context_t *ctx, uint16_t op, uint16_t word_count, const uint32_t *args) { + switch (op) { + case SpvOpName: + { + // FIXME check size, check strlen + const uint32_t id = args[0]; + const char *name = (const char*)(args + 1); + //L("OpName(id=%d) => %s", id, name); + ctx->nodes[id].name = name[0] != '\0' ? name : NULL; + break; + } + case SpvOpMemberName: + { + // FIXME check size, check strlen + const uint32_t id = args[0]; + const uint32_t index = args[1]; + const char *name = (const char*)(args + 2); + //L("OpMemberName(id=%d) => index=%d %s", id, index, name); + //ctx->nodes[id].name = name; + break; + } + case SpvOpTypeImage: + { + // FIXME check size check strlen + const uint32_t result_id = args[0]; + const uint32_t type_id = args[1]; + const uint32_t dim = args[2]; + const uint32_t depth = args[3]; + const uint32_t arrayed = args[4]; + const uint32_t ms = args[5]; + const uint32_t sampled = args[6]; + const uint32_t format = args[7]; + node_t *node = ctx->nodes + result_id; + node->op = op; + node->type_id = type_id; + //L("OpTypeImage(id=%d) => type_id=%d dim=%08x depth=%d arrayed=%d ms=%d sampled=%d format=%s(%d)", + //result_id, type_id, dim, depth, arrayed, ms, sampled,imageFormatName(format), format); + break; + } + case SpvOpTypePointer: + { + // FIXME check size + const uint32_t id = args[0]; + const uint32_t storage_class = args[1]; + const uint32_t type_id = args[2]; + node_t *node = ctx->nodes + id; + node->op = op; + node->type_id = type_id; + node->storage_class = storage_class; + //L("OpTypePointer(id=%d) => storage_class=%d type_id=%d", id, storage_class, type_id); + //ctx->nodes[id].name = name; + break; + } + case SpvOpVariable: + { + const uint32_t type_id = args[0]; + const uint32_t result_id = args[1]; + const uint32_t storage_class = args[2]; + node_t *node = ctx->nodes + result_id; + node->op = op; + node->type_id = type_id; + node->storage_class = storage_class; + //L("OpVariable(id=%d) => type_id=%d storage_class=%d", result_id, type_id, storage_class); + break; + } + case SpvOpMemberDecorate: + { + const uint32_t id = args[0]; + const uint32_t member_index = args[1]; + const uint32_t decor = args[2]; + //L("OpMemberDecorate(id=%d) => member=%d decor=%d ...", id, member_index, decor); + break; + } + case SpvOpDecorate: + { + const uint32_t id = args[0]; + // FIXME check size + const uint32_t decor = args[1]; + node_t *node = ctx->nodes + id; + switch (decor) { + case SpvDecorationDescriptorSet: + { + const uint32_t ds = args[2]; + //L("OpDecorate(id=%d) => DescriptorSet = %d ", id, ds); + binding_t *binding = getBinding(ctx, id); + if (!binding) + return false; + binding->descriptor_set = ds; + break; + } + + case SpvDecorationBinding: + { + const uint32_t binding = args[2]; + //L("OpDecorate(id=%d) => Binding = %d ", id, binding); + binding_t *b = getBinding(ctx, id); + if (!b) + return false; + b->binding = binding; + break; + } + + case SpvDecorationNonWritable: + { + //node->flags &= Flag_NonWritable; + //L("OpDecorate(id=%d) => NonWriteable", id); + break; + } + + case SpvDecorationNonReadable: + { + //node->flags &= Flag_NonReadable; + //L("OpDecorate(id=%d) => NonReadable", id); + break; + } + + default: + break; + //L("OpDecorate(id=%d) => %d ... ", id, decor); + } + + break; + } + + default: + if (word_count > 1 && (int)args[1] < ctx->nodes_count) { + const uint32_t id = args[1]; + //L("op=%d word_count=%d guessed dest_id=%d", op, word_count, id); + } else { + //L("op=%d word_count=%d", op, word_count); + } + } + return true; +} + +qboolean parseHeader(context_t *ctx, const uint32_t *data, int n) { + const uint32_t magic = data[0]; + //L("magic = %#08x", magic); + if (magic != SpvMagicNumber) + return false; + + const uint32_t version = data[1]; + //L("version = %#08x(%d.%d)", version, (version>>16)&0xff, (version>>8)&0xff); + + //L("generator magic = %#08x", data[2]); + + ctx->nodes_count = data[3]; + //L("nodes_count = %d", ctx->nodes_count); + + ctx->nodes = MALLOC(ctx->nodes_count * sizeof(*ctx->nodes)); + + for (int i = 0; i < ctx->nodes_count; ++i) { + ctx->nodes[i].binding = -1; + ctx->nodes[i].op = SpvOpMax; + ctx->nodes[i].type_id = -1; + ctx->nodes[i].name = NULL; + ctx->nodes[i].storage_class = -1; + ctx->nodes[i].flags = 0; + } + + return true; +} + +qboolean processBindings(context_t *ctx, vk_spirv_t *out) { + for (int i = 0; i < ctx->bindings_count; ++i) { + binding_t *binding = ctx->bindings + i; + //L("%02d [%d:%d] id=%d name=%s", i, binding->descriptor_set, binding->binding, binding->id, binding->name); + { + const node_t *node = ctx->nodes + binding->id; + for (;;) { + //L(" op=%s(%d) type_id=%d name=%s", opTypeName(node->op), node->op, node->type_id, node->name ? node->name : "N/A"); + + if ((int)node->storage_class >= 0) { + //L(" storage_class=%s", storageClassName(node->storage_class)); + } + + if (!binding->name && node->name) + binding->name = node->name; + + //binding->flags |= node->flags; + + if ((int)node->type_id == -1) + break; + node = ctx->nodes + node->type_id; + } + } + } + + out->bindings_count = ctx->bindings_count; + out->bindings = MALLOC(sizeof(vk_binding_t) * out->bindings_count); + + for (int i = 0; i < ctx->bindings_count; ++i) { + const binding_t *src = ctx->bindings + i; + vk_binding_t *dst = out->bindings + i; + + dst->binding = src->binding; + dst->descriptor_set = src->descriptor_set; + dst->name = strdup(src->name); + } + + return true; +} + +qboolean R_VkSpirvParse(vk_spirv_t *out, const uint32_t *data, int n) { + if (n < 5) { + return false; + } + + context_t ctx = {0}; + + if (!parseHeader(&ctx, data, n)) + return false; + + for (int i = 0; i < MAX_BINDINGS; ++i) { + ctx.bindings[i].id = -1; + ctx.bindings[i].descriptor_set = -1; + ctx.bindings[i].binding = -1; + } + + qboolean ret = false; + for (size_t i = 5; i < n; ++i) { + const uint16_t op_code = data[i] & SpvOpCodeMask; + const uint16_t word_count = data[i] >> SpvWordCountShift; + + if (word_count > (n-i)) + goto cleanup; + + if (!spvParseOp(&ctx, op_code, word_count, data + i + 1)) + goto cleanup; + + i += word_count-1; + } + + if (!processBindings(&ctx, out)) + goto cleanup; + + ret = true; + +cleanup: + if (ctx.nodes) + FREE(ctx.nodes); + return ret; +} + +void R_VkSpirvFree(vk_spirv_t *spirv) { + for (int i = 0; i < spirv->bindings_count; ++i) { + vk_binding_t *bind = spirv->bindings + i; + if (bind->name) + FREE(bind->name); + } + + FREE(spirv->bindings); + memset(spirv, 0, sizeof(*spirv)); +} diff --git a/ref_vk/vk_spirv.h b/ref_vk/vk_spirv.h new file mode 100644 index 00000000..f9db6251 --- /dev/null +++ b/ref_vk/vk_spirv.h @@ -0,0 +1,31 @@ +#pragma once +#include + +#include "xash3d_types.h" + +// enum { +// Flag_NonWritable = (1<<0), +// Flag_NonReadable = (1<<1), +// }; + +typedef struct { + char *name; + int descriptor_set; + int binding; + + // TODO: in-out, type, image format, etc + //uint32_t flags; +} vk_binding_t; + +typedef struct { + vk_binding_t *bindings; + int bindings_count; + + // TODO: + // - push constants + // - specialization + // - types +} vk_spirv_t; + +qboolean R_VkSpirvParse(vk_spirv_t *out, const uint32_t *instructions, int instructions_count); +void R_VkSpirvFree(vk_spirv_t *spirv); From e506158ac14c59cb0c058e82ddfdc1b5054fc648 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 7 Oct 2022 08:49:04 -0700 Subject: [PATCH 448/548] rt: parse compute and rt shaders too --- ref_vk/vk_pipeline.c | 49 ++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/ref_vk/vk_pipeline.c b/ref_vk/vk_pipeline.c index e5ba4bb5..27557f40 100644 --- a/ref_vk/vk_pipeline.c +++ b/ref_vk/vk_pipeline.c @@ -59,6 +59,12 @@ static qboolean R_VkShaderLoad(r_vk_shader_t *shader, const char *filename) { if (!R_VkSpirvParse(&shader->spirv, smci.pCode, size / 4)) { gEngine.Con_Printf(S_ERROR "Error parsing SPIR-V for %s\n", filename); + } else { + gEngine.Con_Reportf("Got %d bindings for shader %s\n", shader->spirv.bindings_count, filename); + for (int j = 0; j < shader->spirv.bindings_count; ++j) { + const vk_binding_t *binding = shader->spirv.bindings + j; + gEngine.Con_Reportf(" %02d [%d:%d] name=%s\n", j, binding->descriptor_set, binding->binding, binding->name); + } } Mem_Free(buf); @@ -113,12 +119,12 @@ VkPipeline VK_PipelineGraphicsCreate(const vk_pipeline_graphics_create_info_t *c VkPipelineColorBlendAttachmentState blend_attachment = { .blendEnable = ci->blendEnable, - .srcColorBlendFactor = ci->srcColorBlendFactor, - .dstColorBlendFactor = ci->dstColorBlendFactor, - .colorBlendOp = ci->colorBlendOp, - .srcAlphaBlendFactor = ci->srcAlphaBlendFactor, - .dstAlphaBlendFactor = ci->dstAlphaBlendFactor, - .alphaBlendOp = ci->alphaBlendOp, + .srcColorBlendFactor = ci->srcColorBlendFactor, + .dstColorBlendFactor = ci->dstColorBlendFactor, + .colorBlendOp = ci->colorBlendOp, + .srcAlphaBlendFactor = ci->srcAlphaBlendFactor, + .dstAlphaBlendFactor = ci->dstAlphaBlendFactor, + .alphaBlendOp = ci->alphaBlendOp, .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, }; @@ -174,13 +180,6 @@ VkPipeline VK_PipelineGraphicsCreate(const vk_pipeline_graphics_create_info_t *c if (!R_VkShaderLoad(shaders + i, ci->stages[i].filename)) goto finalize; - gEngine.Con_Reportf("Got %d bindings for shader %s\n", shaders[i].spirv.bindings_count, ci->stages[i].filename); - - for (int j = 0; j < shaders[i].spirv.bindings_count; ++j) { - const vk_binding_t *binding = shaders[i].spirv.bindings + j; - gEngine.Con_Reportf(" %02d [%d:%d] name=%s\n", j, binding->descriptor_set, binding->binding, binding->name); - } - stage_create_infos[i] = (VkPipelineShaderStageCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = ci->stages[i].stage, @@ -200,13 +199,17 @@ finalize: } VkPipeline VK_PipelineComputeCreate(const vk_pipeline_compute_create_info_t *ci) { + r_vk_shader_t shader = {0}; + if (!R_VkShaderLoad(&shader, ci->shader_filename)) + return VK_NULL_HANDLE; + const VkComputePipelineCreateInfo cpci = { .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .layout = ci->layout, .stage = (VkPipelineShaderStageCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_COMPUTE_BIT, - // FIXME .module = loadShader(ci->shader_filename), + .module = shader.module, .pName = "main", .pSpecializationInfo = ci->specialization_info, }, @@ -214,7 +217,7 @@ VkPipeline VK_PipelineComputeCreate(const vk_pipeline_compute_create_info_t *ci) VkPipeline pipeline; XVK_CHECK(vkCreateComputePipelines(vk_core.device, VK_NULL_HANDLE, 1, &cpci, NULL, &pipeline)); - vkDestroyShaderModule(vk_core.device, cpci.stage.module, NULL); + R_VkShaderDestroy(&shader); return pipeline; } @@ -240,12 +243,21 @@ vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_ .layout = create->layout, }; - ASSERT(create->stages_count <= MAX_SHADER_STAGES); ASSERT(shader_groups_count <= MAX_SHADER_GROUPS); + if (create->stages_count > MAX_SHADER_STAGES) { + gEngine.Con_Printf(S_ERROR "Too many shader stages %d, max=%d\n", create->stages_count, MAX_SHADER_STAGES); + return ret; + } + + r_vk_shader_t shaders[MAX_SHADER_STAGES] = {0}; + for (int i = 0; i < create->stages_count; ++i) { const vk_shader_stage_t *const stage = create->stages + i; + if (!R_VkShaderLoad(shaders + i, stage->filename)) + goto destroy_shaders; + if (stage->stage == VK_SHADER_STAGE_RAYGEN_BIT_KHR) { ASSERT(raygen_index == -1); raygen_index = i; @@ -254,7 +266,7 @@ vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_ stages[i] = (VkPipelineShaderStageCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = stage->stage, - // FIXME .module = loadShader(stage->filename), + .module = shaders[i].module, .pName = "main", .pSpecializationInfo = stage->specialization_info, }; @@ -315,8 +327,9 @@ vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_ XVK_CHECK(vkCreateRayTracingPipelinesKHR(vk_core.device, VK_NULL_HANDLE, g_pipeline_cache, 1, &rtpci, NULL, &ret.pipeline)); +destroy_shaders: for (int i = 0; i < create->stages_count; ++i) - vkDestroyShaderModule(vk_core.device, stages[i].module, NULL); + R_VkShaderDestroy(shaders + i); if (ret.pipeline == VK_NULL_HANDLE) return ret; From e849754faec89b34ddb67cfcb1deb57c64c0a745 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Fri, 7 Oct 2022 17:20:02 +0100 Subject: [PATCH 449/548] vk: add spirv.h include path for win32 --- ref_vk/wscript | 1 + 1 file changed, 1 insertion(+) diff --git a/ref_vk/wscript b/ref_vk/wscript index 08fe3a3f..2d5daec8 100644 --- a/ref_vk/wscript +++ b/ref_vk/wscript @@ -100,6 +100,7 @@ def build(bld): if bld.env.DEST_OS == 'win32': includes.append(bld.env.VULKAN_SDK + '\\Include') + includes.append(bld.env.VULKAN_SDK + '\\Source\SPIRV-Reflect\include') # for spirv.h if bld.env.HAVE_AFTERMATH: defines.append('USE_AFTERMATH') From 78ec2e2c93af265c911dc3430392788cda440473 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 9 Oct 2022 13:29:26 -0700 Subject: [PATCH 450/548] vk: start making script to compile pipelines --- ref_vk/rt.json | 32 +++++++++++++++ ref_vk/sebastian.py | 96 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 ref_vk/rt.json create mode 100644 ref_vk/sebastian.py diff --git a/ref_vk/rt.json b/ref_vk/rt.json new file mode 100644 index 00000000..b67196a4 --- /dev/null +++ b/ref_vk/rt.json @@ -0,0 +1,32 @@ +{ + "primary_ray": { + "rgen": "ray_primary", + "miss": [ + "ray_primary" + ], + "hit": [ + {"closest": "ray_primary"}, + {"closest": "ray_primary", "any": "ray_common_alphatest"} + ] + }, + "light_direct": { + "template": true, + "miss": [ + "ray_shadow" + ], + "hit": [ + {"closest": "ray_shadow", "any": "ray_common_alphatest"} + ] + }, + "light_direct_poly": { + "inherit": "light_direct", + "rgen": "ray_light_poly_direct" + }, + "light_direct_point": { + "inherit": "light_direct", + "rgen": "ray_light_direct_point" + }, + "denoiser": { + "comp": "denoiser" + } +} diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py new file mode 100644 index 00000000..4e7ea4df --- /dev/null +++ b/ref_vk/sebastian.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +import json +import argparse +import struct +import traceback + +parser = argparse.ArgumentParser(description='Build pipeline descriptor') +parser.add_argument('pipelines', type=argparse.FileType('r')) +parser.add_argument('--path', nargs='*', help='Directory to look for shaders') +args = parser.parse_args() + +def parseSpirv(raw_data): + if len(raw_data) % 4 != 0: + raise Exception('SPIR-V size should be divisible by 4') + + size = len(raw_data) // 4 + data = struct.unpack(str(size) + 'I', raw_data) + + print(data[0:5]) + +class Shader: + def __init__(self, name, filename): + self.name = name + self.__raw_data = open(filename, 'rb').read() + print(name, '=>', len(self.__raw_data)) + + try: + parseSpirv(self.__raw_data) + except: + traceback.print_exc() + +def doLoadShader(name): + try: + return Shader(name, name) + except: + pass + + if args.path: + for path in args.path: + try: + return Shader(name, path + '/' + name) + except: + pass + + raise Exception('Cannot load shader ' + name) + +shaders = dict() +def loadShader(name): + if name in shaders: + return shaders[name] + + shader = doLoadShader(name) + shaders[name] = shader + return shader + +class PipelineRayTracing: + def __init__(self, name, desc): + self.name = name + self.rgen = loadShader(desc['rgen'] + '.rgen.spv') + self.miss = [] if not 'miss' in desc else [loadShader(s + '.rmiss.spv') for s in desc['miss']] + + def loadHit(hit): + ret = dict() + suffixes = {'closest': '.rchit.spv', 'any': '.rahit.spv'} + for k, v in hit.items(): + ret[k] = loadShader(v + suffixes[k]) + return ret + + self.hit = [] if not 'hit' in desc else [loadHit(hit) for hit in desc['hit']] + +class PipelineCompute: + def __init__(self, name, desc): + self.name = name + self.comp = loadShader(desc['comp'] + '.comp.spv') + +def parsePipeline(pipelines, name, desc): + if 'inherit' in desc: + inherit = pipelines[desc['inherit']] + for k, v in inherit.items(): + if not k in desc: + desc[k] = v + if 'rgen' in desc: + return PipelineRayTracing(name, desc) + elif 'comp' in desc: + return PipelineCompute(name, desc) + +def loadPipelines(): + pipelines_desc = json.load(args.pipelines) + pipelines = dict() + for k, v in pipelines_desc.items(): + if 'template' in v and v['template']: + continue + pipelines[k] = parsePipeline(pipelines_desc, k, v) + return pipelines + +pipelines = loadPipelines() From 3aa9b4bf3cdc4be9d4627e2f93bc3df7cfb6e664 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 15 Oct 2022 11:07:53 -0700 Subject: [PATCH 451/548] vk: extract some bindings with names and sets --- ref_vk/sebastian.py | 74 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 2 deletions(-) mode change 100644 => 100755 ref_vk/sebastian.py diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py old mode 100644 new mode 100755 index 4e7ea4df..8af04773 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -3,20 +3,90 @@ import json import argparse import struct import traceback +from spirv import spv parser = argparse.ArgumentParser(description='Build pipeline descriptor') -parser.add_argument('pipelines', type=argparse.FileType('r')) parser.add_argument('--path', nargs='*', help='Directory to look for shaders') +parser.add_argument('pipelines', type=argparse.FileType('r')) +# TODO strip debug OpName OpLine etc args = parser.parse_args() +spvOp = spv['Op'] +spvOpNames = dict() +for name, n in spvOp.items(): + spvOpNames[n] = name + +class SpirvNode: + def __init__(self): + self.descriptor_set = None + self.binding = None + self.name = None + pass + +class SpirvContext: + def __init__(self, nodes_count): + self.nodes = [SpirvNode() for i in range(0, nodes_count)] + #self.bindings = dict() + pass + + def getNode(self, index): + return self.nodes[index] + + #def bindNode(self, index): + #if not index in bindings + #self.bindings[index] + + +def spvOpHandleName(ctx, args): + index = args[0] + name = struct.pack(str(len(args)-1)+'I', *args[1:]).split(b'\x00')[0].decode('utf8') + ctx.getNode(index).name = name + #print('Name for', args[0], name, len(name)) + +def spvOpHandleDecorate(ctx, args): + node = ctx.getNode(args[0]) + decor = args[1] + if decor == spv['Decoration']['DescriptorSet']: + node.descriptor_set = args[2] + elif decor == spv['Decoration']['Binding']: + node.binding = args[2] + #else: + #print('Decor ', id, decor) + +spvOpHandlers = { + spvOp['OpName']: spvOpHandleName, + spvOp['OpDecorate']: spvOpHandleDecorate +} + def parseSpirv(raw_data): if len(raw_data) % 4 != 0: raise Exception('SPIR-V size should be divisible by 4') size = len(raw_data) // 4 + if size < 5: + raise Exception('SPIR-V data is too short') + data = struct.unpack(str(size) + 'I', raw_data) - print(data[0:5]) + if data[0] != spv['MagicNumber']: + raise Exception('Unexpected magic ' + str(data[0])) + + nodes_count = data[3] + ctx = SpirvContext(nodes_count) + + off = 5 + while off < size: + op = data[off] & 0xffff + words = data[off] >> 16 + args = data[off+1:off+words] + if op in spvOpHandlers: + spvOpHandlers[op](ctx, args) + #print(spvOpNames[op], args) + off += words + + for index, node in enumerate(ctx.nodes): + if node.descriptor_set is not None: + print('[%d:%d] %s (id=%d)' % (node.descriptor_set, node.binding, node.name, index)) class Shader: def __init__(self, name, filename): From 9366a6ffdabedad12623a34c2d67ea0f0e068e2f Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 15 Oct 2022 11:11:36 -0700 Subject: [PATCH 452/548] seba: handle --path better --- ref_vk/sebastian.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 8af04773..f2f4e4f0 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -6,7 +6,7 @@ import traceback from spirv import spv parser = argparse.ArgumentParser(description='Build pipeline descriptor') -parser.add_argument('--path', nargs='*', help='Directory to look for shaders') +parser.add_argument('--path', action='append', help='Directory to look for shaders') parser.add_argument('pipelines', type=argparse.FileType('r')) # TODO strip debug OpName OpLine etc args = parser.parse_args() From 9478c870e329f47ef1c9e5db522f92d49daade14 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 15 Oct 2022 11:24:56 -0700 Subject: [PATCH 453/548] seba: improve shader loading a bit --- ref_vk/sebastian.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index f2f4e4f0..746aa55d 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -84,31 +84,32 @@ def parseSpirv(raw_data): #print(spvOpNames[op], args) off += words - for index, node in enumerate(ctx.nodes): - if node.descriptor_set is not None: - print('[%d:%d] %s (id=%d)' % (node.descriptor_set, node.binding, node.name, index)) + return ctx class Shader: - def __init__(self, name, filename): + def __init__(self, name, file): self.name = name - self.__raw_data = open(filename, 'rb').read() - print(name, '=>', len(self.__raw_data)) + self.raw_data = file + print(name, '=>', len(self.raw_data)) + self.spirv = parseSpirv(self.raw_data) - try: - parseSpirv(self.__raw_data) - except: - traceback.print_exc() + def __str__(self): + ret = '' + for index, node in enumerate(self.spirv.nodes): + if node.descriptor_set is not None: + ret += ('[%d:%d] (id=%d) %s\n' % (node.descriptor_set, node.binding, index, node.name)) + return ret -def doLoadShader(name): +def loadShaderFile(name): try: - return Shader(name, name) + return open(name, 'rb').read() except: pass if args.path: for path in args.path: try: - return Shader(name, path + '/' + name) + return open(path + '/' + name, 'rb').read() except: pass @@ -119,7 +120,8 @@ def loadShader(name): if name in shaders: return shaders[name] - shader = doLoadShader(name) + file = loadShaderFile(name); + shader = Shader(name, file) shaders[name] = shader return shader From 9c679c39b823348a3df55cf20244ba127d09882b Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 15 Oct 2022 12:13:52 -0700 Subject: [PATCH 454/548] seba: produce serialized meat file --- ref_vk/sebastian.py | 128 +++++++++++++++++++++++++++++++++----------- 1 file changed, 98 insertions(+), 30 deletions(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 746aa55d..5026489d 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -7,6 +7,7 @@ from spirv import spv parser = argparse.ArgumentParser(description='Build pipeline descriptor') parser.add_argument('--path', action='append', help='Directory to look for shaders') +parser.add_argument('--output', '-o', type=argparse.FileType('wb'), help='Compiled pipeline') parser.add_argument('pipelines', type=argparse.FileType('r')) # TODO strip debug OpName OpLine etc args = parser.parse_args() @@ -100,50 +101,100 @@ class Shader: ret += ('[%d:%d] (id=%d) %s\n' % (node.descriptor_set, node.binding, index, node.name)) return ret -def loadShaderFile(name): - try: - return open(name, 'rb').read() - except: - pass +class Shaders: + def __init__(self): + self.__map = dict() + self.__shaders = [] - if args.path: - for path in args.path: - try: - return open(path + '/' + name, 'rb').read() - except: - pass + def __loadShaderFile(name): + try: + return open(name, 'rb').read() + except: + pass - raise Exception('Cannot load shader ' + name) + if args.path: + for path in args.path: + try: + return open(path + '/' + name, 'rb').read() + except: + pass -shaders = dict() -def loadShader(name): - if name in shaders: - return shaders[name] + raise Exception('Cannot load shader ' + name) - file = loadShaderFile(name); - shader = Shader(name, file) - shaders[name] = shader - return shader + def load(self, name): + if name in self.__map: + return self.__shaders[self.__map[name]] + + file = Shaders.__loadShaderFile(name); + shader = Shader(name, file) + + index = len(self.__shaders) + self.__shaders.append(shader) + self.__map[name] = index + + return shader + + def getIndex(self, name): + return self.__map[name] + + def serialize(self, file): + file.write(struct.pack('I', len(self.__shaders))) + for shader in self.__shaders: + file.write(struct.pack('I', len(shader.raw_data))) + file.write(shader.raw_data) + + def serializeIndex(self, out, shader): + out.write(struct.pack('I', self.getIndex(shader.name))) + +shaders = Shaders() + + +PIPELINE_COMPUTE = 1 +PIPELINE_RAYTRACING = 2 +NO_SHADER = 0xffffffff class PipelineRayTracing: + def __loadHit(hit): + ret = dict() + suffixes = {'closest': '.rchit.spv', 'any': '.rahit.spv'} + for k, v in hit.items(): + ret[k] = shaders.load(v + suffixes[k]) + return ret + def __init__(self, name, desc): + self.type = PIPELINE_RAYTRACING self.name = name - self.rgen = loadShader(desc['rgen'] + '.rgen.spv') - self.miss = [] if not 'miss' in desc else [loadShader(s + '.rmiss.spv') for s in desc['miss']] + self.rgen = shaders.load(desc['rgen'] + '.rgen.spv') + self.miss = [] if not 'miss' in desc else [shaders.load(s + '.rmiss.spv') for s in desc['miss']] + self.hit = [] if not 'hit' in desc else [PipelineRayTracing.__loadHit(hit) for hit in desc['hit']] - def loadHit(hit): - ret = dict() - suffixes = {'closest': '.rchit.spv', 'any': '.rahit.spv'} - for k, v in hit.items(): - ret[k] = loadShader(v + suffixes[k]) - return ret + def serialize(self, out): + shaders.serializeIndex(out, self.rgen) - self.hit = [] if not 'hit' in desc else [loadHit(hit) for hit in desc['hit']] + out.write(struct.pack('I', len(self.miss))) + for shader in self.miss: + shaders.serializeIndex(out, shader) + + out.write(struct.pack('I', len(self.hit))) + for hit in self.hit: + if 'closest' in hit: + shaders.serializeIndex(out, hit['closest']) + else: + out.write(struct.pack('I', NO_SHADER)) + + if 'any' in hit: + shaders.serializeIndex(out, hit['any']) + else: + out.write(struct.pack('I', NO_SHADER)) class PipelineCompute: def __init__(self, name, desc): + self.type = PIPELINE_COMPUTE self.name = name - self.comp = loadShader(desc['comp'] + '.comp.spv') + self.comp = shaders.load(desc['comp'] + '.comp.spv') + + def serialize(self, out): + shaders.serializeIndex(out, self.comp) def parsePipeline(pipelines, name, desc): if 'inherit' in desc: @@ -165,4 +216,21 @@ def loadPipelines(): pipelines[k] = parsePipeline(pipelines_desc, k, v) return pipelines +def writeOutput(out, pipelines): + MAGIC = bytearray([ord(c) for c in 'MEAT']) + out.write(MAGIC) + + shaders.serialize(out) + + out.write(struct.pack('I', len(pipelines))) + for name, pipeline in pipelines.items(): + out.write(struct.pack('I', pipeline.type)) + bs = pipeline.name.encode('utf-8') + out.write(struct.pack('I', len(bs))) + out.write(bs) + pipeline.serialize(out) + pipelines = loadPipelines() + +if args.output: + writeOutput(args.output, pipelines) From 09b0e0f8ff554ca9ed2afb64b2f0dfa7caf6247d Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 15 Oct 2022 12:44:54 -0700 Subject: [PATCH 455/548] rt: deduplicate sbt spec copies into ray pass --- ref_vk/ray_pass.c | 26 ++++++++++++++++++++++---- ref_vk/vk_ray_light_direct.c | 36 ++---------------------------------- ref_vk/vk_ray_primary.c | 18 +----------------- 3 files changed, 25 insertions(+), 55 deletions(-) diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c index cf7a540d..9c9a89e3 100644 --- a/ref_vk/ray_pass.c +++ b/ref_vk/ray_pass.c @@ -60,6 +60,24 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create ray_pass_tracing_impl_t *const pass = Mem_Malloc(vk_core.pool, sizeof(*pass)); ray_pass_t *const header = &pass->header; + // TODO support external specialization + ASSERT(!create->specialization); + + const struct SpecializationData { + uint32_t sbt_record_size; + } spec_data = { + .sbt_record_size = vk_core.physical_device.sbt_record_size, + }; + const VkSpecializationMapEntry spec_map[] = { + {.constantID = SPEC_SBT_RECORD_SIZE_INDEX, .offset = offsetof(struct SpecializationData, sbt_record_size), .size = sizeof(uint32_t) }, + }; + const VkSpecializationInfo spec = { + .mapEntryCount = COUNTOF(spec_map), + .pMapEntries = spec_map, + .dataSize = sizeof(spec_data), + .pData = &spec_data, + }; + initPassDescriptors(header, &create->layout); { @@ -83,7 +101,7 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create stages[stage_index++] = (vk_shader_stage_t) { .filename = create->raygen, .stage = VK_SHADER_STAGE_RAYGEN_BIT_KHR, - .specialization_info = create->specialization, + .specialization_info = &spec, }; for (int i = 0; i < create->miss_count; ++i) { @@ -98,7 +116,7 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create stages[stage_index++] = (vk_shader_stage_t) { .filename = *shader, .stage = VK_SHADER_STAGE_MISS_BIT_KHR, - .specialization_info = create->specialization, + .specialization_info = &spec, }; } @@ -114,7 +132,7 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create stages[stage_index++] = (vk_shader_stage_t) { .filename = group->any, .stage = VK_SHADER_STAGE_ANY_HIT_BIT_KHR, - .specialization_info = create->specialization, + .specialization_info = &spec, }; } else { hits[hit_index].any = -1; @@ -126,7 +144,7 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create stages[stage_index++] = (vk_shader_stage_t) { .filename = group->closest, .stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, - .specialization_info = create->specialization, + .specialization_info = &spec, }; } else { hits[hit_index].closest = -1; diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 2dd3bbb8..eff03836 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -58,22 +58,6 @@ static const int semantics_point[] = { }; struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { - // FIXME move this into vk_pipeline - const struct SpecializationData { - uint32_t sbt_record_size; - } spec_data = { - .sbt_record_size = vk_core.physical_device.sbt_record_size, - }; - const VkSpecializationMapEntry spec_map[] = { - {.constantID = SPEC_SBT_RECORD_SIZE_INDEX, .offset = offsetof(struct SpecializationData, sbt_record_size), .size = sizeof(uint32_t) }, - }; - VkSpecializationInfo spec = { - .mapEntryCount = COUNTOF(spec_map), - .pMapEntries = spec_map, - .dataSize = sizeof(spec_data), - .pData = &spec_data, - }; - const ray_pass_shader_t miss[] = { "ray_shadow.rmiss.spv" }; @@ -97,29 +81,13 @@ struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { .miss_count = COUNTOF(miss), .hit = hit, .hit_count = COUNTOF(hit), - .specialization = &spec, + .specialization = NULL, }; return RayPassCreateTracing( &rpc ); } struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ) { - // FIXME move this into vk_pipeline - const struct SpecializationData { - uint32_t sbt_record_size; - } spec_data = { - .sbt_record_size = vk_core.physical_device.sbt_record_size, - }; - const VkSpecializationMapEntry spec_map[] = { - {.constantID = SPEC_SBT_RECORD_SIZE_INDEX, .offset = offsetof(struct SpecializationData, sbt_record_size), .size = sizeof(uint32_t) }, - }; - VkSpecializationInfo spec = { - .mapEntryCount = COUNTOF(spec_map), - .pMapEntries = spec_map, - .dataSize = sizeof(spec_data), - .pData = &spec_data, - }; - const ray_pass_shader_t miss[] = { "ray_shadow.rmiss.spv" }; @@ -143,7 +111,7 @@ struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ) { .miss_count = COUNTOF(miss), .hit = hit, .hit_count = COUNTOF(hit), - .specialization = &spec, + .specialization = NULL, }; return RayPassCreateTracing( &rpc ); diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index ff7a7532..a8925290 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -40,22 +40,6 @@ static const int semantics[] = { }; struct ray_pass_s *R_VkRayPrimaryPassCreate( void ) { - // FIXME move this into vk_pipeline or something - const struct SpecializationData { - uint32_t sbt_record_size; - } spec_data = { - .sbt_record_size = vk_core.physical_device.sbt_record_size, - }; - const VkSpecializationMapEntry spec_map[] = { - {.constantID = SPEC_SBT_RECORD_SIZE_INDEX, .offset = offsetof(struct SpecializationData, sbt_record_size), .size = sizeof(uint32_t) }, - }; - const VkSpecializationInfo spec = { - .mapEntryCount = COUNTOF(spec_map), - .pMapEntries = spec_map, - .dataSize = sizeof(spec_data), - .pData = &spec_data, - }; - const ray_pass_shader_t miss[] = { "ray_primary.rmiss.spv" }; @@ -82,7 +66,7 @@ struct ray_pass_s *R_VkRayPrimaryPassCreate( void ) { .miss_count = COUNTOF(miss), .hit = hit, .hit_count = COUNTOF(hit), - .specialization = &spec, + .specialization = NULL, }; return RayPassCreateTracing( &rpc ); From 99489cf05c432051a92d57c32e8b9cb6d9dcbc27 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 15 Oct 2022 13:05:31 -0700 Subject: [PATCH 456/548] seba: add spirv.py --- ref_vk/sebastian.py | 1 - ref_vk/spirv.py | 1829 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1829 insertions(+), 1 deletion(-) create mode 100644 ref_vk/spirv.py diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 5026489d..a5241f3c 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -2,7 +2,6 @@ import json import argparse import struct -import traceback from spirv import spv parser = argparse.ArgumentParser(description='Build pipeline descriptor') diff --git a/ref_vk/spirv.py b/ref_vk/spirv.py new file mode 100644 index 00000000..56b34ce0 --- /dev/null +++ b/ref_vk/spirv.py @@ -0,0 +1,1829 @@ +# Copyright (c) 2014-2020 The Khronos Group Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and/or associated documentation files (the "Materials"), +# to deal in the Materials without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Materials, and to permit persons to whom the +# Materials are furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Materials. +# +# MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +# STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +# HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +# +# THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS +# IN THE MATERIALS. + +# This header is automatically generated by the same tool that creates +# the Binary Section of the SPIR-V specification. + +# Enumeration tokens for SPIR-V, in various styles: +# C, C++, C++11, JSON, Lua, Python, C#, D, Beef +# +# - C will have tokens with a "Spv" prefix, e.g.: SpvSourceLanguageGLSL +# - C++ will have tokens in the "spv" name space, e.g.: spv::SourceLanguageGLSL +# - C++11 will use enum classes in the spv namespace, e.g.: spv::SourceLanguage::GLSL +# - Lua will use tables, e.g.: spv.SourceLanguage.GLSL +# - Python will use dictionaries, e.g.: spv['SourceLanguage']['GLSL'] +# - C# will use enum classes in the Specification class located in the "Spv" namespace, +# e.g.: Spv.Specification.SourceLanguage.GLSL +# - D will have tokens under the "spv" module, e.g: spv.SourceLanguage.GLSL +# - Beef will use enum classes in the Specification class located in the "Spv" namespace, +# e.g.: Spv.Specification.SourceLanguage.GLSL +# +# Some tokens act like mask values, which can be OR'd together, +# while others are mutually exclusive. The mask-like ones have +# "Mask" in their name, and a parallel enum that has the shift +# amount (1 << x) for each corresponding enumerant. + +spv = { + 'MagicNumber' : 0x07230203, + 'Version' : 0x00010600, + 'Revision' : 1, + 'OpCodeMask' : 0xffff, + 'WordCountShift' : 16, + + 'SourceLanguage' : { + 'Unknown' : 0, + 'ESSL' : 1, + 'GLSL' : 2, + 'OpenCL_C' : 3, + 'OpenCL_CPP' : 4, + 'HLSL' : 5, + 'CPP_for_OpenCL' : 6, + 'SYCL' : 7, + }, + + 'ExecutionModel' : { + 'Vertex' : 0, + 'TessellationControl' : 1, + 'TessellationEvaluation' : 2, + 'Geometry' : 3, + 'Fragment' : 4, + 'GLCompute' : 5, + 'Kernel' : 6, + 'TaskNV' : 5267, + 'MeshNV' : 5268, + 'RayGenerationKHR' : 5313, + 'RayGenerationNV' : 5313, + 'IntersectionKHR' : 5314, + 'IntersectionNV' : 5314, + 'AnyHitKHR' : 5315, + 'AnyHitNV' : 5315, + 'ClosestHitKHR' : 5316, + 'ClosestHitNV' : 5316, + 'MissKHR' : 5317, + 'MissNV' : 5317, + 'CallableKHR' : 5318, + 'CallableNV' : 5318, + }, + + 'AddressingModel' : { + 'Logical' : 0, + 'Physical32' : 1, + 'Physical64' : 2, + 'PhysicalStorageBuffer64' : 5348, + 'PhysicalStorageBuffer64EXT' : 5348, + }, + + 'MemoryModel' : { + 'Simple' : 0, + 'GLSL450' : 1, + 'OpenCL' : 2, + 'Vulkan' : 3, + 'VulkanKHR' : 3, + }, + + 'ExecutionMode' : { + 'Invocations' : 0, + 'SpacingEqual' : 1, + 'SpacingFractionalEven' : 2, + 'SpacingFractionalOdd' : 3, + 'VertexOrderCw' : 4, + 'VertexOrderCcw' : 5, + 'PixelCenterInteger' : 6, + 'OriginUpperLeft' : 7, + 'OriginLowerLeft' : 8, + 'EarlyFragmentTests' : 9, + 'PointMode' : 10, + 'Xfb' : 11, + 'DepthReplacing' : 12, + 'DepthGreater' : 14, + 'DepthLess' : 15, + 'DepthUnchanged' : 16, + 'LocalSize' : 17, + 'LocalSizeHint' : 18, + 'InputPoints' : 19, + 'InputLines' : 20, + 'InputLinesAdjacency' : 21, + 'Triangles' : 22, + 'InputTrianglesAdjacency' : 23, + 'Quads' : 24, + 'Isolines' : 25, + 'OutputVertices' : 26, + 'OutputPoints' : 27, + 'OutputLineStrip' : 28, + 'OutputTriangleStrip' : 29, + 'VecTypeHint' : 30, + 'ContractionOff' : 31, + 'Initializer' : 33, + 'Finalizer' : 34, + 'SubgroupSize' : 35, + 'SubgroupsPerWorkgroup' : 36, + 'SubgroupsPerWorkgroupId' : 37, + 'LocalSizeId' : 38, + 'LocalSizeHintId' : 39, + 'SubgroupUniformControlFlowKHR' : 4421, + 'PostDepthCoverage' : 4446, + 'DenormPreserve' : 4459, + 'DenormFlushToZero' : 4460, + 'SignedZeroInfNanPreserve' : 4461, + 'RoundingModeRTE' : 4462, + 'RoundingModeRTZ' : 4463, + 'EarlyAndLateFragmentTestsAMD' : 5017, + 'StencilRefReplacingEXT' : 5027, + 'StencilRefUnchangedFrontAMD' : 5079, + 'StencilRefGreaterFrontAMD' : 5080, + 'StencilRefLessFrontAMD' : 5081, + 'StencilRefUnchangedBackAMD' : 5082, + 'StencilRefGreaterBackAMD' : 5083, + 'StencilRefLessBackAMD' : 5084, + 'OutputLinesNV' : 5269, + 'OutputPrimitivesNV' : 5270, + 'DerivativeGroupQuadsNV' : 5289, + 'DerivativeGroupLinearNV' : 5290, + 'OutputTrianglesNV' : 5298, + 'PixelInterlockOrderedEXT' : 5366, + 'PixelInterlockUnorderedEXT' : 5367, + 'SampleInterlockOrderedEXT' : 5368, + 'SampleInterlockUnorderedEXT' : 5369, + 'ShadingRateInterlockOrderedEXT' : 5370, + 'ShadingRateInterlockUnorderedEXT' : 5371, + 'SharedLocalMemorySizeINTEL' : 5618, + 'RoundingModeRTPINTEL' : 5620, + 'RoundingModeRTNINTEL' : 5621, + 'FloatingPointModeALTINTEL' : 5622, + 'FloatingPointModeIEEEINTEL' : 5623, + 'MaxWorkgroupSizeINTEL' : 5893, + 'MaxWorkDimINTEL' : 5894, + 'NoGlobalOffsetINTEL' : 5895, + 'NumSIMDWorkitemsINTEL' : 5896, + 'SchedulerTargetFmaxMhzINTEL' : 5903, + 'NamedBarrierCountINTEL' : 6417, + }, + + 'StorageClass' : { + 'UniformConstant' : 0, + 'Input' : 1, + 'Uniform' : 2, + 'Output' : 3, + 'Workgroup' : 4, + 'CrossWorkgroup' : 5, + 'Private' : 6, + 'Function' : 7, + 'Generic' : 8, + 'PushConstant' : 9, + 'AtomicCounter' : 10, + 'Image' : 11, + 'StorageBuffer' : 12, + 'CallableDataKHR' : 5328, + 'CallableDataNV' : 5328, + 'IncomingCallableDataKHR' : 5329, + 'IncomingCallableDataNV' : 5329, + 'RayPayloadKHR' : 5338, + 'RayPayloadNV' : 5338, + 'HitAttributeKHR' : 5339, + 'HitAttributeNV' : 5339, + 'IncomingRayPayloadKHR' : 5342, + 'IncomingRayPayloadNV' : 5342, + 'ShaderRecordBufferKHR' : 5343, + 'ShaderRecordBufferNV' : 5343, + 'PhysicalStorageBuffer' : 5349, + 'PhysicalStorageBufferEXT' : 5349, + 'CodeSectionINTEL' : 5605, + 'DeviceOnlyINTEL' : 5936, + 'HostOnlyINTEL' : 5937, + }, + + 'Dim' : { + 'Dim1D' : 0, + 'Dim2D' : 1, + 'Dim3D' : 2, + 'Cube' : 3, + 'Rect' : 4, + 'Buffer' : 5, + 'SubpassData' : 6, + }, + + 'SamplerAddressingMode' : { + 'None' : 0, + 'ClampToEdge' : 1, + 'Clamp' : 2, + 'Repeat' : 3, + 'RepeatMirrored' : 4, + }, + + 'SamplerFilterMode' : { + 'Nearest' : 0, + 'Linear' : 1, + }, + + 'ImageFormat' : { + 'Unknown' : 0, + 'Rgba32f' : 1, + 'Rgba16f' : 2, + 'R32f' : 3, + 'Rgba8' : 4, + 'Rgba8Snorm' : 5, + 'Rg32f' : 6, + 'Rg16f' : 7, + 'R11fG11fB10f' : 8, + 'R16f' : 9, + 'Rgba16' : 10, + 'Rgb10A2' : 11, + 'Rg16' : 12, + 'Rg8' : 13, + 'R16' : 14, + 'R8' : 15, + 'Rgba16Snorm' : 16, + 'Rg16Snorm' : 17, + 'Rg8Snorm' : 18, + 'R16Snorm' : 19, + 'R8Snorm' : 20, + 'Rgba32i' : 21, + 'Rgba16i' : 22, + 'Rgba8i' : 23, + 'R32i' : 24, + 'Rg32i' : 25, + 'Rg16i' : 26, + 'Rg8i' : 27, + 'R16i' : 28, + 'R8i' : 29, + 'Rgba32ui' : 30, + 'Rgba16ui' : 31, + 'Rgba8ui' : 32, + 'R32ui' : 33, + 'Rgb10a2ui' : 34, + 'Rg32ui' : 35, + 'Rg16ui' : 36, + 'Rg8ui' : 37, + 'R16ui' : 38, + 'R8ui' : 39, + 'R64ui' : 40, + 'R64i' : 41, + }, + + 'ImageChannelOrder' : { + 'R' : 0, + 'A' : 1, + 'RG' : 2, + 'RA' : 3, + 'RGB' : 4, + 'RGBA' : 5, + 'BGRA' : 6, + 'ARGB' : 7, + 'Intensity' : 8, + 'Luminance' : 9, + 'Rx' : 10, + 'RGx' : 11, + 'RGBx' : 12, + 'Depth' : 13, + 'DepthStencil' : 14, + 'sRGB' : 15, + 'sRGBx' : 16, + 'sRGBA' : 17, + 'sBGRA' : 18, + 'ABGR' : 19, + }, + + 'ImageChannelDataType' : { + 'SnormInt8' : 0, + 'SnormInt16' : 1, + 'UnormInt8' : 2, + 'UnormInt16' : 3, + 'UnormShort565' : 4, + 'UnormShort555' : 5, + 'UnormInt101010' : 6, + 'SignedInt8' : 7, + 'SignedInt16' : 8, + 'SignedInt32' : 9, + 'UnsignedInt8' : 10, + 'UnsignedInt16' : 11, + 'UnsignedInt32' : 12, + 'HalfFloat' : 13, + 'Float' : 14, + 'UnormInt24' : 15, + 'UnormInt101010_2' : 16, + }, + + 'ImageOperandsShift' : { + 'Bias' : 0, + 'Lod' : 1, + 'Grad' : 2, + 'ConstOffset' : 3, + 'Offset' : 4, + 'ConstOffsets' : 5, + 'Sample' : 6, + 'MinLod' : 7, + 'MakeTexelAvailable' : 8, + 'MakeTexelAvailableKHR' : 8, + 'MakeTexelVisible' : 9, + 'MakeTexelVisibleKHR' : 9, + 'NonPrivateTexel' : 10, + 'NonPrivateTexelKHR' : 10, + 'VolatileTexel' : 11, + 'VolatileTexelKHR' : 11, + 'SignExtend' : 12, + 'ZeroExtend' : 13, + 'Nontemporal' : 14, + 'Offsets' : 16, + }, + + 'ImageOperandsMask' : { + 'MaskNone' : 0, + 'Bias' : 0x00000001, + 'Lod' : 0x00000002, + 'Grad' : 0x00000004, + 'ConstOffset' : 0x00000008, + 'Offset' : 0x00000010, + 'ConstOffsets' : 0x00000020, + 'Sample' : 0x00000040, + 'MinLod' : 0x00000080, + 'MakeTexelAvailable' : 0x00000100, + 'MakeTexelAvailableKHR' : 0x00000100, + 'MakeTexelVisible' : 0x00000200, + 'MakeTexelVisibleKHR' : 0x00000200, + 'NonPrivateTexel' : 0x00000400, + 'NonPrivateTexelKHR' : 0x00000400, + 'VolatileTexel' : 0x00000800, + 'VolatileTexelKHR' : 0x00000800, + 'SignExtend' : 0x00001000, + 'ZeroExtend' : 0x00002000, + 'Nontemporal' : 0x00004000, + 'Offsets' : 0x00010000, + }, + + 'FPFastMathModeShift' : { + 'NotNaN' : 0, + 'NotInf' : 1, + 'NSZ' : 2, + 'AllowRecip' : 3, + 'Fast' : 4, + 'AllowContractFastINTEL' : 16, + 'AllowReassocINTEL' : 17, + }, + + 'FPFastMathModeMask' : { + 'MaskNone' : 0, + 'NotNaN' : 0x00000001, + 'NotInf' : 0x00000002, + 'NSZ' : 0x00000004, + 'AllowRecip' : 0x00000008, + 'Fast' : 0x00000010, + 'AllowContractFastINTEL' : 0x00010000, + 'AllowReassocINTEL' : 0x00020000, + }, + + 'FPRoundingMode' : { + 'RTE' : 0, + 'RTZ' : 1, + 'RTP' : 2, + 'RTN' : 3, + }, + + 'LinkageType' : { + 'Export' : 0, + 'Import' : 1, + 'LinkOnceODR' : 2, + }, + + 'AccessQualifier' : { + 'ReadOnly' : 0, + 'WriteOnly' : 1, + 'ReadWrite' : 2, + }, + + 'FunctionParameterAttribute' : { + 'Zext' : 0, + 'Sext' : 1, + 'ByVal' : 2, + 'Sret' : 3, + 'NoAlias' : 4, + 'NoCapture' : 5, + 'NoWrite' : 6, + 'NoReadWrite' : 7, + }, + + 'Decoration' : { + 'RelaxedPrecision' : 0, + 'SpecId' : 1, + 'Block' : 2, + 'BufferBlock' : 3, + 'RowMajor' : 4, + 'ColMajor' : 5, + 'ArrayStride' : 6, + 'MatrixStride' : 7, + 'GLSLShared' : 8, + 'GLSLPacked' : 9, + 'CPacked' : 10, + 'BuiltIn' : 11, + 'NoPerspective' : 13, + 'Flat' : 14, + 'Patch' : 15, + 'Centroid' : 16, + 'Sample' : 17, + 'Invariant' : 18, + 'Restrict' : 19, + 'Aliased' : 20, + 'Volatile' : 21, + 'Constant' : 22, + 'Coherent' : 23, + 'NonWritable' : 24, + 'NonReadable' : 25, + 'Uniform' : 26, + 'UniformId' : 27, + 'SaturatedConversion' : 28, + 'Stream' : 29, + 'Location' : 30, + 'Component' : 31, + 'Index' : 32, + 'Binding' : 33, + 'DescriptorSet' : 34, + 'Offset' : 35, + 'XfbBuffer' : 36, + 'XfbStride' : 37, + 'FuncParamAttr' : 38, + 'FPRoundingMode' : 39, + 'FPFastMathMode' : 40, + 'LinkageAttributes' : 41, + 'NoContraction' : 42, + 'InputAttachmentIndex' : 43, + 'Alignment' : 44, + 'MaxByteOffset' : 45, + 'AlignmentId' : 46, + 'MaxByteOffsetId' : 47, + 'NoSignedWrap' : 4469, + 'NoUnsignedWrap' : 4470, + 'ExplicitInterpAMD' : 4999, + 'OverrideCoverageNV' : 5248, + 'PassthroughNV' : 5250, + 'ViewportRelativeNV' : 5252, + 'SecondaryViewportRelativeNV' : 5256, + 'PerPrimitiveNV' : 5271, + 'PerViewNV' : 5272, + 'PerTaskNV' : 5273, + 'PerVertexKHR' : 5285, + 'PerVertexNV' : 5285, + 'NonUniform' : 5300, + 'NonUniformEXT' : 5300, + 'RestrictPointer' : 5355, + 'RestrictPointerEXT' : 5355, + 'AliasedPointer' : 5356, + 'AliasedPointerEXT' : 5356, + 'BindlessSamplerNV' : 5398, + 'BindlessImageNV' : 5399, + 'BoundSamplerNV' : 5400, + 'BoundImageNV' : 5401, + 'SIMTCallINTEL' : 5599, + 'ReferencedIndirectlyINTEL' : 5602, + 'ClobberINTEL' : 5607, + 'SideEffectsINTEL' : 5608, + 'VectorComputeVariableINTEL' : 5624, + 'FuncParamIOKindINTEL' : 5625, + 'VectorComputeFunctionINTEL' : 5626, + 'StackCallINTEL' : 5627, + 'GlobalVariableOffsetINTEL' : 5628, + 'CounterBuffer' : 5634, + 'HlslCounterBufferGOOGLE' : 5634, + 'HlslSemanticGOOGLE' : 5635, + 'UserSemantic' : 5635, + 'UserTypeGOOGLE' : 5636, + 'FunctionRoundingModeINTEL' : 5822, + 'FunctionDenormModeINTEL' : 5823, + 'RegisterINTEL' : 5825, + 'MemoryINTEL' : 5826, + 'NumbanksINTEL' : 5827, + 'BankwidthINTEL' : 5828, + 'MaxPrivateCopiesINTEL' : 5829, + 'SinglepumpINTEL' : 5830, + 'DoublepumpINTEL' : 5831, + 'MaxReplicatesINTEL' : 5832, + 'SimpleDualPortINTEL' : 5833, + 'MergeINTEL' : 5834, + 'BankBitsINTEL' : 5835, + 'ForcePow2DepthINTEL' : 5836, + 'BurstCoalesceINTEL' : 5899, + 'CacheSizeINTEL' : 5900, + 'DontStaticallyCoalesceINTEL' : 5901, + 'PrefetchINTEL' : 5902, + 'StallEnableINTEL' : 5905, + 'FuseLoopsInFunctionINTEL' : 5907, + 'AliasScopeINTEL' : 5914, + 'NoAliasINTEL' : 5915, + 'BufferLocationINTEL' : 5921, + 'IOPipeStorageINTEL' : 5944, + 'FunctionFloatingPointModeINTEL' : 6080, + 'SingleElementVectorINTEL' : 6085, + 'VectorComputeCallableFunctionINTEL' : 6087, + 'MediaBlockIOINTEL' : 6140, + }, + + 'BuiltIn' : { + 'Position' : 0, + 'PointSize' : 1, + 'ClipDistance' : 3, + 'CullDistance' : 4, + 'VertexId' : 5, + 'InstanceId' : 6, + 'PrimitiveId' : 7, + 'InvocationId' : 8, + 'Layer' : 9, + 'ViewportIndex' : 10, + 'TessLevelOuter' : 11, + 'TessLevelInner' : 12, + 'TessCoord' : 13, + 'PatchVertices' : 14, + 'FragCoord' : 15, + 'PointCoord' : 16, + 'FrontFacing' : 17, + 'SampleId' : 18, + 'SamplePosition' : 19, + 'SampleMask' : 20, + 'FragDepth' : 22, + 'HelperInvocation' : 23, + 'NumWorkgroups' : 24, + 'WorkgroupSize' : 25, + 'WorkgroupId' : 26, + 'LocalInvocationId' : 27, + 'GlobalInvocationId' : 28, + 'LocalInvocationIndex' : 29, + 'WorkDim' : 30, + 'GlobalSize' : 31, + 'EnqueuedWorkgroupSize' : 32, + 'GlobalOffset' : 33, + 'GlobalLinearId' : 34, + 'SubgroupSize' : 36, + 'SubgroupMaxSize' : 37, + 'NumSubgroups' : 38, + 'NumEnqueuedSubgroups' : 39, + 'SubgroupId' : 40, + 'SubgroupLocalInvocationId' : 41, + 'VertexIndex' : 42, + 'InstanceIndex' : 43, + 'SubgroupEqMask' : 4416, + 'SubgroupEqMaskKHR' : 4416, + 'SubgroupGeMask' : 4417, + 'SubgroupGeMaskKHR' : 4417, + 'SubgroupGtMask' : 4418, + 'SubgroupGtMaskKHR' : 4418, + 'SubgroupLeMask' : 4419, + 'SubgroupLeMaskKHR' : 4419, + 'SubgroupLtMask' : 4420, + 'SubgroupLtMaskKHR' : 4420, + 'BaseVertex' : 4424, + 'BaseInstance' : 4425, + 'DrawIndex' : 4426, + 'PrimitiveShadingRateKHR' : 4432, + 'DeviceIndex' : 4438, + 'ViewIndex' : 4440, + 'ShadingRateKHR' : 4444, + 'BaryCoordNoPerspAMD' : 4992, + 'BaryCoordNoPerspCentroidAMD' : 4993, + 'BaryCoordNoPerspSampleAMD' : 4994, + 'BaryCoordSmoothAMD' : 4995, + 'BaryCoordSmoothCentroidAMD' : 4996, + 'BaryCoordSmoothSampleAMD' : 4997, + 'BaryCoordPullModelAMD' : 4998, + 'FragStencilRefEXT' : 5014, + 'ViewportMaskNV' : 5253, + 'SecondaryPositionNV' : 5257, + 'SecondaryViewportMaskNV' : 5258, + 'PositionPerViewNV' : 5261, + 'ViewportMaskPerViewNV' : 5262, + 'FullyCoveredEXT' : 5264, + 'TaskCountNV' : 5274, + 'PrimitiveCountNV' : 5275, + 'PrimitiveIndicesNV' : 5276, + 'ClipDistancePerViewNV' : 5277, + 'CullDistancePerViewNV' : 5278, + 'LayerPerViewNV' : 5279, + 'MeshViewCountNV' : 5280, + 'MeshViewIndicesNV' : 5281, + 'BaryCoordKHR' : 5286, + 'BaryCoordNV' : 5286, + 'BaryCoordNoPerspKHR' : 5287, + 'BaryCoordNoPerspNV' : 5287, + 'FragSizeEXT' : 5292, + 'FragmentSizeNV' : 5292, + 'FragInvocationCountEXT' : 5293, + 'InvocationsPerPixelNV' : 5293, + 'LaunchIdKHR' : 5319, + 'LaunchIdNV' : 5319, + 'LaunchSizeKHR' : 5320, + 'LaunchSizeNV' : 5320, + 'WorldRayOriginKHR' : 5321, + 'WorldRayOriginNV' : 5321, + 'WorldRayDirectionKHR' : 5322, + 'WorldRayDirectionNV' : 5322, + 'ObjectRayOriginKHR' : 5323, + 'ObjectRayOriginNV' : 5323, + 'ObjectRayDirectionKHR' : 5324, + 'ObjectRayDirectionNV' : 5324, + 'RayTminKHR' : 5325, + 'RayTminNV' : 5325, + 'RayTmaxKHR' : 5326, + 'RayTmaxNV' : 5326, + 'InstanceCustomIndexKHR' : 5327, + 'InstanceCustomIndexNV' : 5327, + 'ObjectToWorldKHR' : 5330, + 'ObjectToWorldNV' : 5330, + 'WorldToObjectKHR' : 5331, + 'WorldToObjectNV' : 5331, + 'HitTNV' : 5332, + 'HitKindKHR' : 5333, + 'HitKindNV' : 5333, + 'CurrentRayTimeNV' : 5334, + 'IncomingRayFlagsKHR' : 5351, + 'IncomingRayFlagsNV' : 5351, + 'RayGeometryIndexKHR' : 5352, + 'WarpsPerSMNV' : 5374, + 'SMCountNV' : 5375, + 'WarpIDNV' : 5376, + 'SMIDNV' : 5377, + 'CullMaskKHR' : 6021, + }, + + 'SelectionControlShift' : { + 'Flatten' : 0, + 'DontFlatten' : 1, + }, + + 'SelectionControlMask' : { + 'MaskNone' : 0, + 'Flatten' : 0x00000001, + 'DontFlatten' : 0x00000002, + }, + + 'LoopControlShift' : { + 'Unroll' : 0, + 'DontUnroll' : 1, + 'DependencyInfinite' : 2, + 'DependencyLength' : 3, + 'MinIterations' : 4, + 'MaxIterations' : 5, + 'IterationMultiple' : 6, + 'PeelCount' : 7, + 'PartialCount' : 8, + 'InitiationIntervalINTEL' : 16, + 'MaxConcurrencyINTEL' : 17, + 'DependencyArrayINTEL' : 18, + 'PipelineEnableINTEL' : 19, + 'LoopCoalesceINTEL' : 20, + 'MaxInterleavingINTEL' : 21, + 'SpeculatedIterationsINTEL' : 22, + 'NoFusionINTEL' : 23, + }, + + 'LoopControlMask' : { + 'MaskNone' : 0, + 'Unroll' : 0x00000001, + 'DontUnroll' : 0x00000002, + 'DependencyInfinite' : 0x00000004, + 'DependencyLength' : 0x00000008, + 'MinIterations' : 0x00000010, + 'MaxIterations' : 0x00000020, + 'IterationMultiple' : 0x00000040, + 'PeelCount' : 0x00000080, + 'PartialCount' : 0x00000100, + 'InitiationIntervalINTEL' : 0x00010000, + 'MaxConcurrencyINTEL' : 0x00020000, + 'DependencyArrayINTEL' : 0x00040000, + 'PipelineEnableINTEL' : 0x00080000, + 'LoopCoalesceINTEL' : 0x00100000, + 'MaxInterleavingINTEL' : 0x00200000, + 'SpeculatedIterationsINTEL' : 0x00400000, + 'NoFusionINTEL' : 0x00800000, + }, + + 'FunctionControlShift' : { + 'Inline' : 0, + 'DontInline' : 1, + 'Pure' : 2, + 'Const' : 3, + 'OptNoneINTEL' : 16, + }, + + 'FunctionControlMask' : { + 'MaskNone' : 0, + 'Inline' : 0x00000001, + 'DontInline' : 0x00000002, + 'Pure' : 0x00000004, + 'Const' : 0x00000008, + 'OptNoneINTEL' : 0x00010000, + }, + + 'MemorySemanticsShift' : { + 'Acquire' : 1, + 'Release' : 2, + 'AcquireRelease' : 3, + 'SequentiallyConsistent' : 4, + 'UniformMemory' : 6, + 'SubgroupMemory' : 7, + 'WorkgroupMemory' : 8, + 'CrossWorkgroupMemory' : 9, + 'AtomicCounterMemory' : 10, + 'ImageMemory' : 11, + 'OutputMemory' : 12, + 'OutputMemoryKHR' : 12, + 'MakeAvailable' : 13, + 'MakeAvailableKHR' : 13, + 'MakeVisible' : 14, + 'MakeVisibleKHR' : 14, + 'Volatile' : 15, + }, + + 'MemorySemanticsMask' : { + 'MaskNone' : 0, + 'Acquire' : 0x00000002, + 'Release' : 0x00000004, + 'AcquireRelease' : 0x00000008, + 'SequentiallyConsistent' : 0x00000010, + 'UniformMemory' : 0x00000040, + 'SubgroupMemory' : 0x00000080, + 'WorkgroupMemory' : 0x00000100, + 'CrossWorkgroupMemory' : 0x00000200, + 'AtomicCounterMemory' : 0x00000400, + 'ImageMemory' : 0x00000800, + 'OutputMemory' : 0x00001000, + 'OutputMemoryKHR' : 0x00001000, + 'MakeAvailable' : 0x00002000, + 'MakeAvailableKHR' : 0x00002000, + 'MakeVisible' : 0x00004000, + 'MakeVisibleKHR' : 0x00004000, + 'Volatile' : 0x00008000, + }, + + 'MemoryAccessShift' : { + 'Volatile' : 0, + 'Aligned' : 1, + 'Nontemporal' : 2, + 'MakePointerAvailable' : 3, + 'MakePointerAvailableKHR' : 3, + 'MakePointerVisible' : 4, + 'MakePointerVisibleKHR' : 4, + 'NonPrivatePointer' : 5, + 'NonPrivatePointerKHR' : 5, + 'AliasScopeINTELMask' : 16, + 'NoAliasINTELMask' : 17, + }, + + 'MemoryAccessMask' : { + 'MaskNone' : 0, + 'Volatile' : 0x00000001, + 'Aligned' : 0x00000002, + 'Nontemporal' : 0x00000004, + 'MakePointerAvailable' : 0x00000008, + 'MakePointerAvailableKHR' : 0x00000008, + 'MakePointerVisible' : 0x00000010, + 'MakePointerVisibleKHR' : 0x00000010, + 'NonPrivatePointer' : 0x00000020, + 'NonPrivatePointerKHR' : 0x00000020, + 'AliasScopeINTELMask' : 0x00010000, + 'NoAliasINTELMask' : 0x00020000, + }, + + 'Scope' : { + 'CrossDevice' : 0, + 'Device' : 1, + 'Workgroup' : 2, + 'Subgroup' : 3, + 'Invocation' : 4, + 'QueueFamily' : 5, + 'QueueFamilyKHR' : 5, + 'ShaderCallKHR' : 6, + }, + + 'GroupOperation' : { + 'Reduce' : 0, + 'InclusiveScan' : 1, + 'ExclusiveScan' : 2, + 'ClusteredReduce' : 3, + 'PartitionedReduceNV' : 6, + 'PartitionedInclusiveScanNV' : 7, + 'PartitionedExclusiveScanNV' : 8, + }, + + 'KernelEnqueueFlags' : { + 'NoWait' : 0, + 'WaitKernel' : 1, + 'WaitWorkGroup' : 2, + }, + + 'KernelProfilingInfoShift' : { + 'CmdExecTime' : 0, + }, + + 'KernelProfilingInfoMask' : { + 'MaskNone' : 0, + 'CmdExecTime' : 0x00000001, + }, + + 'Capability' : { + 'Matrix' : 0, + 'Shader' : 1, + 'Geometry' : 2, + 'Tessellation' : 3, + 'Addresses' : 4, + 'Linkage' : 5, + 'Kernel' : 6, + 'Vector16' : 7, + 'Float16Buffer' : 8, + 'Float16' : 9, + 'Float64' : 10, + 'Int64' : 11, + 'Int64Atomics' : 12, + 'ImageBasic' : 13, + 'ImageReadWrite' : 14, + 'ImageMipmap' : 15, + 'Pipes' : 17, + 'Groups' : 18, + 'DeviceEnqueue' : 19, + 'LiteralSampler' : 20, + 'AtomicStorage' : 21, + 'Int16' : 22, + 'TessellationPointSize' : 23, + 'GeometryPointSize' : 24, + 'ImageGatherExtended' : 25, + 'StorageImageMultisample' : 27, + 'UniformBufferArrayDynamicIndexing' : 28, + 'SampledImageArrayDynamicIndexing' : 29, + 'StorageBufferArrayDynamicIndexing' : 30, + 'StorageImageArrayDynamicIndexing' : 31, + 'ClipDistance' : 32, + 'CullDistance' : 33, + 'ImageCubeArray' : 34, + 'SampleRateShading' : 35, + 'ImageRect' : 36, + 'SampledRect' : 37, + 'GenericPointer' : 38, + 'Int8' : 39, + 'InputAttachment' : 40, + 'SparseResidency' : 41, + 'MinLod' : 42, + 'Sampled1D' : 43, + 'Image1D' : 44, + 'SampledCubeArray' : 45, + 'SampledBuffer' : 46, + 'ImageBuffer' : 47, + 'ImageMSArray' : 48, + 'StorageImageExtendedFormats' : 49, + 'ImageQuery' : 50, + 'DerivativeControl' : 51, + 'InterpolationFunction' : 52, + 'TransformFeedback' : 53, + 'GeometryStreams' : 54, + 'StorageImageReadWithoutFormat' : 55, + 'StorageImageWriteWithoutFormat' : 56, + 'MultiViewport' : 57, + 'SubgroupDispatch' : 58, + 'NamedBarrier' : 59, + 'PipeStorage' : 60, + 'GroupNonUniform' : 61, + 'GroupNonUniformVote' : 62, + 'GroupNonUniformArithmetic' : 63, + 'GroupNonUniformBallot' : 64, + 'GroupNonUniformShuffle' : 65, + 'GroupNonUniformShuffleRelative' : 66, + 'GroupNonUniformClustered' : 67, + 'GroupNonUniformQuad' : 68, + 'ShaderLayer' : 69, + 'ShaderViewportIndex' : 70, + 'UniformDecoration' : 71, + 'FragmentShadingRateKHR' : 4422, + 'SubgroupBallotKHR' : 4423, + 'DrawParameters' : 4427, + 'WorkgroupMemoryExplicitLayoutKHR' : 4428, + 'WorkgroupMemoryExplicitLayout8BitAccessKHR' : 4429, + 'WorkgroupMemoryExplicitLayout16BitAccessKHR' : 4430, + 'SubgroupVoteKHR' : 4431, + 'StorageBuffer16BitAccess' : 4433, + 'StorageUniformBufferBlock16' : 4433, + 'StorageUniform16' : 4434, + 'UniformAndStorageBuffer16BitAccess' : 4434, + 'StoragePushConstant16' : 4435, + 'StorageInputOutput16' : 4436, + 'DeviceGroup' : 4437, + 'MultiView' : 4439, + 'VariablePointersStorageBuffer' : 4441, + 'VariablePointers' : 4442, + 'AtomicStorageOps' : 4445, + 'SampleMaskPostDepthCoverage' : 4447, + 'StorageBuffer8BitAccess' : 4448, + 'UniformAndStorageBuffer8BitAccess' : 4449, + 'StoragePushConstant8' : 4450, + 'DenormPreserve' : 4464, + 'DenormFlushToZero' : 4465, + 'SignedZeroInfNanPreserve' : 4466, + 'RoundingModeRTE' : 4467, + 'RoundingModeRTZ' : 4468, + 'RayQueryProvisionalKHR' : 4471, + 'RayQueryKHR' : 4472, + 'RayTraversalPrimitiveCullingKHR' : 4478, + 'RayTracingKHR' : 4479, + 'Float16ImageAMD' : 5008, + 'ImageGatherBiasLodAMD' : 5009, + 'FragmentMaskAMD' : 5010, + 'StencilExportEXT' : 5013, + 'ImageReadWriteLodAMD' : 5015, + 'Int64ImageEXT' : 5016, + 'ShaderClockKHR' : 5055, + 'SampleMaskOverrideCoverageNV' : 5249, + 'GeometryShaderPassthroughNV' : 5251, + 'ShaderViewportIndexLayerEXT' : 5254, + 'ShaderViewportIndexLayerNV' : 5254, + 'ShaderViewportMaskNV' : 5255, + 'ShaderStereoViewNV' : 5259, + 'PerViewAttributesNV' : 5260, + 'FragmentFullyCoveredEXT' : 5265, + 'MeshShadingNV' : 5266, + 'ImageFootprintNV' : 5282, + 'FragmentBarycentricKHR' : 5284, + 'FragmentBarycentricNV' : 5284, + 'ComputeDerivativeGroupQuadsNV' : 5288, + 'FragmentDensityEXT' : 5291, + 'ShadingRateNV' : 5291, + 'GroupNonUniformPartitionedNV' : 5297, + 'ShaderNonUniform' : 5301, + 'ShaderNonUniformEXT' : 5301, + 'RuntimeDescriptorArray' : 5302, + 'RuntimeDescriptorArrayEXT' : 5302, + 'InputAttachmentArrayDynamicIndexing' : 5303, + 'InputAttachmentArrayDynamicIndexingEXT' : 5303, + 'UniformTexelBufferArrayDynamicIndexing' : 5304, + 'UniformTexelBufferArrayDynamicIndexingEXT' : 5304, + 'StorageTexelBufferArrayDynamicIndexing' : 5305, + 'StorageTexelBufferArrayDynamicIndexingEXT' : 5305, + 'UniformBufferArrayNonUniformIndexing' : 5306, + 'UniformBufferArrayNonUniformIndexingEXT' : 5306, + 'SampledImageArrayNonUniformIndexing' : 5307, + 'SampledImageArrayNonUniformIndexingEXT' : 5307, + 'StorageBufferArrayNonUniformIndexing' : 5308, + 'StorageBufferArrayNonUniformIndexingEXT' : 5308, + 'StorageImageArrayNonUniformIndexing' : 5309, + 'StorageImageArrayNonUniformIndexingEXT' : 5309, + 'InputAttachmentArrayNonUniformIndexing' : 5310, + 'InputAttachmentArrayNonUniformIndexingEXT' : 5310, + 'UniformTexelBufferArrayNonUniformIndexing' : 5311, + 'UniformTexelBufferArrayNonUniformIndexingEXT' : 5311, + 'StorageTexelBufferArrayNonUniformIndexing' : 5312, + 'StorageTexelBufferArrayNonUniformIndexingEXT' : 5312, + 'RayTracingNV' : 5340, + 'RayTracingMotionBlurNV' : 5341, + 'VulkanMemoryModel' : 5345, + 'VulkanMemoryModelKHR' : 5345, + 'VulkanMemoryModelDeviceScope' : 5346, + 'VulkanMemoryModelDeviceScopeKHR' : 5346, + 'PhysicalStorageBufferAddresses' : 5347, + 'PhysicalStorageBufferAddressesEXT' : 5347, + 'ComputeDerivativeGroupLinearNV' : 5350, + 'RayTracingProvisionalKHR' : 5353, + 'CooperativeMatrixNV' : 5357, + 'FragmentShaderSampleInterlockEXT' : 5363, + 'FragmentShaderShadingRateInterlockEXT' : 5372, + 'ShaderSMBuiltinsNV' : 5373, + 'FragmentShaderPixelInterlockEXT' : 5378, + 'DemoteToHelperInvocation' : 5379, + 'DemoteToHelperInvocationEXT' : 5379, + 'BindlessTextureNV' : 5390, + 'SubgroupShuffleINTEL' : 5568, + 'SubgroupBufferBlockIOINTEL' : 5569, + 'SubgroupImageBlockIOINTEL' : 5570, + 'SubgroupImageMediaBlockIOINTEL' : 5579, + 'RoundToInfinityINTEL' : 5582, + 'FloatingPointModeINTEL' : 5583, + 'IntegerFunctions2INTEL' : 5584, + 'FunctionPointersINTEL' : 5603, + 'IndirectReferencesINTEL' : 5604, + 'AsmINTEL' : 5606, + 'AtomicFloat32MinMaxEXT' : 5612, + 'AtomicFloat64MinMaxEXT' : 5613, + 'AtomicFloat16MinMaxEXT' : 5616, + 'VectorComputeINTEL' : 5617, + 'VectorAnyINTEL' : 5619, + 'ExpectAssumeKHR' : 5629, + 'SubgroupAvcMotionEstimationINTEL' : 5696, + 'SubgroupAvcMotionEstimationIntraINTEL' : 5697, + 'SubgroupAvcMotionEstimationChromaINTEL' : 5698, + 'VariableLengthArrayINTEL' : 5817, + 'FunctionFloatControlINTEL' : 5821, + 'FPGAMemoryAttributesINTEL' : 5824, + 'FPFastMathModeINTEL' : 5837, + 'ArbitraryPrecisionIntegersINTEL' : 5844, + 'ArbitraryPrecisionFloatingPointINTEL' : 5845, + 'UnstructuredLoopControlsINTEL' : 5886, + 'FPGALoopControlsINTEL' : 5888, + 'KernelAttributesINTEL' : 5892, + 'FPGAKernelAttributesINTEL' : 5897, + 'FPGAMemoryAccessesINTEL' : 5898, + 'FPGAClusterAttributesINTEL' : 5904, + 'LoopFuseINTEL' : 5906, + 'MemoryAccessAliasingINTEL' : 5910, + 'FPGABufferLocationINTEL' : 5920, + 'ArbitraryPrecisionFixedPointINTEL' : 5922, + 'USMStorageClassesINTEL' : 5935, + 'IOPipesINTEL' : 5943, + 'BlockingPipesINTEL' : 5945, + 'FPGARegINTEL' : 5948, + 'DotProductInputAll' : 6016, + 'DotProductInputAllKHR' : 6016, + 'DotProductInput4x8Bit' : 6017, + 'DotProductInput4x8BitKHR' : 6017, + 'DotProductInput4x8BitPacked' : 6018, + 'DotProductInput4x8BitPackedKHR' : 6018, + 'DotProduct' : 6019, + 'DotProductKHR' : 6019, + 'RayCullMaskKHR' : 6020, + 'BitInstructions' : 6025, + 'GroupNonUniformRotateKHR' : 6026, + 'AtomicFloat32AddEXT' : 6033, + 'AtomicFloat64AddEXT' : 6034, + 'LongConstantCompositeINTEL' : 6089, + 'OptNoneINTEL' : 6094, + 'AtomicFloat16AddEXT' : 6095, + 'DebugInfoModuleINTEL' : 6114, + 'SplitBarrierINTEL' : 6141, + 'GroupUniformArithmeticKHR' : 6400, + }, + + 'RayFlagsShift' : { + 'OpaqueKHR' : 0, + 'NoOpaqueKHR' : 1, + 'TerminateOnFirstHitKHR' : 2, + 'SkipClosestHitShaderKHR' : 3, + 'CullBackFacingTrianglesKHR' : 4, + 'CullFrontFacingTrianglesKHR' : 5, + 'CullOpaqueKHR' : 6, + 'CullNoOpaqueKHR' : 7, + 'SkipTrianglesKHR' : 8, + 'SkipAABBsKHR' : 9, + }, + + 'RayFlagsMask' : { + 'MaskNone' : 0, + 'OpaqueKHR' : 0x00000001, + 'NoOpaqueKHR' : 0x00000002, + 'TerminateOnFirstHitKHR' : 0x00000004, + 'SkipClosestHitShaderKHR' : 0x00000008, + 'CullBackFacingTrianglesKHR' : 0x00000010, + 'CullFrontFacingTrianglesKHR' : 0x00000020, + 'CullOpaqueKHR' : 0x00000040, + 'CullNoOpaqueKHR' : 0x00000080, + 'SkipTrianglesKHR' : 0x00000100, + 'SkipAABBsKHR' : 0x00000200, + }, + + 'RayQueryIntersection' : { + 'RayQueryCandidateIntersectionKHR' : 0, + 'RayQueryCommittedIntersectionKHR' : 1, + }, + + 'RayQueryCommittedIntersectionType' : { + 'RayQueryCommittedIntersectionNoneKHR' : 0, + 'RayQueryCommittedIntersectionTriangleKHR' : 1, + 'RayQueryCommittedIntersectionGeneratedKHR' : 2, + }, + + 'RayQueryCandidateIntersectionType' : { + 'RayQueryCandidateIntersectionTriangleKHR' : 0, + 'RayQueryCandidateIntersectionAABBKHR' : 1, + }, + + 'FragmentShadingRateShift' : { + 'Vertical2Pixels' : 0, + 'Vertical4Pixels' : 1, + 'Horizontal2Pixels' : 2, + 'Horizontal4Pixels' : 3, + }, + + 'FragmentShadingRateMask' : { + 'MaskNone' : 0, + 'Vertical2Pixels' : 0x00000001, + 'Vertical4Pixels' : 0x00000002, + 'Horizontal2Pixels' : 0x00000004, + 'Horizontal4Pixels' : 0x00000008, + }, + + 'FPDenormMode' : { + 'Preserve' : 0, + 'FlushToZero' : 1, + }, + + 'FPOperationMode' : { + 'IEEE' : 0, + 'ALT' : 1, + }, + + 'QuantizationModes' : { + 'TRN' : 0, + 'TRN_ZERO' : 1, + 'RND' : 2, + 'RND_ZERO' : 3, + 'RND_INF' : 4, + 'RND_MIN_INF' : 5, + 'RND_CONV' : 6, + 'RND_CONV_ODD' : 7, + }, + + 'OverflowModes' : { + 'WRAP' : 0, + 'SAT' : 1, + 'SAT_ZERO' : 2, + 'SAT_SYM' : 3, + }, + + 'PackedVectorFormat' : { + 'PackedVectorFormat4x8Bit' : 0, + 'PackedVectorFormat4x8BitKHR' : 0, + }, + + 'Op' : { + 'OpNop' : 0, + 'OpUndef' : 1, + 'OpSourceContinued' : 2, + 'OpSource' : 3, + 'OpSourceExtension' : 4, + 'OpName' : 5, + 'OpMemberName' : 6, + 'OpString' : 7, + 'OpLine' : 8, + 'OpExtension' : 10, + 'OpExtInstImport' : 11, + 'OpExtInst' : 12, + 'OpMemoryModel' : 14, + 'OpEntryPoint' : 15, + 'OpExecutionMode' : 16, + 'OpCapability' : 17, + 'OpTypeVoid' : 19, + 'OpTypeBool' : 20, + 'OpTypeInt' : 21, + 'OpTypeFloat' : 22, + 'OpTypeVector' : 23, + 'OpTypeMatrix' : 24, + 'OpTypeImage' : 25, + 'OpTypeSampler' : 26, + 'OpTypeSampledImage' : 27, + 'OpTypeArray' : 28, + 'OpTypeRuntimeArray' : 29, + 'OpTypeStruct' : 30, + 'OpTypeOpaque' : 31, + 'OpTypePointer' : 32, + 'OpTypeFunction' : 33, + 'OpTypeEvent' : 34, + 'OpTypeDeviceEvent' : 35, + 'OpTypeReserveId' : 36, + 'OpTypeQueue' : 37, + 'OpTypePipe' : 38, + 'OpTypeForwardPointer' : 39, + 'OpConstantTrue' : 41, + 'OpConstantFalse' : 42, + 'OpConstant' : 43, + 'OpConstantComposite' : 44, + 'OpConstantSampler' : 45, + 'OpConstantNull' : 46, + 'OpSpecConstantTrue' : 48, + 'OpSpecConstantFalse' : 49, + 'OpSpecConstant' : 50, + 'OpSpecConstantComposite' : 51, + 'OpSpecConstantOp' : 52, + 'OpFunction' : 54, + 'OpFunctionParameter' : 55, + 'OpFunctionEnd' : 56, + 'OpFunctionCall' : 57, + 'OpVariable' : 59, + 'OpImageTexelPointer' : 60, + 'OpLoad' : 61, + 'OpStore' : 62, + 'OpCopyMemory' : 63, + 'OpCopyMemorySized' : 64, + 'OpAccessChain' : 65, + 'OpInBoundsAccessChain' : 66, + 'OpPtrAccessChain' : 67, + 'OpArrayLength' : 68, + 'OpGenericPtrMemSemantics' : 69, + 'OpInBoundsPtrAccessChain' : 70, + 'OpDecorate' : 71, + 'OpMemberDecorate' : 72, + 'OpDecorationGroup' : 73, + 'OpGroupDecorate' : 74, + 'OpGroupMemberDecorate' : 75, + 'OpVectorExtractDynamic' : 77, + 'OpVectorInsertDynamic' : 78, + 'OpVectorShuffle' : 79, + 'OpCompositeConstruct' : 80, + 'OpCompositeExtract' : 81, + 'OpCompositeInsert' : 82, + 'OpCopyObject' : 83, + 'OpTranspose' : 84, + 'OpSampledImage' : 86, + 'OpImageSampleImplicitLod' : 87, + 'OpImageSampleExplicitLod' : 88, + 'OpImageSampleDrefImplicitLod' : 89, + 'OpImageSampleDrefExplicitLod' : 90, + 'OpImageSampleProjImplicitLod' : 91, + 'OpImageSampleProjExplicitLod' : 92, + 'OpImageSampleProjDrefImplicitLod' : 93, + 'OpImageSampleProjDrefExplicitLod' : 94, + 'OpImageFetch' : 95, + 'OpImageGather' : 96, + 'OpImageDrefGather' : 97, + 'OpImageRead' : 98, + 'OpImageWrite' : 99, + 'OpImage' : 100, + 'OpImageQueryFormat' : 101, + 'OpImageQueryOrder' : 102, + 'OpImageQuerySizeLod' : 103, + 'OpImageQuerySize' : 104, + 'OpImageQueryLod' : 105, + 'OpImageQueryLevels' : 106, + 'OpImageQuerySamples' : 107, + 'OpConvertFToU' : 109, + 'OpConvertFToS' : 110, + 'OpConvertSToF' : 111, + 'OpConvertUToF' : 112, + 'OpUConvert' : 113, + 'OpSConvert' : 114, + 'OpFConvert' : 115, + 'OpQuantizeToF16' : 116, + 'OpConvertPtrToU' : 117, + 'OpSatConvertSToU' : 118, + 'OpSatConvertUToS' : 119, + 'OpConvertUToPtr' : 120, + 'OpPtrCastToGeneric' : 121, + 'OpGenericCastToPtr' : 122, + 'OpGenericCastToPtrExplicit' : 123, + 'OpBitcast' : 124, + 'OpSNegate' : 126, + 'OpFNegate' : 127, + 'OpIAdd' : 128, + 'OpFAdd' : 129, + 'OpISub' : 130, + 'OpFSub' : 131, + 'OpIMul' : 132, + 'OpFMul' : 133, + 'OpUDiv' : 134, + 'OpSDiv' : 135, + 'OpFDiv' : 136, + 'OpUMod' : 137, + 'OpSRem' : 138, + 'OpSMod' : 139, + 'OpFRem' : 140, + 'OpFMod' : 141, + 'OpVectorTimesScalar' : 142, + 'OpMatrixTimesScalar' : 143, + 'OpVectorTimesMatrix' : 144, + 'OpMatrixTimesVector' : 145, + 'OpMatrixTimesMatrix' : 146, + 'OpOuterProduct' : 147, + 'OpDot' : 148, + 'OpIAddCarry' : 149, + 'OpISubBorrow' : 150, + 'OpUMulExtended' : 151, + 'OpSMulExtended' : 152, + 'OpAny' : 154, + 'OpAll' : 155, + 'OpIsNan' : 156, + 'OpIsInf' : 157, + 'OpIsFinite' : 158, + 'OpIsNormal' : 159, + 'OpSignBitSet' : 160, + 'OpLessOrGreater' : 161, + 'OpOrdered' : 162, + 'OpUnordered' : 163, + 'OpLogicalEqual' : 164, + 'OpLogicalNotEqual' : 165, + 'OpLogicalOr' : 166, + 'OpLogicalAnd' : 167, + 'OpLogicalNot' : 168, + 'OpSelect' : 169, + 'OpIEqual' : 170, + 'OpINotEqual' : 171, + 'OpUGreaterThan' : 172, + 'OpSGreaterThan' : 173, + 'OpUGreaterThanEqual' : 174, + 'OpSGreaterThanEqual' : 175, + 'OpULessThan' : 176, + 'OpSLessThan' : 177, + 'OpULessThanEqual' : 178, + 'OpSLessThanEqual' : 179, + 'OpFOrdEqual' : 180, + 'OpFUnordEqual' : 181, + 'OpFOrdNotEqual' : 182, + 'OpFUnordNotEqual' : 183, + 'OpFOrdLessThan' : 184, + 'OpFUnordLessThan' : 185, + 'OpFOrdGreaterThan' : 186, + 'OpFUnordGreaterThan' : 187, + 'OpFOrdLessThanEqual' : 188, + 'OpFUnordLessThanEqual' : 189, + 'OpFOrdGreaterThanEqual' : 190, + 'OpFUnordGreaterThanEqual' : 191, + 'OpShiftRightLogical' : 194, + 'OpShiftRightArithmetic' : 195, + 'OpShiftLeftLogical' : 196, + 'OpBitwiseOr' : 197, + 'OpBitwiseXor' : 198, + 'OpBitwiseAnd' : 199, + 'OpNot' : 200, + 'OpBitFieldInsert' : 201, + 'OpBitFieldSExtract' : 202, + 'OpBitFieldUExtract' : 203, + 'OpBitReverse' : 204, + 'OpBitCount' : 205, + 'OpDPdx' : 207, + 'OpDPdy' : 208, + 'OpFwidth' : 209, + 'OpDPdxFine' : 210, + 'OpDPdyFine' : 211, + 'OpFwidthFine' : 212, + 'OpDPdxCoarse' : 213, + 'OpDPdyCoarse' : 214, + 'OpFwidthCoarse' : 215, + 'OpEmitVertex' : 218, + 'OpEndPrimitive' : 219, + 'OpEmitStreamVertex' : 220, + 'OpEndStreamPrimitive' : 221, + 'OpControlBarrier' : 224, + 'OpMemoryBarrier' : 225, + 'OpAtomicLoad' : 227, + 'OpAtomicStore' : 228, + 'OpAtomicExchange' : 229, + 'OpAtomicCompareExchange' : 230, + 'OpAtomicCompareExchangeWeak' : 231, + 'OpAtomicIIncrement' : 232, + 'OpAtomicIDecrement' : 233, + 'OpAtomicIAdd' : 234, + 'OpAtomicISub' : 235, + 'OpAtomicSMin' : 236, + 'OpAtomicUMin' : 237, + 'OpAtomicSMax' : 238, + 'OpAtomicUMax' : 239, + 'OpAtomicAnd' : 240, + 'OpAtomicOr' : 241, + 'OpAtomicXor' : 242, + 'OpPhi' : 245, + 'OpLoopMerge' : 246, + 'OpSelectionMerge' : 247, + 'OpLabel' : 248, + 'OpBranch' : 249, + 'OpBranchConditional' : 250, + 'OpSwitch' : 251, + 'OpKill' : 252, + 'OpReturn' : 253, + 'OpReturnValue' : 254, + 'OpUnreachable' : 255, + 'OpLifetimeStart' : 256, + 'OpLifetimeStop' : 257, + 'OpGroupAsyncCopy' : 259, + 'OpGroupWaitEvents' : 260, + 'OpGroupAll' : 261, + 'OpGroupAny' : 262, + 'OpGroupBroadcast' : 263, + 'OpGroupIAdd' : 264, + 'OpGroupFAdd' : 265, + 'OpGroupFMin' : 266, + 'OpGroupUMin' : 267, + 'OpGroupSMin' : 268, + 'OpGroupFMax' : 269, + 'OpGroupUMax' : 270, + 'OpGroupSMax' : 271, + 'OpReadPipe' : 274, + 'OpWritePipe' : 275, + 'OpReservedReadPipe' : 276, + 'OpReservedWritePipe' : 277, + 'OpReserveReadPipePackets' : 278, + 'OpReserveWritePipePackets' : 279, + 'OpCommitReadPipe' : 280, + 'OpCommitWritePipe' : 281, + 'OpIsValidReserveId' : 282, + 'OpGetNumPipePackets' : 283, + 'OpGetMaxPipePackets' : 284, + 'OpGroupReserveReadPipePackets' : 285, + 'OpGroupReserveWritePipePackets' : 286, + 'OpGroupCommitReadPipe' : 287, + 'OpGroupCommitWritePipe' : 288, + 'OpEnqueueMarker' : 291, + 'OpEnqueueKernel' : 292, + 'OpGetKernelNDrangeSubGroupCount' : 293, + 'OpGetKernelNDrangeMaxSubGroupSize' : 294, + 'OpGetKernelWorkGroupSize' : 295, + 'OpGetKernelPreferredWorkGroupSizeMultiple' : 296, + 'OpRetainEvent' : 297, + 'OpReleaseEvent' : 298, + 'OpCreateUserEvent' : 299, + 'OpIsValidEvent' : 300, + 'OpSetUserEventStatus' : 301, + 'OpCaptureEventProfilingInfo' : 302, + 'OpGetDefaultQueue' : 303, + 'OpBuildNDRange' : 304, + 'OpImageSparseSampleImplicitLod' : 305, + 'OpImageSparseSampleExplicitLod' : 306, + 'OpImageSparseSampleDrefImplicitLod' : 307, + 'OpImageSparseSampleDrefExplicitLod' : 308, + 'OpImageSparseSampleProjImplicitLod' : 309, + 'OpImageSparseSampleProjExplicitLod' : 310, + 'OpImageSparseSampleProjDrefImplicitLod' : 311, + 'OpImageSparseSampleProjDrefExplicitLod' : 312, + 'OpImageSparseFetch' : 313, + 'OpImageSparseGather' : 314, + 'OpImageSparseDrefGather' : 315, + 'OpImageSparseTexelsResident' : 316, + 'OpNoLine' : 317, + 'OpAtomicFlagTestAndSet' : 318, + 'OpAtomicFlagClear' : 319, + 'OpImageSparseRead' : 320, + 'OpSizeOf' : 321, + 'OpTypePipeStorage' : 322, + 'OpConstantPipeStorage' : 323, + 'OpCreatePipeFromPipeStorage' : 324, + 'OpGetKernelLocalSizeForSubgroupCount' : 325, + 'OpGetKernelMaxNumSubgroups' : 326, + 'OpTypeNamedBarrier' : 327, + 'OpNamedBarrierInitialize' : 328, + 'OpMemoryNamedBarrier' : 329, + 'OpModuleProcessed' : 330, + 'OpExecutionModeId' : 331, + 'OpDecorateId' : 332, + 'OpGroupNonUniformElect' : 333, + 'OpGroupNonUniformAll' : 334, + 'OpGroupNonUniformAny' : 335, + 'OpGroupNonUniformAllEqual' : 336, + 'OpGroupNonUniformBroadcast' : 337, + 'OpGroupNonUniformBroadcastFirst' : 338, + 'OpGroupNonUniformBallot' : 339, + 'OpGroupNonUniformInverseBallot' : 340, + 'OpGroupNonUniformBallotBitExtract' : 341, + 'OpGroupNonUniformBallotBitCount' : 342, + 'OpGroupNonUniformBallotFindLSB' : 343, + 'OpGroupNonUniformBallotFindMSB' : 344, + 'OpGroupNonUniformShuffle' : 345, + 'OpGroupNonUniformShuffleXor' : 346, + 'OpGroupNonUniformShuffleUp' : 347, + 'OpGroupNonUniformShuffleDown' : 348, + 'OpGroupNonUniformIAdd' : 349, + 'OpGroupNonUniformFAdd' : 350, + 'OpGroupNonUniformIMul' : 351, + 'OpGroupNonUniformFMul' : 352, + 'OpGroupNonUniformSMin' : 353, + 'OpGroupNonUniformUMin' : 354, + 'OpGroupNonUniformFMin' : 355, + 'OpGroupNonUniformSMax' : 356, + 'OpGroupNonUniformUMax' : 357, + 'OpGroupNonUniformFMax' : 358, + 'OpGroupNonUniformBitwiseAnd' : 359, + 'OpGroupNonUniformBitwiseOr' : 360, + 'OpGroupNonUniformBitwiseXor' : 361, + 'OpGroupNonUniformLogicalAnd' : 362, + 'OpGroupNonUniformLogicalOr' : 363, + 'OpGroupNonUniformLogicalXor' : 364, + 'OpGroupNonUniformQuadBroadcast' : 365, + 'OpGroupNonUniformQuadSwap' : 366, + 'OpCopyLogical' : 400, + 'OpPtrEqual' : 401, + 'OpPtrNotEqual' : 402, + 'OpPtrDiff' : 403, + 'OpTerminateInvocation' : 4416, + 'OpSubgroupBallotKHR' : 4421, + 'OpSubgroupFirstInvocationKHR' : 4422, + 'OpSubgroupAllKHR' : 4428, + 'OpSubgroupAnyKHR' : 4429, + 'OpSubgroupAllEqualKHR' : 4430, + 'OpGroupNonUniformRotateKHR' : 4431, + 'OpSubgroupReadInvocationKHR' : 4432, + 'OpTraceRayKHR' : 4445, + 'OpExecuteCallableKHR' : 4446, + 'OpConvertUToAccelerationStructureKHR' : 4447, + 'OpIgnoreIntersectionKHR' : 4448, + 'OpTerminateRayKHR' : 4449, + 'OpSDot' : 4450, + 'OpSDotKHR' : 4450, + 'OpUDot' : 4451, + 'OpUDotKHR' : 4451, + 'OpSUDot' : 4452, + 'OpSUDotKHR' : 4452, + 'OpSDotAccSat' : 4453, + 'OpSDotAccSatKHR' : 4453, + 'OpUDotAccSat' : 4454, + 'OpUDotAccSatKHR' : 4454, + 'OpSUDotAccSat' : 4455, + 'OpSUDotAccSatKHR' : 4455, + 'OpTypeRayQueryKHR' : 4472, + 'OpRayQueryInitializeKHR' : 4473, + 'OpRayQueryTerminateKHR' : 4474, + 'OpRayQueryGenerateIntersectionKHR' : 4475, + 'OpRayQueryConfirmIntersectionKHR' : 4476, + 'OpRayQueryProceedKHR' : 4477, + 'OpRayQueryGetIntersectionTypeKHR' : 4479, + 'OpGroupIAddNonUniformAMD' : 5000, + 'OpGroupFAddNonUniformAMD' : 5001, + 'OpGroupFMinNonUniformAMD' : 5002, + 'OpGroupUMinNonUniformAMD' : 5003, + 'OpGroupSMinNonUniformAMD' : 5004, + 'OpGroupFMaxNonUniformAMD' : 5005, + 'OpGroupUMaxNonUniformAMD' : 5006, + 'OpGroupSMaxNonUniformAMD' : 5007, + 'OpFragmentMaskFetchAMD' : 5011, + 'OpFragmentFetchAMD' : 5012, + 'OpReadClockKHR' : 5056, + 'OpImageSampleFootprintNV' : 5283, + 'OpGroupNonUniformPartitionNV' : 5296, + 'OpWritePackedPrimitiveIndices4x8NV' : 5299, + 'OpReportIntersectionKHR' : 5334, + 'OpReportIntersectionNV' : 5334, + 'OpIgnoreIntersectionNV' : 5335, + 'OpTerminateRayNV' : 5336, + 'OpTraceNV' : 5337, + 'OpTraceMotionNV' : 5338, + 'OpTraceRayMotionNV' : 5339, + 'OpTypeAccelerationStructureKHR' : 5341, + 'OpTypeAccelerationStructureNV' : 5341, + 'OpExecuteCallableNV' : 5344, + 'OpTypeCooperativeMatrixNV' : 5358, + 'OpCooperativeMatrixLoadNV' : 5359, + 'OpCooperativeMatrixStoreNV' : 5360, + 'OpCooperativeMatrixMulAddNV' : 5361, + 'OpCooperativeMatrixLengthNV' : 5362, + 'OpBeginInvocationInterlockEXT' : 5364, + 'OpEndInvocationInterlockEXT' : 5365, + 'OpDemoteToHelperInvocation' : 5380, + 'OpDemoteToHelperInvocationEXT' : 5380, + 'OpIsHelperInvocationEXT' : 5381, + 'OpConvertUToImageNV' : 5391, + 'OpConvertUToSamplerNV' : 5392, + 'OpConvertImageToUNV' : 5393, + 'OpConvertSamplerToUNV' : 5394, + 'OpConvertUToSampledImageNV' : 5395, + 'OpConvertSampledImageToUNV' : 5396, + 'OpSamplerImageAddressingModeNV' : 5397, + 'OpSubgroupShuffleINTEL' : 5571, + 'OpSubgroupShuffleDownINTEL' : 5572, + 'OpSubgroupShuffleUpINTEL' : 5573, + 'OpSubgroupShuffleXorINTEL' : 5574, + 'OpSubgroupBlockReadINTEL' : 5575, + 'OpSubgroupBlockWriteINTEL' : 5576, + 'OpSubgroupImageBlockReadINTEL' : 5577, + 'OpSubgroupImageBlockWriteINTEL' : 5578, + 'OpSubgroupImageMediaBlockReadINTEL' : 5580, + 'OpSubgroupImageMediaBlockWriteINTEL' : 5581, + 'OpUCountLeadingZerosINTEL' : 5585, + 'OpUCountTrailingZerosINTEL' : 5586, + 'OpAbsISubINTEL' : 5587, + 'OpAbsUSubINTEL' : 5588, + 'OpIAddSatINTEL' : 5589, + 'OpUAddSatINTEL' : 5590, + 'OpIAverageINTEL' : 5591, + 'OpUAverageINTEL' : 5592, + 'OpIAverageRoundedINTEL' : 5593, + 'OpUAverageRoundedINTEL' : 5594, + 'OpISubSatINTEL' : 5595, + 'OpUSubSatINTEL' : 5596, + 'OpIMul32x16INTEL' : 5597, + 'OpUMul32x16INTEL' : 5598, + 'OpConstantFunctionPointerINTEL' : 5600, + 'OpFunctionPointerCallINTEL' : 5601, + 'OpAsmTargetINTEL' : 5609, + 'OpAsmINTEL' : 5610, + 'OpAsmCallINTEL' : 5611, + 'OpAtomicFMinEXT' : 5614, + 'OpAtomicFMaxEXT' : 5615, + 'OpAssumeTrueKHR' : 5630, + 'OpExpectKHR' : 5631, + 'OpDecorateString' : 5632, + 'OpDecorateStringGOOGLE' : 5632, + 'OpMemberDecorateString' : 5633, + 'OpMemberDecorateStringGOOGLE' : 5633, + 'OpVmeImageINTEL' : 5699, + 'OpTypeVmeImageINTEL' : 5700, + 'OpTypeAvcImePayloadINTEL' : 5701, + 'OpTypeAvcRefPayloadINTEL' : 5702, + 'OpTypeAvcSicPayloadINTEL' : 5703, + 'OpTypeAvcMcePayloadINTEL' : 5704, + 'OpTypeAvcMceResultINTEL' : 5705, + 'OpTypeAvcImeResultINTEL' : 5706, + 'OpTypeAvcImeResultSingleReferenceStreamoutINTEL' : 5707, + 'OpTypeAvcImeResultDualReferenceStreamoutINTEL' : 5708, + 'OpTypeAvcImeSingleReferenceStreaminINTEL' : 5709, + 'OpTypeAvcImeDualReferenceStreaminINTEL' : 5710, + 'OpTypeAvcRefResultINTEL' : 5711, + 'OpTypeAvcSicResultINTEL' : 5712, + 'OpSubgroupAvcMceGetDefaultInterBaseMultiReferencePenaltyINTEL' : 5713, + 'OpSubgroupAvcMceSetInterBaseMultiReferencePenaltyINTEL' : 5714, + 'OpSubgroupAvcMceGetDefaultInterShapePenaltyINTEL' : 5715, + 'OpSubgroupAvcMceSetInterShapePenaltyINTEL' : 5716, + 'OpSubgroupAvcMceGetDefaultInterDirectionPenaltyINTEL' : 5717, + 'OpSubgroupAvcMceSetInterDirectionPenaltyINTEL' : 5718, + 'OpSubgroupAvcMceGetDefaultIntraLumaShapePenaltyINTEL' : 5719, + 'OpSubgroupAvcMceGetDefaultInterMotionVectorCostTableINTEL' : 5720, + 'OpSubgroupAvcMceGetDefaultHighPenaltyCostTableINTEL' : 5721, + 'OpSubgroupAvcMceGetDefaultMediumPenaltyCostTableINTEL' : 5722, + 'OpSubgroupAvcMceGetDefaultLowPenaltyCostTableINTEL' : 5723, + 'OpSubgroupAvcMceSetMotionVectorCostFunctionINTEL' : 5724, + 'OpSubgroupAvcMceGetDefaultIntraLumaModePenaltyINTEL' : 5725, + 'OpSubgroupAvcMceGetDefaultNonDcLumaIntraPenaltyINTEL' : 5726, + 'OpSubgroupAvcMceGetDefaultIntraChromaModeBasePenaltyINTEL' : 5727, + 'OpSubgroupAvcMceSetAcOnlyHaarINTEL' : 5728, + 'OpSubgroupAvcMceSetSourceInterlacedFieldPolarityINTEL' : 5729, + 'OpSubgroupAvcMceSetSingleReferenceInterlacedFieldPolarityINTEL' : 5730, + 'OpSubgroupAvcMceSetDualReferenceInterlacedFieldPolaritiesINTEL' : 5731, + 'OpSubgroupAvcMceConvertToImePayloadINTEL' : 5732, + 'OpSubgroupAvcMceConvertToImeResultINTEL' : 5733, + 'OpSubgroupAvcMceConvertToRefPayloadINTEL' : 5734, + 'OpSubgroupAvcMceConvertToRefResultINTEL' : 5735, + 'OpSubgroupAvcMceConvertToSicPayloadINTEL' : 5736, + 'OpSubgroupAvcMceConvertToSicResultINTEL' : 5737, + 'OpSubgroupAvcMceGetMotionVectorsINTEL' : 5738, + 'OpSubgroupAvcMceGetInterDistortionsINTEL' : 5739, + 'OpSubgroupAvcMceGetBestInterDistortionsINTEL' : 5740, + 'OpSubgroupAvcMceGetInterMajorShapeINTEL' : 5741, + 'OpSubgroupAvcMceGetInterMinorShapeINTEL' : 5742, + 'OpSubgroupAvcMceGetInterDirectionsINTEL' : 5743, + 'OpSubgroupAvcMceGetInterMotionVectorCountINTEL' : 5744, + 'OpSubgroupAvcMceGetInterReferenceIdsINTEL' : 5745, + 'OpSubgroupAvcMceGetInterReferenceInterlacedFieldPolaritiesINTEL' : 5746, + 'OpSubgroupAvcImeInitializeINTEL' : 5747, + 'OpSubgroupAvcImeSetSingleReferenceINTEL' : 5748, + 'OpSubgroupAvcImeSetDualReferenceINTEL' : 5749, + 'OpSubgroupAvcImeRefWindowSizeINTEL' : 5750, + 'OpSubgroupAvcImeAdjustRefOffsetINTEL' : 5751, + 'OpSubgroupAvcImeConvertToMcePayloadINTEL' : 5752, + 'OpSubgroupAvcImeSetMaxMotionVectorCountINTEL' : 5753, + 'OpSubgroupAvcImeSetUnidirectionalMixDisableINTEL' : 5754, + 'OpSubgroupAvcImeSetEarlySearchTerminationThresholdINTEL' : 5755, + 'OpSubgroupAvcImeSetWeightedSadINTEL' : 5756, + 'OpSubgroupAvcImeEvaluateWithSingleReferenceINTEL' : 5757, + 'OpSubgroupAvcImeEvaluateWithDualReferenceINTEL' : 5758, + 'OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminINTEL' : 5759, + 'OpSubgroupAvcImeEvaluateWithDualReferenceStreaminINTEL' : 5760, + 'OpSubgroupAvcImeEvaluateWithSingleReferenceStreamoutINTEL' : 5761, + 'OpSubgroupAvcImeEvaluateWithDualReferenceStreamoutINTEL' : 5762, + 'OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminoutINTEL' : 5763, + 'OpSubgroupAvcImeEvaluateWithDualReferenceStreaminoutINTEL' : 5764, + 'OpSubgroupAvcImeConvertToMceResultINTEL' : 5765, + 'OpSubgroupAvcImeGetSingleReferenceStreaminINTEL' : 5766, + 'OpSubgroupAvcImeGetDualReferenceStreaminINTEL' : 5767, + 'OpSubgroupAvcImeStripSingleReferenceStreamoutINTEL' : 5768, + 'OpSubgroupAvcImeStripDualReferenceStreamoutINTEL' : 5769, + 'OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeMotionVectorsINTEL' : 5770, + 'OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeDistortionsINTEL' : 5771, + 'OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeReferenceIdsINTEL' : 5772, + 'OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeMotionVectorsINTEL' : 5773, + 'OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeDistortionsINTEL' : 5774, + 'OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeReferenceIdsINTEL' : 5775, + 'OpSubgroupAvcImeGetBorderReachedINTEL' : 5776, + 'OpSubgroupAvcImeGetTruncatedSearchIndicationINTEL' : 5777, + 'OpSubgroupAvcImeGetUnidirectionalEarlySearchTerminationINTEL' : 5778, + 'OpSubgroupAvcImeGetWeightingPatternMinimumMotionVectorINTEL' : 5779, + 'OpSubgroupAvcImeGetWeightingPatternMinimumDistortionINTEL' : 5780, + 'OpSubgroupAvcFmeInitializeINTEL' : 5781, + 'OpSubgroupAvcBmeInitializeINTEL' : 5782, + 'OpSubgroupAvcRefConvertToMcePayloadINTEL' : 5783, + 'OpSubgroupAvcRefSetBidirectionalMixDisableINTEL' : 5784, + 'OpSubgroupAvcRefSetBilinearFilterEnableINTEL' : 5785, + 'OpSubgroupAvcRefEvaluateWithSingleReferenceINTEL' : 5786, + 'OpSubgroupAvcRefEvaluateWithDualReferenceINTEL' : 5787, + 'OpSubgroupAvcRefEvaluateWithMultiReferenceINTEL' : 5788, + 'OpSubgroupAvcRefEvaluateWithMultiReferenceInterlacedINTEL' : 5789, + 'OpSubgroupAvcRefConvertToMceResultINTEL' : 5790, + 'OpSubgroupAvcSicInitializeINTEL' : 5791, + 'OpSubgroupAvcSicConfigureSkcINTEL' : 5792, + 'OpSubgroupAvcSicConfigureIpeLumaINTEL' : 5793, + 'OpSubgroupAvcSicConfigureIpeLumaChromaINTEL' : 5794, + 'OpSubgroupAvcSicGetMotionVectorMaskINTEL' : 5795, + 'OpSubgroupAvcSicConvertToMcePayloadINTEL' : 5796, + 'OpSubgroupAvcSicSetIntraLumaShapePenaltyINTEL' : 5797, + 'OpSubgroupAvcSicSetIntraLumaModeCostFunctionINTEL' : 5798, + 'OpSubgroupAvcSicSetIntraChromaModeCostFunctionINTEL' : 5799, + 'OpSubgroupAvcSicSetBilinearFilterEnableINTEL' : 5800, + 'OpSubgroupAvcSicSetSkcForwardTransformEnableINTEL' : 5801, + 'OpSubgroupAvcSicSetBlockBasedRawSkipSadINTEL' : 5802, + 'OpSubgroupAvcSicEvaluateIpeINTEL' : 5803, + 'OpSubgroupAvcSicEvaluateWithSingleReferenceINTEL' : 5804, + 'OpSubgroupAvcSicEvaluateWithDualReferenceINTEL' : 5805, + 'OpSubgroupAvcSicEvaluateWithMultiReferenceINTEL' : 5806, + 'OpSubgroupAvcSicEvaluateWithMultiReferenceInterlacedINTEL' : 5807, + 'OpSubgroupAvcSicConvertToMceResultINTEL' : 5808, + 'OpSubgroupAvcSicGetIpeLumaShapeINTEL' : 5809, + 'OpSubgroupAvcSicGetBestIpeLumaDistortionINTEL' : 5810, + 'OpSubgroupAvcSicGetBestIpeChromaDistortionINTEL' : 5811, + 'OpSubgroupAvcSicGetPackedIpeLumaModesINTEL' : 5812, + 'OpSubgroupAvcSicGetIpeChromaModeINTEL' : 5813, + 'OpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL' : 5814, + 'OpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL' : 5815, + 'OpSubgroupAvcSicGetInterRawSadsINTEL' : 5816, + 'OpVariableLengthArrayINTEL' : 5818, + 'OpSaveMemoryINTEL' : 5819, + 'OpRestoreMemoryINTEL' : 5820, + 'OpArbitraryFloatSinCosPiINTEL' : 5840, + 'OpArbitraryFloatCastINTEL' : 5841, + 'OpArbitraryFloatCastFromIntINTEL' : 5842, + 'OpArbitraryFloatCastToIntINTEL' : 5843, + 'OpArbitraryFloatAddINTEL' : 5846, + 'OpArbitraryFloatSubINTEL' : 5847, + 'OpArbitraryFloatMulINTEL' : 5848, + 'OpArbitraryFloatDivINTEL' : 5849, + 'OpArbitraryFloatGTINTEL' : 5850, + 'OpArbitraryFloatGEINTEL' : 5851, + 'OpArbitraryFloatLTINTEL' : 5852, + 'OpArbitraryFloatLEINTEL' : 5853, + 'OpArbitraryFloatEQINTEL' : 5854, + 'OpArbitraryFloatRecipINTEL' : 5855, + 'OpArbitraryFloatRSqrtINTEL' : 5856, + 'OpArbitraryFloatCbrtINTEL' : 5857, + 'OpArbitraryFloatHypotINTEL' : 5858, + 'OpArbitraryFloatSqrtINTEL' : 5859, + 'OpArbitraryFloatLogINTEL' : 5860, + 'OpArbitraryFloatLog2INTEL' : 5861, + 'OpArbitraryFloatLog10INTEL' : 5862, + 'OpArbitraryFloatLog1pINTEL' : 5863, + 'OpArbitraryFloatExpINTEL' : 5864, + 'OpArbitraryFloatExp2INTEL' : 5865, + 'OpArbitraryFloatExp10INTEL' : 5866, + 'OpArbitraryFloatExpm1INTEL' : 5867, + 'OpArbitraryFloatSinINTEL' : 5868, + 'OpArbitraryFloatCosINTEL' : 5869, + 'OpArbitraryFloatSinCosINTEL' : 5870, + 'OpArbitraryFloatSinPiINTEL' : 5871, + 'OpArbitraryFloatCosPiINTEL' : 5872, + 'OpArbitraryFloatASinINTEL' : 5873, + 'OpArbitraryFloatASinPiINTEL' : 5874, + 'OpArbitraryFloatACosINTEL' : 5875, + 'OpArbitraryFloatACosPiINTEL' : 5876, + 'OpArbitraryFloatATanINTEL' : 5877, + 'OpArbitraryFloatATanPiINTEL' : 5878, + 'OpArbitraryFloatATan2INTEL' : 5879, + 'OpArbitraryFloatPowINTEL' : 5880, + 'OpArbitraryFloatPowRINTEL' : 5881, + 'OpArbitraryFloatPowNINTEL' : 5882, + 'OpLoopControlINTEL' : 5887, + 'OpAliasDomainDeclINTEL' : 5911, + 'OpAliasScopeDeclINTEL' : 5912, + 'OpAliasScopeListDeclINTEL' : 5913, + 'OpFixedSqrtINTEL' : 5923, + 'OpFixedRecipINTEL' : 5924, + 'OpFixedRsqrtINTEL' : 5925, + 'OpFixedSinINTEL' : 5926, + 'OpFixedCosINTEL' : 5927, + 'OpFixedSinCosINTEL' : 5928, + 'OpFixedSinPiINTEL' : 5929, + 'OpFixedCosPiINTEL' : 5930, + 'OpFixedSinCosPiINTEL' : 5931, + 'OpFixedLogINTEL' : 5932, + 'OpFixedExpINTEL' : 5933, + 'OpPtrCastToCrossWorkgroupINTEL' : 5934, + 'OpCrossWorkgroupCastToPtrINTEL' : 5938, + 'OpReadPipeBlockingINTEL' : 5946, + 'OpWritePipeBlockingINTEL' : 5947, + 'OpFPGARegINTEL' : 5949, + 'OpRayQueryGetRayTMinKHR' : 6016, + 'OpRayQueryGetRayFlagsKHR' : 6017, + 'OpRayQueryGetIntersectionTKHR' : 6018, + 'OpRayQueryGetIntersectionInstanceCustomIndexKHR' : 6019, + 'OpRayQueryGetIntersectionInstanceIdKHR' : 6020, + 'OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR' : 6021, + 'OpRayQueryGetIntersectionGeometryIndexKHR' : 6022, + 'OpRayQueryGetIntersectionPrimitiveIndexKHR' : 6023, + 'OpRayQueryGetIntersectionBarycentricsKHR' : 6024, + 'OpRayQueryGetIntersectionFrontFaceKHR' : 6025, + 'OpRayQueryGetIntersectionCandidateAABBOpaqueKHR' : 6026, + 'OpRayQueryGetIntersectionObjectRayDirectionKHR' : 6027, + 'OpRayQueryGetIntersectionObjectRayOriginKHR' : 6028, + 'OpRayQueryGetWorldRayDirectionKHR' : 6029, + 'OpRayQueryGetWorldRayOriginKHR' : 6030, + 'OpRayQueryGetIntersectionObjectToWorldKHR' : 6031, + 'OpRayQueryGetIntersectionWorldToObjectKHR' : 6032, + 'OpAtomicFAddEXT' : 6035, + 'OpTypeBufferSurfaceINTEL' : 6086, + 'OpTypeStructContinuedINTEL' : 6090, + 'OpConstantCompositeContinuedINTEL' : 6091, + 'OpSpecConstantCompositeContinuedINTEL' : 6092, + 'OpControlBarrierArriveINTEL' : 6142, + 'OpControlBarrierWaitINTEL' : 6143, + 'OpGroupIMulKHR' : 6401, + 'OpGroupFMulKHR' : 6402, + 'OpGroupBitwiseAndKHR' : 6403, + 'OpGroupBitwiseOrKHR' : 6404, + 'OpGroupBitwiseXorKHR' : 6405, + 'OpGroupLogicalAndKHR' : 6406, + 'OpGroupLogicalOrKHR' : 6407, + 'OpGroupLogicalXorKHR' : 6408, + }, + +} + From f7c04760e38c82d7cf01c0b15240fbc0c704a26b Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 15 Oct 2022 14:26:58 -0700 Subject: [PATCH 457/548] rt: begin loading meatpipe shaders --- ref_vk/vk_meatpipe.c | 96 ++++++++++++++++++++++++++++++++++++++++++++ ref_vk/vk_meatpipe.h | 10 +++++ ref_vk/vk_pipeline.c | 95 ++++++++++++++++++++----------------------- ref_vk/vk_pipeline.h | 3 ++ 4 files changed, 152 insertions(+), 52 deletions(-) create mode 100644 ref_vk/vk_meatpipe.c create mode 100644 ref_vk/vk_meatpipe.h diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c new file mode 100644 index 00000000..f6fd0bbb --- /dev/null +++ b/ref_vk/vk_meatpipe.c @@ -0,0 +1,96 @@ +#include "vk_meatpipe.h" + +#include "vk_pipeline.h" + +#include "ray_pass.h" +#include "vk_common.h" + +#define CHAR4UINT(a,b,c,d) (((d)<<24)|((c)<<16)|((b)<<8)|(a)) +static const uint32_t k_meatpipe_magic = CHAR4UINT('M', 'E', 'A', 'T'); + +typedef struct { + const byte *data; + int off, size; + qboolean error; +} cursor_t; + +const void* curReadPtr(cursor_t *cur, int size) { + const int left = cur->size - cur->off; + if (left < size) { + cur->error = true; + return NULL; + } + + const void* const ret = cur->data + cur->off; + cur->off += size; + return ret; +} + +#define CUR_ERROR(errmsg, ...) \ + if (cur.error) { \ + gEngine.Con_Printf(S_ERROR "(off=%d left=%d) " errmsg "\n", #__VA_ARGS__); \ + goto finalize; \ + } + +#define READ_PTR(size, errmsg, ...) \ + curReadPtr(&cur, size); CUR_ERROR(errmsg, #__VA_ARGS__) + +uint32_t curReadU32(cursor_t *cur) { + const void *src = curReadPtr(cur, sizeof(uint32_t)); + if (!src) + return 0; + + uint32_t ret; + memcpy(&ret, src, sizeof(uint32_t)); + return ret; +} + +#define READ_U32(errmsg, ...) \ + curReadU32(&cur); CUR_ERROR(errmsg, #__VA_ARGS__) + +qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename) { + qboolean ret = false; + fs_offset_t size = 0; + byte* const buf = gEngine.fsapi->LoadFile(filename, &size, false); + + if (!buf) { + gEngine.Con_Printf(S_ERROR "Couldn't read \"%s\"\n", filename); + return false; + } + + cursor_t cur = { .data = buf, .off = 0, .size = size }; + + const uint32_t magic = READ_U32("Couldn't read magic"); + + if (magic != k_meatpipe_magic) { + gEngine.Con_Printf(S_ERROR "Meatpipe magic invalid for \"%s\": got %08x expected %08x\n", filename, magic, k_meatpipe_magic); + goto finalize; + } + + const int shaders_count = READ_U32("Couldn't read shaders count"); + VkShaderModule *shaders = Mem_Malloc(vk_core.pool, sizeof(VkShaderModule) * shaders_count); + for (int i = 0; i < shaders_count; ++i) + shaders[i] = VK_NULL_HANDLE; + + for (int i = 0; i < shaders_count; ++i) { + char name[256]; + Q_snprintf(name, sizeof(name), "%s@%d", filename, i); // TODO serialize origin name + const int size = READ_U32("Couldn't read shader %s size", name); + const void *src = READ_PTR(size, "Couldn't read shader %s data", name); + shaders[i] = R_VkShaderLoadFromMem(src, size, name); + gEngine.Con_Reportf("%d: loaded %s into %p\n", i, name, shaders[i]); + } + + ret = true; +finalize: + for (int i = 0; i < shaders_count; ++i) { + if (shaders[i] != VK_NULL_HANDLE) { + R_VkShaderDestroy(shaders[i]); + } + } + Mem_Free(buf); + return ret; +} + +void R_VkMeatpipeDestroy(vk_meatpipe_t *mp) { +} diff --git a/ref_vk/vk_meatpipe.h b/ref_vk/vk_meatpipe.h new file mode 100644 index 00000000..69bfc097 --- /dev/null +++ b/ref_vk/vk_meatpipe.h @@ -0,0 +1,10 @@ +#pragma once + +#include "xash3d_types.h" + +typedef struct { + int pog; +} vk_meatpipe_t; + +qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename); +void R_VkMeatpipeDestroy(vk_meatpipe_t *mp); diff --git a/ref_vk/vk_pipeline.c b/ref_vk/vk_pipeline.c index 27557f40..f802e498 100644 --- a/ref_vk/vk_pipeline.c +++ b/ref_vk/vk_pipeline.c @@ -1,5 +1,4 @@ #include "vk_pipeline.h" -#include "vk_spirv.h" #include "vk_framectl.h" // VkRenderPass @@ -26,54 +25,47 @@ void VK_PipelineShutdown( void ) vkDestroyPipelineCache(vk_core.device, g_pipeline_cache, NULL); } -typedef struct { - VkShaderModule module; - - vk_spirv_t spirv; -} r_vk_shader_t; - -static qboolean R_VkShaderLoad(r_vk_shader_t *shader, const char *filename) { - *shader = (r_vk_shader_t){0}; - - fs_offset_t size = 0; - byte* const buf = gEngine.fsapi->LoadFile(filename, &size, false); - - if (!buf) { - gEngine.Con_Printf( S_ERROR "Cannot open shader file \"%s\"\n", filename); - return false; - } - - if ((size % 4 != 0) || (((uintptr_t)buf & 3) != 0)) { - gEngine.Con_Printf(S_ERROR "size %zu or buf %p is not aligned to 4 bytes as required by SPIR-V/Vulkan spec", size, buf); - return false; +VkShaderModule R_VkShaderLoadFromMem(const void *ptr, uint32_t size, const char *name) { + if ((size % 4 != 0) || (((uintptr_t)ptr & 3) != 0)) { + gEngine.Con_Printf(S_ERROR "Couldn't load shader %s: size %zu or buf %p is not aligned to 4 bytes as required by SPIR-V/Vulkan spec\n", name, size, ptr); + return VK_NULL_HANDLE; } const VkShaderModuleCreateInfo smci = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = size, - .pCode = (const uint32_t*)(void*)buf, + .pCode = (const uint32_t*)(void*)ptr, }; - XVK_CHECK(vkCreateShaderModule(vk_core.device, &smci, NULL, &shader->module)); - SET_DEBUG_NAME(shader->module, VK_OBJECT_TYPE_SHADER_MODULE, filename); - - if (!R_VkSpirvParse(&shader->spirv, smci.pCode, size / 4)) { - gEngine.Con_Printf(S_ERROR "Error parsing SPIR-V for %s\n", filename); - } else { - gEngine.Con_Reportf("Got %d bindings for shader %s\n", shader->spirv.bindings_count, filename); - for (int j = 0; j < shader->spirv.bindings_count; ++j) { - const vk_binding_t *binding = shader->spirv.bindings + j; - gEngine.Con_Reportf(" %02d [%d:%d] name=%s\n", j, binding->descriptor_set, binding->binding, binding->name); - } + VkShaderModule module = VK_NULL_HANDLE; + const VkResult result = vkCreateShaderModule(vk_core.device, &smci, NULL, &module); + if (result != VK_SUCCESS) { + gEngine.Con_Printf(S_ERROR "Couldn't load shader %s: error (%d): %s\n", name, result, R_VkResultName(result)); + return VK_NULL_HANDLE; } - Mem_Free(buf); - return true; + SET_DEBUG_NAME(module, VK_OBJECT_TYPE_SHADER_MODULE, name); + return module; } -static void R_VkShaderDestroy(r_vk_shader_t *shader) { - R_VkSpirvFree(&shader->spirv); - vkDestroyShaderModule(vk_core.device, shader->module, NULL); +static VkShaderModule R_VkShaderLoadFromFile(const char *filename) { + fs_offset_t size = 0; + byte* const buf = gEngine.fsapi->LoadFile(filename, &size, false); + + if (!buf) { + gEngine.Con_Printf( S_ERROR "Cannot open shader file \"%s\"\n", filename); + return VK_NULL_HANDLE; + } + + const VkShaderModule module = R_VkShaderLoadFromMem(buf, size, filename); + +finalize: + Mem_Free(buf); + return module; +} + +void R_VkShaderDestroy(VkShaderModule module) { + vkDestroyShaderModule(vk_core.device, module, NULL); } VkPipeline VK_PipelineGraphicsCreate(const vk_pipeline_graphics_create_info_t *ci) @@ -174,16 +166,16 @@ VkPipeline VK_PipelineGraphicsCreate(const vk_pipeline_graphics_create_info_t *c if (ci->num_stages > MAX_STAGES) return VK_NULL_HANDLE; - r_vk_shader_t shaders[MAX_STAGES] = {0}; + VkShaderModule shaders[MAX_STAGES] = {VK_NULL_HANDLE}; for (int i = 0; i < ci->num_stages; ++i) { - if (!R_VkShaderLoad(shaders + i, ci->stages[i].filename)) + if (VK_NULL_HANDLE == (shaders[i] = R_VkShaderLoadFromFile(ci->stages[i].filename))) goto finalize; stage_create_infos[i] = (VkPipelineShaderStageCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = ci->stages[i].stage, - .module = shaders[i].module, + .module = shaders[i], .pSpecializationInfo = ci->stages[i].specialization_info, .pName = "main", }; @@ -191,16 +183,15 @@ VkPipeline VK_PipelineGraphicsCreate(const vk_pipeline_graphics_create_info_t *c XVK_CHECK(vkCreateGraphicsPipelines(vk_core.device, g_pipeline_cache, 1, &gpci, NULL, &pipeline)); finalize: - for (int i = 0; i < ci->num_stages; ++i) { - R_VkShaderDestroy(shaders + i); - } + for (int i = 0; i < ci->num_stages; ++i) + R_VkShaderDestroy(shaders[i]); return pipeline; } VkPipeline VK_PipelineComputeCreate(const vk_pipeline_compute_create_info_t *ci) { - r_vk_shader_t shader = {0}; - if (!R_VkShaderLoad(&shader, ci->shader_filename)) + const VkShaderModule shader = R_VkShaderLoadFromFile(ci->shader_filename); + if (shader == VK_NULL_HANDLE) return VK_NULL_HANDLE; const VkComputePipelineCreateInfo cpci = { @@ -209,7 +200,7 @@ VkPipeline VK_PipelineComputeCreate(const vk_pipeline_compute_create_info_t *ci) .stage = (VkPipelineShaderStageCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_COMPUTE_BIT, - .module = shader.module, + .module = shader, .pName = "main", .pSpecializationInfo = ci->specialization_info, }, @@ -217,7 +208,7 @@ VkPipeline VK_PipelineComputeCreate(const vk_pipeline_compute_create_info_t *ci) VkPipeline pipeline; XVK_CHECK(vkCreateComputePipelines(vk_core.device, VK_NULL_HANDLE, 1, &cpci, NULL, &pipeline)); - R_VkShaderDestroy(&shader); + R_VkShaderDestroy(shader); return pipeline; } @@ -250,12 +241,12 @@ vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_ return ret; } - r_vk_shader_t shaders[MAX_SHADER_STAGES] = {0}; + VkShaderModule shaders[MAX_SHADER_STAGES] = {0}; for (int i = 0; i < create->stages_count; ++i) { const vk_shader_stage_t *const stage = create->stages + i; - if (!R_VkShaderLoad(shaders + i, stage->filename)) + if (VK_NULL_HANDLE == (shaders[i] = R_VkShaderLoadFromFile(stage->filename))) goto destroy_shaders; if (stage->stage == VK_SHADER_STAGE_RAYGEN_BIT_KHR) { @@ -266,7 +257,7 @@ vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_ stages[i] = (VkPipelineShaderStageCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = stage->stage, - .module = shaders[i].module, + .module = shaders[i], .pName = "main", .pSpecializationInfo = stage->specialization_info, }; @@ -329,7 +320,7 @@ vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_ destroy_shaders: for (int i = 0; i < create->stages_count; ++i) - R_VkShaderDestroy(shaders + i); + R_VkShaderDestroy(shaders[i]); if (ret.pipeline == VK_NULL_HANDLE) return ret; diff --git a/ref_vk/vk_pipeline.h b/ref_vk/vk_pipeline.h index 5c7b7814..9abf435f 100644 --- a/ref_vk/vk_pipeline.h +++ b/ref_vk/vk_pipeline.h @@ -3,6 +3,9 @@ #include "vk_core.h" #include "vk_buffer.h" +VkShaderModule R_VkShaderLoadFromMem(const void *ptr, uint32_t size, const char *name); +void R_VkShaderDestroy(VkShaderModule module); + typedef struct { const char *filename; VkShaderStageFlagBits stage; From c6d29213ec0dc88c334f0339a398540a2b4beee6 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 15 Oct 2022 14:28:04 -0700 Subject: [PATCH 458/548] vk: remove spirv parsing --- ref_vk/vk_spirv.c | 438 ---------------------------------------------- ref_vk/vk_spirv.h | 31 ---- 2 files changed, 469 deletions(-) delete mode 100644 ref_vk/vk_spirv.c delete mode 100644 ref_vk/vk_spirv.h diff --git a/ref_vk/vk_spirv.c b/ref_vk/vk_spirv.c deleted file mode 100644 index 9d2c859a..00000000 --- a/ref_vk/vk_spirv.c +++ /dev/null @@ -1,438 +0,0 @@ -#include "vk_spirv.h" -#include "vk_common.h" - -#include -#include -#include -#include -#include - -//#define L(msg, ...) fprintf(stderr, msg "\n",##__VA_ARGS__) -#define L(msg, ...) gEngine.Con_Reportf(msg "\n",##__VA_ARGS__) -#define LE(msg, ...) gEngine.Con_Printf(S_ERROR msg "\n",##__VA_ARGS__) - -#define MALLOC malloc -#define FREE free - -#define CHECK(cond) assert(cond) - -static const char *opTypeName(uint16_t op) { - switch(op) { - case SpvOpVariable: return "SpvOpVariable"; - case SpvOpTypeVoid: return "SpvOpTypeVoid"; - case SpvOpTypeBool: return "SpvOpTypeBool"; - case SpvOpTypeInt: return "SpvOpTypeInt"; - case SpvOpTypeFloat: return "SpvOpTypeFloat"; - case SpvOpTypeVector: return "SpvOpTypeVector"; - case SpvOpTypeMatrix: return "SpvOpTypeMatrix"; - case SpvOpTypeImage: return "SpvOpTypeImage"; - case SpvOpTypeSampler: return "SpvOpTypeSampler"; - case SpvOpTypeSampledImage: return "SpvOpTypeSampledImage"; - case SpvOpTypeArray: return "SpvOpTypeArray"; - case SpvOpTypeRuntimeArray: return "SpvOpTypeRuntimeArray"; - case SpvOpTypeStruct: return "SpvOpTypeStruct"; - case SpvOpTypeOpaque: return "SpvOpTypeOpaque"; - case SpvOpTypePointer: return "SpvOpTypePointer"; - case SpvOpTypeFunction: return "SpvOpTypeFunction"; - case SpvOpTypeEvent: return "SpvOpTypeEvent"; - case SpvOpTypeDeviceEvent: return "SpvOpTypeDeviceEvent"; - case SpvOpTypeReserveId: return "SpvOpTypeReserveId"; - case SpvOpTypeQueue: return "SpvOpTypeQueue"; - case SpvOpTypePipe: return "SpvOpTypePipe"; - case SpvOpTypeForwardPointer: return "SpvOpTypeForwardPointer"; - case SpvOpTypePipeStorage: return "SpvOpTypePipeStorage"; - case SpvOpTypeNamedBarrier: return "SpvOpTypeNamedBarrier"; - case SpvOpTypeRayQueryKHR: return "SpvOpTypeRayQueryKHR"; - case SpvOpTypeAccelerationStructureKHR: return "SpvOpTypeAccelerationStructureKHR"; - case SpvOpTypeCooperativeMatrixNV: return "SpvOpTypeCooperativeMatrixNV"; - default: return "UNKNOWN"; - } -} - -static const char *storageClassName(SpvStorageClass storage_class) { - switch(storage_class) { - case SpvStorageClassUniformConstant: return "SpvStorageClassUniformConstant"; - case SpvStorageClassInput: return "SpvStorageClassInput"; - case SpvStorageClassUniform: return "SpvStorageClassUniform"; - case SpvStorageClassOutput: return "SpvStorageClassOutput"; - case SpvStorageClassWorkgroup: return "SpvStorageClassWorkgroup"; - case SpvStorageClassCrossWorkgroup: return "SpvStorageClassCrossWorkgroup"; - case SpvStorageClassPrivate: return "SpvStorageClassPrivate"; - case SpvStorageClassFunction: return "SpvStorageClassFunction"; - case SpvStorageClassGeneric: return "SpvStorageClassGeneric"; - case SpvStorageClassPushConstant: return "SpvStorageClassPushConstant"; - case SpvStorageClassAtomicCounter: return "SpvStorageClassAtomicCounter"; - case SpvStorageClassImage: return "SpvStorageClassImage"; - case SpvStorageClassStorageBuffer: return "SpvStorageClassStorageBuffer"; - case SpvStorageClassCallableDataKHR: return "SpvStorageClassCallableDataKHR"; - case SpvStorageClassIncomingCallableDataKHR: return "SpvStorageClassIncomingCallableDataKHR"; - case SpvStorageClassRayPayloadKHR: return "SpvStorageClassRayPayloadKHR"; - case SpvStorageClassHitAttributeKHR: return "SpvStorageClassHitAttributeKHR"; - case SpvStorageClassIncomingRayPayloadKHR: return "SpvStorageClassIncomingRayPayloadKHR"; - case SpvStorageClassShaderRecordBufferKHR: return "SpvStorageClassShaderRecordBufferKHR"; - case SpvStorageClassPhysicalStorageBuffer: return "SpvStorageClassPhysicalStorageBuffer"; - case SpvStorageClassCodeSectionINTEL: return "SpvStorageClassCodeSectionINTEL"; - case SpvStorageClassDeviceOnlyINTEL: return "SpvStorageClassDeviceOnlyINTEL"; - case SpvStorageClassHostOnlyINTEL: return "SpvStorageClassHostOnlyINTEL"; - case SpvStorageClassMax: return "SpvStorageClassMax"; - } - return "UNKNOWN"; -} - -static const char *imageFormatName(SpvImageFormat format) { - switch(format) { - case SpvImageFormatUnknown: return "SpvImageFormatUnknown"; - case SpvImageFormatRgba32f: return "SpvImageFormatRgba32f"; - case SpvImageFormatRgba16f: return "SpvImageFormatRgba16f"; - case SpvImageFormatR32f: return "SpvImageFormatR32f"; - case SpvImageFormatRgba8: return "SpvImageFormatRgba8"; - case SpvImageFormatRgba8Snorm: return "SpvImageFormatRgba8Snorm"; - case SpvImageFormatRg32f: return "SpvImageFormatRg32f"; - case SpvImageFormatRg16f: return "SpvImageFormatRg16f"; - case SpvImageFormatR11fG11fB10f: return "SpvImageFormatR11fG11fB10f"; - case SpvImageFormatR16f: return "SpvImageFormatR16f"; - case SpvImageFormatRgba16: return "SpvImageFormatRgba16"; - case SpvImageFormatRgb10A2: return "SpvImageFormatRgb10A2"; - case SpvImageFormatRg16: return "SpvImageFormatRg16"; - case SpvImageFormatRg8: return "SpvImageFormatRg8"; - case SpvImageFormatR16: return "SpvImageFormatR16"; - case SpvImageFormatR8: return "SpvImageFormatR8"; - case SpvImageFormatRgba16Snorm: return "SpvImageFormatRgba16Snorm"; - case SpvImageFormatRg16Snorm: return "SpvImageFormatRg16Snorm"; - case SpvImageFormatRg8Snorm: return "SpvImageFormatRg8Snorm"; - case SpvImageFormatR16Snorm: return "SpvImageFormatR16Snorm"; - case SpvImageFormatR8Snorm: return "SpvImageFormatR8Snorm"; - case SpvImageFormatRgba32i: return "SpvImageFormatRgba32i"; - case SpvImageFormatRgba16i: return "SpvImageFormatRgba16i"; - case SpvImageFormatRgba8i: return "SpvImageFormatRgba8i"; - case SpvImageFormatR32i: return "SpvImageFormatR32i"; - case SpvImageFormatRg32i: return "SpvImageFormatRg32i"; - case SpvImageFormatRg16i: return "SpvImageFormatRg16i"; - case SpvImageFormatRg8i: return "SpvImageFormatRg8i"; - case SpvImageFormatR16i: return "SpvImageFormatR16i"; - case SpvImageFormatR8i: return "SpvImageFormatR8i"; - case SpvImageFormatRgba32ui: return "SpvImageFormatRgba32ui"; - case SpvImageFormatRgba16ui: return "SpvImageFormatRgba16ui"; - case SpvImageFormatRgba8ui: return "SpvImageFormatRgba8ui"; - case SpvImageFormatR32ui: return "SpvImageFormatR32ui"; - case SpvImageFormatRgb10a2ui: return "SpvImageFormatRgb10a2ui"; - case SpvImageFormatRg32ui: return "SpvImageFormatRg32ui"; - case SpvImageFormatRg16ui: return "SpvImageFormatRg16ui"; - case SpvImageFormatRg8ui: return "SpvImageFormatRg8ui"; - case SpvImageFormatR16ui: return "SpvImageFormatR16ui"; - case SpvImageFormatR8ui: return "SpvImageFormatR8ui"; - case SpvImageFormatR64ui: return "SpvImageFormatR64ui"; - case SpvImageFormatR64i: return "SpvImageFormatR64i"; - case SpvImageFormatMax: return "SpvImageFormatMax"; - } - return "UNKNOWN"; -} - -typedef struct { - int id; - const char *name; - int descriptor_set; - int binding; - - // TODO: in-out, type, image format, etc - //uint32_t flags; -} binding_t; - -#define MAX_BINDINGS 32 -typedef struct node_t { - SpvOp op; - int binding; - const char *name; - uint32_t type_id; - uint32_t storage_class; - uint32_t flags; -} node_t; - -typedef struct { - int nodes_count; - node_t *nodes; - - int bindings_count; - binding_t bindings[MAX_BINDINGS]; -} context_t; - -binding_t *getBinding(context_t *ctx, int id) { - if(id < 0) { - LE("id %d < 0", id); - return NULL; - } - - if (id >= ctx->nodes_count) { - LE("id %d > %d", id, ctx->nodes_count); - return NULL; - } - - node_t *sid = ctx->nodes + id; - - if (sid->binding < 0) { - if (ctx->bindings_count >= MAX_BINDINGS) { - LE("too many bindings %d", MAX_BINDINGS); - return NULL; - } - - sid->binding = ctx->bindings_count++; - ctx->bindings[sid->binding].id = id; - } - - return ctx->bindings + sid->binding; -} - -static qboolean spvParseOp(context_t *ctx, uint16_t op, uint16_t word_count, const uint32_t *args) { - switch (op) { - case SpvOpName: - { - // FIXME check size, check strlen - const uint32_t id = args[0]; - const char *name = (const char*)(args + 1); - //L("OpName(id=%d) => %s", id, name); - ctx->nodes[id].name = name[0] != '\0' ? name : NULL; - break; - } - case SpvOpMemberName: - { - // FIXME check size, check strlen - const uint32_t id = args[0]; - const uint32_t index = args[1]; - const char *name = (const char*)(args + 2); - //L("OpMemberName(id=%d) => index=%d %s", id, index, name); - //ctx->nodes[id].name = name; - break; - } - case SpvOpTypeImage: - { - // FIXME check size check strlen - const uint32_t result_id = args[0]; - const uint32_t type_id = args[1]; - const uint32_t dim = args[2]; - const uint32_t depth = args[3]; - const uint32_t arrayed = args[4]; - const uint32_t ms = args[5]; - const uint32_t sampled = args[6]; - const uint32_t format = args[7]; - node_t *node = ctx->nodes + result_id; - node->op = op; - node->type_id = type_id; - //L("OpTypeImage(id=%d) => type_id=%d dim=%08x depth=%d arrayed=%d ms=%d sampled=%d format=%s(%d)", - //result_id, type_id, dim, depth, arrayed, ms, sampled,imageFormatName(format), format); - break; - } - case SpvOpTypePointer: - { - // FIXME check size - const uint32_t id = args[0]; - const uint32_t storage_class = args[1]; - const uint32_t type_id = args[2]; - node_t *node = ctx->nodes + id; - node->op = op; - node->type_id = type_id; - node->storage_class = storage_class; - //L("OpTypePointer(id=%d) => storage_class=%d type_id=%d", id, storage_class, type_id); - //ctx->nodes[id].name = name; - break; - } - case SpvOpVariable: - { - const uint32_t type_id = args[0]; - const uint32_t result_id = args[1]; - const uint32_t storage_class = args[2]; - node_t *node = ctx->nodes + result_id; - node->op = op; - node->type_id = type_id; - node->storage_class = storage_class; - //L("OpVariable(id=%d) => type_id=%d storage_class=%d", result_id, type_id, storage_class); - break; - } - case SpvOpMemberDecorate: - { - const uint32_t id = args[0]; - const uint32_t member_index = args[1]; - const uint32_t decor = args[2]; - //L("OpMemberDecorate(id=%d) => member=%d decor=%d ...", id, member_index, decor); - break; - } - case SpvOpDecorate: - { - const uint32_t id = args[0]; - // FIXME check size - const uint32_t decor = args[1]; - node_t *node = ctx->nodes + id; - switch (decor) { - case SpvDecorationDescriptorSet: - { - const uint32_t ds = args[2]; - //L("OpDecorate(id=%d) => DescriptorSet = %d ", id, ds); - binding_t *binding = getBinding(ctx, id); - if (!binding) - return false; - binding->descriptor_set = ds; - break; - } - - case SpvDecorationBinding: - { - const uint32_t binding = args[2]; - //L("OpDecorate(id=%d) => Binding = %d ", id, binding); - binding_t *b = getBinding(ctx, id); - if (!b) - return false; - b->binding = binding; - break; - } - - case SpvDecorationNonWritable: - { - //node->flags &= Flag_NonWritable; - //L("OpDecorate(id=%d) => NonWriteable", id); - break; - } - - case SpvDecorationNonReadable: - { - //node->flags &= Flag_NonReadable; - //L("OpDecorate(id=%d) => NonReadable", id); - break; - } - - default: - break; - //L("OpDecorate(id=%d) => %d ... ", id, decor); - } - - break; - } - - default: - if (word_count > 1 && (int)args[1] < ctx->nodes_count) { - const uint32_t id = args[1]; - //L("op=%d word_count=%d guessed dest_id=%d", op, word_count, id); - } else { - //L("op=%d word_count=%d", op, word_count); - } - } - return true; -} - -qboolean parseHeader(context_t *ctx, const uint32_t *data, int n) { - const uint32_t magic = data[0]; - //L("magic = %#08x", magic); - if (magic != SpvMagicNumber) - return false; - - const uint32_t version = data[1]; - //L("version = %#08x(%d.%d)", version, (version>>16)&0xff, (version>>8)&0xff); - - //L("generator magic = %#08x", data[2]); - - ctx->nodes_count = data[3]; - //L("nodes_count = %d", ctx->nodes_count); - - ctx->nodes = MALLOC(ctx->nodes_count * sizeof(*ctx->nodes)); - - for (int i = 0; i < ctx->nodes_count; ++i) { - ctx->nodes[i].binding = -1; - ctx->nodes[i].op = SpvOpMax; - ctx->nodes[i].type_id = -1; - ctx->nodes[i].name = NULL; - ctx->nodes[i].storage_class = -1; - ctx->nodes[i].flags = 0; - } - - return true; -} - -qboolean processBindings(context_t *ctx, vk_spirv_t *out) { - for (int i = 0; i < ctx->bindings_count; ++i) { - binding_t *binding = ctx->bindings + i; - //L("%02d [%d:%d] id=%d name=%s", i, binding->descriptor_set, binding->binding, binding->id, binding->name); - { - const node_t *node = ctx->nodes + binding->id; - for (;;) { - //L(" op=%s(%d) type_id=%d name=%s", opTypeName(node->op), node->op, node->type_id, node->name ? node->name : "N/A"); - - if ((int)node->storage_class >= 0) { - //L(" storage_class=%s", storageClassName(node->storage_class)); - } - - if (!binding->name && node->name) - binding->name = node->name; - - //binding->flags |= node->flags; - - if ((int)node->type_id == -1) - break; - node = ctx->nodes + node->type_id; - } - } - } - - out->bindings_count = ctx->bindings_count; - out->bindings = MALLOC(sizeof(vk_binding_t) * out->bindings_count); - - for (int i = 0; i < ctx->bindings_count; ++i) { - const binding_t *src = ctx->bindings + i; - vk_binding_t *dst = out->bindings + i; - - dst->binding = src->binding; - dst->descriptor_set = src->descriptor_set; - dst->name = strdup(src->name); - } - - return true; -} - -qboolean R_VkSpirvParse(vk_spirv_t *out, const uint32_t *data, int n) { - if (n < 5) { - return false; - } - - context_t ctx = {0}; - - if (!parseHeader(&ctx, data, n)) - return false; - - for (int i = 0; i < MAX_BINDINGS; ++i) { - ctx.bindings[i].id = -1; - ctx.bindings[i].descriptor_set = -1; - ctx.bindings[i].binding = -1; - } - - qboolean ret = false; - for (size_t i = 5; i < n; ++i) { - const uint16_t op_code = data[i] & SpvOpCodeMask; - const uint16_t word_count = data[i] >> SpvWordCountShift; - - if (word_count > (n-i)) - goto cleanup; - - if (!spvParseOp(&ctx, op_code, word_count, data + i + 1)) - goto cleanup; - - i += word_count-1; - } - - if (!processBindings(&ctx, out)) - goto cleanup; - - ret = true; - -cleanup: - if (ctx.nodes) - FREE(ctx.nodes); - return ret; -} - -void R_VkSpirvFree(vk_spirv_t *spirv) { - for (int i = 0; i < spirv->bindings_count; ++i) { - vk_binding_t *bind = spirv->bindings + i; - if (bind->name) - FREE(bind->name); - } - - FREE(spirv->bindings); - memset(spirv, 0, sizeof(*spirv)); -} diff --git a/ref_vk/vk_spirv.h b/ref_vk/vk_spirv.h deleted file mode 100644 index f9db6251..00000000 --- a/ref_vk/vk_spirv.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include - -#include "xash3d_types.h" - -// enum { -// Flag_NonWritable = (1<<0), -// Flag_NonReadable = (1<<1), -// }; - -typedef struct { - char *name; - int descriptor_set; - int binding; - - // TODO: in-out, type, image format, etc - //uint32_t flags; -} vk_binding_t; - -typedef struct { - vk_binding_t *bindings; - int bindings_count; - - // TODO: - // - push constants - // - specialization - // - types -} vk_spirv_t; - -qboolean R_VkSpirvParse(vk_spirv_t *out, const uint32_t *instructions, int instructions_count); -void R_VkSpirvFree(vk_spirv_t *spirv); From f2f712b1a864947766f2285403e8d5604c14a5ee Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 15 Oct 2022 14:32:51 -0700 Subject: [PATCH 459/548] vk: fixup meatpipe error messages --- ref_vk/vk_meatpipe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index f6fd0bbb..e134f4b9 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -28,7 +28,7 @@ const void* curReadPtr(cursor_t *cur, int size) { #define CUR_ERROR(errmsg, ...) \ if (cur.error) { \ - gEngine.Con_Printf(S_ERROR "(off=%d left=%d) " errmsg "\n", #__VA_ARGS__); \ + gEngine.Con_Printf(S_ERROR "(off=%d left=%d) " errmsg "\n", cur.off, (cur.size - cur.off), #__VA_ARGS__); \ goto finalize; \ } From cb0d09557dd25a56ef37a900acc904424526685c Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 15 Oct 2022 15:09:45 -0700 Subject: [PATCH 460/548] meat: stub parsing pipelines --- ref_vk/vk_meatpipe.c | 45 ++++++++++++++++++++++++++++++++++++++++++-- ref_vk/vk_rtx.c | 23 ++++++++++++++-------- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index e134f4b9..ff78f687 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -33,7 +33,7 @@ const void* curReadPtr(cursor_t *cur, int size) { } #define READ_PTR(size, errmsg, ...) \ - curReadPtr(&cur, size); CUR_ERROR(errmsg, #__VA_ARGS__) + curReadPtr(&cur, size); CUR_ERROR(errmsg, ##__VA_ARGS__) uint32_t curReadU32(cursor_t *cur) { const void *src = curReadPtr(cur, sizeof(uint32_t)); @@ -46,7 +46,7 @@ uint32_t curReadU32(cursor_t *cur) { } #define READ_U32(errmsg, ...) \ - curReadU32(&cur); CUR_ERROR(errmsg, #__VA_ARGS__) + curReadU32(&cur); CUR_ERROR(errmsg, ##__VA_ARGS__) qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename) { qboolean ret = false; @@ -81,6 +81,47 @@ qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename) { gEngine.Con_Reportf("%d: loaded %s into %p\n", i, name, shaders[i]); } + const int pipelines_count = READ_U32("Couldn't read pipelines count"); + for (int i = 0; i < pipelines_count; ++i) { + const uint32_t head = READ_U32("Couldn't read pipeline %d head", i); + const int name_len = READ_U32("Coulnd't read pipeline %d name len", i); + const char *name_src = READ_PTR(name_len, "Couldn't read pipeline %d name", i); + char name[64]; +#define MIN(a,b) ((a)<(b)?(a):(b)) + const int name_max = MIN(sizeof(name)-1, name_len); + memcpy(name, name_src, name_max); + name[name_max] = '\0'; + gEngine.Con_Reportf("%d: loading pipeline %s\n", i, name); + +#define PIPELINE_COMPUTE 1 +#define PIPELINE_RAYTRACING 2 +#define NO_SHADER 0xffffffff + + switch (head) { + case PIPELINE_COMPUTE: + { + const uint32_t shader_comp = READ_U32("Couldn't read comp shader for %d %s", i, name); + break; + } + + case PIPELINE_RAYTRACING: + { + const uint32_t shader_rgen = READ_U32("Couldn't read rgen shader for %d %s", i, name); + const int miss_count = READ_U32("Couldn't read miss count for %d %s", i, name); + for (int j = 0; j < miss_count; ++j) { + const uint32_t shader_miss = READ_U32("Couldn't read miss shader %d for %d %s", j, i, name); + } + + const int hit_count = READ_U32("Couldn't read hit count for %d %s", i, name); + for (int j = 0; j < hit_count; ++j) { + const uint32_t closest = READ_U32("Couldn't read closest shader %d for %d %s", j, i, name); + const uint32_t any = READ_U32("Couldn't read any shader %d for %d %s", j, i, name); + } + break; + } + } + } + ret = true; finalize: for (int i = 0; i < shaders_count; ++i) { diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 2bd40522..6e842934 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -7,18 +7,19 @@ #include "vk_ray_primary.h" #include "vk_ray_light_direct.h" -#include "vk_core.h" -#include "vk_common.h" #include "vk_buffer.h" -#include "vk_pipeline.h" -#include "vk_staging.h" +#include "vk_common.h" +#include "vk_core.h" #include "vk_cvar.h" -#include "vk_textures.h" -#include "vk_light.h" -#include "vk_descriptor.h" -#include "vk_ray_internal.h" #include "vk_denoiser.h" +#include "vk_descriptor.h" +#include "vk_light.h" #include "vk_math.h" +#include "vk_meatpipe.h" +#include "vk_pipeline.h" +#include "vk_ray_internal.h" +#include "vk_staging.h" +#include "vk_textures.h" #include "alolcator.h" @@ -86,6 +87,8 @@ static struct { struct ray_pass_s *denoiser; } pass; + vk_meatpipe_t mainpipe; + qboolean reload_pipeline; qboolean reload_lighting; } g_rtx = {0}; @@ -391,6 +394,8 @@ qboolean VK_RayInit( void ) g_rtx.pass.denoiser = R_VkRayDenoiserCreate(); ASSERT(g_rtx.pass.denoiser); + ASSERT(R_VkMeatpipeLoad(&g_rtx.mainpipe, "rt.meat")); + g_rtx.uniform_unit_size = ALIGN_UP(sizeof(struct UniformBuffer), vk_core.physical_device.properties.limits.minUniformBufferOffsetAlignment); if (!VK_BufferCreate("ray uniform_buffer", &g_rtx.uniform_buffer, g_rtx.uniform_unit_size * MAX_FRAMES_IN_FLIGHT, @@ -459,6 +464,8 @@ qboolean VK_RayInit( void ) void VK_RayShutdown( void ) { ASSERT(vk_core.rtx); + R_VkMeatpipeDestroy(&g_rtx.mainpipe); + RayPassDestroy(g_rtx.pass.denoiser); RayPassDestroy(g_rtx.pass.light_direct_poly); RayPassDestroy(g_rtx.pass.light_direct_point); From cb406f14c5f034d3b55f50680929ce1eb560ef58 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 16 Oct 2022 23:26:59 -0700 Subject: [PATCH 461/548] meat: fixup build on linux --- ref_vk/vk_meatpipe.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index ff78f687..ba4aac42 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -60,28 +60,35 @@ qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename) { cursor_t cur = { .data = buf, .off = 0, .size = size }; - const uint32_t magic = READ_U32("Couldn't read magic"); + int shaders_count = 0; + VkShaderModule *shaders = NULL; + int pipelines_count = 0; - if (magic != k_meatpipe_magic) { - gEngine.Con_Printf(S_ERROR "Meatpipe magic invalid for \"%s\": got %08x expected %08x\n", filename, magic, k_meatpipe_magic); - goto finalize; + { + const uint32_t magic = READ_U32("Couldn't read magic"); + + if (magic != k_meatpipe_magic) { + gEngine.Con_Printf(S_ERROR "Meatpipe magic invalid for \"%s\": got %08x expected %08x\n", filename, magic, k_meatpipe_magic); + goto finalize; + } } - const int shaders_count = READ_U32("Couldn't read shaders count"); - VkShaderModule *shaders = Mem_Malloc(vk_core.pool, sizeof(VkShaderModule) * shaders_count); + shaders_count = READ_U32("Couldn't read shaders count"); + shaders = Mem_Malloc(vk_core.pool, sizeof(VkShaderModule) * shaders_count); for (int i = 0; i < shaders_count; ++i) shaders[i] = VK_NULL_HANDLE; for (int i = 0; i < shaders_count; ++i) { char name[256]; Q_snprintf(name, sizeof(name), "%s@%d", filename, i); // TODO serialize origin name + const int size = READ_U32("Couldn't read shader %s size", name); const void *src = READ_PTR(size, "Couldn't read shader %s data", name); shaders[i] = R_VkShaderLoadFromMem(src, size, name); gEngine.Con_Reportf("%d: loaded %s into %p\n", i, name, shaders[i]); } - const int pipelines_count = READ_U32("Couldn't read pipelines count"); + pipelines_count = READ_U32("Couldn't read pipelines count"); for (int i = 0; i < pipelines_count; ++i) { const uint32_t head = READ_U32("Couldn't read pipeline %d head", i); const int name_len = READ_U32("Coulnd't read pipeline %d name len", i); @@ -129,6 +136,10 @@ finalize: R_VkShaderDestroy(shaders[i]); } } + + if (shaders) + Mem_Free(shaders); + Mem_Free(buf); return ret; } From ecac9a1f4880c028df7c1f64df5fc8e36602b33f Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 16 Oct 2022 23:44:13 -0700 Subject: [PATCH 462/548] vk: compile w/ -Werror, fix a bunch of bad stuff --- ref_vk/vk_beams.c | 2 +- ref_vk/vk_common.h | 2 +- ref_vk/vk_light.c | 2 +- ref_vk/vk_mapents.c | 9 +++++---- ref_vk/vk_meatpipe.c | 2 +- ref_vk/vk_pipeline.c | 2 +- ref_vk/vk_ray_accel.c | 2 +- ref_vk/vk_studio.c | 2 +- ref_vk/wscript | 4 +++- 9 files changed, 15 insertions(+), 12 deletions(-) diff --git a/ref_vk/vk_beams.c b/ref_vk/vk_beams.c index 81440c05..6a627981 100644 --- a/ref_vk/vk_beams.c +++ b/ref_vk/vk_beams.c @@ -161,7 +161,7 @@ static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, f float div, length, fraction, factor; float flMaxWidth, vLast, vStep, brightness; vec3_t perp1, vLastNormal; - beamseg_t curSeg; + beamseg_t curSeg = {0}; int total_vertices = 0; int total_indices = 0; r_geometry_buffer_lock_t buffer; diff --git a/ref_vk/vk_common.h b/ref_vk/vk_common.h index bd7c9966..053b6d54 100644 --- a/ref_vk/vk_common.h +++ b/ref_vk/vk_common.h @@ -6,7 +6,7 @@ #include "com_strings.h" #include "crtlib.h" -#define ASSERT(x) if(!( x )) gEngine.Host_Error( "assert " #x " failed at %s:%i\n", __FILE__, __LINE__ ) +#define ASSERT(x) if(!( x )) gEngine.Host_Error( "assert %s failed at %s:%d\n", #x, __FILE__, __LINE__ ) #define Mem_Malloc( pool, size ) gEngine._Mem_Alloc( pool, size, false, __FILE__, __LINE__ ) #define Mem_Calloc( pool, size ) gEngine._Mem_Alloc( pool, size, true, __FILE__, __LINE__ ) diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 413bde42..cb9aa086 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -779,7 +779,7 @@ void RT_LightAddFlashlight(const struct cl_entity_s *ent, qboolean local_player vec3_t color; vec3_t origin; vec3_t angles; - vk_light_entity_t le; + vk_light_entity_t le = { .type = LightTypeSpot }; float thirdperson_offset = 25; vec3_t forward, view_ofs; diff --git a/ref_vk/vk_mapents.c b/ref_vk/vk_mapents.c index f7240c87..a8489937 100644 --- a/ref_vk/vk_mapents.c +++ b/ref_vk/vk_mapents.c @@ -82,7 +82,7 @@ static unsigned parseEntPropString(const char* value, string *out, unsigned bit) const int len = Q_strlen(value); if (len >= sizeof(string)) gEngine.Con_Printf(S_ERROR "Map entity value '%s' is too long, max length is %d\n", - value, sizeof(string)); + value, (int)sizeof(string)); Q_strncpy(*out, value, sizeof(*out)); return bit; } @@ -380,14 +380,15 @@ int findLightEntityWithIndex( int index ) { static void addPatchEntity( const entity_props_t *props, uint32_t have_fields ) { for (int i = 0; i < props->_xvk_ent_id.num; ++i) { - const int light_index = findLightEntityWithIndex( props->_xvk_ent_id.values[i] ); + const int ent_id = props->_xvk_ent_id.values[i]; + const int light_index = findLightEntityWithIndex( ent_id ); if (light_index < 0) { - gEngine.Con_Printf(S_ERROR "Patch light entity with index=%d not found\n", props->_xvk_ent_id); + gEngine.Con_Printf(S_ERROR "Patch light entity with index=%d not found\n", ent_id); continue; } if (have_fields == Field__xvk_ent_id) { - gEngine.Con_Reportf("Deleting light entity (%d of %d) with index=%d\n", light_index, g_map_entities.num_lights, props->_xvk_ent_id); + gEngine.Con_Reportf("Deleting light entity (%d of %d) with index=%d\n", light_index, g_map_entities.num_lights, ent_id); g_map_entities.num_lights--; memmove(g_map_entities.lights + light_index, g_map_entities.lights + light_index + 1, sizeof(*g_map_entities.lights) * g_map_entities.num_lights - light_index); continue; diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index ba4aac42..e178e49a 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -28,7 +28,7 @@ const void* curReadPtr(cursor_t *cur, int size) { #define CUR_ERROR(errmsg, ...) \ if (cur.error) { \ - gEngine.Con_Printf(S_ERROR "(off=%d left=%d) " errmsg "\n", cur.off, (cur.size - cur.off), #__VA_ARGS__); \ + gEngine.Con_Printf(S_ERROR "(off=%d left=%d) " errmsg "\n", cur.off, (cur.size - cur.off), ##__VA_ARGS__); \ goto finalize; \ } diff --git a/ref_vk/vk_pipeline.c b/ref_vk/vk_pipeline.c index f802e498..3e48c00f 100644 --- a/ref_vk/vk_pipeline.c +++ b/ref_vk/vk_pipeline.c @@ -27,7 +27,7 @@ void VK_PipelineShutdown( void ) VkShaderModule R_VkShaderLoadFromMem(const void *ptr, uint32_t size, const char *name) { if ((size % 4 != 0) || (((uintptr_t)ptr & 3) != 0)) { - gEngine.Con_Printf(S_ERROR "Couldn't load shader %s: size %zu or buf %p is not aligned to 4 bytes as required by SPIR-V/Vulkan spec\n", name, size, ptr); + gEngine.Con_Printf(S_ERROR "Couldn't load shader %s: size %u or buf %p is not aligned to 4 bytes as required by SPIR-V/Vulkan spec\n", name, size, ptr); return VK_NULL_HANDLE; } diff --git a/ref_vk/vk_ray_accel.c b/ref_vk/vk_ray_accel.c index 2ab5772b..da6638a9 100644 --- a/ref_vk/vk_ray_accel.c +++ b/ref_vk/vk_ray_accel.c @@ -81,7 +81,7 @@ qboolean createOrUpdateAccelerationStructure(VkCommandBuffer cmdbuf, const as_bu }; if (buffer_offset == ALO_ALLOC_FAILED) { - gEngine.Con_Printf(S_ERROR "Failed to allocated %u bytes for accel buffer\n", asci.size); + gEngine.Con_Printf(S_ERROR "Failed to allocated %u bytes for accel buffer\n", (uint32_t)asci.size); return false; } diff --git a/ref_vk/vk_studio.c b/ref_vk/vk_studio.c index 10865960..72b9ffc4 100644 --- a/ref_vk/vk_studio.c +++ b/ref_vk/vk_studio.c @@ -39,7 +39,7 @@ typedef struct cvar_t *r_glowshellfreq; -cvar_t r_shadows = { "r_shadows", "0", 0 }; +cvar_t r_shadows = { (char*)"r_shadows", (char*)"0", 0 }; typedef struct sortedmesh_s { diff --git a/ref_vk/wscript b/ref_vk/wscript index 2d5daec8..4465bc5a 100644 --- a/ref_vk/wscript +++ b/ref_vk/wscript @@ -100,7 +100,7 @@ def build(bld): if bld.env.DEST_OS == 'win32': includes.append(bld.env.VULKAN_SDK + '\\Include') - includes.append(bld.env.VULKAN_SDK + '\\Source\SPIRV-Reflect\include') # for spirv.h + includes.append(bld.env.VULKAN_SDK + '\\Source\SPIRV-Reflect\include') # for spirv.h if bld.env.HAVE_AFTERMATH: defines.append('USE_AFTERMATH') @@ -112,6 +112,8 @@ def build(bld): if bld.env.COMPILER_CC == 'msvc': bld.env.CFLAGS += ['/WX'] + else: + bld.env.CFLAGS += ['-Werror'] # TODO , '-Wall'] bld.shlib( source = source, From 7f8098512477f104c2c4622e3b279ac12885f3be Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 16 Oct 2022 23:51:59 -0700 Subject: [PATCH 463/548] vk: fixup swapchain printf --- ref_vk/vk_swapchain.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_vk/vk_swapchain.c b/ref_vk/vk_swapchain.c index dab6d5e9..611bd3a6 100644 --- a/ref_vk/vk_swapchain.c +++ b/ref_vk/vk_swapchain.c @@ -216,7 +216,7 @@ r_vk_swapchain_framebuffer_t R_VkSwapchainAcquire( VkSemaphore sem_image_availa force_recreate = true; continue; } - gEngine.Con_Printf(S_WARN "second vkAcquireNextImageKHR failed, frame will be lost\n", R_VkResultName(acquire_result)); + gEngine.Con_Printf(S_WARN "second vkAcquireNextImageKHR failed with %s, frame will be lost\n", R_VkResultName(acquire_result)); return ret; } From 141cc5de6445c6079c598333ed47e632c016727f Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Mon, 17 Oct 2022 00:01:48 -0700 Subject: [PATCH 464/548] vk: silence "may be used uninitialized" warning --- ref_vk/vk_beams.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_vk/vk_beams.c b/ref_vk/vk_beams.c index 6a627981..d1438b1d 100644 --- a/ref_vk/vk_beams.c +++ b/ref_vk/vk_beams.c @@ -160,7 +160,7 @@ static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, f int i, total_segs, segs_drawn; float div, length, fraction, factor; float flMaxWidth, vLast, vStep, brightness; - vec3_t perp1, vLastNormal; + vec3_t perp1, vLastNormal = {0}; beamseg_t curSeg = {0}; int total_vertices = 0; int total_indices = 0; From ca296d7a4e22591303f44fb2e0707985f55177b2 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Mon, 17 Oct 2022 00:14:37 -0700 Subject: [PATCH 465/548] meat: remove extra debug log param --- ref_vk/vk_meatpipe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index e178e49a..9ab6316f 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -85,7 +85,7 @@ qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename) { const int size = READ_U32("Couldn't read shader %s size", name); const void *src = READ_PTR(size, "Couldn't read shader %s data", name); shaders[i] = R_VkShaderLoadFromMem(src, size, name); - gEngine.Con_Reportf("%d: loaded %s into %p\n", i, name, shaders[i]); + gEngine.Con_Reportf("%d: loaded %s\n", i, name); } pipelines_count = READ_U32("Couldn't read pipelines count"); From b63c328ed542f69f300a925bbfd6869d58f9094e Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Mon, 17 Oct 2022 08:35:10 -0700 Subject: [PATCH 466/548] vk: update % for long-long on 32 bit linux --- ref_vk/vk_devmem.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ref_vk/vk_devmem.c b/ref_vk/vk_devmem.c index 8690a66e..d6cdeec9 100644 --- a/ref_vk/vk_devmem.c +++ b/ref_vk/vk_devmem.c @@ -67,8 +67,8 @@ static int allocateDeviceMemory(VkMemoryRequirements req, int type_index, VkMemo }; if (g_vk_devmem.verbose) { - gEngine.Con_Reportf("allocateDeviceMemory size=%zu memoryTypeBits=0x%x allocate_flags=0x%x => typeIndex=%d\n", - mai.allocationSize, req.memoryTypeBits, + gEngine.Con_Reportf("allocateDeviceMemory size=%llu memoryTypeBits=0x%x allocate_flags=0x%x => typeIndex=%d\n", + (unsigned long long)mai.allocationSize, req.memoryTypeBits, allocate_flags, mai.memoryTypeIndex); } @@ -101,8 +101,8 @@ vk_devmem_t VK_DevMemAllocate(const char *name, VkMemoryRequirements req, VkMemo const int type_index = findMemoryWithType(req.memoryTypeBits, prop_flags); if (g_vk_devmem.verbose) { - gEngine.Con_Reportf("VK_DevMemAllocate name=\"%s\" size=%zu alignment=%zu memoryTypeBits=0x%x prop_flags=%c%c%c%c%c allocate_flags=0x%x => type_index=%d\n", - name, req.size, req.alignment, req.memoryTypeBits, + gEngine.Con_Reportf("VK_DevMemAllocate name=\"%s\" size=%llu alignment=%llu memoryTypeBits=0x%x prop_flags=%c%c%c%c%c allocate_flags=0x%x => type_index=%d\n", + name, (unsigned long long)req.size, (unsigned long long)req.alignment, req.memoryTypeBits, prop_flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT ? 'D' : '.', prop_flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT ? 'V' : '.', prop_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ? 'C' : '.', From d4114b842357f95034551566fb6384edbdf6b7cd Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 22 Oct 2022 13:04:00 -0700 Subject: [PATCH 467/548] meat: load pipelines from meat - added shader_module alongside shader filenames to various things from ray pass to pipelines and stages - this broke unconditional deletion of shader modules, because now lifetime is controlled from outside. this is commented out - layout data is passed to meatpipe as extern for known string names of pipelines. this will be fixed when we start pasing bindings properly these pipelines aren't used yet. --- ref_vk/ray_pass.c | 30 ++++- ref_vk/ray_pass.h | 14 ++- ref_vk/vk_denoiser.c | 14 ++- ref_vk/vk_meatpipe.c | 224 ++++++++++++++++++++++++++--------- ref_vk/vk_meatpipe.h | 4 +- ref_vk/vk_pipeline.c | 11 +- ref_vk/vk_pipeline.h | 2 + ref_vk/vk_ray_light_direct.c | 28 +++-- ref_vk/vk_ray_primary.c | 14 ++- 9 files changed, 245 insertions(+), 96 deletions(-) diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c index 9c9a89e3..64cec307 100644 --- a/ref_vk/ray_pass.c +++ b/ref_vk/ray_pass.c @@ -99,13 +99,15 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create }; stages[stage_index++] = (vk_shader_stage_t) { + .module = create->raygen_module, .filename = create->raygen, .stage = VK_SHADER_STAGE_RAYGEN_BIT_KHR, .specialization_info = &spec, }; for (int i = 0; i < create->miss_count; ++i) { - const ray_pass_shader_t *const shader = create->miss + i; + const char* shader_filename = create->miss ? create->miss[i] : NULL; + const VkShaderModule shader_module = create->miss_module ? create->miss_module[i] : VK_NULL_HANDLE; ASSERT(stage_index < MAX_STAGES); ASSERT(miss_index < MAX_MISS_GROUPS); @@ -114,7 +116,8 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create // TODO really, there should be a global table of shader modules as some of them are used across several passes (e.g. any hit alpha test) misses[miss_index++] = stage_index; stages[stage_index++] = (vk_shader_stage_t) { - .filename = *shader, + .module = shader_module, + .filename = shader_filename, .stage = VK_SHADER_STAGE_MISS_BIT_KHR, .specialization_info = &spec, }; @@ -126,7 +129,16 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create ASSERT(hit_index < MAX_HIT_GROUPS); // TODO handle duplicate filenames - if (group->any) { + if (group->any_module) { + ASSERT(stage_index < MAX_STAGES); + hits[hit_index].any = stage_index; + stages[stage_index++] = (vk_shader_stage_t) { + .module = group->any_module, + .filename = NULL, + .stage = VK_SHADER_STAGE_ANY_HIT_BIT_KHR, + .specialization_info = &spec, + }; + } if (group->any) { ASSERT(stage_index < MAX_STAGES); hits[hit_index].any = stage_index; stages[stage_index++] = (vk_shader_stage_t) { @@ -138,7 +150,16 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create hits[hit_index].any = -1; } - if (group->closest) { + if (group->closest_module) { + ASSERT(stage_index < MAX_STAGES); + hits[hit_index].closest = stage_index; + stages[stage_index++] = (vk_shader_stage_t) { + .module = group->closest_module, + .filename = NULL, + .stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, + .specialization_info = &spec, + }; + } if (group->closest) { ASSERT(stage_index < MAX_STAGES); hits[hit_index].closest = stage_index; stages[stage_index++] = (vk_shader_stage_t) { @@ -182,6 +203,7 @@ struct ray_pass_s *RayPassCreateCompute( const ray_pass_create_compute_t *create const vk_pipeline_compute_create_info_t pcci = { .layout = header->desc.riptors.pipeline_layout, + .shader_module = create->shader_module, .shader_filename = create->shader, .specialization_info = create->specialization, }; diff --git a/ref_vk/ray_pass.h b/ref_vk/ray_pass.h index fe2baef3..c105acad 100644 --- a/ref_vk/ray_pass.h +++ b/ref_vk/ray_pass.h @@ -16,6 +16,7 @@ typedef struct { struct ray_pass_s; +typedef struct ray_pass_s* ray_pass_p; typedef const char* ray_pass_shader_t; @@ -23,7 +24,8 @@ typedef struct { const char *debug_name; ray_pass_layout_t layout; - ray_pass_shader_t shader; + ray_pass_shader_t shader; // FIXME remove + VkShaderModule shader_module; const VkSpecializationInfo *specialization; } ray_pass_create_compute_t; @@ -31,17 +33,21 @@ struct ray_pass_s *RayPassCreateCompute( const ray_pass_create_compute_t *create typedef struct { - ray_pass_shader_t closest; - ray_pass_shader_t any; + ray_pass_shader_t closest; // FIXME remove + VkShaderModule closest_module; + ray_pass_shader_t any; // FIXME remove + VkShaderModule any_module; } ray_pass_hit_group_t; typedef struct { const char *debug_name; ray_pass_layout_t layout; - ray_pass_shader_t raygen; + ray_pass_shader_t raygen; // FIXME remove + VkShaderModule raygen_module; const ray_pass_shader_t *miss; + VkShaderModule *miss_module; int miss_count; const ray_pass_hit_group_t *hit; diff --git a/ref_vk/vk_denoiser.c b/ref_vk/vk_denoiser.c index d3ac9ece..c03bc836 100644 --- a/ref_vk/vk_denoiser.c +++ b/ref_vk/vk_denoiser.c @@ -38,15 +38,17 @@ static const int semantics[] = { #undef OUT }; +const ray_pass_layout_t denoiser_layout_fixme = { + .bindings = bindings, + .bindings_semantics = semantics, + .bindings_count = COUNTOF(bindings), + .push_constants = {0}, +}; + struct ray_pass_s *R_VkRayDenoiserCreate( void ) { const ray_pass_create_compute_t rpcc = { .debug_name = "denoiser", - .layout = { - .bindings = bindings, - .bindings_semantics = semantics, - .bindings_count = COUNTOF(bindings), - .push_constants = {0}, - }, + .layout = denoiser_layout_fixme, .shader = "denoiser.comp.spv", .specialization = NULL, }; diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 9ab6316f..790d12fb 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -14,6 +14,12 @@ typedef struct { qboolean error; } cursor_t; +typedef struct { + cursor_t cur; + int shaders_count; + VkShaderModule *shaders; +} load_context_t; + const void* curReadPtr(cursor_t *cur, int size) { const int left = cur->size - cur->off; if (left < size) { @@ -27,13 +33,13 @@ const void* curReadPtr(cursor_t *cur, int size) { } #define CUR_ERROR(errmsg, ...) \ - if (cur.error) { \ - gEngine.Con_Printf(S_ERROR "(off=%d left=%d) " errmsg "\n", cur.off, (cur.size - cur.off), ##__VA_ARGS__); \ + if (ctx->cur.error) { \ + gEngine.Con_Printf(S_ERROR "(off=%d left=%d) " errmsg "\n", ctx->cur.off, (ctx->cur.size - ctx->cur.off), ##__VA_ARGS__); \ goto finalize; \ } #define READ_PTR(size, errmsg, ...) \ - curReadPtr(&cur, size); CUR_ERROR(errmsg, ##__VA_ARGS__) + curReadPtr(&ctx->cur, size); CUR_ERROR(errmsg, ##__VA_ARGS__) uint32_t curReadU32(cursor_t *cur) { const void *src = curReadPtr(cur, sizeof(uint32_t)); @@ -46,7 +52,128 @@ uint32_t curReadU32(cursor_t *cur) { } #define READ_U32(errmsg, ...) \ - curReadU32(&cur); CUR_ERROR(errmsg, ##__VA_ARGS__) + curReadU32(&ctx->cur); CUR_ERROR(errmsg, ##__VA_ARGS__) + +#define NO_SHADER 0xffffffff + +extern const ray_pass_layout_t ray_primary_layout_fixme; +extern const ray_pass_layout_t light_direct_poly_layout_fixme; +extern const ray_pass_layout_t light_direct_point_layout_fixme; +extern const ray_pass_layout_t denoiser_layout_fixme; + +static ray_pass_layout_t FIXME_getLayoutFor(const char *name) { + if (strcmp(name, "primary_ray") == 0) + return ray_primary_layout_fixme; + if (strcmp(name, "light_direct_poly") == 0) + return light_direct_poly_layout_fixme; + if (strcmp(name, "light_direct_point") == 0) + return light_direct_point_layout_fixme; + if (strcmp(name, "denoiser") == 0) + return denoiser_layout_fixme; + + gEngine.Host_Error("Unexpected pass name %s", name); + return (ray_pass_layout_t){0}; +} + +static struct ray_pass_s *pipelineLoadCompute(load_context_t *ctx, int i, const char *name) { + const uint32_t shader_comp = READ_U32("Couldn't read comp shader for %d %s", i, name); + + if (shader_comp >= ctx->shaders_count) { + gEngine.Con_Printf(S_ERROR "Pipeline %s shader index out of bounds %d (count %d)\n", name, shader_comp, ctx->shaders_count); + goto finalize; + } + + const ray_pass_create_compute_t rpcc = { + .debug_name = name, + .layout = FIXME_getLayoutFor(name), + .shader = NULL, + .shader_module = ctx->shaders[shader_comp], + }; + + return RayPassCreateCompute(&rpcc); + +finalize: + return NULL; +} + +static struct ray_pass_s *pipelineLoadRT(load_context_t *ctx, int i, const char *name) { + ray_pass_create_tracing_t rpct = { + .debug_name = name, + .layout = FIXME_getLayoutFor(name), + }; + + // FIXME bounds check shader indices + + const uint32_t shader_rgen = READ_U32("Couldn't read rgen shader for %d %s", i, name); + rpct.raygen_module = ctx->shaders[shader_rgen]; + + rpct.miss_count = READ_U32("Couldn't read miss count for %d %s", i, name); + if (rpct.miss_count) { + rpct.miss_module = Mem_Malloc(vk_core.pool, sizeof(VkShaderModule) * rpct.miss_count); + for (int j = 0; j < rpct.miss_count; ++j) { + const uint32_t shader_miss = READ_U32("Couldn't read miss shader %d for %d %s", j, i, name); + rpct.miss_module[j] = ctx->shaders[shader_miss]; + } + } + + rpct.hit_count = READ_U32("Couldn't read hit count for %d %s", i, name); + if (rpct.hit_count) { + ray_pass_hit_group_t *hit = Mem_Malloc(vk_core.pool, sizeof(rpct.hit[0]) * rpct.hit_count); + rpct.hit = hit; + for (int j = 0; j < rpct.hit_count; ++j) { + const uint32_t closest = READ_U32("Couldn't read closest shader %d for %d %s", j, i, name); + const uint32_t any = READ_U32("Couldn't read any shader %d for %d %s", j, i, name); + + hit[j] = (ray_pass_hit_group_t){ + .closest = NULL, + .closest_module = (closest == NO_SHADER) ? VK_NULL_HANDLE : ctx->shaders[closest], + .any = NULL, + .any_module = (any == NO_SHADER) ? VK_NULL_HANDLE : ctx->shaders[any], + }; + } + } + + struct ray_pass_s* const ret = RayPassCreateTracing(&rpct); + +finalize: + if (rpct.hit) + Mem_Free((void*)rpct.hit); + + if (rpct.miss_module) + Mem_Free(rpct.miss_module); + + return ret; +} + +static struct ray_pass_s *pipelineLoad(load_context_t *ctx, int i) { + const uint32_t head = READ_U32("Couldn't read pipeline %d head", i); + + char name[64]; + const int name_len = READ_U32("Coulnd't read pipeline %d name len", i); + const char *name_src = READ_PTR(name_len, "Couldn't read pipeline %d name", i); +#define MIN(a,b) ((a)<(b)?(a):(b)) + const int name_max = MIN(sizeof(name)-1, name_len); + memcpy(name, name_src, name_max); + name[name_max] = '\0'; + + gEngine.Con_Reportf("%d: loading pipeline %s\n", i, name); + +#define PIPELINE_COMPUTE 1 +#define PIPELINE_RAYTRACING 2 + + switch (head) { + case PIPELINE_COMPUTE: + return pipelineLoadCompute(ctx, i, name); + case PIPELINE_RAYTRACING: + return pipelineLoadRT(ctx, i, name); + default: + gEngine.Con_Printf(S_ERROR "Unexpected pipeline type %d\n", head); + return NULL; + } + +finalize: + return NULL; +} qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename) { qboolean ret = false; @@ -58,11 +185,14 @@ qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename) { return false; } - cursor_t cur = { .data = buf, .off = 0, .size = size }; + load_context_t context = { + .cur = { .data = buf, .off = 0, .size = size }, + .shaders_count = 0, + .shaders = NULL, + }; + load_context_t *ctx = &context; - int shaders_count = 0; - VkShaderModule *shaders = NULL; - int pipelines_count = 0; + out->passes_count = 0; { const uint32_t magic = READ_U32("Couldn't read magic"); @@ -73,76 +203,54 @@ qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename) { } } - shaders_count = READ_U32("Couldn't read shaders count"); - shaders = Mem_Malloc(vk_core.pool, sizeof(VkShaderModule) * shaders_count); - for (int i = 0; i < shaders_count; ++i) - shaders[i] = VK_NULL_HANDLE; + ctx->shaders_count = READ_U32("Couldn't read shaders count"); + ctx->shaders = Mem_Malloc(vk_core.pool, sizeof(VkShaderModule) * ctx->shaders_count); + for (int i = 0; i < ctx->shaders_count; ++i) + ctx->shaders[i] = VK_NULL_HANDLE; - for (int i = 0; i < shaders_count; ++i) { + for (int i = 0; i < ctx->shaders_count; ++i) { char name[256]; Q_snprintf(name, sizeof(name), "%s@%d", filename, i); // TODO serialize origin name const int size = READ_U32("Couldn't read shader %s size", name); const void *src = READ_PTR(size, "Couldn't read shader %s data", name); - shaders[i] = R_VkShaderLoadFromMem(src, size, name); + ctx->shaders[i] = R_VkShaderLoadFromMem(src, size, name); gEngine.Con_Reportf("%d: loaded %s\n", i, name); } - pipelines_count = READ_U32("Couldn't read pipelines count"); - for (int i = 0; i < pipelines_count; ++i) { - const uint32_t head = READ_U32("Couldn't read pipeline %d head", i); - const int name_len = READ_U32("Coulnd't read pipeline %d name len", i); - const char *name_src = READ_PTR(name_len, "Couldn't read pipeline %d name", i); - char name[64]; -#define MIN(a,b) ((a)<(b)?(a):(b)) - const int name_max = MIN(sizeof(name)-1, name_len); - memcpy(name, name_src, name_max); - name[name_max] = '\0'; - gEngine.Con_Reportf("%d: loading pipeline %s\n", i, name); - -#define PIPELINE_COMPUTE 1 -#define PIPELINE_RAYTRACING 2 -#define NO_SHADER 0xffffffff - - switch (head) { - case PIPELINE_COMPUTE: - { - const uint32_t shader_comp = READ_U32("Couldn't read comp shader for %d %s", i, name); - break; - } - - case PIPELINE_RAYTRACING: - { - const uint32_t shader_rgen = READ_U32("Couldn't read rgen shader for %d %s", i, name); - const int miss_count = READ_U32("Couldn't read miss count for %d %s", i, name); - for (int j = 0; j < miss_count; ++j) { - const uint32_t shader_miss = READ_U32("Couldn't read miss shader %d for %d %s", j, i, name); - } - - const int hit_count = READ_U32("Couldn't read hit count for %d %s", i, name); - for (int j = 0; j < hit_count; ++j) { - const uint32_t closest = READ_U32("Couldn't read closest shader %d for %d %s", j, i, name); - const uint32_t any = READ_U32("Couldn't read any shader %d for %d %s", j, i, name); - } - break; - } - } + out->passes_count = READ_U32("Couldn't read pipelines count"); + out->passes = Mem_Malloc(vk_core.pool, sizeof(out->passes[0]) * out->passes_count); + for (int i = 0; i < out->passes_count; ++i) { + if (!(out->passes[i] = pipelineLoad(ctx, i))) + goto finalize; } ret = true; finalize: - for (int i = 0; i < shaders_count; ++i) { - if (shaders[i] != VK_NULL_HANDLE) { - R_VkShaderDestroy(shaders[i]); + if (!ret) { + R_VkMeatpipeDestroy(out); + } + + for (int i = 0; i < ctx->shaders_count; ++i) { + if (ctx->shaders[i] != VK_NULL_HANDLE) { + R_VkShaderDestroy(ctx->shaders[i]); } } - if (shaders) - Mem_Free(shaders); + if (ctx->shaders) + Mem_Free(ctx->shaders); Mem_Free(buf); return ret; } void R_VkMeatpipeDestroy(vk_meatpipe_t *mp) { + for (int i = 0; i < mp->passes_count; ++i) { + if (!mp->passes[i]) + break; + + RayPassDestroy(mp->passes[i]); + } + + mp->passes_count = 0; } diff --git a/ref_vk/vk_meatpipe.h b/ref_vk/vk_meatpipe.h index 69bfc097..aa4b0354 100644 --- a/ref_vk/vk_meatpipe.h +++ b/ref_vk/vk_meatpipe.h @@ -1,9 +1,11 @@ #pragma once +#include "ray_pass.h" #include "xash3d_types.h" typedef struct { - int pog; + int passes_count; + ray_pass_p *passes; } vk_meatpipe_t; qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename); diff --git a/ref_vk/vk_pipeline.c b/ref_vk/vk_pipeline.c index 3e48c00f..ac3ff8b6 100644 --- a/ref_vk/vk_pipeline.c +++ b/ref_vk/vk_pipeline.c @@ -190,7 +190,7 @@ finalize: } VkPipeline VK_PipelineComputeCreate(const vk_pipeline_compute_create_info_t *ci) { - const VkShaderModule shader = R_VkShaderLoadFromFile(ci->shader_filename); + const VkShaderModule shader = ci->shader_module ? ci->shader_module : R_VkShaderLoadFromFile(ci->shader_filename); if (shader == VK_NULL_HANDLE) return VK_NULL_HANDLE; @@ -208,7 +208,7 @@ VkPipeline VK_PipelineComputeCreate(const vk_pipeline_compute_create_info_t *ci) VkPipeline pipeline; XVK_CHECK(vkCreateComputePipelines(vk_core.device, VK_NULL_HANDLE, 1, &cpci, NULL, &pipeline)); - R_VkShaderDestroy(shader); + // FIXME R_VkShaderDestroy(shader); return pipeline; } @@ -246,7 +246,8 @@ vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_ for (int i = 0; i < create->stages_count; ++i) { const vk_shader_stage_t *const stage = create->stages + i; - if (VK_NULL_HANDLE == (shaders[i] = R_VkShaderLoadFromFile(stage->filename))) + shaders[i] = (stage->module != VK_NULL_HANDLE) ? stage->module : R_VkShaderLoadFromFile(stage->filename); + if (VK_NULL_HANDLE == shaders[i]) goto destroy_shaders; if (stage->stage == VK_SHADER_STAGE_RAYGEN_BIT_KHR) { @@ -319,8 +320,8 @@ vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_ XVK_CHECK(vkCreateRayTracingPipelinesKHR(vk_core.device, VK_NULL_HANDLE, g_pipeline_cache, 1, &rtpci, NULL, &ret.pipeline)); destroy_shaders: - for (int i = 0; i < create->stages_count; ++i) - R_VkShaderDestroy(shaders[i]); + //for (int i = 0; i < create->stages_count; ++i) + // FIXMER R_VkShaderDestroy(shaders[i]); if (ret.pipeline == VK_NULL_HANDLE) return ret; diff --git a/ref_vk/vk_pipeline.h b/ref_vk/vk_pipeline.h index 9abf435f..c7a94bea 100644 --- a/ref_vk/vk_pipeline.h +++ b/ref_vk/vk_pipeline.h @@ -7,6 +7,7 @@ VkShaderModule R_VkShaderLoadFromMem(const void *ptr, uint32_t size, const char void R_VkShaderDestroy(VkShaderModule module); typedef struct { + VkShaderModule module; const char *filename; VkShaderStageFlagBits stage; const VkSpecializationInfo *specialization_info; @@ -42,6 +43,7 @@ VkPipeline VK_PipelineGraphicsCreate(const vk_pipeline_graphics_create_info_t *c typedef struct { VkPipelineLayout layout; const char *shader_filename; + VkShaderModule shader_module; const VkSpecializationInfo *specialization_info; } vk_pipeline_compute_create_info_t; diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index eff03836..7a57e8f9 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -57,6 +57,13 @@ static const int semantics_point[] = { #undef OUT }; +const ray_pass_layout_t light_direct_poly_layout_fixme = { + .bindings = bindings, + .bindings_semantics = semantics_poly, + .bindings_count = COUNTOF(bindings), + .push_constants = {0}, +}; + struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { const ray_pass_shader_t miss[] = { "ray_shadow.rmiss.spv" @@ -70,13 +77,8 @@ struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { const ray_pass_create_tracing_t rpc = { .debug_name = "light direct poly", - .layout = { - .bindings = bindings, - .bindings_semantics = semantics_poly, - .bindings_count = COUNTOF(bindings), - .push_constants = {0}, - }, .raygen = "ray_light_poly_direct.rgen.spv", + .layout = light_direct_poly_layout_fixme, .miss = miss, .miss_count = COUNTOF(miss), .hit = hit, @@ -87,6 +89,13 @@ struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { return RayPassCreateTracing( &rpc ); } +ray_pass_layout_t light_direct_point_layout_fixme = { + .bindings = bindings, + .bindings_semantics = semantics_point, + .bindings_count = COUNTOF(bindings), + .push_constants = {0}, +}; + struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ) { const ray_pass_shader_t miss[] = { "ray_shadow.rmiss.spv" @@ -100,12 +109,7 @@ struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ) { const ray_pass_create_tracing_t rpc = { .debug_name = "light direct point", - .layout = { - .bindings = bindings, - .bindings_semantics = semantics_point, - .bindings_count = COUNTOF(bindings), - .push_constants = {0}, - }, + .layout = light_direct_point_layout_fixme, .raygen = "ray_light_direct_point.rgen.spv", .miss = miss, .miss_count = COUNTOF(miss), diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index a8925290..bb93ac67 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -39,6 +39,13 @@ static const int semantics[] = { #undef OUT }; +const ray_pass_layout_t ray_primary_layout_fixme = { + .bindings = bindings, + .bindings_semantics = semantics, + .bindings_count = COUNTOF(bindings), + .push_constants = {0}, +}; + struct ray_pass_s *R_VkRayPrimaryPassCreate( void ) { const ray_pass_shader_t miss[] = { "ray_primary.rmiss.spv" @@ -55,12 +62,7 @@ struct ray_pass_s *R_VkRayPrimaryPassCreate( void ) { const ray_pass_create_tracing_t rpc = { .debug_name = "primary ray", - .layout = { - .bindings = bindings, - .bindings_semantics = semantics, - .bindings_count = COUNTOF(bindings), - .push_constants = {0}, - }, + .layout = ray_primary_layout_fixme, .raygen = "ray_primary.rgen.spv", .miss = miss, .miss_count = COUNTOF(miss), From 5600ce756930c1114d4877ac124715b505c78e6b Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 22 Oct 2022 14:43:41 -0700 Subject: [PATCH 468/548] rt: use meatpipe for rendering --- ref_vk/ray_pass.c | 4 +-- ref_vk/vk_meatpipe.c | 6 ++++ ref_vk/vk_meatpipe.h | 3 ++ ref_vk/vk_rtx.c | 68 +++++++++++++++++++++++++------------------- 4 files changed, 49 insertions(+), 32 deletions(-) diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c index 64cec307..97b046d6 100644 --- a/ref_vk/ray_pass.c +++ b/ref_vk/ray_pass.c @@ -138,7 +138,7 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create .stage = VK_SHADER_STAGE_ANY_HIT_BIT_KHR, .specialization_info = &spec, }; - } if (group->any) { + } else if (group->any) { ASSERT(stage_index < MAX_STAGES); hits[hit_index].any = stage_index; stages[stage_index++] = (vk_shader_stage_t) { @@ -159,7 +159,7 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create .stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, .specialization_info = &spec, }; - } if (group->closest) { + } else if (group->closest) { ASSERT(stage_index < MAX_STAGES); hits[hit_index].closest = stage_index; stages[stage_index++] = (vk_shader_stage_t) { diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 790d12fb..96fb599b 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -254,3 +254,9 @@ void R_VkMeatpipeDestroy(vk_meatpipe_t *mp) { mp->passes_count = 0; } + +void R_VkMeatpipePerform(vk_meatpipe_t *mp, VkCommandBuffer cmdbuf, int frame_set_slot, struct vk_ray_resources_s *res) { + for (int i = 0; i < mp->passes_count; ++i) { + RayPassPerform(cmdbuf, frame_set_slot, mp->passes[i], res); + } +} diff --git a/ref_vk/vk_meatpipe.h b/ref_vk/vk_meatpipe.h index aa4b0354..e0497eca 100644 --- a/ref_vk/vk_meatpipe.h +++ b/ref_vk/vk_meatpipe.h @@ -10,3 +10,6 @@ typedef struct { qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename); void R_VkMeatpipeDestroy(vk_meatpipe_t *mp); + +struct vk_ray_resources_s; +void R_VkMeatpipePerform(vk_meatpipe_t *mp, VkCommandBuffer cmdbuf, int frame_set_slot, struct vk_ray_resources_s *res); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 6e842934..bdabe53f 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -80,12 +80,12 @@ static struct { unsigned frame_number; xvk_ray_frame_images_t frames[MAX_FRAMES_IN_FLIGHT]; - struct { - struct ray_pass_s *primary_ray; - struct ray_pass_s *light_direct_poly; - struct ray_pass_s *light_direct_point; - struct ray_pass_s *denoiser; - } pass; + /* struct { */ + /* struct ray_pass_s *primary_ray; */ + /* struct ray_pass_s *light_direct_poly; */ + /* struct ray_pass_s *light_direct_point; */ + /* struct ray_pass_s *denoiser; */ + /* } pass; */ vk_meatpipe_t mainpipe; @@ -245,9 +245,9 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } - RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.primary_ray, &res ); + //RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.primary_ray, &res ); - { + { // FIXME this should be done automatically inside meatpipe, TODO //const uint32_t size = sizeof(struct Lights); //const uint32_t size = sizeof(struct LightsMetadata); // + 8 * sizeof(uint32_t); const VkBufferMemoryBarrier bmb[] = {{ @@ -264,9 +264,11 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } - RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.light_direct_poly, &res ); - RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.light_direct_point, &res ); - RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.denoiser, &res ); + //RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.light_direct_poly, &res ); + //RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.light_direct_point, &res ); + //RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.denoiser, &res ); + + R_VkMeatpipePerform(&g_rtx.mainpipe, cmdbuf, args->frame_index, &res); { const r_vkimage_blit_args blit_args = { @@ -321,10 +323,16 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) gEngine.Con_Printf(S_WARN "Reloading RTX shaders/pipelines\n"); XVK_CHECK(vkDeviceWaitIdle(vk_core.device)); - reloadPass( &g_rtx.pass.primary_ray, R_VkRayPrimaryPassCreate()); - reloadPass( &g_rtx.pass.light_direct_poly, R_VkRayLightDirectPolyPassCreate()); - reloadPass( &g_rtx.pass.light_direct_point, R_VkRayLightDirectPointPassCreate()); - reloadPass( &g_rtx.pass.denoiser, R_VkRayDenoiserCreate()); + /* reloadPass( &g_rtx.pass.primary_ray, R_VkRayPrimaryPassCreate()); */ + /* reloadPass( &g_rtx.pass.light_direct_poly, R_VkRayLightDirectPolyPassCreate()); */ + /* reloadPass( &g_rtx.pass.light_direct_point, R_VkRayLightDirectPointPassCreate()); */ + /* reloadPass( &g_rtx.pass.denoiser, R_VkRayDenoiserCreate()); */ + + vk_meatpipe_t newpipe; + if (R_VkMeatpipeLoad(&newpipe, "rt.meat")) { + R_VkMeatpipeDestroy(&g_rtx.mainpipe); + g_rtx.mainpipe = newpipe; + } g_rtx.reload_pipeline = false; } @@ -382,17 +390,17 @@ qboolean VK_RayInit( void ) if (!RT_VkAccelInit()) return false; - g_rtx.pass.primary_ray = R_VkRayPrimaryPassCreate(); - ASSERT(g_rtx.pass.primary_ray); - - g_rtx.pass.light_direct_poly = R_VkRayLightDirectPolyPassCreate(); - ASSERT(g_rtx.pass.light_direct_poly); - - g_rtx.pass.light_direct_point = R_VkRayLightDirectPointPassCreate(); - ASSERT(g_rtx.pass.light_direct_point); - - g_rtx.pass.denoiser = R_VkRayDenoiserCreate(); - ASSERT(g_rtx.pass.denoiser); + /* g_rtx.pass.primary_ray = R_VkRayPrimaryPassCreate(); */ + /* ASSERT(g_rtx.pass.primary_ray); */ + /* */ + /* g_rtx.pass.light_direct_poly = R_VkRayLightDirectPolyPassCreate(); */ + /* ASSERT(g_rtx.pass.light_direct_poly); */ + /* */ + /* g_rtx.pass.light_direct_point = R_VkRayLightDirectPointPassCreate(); */ + /* ASSERT(g_rtx.pass.light_direct_point); */ + /* */ + /* g_rtx.pass.denoiser = R_VkRayDenoiserCreate(); */ + /* ASSERT(g_rtx.pass.denoiser); */ ASSERT(R_VkMeatpipeLoad(&g_rtx.mainpipe, "rt.meat")); @@ -466,10 +474,10 @@ void VK_RayShutdown( void ) { R_VkMeatpipeDestroy(&g_rtx.mainpipe); - RayPassDestroy(g_rtx.pass.denoiser); - RayPassDestroy(g_rtx.pass.light_direct_poly); - RayPassDestroy(g_rtx.pass.light_direct_point); - RayPassDestroy(g_rtx.pass.primary_ray); + /* RayPassDestroy(g_rtx.pass.denoiser); */ + /* RayPassDestroy(g_rtx.pass.light_direct_poly); */ + /* RayPassDestroy(g_rtx.pass.light_direct_point); */ + /* RayPassDestroy(g_rtx.pass.primary_ray); */ for (int i = 0; i < ARRAYSIZE(g_rtx.frames); ++i) { XVK_ImageDestroy(&g_rtx.frames[i].denoised); From dcc96fe7edf2b1baba0b8f295efd24d7171934b1 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 22 Oct 2022 14:44:31 -0700 Subject: [PATCH 469/548] meat: also store shader names --- ref_vk/sebastian.py | 40 ++++++++++++++++++++++++++--------- ref_vk/vk_meatpipe.c | 50 +++++++++++++++++++++++++++++++------------- 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index a5241f3c..93cc1e30 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -16,6 +16,27 @@ spvOpNames = dict() for name, n in spvOp.items(): spvOpNames[n] = name +class Serializer: + def __init__(self, file): + self.file = file + + def write(self, v): + self.file.write(v) + + def writeU32(self, v): + self.write(struct.pack('I', v)) + + def writeBytes(self, v): + self.writeU32(len(v)) + self.write(v) + + def writeString(self, v): + bs = v.encode('utf-8') + b'\x00' + rem = len(bs) % 4 + if rem != 0: + bs += b'\x00' * (4 - rem) + self.writeBytes(bs) + class SpirvNode: def __init__(self): self.descriptor_set = None @@ -136,11 +157,11 @@ class Shaders: def getIndex(self, name): return self.__map[name] - def serialize(self, file): - file.write(struct.pack('I', len(self.__shaders))) + def serialize(self, out): + out.writeU32(len(self.__shaders)) for shader in self.__shaders: - file.write(struct.pack('I', len(shader.raw_data))) - file.write(shader.raw_data) + out.writeString(shader.name) + out.writeBytes(shader.raw_data) def serializeIndex(self, out, shader): out.write(struct.pack('I', self.getIndex(shader.name))) @@ -215,18 +236,17 @@ def loadPipelines(): pipelines[k] = parsePipeline(pipelines_desc, k, v) return pipelines -def writeOutput(out, pipelines): +def writeOutput(file, pipelines): MAGIC = bytearray([ord(c) for c in 'MEAT']) + out = Serializer(file) out.write(MAGIC) shaders.serialize(out) - out.write(struct.pack('I', len(pipelines))) + out.writeU32(len(pipelines)) for name, pipeline in pipelines.items(): - out.write(struct.pack('I', pipeline.type)) - bs = pipeline.name.encode('utf-8') - out.write(struct.pack('I', len(bs))) - out.write(bs) + out.writeU32(pipeline.type) + out.writeString(pipeline.name) pipeline.serialize(out) pipelines = loadPipelines() diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 96fb599b..fef37e85 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -5,6 +5,8 @@ #include "ray_pass.h" #include "vk_common.h" +#define MIN(a,b) ((a)<(b)?(a):(b)) + #define CHAR4UINT(a,b,c,d) (((d)<<24)|((c)<<16)|((b)<<8)|(a)) static const uint32_t k_meatpipe_magic = CHAR4UINT('M', 'E', 'A', 'T'); @@ -54,6 +56,24 @@ uint32_t curReadU32(cursor_t *cur) { #define READ_U32(errmsg, ...) \ curReadU32(&ctx->cur); CUR_ERROR(errmsg, ##__VA_ARGS__) +int curReadStr(cursor_t *cur, char* out, int out_size) { + const int len = curReadU32(cur); + if (cur->error) + return -1; + + const char *src = curReadPtr(cur, len); + if (cur->error) + return -1; + + const int max = MIN(out_size, len); \ + memcpy(out, src, max); \ + out[max] = '\0'; + return len; +} + +#define READ_STR(out, errmsg, ...) \ + curReadStr(&ctx->cur, out, sizeof(out)); CUR_ERROR(errmsg, ##__VA_ARGS__) + #define NO_SHADER 0xffffffff extern const ray_pass_layout_t ray_primary_layout_fixme; @@ -149,12 +169,7 @@ static struct ray_pass_s *pipelineLoad(load_context_t *ctx, int i) { const uint32_t head = READ_U32("Couldn't read pipeline %d head", i); char name[64]; - const int name_len = READ_U32("Coulnd't read pipeline %d name len", i); - const char *name_src = READ_PTR(name_len, "Couldn't read pipeline %d name", i); -#define MIN(a,b) ((a)<(b)?(a):(b)) - const int name_max = MIN(sizeof(name)-1, name_len); - memcpy(name, name_src, name_max); - name[name_max] = '\0'; + READ_STR(name, "Couldn't read pipeline %d name", i); gEngine.Con_Reportf("%d: loading pipeline %s\n", i, name); @@ -205,17 +220,21 @@ qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename) { ctx->shaders_count = READ_U32("Couldn't read shaders count"); ctx->shaders = Mem_Malloc(vk_core.pool, sizeof(VkShaderModule) * ctx->shaders_count); - for (int i = 0; i < ctx->shaders_count; ++i) + for (int i = 0; i < ctx->shaders_count; ++i) { ctx->shaders[i] = VK_NULL_HANDLE; - for (int i = 0; i < ctx->shaders_count; ++i) { - char name[256]; - Q_snprintf(name, sizeof(name), "%s@%d", filename, i); // TODO serialize origin name + char name[64]; + READ_STR(name, "Couldn't read shader %d name", i); const int size = READ_U32("Couldn't read shader %s size", name); const void *src = READ_PTR(size, "Couldn't read shader %s data", name); - ctx->shaders[i] = R_VkShaderLoadFromMem(src, size, name); - gEngine.Con_Reportf("%d: loaded %s\n", i, name); + + if (VK_NULL_HANDLE == (ctx->shaders[i] = R_VkShaderLoadFromMem(src, size, name))) { + gEngine.Con_Printf(S_ERROR "Failed to load shader %d:%s\n", i, name); + goto finalize; + } + + gEngine.Con_Reportf("%d: Shader loaded %s\n", i, name); } out->passes_count = READ_U32("Couldn't read pipelines count"); @@ -232,9 +251,10 @@ finalize: } for (int i = 0; i < ctx->shaders_count; ++i) { - if (ctx->shaders[i] != VK_NULL_HANDLE) { - R_VkShaderDestroy(ctx->shaders[i]); - } + if (ctx->shaders[i] == VK_NULL_HANDLE) + break; + + R_VkShaderDestroy(ctx->shaders[i]); } if (ctx->shaders) From c1cc5f56f6042e6b1e56db70c93978a505950348 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 22 Oct 2022 14:52:24 -0700 Subject: [PATCH 470/548] rt: remove direct usage of passes --- ref_vk/vk_denoiser.c | 14 ---------- ref_vk/vk_denoiser.h | 4 --- ref_vk/vk_ray_light_direct.c | 54 +----------------------------------- ref_vk/vk_ray_light_direct.h | 4 --- ref_vk/vk_ray_primary.c | 30 -------------------- ref_vk/vk_ray_primary.h | 3 -- ref_vk/vk_rtx.c | 43 ---------------------------- 7 files changed, 1 insertion(+), 151 deletions(-) delete mode 100644 ref_vk/vk_denoiser.h delete mode 100644 ref_vk/vk_ray_light_direct.h delete mode 100644 ref_vk/vk_ray_primary.h diff --git a/ref_vk/vk_denoiser.c b/ref_vk/vk_denoiser.c index c03bc836..43f6d8af 100644 --- a/ref_vk/vk_denoiser.c +++ b/ref_vk/vk_denoiser.c @@ -1,5 +1,3 @@ -#include "vk_denoiser.h" - #include "ray_resources.h" #include "ray_pass.h" @@ -44,15 +42,3 @@ const ray_pass_layout_t denoiser_layout_fixme = { .bindings_count = COUNTOF(bindings), .push_constants = {0}, }; - -struct ray_pass_s *R_VkRayDenoiserCreate( void ) { - const ray_pass_create_compute_t rpcc = { - .debug_name = "denoiser", - .layout = denoiser_layout_fixme, - .shader = "denoiser.comp.spv", - .specialization = NULL, - }; - - return RayPassCreateCompute( &rpcc ); -} - diff --git a/ref_vk/vk_denoiser.h b/ref_vk/vk_denoiser.h deleted file mode 100644 index 4cb69f71..00000000 --- a/ref_vk/vk_denoiser.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -struct ray_pass_s; -struct ray_pass_s *R_VkRayDenoiserCreate( void ); diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c index 7a57e8f9..835343fa 100644 --- a/ref_vk/vk_ray_light_direct.c +++ b/ref_vk/vk_ray_light_direct.c @@ -1,5 +1,3 @@ -#include "vk_ray_light_direct.h" - #include "ray_resources.h" #include "ray_pass.h" @@ -64,59 +62,9 @@ const ray_pass_layout_t light_direct_poly_layout_fixme = { .push_constants = {0}, }; -struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ) { - const ray_pass_shader_t miss[] = { - "ray_shadow.rmiss.spv" - }; - - const ray_pass_hit_group_t hit[] = { { - .closest = NULL, - .any = "ray_common_alphatest.rahit.spv", - }, - }; - - const ray_pass_create_tracing_t rpc = { - .debug_name = "light direct poly", - .raygen = "ray_light_poly_direct.rgen.spv", - .layout = light_direct_poly_layout_fixme, - .miss = miss, - .miss_count = COUNTOF(miss), - .hit = hit, - .hit_count = COUNTOF(hit), - .specialization = NULL, - }; - - return RayPassCreateTracing( &rpc ); -} - -ray_pass_layout_t light_direct_point_layout_fixme = { +const ray_pass_layout_t light_direct_point_layout_fixme = { .bindings = bindings, .bindings_semantics = semantics_point, .bindings_count = COUNTOF(bindings), .push_constants = {0}, }; - -struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ) { - const ray_pass_shader_t miss[] = { - "ray_shadow.rmiss.spv" - }; - - const ray_pass_hit_group_t hit[] = { { - .closest = "ray_shadow.rchit.spv", - .any = "ray_common_alphatest.rahit.spv", - }, - }; - - const ray_pass_create_tracing_t rpc = { - .debug_name = "light direct point", - .layout = light_direct_point_layout_fixme, - .raygen = "ray_light_direct_point.rgen.spv", - .miss = miss, - .miss_count = COUNTOF(miss), - .hit = hit, - .hit_count = COUNTOF(hit), - .specialization = NULL, - }; - - return RayPassCreateTracing( &rpc ); -} diff --git a/ref_vk/vk_ray_light_direct.h b/ref_vk/vk_ray_light_direct.h deleted file mode 100644 index c6339ddb..00000000 --- a/ref_vk/vk_ray_light_direct.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -struct ray_pass_s *R_VkRayLightDirectPolyPassCreate( void ); -struct ray_pass_s *R_VkRayLightDirectPointPassCreate( void ); diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c index bb93ac67..216ff8fe 100644 --- a/ref_vk/vk_ray_primary.c +++ b/ref_vk/vk_ray_primary.c @@ -1,5 +1,3 @@ -#include "vk_ray_primary.h" - #include "ray_resources.h" #include "ray_pass.h" @@ -45,31 +43,3 @@ const ray_pass_layout_t ray_primary_layout_fixme = { .bindings_count = COUNTOF(bindings), .push_constants = {0}, }; - -struct ray_pass_s *R_VkRayPrimaryPassCreate( void ) { - const ray_pass_shader_t miss[] = { - "ray_primary.rmiss.spv" - }; - - const ray_pass_hit_group_t hit[] = { { - .closest = "ray_primary.rchit.spv", - .any = NULL, - }, { - .closest = "ray_primary.rchit.spv", - .any = "ray_common_alphatest.rahit.spv", - }, - }; - - const ray_pass_create_tracing_t rpc = { - .debug_name = "primary ray", - .layout = ray_primary_layout_fixme, - .raygen = "ray_primary.rgen.spv", - .miss = miss, - .miss_count = COUNTOF(miss), - .hit = hit, - .hit_count = COUNTOF(hit), - .specialization = NULL, - }; - - return RayPassCreateTracing( &rpc ); -} diff --git a/ref_vk/vk_ray_primary.h b/ref_vk/vk_ray_primary.h deleted file mode 100644 index 38f89f80..00000000 --- a/ref_vk/vk_ray_primary.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -struct ray_pass_s *R_VkRayPrimaryPassCreate( void ); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index bdabe53f..51261609 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -4,14 +4,10 @@ #include "ray_resources.h" #include "vk_ray_accel.h" -#include "vk_ray_primary.h" -#include "vk_ray_light_direct.h" - #include "vk_buffer.h" #include "vk_common.h" #include "vk_core.h" #include "vk_cvar.h" -#include "vk_denoiser.h" #include "vk_descriptor.h" #include "vk_light.h" #include "vk_math.h" @@ -79,14 +75,6 @@ static struct { // TODO with proper intra-cmdbuf sync we don't really need 2x images unsigned frame_number; xvk_ray_frame_images_t frames[MAX_FRAMES_IN_FLIGHT]; - - /* struct { */ - /* struct ray_pass_s *primary_ray; */ - /* struct ray_pass_s *light_direct_poly; */ - /* struct ray_pass_s *light_direct_point; */ - /* struct ray_pass_s *denoiser; */ - /* } pass; */ - vk_meatpipe_t mainpipe; qboolean reload_pipeline; @@ -245,8 +233,6 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } - //RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.primary_ray, &res ); - { // FIXME this should be done automatically inside meatpipe, TODO //const uint32_t size = sizeof(struct Lights); //const uint32_t size = sizeof(struct LightsMetadata); // + 8 * sizeof(uint32_t); @@ -264,10 +250,6 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } - //RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.light_direct_poly, &res ); - //RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.light_direct_point, &res ); - //RayPassPerform( cmdbuf, args->frame_index, g_rtx.pass.denoiser, &res ); - R_VkMeatpipePerform(&g_rtx.mainpipe, cmdbuf, args->frame_index, &res); { @@ -294,14 +276,6 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* DEBUG_END(cmdbuf); } -static void reloadPass( struct ray_pass_s **slot, struct ray_pass_s *new_pass ) { - if (!new_pass) - return; - - RayPassDestroy( *slot ); - *slot = new_pass; -} - void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) { const VkCommandBuffer cmdbuf = args->cmdbuf; @@ -323,11 +297,6 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) gEngine.Con_Printf(S_WARN "Reloading RTX shaders/pipelines\n"); XVK_CHECK(vkDeviceWaitIdle(vk_core.device)); - /* reloadPass( &g_rtx.pass.primary_ray, R_VkRayPrimaryPassCreate()); */ - /* reloadPass( &g_rtx.pass.light_direct_poly, R_VkRayLightDirectPolyPassCreate()); */ - /* reloadPass( &g_rtx.pass.light_direct_point, R_VkRayLightDirectPointPassCreate()); */ - /* reloadPass( &g_rtx.pass.denoiser, R_VkRayDenoiserCreate()); */ - vk_meatpipe_t newpipe; if (R_VkMeatpipeLoad(&newpipe, "rt.meat")) { R_VkMeatpipeDestroy(&g_rtx.mainpipe); @@ -390,18 +359,6 @@ qboolean VK_RayInit( void ) if (!RT_VkAccelInit()) return false; - /* g_rtx.pass.primary_ray = R_VkRayPrimaryPassCreate(); */ - /* ASSERT(g_rtx.pass.primary_ray); */ - /* */ - /* g_rtx.pass.light_direct_poly = R_VkRayLightDirectPolyPassCreate(); */ - /* ASSERT(g_rtx.pass.light_direct_poly); */ - /* */ - /* g_rtx.pass.light_direct_point = R_VkRayLightDirectPointPassCreate(); */ - /* ASSERT(g_rtx.pass.light_direct_point); */ - /* */ - /* g_rtx.pass.denoiser = R_VkRayDenoiserCreate(); */ - /* ASSERT(g_rtx.pass.denoiser); */ - ASSERT(R_VkMeatpipeLoad(&g_rtx.mainpipe, "rt.meat")); g_rtx.uniform_unit_size = ALIGN_UP(sizeof(struct UniformBuffer), vk_core.physical_device.properties.limits.minUniformBufferOffsetAlignment); From 2252731073e0a074c5b27896b37a8025ea787e59 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 22 Oct 2022 15:07:09 -0700 Subject: [PATCH 471/548] meat: remove support of loading shaders from files for passes --- ref_vk/ray_pass.c | 22 ++-------------------- ref_vk/ray_pass.h | 10 +++------- ref_vk/vk_meatpipe.c | 3 --- ref_vk/vk_pipeline.c | 20 ++++---------------- ref_vk/vk_pipeline.h | 4 ++-- 5 files changed, 11 insertions(+), 48 deletions(-) diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c index 97b046d6..aa3f8295 100644 --- a/ref_vk/ray_pass.c +++ b/ref_vk/ray_pass.c @@ -100,13 +100,12 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create stages[stage_index++] = (vk_shader_stage_t) { .module = create->raygen_module, - .filename = create->raygen, + .filename = NULL, .stage = VK_SHADER_STAGE_RAYGEN_BIT_KHR, .specialization_info = &spec, }; for (int i = 0; i < create->miss_count; ++i) { - const char* shader_filename = create->miss ? create->miss[i] : NULL; const VkShaderModule shader_module = create->miss_module ? create->miss_module[i] : VK_NULL_HANDLE; ASSERT(stage_index < MAX_STAGES); @@ -117,7 +116,7 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create misses[miss_index++] = stage_index; stages[stage_index++] = (vk_shader_stage_t) { .module = shader_module, - .filename = shader_filename, + .filename = NULL, .stage = VK_SHADER_STAGE_MISS_BIT_KHR, .specialization_info = &spec, }; @@ -138,14 +137,6 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create .stage = VK_SHADER_STAGE_ANY_HIT_BIT_KHR, .specialization_info = &spec, }; - } else if (group->any) { - ASSERT(stage_index < MAX_STAGES); - hits[hit_index].any = stage_index; - stages[stage_index++] = (vk_shader_stage_t) { - .filename = group->any, - .stage = VK_SHADER_STAGE_ANY_HIT_BIT_KHR, - .specialization_info = &spec, - }; } else { hits[hit_index].any = -1; } @@ -159,14 +150,6 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create .stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, .specialization_info = &spec, }; - } else if (group->closest) { - ASSERT(stage_index < MAX_STAGES); - hits[hit_index].closest = stage_index; - stages[stage_index++] = (vk_shader_stage_t) { - .filename = group->closest, - .stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, - .specialization_info = &spec, - }; } else { hits[hit_index].closest = -1; } @@ -204,7 +187,6 @@ struct ray_pass_s *RayPassCreateCompute( const ray_pass_create_compute_t *create const vk_pipeline_compute_create_info_t pcci = { .layout = header->desc.riptors.pipeline_layout, .shader_module = create->shader_module, - .shader_filename = create->shader, .specialization_info = create->specialization, }; diff --git a/ref_vk/ray_pass.h b/ref_vk/ray_pass.h index c105acad..5adbee98 100644 --- a/ref_vk/ray_pass.h +++ b/ref_vk/ray_pass.h @@ -18,13 +18,10 @@ typedef struct { struct ray_pass_s; typedef struct ray_pass_s* ray_pass_p; -typedef const char* ray_pass_shader_t; - typedef struct { const char *debug_name; ray_pass_layout_t layout; - ray_pass_shader_t shader; // FIXME remove VkShaderModule shader_module; const VkSpecializationInfo *specialization; } ray_pass_create_compute_t; @@ -33,9 +30,7 @@ struct ray_pass_s *RayPassCreateCompute( const ray_pass_create_compute_t *create typedef struct { - ray_pass_shader_t closest; // FIXME remove VkShaderModule closest_module; - ray_pass_shader_t any; // FIXME remove VkShaderModule any_module; } ray_pass_hit_group_t; @@ -43,10 +38,11 @@ typedef struct { const char *debug_name; ray_pass_layout_t layout; - ray_pass_shader_t raygen; // FIXME remove + // TODO make a single tables of all shader modules + // and then reference them by index in raygen/miss/hit tables + // like it's done in vk_pipeline_ray_create_info_t VkShaderModule raygen_module; - const ray_pass_shader_t *miss; VkShaderModule *miss_module; int miss_count; diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index fef37e85..e99e9a0e 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -106,7 +106,6 @@ static struct ray_pass_s *pipelineLoadCompute(load_context_t *ctx, int i, const const ray_pass_create_compute_t rpcc = { .debug_name = name, .layout = FIXME_getLayoutFor(name), - .shader = NULL, .shader_module = ctx->shaders[shader_comp], }; @@ -145,9 +144,7 @@ static struct ray_pass_s *pipelineLoadRT(load_context_t *ctx, int i, const char const uint32_t any = READ_U32("Couldn't read any shader %d for %d %s", j, i, name); hit[j] = (ray_pass_hit_group_t){ - .closest = NULL, .closest_module = (closest == NO_SHADER) ? VK_NULL_HANDLE : ctx->shaders[closest], - .any = NULL, .any_module = (any == NO_SHADER) ? VK_NULL_HANDLE : ctx->shaders[any], }; } diff --git a/ref_vk/vk_pipeline.c b/ref_vk/vk_pipeline.c index ac3ff8b6..438fdd84 100644 --- a/ref_vk/vk_pipeline.c +++ b/ref_vk/vk_pipeline.c @@ -190,17 +190,13 @@ finalize: } VkPipeline VK_PipelineComputeCreate(const vk_pipeline_compute_create_info_t *ci) { - const VkShaderModule shader = ci->shader_module ? ci->shader_module : R_VkShaderLoadFromFile(ci->shader_filename); - if (shader == VK_NULL_HANDLE) - return VK_NULL_HANDLE; - const VkComputePipelineCreateInfo cpci = { .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .layout = ci->layout, .stage = (VkPipelineShaderStageCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_COMPUTE_BIT, - .module = shader, + .module = ci->shader_module, .pName = "main", .pSpecializationInfo = ci->specialization_info, }, @@ -208,7 +204,6 @@ VkPipeline VK_PipelineComputeCreate(const vk_pipeline_compute_create_info_t *ci) VkPipeline pipeline; XVK_CHECK(vkCreateComputePipelines(vk_core.device, VK_NULL_HANDLE, 1, &cpci, NULL, &pipeline)); - // FIXME R_VkShaderDestroy(shader); return pipeline; } @@ -241,14 +236,11 @@ vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_ return ret; } - VkShaderModule shaders[MAX_SHADER_STAGES] = {0}; - for (int i = 0; i < create->stages_count; ++i) { const vk_shader_stage_t *const stage = create->stages + i; - shaders[i] = (stage->module != VK_NULL_HANDLE) ? stage->module : R_VkShaderLoadFromFile(stage->filename); - if (VK_NULL_HANDLE == shaders[i]) - goto destroy_shaders; + // FIXME going away from loading shaders directly + ASSERT(!stage->filename); if (stage->stage == VK_SHADER_STAGE_RAYGEN_BIT_KHR) { ASSERT(raygen_index == -1); @@ -258,7 +250,7 @@ vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_ stages[i] = (VkPipelineShaderStageCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = stage->stage, - .module = shaders[i], + .module = stage->module, .pName = "main", .pSpecializationInfo = stage->specialization_info, }; @@ -319,10 +311,6 @@ vk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_ XVK_CHECK(vkCreateRayTracingPipelinesKHR(vk_core.device, VK_NULL_HANDLE, g_pipeline_cache, 1, &rtpci, NULL, &ret.pipeline)); -destroy_shaders: - //for (int i = 0; i < create->stages_count; ++i) - // FIXMER R_VkShaderDestroy(shaders[i]); - if (ret.pipeline == VK_NULL_HANDLE) return ret; diff --git a/ref_vk/vk_pipeline.h b/ref_vk/vk_pipeline.h index c7a94bea..6ae85119 100644 --- a/ref_vk/vk_pipeline.h +++ b/ref_vk/vk_pipeline.h @@ -8,7 +8,7 @@ void R_VkShaderDestroy(VkShaderModule module); typedef struct { VkShaderModule module; - const char *filename; + const char *filename; VkShaderStageFlagBits stage; const VkSpecializationInfo *specialization_info; } vk_shader_stage_t; @@ -42,7 +42,6 @@ VkPipeline VK_PipelineGraphicsCreate(const vk_pipeline_graphics_create_info_t *c typedef struct { VkPipelineLayout layout; - const char *shader_filename; VkShaderModule shader_module; const VkSpecializationInfo *specialization_info; } vk_pipeline_compute_create_info_t; @@ -58,6 +57,7 @@ typedef struct { const char *debug_name; VkPipelineLayout layout; + // FIXME make this pointer to shader modules, add int raygen_index const vk_shader_stage_t *stages; int stages_count; From c2cf73848322bb6fc7894e2da8745102aabc0852 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 22 Oct 2022 16:53:32 -0700 Subject: [PATCH 472/548] fixup linux compilation --- ref_vk/vk_meatpipe.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index e99e9a0e..e99810b9 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -40,6 +40,12 @@ const void* curReadPtr(cursor_t *cur, int size) { goto finalize; \ } +#define CUR_ERROR_RETURN(retval, errmsg, ...) \ + if (ctx->cur.error) { \ + gEngine.Con_Printf(S_ERROR "(off=%d left=%d) " errmsg "\n", ctx->cur.off, (ctx->cur.size - ctx->cur.off), ##__VA_ARGS__); \ + return retval; \ + } + #define READ_PTR(size, errmsg, ...) \ curReadPtr(&ctx->cur, size); CUR_ERROR(errmsg, ##__VA_ARGS__) @@ -56,6 +62,9 @@ uint32_t curReadU32(cursor_t *cur) { #define READ_U32(errmsg, ...) \ curReadU32(&ctx->cur); CUR_ERROR(errmsg, ##__VA_ARGS__) +#define READ_U32_RETURN(retval, errmsg, ...) \ + curReadU32(&ctx->cur); CUR_ERROR_RETURN(retval, errmsg, ##__VA_ARGS__) + int curReadStr(cursor_t *cur, char* out, int out_size) { const int len = curReadU32(cur); if (cur->error) @@ -96,11 +105,11 @@ static ray_pass_layout_t FIXME_getLayoutFor(const char *name) { } static struct ray_pass_s *pipelineLoadCompute(load_context_t *ctx, int i, const char *name) { - const uint32_t shader_comp = READ_U32("Couldn't read comp shader for %d %s", i, name); + const uint32_t shader_comp = READ_U32_RETURN(NULL, "Couldn't read comp shader for %d %s", i, name); if (shader_comp >= ctx->shaders_count) { gEngine.Con_Printf(S_ERROR "Pipeline %s shader index out of bounds %d (count %d)\n", name, shader_comp, ctx->shaders_count); - goto finalize; + return NULL; } const ray_pass_create_compute_t rpcc = { @@ -110,12 +119,10 @@ static struct ray_pass_s *pipelineLoadCompute(load_context_t *ctx, int i, const }; return RayPassCreateCompute(&rpcc); - -finalize: - return NULL; } static struct ray_pass_s *pipelineLoadRT(load_context_t *ctx, int i, const char *name) { + ray_pass_p ret = NULL; ray_pass_create_tracing_t rpct = { .debug_name = name, .layout = FIXME_getLayoutFor(name), @@ -150,7 +157,7 @@ static struct ray_pass_s *pipelineLoadRT(load_context_t *ctx, int i, const char } } - struct ray_pass_s* const ret = RayPassCreateTracing(&rpct); + ret = RayPassCreateTracing(&rpct); finalize: if (rpct.hit) From af8702ecb326c77dff921f26dfe2cc1b4829a457 Mon Sep 17 00:00:00 2001 From: NightFox <0x4E69676874466F78@users.noreply.github.com> Date: Mon, 24 Oct 2022 03:15:11 +0300 Subject: [PATCH 473/548] added support comments for rt.json support //comment and /* comment */ and autofix comma --- ref_vk/sebastian.py | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 93cc1e30..2bdc0583 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -16,6 +16,42 @@ spvOpNames = dict() for name, n in spvOp.items(): spvOpNames[n] = name +# remove comment lines and fix comma +def prepareJSON(path): + raw_json = buffer = result = "" + onecomment = blockcomment = 0 + for char in path.read(): + if (len(buffer) > 1): + buffer = buffer[1:] + buffer += char + if buffer == "*/": + blockcomment = 0 + raw_json = raw_json[:-1] + elif blockcomment: + continue + elif buffer == "/*": + blockcomment = 1 + elif char == "\n" or char == "\r": + buffer = "" + onecomment = 0 + elif char == "\t" or char == " " or onecomment: + continue + elif buffer == "//": + raw_json = raw_json[:-1] + onecomment = 1 + elif buffer != "": + raw_json += char + raw_json = raw_json.replace(",]","]") + raw_json = raw_json.replace(",}","}") + try: + result = json.loads(raw_json) + print(json.dumps(result, sort_keys=False, indent=4)) + except json.decoder.JSONDecodeError as exp: + print("Decoding JSON has failed") + print(raw_json) + raise + return result + class Serializer: def __init__(self, file): self.file = file @@ -228,7 +264,7 @@ def parsePipeline(pipelines, name, desc): return PipelineCompute(name, desc) def loadPipelines(): - pipelines_desc = json.load(args.pipelines) + pipelines_desc = prepareJSON(args.pipelines) pipelines = dict() for k, v in pipelines_desc.items(): if 'template' in v and v['template']: From 6f80bc0015e56810a72f97e5353c752a99b17281 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 29 Oct 2022 11:35:50 -0700 Subject: [PATCH 474/548] seba: slightly improve serialization routines --- ref_vk/sebastian.py | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 2bdc0583..858b880e 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -73,6 +73,14 @@ class Serializer: bs += b'\x00' * (4 - rem) self.writeBytes(bs) + def writeArray(self, v): + self.writeU32(len(v)) + for i in v: + if isinstance(i, int): + self.writeU32(i) + else: + i.serialize(self) + class SpirvNode: def __init__(self): self.descriptor_set = None @@ -190,8 +198,8 @@ class Shaders: return shader - def getIndex(self, name): - return self.__map[name] + def getIndex(self, shader): + return self.__map[shader.name] def serialize(self, out): out.writeU32(len(self.__shaders)) @@ -199,9 +207,6 @@ class Shaders: out.writeString(shader.name) out.writeBytes(shader.raw_data) - def serializeIndex(self, out, shader): - out.write(struct.pack('I', self.getIndex(shader.name))) - shaders = Shaders() @@ -225,23 +230,13 @@ class PipelineRayTracing: self.hit = [] if not 'hit' in desc else [PipelineRayTracing.__loadHit(hit) for hit in desc['hit']] def serialize(self, out): - shaders.serializeIndex(out, self.rgen) + out.writeU32(shaders.getIndex(self.rgen)) + out.writeArray([shaders.getIndex(s) for s in self.miss]) - out.write(struct.pack('I', len(self.miss))) - for shader in self.miss: - shaders.serializeIndex(out, shader) - - out.write(struct.pack('I', len(self.hit))) + out.writeU32(len(self.hit)) for hit in self.hit: - if 'closest' in hit: - shaders.serializeIndex(out, hit['closest']) - else: - out.write(struct.pack('I', NO_SHADER)) - - if 'any' in hit: - shaders.serializeIndex(out, hit['any']) - else: - out.write(struct.pack('I', NO_SHADER)) + out.writeU32(shaders.getIndex(hit['closest']) if 'closest' in hit else NO_SHADER) + out.writeU32(shaders.getIndex(hit['any']) if 'any' in hit else NO_SHADER) class PipelineCompute: def __init__(self, name, desc): @@ -250,7 +245,7 @@ class PipelineCompute: self.comp = shaders.load(desc['comp'] + '.comp.spv') def serialize(self, out): - shaders.serializeIndex(out, self.comp) + out.writeU32(shaders.getIndex(self.comp)) def parsePipeline(pipelines, name, desc): if 'inherit' in desc: From 556440df27a77910e0a2cdc5e84b3b87b3d3d298 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 29 Oct 2022 12:25:56 -0700 Subject: [PATCH 475/548] seba: collect all bindings for pipelines --- ref_vk/sebastian.py | 115 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 94 insertions(+), 21 deletions(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 858b880e..583beee5 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -2,6 +2,7 @@ import json import argparse import struct +import copy from spirv import spv parser = argparse.ArgumentParser(description='Build pipeline descriptor') @@ -151,6 +152,38 @@ def parseSpirv(raw_data): return ctx +class Binding: + STAGE_VERTEX_BIT = 0x00000001 + STAGE_TESSELLATION_CONTROL_BIT = 0x00000002 + STAGE_TESSELLATION_EVALUATION_BIT = 0x00000004 + STAGE_GEOMETRY_BIT = 0x00000008 + STAGE_FRAGMENT_BIT = 0x00000010 + STAGE_COMPUTE_BIT = 0x00000020 + STAGE_ALL_GRAPHICS = 0x0000001F + STAGE_ALL = 0x7FFFFFFF + STAGE_RAYGEN_BIT_KHR = 0x00000100 + STAGE_ANY_HIT_BIT_KHR = 0x00000200 + STAGE_CLOSEST_HIT_BIT_KHR = 0x00000400 + STAGE_MISS_BIT_KHR = 0x00000800 + STAGE_INTERSECTION_BIT_KHR = 0x00001000 + STAGE_CALLABLE_BIT_KHR = 0x00002000 + STAGE_TASK_BIT_NV = 0x00000040 + STAGE_MESH_BIT_NV = 0x00000080 + STAGE_SUBPASS_SHADING_BIT_HUAWEI = 0x00004000 + + def __init__(self, name, descriptor_set, index, stages): + self.name = name + self.index = index + self.descriptor_set = descriptor_set + self.stages = stages + #TODO: type, count, etc + + def serialize(self, out): + out.writeString(self.name) + out.writeU32(self.descriptor_set) + out.writeU32(self.index) + out.writeU32(self.stages) + class Shader: def __init__(self, name, file): self.name = name @@ -165,7 +198,23 @@ class Shader: ret += ('[%d:%d] (id=%d) %s\n' % (node.descriptor_set, node.binding, index, node.name)) return ret + def getBindings(self): + ret = [] + for node in self.spirv.nodes: + if node.binding == None or node.descriptor_set == None: + continue + ret.append(Binding(node.name, node.descriptor_set, node.binding, 0)) + return ret + class Shaders: + __suffixes = { + Binding.STAGE_COMPUTE_BIT: '.comp.spv', + Binding.STAGE_RAYGEN_BIT_KHR: '.rgen.spv', + Binding.STAGE_ANY_HIT_BIT_KHR: '.rahit.spv', + Binding.STAGE_CLOSEST_HIT_BIT_KHR: '.rchit.spv', + Binding.STAGE_MISS_BIT_KHR: '.rmiss.spv' + } + def __init__(self): self.__map = dict() self.__shaders = [] @@ -185,7 +234,8 @@ class Shaders: raise Exception('Cannot load shader ' + name) - def load(self, name): + def load(self, name, stage): + name = name + self.__suffixes[stage] if name in self.__map: return self.__shaders[self.__map[name]] @@ -209,27 +259,46 @@ class Shaders: shaders = Shaders() - PIPELINE_COMPUTE = 1 PIPELINE_RAYTRACING = 2 NO_SHADER = 0xffffffff -class PipelineRayTracing: - def __loadHit(hit): - ret = dict() - suffixes = {'closest': '.rchit.spv', 'any': '.rahit.spv'} - for k, v in hit.items(): - ret[k] = shaders.load(v + suffixes[k]) - return ret - - def __init__(self, name, desc): - self.type = PIPELINE_RAYTRACING +class Pipeline: + def __init__(self, name, type_id): self.name = name - self.rgen = shaders.load(desc['rgen'] + '.rgen.spv') - self.miss = [] if not 'miss' in desc else [shaders.load(s + '.rmiss.spv') for s in desc['miss']] - self.hit = [] if not 'hit' in desc else [PipelineRayTracing.__loadHit(hit) for hit in desc['hit']] + self.type = type_id + self.__bindings = {} + + def addShader(self, shader_name, stage): + shader = shaders.load(shader_name, stage) + for binding in shader.getBindings(): + addr = (binding.descriptor_set, binding.index) + if addr in self.__bindings: + self.__bindings[addr].stages |= stage + else: + self.__bindings[addr] = copy.deepcopy(binding) + + return shader def serialize(self, out): + print(self.__bindings) + out.writeU32(self.type) + out.writeString(self.name) + #out.writeArray(self.__bindings) + +class PipelineRayTracing(Pipeline): + __hit2stage = { + 'closest': Binding.STAGE_CLOSEST_HIT_BIT_KHR, + 'any': Binding.STAGE_ANY_HIT_BIT_KHR, + } + def __init__(self, name, desc): + super().__init__(name, PIPELINE_RAYTRACING) + self.rgen = self.addShader(desc['rgen'], Binding.STAGE_RAYGEN_BIT_KHR) + self.miss = [] if not 'miss' in desc else [self.addShader(s, Binding.STAGE_MISS_BIT_KHR) for s in desc['miss']] + self.hit = [] if not 'hit' in desc else [self.__loadHit(hit) for hit in desc['hit']] + + def serialize(self, out): + super().serialize(out) out.writeU32(shaders.getIndex(self.rgen)) out.writeArray([shaders.getIndex(s) for s in self.miss]) @@ -238,13 +307,19 @@ class PipelineRayTracing: out.writeU32(shaders.getIndex(hit['closest']) if 'closest' in hit else NO_SHADER) out.writeU32(shaders.getIndex(hit['any']) if 'any' in hit else NO_SHADER) -class PipelineCompute: + def __loadHit(self, hit): + ret = dict() + for k, v in hit.items(): + ret[k] = self.addShader(v, self.__hit2stage[k]) + return ret + +class PipelineCompute(Pipeline): def __init__(self, name, desc): - self.type = PIPELINE_COMPUTE - self.name = name - self.comp = shaders.load(desc['comp'] + '.comp.spv') + super().__init__(name, PIPELINE_COMPUTE) + self.comp = self.addShader(desc['comp'], Binding.STAGE_COMPUTE_BIT) def serialize(self, out): + super().serialize(out) out.writeU32(shaders.getIndex(self.comp)) def parsePipeline(pipelines, name, desc): @@ -276,8 +351,6 @@ def writeOutput(file, pipelines): out.writeU32(len(pipelines)) for name, pipeline in pipelines.items(): - out.writeU32(pipeline.type) - out.writeString(pipeline.name) pipeline.serialize(out) pipelines = loadPipelines() From 1500cea931885b8e944359bdae33a757fa4aa6fb Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 29 Oct 2022 12:52:29 -0700 Subject: [PATCH 476/548] meat: write and parse bindings (not used for layout yet) --- ref_vk/sebastian.py | 14 +++++--------- ref_vk/vk_meatpipe.c | 30 +++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 583beee5..6ff55c1f 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -273,18 +273,18 @@ class Pipeline: shader = shaders.load(shader_name, stage) for binding in shader.getBindings(): addr = (binding.descriptor_set, binding.index) - if addr in self.__bindings: - self.__bindings[addr].stages |= stage - else: + if not addr in self.__bindings: self.__bindings[addr] = copy.deepcopy(binding) + self.__bindings[addr].stages |= stage + return shader def serialize(self, out): print(self.__bindings) out.writeU32(self.type) out.writeString(self.name) - #out.writeArray(self.__bindings) + out.writeArray(self.__bindings.values()) class PipelineRayTracing(Pipeline): __hit2stage = { @@ -346,12 +346,8 @@ def writeOutput(file, pipelines): MAGIC = bytearray([ord(c) for c in 'MEAT']) out = Serializer(file) out.write(MAGIC) - shaders.serialize(out) - - out.writeU32(len(pipelines)) - for name, pipeline in pipelines.items(): - pipeline.serialize(out) + out.writeArray(pipelines.values()) pipelines = loadPipelines() diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index e99810b9..97c63737 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -83,6 +83,9 @@ int curReadStr(cursor_t *cur, char* out, int out_size) { #define READ_STR(out, errmsg, ...) \ curReadStr(&ctx->cur, out, sizeof(out)); CUR_ERROR(errmsg, ##__VA_ARGS__) +#define READ_STR_RETURN(retval, out, errmsg, ...) \ + curReadStr(&ctx->cur, out, sizeof(out)); CUR_ERROR_RETURN(retval, errmsg, ##__VA_ARGS__) + #define NO_SHADER 0xffffffff extern const ray_pass_layout_t ray_primary_layout_fixme; @@ -169,24 +172,45 @@ finalize: return ret; } +static qboolean readBindings(load_context_t *ctx) { + const int count = READ_U32_RETURN(false, "Coulnd't read bindings count"); + + for (int i = 0; i < count; ++i) { + char name[64]; + READ_STR_RETURN(false, name, "Couldn't read binding name"); + const uint32_t descriptor_set = READ_U32_RETURN(false, "Couldn't read descriptor_set for binding %s", name); + const uint32_t binding = READ_U32_RETURN(false, "Couldn't read binding for binding %s", name); + const uint32_t stages = READ_U32_RETURN(false, "Couldn't read stages for binding %s", name); + + gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x\n", i, name, descriptor_set, binding, stages); + } + + return true; +} + static struct ray_pass_s *pipelineLoad(load_context_t *ctx, int i) { - const uint32_t head = READ_U32("Couldn't read pipeline %d head", i); + const uint32_t type = READ_U32("Couldn't read pipeline %d type", i); char name[64]; READ_STR(name, "Couldn't read pipeline %d name", i); gEngine.Con_Reportf("%d: loading pipeline %s\n", i, name); + if (!readBindings(ctx)) { + gEngine.Con_Printf(S_ERROR "Couldn't read bindings for pipeline %s\n", name); + return NULL; + } + #define PIPELINE_COMPUTE 1 #define PIPELINE_RAYTRACING 2 - switch (head) { + switch (type) { case PIPELINE_COMPUTE: return pipelineLoadCompute(ctx, i, name); case PIPELINE_RAYTRACING: return pipelineLoadRT(ctx, i, name); default: - gEngine.Con_Printf(S_ERROR "Unexpected pipeline type %d\n", head); + gEngine.Con_Printf(S_ERROR "Unexpected pipeline type %d\n", type); return NULL; } From 2f45e38e628fc6a52ba1f91a4772fba505003bc3 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 29 Oct 2022 13:25:00 -0700 Subject: [PATCH 477/548] seba: quiet --- ref_vk/sebastian.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 6ff55c1f..4558d5a9 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -46,7 +46,7 @@ def prepareJSON(path): raw_json = raw_json.replace(",}","}") try: result = json.loads(raw_json) - print(json.dumps(result, sort_keys=False, indent=4)) + #print(json.dumps(result, sort_keys=False, indent=4)) except json.decoder.JSONDecodeError as exp: print("Decoding JSON has failed") print(raw_json) @@ -281,7 +281,7 @@ class Pipeline: return shader def serialize(self, out): - print(self.__bindings) + #print(self.__bindings) out.writeU32(self.type) out.writeString(self.name) out.writeArray(self.__bindings.values()) From 6cc8bb2f46f435355b53e46a96f6c3f744816c6b Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 29 Oct 2022 14:01:14 -0700 Subject: [PATCH 478/548] rt: name uniform buffers directly to ease spirv parsing --- ref_vk/shaders/additive.rahit | 14 +++++----- ref_vk/shaders/alphamask.rahit | 12 ++++---- ref_vk/shaders/ray_common_alphatest.rahit | 4 +-- ref_vk/shaders/ray_kusochki.glsl | 10 +++++-- ref_vk/shaders/ray_light_direct.glsl | 8 +++--- ref_vk/shaders/ray_primary.rchit | 4 +-- ref_vk/shaders/ray_primary.rgen | 9 +++--- ref_vk/shaders/ray_shadow.rchit | 2 +- ref_vk/shaders/rt_geometry.glsl | 34 +++++++++++------------ ref_vk/vk_core.c | 1 + 10 files changed, 51 insertions(+), 47 deletions(-) diff --git a/ref_vk/shaders/additive.rahit b/ref_vk/shaders/additive.rahit index 4461d00c..ab9a7eb2 100644 --- a/ref_vk/shaders/additive.rahit +++ b/ref_vk/shaders/additive.rahit @@ -18,17 +18,17 @@ hitAttributeEXT vec2 bary; void main() { const int instance_kusochki_offset = gl_InstanceCustomIndexEXT; const int kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT; - const uint first_index_offset = kusochki[kusok_index].index_offset + gl_PrimitiveID * 3; + const uint first_index_offset = getKusok(kusok_index).index_offset + gl_PrimitiveID * 3; - const uint vi1 = uint(indices[first_index_offset+0]) + kusochki[kusok_index].vertex_offset; - const uint vi2 = uint(indices[first_index_offset+1]) + kusochki[kusok_index].vertex_offset; - const uint vi3 = uint(indices[first_index_offset+2]) + kusochki[kusok_index].vertex_offset; + const uint vi1 = uint(getIndex(first_index_offset+0)) + getKusok(kusok_index).vertex_offset; + const uint vi2 = uint(getIndex(first_index_offset+1)) + getKusok(kusok_index).vertex_offset; + const uint vi3 = uint(getIndex(first_index_offset+2)) + getKusok(kusok_index).vertex_offset; - const vec2 texture_uv = vertices[vi1].gl_tc * (1. - bary.x - bary.y) + vertices[vi2].gl_tc * bary.x + vertices[vi3].gl_tc * bary.y + push_constants.time * kusochki[kusok_index].uv_speed; + const vec2 texture_uv = getVertex(vi1).gl_tc * (1. - bary.x - bary.y) + getVertex(vi2).gl_tc * bary.x + getVertex(vi3).gl_tc * bary.y + push_constants.time * getKusok(kusok_index).uv_speed; // TODO mips - const uint tex_index = kusochki[kusok_index].tex_base_color; + const uint tex_index = getKusok(kusok_index).tex_base_color; const vec4 texture_color = texture(textures[nonuniformEXT(tex_index)], texture_uv); - const vec3 color = texture_color.rgb * kusochki[kusok_index].color.rgb * texture_color.a * kusochki[kusok_index].color.a; + const vec3 color = texture_color.rgb * getKusok(kusok_index).color.rgb * texture_color.a * getKusok(kusok_index).color.a; const float overshoot = gl_HitTEXT - payload_additive.ray_distance; diff --git a/ref_vk/shaders/alphamask.rahit b/ref_vk/shaders/alphamask.rahit index 30bc2569..f2d07213 100644 --- a/ref_vk/shaders/alphamask.rahit +++ b/ref_vk/shaders/alphamask.rahit @@ -12,14 +12,14 @@ hitAttributeEXT vec2 bary; void main() { const int instance_kusochki_offset = gl_InstanceCustomIndexEXT; const int kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT; - const uint first_index_offset = kusochki[kusok_index].index_offset + gl_PrimitiveID * 3; + const uint first_index_offset = getKusok(kusok_index).index_offset + gl_PrimitiveID * 3; - const uint vi1 = uint(indices[first_index_offset+0]) + kusochki[kusok_index].vertex_offset; - const uint vi2 = uint(indices[first_index_offset+1]) + kusochki[kusok_index].vertex_offset; - const uint vi3 = uint(indices[first_index_offset+2]) + kusochki[kusok_index].vertex_offset; + const uint vi1 = uint(getIndex(first_index_offset+0)) + getKusok(kusok_index).vertex_offset; + const uint vi2 = uint(getIndex(first_index_offset+1)) + getKusok(kusok_index).vertex_offset; + const uint vi3 = uint(getIndex(first_index_offset+2)) + getKusok(kusok_index).vertex_offset; - const vec2 texture_uv = vertices[vi1].gl_tc * (1. - bary.x - bary.y) + vertices[vi2].gl_tc * bary.x + vertices[vi3].gl_tc * bary.y; - const uint tex_index = kusochki[kusok_index].tex_base_color; + const vec2 texture_uv = getVertex(vi1).gl_tc * (1. - bary.x - bary.y) + getVertex(vi2).gl_tc * bary.x + getVertex(vi3).gl_tc * bary.y; + const uint tex_index = getKusok(kusok_index).tex_base_color; const vec4 texture_color = texture(textures[nonuniformEXT(tex_index)], texture_uv); if (texture_color.a < 0.1) { diff --git a/ref_vk/shaders/ray_common_alphatest.rahit b/ref_vk/shaders/ray_common_alphatest.rahit index 9279651b..a864c5e1 100644 --- a/ref_vk/shaders/ray_common_alphatest.rahit +++ b/ref_vk/shaders/ray_common_alphatest.rahit @@ -10,7 +10,7 @@ layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; // TODO not really needed here? // It's an artifact of readHitGeometry() computing uv_lods, which we don't really use in this shader // Split readHitGeometry into basic and advanced -layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; +layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; } ubo; hitAttributeEXT vec2 bary; @@ -20,7 +20,7 @@ const float alpha_mask_threshold = .1f; void main() { const Geometry geom = readHitGeometry(); - const uint tex_index = kusochki[geom.kusok_index].tex_base_color; + const uint tex_index = getKusok(geom.kusok_index).tex_base_color; const vec4 texture_color = texture(textures[nonuniformEXT(tex_index)], geom.uv); if (texture_color.a < alpha_mask_threshold) { diff --git a/ref_vk/shaders/ray_kusochki.glsl b/ref_vk/shaders/ray_kusochki.glsl index 157857a5..96546e66 100644 --- a/ref_vk/shaders/ray_kusochki.glsl +++ b/ref_vk/shaders/ray_kusochki.glsl @@ -17,6 +17,10 @@ struct Vertex { uint _unused_color_u8_4; }; -layout(std430, binding = 3, set = 0) readonly buffer Kusochki { Kusok kusochki[]; }; -layout(std430, binding = 4, set = 0) readonly buffer Indices { uint16_t indices[]; }; -layout(std430, binding = 5, set = 0) readonly buffer Vertices { Vertex vertices[]; }; +layout(std430, binding = 3, set = 0) readonly buffer Kusochki { Kusok a[]; } kusochki; +layout(std430, binding = 4, set = 0) readonly buffer Indices { uint16_t a[]; } indices; +layout(std430, binding = 5, set = 0) readonly buffer Vertices { Vertex a[]; } vertices; + +Kusok getKusok(uint index) { return kusochki.a[index]; } +uint16_t getIndex(uint index) { return indices.a[index]; } +Vertex getVertex(uint index) { return vertices.a[index]; } diff --git a/ref_vk/shaders/ray_light_direct.glsl b/ref_vk/shaders/ray_light_direct.glsl index 195248c2..acaa5d90 100644 --- a/ref_vk/shaders/ray_light_direct.glsl +++ b/ref_vk/shaders/ray_light_direct.glsl @@ -16,7 +16,7 @@ OUTPUTS(X) #undef X layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; -layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; +layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; } ubo; #include "ray_kusochki.glsl" @@ -43,11 +43,11 @@ void main() { const vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.; const ivec2 pix = ivec2(gl_LaunchIDEXT.xy); - rand01_state = ubo.random_seed + gl_LaunchIDEXT.x * 1833 + gl_LaunchIDEXT.y * 31337; + rand01_state = ubo.ubo.random_seed + gl_LaunchIDEXT.x * 1833 + gl_LaunchIDEXT.y * 31337; // FIXME incorrect for reflection/refraction - const vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); - const vec3 direction = normalize((ubo.inv_view * vec4(target.xyz, 0)).xyz); + const vec4 target = ubo.ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); + const vec3 direction = normalize((ubo.ubo.inv_view * vec4(target.xyz, 0)).xyz); const vec4 material_data = imageLoad(material_rmxx, pix); diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index ac49056e..c2073d47 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -11,7 +11,7 @@ #include "color_spaces.glsl" layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; -layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; +layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; } ubo; layout(set = 0, binding = 7) uniform samplerCube skybox; layout(location = PAYLOAD_LOCATION_PRIMARY) rayPayloadInEXT RayPayloadPrimary payload; @@ -28,7 +28,7 @@ void main() { payload.hit_t = vec4(geom.pos, gl_HitTEXT); - const Kusok kusok = kusochki[geom.kusok_index]; + const Kusok kusok = getKusok(geom.kusok_index); const uint tex_base_color = kusok.tex_base_color; if ((tex_base_color & KUSOK_MATERIAL_FLAG_SKYBOX) != 0) { diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index 912f899e..bcdd57ef 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -8,7 +8,7 @@ RAY_PRIMARY_OUTPUTS(X) #undef X layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; -layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; }; +layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; } ubo; layout(location = PAYLOAD_LOCATION_PRIMARY) rayPayloadEXT RayPayloadPrimary payload; @@ -16,10 +16,9 @@ void main() { const vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.; // FIXME start on a near plane - const vec3 origin = (ubo.inv_view * vec4(0, 0, 0, 1)).xyz; - const vec4 target = ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); - //vec3 direction = (ubo.inv_view * vec4(normalize(target.xyz), 0)).xyz; - const vec3 direction = normalize((ubo.inv_view * vec4(target.xyz, 0)).xyz); + const vec3 origin = (ubo.ubo.inv_view * vec4(0, 0, 0, 1)).xyz; + const vec4 target = ubo.ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); + const vec3 direction = normalize((ubo.ubo.inv_view * vec4(target.xyz, 0)).xyz); payload.hit_t = vec4(0.); payload.base_color_a = vec4(0.); diff --git a/ref_vk/shaders/ray_shadow.rchit b/ref_vk/shaders/ray_shadow.rchit index a44f422d..8823e998 100644 --- a/ref_vk/shaders/ray_shadow.rchit +++ b/ref_vk/shaders/ray_shadow.rchit @@ -9,7 +9,7 @@ layout(location = PAYLOAD_LOCATION_SHADOW) rayPayloadInEXT RayPayloadShadow payl void main() { const int instance_kusochki_offset = gl_InstanceCustomIndexEXT; const int kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT; - const uint tex_base_color = kusochki[kusok_index].tex_base_color; + const uint tex_base_color = getKusok(kusok_index).tex_base_color; payload_shadow.hit_type = ((tex_base_color & KUSOK_MATERIAL_FLAG_SKYBOX) == 0) ? SHADOW_HIT : SHADOW_SKY ; } diff --git a/ref_vk/shaders/rt_geometry.glsl b/ref_vk/shaders/rt_geometry.glsl index ad2ddd2c..f232035e 100644 --- a/ref_vk/shaders/rt_geometry.glsl +++ b/ref_vk/shaders/rt_geometry.glsl @@ -56,23 +56,23 @@ Geometry readHitGeometry() { const int instance_kusochki_offset = gl_InstanceCustomIndexEXT; geom.kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT; - const Kusok kusok = kusochki[geom.kusok_index]; + const Kusok kusok = getKusok(geom.kusok_index); const uint first_index_offset = kusok.index_offset + gl_PrimitiveID * 3; - const uint vi1 = uint(indices[first_index_offset+0]) + kusok.vertex_offset; - const uint vi2 = uint(indices[first_index_offset+1]) + kusok.vertex_offset; - const uint vi3 = uint(indices[first_index_offset+2]) + kusok.vertex_offset; + const uint vi1 = uint(getIndex(first_index_offset+0)) + kusok.vertex_offset; + const uint vi2 = uint(getIndex(first_index_offset+1)) + kusok.vertex_offset; + const uint vi3 = uint(getIndex(first_index_offset+2)) + kusok.vertex_offset; const vec3 pos[3] = { - gl_ObjectToWorldEXT * vec4(vertices[vi1].pos, 1.f), - gl_ObjectToWorldEXT * vec4(vertices[vi2].pos, 1.f), - gl_ObjectToWorldEXT * vec4(vertices[vi3].pos, 1.f), + gl_ObjectToWorldEXT * vec4(getVertex(vi1).pos, 1.f), + gl_ObjectToWorldEXT * vec4(getVertex(vi2).pos, 1.f), + gl_ObjectToWorldEXT * vec4(getVertex(vi3).pos, 1.f), }; const vec2 uvs[3] = { - vertices[vi1].gl_tc, - vertices[vi2].gl_tc, - vertices[vi3].gl_tc, + getVertex(vi1).gl_tc, + getVertex(vi2).gl_tc, + getVertex(vi3).gl_tc, }; geom.pos = baryMix(pos[0], pos[1], pos[2], bary); @@ -85,17 +85,17 @@ Geometry readHitGeometry() { // NOTE: only support rotations, for arbitrary transform would need to do transpose(inverse(mat3(gl_ObjectToWorldEXT))) const mat3 normalTransform = mat3(gl_ObjectToWorldEXT); geom.normal_shading = normalize(normalTransform * baryMix( - vertices[vi1].normal, - vertices[vi2].normal, - vertices[vi3].normal, + getVertex(vi1).normal, + getVertex(vi2).normal, + getVertex(vi3).normal, bary)); geom.tangent = normalize(normalTransform * baryMix( - vertices[vi1].tangent, - vertices[vi2].tangent, - vertices[vi3].tangent, + getVertex(vi1).tangent, + getVertex(vi2).tangent, + getVertex(vi3).tangent, bary)); - geom.uv_lods = computeAnisotropicEllipseAxes(geom.pos, geom.normal_geometry, gl_WorldRayDirectionEXT, ubo.ray_cone_width * gl_HitTEXT, pos, uvs, geom.uv); + geom.uv_lods = computeAnisotropicEllipseAxes(geom.pos, geom.normal_geometry, gl_WorldRayDirectionEXT, ubo.ubo.ray_cone_width * gl_HitTEXT, pos, uvs, geom.uv); return geom; } diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index 89d4b46f..f2b0911a 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -504,6 +504,7 @@ static qboolean createDevice( void ) { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, .pNext = head, .features.samplerAnisotropy = candidate_device->features.features.samplerAnisotropy, + .features.shaderInt16 = true, }; head = &features; From c2e4f199ecfd19b5ec3bce962731f01da2f025b7 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 29 Oct 2022 14:05:28 -0700 Subject: [PATCH 479/548] rt: expose light binding name directly --- ref_vk/shaders/light.glsl | 20 +++++++++---------- ref_vk/shaders/light_polygon.glsl | 32 +++++++++++++++---------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index d9b715e3..00ace37a 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -1,4 +1,4 @@ -layout (set = 0, binding = BINDING_LIGHTS) readonly buffer UBOLights { LightsMetadata lights; }; // TODO this is pretty much static and should be a buffer, not UBO +layout (set = 0, binding = BINDING_LIGHTS) readonly buffer UBOLights { LightsMetadata m; } lights; // TODO this is pretty much static and should be a buffer, not UBO layout (set = 0, binding = BINDING_LIGHT_CLUSTERS, align = 1) readonly buffer UBOLightClusters { ivec3 grid_min, grid_size; //uint8_t clusters_data[MAX_LIGHT_CLUSTERS * LIGHT_CLUSTER_SIZE + HACK_OFFSET]; @@ -19,25 +19,25 @@ const float shadow_offset_fudge = .1; void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { diffuse = specular = vec3(0.); - //diffuse = vec3(1.);//float(lights.num_point_lights) / 64.); + //diffuse = vec3(1.);//float(lights.m.num_point_lights) / 64.); //#define USE_CLUSTERS #ifdef USE_CLUSTERS const uint num_point_lights = uint(light_grid.clusters[cluster_index].num_point_lights); for (uint j = 0; j < num_point_lights; ++j) { const uint i = uint(light_grid.clusters[cluster_index].point_lights[j]); #else - for (uint i = 0; i < lights.num_point_lights; ++i) { + for (uint i = 0; i < lights.m.num_point_lights; ++i) { #endif - vec3 color = lights.point_lights[i].color_stopdot.rgb * throughput; + vec3 color = lights.m.point_lights[i].color_stopdot.rgb * throughput; if (dot(color,color) < color_culling_threshold) continue; - const vec4 origin_r = lights.point_lights[i].origin_r; - const float stopdot = lights.point_lights[i].color_stopdot.a; - const vec3 dir = lights.point_lights[i].dir_stopdot2.xyz; - const float stopdot2 = lights.point_lights[i].dir_stopdot2.a; - const bool not_environment = (lights.point_lights[i].environment == 0); + const vec4 origin_r = lights.m.point_lights[i].origin_r; + const float stopdot = lights.m.point_lights[i].color_stopdot.a; + const vec3 dir = lights.m.point_lights[i].dir_stopdot2.xyz; + const float stopdot2 = lights.m.point_lights[i].dir_stopdot2.a; + const bool not_environment = (lights.m.point_lights[i].environment == 0); const vec3 light_dir = not_environment ? (origin_r.xyz - P) : -dir; // TODO need to randomize sampling direction for environment soft shadow const float radius = origin_r.w; @@ -56,7 +56,7 @@ void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec spot_attenuation = (spot_dot - stopdot2) / (stopdot - stopdot2); //float fdist = 1.f; - float light_dist = 1e5; // TODO this is supposedly not the right way to do shadows for environment lights. qrad checks for hitting SURF_SKY, and maybe we should too? + float light_dist = 1e5; // TODO this is supposedly not the right way to do shadows for environment lights.m. qrad checks for hitting SURF_SKY, and maybe we should too? const float d2 = dot(light_dir, light_dir); const float r2 = origin_r.w * origin_r.w; if (not_environment) { diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 1ba25c34..02dd56eb 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -28,7 +28,7 @@ vec4 getPolygonLightSampleSimple(vec3 P, vec3 view_dir, const PolygonLight poly) vertices_count = 3; // FIXME for (uint i = 0; i < vertices_count; ++i) { - v[i] = lights.polygon_vertices[vertices_offset + i].xyz; + v[i] = lights.m.polygon_vertices[vertices_offset + i].xyz; } vec2 rnd = vec2(sqrt(rand01()), rand01()); @@ -49,12 +49,12 @@ vec4 getPolygonLightSampleSimpleSolid(vec3 P, vec3 view_dir, const PolygonLight float total_contrib = 0.; float eps1 = rand01(); vec3 v[3]; - v[0] = normalize(lights.polygon_vertices[vertices_offset + 0].xyz - P); - v[1] = normalize(lights.polygon_vertices[vertices_offset + 1].xyz - P); + v[0] = normalize(lights.m.polygon_vertices[vertices_offset + 0].xyz - P); + v[1] = normalize(lights.m.polygon_vertices[vertices_offset + 1].xyz - P); const float householder_sign = (v[0].x > 0.0f) ? -1.0f : 1.0f; const vec2 householder_yz = v[0].yz * (1.0f / (abs(v[0].x) + 1.0f)); for (uint i = 2; i < vertices_count; ++i) { - v[2] = normalize(lights.polygon_vertices[vertices_offset + i].xyz - P); + v[2] = normalize(lights.m.polygon_vertices[vertices_offset + i].xyz - P); // effectively mindlessly copypasted from polygon_sampling.glsl, Peters 2021 // https://github.com/MomentsInGraphics/vulkan_renderer/blob/main/src/shaders/polygon_sampling.glsl @@ -98,9 +98,9 @@ vec4 getPolygonLightSampleSimpleSolid(vec3 P, vec3 view_dir, const PolygonLight rnd.x = 1.f - rnd.x; const vec3 light_dir = baryMix( - lights.polygon_vertices[vertices_offset + 0].xyz, - lights.polygon_vertices[vertices_offset + selected - 1].xyz, - lights.polygon_vertices[vertices_offset + selected].xyz, + lights.m.polygon_vertices[vertices_offset + 0].xyz, + lights.m.polygon_vertices[vertices_offset + selected - 1].xyz, + lights.m.polygon_vertices[vertices_offset + selected].xyz, rnd) - P; const vec3 light_dir_n = normalize(light_dir); return vec4(light_dir_n, total_contrib); @@ -113,7 +113,7 @@ vec4 getPolygonLightSampleProjected(vec3 view_dir, SampleContext ctx, const Poly uint vertices_count = poly.vertices_count_offset >> 16; for (uint i = 0; i < vertices_count; ++i) { - clipped[i] = ctx.world_to_shading * vec4(lights.polygon_vertices[vertices_offset + i].xyz, 1.); + clipped[i] = ctx.world_to_shading * vec4(lights.m.polygon_vertices[vertices_offset + i].xyz, 1.); } vertices_count = clip_polygon(vertices_count, clipped); @@ -138,7 +138,7 @@ vec4 getPolygonLightSampleSolid(vec3 P, vec3 view_dir, SampleContext ctx, const uint vertices_count = poly.vertices_count_offset >> 16; for (uint i = 0; i < vertices_count; ++i) { - clipped[i] = lights.polygon_vertices[vertices_offset + i].xyz; + clipped[i] = lights.m.polygon_vertices[vertices_offset + i].xyz; } #define DONT_CLIP @@ -198,7 +198,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate const uint selected = uint(light_grid.clusters[cluster_index].polygons[rand_range(num_polygons)]); - const PolygonLight poly = lights.polygons[selected]; + const PolygonLight poly = lights.m.polygons[selected]; const SampleContext ctx = buildSampleContext(P, N, view_dir); sampleSinglePolygonLight(P, N, view_dir, ctx, material, poly, diffuse, specular); @@ -218,10 +218,10 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate for (uint i = 0; i < num_polygons; ++i) { const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); #else - for (uint index = 0; index < lights.num_polygons; ++index) { + for (uint index = 0; index < lights.m.num_polygons; ++index) { #endif - const PolygonLight poly = lights.polygons[index]; + const PolygonLight poly = lights.m.polygons[index]; const float plane_dist = dot(poly.plane, vec4(P, 1.f)); @@ -259,7 +259,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate // TODO move this to pickPolygonLight function const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); #else - const uint num_polygons = lights.num_polygons; + const uint num_polygons = lights.m.num_polygons; #endif uint selected = 0; @@ -272,7 +272,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate const uint index = i; #endif - const PolygonLight poly = lights.polygons[index]; + const PolygonLight poly = lights.m.polygons[index]; const vec3 dir = poly.center - P; const vec3 light_dir = normalize(dir); @@ -299,7 +299,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate } #if 0 - const PolygonLight poly = lights.polygons[selected - 1]; + const PolygonLight poly = lights.m.polygons[selected - 1]; const vec3 emissive = poly.emissive; vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); evalSplitBRDF(N, normalize(poly.center-P), view_dir, material, poly_diffuse, poly_specular); @@ -307,7 +307,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate specular += throughput * emissive * total_contrib; #else const SampleContext ctx = buildSampleContext(P, N, view_dir); - const PolygonLight poly = lights.polygons[selected - 1]; + const PolygonLight poly = lights.m.polygons[selected - 1]; #ifdef PROJECTED const vec4 light_sample_dir = getPolygonLightSampleProjected(view_dir, ctx, poly); #else From a5d5167f1d1fe3093c7b04ccd5f55fb24fd631fb Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 29 Oct 2022 15:34:37 -0700 Subject: [PATCH 480/548] meat: use bindings from meat file also compare them with currently hardcoded values to make sure there are no regressions next: - remove old hardcoded semantic/binding tables - remove comparisons - extract formats from spirv --- ref_vk/ray_pass.c | 6 +++ ref_vk/ray_resources.c | 73 ++++++++++++++++++++++++++++++++++ ref_vk/ray_resources.h | 8 ++++ ref_vk/shaders/denoiser.comp | 8 +++- ref_vk/vk_denoiser.c | 5 ++- ref_vk/vk_meatpipe.c | 77 +++++++++++++++++++++++++++++++----- 6 files changed, 163 insertions(+), 14 deletions(-) diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c index aa3f8295..bcf93e76 100644 --- a/ref_vk/ray_pass.c +++ b/ref_vk/ray_pass.c @@ -53,6 +53,11 @@ static void finalizePassDescriptors( ray_pass_t *header, const ray_pass_layout_t header->desc.binding_semantics = Mem_Malloc(vk_core.pool, semantics_size); memcpy(header->desc.binding_semantics, layout->bindings_semantics, semantics_size); + const size_t bindings_size = sizeof(layout->bindings[0]) * layout->bindings_count; + VkDescriptorSetLayoutBinding *bindings = Mem_Malloc(vk_core.pool, bindings_size); + memcpy(bindings, layout->bindings, bindings_size); + header->desc.riptors.bindings = bindings; + header->desc.riptors.values = Mem_Malloc(vk_core.pool, sizeof(header->desc.riptors.values[0]) * layout->bindings_count); } @@ -224,6 +229,7 @@ void RayPassDestroy( struct ray_pass_s *pass ) { VK_DescriptorsDestroy(&pass->desc.riptors); Mem_Free(pass->desc.riptors.values); Mem_Free(pass->desc.binding_semantics); + Mem_Free((void*)pass->desc.riptors.bindings); Mem_Free(pass); } diff --git a/ref_vk/ray_resources.c b/ref_vk/ray_resources.c index c547e18d..8e3d7b1f 100644 --- a/ref_vk/ray_resources.c +++ b/ref_vk/ray_resources.c @@ -95,3 +95,76 @@ void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill) { 0, 0, NULL, 0, NULL, image_barriers_count, image_barriers); } } + +#define FIXME_DESC(name_, semantic_, type_, count_) \ + { .name = name_, \ + .desc.semantic = semantic_, \ + .desc.type = VK_DESCRIPTOR_TYPE_##type_, \ + .desc.count = count_, \ + } + +#define FIXME_DESC_IN(name_, semantic_, type_, count_) \ + { .name = name_, \ + .desc.semantic = (RayResource_##semantic_ + 1), \ + .desc.type = VK_DESCRIPTOR_TYPE_##type_, \ + .desc.count = count_, \ + } + +#define FIXME_DESC_OUT(name_, semantic_, type_, count_) \ + { .name = name_, \ + .desc.semantic = -(RayResource_##semantic_ + 1), \ + .desc.type = VK_DESCRIPTOR_TYPE_##type_, \ + .desc.count = count_, \ + } + +static const struct { + const char *name; + ray_resource_binding_desc_fixme_t desc; +} fixme_descs[] = { + FIXME_DESC_IN("ubo", ubo, UNIFORM_BUFFER, 1), + FIXME_DESC_IN("tlas", tlas, ACCELERATION_STRUCTURE_KHR, 1), + FIXME_DESC_IN("kusochki", kusochki, STORAGE_BUFFER, 1), + FIXME_DESC_IN("indices", indices, STORAGE_BUFFER, 1), + FIXME_DESC_IN("vertices", vertices, STORAGE_BUFFER, 1), + FIXME_DESC_IN("textures", all_textures, COMBINED_IMAGE_SAMPLER, MAX_TEXTURES), + FIXME_DESC_IN("skybox", skybox, COMBINED_IMAGE_SAMPLER, 1), + FIXME_DESC_IN("lights", lights, STORAGE_BUFFER, 1), + FIXME_DESC_IN("light_clusters", light_clusters, STORAGE_BUFFER, 1), + + FIXME_DESC_OUT("out_image_base_color_a", base_color_a, STORAGE_IMAGE, 1), + FIXME_DESC_IN("src_base_color", base_color_a, STORAGE_IMAGE, 1), + + FIXME_DESC_OUT("out_image_position_t", position_t, STORAGE_IMAGE, 1), + FIXME_DESC_IN("position_t", position_t, STORAGE_IMAGE, 1), + + FIXME_DESC_OUT("out_image_normals_gs", normals_gs, STORAGE_IMAGE, 1), + FIXME_DESC_IN("normals_gs", normals_gs, STORAGE_IMAGE, 1), + + FIXME_DESC_OUT("out_image_material_rmxx", material_rmxx, STORAGE_IMAGE, 1), + FIXME_DESC_IN("material_rmxx", material_rmxx, STORAGE_IMAGE, 1), + + FIXME_DESC_OUT("out_image_emissive", emissive, STORAGE_IMAGE, 1), + FIXME_DESC_IN("src_emissive", emissive, STORAGE_IMAGE, 1), + + FIXME_DESC_OUT("out_image_light_poly_diffuse", light_poly_diffuse, STORAGE_IMAGE, 1), + FIXME_DESC_IN("src_light_direct_poly_diffuse", light_poly_diffuse, STORAGE_IMAGE, 1), + + FIXME_DESC_OUT("out_image_light_poly_specular", light_poly_specular, STORAGE_IMAGE, 1), + FIXME_DESC_IN("src_light_direct_poly_specular", light_poly_specular, STORAGE_IMAGE, 1), + + FIXME_DESC_OUT("out_image_light_point_diffuse", light_point_diffuse, STORAGE_IMAGE, 1), + FIXME_DESC_IN("src_light_direct_point_diffuse", light_point_diffuse, STORAGE_IMAGE, 1), + + FIXME_DESC_OUT("out_image_light_point_specular", light_point_specular, STORAGE_IMAGE, 1), + FIXME_DESC_IN("src_light_direct_point_specular", light_point_specular, STORAGE_IMAGE, 1), + + FIXME_DESC_OUT("dest", denoised, STORAGE_IMAGE, 1), +}; + +const ray_resource_binding_desc_fixme_t *RayResouceGetBindingForName_FIXME(const char *name) { + for (int i = 0; i < COUNTOF(fixme_descs); ++i) { + if (strcmp(name, fixme_descs[i].name) == 0) + return &fixme_descs[i].desc; + } + return NULL; +} diff --git a/ref_vk/ray_resources.h b/ref_vk/ray_resources.h index dd613f16..8c2e59d1 100644 --- a/ref_vk/ray_resources.h +++ b/ref_vk/ray_resources.h @@ -59,3 +59,11 @@ typedef struct { } ray_resources_fill_t; void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill); + +typedef struct { + int semantic; + VkDescriptorType type; + int count; +} ray_resource_binding_desc_fixme_t; + +const ray_resource_binding_desc_fixme_t *RayResouceGetBindingForName_FIXME(const char *name); diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index c8646dc8..f6ddb35a 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -17,8 +17,8 @@ layout(set = 0, binding = 4, rgba16f) uniform readonly image2D src_light_direct_ layout(set = 0, binding = 5, rgba16f) uniform readonly image2D src_light_direct_point_specular; layout(set = 0, binding = 6, rgba16f) uniform readonly image2D src_emissive; -layout(set = 0, binding = 7, rgba32f) uniform readonly image2D src_position_t; -layout(set = 0, binding = 8, rgba16f) uniform readonly image2D src_normals_gs; +//layout(set = 0, binding = 7, rgba32f) uniform readonly image2D src_position_t; +//layout(set = 0, binding = 8, rgba16f) uniform readonly image2D src_normals_gs; //layout(set = 0, binding = 2, rgba16f) uniform readonly image2D src_light_direct_poly_diffuse; /* layout(set = 0, binding = 3, rgba16f) uniform readonly image2D src_specular; */ @@ -56,11 +56,13 @@ vec3 reinhard02(vec3 c, vec3 Cwhite2) { float normpdf2(in float x2, in float sigma) { return 0.39894*exp(-0.5*x2/(sigma*sigma))/sigma; } float normpdf(in float x, in float sigma) { return normpdf2(x*x, sigma); } +/* void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { const vec4 n = imageLoad(src_normals_gs, uv); geometry_normal = normalDecode(n.xy); shading_normal = normalDecode(n.zw); } +*/ void main() { ivec2 res = ivec2(imageSize(src_base_color)); @@ -90,8 +92,10 @@ void main() { //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_light_direct_poly, pix).rgb), 0.)); return; //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_specular, pix).rgb), 0.)); return; + /* vec3 geometry_normal, shading_normal; readNormals(pix, geometry_normal, shading_normal); + */ //imageStore(dest, pix, vec4(.5 + geometry_normal * .5, 0.)); return; diff --git a/ref_vk/vk_denoiser.c b/ref_vk/vk_denoiser.c index 43f6d8af..593e99cc 100644 --- a/ref_vk/vk_denoiser.c +++ b/ref_vk/vk_denoiser.c @@ -11,8 +11,9 @@ X(4, light_point_diffuse) \ X(5, light_point_specular) \ X(6, emissive) \ - X(7, position_t) \ - X(8, normals_gs) \ + + //X(7, position_t) \ + //X(8, normals_gs) \ static const VkDescriptorSetLayoutBinding bindings[] = { #define BIND_IMAGE(index, name) \ diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 97c63737..01335e0a 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -1,6 +1,7 @@ #include "vk_meatpipe.h" #include "vk_pipeline.h" +#include "ray_resources.h" #include "ray_pass.h" #include "vk_common.h" @@ -107,7 +108,31 @@ static ray_pass_layout_t FIXME_getLayoutFor(const char *name) { return (ray_pass_layout_t){0}; } -static struct ray_pass_s *pipelineLoadCompute(load_context_t *ctx, int i, const char *name) { +static qboolean FIXME_compareLayouts(const ray_pass_layout_t *a, const ray_pass_layout_t *b) { + ASSERT(a->bindings_count == b->bindings_count); + + for (int i = 0; i < a->bindings_count; ++i) { + qboolean found = false; + const VkDescriptorSetLayoutBinding *ab = a->bindings + i; + for (int j = 0; j < b->bindings_count; ++j) { + const VkDescriptorSetLayoutBinding *bb = b->bindings + j; + if (ab->binding == bb->binding) { + ASSERT(!found); + found = true; + ASSERT(ab->descriptorType == bb->descriptorType); + ASSERT(ab->descriptorCount == bb->descriptorCount); + ASSERT(ab->stageFlags == bb->stageFlags); + ASSERT(ab->pImmutableSamplers == bb->pImmutableSamplers); + ASSERT(a->bindings_semantics[i] == b->bindings_semantics[j]); + } + } + ASSERT(found); + } + + return true; +} + +static struct ray_pass_s *pipelineLoadCompute(load_context_t *ctx, int i, const char *name, const ray_pass_layout_t *layout) { const uint32_t shader_comp = READ_U32_RETURN(NULL, "Couldn't read comp shader for %d %s", i, name); if (shader_comp >= ctx->shaders_count) { @@ -115,20 +140,23 @@ static struct ray_pass_s *pipelineLoadCompute(load_context_t *ctx, int i, const return NULL; } + const ray_pass_layout_t known_layout = FIXME_getLayoutFor(name); + ASSERT(FIXME_compareLayouts(&known_layout, layout)); + const ray_pass_create_compute_t rpcc = { .debug_name = name, - .layout = FIXME_getLayoutFor(name), + .layout = *layout, .shader_module = ctx->shaders[shader_comp], }; return RayPassCreateCompute(&rpcc); } -static struct ray_pass_s *pipelineLoadRT(load_context_t *ctx, int i, const char *name) { +static struct ray_pass_s *pipelineLoadRT(load_context_t *ctx, int i, const char *name, const ray_pass_layout_t *layout) { ray_pass_p ret = NULL; ray_pass_create_tracing_t rpct = { .debug_name = name, - .layout = FIXME_getLayoutFor(name), + .layout = *layout, }; // FIXME bounds check shader indices @@ -172,9 +200,15 @@ finalize: return ret; } -static qboolean readBindings(load_context_t *ctx) { +#define MAX_BINDINGS 32 +static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindings, int* semantics) { const int count = READ_U32_RETURN(false, "Coulnd't read bindings count"); + if (count > MAX_BINDINGS) { + gEngine.Con_Printf(S_ERROR "Too many binding (%d), max: %d\n", count, MAX_BINDINGS); + return 0; + } + for (int i = 0; i < count; ++i) { char name[64]; READ_STR_RETURN(false, name, "Couldn't read binding name"); @@ -182,10 +216,23 @@ static qboolean readBindings(load_context_t *ctx) { const uint32_t binding = READ_U32_RETURN(false, "Couldn't read binding for binding %s", name); const uint32_t stages = READ_U32_RETURN(false, "Couldn't read stages for binding %s", name); - gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x\n", i, name, descriptor_set, binding, stages); + const ray_resource_binding_desc_fixme_t *binding_fixme = RayResouceGetBindingForName_FIXME(name); + if (!binding_fixme) + return 0; + + bindings[i] = (VkDescriptorSetLayoutBinding){ + .binding = binding, + .descriptorType = binding_fixme->type, + .descriptorCount = binding_fixme->count, + .stageFlags = stages, + .pImmutableSamplers = NULL, + }; + semantics[i] = binding_fixme->semantic; + + gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x type=%d semantic=%d\n", i, name, descriptor_set, binding, stages, binding_fixme->type, binding_fixme->semantic); } - return true; + return count; } static struct ray_pass_s *pipelineLoad(load_context_t *ctx, int i) { @@ -196,7 +243,17 @@ static struct ray_pass_s *pipelineLoad(load_context_t *ctx, int i) { gEngine.Con_Reportf("%d: loading pipeline %s\n", i, name); - if (!readBindings(ctx)) { + int semantics[MAX_BINDINGS]; + VkDescriptorSetLayoutBinding bindings[MAX_BINDINGS]; + + const ray_pass_layout_t layout = { + .bindings_semantics = semantics, + .bindings = bindings, + .bindings_count = readBindings(ctx, bindings, semantics), + .push_constants = {0}, + }; + + if (!layout.bindings_count) { gEngine.Con_Printf(S_ERROR "Couldn't read bindings for pipeline %s\n", name); return NULL; } @@ -206,9 +263,9 @@ static struct ray_pass_s *pipelineLoad(load_context_t *ctx, int i) { switch (type) { case PIPELINE_COMPUTE: - return pipelineLoadCompute(ctx, i, name); + return pipelineLoadCompute(ctx, i, name, &layout); case PIPELINE_RAYTRACING: - return pipelineLoadRT(ctx, i, name); + return pipelineLoadRT(ctx, i, name, &layout); default: gEngine.Con_Printf(S_ERROR "Unexpected pipeline type %d\n", type); return NULL; From 31a1aef6abd51fdf66f4322d2d74260842fbd452 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 29 Oct 2022 15:43:28 -0700 Subject: [PATCH 481/548] fixup linux --- ref_vk/vk_meatpipe.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 01335e0a..5463e3f3 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -236,6 +236,14 @@ static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindi } static struct ray_pass_s *pipelineLoad(load_context_t *ctx, int i) { + int semantics[MAX_BINDINGS]; + VkDescriptorSetLayoutBinding bindings[MAX_BINDINGS]; + ray_pass_layout_t layout = { + .bindings_semantics = semantics, + .bindings = bindings, + .push_constants = {0}, + }; + const uint32_t type = READ_U32("Couldn't read pipeline %d type", i); char name[64]; @@ -243,16 +251,7 @@ static struct ray_pass_s *pipelineLoad(load_context_t *ctx, int i) { gEngine.Con_Reportf("%d: loading pipeline %s\n", i, name); - int semantics[MAX_BINDINGS]; - VkDescriptorSetLayoutBinding bindings[MAX_BINDINGS]; - - const ray_pass_layout_t layout = { - .bindings_semantics = semantics, - .bindings = bindings, - .bindings_count = readBindings(ctx, bindings, semantics), - .push_constants = {0}, - }; - + layout.bindings_count = readBindings(ctx, bindings, semantics); if (!layout.bindings_count) { gEngine.Con_Printf(S_ERROR "Couldn't read bindings for pipeline %s\n", name); return NULL; From 3f01b95459de56d610fd41aeebc455fd2bf4b269 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 29 Oct 2022 15:53:08 -0700 Subject: [PATCH 482/548] fixup missing resource description --- ref_vk/ray_resources.c | 1 + ref_vk/vk_meatpipe.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ref_vk/ray_resources.c b/ref_vk/ray_resources.c index 8e3d7b1f..d494cd95 100644 --- a/ref_vk/ray_resources.c +++ b/ref_vk/ray_resources.c @@ -130,6 +130,7 @@ static const struct { FIXME_DESC_IN("skybox", skybox, COMBINED_IMAGE_SAMPLER, 1), FIXME_DESC_IN("lights", lights, STORAGE_BUFFER, 1), FIXME_DESC_IN("light_clusters", light_clusters, STORAGE_BUFFER, 1), + FIXME_DESC_IN("light_grid", light_clusters, STORAGE_BUFFER, 1), FIXME_DESC_OUT("out_image_base_color_a", base_color_a, STORAGE_IMAGE, 1), FIXME_DESC_IN("src_base_color", base_color_a, STORAGE_IMAGE, 1), diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 5463e3f3..087baf44 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -217,8 +217,10 @@ static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindi const uint32_t stages = READ_U32_RETURN(false, "Couldn't read stages for binding %s", name); const ray_resource_binding_desc_fixme_t *binding_fixme = RayResouceGetBindingForName_FIXME(name); - if (!binding_fixme) + if (!binding_fixme) { + gEngine.Con_Printf(S_ERROR "Couldn't find fixme desc for binding %s\n", name); return 0; + } bindings[i] = (VkDescriptorSetLayoutBinding){ .binding = binding, From 9fded1e4cada9a01eb6cc2576e970cfe088a473e Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 5 Nov 2022 11:33:31 -0700 Subject: [PATCH 483/548] vk: remove old hardcoded bindings definitions --- ref_vk/vk_denoiser.c | 45 ----------------------- ref_vk/vk_meatpipe.c | 46 ------------------------ ref_vk/vk_ray_light_direct.c | 70 ------------------------------------ ref_vk/vk_ray_primary.c | 45 ----------------------- 4 files changed, 206 deletions(-) delete mode 100644 ref_vk/vk_denoiser.c delete mode 100644 ref_vk/vk_ray_light_direct.c delete mode 100644 ref_vk/vk_ray_primary.c diff --git a/ref_vk/vk_denoiser.c b/ref_vk/vk_denoiser.c deleted file mode 100644 index 593e99cc..00000000 --- a/ref_vk/vk_denoiser.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "ray_resources.h" -#include "ray_pass.h" - -#define LIST_OUTPUTS(X) \ - X(0, denoised) \ - -#define LIST_INPUTS(X) \ - X(1, base_color_a) \ - X(2, light_poly_diffuse) \ - X(3, light_poly_specular) \ - X(4, light_point_diffuse) \ - X(5, light_point_specular) \ - X(6, emissive) \ - - //X(7, position_t) \ - //X(8, normals_gs) \ - -static const VkDescriptorSetLayoutBinding bindings[] = { -#define BIND_IMAGE(index, name) \ - { \ - .binding = index, \ - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, \ - .descriptorCount = 1, \ - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, \ - }, - LIST_OUTPUTS(BIND_IMAGE) - LIST_INPUTS(BIND_IMAGE) -#undef BIND_IMAGE -}; - -static const int semantics[] = { -#define IN(index, name, ...) (RayResource_##name + 1), -#define OUT(index, name, ...) -(RayResource_##name + 1), - LIST_OUTPUTS(OUT) - LIST_INPUTS(IN) -#undef IN -#undef OUT -}; - -const ray_pass_layout_t denoiser_layout_fixme = { - .bindings = bindings, - .bindings_semantics = semantics, - .bindings_count = COUNTOF(bindings), - .push_constants = {0}, -}; diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 087baf44..48bcb615 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -89,49 +89,6 @@ int curReadStr(cursor_t *cur, char* out, int out_size) { #define NO_SHADER 0xffffffff -extern const ray_pass_layout_t ray_primary_layout_fixme; -extern const ray_pass_layout_t light_direct_poly_layout_fixme; -extern const ray_pass_layout_t light_direct_point_layout_fixme; -extern const ray_pass_layout_t denoiser_layout_fixme; - -static ray_pass_layout_t FIXME_getLayoutFor(const char *name) { - if (strcmp(name, "primary_ray") == 0) - return ray_primary_layout_fixme; - if (strcmp(name, "light_direct_poly") == 0) - return light_direct_poly_layout_fixme; - if (strcmp(name, "light_direct_point") == 0) - return light_direct_point_layout_fixme; - if (strcmp(name, "denoiser") == 0) - return denoiser_layout_fixme; - - gEngine.Host_Error("Unexpected pass name %s", name); - return (ray_pass_layout_t){0}; -} - -static qboolean FIXME_compareLayouts(const ray_pass_layout_t *a, const ray_pass_layout_t *b) { - ASSERT(a->bindings_count == b->bindings_count); - - for (int i = 0; i < a->bindings_count; ++i) { - qboolean found = false; - const VkDescriptorSetLayoutBinding *ab = a->bindings + i; - for (int j = 0; j < b->bindings_count; ++j) { - const VkDescriptorSetLayoutBinding *bb = b->bindings + j; - if (ab->binding == bb->binding) { - ASSERT(!found); - found = true; - ASSERT(ab->descriptorType == bb->descriptorType); - ASSERT(ab->descriptorCount == bb->descriptorCount); - ASSERT(ab->stageFlags == bb->stageFlags); - ASSERT(ab->pImmutableSamplers == bb->pImmutableSamplers); - ASSERT(a->bindings_semantics[i] == b->bindings_semantics[j]); - } - } - ASSERT(found); - } - - return true; -} - static struct ray_pass_s *pipelineLoadCompute(load_context_t *ctx, int i, const char *name, const ray_pass_layout_t *layout) { const uint32_t shader_comp = READ_U32_RETURN(NULL, "Couldn't read comp shader for %d %s", i, name); @@ -140,9 +97,6 @@ static struct ray_pass_s *pipelineLoadCompute(load_context_t *ctx, int i, const return NULL; } - const ray_pass_layout_t known_layout = FIXME_getLayoutFor(name); - ASSERT(FIXME_compareLayouts(&known_layout, layout)); - const ray_pass_create_compute_t rpcc = { .debug_name = name, .layout = *layout, diff --git a/ref_vk/vk_ray_light_direct.c b/ref_vk/vk_ray_light_direct.c deleted file mode 100644 index 835343fa..00000000 --- a/ref_vk/vk_ray_light_direct.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "ray_resources.h" -#include "ray_pass.h" - -#define LIST_SCENE_BINDINGS(X) \ - X(1, tlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1) \ - X(2, ubo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1) \ - X(3, kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ - X(4, indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ - X(5, vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ - X(6, all_textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES) \ - X(7, lights, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ - X(8, light_clusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) \ - -#define LIST_COMMON_BINDINGS(X) \ - LIST_SCENE_BINDINGS(X) \ - RAY_LIGHT_DIRECT_INPUTS(X) - -// FIXME more conservative shader stages -#define INIT_BINDING(index, name, type, count) \ - { \ - .binding = index, \ - .descriptorType = type, \ - .descriptorCount = count, \ - .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, \ - }, - -#define INIT_IMAGE(index, name, ...) INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1) - -static const VkDescriptorSetLayoutBinding bindings[] = { - LIST_SCENE_BINDINGS(INIT_BINDING) - RAY_LIGHT_DIRECT_INPUTS(INIT_IMAGE) - - // FIXME it's an artifact that point and poly outputs have same bindings indices - RAY_LIGHT_DIRECT_POLY_OUTPUTS(INIT_IMAGE) -}; - -#undef INIT_IMAGE -#undef INIT_BINDING - -static const int semantics_poly[] = { -#define IN(index, name, ...) (RayResource_##name + 1), -#define OUT(index, name, ...) -(RayResource_##name + 1), - LIST_COMMON_BINDINGS(IN) - RAY_LIGHT_DIRECT_POLY_OUTPUTS(OUT) -#undef IN -#undef OUT -}; - -static const int semantics_point[] = { -#define IN(index, name, ...) (RayResource_##name + 1), -#define OUT(index, name, ...) -(RayResource_##name + 1), - LIST_COMMON_BINDINGS(IN) - RAY_LIGHT_DIRECT_POINT_OUTPUTS(OUT) -#undef IN -#undef OUT -}; - -const ray_pass_layout_t light_direct_poly_layout_fixme = { - .bindings = bindings, - .bindings_semantics = semantics_poly, - .bindings_count = COUNTOF(bindings), - .push_constants = {0}, -}; - -const ray_pass_layout_t light_direct_point_layout_fixme = { - .bindings = bindings, - .bindings_semantics = semantics_point, - .bindings_count = COUNTOF(bindings), - .push_constants = {0}, -}; diff --git a/ref_vk/vk_ray_primary.c b/ref_vk/vk_ray_primary.c deleted file mode 100644 index 216ff8fe..00000000 --- a/ref_vk/vk_ray_primary.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "ray_resources.h" -#include "ray_pass.h" - -#define LIST_COMMON_BINDINGS(X) \ - X(1, tlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR) \ - X(2, ubo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR) \ - X(3, kusochki, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR) \ - X(4, indices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR) \ - X(5, vertices, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR) \ - X(6, all_textures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_TEXTURES, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR) \ - X(7, skybox, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR) \ - -static const VkDescriptorSetLayoutBinding bindings[] = { -#define INIT_BINDING(index, name, type, count, stages) \ - { \ - .binding = index, \ - .descriptorType = type, \ - .descriptorCount = count, \ - .stageFlags = stages, \ - }, -#define INIT_IMAGE(index, name, ...) \ - INIT_BINDING(index, name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR) - - LIST_COMMON_BINDINGS(INIT_BINDING) - RAY_PRIMARY_OUTPUTS(INIT_IMAGE) - -#undef INIT_IMAGE -#undef INIT_BINDING -}; - -static const int semantics[] = { -#define IN(index, name, ...) (RayResource_##name + 1), -#define OUT(index, name, ...) -(RayResource_##name + 1), - LIST_COMMON_BINDINGS(IN) - RAY_PRIMARY_OUTPUTS(OUT) -#undef IN -#undef OUT -}; - -const ray_pass_layout_t ray_primary_layout_fixme = { - .bindings = bindings, - .bindings_semantics = semantics, - .bindings_count = COUNTOF(bindings), - .push_constants = {0}, -}; From f5779956c958781d1b53336db0debb1fbd5e6787 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 5 Nov 2022 12:33:07 -0700 Subject: [PATCH 484/548] meat: recover in/out from bindings directly sebastian detects out_ prefix in binding name, writes it as write_bit in binding header meatpipe then reads it and adjusts semantic accordingly --- ref_vk/ray_resources.c | 74 +++++++-------------- ref_vk/sebastian.py | 16 ++++- ref_vk/shaders/denoiser.comp | 96 ++++++++++++++-------------- ref_vk/shaders/ray_light_direct.glsl | 10 +-- ref_vk/shaders/ray_primary.rgen | 12 ++-- ref_vk/vk_meatpipe.c | 10 ++- 6 files changed, 101 insertions(+), 117 deletions(-) diff --git a/ref_vk/ray_resources.c b/ref_vk/ray_resources.c index d494cd95..0ce18ae4 100644 --- a/ref_vk/ray_resources.c +++ b/ref_vk/ray_resources.c @@ -97,69 +97,39 @@ void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill) { } #define FIXME_DESC(name_, semantic_, type_, count_) \ - { .name = name_, \ - .desc.semantic = semantic_, \ - .desc.type = VK_DESCRIPTOR_TYPE_##type_, \ - .desc.count = count_, \ - } - -#define FIXME_DESC_IN(name_, semantic_, type_, count_) \ { .name = name_, \ .desc.semantic = (RayResource_##semantic_ + 1), \ .desc.type = VK_DESCRIPTOR_TYPE_##type_, \ .desc.count = count_, \ } -#define FIXME_DESC_OUT(name_, semantic_, type_, count_) \ - { .name = name_, \ - .desc.semantic = -(RayResource_##semantic_ + 1), \ - .desc.type = VK_DESCRIPTOR_TYPE_##type_, \ - .desc.count = count_, \ - } - static const struct { const char *name; ray_resource_binding_desc_fixme_t desc; } fixme_descs[] = { - FIXME_DESC_IN("ubo", ubo, UNIFORM_BUFFER, 1), - FIXME_DESC_IN("tlas", tlas, ACCELERATION_STRUCTURE_KHR, 1), - FIXME_DESC_IN("kusochki", kusochki, STORAGE_BUFFER, 1), - FIXME_DESC_IN("indices", indices, STORAGE_BUFFER, 1), - FIXME_DESC_IN("vertices", vertices, STORAGE_BUFFER, 1), - FIXME_DESC_IN("textures", all_textures, COMBINED_IMAGE_SAMPLER, MAX_TEXTURES), - FIXME_DESC_IN("skybox", skybox, COMBINED_IMAGE_SAMPLER, 1), - FIXME_DESC_IN("lights", lights, STORAGE_BUFFER, 1), - FIXME_DESC_IN("light_clusters", light_clusters, STORAGE_BUFFER, 1), - FIXME_DESC_IN("light_grid", light_clusters, STORAGE_BUFFER, 1), + // External + FIXME_DESC("ubo", ubo, UNIFORM_BUFFER, 1), + FIXME_DESC("tlas", tlas, ACCELERATION_STRUCTURE_KHR, 1), + FIXME_DESC("kusochki", kusochki, STORAGE_BUFFER, 1), + FIXME_DESC("indices", indices, STORAGE_BUFFER, 1), + FIXME_DESC("vertices", vertices, STORAGE_BUFFER, 1), + FIXME_DESC("textures", all_textures, COMBINED_IMAGE_SAMPLER, MAX_TEXTURES), + FIXME_DESC("skybox", skybox, COMBINED_IMAGE_SAMPLER, 1), + FIXME_DESC("lights", lights, STORAGE_BUFFER, 1), + FIXME_DESC("light_clusters", light_clusters, STORAGE_BUFFER, 1), + FIXME_DESC("light_grid", light_clusters, STORAGE_BUFFER, 1), + FIXME_DESC("dest", denoised, STORAGE_IMAGE, 1), - FIXME_DESC_OUT("out_image_base_color_a", base_color_a, STORAGE_IMAGE, 1), - FIXME_DESC_IN("src_base_color", base_color_a, STORAGE_IMAGE, 1), - - FIXME_DESC_OUT("out_image_position_t", position_t, STORAGE_IMAGE, 1), - FIXME_DESC_IN("position_t", position_t, STORAGE_IMAGE, 1), - - FIXME_DESC_OUT("out_image_normals_gs", normals_gs, STORAGE_IMAGE, 1), - FIXME_DESC_IN("normals_gs", normals_gs, STORAGE_IMAGE, 1), - - FIXME_DESC_OUT("out_image_material_rmxx", material_rmxx, STORAGE_IMAGE, 1), - FIXME_DESC_IN("material_rmxx", material_rmxx, STORAGE_IMAGE, 1), - - FIXME_DESC_OUT("out_image_emissive", emissive, STORAGE_IMAGE, 1), - FIXME_DESC_IN("src_emissive", emissive, STORAGE_IMAGE, 1), - - FIXME_DESC_OUT("out_image_light_poly_diffuse", light_poly_diffuse, STORAGE_IMAGE, 1), - FIXME_DESC_IN("src_light_direct_poly_diffuse", light_poly_diffuse, STORAGE_IMAGE, 1), - - FIXME_DESC_OUT("out_image_light_poly_specular", light_poly_specular, STORAGE_IMAGE, 1), - FIXME_DESC_IN("src_light_direct_poly_specular", light_poly_specular, STORAGE_IMAGE, 1), - - FIXME_DESC_OUT("out_image_light_point_diffuse", light_point_diffuse, STORAGE_IMAGE, 1), - FIXME_DESC_IN("src_light_direct_point_diffuse", light_point_diffuse, STORAGE_IMAGE, 1), - - FIXME_DESC_OUT("out_image_light_point_specular", light_point_specular, STORAGE_IMAGE, 1), - FIXME_DESC_IN("src_light_direct_point_specular", light_point_specular, STORAGE_IMAGE, 1), - - FIXME_DESC_OUT("dest", denoised, STORAGE_IMAGE, 1), + // Internal, temporary + FIXME_DESC("base_color_a", base_color_a, STORAGE_IMAGE, 1), + FIXME_DESC("position_t", position_t, STORAGE_IMAGE, 1), + FIXME_DESC("normals_gs", normals_gs, STORAGE_IMAGE, 1), + FIXME_DESC("material_rmxx", material_rmxx, STORAGE_IMAGE, 1), + FIXME_DESC("emissive", emissive, STORAGE_IMAGE, 1), + FIXME_DESC("light_poly_diffuse", light_poly_diffuse, STORAGE_IMAGE, 1), + FIXME_DESC("light_poly_specular", light_poly_specular, STORAGE_IMAGE, 1), + FIXME_DESC("light_point_diffuse", light_point_diffuse, STORAGE_IMAGE, 1), + FIXME_DESC("light_point_specular", light_point_specular, STORAGE_IMAGE, 1), }; const ray_resource_binding_desc_fixme_t *RayResouceGetBindingForName_FIXME(const char *name) { diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 4558d5a9..c5bc6c83 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -171,17 +171,27 @@ class Binding: STAGE_MESH_BIT_NV = 0x00000080 STAGE_SUBPASS_SHADING_BIT_HUAWEI = 0x00004000 + WRITE_BIT = 0x80000000 + def __init__(self, name, descriptor_set, index, stages): - self.name = name + self.write = name.startswith('out_') + self.name = name.removeprefix('out_') self.index = index self.descriptor_set = descriptor_set self.stages = stages + + assert(self.descriptor_set >= 0) + assert(self.descriptor_set < 255) + + assert(self.index >= 0) + assert(self.index < 255) + #TODO: type, count, etc def serialize(self, out): out.writeString(self.name) - out.writeU32(self.descriptor_set) - out.writeU32(self.index) + header = (Binding.WRITE_BIT if self.write else 0) | (self.descriptor_set << 8) | self.index + out.writeU32(header) out.writeU32(self.stages) class Shader: diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index f6ddb35a..293e9718 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -6,25 +6,25 @@ layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; -layout(set = 0, binding = 0, rgba16f) uniform image2D dest; +layout(set = 0, binding = 0, rgba16f) uniform image2D out_dest; -layout(set = 0, binding = 1, rgba8) uniform readonly image2D src_base_color; +layout(set = 0, binding = 1, rgba8) uniform readonly image2D base_color_a; -layout(set = 0, binding = 2, rgba16f) uniform readonly image2D src_light_direct_poly_diffuse; -layout(set = 0, binding = 3, rgba16f) uniform readonly image2D src_light_direct_poly_specular; +layout(set = 0, binding = 2, rgba16f) uniform readonly image2D light_poly_diffuse; +layout(set = 0, binding = 3, rgba16f) uniform readonly image2D light_poly_specular; -layout(set = 0, binding = 4, rgba16f) uniform readonly image2D src_light_direct_point_diffuse; -layout(set = 0, binding = 5, rgba16f) uniform readonly image2D src_light_direct_point_specular; -layout(set = 0, binding = 6, rgba16f) uniform readonly image2D src_emissive; +layout(set = 0, binding = 4, rgba16f) uniform readonly image2D light_point_diffuse; +layout(set = 0, binding = 5, rgba16f) uniform readonly image2D light_point_specular; +layout(set = 0, binding = 6, rgba16f) uniform readonly image2D emissive; -//layout(set = 0, binding = 7, rgba32f) uniform readonly image2D src_position_t; -//layout(set = 0, binding = 8, rgba16f) uniform readonly image2D src_normals_gs; +//layout(set = 0, binding = 7, rgba32f) uniform readonly image2D position_t; +//layout(set = 0, binding = 8, rgba16f) uniform readonly image2D normals_gs; -//layout(set = 0, binding = 2, rgba16f) uniform readonly image2D src_light_direct_poly_diffuse; -/* layout(set = 0, binding = 3, rgba16f) uniform readonly image2D src_specular; */ -/* layout(set = 0, binding = 4, rgba16f) uniform readonly image2D src_additive; */ -/* layout(set = 0, binding = 5, rgba16f) uniform readonly image2D src_normals; */ -/* layout(set = 0, binding = 6, rgba32f) uniform readonly image2D src_position_t; */ +//layout(set = 0, binding = 2, rgba16f) uniform readonly image2D light_poly_diffuse; +/* layout(set = 0, binding = 3, rgba16f) uniform readonly image2D specular; */ +/* layout(set = 0, binding = 4, rgba16f) uniform readonly image2D additive; */ +/* layout(set = 0, binding = 5, rgba16f) uniform readonly image2D normals; */ +/* layout(set = 0, binding = 6, rgba32f) uniform readonly image2D position_t; */ // Blatantly copypasted from https://www.shadertoy.com/view/XsGfWV vec3 aces_tonemap(vec3 color){ @@ -58,14 +58,14 @@ float normpdf(in float x, in float sigma) { return normpdf2(x*x, sigma); } /* void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { - const vec4 n = imageLoad(src_normals_gs, uv); + const vec4 n = imageLoad(normals_gs, uv); geometry_normal = normalDecode(n.xy); shading_normal = normalDecode(n.zw); } */ void main() { - ivec2 res = ivec2(imageSize(src_base_color)); + ivec2 res = ivec2(imageSize(base_color_a)); ivec2 pix = ivec2(gl_GlobalInvocationID); if (any(greaterThanEqual(pix, res))) { @@ -73,42 +73,42 @@ void main() { } /* if (pix.y < res.y / 3) { */ - /* imageStore(dest, pix, vec4(pow(float(pix.x) / res.x, 2.2))); return; */ + /* imageStore(out_dest, pix, vec4(pow(float(pix.x) / res.x, 2.2))); return; */ /* } else if (pix.y < res.y * 2 / 3) { */ - /* imageStore(dest, pix, vec4(float(pix.x) / res.x)); return; */ + /* imageStore(out_dest, pix, vec4(float(pix.x) / res.x)); return; */ /* } else { */ - /* imageStore(dest, pix, vec4(sqrt(float(pix.x) / res.x))); return; */ + /* imageStore(out_dest, pix, vec4(sqrt(float(pix.x) / res.x))); return; */ /* } */ - //const float material_index = imageLoad(src_light_direct_poly, pix).a; + //const float material_index = imageLoad(light_poly, pix).a; - //imageStore(dest, pix, vec4(aces_tonemap(base_color.rgb), 0.)); return; - //imageStore(dest, pix, vec4((base_color.rgb), 0.)); return; - //imageStore(dest, pix, vec4(fract(imageLoad(src_position_t, pix).rgb / 10.), 0.)); return; - //imageStore(dest, pix, vec4((imageLoad(src_light_direct_poly, pix).rgb), 0.)); return; - //imageStore(dest, pix, vec4((imageLoad(src_light_direct_poly, pix).rgb * base_color.rgb), 0.)); return; - //imageStore(dest, pix, vec4(imageLoad(src_normals, pix)*.5 + .5f)); return; - //imageStore(dest, pix, vec4(imageLoad(src_specular, pix)*.5 + .5f)); return; - //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_light_direct_poly, pix).rgb), 0.)); return; - //imageStore(dest, pix, vec4(aces_tonemap(imageLoad(src_specular, pix).rgb), 0.)); return; + //imageStore(out_dest, pix, vec4(aces_tonemap(base_color_a.rgb), 0.)); return; + //imageStore(out_dest, pix, vec4((base_color_a.rgb), 0.)); return; + //imageStore(out_dest, pix, vec4(fract(imageLoad(position_t, pix).rgb / 10.), 0.)); return; + //imageStore(out_dest, pix, vec4((imageLoad(light_poly, pix).rgb), 0.)); return; + //imageStore(out_dest, pix, vec4((imageLoad(light_poly, pix).rgb * base_color_a.rgb), 0.)); return; + //imageStore(out_dest, pix, vec4(imageLoad(normals, pix)*.5 + .5f)); return; + //imageStore(out_dest, pix, vec4(imageLoad(specular, pix)*.5 + .5f)); return; + //imageStore(out_dest, pix, vec4(aces_tonemap(imageLoad(light_poly, pix).rgb), 0.)); return; + //imageStore(out_dest, pix, vec4(aces_tonemap(imageLoad(specular, pix).rgb), 0.)); return; /* vec3 geometry_normal, shading_normal; readNormals(pix, geometry_normal, shading_normal); */ - //imageStore(dest, pix, vec4(.5 + geometry_normal * .5, 0.)); return; + //imageStore(out_dest, pix, vec4(.5 + geometry_normal * .5, 0.)); return; /* const uint mi = uint(material_index); */ - /* imageStore(dest, pix, vec4(rand3_f01(uvec3(mi,mi+1,mi+2)), 0.)); */ + /* imageStore(out_dest, pix, vec4(rand3_f01(uvec3(mi,mi+1,mi+2)), 0.)); */ /* return; */ #if 1 vec3 colour = vec3(0.); - colour += imageLoad(src_light_direct_poly_diffuse, pix).rgb; - colour += imageLoad(src_light_direct_poly_specular, pix).rgb; - colour += imageLoad(src_light_direct_point_diffuse, pix).rgb; - colour += imageLoad(src_light_direct_point_specular, pix).rgb; + colour += imageLoad(light_poly_diffuse, pix).rgb; + colour += imageLoad(light_poly_specular, pix).rgb; + colour += imageLoad(light_point_diffuse, pix).rgb; + colour += imageLoad(light_point_specular, pix).rgb; #else float total_scale = 0.; vec3 colour = vec3(0.); @@ -116,7 +116,7 @@ void main() { float specular_total_scale = 0.; vec3 speculour = vec3(0.); - const vec4 center_pos = imageLoad(src_position_t, pix); + const vec4 center_pos = imageLoad(position_t, pix); for (int x = -KERNEL_SIZE; x <= KERNEL_SIZE; ++x) for (int y = -KERNEL_SIZE; y <= KERNEL_SIZE; ++y) { const ivec2 p = pix + ivec2(x, y); @@ -126,7 +126,7 @@ void main() { float scale = 1.f; - // const vec4 c = imageLoad(src_light_direct_poly, p); + // const vec4 c = imageLoad(light_poly, p); // if (c.a != material_index) // continue; @@ -136,19 +136,19 @@ void main() { // FIXME also filter by depth, (kusok index?), etc //scale *= smoothstep(.9, 1., dot(sample_geometry_normal, geometry_normal)); - const vec4 sample_pos = imageLoad(src_position_t, p); + const vec4 sample_pos = imageLoad(position_t, p); scale *= smoothstep(4. * center_pos.w / 100., 0., distance(center_pos.xyz, sample_pos.xyz)); if ( scale <= 0. ) continue; vec3 diffuse = vec3(0.); - diffuse += imageLoad(src_light_direct_point_diffuse, p).rgb; - diffuse += imageLoad(src_light_direct_poly_diffuse, p).rgb; + diffuse += imageLoad(light_point_diffuse, p).rgb; + diffuse += imageLoad(light_poly_diffuse, p).rgb; vec3 specular = vec3(0.); - specular += imageLoad(src_light_direct_poly_specular, p).rgb; - specular += imageLoad(src_light_direct_point_specular, p).rgb; + specular += imageLoad(light_poly_specular, p).rgb; + specular += imageLoad(light_point_specular, p).rgb; { const float sigma = KERNEL_SIZE / 2.; @@ -176,10 +176,10 @@ void main() { } #endif - const vec4 base_color = imageLoad(src_base_color, pix); - colour *= SRGBtoLINEAR(base_color.rgb); - colour += imageLoad(src_emissive, pix).rgb; - //colour += imageLoad(src_additive, pix).rgb; + const vec4 base_color_a = imageLoad(base_color_a, pix); + colour *= SRGBtoLINEAR(base_color_a.rgb); + colour += imageLoad(emissive, pix).rgb; + //colour += imageLoad(additive, pix).rgb; // HACK: exposure // TODO: should be dynamic based on previous frames brightness @@ -204,6 +204,6 @@ void main() { } #endif - imageStore(dest, pix, vec4(colour, 0.)); - //imageStore(dest, pix, imageLoad(src_light_direct_poly, pix)); + imageStore(out_dest, pix, vec4(colour, 0.)); + //imageStore(out_dest, pix, imageLoad(light_poly, pix)); } diff --git a/ref_vk/shaders/ray_light_direct.glsl b/ref_vk/shaders/ray_light_direct.glsl index acaa5d90..dde2126d 100644 --- a/ref_vk/shaders/ray_light_direct.glsl +++ b/ref_vk/shaders/ray_light_direct.glsl @@ -11,7 +11,7 @@ #define X(index, name, format) layout(set=0,binding=index,format) uniform readonly image2D name; RAY_LIGHT_DIRECT_INPUTS(X) #undef X -#define X(index, name, format) layout(set=0,binding=index,format) uniform writeonly image2D out_image_##name; +#define X(index, name, format) layout(set=0,binding=index,format) uniform writeonly image2D out_##name; OUTPUTS(X) #undef X @@ -67,10 +67,10 @@ void main() { computeLighting(pos + geometry_normal * .001, shading_normal, throughput, -direction, material, diffuse, specular); #if LIGHT_POINT - imageStore(out_image_light_point_diffuse, pix, vec4(diffuse / 4.0, 0.f)); - imageStore(out_image_light_point_specular, pix, vec4(specular / 4.0, 0.f)); + imageStore(out_light_point_diffuse, pix, vec4(diffuse / 4.0, 0.f)); + imageStore(out_light_point_specular, pix, vec4(specular / 4.0, 0.f)); #else - imageStore(out_image_light_poly_diffuse, pix, vec4(diffuse / 25.0, 0.f)); - imageStore(out_image_light_poly_specular, pix, vec4(specular/ 25.0, 0.f)); + imageStore(out_light_poly_diffuse, pix, vec4(diffuse / 25.0, 0.f)); + imageStore(out_light_poly_specular, pix, vec4(specular/ 25.0, 0.f)); #endif } diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index bcdd57ef..117268e1 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -3,7 +3,7 @@ #include "ray_primary_common.glsl" -#define X(index, name, format) layout(set=0,binding=index,format) uniform image2D out_image_##name; +#define X(index, name, format) layout(set=0,binding=index,format) uniform writeonly image2D out_##name; RAY_PRIMARY_OUTPUTS(X) #undef X @@ -35,9 +35,9 @@ void main() { origin, 0., direction, L, PAYLOAD_LOCATION_PRIMARY); - imageStore(out_image_position_t, ivec2(gl_LaunchIDEXT.xy), payload.hit_t); - imageStore(out_image_base_color_a, ivec2(gl_LaunchIDEXT.xy), payload.base_color_a); - imageStore(out_image_normals_gs, ivec2(gl_LaunchIDEXT.xy), payload.normals_gs); - imageStore(out_image_material_rmxx, ivec2(gl_LaunchIDEXT.xy), payload.material_rmxx); - imageStore(out_image_emissive, ivec2(gl_LaunchIDEXT.xy), payload.emissive); + imageStore(out_position_t, ivec2(gl_LaunchIDEXT.xy), payload.hit_t); + imageStore(out_base_color_a, ivec2(gl_LaunchIDEXT.xy), payload.base_color_a); + imageStore(out_normals_gs, ivec2(gl_LaunchIDEXT.xy), payload.normals_gs); + imageStore(out_material_rmxx, ivec2(gl_LaunchIDEXT.xy), payload.material_rmxx); + imageStore(out_emissive, ivec2(gl_LaunchIDEXT.xy), payload.emissive); } diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 48bcb615..eb20432a 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -166,10 +166,14 @@ static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindi for (int i = 0; i < count; ++i) { char name[64]; READ_STR_RETURN(false, name, "Couldn't read binding name"); - const uint32_t descriptor_set = READ_U32_RETURN(false, "Couldn't read descriptor_set for binding %s", name); - const uint32_t binding = READ_U32_RETURN(false, "Couldn't read binding for binding %s", name); + const uint32_t header = READ_U32_RETURN(false, "Couldn't read header for binding %s", name); const uint32_t stages = READ_U32_RETURN(false, "Couldn't read stages for binding %s", name); +#define BINDING_WRITE_BIT 0x80000000u + const qboolean write = !!(header & BINDING_WRITE_BIT); + const uint32_t descriptor_set = (header >> 8) & 0xffu; + const uint32_t binding = header & 0xffu; + const ray_resource_binding_desc_fixme_t *binding_fixme = RayResouceGetBindingForName_FIXME(name); if (!binding_fixme) { gEngine.Con_Printf(S_ERROR "Couldn't find fixme desc for binding %s\n", name); @@ -183,7 +187,7 @@ static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindi .stageFlags = stages, .pImmutableSamplers = NULL, }; - semantics[i] = binding_fixme->semantic; + semantics[i] = write ? -binding_fixme->semantic : binding_fixme->semantic; gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x type=%d semantic=%d\n", i, name, descriptor_set, binding, stages, binding_fixme->type, binding_fixme->semantic); } From 84cdbdb331071e5ce86a430fc28ababf29bdc6c7 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 5 Nov 2022 14:27:45 -0700 Subject: [PATCH 485/548] seba: extract types from spirv --- ref_vk/TODO.md | 22 ++++++- ref_vk/sebastian.py | 134 +++++++++++++++++++++++++++++++++++++------ ref_vk/vk_meatpipe.c | 7 ++- 3 files changed, 140 insertions(+), 23 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index b3a8eb11..6d5f9330 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,7 +1,23 @@ +# Real next +- [ ] E213: + - [ ] parse binding types +- [ ] E214: + - [ ] extract binding image format + - [ ] integrate sebastian into waf + +>=E215 +- [ ] automatic resource creation + - [ ] resource management refactoring: + - [ ] resource object: name, metadata(type, etc.), producer, status (ready, barriers, etc) + - [ ] resource automatic resolution: prducing, barriers, etc + - [ ] register existing resources (tlas, buffers, temp images, ...) + - [ ] resource destruction + - [ ] create resources on demand + # Programmable render -- [ ] parse spirv -> get bindings with names - - [ ] spirv needs to be compiled with -g, otherwise there are no OpName entries. Need a custom strip util that strips the rest? - - [ ] unnamed uniform blocks are uncomfortable to parse. +- [x] parse spirv -> get bindings with names + - [x] spirv needs to be compiled with -g, otherwise there are no OpName entries. Need a custom strip util that strips the rest? + - [x] unnamed uniform blocks are uncomfortable to parse. - [ ] passes "export" their bindings as detailed resource descriptions: - [ ] images: name, r/w, format, resolution (? not found in spv, needs to be externally supplied) - [ ] buffers: name, r/w, size, type name (?) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index c5bc6c83..56729d90 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -83,12 +83,49 @@ class Serializer: i.serialize(self) class SpirvNode: + # TODO move this to Binding + TYPE_SAMPLER = 0 + TYPE_COMBINED_IMAGE_SAMPLER = 1 + TYPE_SAMPLED_IMAGE = 2 + TYPE_STORAGE_IMAGE = 3 + TYPE_UNIFORM_TEXEL_BUFFER = 4 + TYPE_STORAGE_TEXEL_BUFFER = 5 + TYPE_UNIFORM_BUFFER = 6 + TYPE_STORAGE_BUFFER = 7 + TYPE_UNIFORM_BUFFER_DYNAMIC = 8 + TYPE_STORAGE_BUFFER_DYNAMIC = 9 + TYPE_INPUT_ATTACHMENT = 10 + TYPE_INLINE_UNIFORM_BLOCK = 1000138000 + TYPE_ACCELERATION_STRUCTURE_KHR = 1000150000 + TYPE_MUTABLE_VALVE = 1000351000 + TYPE_SAMPLE_WEIGHT_IMAGE_QCOM = 1000440000 + TYPE_BLOCK_MATCH_IMAGE_QCOM = 1000440001 + def __init__(self): self.descriptor_set = None self.binding = None self.name = None + self.type_node = None + self.type = None + self.storage_class = None pass + def getType(self): + node = self + while node: + #print(f"Checking node {node.name}, {node.storage_class}, {node.type}") + if node.storage_class == spv['StorageClass']['Uniform']: + return SpirvNode.TYPE_UNIFORM_BUFFER + + if node.storage_class == spv['StorageClass']['StorageBuffer']: + return SpirvNode.TYPE_STORAGE_BUFFER + + if node.type: + return node.type + + node = node.type_node + raise Exception(f"Couldn't find type for node {self.name}") + class SpirvContext: def __init__(self, nodes_count): self.nodes = [SpirvNode() for i in range(0, nodes_count)] @@ -98,18 +135,13 @@ class SpirvContext: def getNode(self, index): return self.nodes[index] - #def bindNode(self, index): - #if not index in bindings - #self.bindings[index] - - -def spvOpHandleName(ctx, args): +def spvOpName(ctx, args): index = args[0] name = struct.pack(str(len(args)-1)+'I', *args[1:]).split(b'\x00')[0].decode('utf8') ctx.getNode(index).name = name #print('Name for', args[0], name, len(name)) -def spvOpHandleDecorate(ctx, args): +def spvOpDecorate(ctx, args): node = ctx.getNode(args[0]) decor = args[1] if decor == spv['Decoration']['DescriptorSet']: @@ -119,9 +151,69 @@ def spvOpHandleDecorate(ctx, args): #else: #print('Decor ', id, decor) +def spvOpVariable(ctx, args): + type_node = ctx.getNode(args[0]) + node = ctx.getNode(args[1]) + storage_class = args[2] + + node.type_node = type_node + node.storage_class = storage_class + #node.op_type = 'OpVariable' + #print(node.name, "=(var)>", type_node.name, args[0]) + +def spvOpTypePointer(ctx, args): + node = ctx.getNode(args[0]) + storage_class = args[1] + type_node = ctx.getNode(args[2]) + + node.type_node = type_node + node.storage_class = storage_class + #node.op_type = 'OpTypePointer' + #print(node.name, "=(ptr)>", type_node.name, args[2]) + +def spvOpTypeAccelerationStructureKHR(ctx, args): + node = ctx.getNode(args[0]) + node.type = SpirvNode.TYPE_ACCELERATION_STRUCTURE_KHR + +def spvOpTypeImage(ctx, args): + node = ctx.getNode(args[0]) + sampled_type = args[1] + dim = args[2] + depth = args[3] + arrayed = args[4] + ms = args[5] + sampled = args[6] + image_format = args[7] + node.type = SpirvNode.TYPE_STORAGE_IMAGE if sampled == 0 or sampled == 2 else SpirvNode.TYPE_COMBINED_IMAGE_SAMPLER # FIXME ? + + node.image_format = image_format + qualifier = None if len(args) < 9 else args[8] + + #print(f"{args[0]}: Image(type={sampled_type}, dim={dim}, depth={depth}, arrayed={arrayed}, ms={ms}, sampled={sampled}, image_format={image_format}, qualifier={qualifier})") + +def spvOpTypeSampledImage(ctx, args): + node = ctx.getNode(args[0]) + image_type = ctx.getNode(args[1]) + node.type_node = image_type + node.type = SpirvNode.TYPE_COMBINED_IMAGE_SAMPLER + +def spvOpTypeArray(ctx, args): + node = ctx.getNode(args[0]) + element_type = ctx.getNode(args[1]) + length = args[2] + + node.type_node = element_type + #print(f"{args[0]}: Array(type={args[1]}, length={length})") + spvOpHandlers = { - spvOp['OpName']: spvOpHandleName, - spvOp['OpDecorate']: spvOpHandleDecorate + spvOp['OpName']: spvOpName, + spvOp['OpDecorate']: spvOpDecorate, + spvOp['OpVariable']: spvOpVariable, + spvOp['OpTypePointer']: spvOpTypePointer, + spvOp['OpTypeAccelerationStructureKHR']: spvOpTypeAccelerationStructureKHR, + spvOp['OpTypeImage']: spvOpTypeImage, + spvOp['OpTypeSampledImage']: spvOpTypeSampledImage, + spvOp['OpTypeArray']: spvOpTypeArray, } def parseSpirv(raw_data): @@ -173,12 +265,15 @@ class Binding: WRITE_BIT = 0x80000000 - def __init__(self, name, descriptor_set, index, stages): - self.write = name.startswith('out_') - self.name = name.removeprefix('out_') - self.index = index - self.descriptor_set = descriptor_set - self.stages = stages + def __init__(self, node): + self.write = node.name.startswith('out_') + self.name = node.name.removeprefix('out_') + self.index = node.binding + self.descriptor_set = node.descriptor_set + self.stages = 0 + self.type = node.getType() + + #print(f" {self.name}: ds={self.descriptor_set}, b={self.index}, type={self.type}") assert(self.descriptor_set >= 0) assert(self.descriptor_set < 255) @@ -192,13 +287,14 @@ class Binding: out.writeString(self.name) header = (Binding.WRITE_BIT if self.write else 0) | (self.descriptor_set << 8) | self.index out.writeU32(header) + out.writeU32(self.type) out.writeU32(self.stages) class Shader: def __init__(self, name, file): self.name = name self.raw_data = file - print(name, '=>', len(self.raw_data)) + #print(name, '=>', len(self.raw_data)) self.spirv = parseSpirv(self.raw_data) def __str__(self): @@ -213,7 +309,7 @@ class Shader: for node in self.spirv.nodes: if node.binding == None or node.descriptor_set == None: continue - ret.append(Binding(node.name, node.descriptor_set, node.binding, 0)) + ret.append(Binding(node)) return ret class Shaders: @@ -291,7 +387,9 @@ class Pipeline: return shader def serialize(self, out): - #print(self.__bindings) + #print(self.name) + #for binding in self.__bindings.values(): + #print(f" {binding.name}: ds={binding.descriptor_set}, b={binding.index}, type={binding.type}, stages={binding.stages:#x}") out.writeU32(self.type) out.writeString(self.name) out.writeArray(self.__bindings.values()) diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index eb20432a..88dd01ab 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -167,6 +167,7 @@ static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindi char name[64]; READ_STR_RETURN(false, name, "Couldn't read binding name"); const uint32_t header = READ_U32_RETURN(false, "Couldn't read header for binding %s", name); + const uint32_t type = READ_U32_RETURN(false, "Couldn't read type for binding %s", name); const uint32_t stages = READ_U32_RETURN(false, "Couldn't read stages for binding %s", name); #define BINDING_WRITE_BIT 0x80000000u @@ -180,16 +181,18 @@ static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindi return 0; } + ASSERT(type == binding_fixme->type); + bindings[i] = (VkDescriptorSetLayoutBinding){ .binding = binding, - .descriptorType = binding_fixme->type, + .descriptorType = type, .descriptorCount = binding_fixme->count, .stageFlags = stages, .pImmutableSamplers = NULL, }; semantics[i] = write ? -binding_fixme->semantic : binding_fixme->semantic; - gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x type=%d semantic=%d\n", i, name, descriptor_set, binding, stages, binding_fixme->type, binding_fixme->semantic); + gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x read_type=%d semantic=%d\n", i, name, descriptor_set, binding, stages, type, binding_fixme->semantic); } return count; From 7d591c46dd56407b34ac202ac662177bfb3632e5 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 5 Nov 2022 14:44:17 -0700 Subject: [PATCH 486/548] vk: remove old fixme declared types for bindings --- ref_vk/TODO.md | 5 +++-- ref_vk/ray_resources.c | 43 +++++++++++++++++++++--------------------- ref_vk/ray_resources.h | 1 - ref_vk/vk_meatpipe.c | 4 +--- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index 6d5f9330..484dc51e 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,6 +1,7 @@ # Real next -- [ ] E213: - - [ ] parse binding types +- [x] E213: + - [x] parse binding types + - [x] remove types from resources FIXME - [ ] E214: - [ ] extract binding image format - [ ] integrate sebastian into waf diff --git a/ref_vk/ray_resources.c b/ref_vk/ray_resources.c index 0ce18ae4..7d672d48 100644 --- a/ref_vk/ray_resources.c +++ b/ref_vk/ray_resources.c @@ -96,10 +96,9 @@ void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill) { } } -#define FIXME_DESC(name_, semantic_, type_, count_) \ +#define FIXME_DESC(name_, semantic_, count_) \ { .name = name_, \ .desc.semantic = (RayResource_##semantic_ + 1), \ - .desc.type = VK_DESCRIPTOR_TYPE_##type_, \ .desc.count = count_, \ } @@ -108,28 +107,28 @@ static const struct { ray_resource_binding_desc_fixme_t desc; } fixme_descs[] = { // External - FIXME_DESC("ubo", ubo, UNIFORM_BUFFER, 1), - FIXME_DESC("tlas", tlas, ACCELERATION_STRUCTURE_KHR, 1), - FIXME_DESC("kusochki", kusochki, STORAGE_BUFFER, 1), - FIXME_DESC("indices", indices, STORAGE_BUFFER, 1), - FIXME_DESC("vertices", vertices, STORAGE_BUFFER, 1), - FIXME_DESC("textures", all_textures, COMBINED_IMAGE_SAMPLER, MAX_TEXTURES), - FIXME_DESC("skybox", skybox, COMBINED_IMAGE_SAMPLER, 1), - FIXME_DESC("lights", lights, STORAGE_BUFFER, 1), - FIXME_DESC("light_clusters", light_clusters, STORAGE_BUFFER, 1), - FIXME_DESC("light_grid", light_clusters, STORAGE_BUFFER, 1), - FIXME_DESC("dest", denoised, STORAGE_IMAGE, 1), + FIXME_DESC("ubo", ubo, 1), + FIXME_DESC("tlas", tlas, 1), + FIXME_DESC("kusochki", kusochki, 1), + FIXME_DESC("indices", indices, 1), + FIXME_DESC("vertices", vertices, 1), + FIXME_DESC("textures", all_textures, MAX_TEXTURES), + FIXME_DESC("skybox", skybox, 1), + FIXME_DESC("lights", lights, 1), + FIXME_DESC("light_clusters", light_clusters, 1), + FIXME_DESC("light_grid", light_clusters, 1), + FIXME_DESC("dest", denoised, 1), // Internal, temporary - FIXME_DESC("base_color_a", base_color_a, STORAGE_IMAGE, 1), - FIXME_DESC("position_t", position_t, STORAGE_IMAGE, 1), - FIXME_DESC("normals_gs", normals_gs, STORAGE_IMAGE, 1), - FIXME_DESC("material_rmxx", material_rmxx, STORAGE_IMAGE, 1), - FIXME_DESC("emissive", emissive, STORAGE_IMAGE, 1), - FIXME_DESC("light_poly_diffuse", light_poly_diffuse, STORAGE_IMAGE, 1), - FIXME_DESC("light_poly_specular", light_poly_specular, STORAGE_IMAGE, 1), - FIXME_DESC("light_point_diffuse", light_point_diffuse, STORAGE_IMAGE, 1), - FIXME_DESC("light_point_specular", light_point_specular, STORAGE_IMAGE, 1), + FIXME_DESC("base_color_a", base_color_a, 1), + FIXME_DESC("position_t", position_t, 1), + FIXME_DESC("normals_gs", normals_gs, 1), + FIXME_DESC("material_rmxx", material_rmxx, 1), + FIXME_DESC("emissive", emissive, 1), + FIXME_DESC("light_poly_diffuse", light_poly_diffuse, 1), + FIXME_DESC("light_poly_specular", light_poly_specular, 1), + FIXME_DESC("light_point_diffuse", light_point_diffuse, 1), + FIXME_DESC("light_point_specular", light_point_specular, 1), }; const ray_resource_binding_desc_fixme_t *RayResouceGetBindingForName_FIXME(const char *name) { diff --git a/ref_vk/ray_resources.h b/ref_vk/ray_resources.h index 8c2e59d1..2451fd66 100644 --- a/ref_vk/ray_resources.h +++ b/ref_vk/ray_resources.h @@ -62,7 +62,6 @@ void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill); typedef struct { int semantic; - VkDescriptorType type; int count; } ray_resource_binding_desc_fixme_t; diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 88dd01ab..b4c8c79b 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -181,8 +181,6 @@ static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindi return 0; } - ASSERT(type == binding_fixme->type); - bindings[i] = (VkDescriptorSetLayoutBinding){ .binding = binding, .descriptorType = type, @@ -192,7 +190,7 @@ static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindi }; semantics[i] = write ? -binding_fixme->semantic : binding_fixme->semantic; - gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x read_type=%d semantic=%d\n", i, name, descriptor_set, binding, stages, type, binding_fixme->semantic); + gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x type=%d semantic=%d\n", i, name, descriptor_set, binding, stages, type, binding_fixme->semantic); } return count; From f607be08313f95757ed66b01fa617e42c7a3e73a Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 5 Nov 2022 14:46:47 -0700 Subject: [PATCH 487/548] add tentative plan for next stream --- ref_vk/TODO.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index 484dc51e..5b290760 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -2,9 +2,11 @@ - [x] E213: - [x] parse binding types - [x] remove types from resources FIXME -- [ ] E214: - - [ ] extract binding image format +- [ ] E214: ~tentative~ - [ ] integrate sebastian into waf + - [ ] serialize binding image format + - [ ] serialize all resources with in/out and formats for images + - [ ] create images on meatpipe load >=E215 - [ ] automatic resource creation From f2697fc7e6d21e45cd9bc69a98e505cb2c8243aa Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 13 Nov 2022 01:19:50 -0800 Subject: [PATCH 488/548] vk: run sebastian as part of waf --- ref_vk/wscript | 12 +++++++- scripts/waifulib/sebastian.py | 53 +++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 scripts/waifulib/sebastian.py diff --git a/ref_vk/wscript b/ref_vk/wscript index 4465bc5a..b17c330e 100644 --- a/ref_vk/wscript +++ b/ref_vk/wscript @@ -37,6 +37,7 @@ def configure(conf): conf.end_msg('found at ' + conf.env.VULKAN_SDK) conf.load('glslc') + conf.load('sebastian') conf.define('REF_DLL', 1) @@ -88,6 +89,8 @@ def build(bld): glsl_source = bld.path.ant_glob(['shaders/*.vert', 'shaders/*.frag', 'shaders/*.comp']) rtx_glsl_source = bld.path.ant_glob(['shaders/*.rgen', 'shaders/*.rchit', 'shaders/*.rmiss', 'shaders/*.rahit']) + meatpipes = bld.path.ant_glob(['*.json']) + includes = ['.', '../filesystem', '../engine', @@ -138,10 +141,17 @@ def build(bld): bld( source = rtx_glsl_source, features = 'glsl', - install_path = bld.env.LIBDIR + '/valve', # FIXME TEMPORARY!!!! glslcflags = '--target-env=vulkan1.2' ) + bld( + source = meatpipes, + features = 'sebastian', + install_path = bld.env.LIBDIR + '/valve' + ) + #print(things.tasks()) + #bld.install_files(bld.env.LIBDIR + '/valve', things) + bld.install_files(bld.env.LIBDIR, bld.path.ant_glob('data/**'), cwd=bld.path.find_dir('data/'), diff --git a/scripts/waifulib/sebastian.py b/scripts/waifulib/sebastian.py new file mode 100644 index 00000000..e6ff0bd0 --- /dev/null +++ b/scripts/waifulib/sebastian.py @@ -0,0 +1,53 @@ +from waflib import TaskGen + +def configure(conf): + conf.find_program('sebastian.py', var='SEBASTIAN', path_list=[conf.path.abspath()]) + +def sebafun(task): + env = task.env + bld = task.generator.bld + + cmd = env.SEBASTIAN + ['-o', task.outputs[0], task.inputs[0], '--path', task.outputs[0].parent.abspath()+'/shaders'] + print("LOL", cmd) + + # CRASHES WITH WEIRD ERRORS (?!?!?!?) + #bld.exec_command([env.SEBASTIAN, '-o', task.outputs[0], task.inputs[0], '--path', task.inputs[0].parent.abspath()+'/shaders'], cwd = task.get_cwd(), env=env.env or None) + #task.exec_command([env.SEBASTIAN, '-o', task.outputs[0], task.inputs[0], '--path', task.inputs[0].parent.abspath()+'/shaders']) + #bld.cmd_and_log(cmd, cwd = task.get_cwd(), env=env.env or None, quiet = 0) + + # install_path = getattr(task, 'install_path', None) + # if install_path: + # bld.add_install_files(install_to = install_path, install_from = task.outputs[:], task = task) + +#TaskGen.declare_chain( +# color = 'BLUE', +# name = 'sebastian', +# rule = '${SEBASTIAN} -o ${TGT} ${SRC} --path ${TGT[0].parent.abspath()}/shaders', +# rule = sebafun, +# shell = False, +# ext_in = '.json', +# ext_out = '.meat' +#) + +import os +from waflib import * +from waflib.Tools import c_preproc, ccroot + +class sebastian(Task.Task): + color = 'BLUE' + run_str = '${SEBASTIAN} -o ${TGT} ${SRC} --path ${TGT[0].parent.abspath()}/shaders', + ext_in = ['.json'] + + #scan = c_preproc.scan + + def keyword(self): + return 'Compiling meatpipe' + +@TaskGen.extension('.json') +def process_meatpipe(self, src): + tsk = self.create_task('sebastian', src, src.change_ext('.meat')) + + inst_to = getattr(self, 'install_path', None) + if inst_to: + self.add_install_files(install_to=inst_to, + install_from=tsk.outputs[:], chmod=Utils.O755, task=tsk) From 5d5b595529125b70eec1d6094d3ecababf7f35cb Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 13 Nov 2022 14:54:18 -0800 Subject: [PATCH 489/548] meat: waf: track shader dependencies properly needs a cleanup, committing with debug outputs for now --- ref_vk/sebastian.py | 114 ++++++++++++++++++++++------------ ref_vk/{ => shaders}/rt.json | 0 ref_vk/wscript | 2 +- scripts/waifulib/sebastian.py | 33 +++++++++- 4 files changed, 104 insertions(+), 45 deletions(-) rename ref_vk/{ => shaders}/rt.json (100%) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 56729d90..64ef3694 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -4,10 +4,16 @@ import argparse import struct import copy from spirv import spv +import sys +import os + +import sys +print(sys.argv, file=sys.stderr) parser = argparse.ArgumentParser(description='Build pipeline descriptor') -parser.add_argument('--path', action='append', help='Directory to look for shaders') +parser.add_argument('--path', '-p', help='Directory where to look for .spv shader files') parser.add_argument('--output', '-o', type=argparse.FileType('wb'), help='Compiled pipeline') +parser.add_argument('--depend', '-d', type=argparse.FileType('w'), help='Generate dependency file (json)') parser.add_argument('pipelines', type=argparse.FileType('r')) # TODO strip debug OpName OpLine etc args = parser.parse_args() @@ -17,6 +23,17 @@ spvOpNames = dict() for name, n in spvOp.items(): spvOpNames[n] = name +print("cwd", os.path.abspath('.'), file=sys.stderr) + +src_dir = os.path.abspath(os.path.dirname(args.pipelines.name)) +print("src", src_dir, file=sys.stderr) + +#dst_dir = os.path.abspath(os.path.dirname(args.output.name)) +#print("dst", dst_dir, file=sys.stderr) + +shaders_path = os.path.abspath(args.path if args.path else '.') +print("shaders_path", shaders_path, file=sys.stderr) + # remove comment lines and fix comma def prepareJSON(path): raw_json = buffer = result = "" @@ -291,26 +308,44 @@ class Binding: out.writeU32(self.stages) class Shader: - def __init__(self, name, file): + def __init__(self, name, fullpath): self.name = name - self.raw_data = file + self.__fullpath = fullpath + self.__raw_data = None + self.__bindings = None #print(name, '=>', len(self.raw_data)) - self.spirv = parseSpirv(self.raw_data) def __str__(self): - ret = '' - for index, node in enumerate(self.spirv.nodes): - if node.descriptor_set is not None: - ret += ('[%d:%d] (id=%d) %s\n' % (node.descriptor_set, node.binding, index, node.name)) - return ret + return self.name + # ret = '' + # for index, node in enumerate(self.__spirv.nodes): + # if node.descriptor_set is not None: + # ret += ('[%d:%d] (id=%d) %s\n' % (node.descriptor_set, node.binding, index, node.name)) + # return ret + + def getRawData(self): + if not self.__raw_data: + self.__raw_data = open(self.__fullpath, 'rb').read() + + return self.__raw_data def getBindings(self): - ret = [] - for node in self.spirv.nodes: + if self.__bindings: + return self.__bindings + + spirv = parseSpirv(self.__raw_data) + + bindings = [] + for node in spirv.nodes: if node.binding == None or node.descriptor_set == None: continue - ret.append(Binding(node)) - return ret + bindings.append(Binding(node)) + + self.__bindings = bindings + return self.__bindings + + def getFilePath(self): + return self.__fullpath class Shaders: __suffixes = { @@ -325,28 +360,13 @@ class Shaders: self.__map = dict() self.__shaders = [] - def __loadShaderFile(name): - try: - return open(name, 'rb').read() - except: - pass - - if args.path: - for path in args.path: - try: - return open(path + '/' + name, 'rb').read() - except: - pass - - raise Exception('Cannot load shader ' + name) - def load(self, name, stage): name = name + self.__suffixes[stage] + fullpath = os.path.join(shaders_path, name) if name in self.__map: return self.__shaders[self.__map[name]] - file = Shaders.__loadShaderFile(name); - shader = Shader(name, file) + shader = Shader(name, fullpath) index = len(self.__shaders) self.__shaders.append(shader) @@ -361,7 +381,10 @@ class Shaders: out.writeU32(len(self.__shaders)) for shader in self.__shaders: out.writeString(shader.name) - out.writeBytes(shader.raw_data) + out.writeBytes(shader.getRawData()) + + def getAllFiles(self): + return [shader.getFilePath() for shader in self.__shaders] shaders = Shaders() @@ -373,26 +396,32 @@ class Pipeline: def __init__(self, name, type_id): self.name = name self.type = type_id - self.__bindings = {} + self.__shaders = [] def addShader(self, shader_name, stage): shader = shaders.load(shader_name, stage) - for binding in shader.getBindings(): - addr = (binding.descriptor_set, binding.index) - if not addr in self.__bindings: - self.__bindings[addr] = copy.deepcopy(binding) - - self.__bindings[addr].stages |= stage - + self.__shaders.append((shader, stage)) return shader + def __mergeBindings(self): + bindings = {} + for shader, stage in self.__shaders: + for binding in shader.getBindings(): + addr = (binding.descriptor_set, binding.index) + if not addr in bindings: + bindings[addr] = copy.deepcopy(binding) + + bindings[addr].stages |= stage + return bindings + def serialize(self, out): + bindings = self.__mergeBindings() #print(self.name) - #for binding in self.__bindings.values(): + #for binding in bindings.values(): #print(f" {binding.name}: ds={binding.descriptor_set}, b={binding.index}, type={binding.type}, stages={binding.stages:#x}") out.writeU32(self.type) out.writeString(self.name) - out.writeArray(self.__bindings.values()) + out.writeArray(bindings.values()) class PipelineRayTracing(Pipeline): __hit2stage = { @@ -459,5 +488,8 @@ def writeOutput(file, pipelines): pipelines = loadPipelines() +if args.depend: + json.dump([os.path.relpath(file) for file in shaders.getAllFiles()], args.depend) + if args.output: writeOutput(args.output, pipelines) diff --git a/ref_vk/rt.json b/ref_vk/shaders/rt.json similarity index 100% rename from ref_vk/rt.json rename to ref_vk/shaders/rt.json diff --git a/ref_vk/wscript b/ref_vk/wscript index b17c330e..4e8128d2 100644 --- a/ref_vk/wscript +++ b/ref_vk/wscript @@ -89,7 +89,7 @@ def build(bld): glsl_source = bld.path.ant_glob(['shaders/*.vert', 'shaders/*.frag', 'shaders/*.comp']) rtx_glsl_source = bld.path.ant_glob(['shaders/*.rgen', 'shaders/*.rchit', 'shaders/*.rmiss', 'shaders/*.rahit']) - meatpipes = bld.path.ant_glob(['*.json']) + meatpipes = bld.path.ant_glob(['shaders/*.json']) includes = ['.', '../filesystem', diff --git a/scripts/waifulib/sebastian.py b/scripts/waifulib/sebastian.py index e6ff0bd0..a9b5d173 100644 --- a/scripts/waifulib/sebastian.py +++ b/scripts/waifulib/sebastian.py @@ -1,4 +1,5 @@ from waflib import TaskGen +import json def configure(conf): conf.find_program('sebastian.py', var='SEBASTIAN', path_list=[conf.path.abspath()]) @@ -35,14 +36,40 @@ from waflib.Tools import c_preproc, ccroot class sebastian(Task.Task): color = 'BLUE' - run_str = '${SEBASTIAN} -o ${TGT} ${SRC} --path ${TGT[0].parent.abspath()}/shaders', + #run_str = '${SEBASTIAN} -o ${TGT} ${SRC}' # --path ${TGT[0].parent.abspath()}/shaders' + run_str = '${SEBASTIAN} -o ${TGT} ${SRC} --path ${TGT[0].parent.abspath()}' ext_in = ['.json'] - #scan = c_preproc.scan - def keyword(self): return 'Compiling meatpipe' + def scan(self): + env = self.env + bld = self.generator.bld + + node = self.inputs[0] + out = self.outputs[0] + + print("############################################### Scanning", node) + + #bld = self.generator.bld + cmd = env.SEBASTIAN + [node.abspath(), '--path', out.parent.abspath(), '--depend', '-'] + #cmd = env.SEBASTIAN + ['--path', out.parent.abspath() + '/shaders', node.abspath(), '--depend', '-'] + + output = bld.cmd_and_log(cmd, cwd = self.get_cwd(), env = env.env or None) + + print("LOOOO99000000000000000OOL", output) + deps = json.loads(output) + + ndeps = [bld.path.find_resource(dep) for dep in deps] + print("FOOOOOOOOOOOOOOUND", ndeps) + + return (ndeps, []) + # dep = node.parent.find_resource(node.name.replace('.dep')) + # if not dep: + # raise ValueError("Could not find the .dep file for %r" % node) + # return ([dep], []) + @TaskGen.extension('.json') def process_meatpipe(self, src): tsk = self.create_task('sebastian', src, src.change_ext('.meat')) From d49b0907b9c5fce4af50d298f26c84c50e37cc92 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 13 Nov 2022 15:07:36 -0800 Subject: [PATCH 490/548] meat/waf: clean things a tiny bit --- ref_vk/sebastian.py | 20 +++++++------- ref_vk/wscript | 2 +- scripts/waifulib/sebastian.py | 49 +++-------------------------------- 3 files changed, 14 insertions(+), 57 deletions(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 64ef3694..ee5397bb 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -7,8 +7,8 @@ from spirv import spv import sys import os -import sys -print(sys.argv, file=sys.stderr) +# import sys +# print(sys.argv, file=sys.stderr) parser = argparse.ArgumentParser(description='Build pipeline descriptor') parser.add_argument('--path', '-p', help='Directory where to look for .spv shader files') @@ -23,16 +23,16 @@ spvOpNames = dict() for name, n in spvOp.items(): spvOpNames[n] = name -print("cwd", os.path.abspath('.'), file=sys.stderr) - -src_dir = os.path.abspath(os.path.dirname(args.pipelines.name)) -print("src", src_dir, file=sys.stderr) - -#dst_dir = os.path.abspath(os.path.dirname(args.output.name)) -#print("dst", dst_dir, file=sys.stderr) +# print("cwd", os.path.abspath('.'), file=sys.stderr) +# +# src_dir = os.path.abspath(os.path.dirname(args.pipelines.name)) +# print("src", src_dir, file=sys.stderr) +# +# #dst_dir = os.path.abspath(os.path.dirname(args.output.name)) +# #print("dst", dst_dir, file=sys.stderr) shaders_path = os.path.abspath(args.path if args.path else '.') -print("shaders_path", shaders_path, file=sys.stderr) +# print("shaders_path", shaders_path, file=sys.stderr) # remove comment lines and fix comma def prepareJSON(path): diff --git a/ref_vk/wscript b/ref_vk/wscript index 4e8128d2..0bdff5a7 100644 --- a/ref_vk/wscript +++ b/ref_vk/wscript @@ -157,7 +157,7 @@ def build(bld): cwd=bld.path.find_dir('data/'), relative_trick=True) - bld.program(features='test', defines=['ALOLCATOR_TEST'],source='alolcator.c', target='alolcator') + #bld.program(features='test', defines=['ALOLCATOR_TEST'],source='alolcator.c', target='alolcator') bld.add_post_fun(printTestSummary) #from waflib.Tools import waf_unit_test diff --git a/scripts/waifulib/sebastian.py b/scripts/waifulib/sebastian.py index a9b5d173..05c398a0 100644 --- a/scripts/waifulib/sebastian.py +++ b/scripts/waifulib/sebastian.py @@ -1,42 +1,11 @@ -from waflib import TaskGen +from waflib import TaskGen, Task, Utils import json def configure(conf): conf.find_program('sebastian.py', var='SEBASTIAN', path_list=[conf.path.abspath()]) -def sebafun(task): - env = task.env - bld = task.generator.bld - - cmd = env.SEBASTIAN + ['-o', task.outputs[0], task.inputs[0], '--path', task.outputs[0].parent.abspath()+'/shaders'] - print("LOL", cmd) - - # CRASHES WITH WEIRD ERRORS (?!?!?!?) - #bld.exec_command([env.SEBASTIAN, '-o', task.outputs[0], task.inputs[0], '--path', task.inputs[0].parent.abspath()+'/shaders'], cwd = task.get_cwd(), env=env.env or None) - #task.exec_command([env.SEBASTIAN, '-o', task.outputs[0], task.inputs[0], '--path', task.inputs[0].parent.abspath()+'/shaders']) - #bld.cmd_and_log(cmd, cwd = task.get_cwd(), env=env.env or None, quiet = 0) - - # install_path = getattr(task, 'install_path', None) - # if install_path: - # bld.add_install_files(install_to = install_path, install_from = task.outputs[:], task = task) - -#TaskGen.declare_chain( -# color = 'BLUE', -# name = 'sebastian', -# rule = '${SEBASTIAN} -o ${TGT} ${SRC} --path ${TGT[0].parent.abspath()}/shaders', -# rule = sebafun, -# shell = False, -# ext_in = '.json', -# ext_out = '.meat' -#) - -import os -from waflib import * -from waflib.Tools import c_preproc, ccroot - class sebastian(Task.Task): - color = 'BLUE' - #run_str = '${SEBASTIAN} -o ${TGT} ${SRC}' # --path ${TGT[0].parent.abspath()}/shaders' + color = 'CYAN' run_str = '${SEBASTIAN} -o ${TGT} ${SRC} --path ${TGT[0].parent.abspath()}' ext_in = ['.json'] @@ -50,25 +19,13 @@ class sebastian(Task.Task): node = self.inputs[0] out = self.outputs[0] - print("############################################### Scanning", node) - - #bld = self.generator.bld cmd = env.SEBASTIAN + [node.abspath(), '--path', out.parent.abspath(), '--depend', '-'] - #cmd = env.SEBASTIAN + ['--path', out.parent.abspath() + '/shaders', node.abspath(), '--depend', '-'] + output = bld.cmd_and_log(cmd, cwd = self.get_cwd(), env = env.env or None, quiet = True) - output = bld.cmd_and_log(cmd, cwd = self.get_cwd(), env = env.env or None) - - print("LOOOO99000000000000000OOL", output) deps = json.loads(output) - ndeps = [bld.path.find_resource(dep) for dep in deps] - print("FOOOOOOOOOOOOOOUND", ndeps) return (ndeps, []) - # dep = node.parent.find_resource(node.name.replace('.dep')) - # if not dep: - # raise ValueError("Could not find the .dep file for %r" % node) - # return ([dep], []) @TaskGen.extension('.json') def process_meatpipe(self, src): From 09823beed8e6befd81dfeedd8473c16bb8f31087 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 13 Nov 2022 15:19:41 -0800 Subject: [PATCH 491/548] meat: remove newer f" syntax as CI python is too old --- ref_vk/sebastian.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index ee5397bb..86f3b88a 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -141,7 +141,7 @@ class SpirvNode: return node.type node = node.type_node - raise Exception(f"Couldn't find type for node {self.name}") + raise Exception('Couldn\'t find type for node %s' % self.name) class SpirvContext: def __init__(self, nodes_count): From 8849ebfe750631ce15893865ebb33e758f0d8be8 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 13 Nov 2022 15:32:51 -0800 Subject: [PATCH 492/548] seba: enforce python3 --- ref_vk/sebastian.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 86f3b88a..9b92b3d4 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 + import json import argparse import struct From 9ddf9c243b6be7c0b0ff633adb9719fce98e4770 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 13 Nov 2022 15:40:25 -0800 Subject: [PATCH 493/548] add debug logs for ci investigation --- ref_vk/sebastian.py | 12 ++++++------ scripts/waifulib/sebastian.py | 7 +++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 9b92b3d4..de3b8eed 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -24,16 +24,16 @@ spvOpNames = dict() for name, n in spvOp.items(): spvOpNames[n] = name -# print("cwd", os.path.abspath('.'), file=sys.stderr) -# -# src_dir = os.path.abspath(os.path.dirname(args.pipelines.name)) -# print("src", src_dir, file=sys.stderr) -# +print("cwd", os.path.abspath('.'), file=sys.stderr) + +src_dir = os.path.abspath(os.path.dirname(args.pipelines.name)) +print("src", src_dir, file=sys.stderr) + # #dst_dir = os.path.abspath(os.path.dirname(args.output.name)) # #print("dst", dst_dir, file=sys.stderr) shaders_path = os.path.abspath(args.path if args.path else '.') -# print("shaders_path", shaders_path, file=sys.stderr) +print("shaders_path", shaders_path, file=sys.stderr) # remove comment lines and fix comma def prepareJSON(path): diff --git a/scripts/waifulib/sebastian.py b/scripts/waifulib/sebastian.py index 05c398a0..7fa3dea1 100644 --- a/scripts/waifulib/sebastian.py +++ b/scripts/waifulib/sebastian.py @@ -3,6 +3,9 @@ import json def configure(conf): conf.find_program('sebastian.py', var='SEBASTIAN', path_list=[conf.path.abspath()]) + #print(conf) + #print(conf.env) + #conf.env.SEBASTIAN = conf.find_file('sebastian.py') class sebastian(Task.Task): color = 'CYAN' @@ -20,10 +23,14 @@ class sebastian(Task.Task): out = self.outputs[0] cmd = env.SEBASTIAN + [node.abspath(), '--path', out.parent.abspath(), '--depend', '-'] + print(cmd) output = bld.cmd_and_log(cmd, cwd = self.get_cwd(), env = env.env or None, quiet = True) deps = json.loads(output) + print(deps) + ndeps = [bld.path.find_resource(dep) for dep in deps] + print(ndeps) return (ndeps, []) From df2546514e56248e1e62f20b9d04ea149937c468 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 13 Nov 2022 16:11:12 -0800 Subject: [PATCH 494/548] seba: make windows find and run it also attempt to fix finding dependency nodes --- ref_vk/wscript | 2 +- scripts/waifulib/sebastian.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ref_vk/wscript b/ref_vk/wscript index 0bdff5a7..151186ee 100644 --- a/ref_vk/wscript +++ b/ref_vk/wscript @@ -61,7 +61,7 @@ def configure(conf): conf.end_msg('SDK: {0}, includes: {1}, libpath: {2}, lib: {3}'.format(path, conf.env.INCLUDES_AFTERMATH, conf.env.LIBPATH_AFTERMATH, conf.env.LIB_AFTERMATH)) # TODO if debug - conf.env.GLSLCFLAGS += ['-g'] + conf.env.GLSLCFLAGS += ['-g', '-O'] if '-Werror=declaration-after-statement' in conf.env.CFLAGS: conf.env.CFLAGS.remove('-Werror=declaration-after-statement') diff --git a/scripts/waifulib/sebastian.py b/scripts/waifulib/sebastian.py index 7fa3dea1..f9b6313f 100644 --- a/scripts/waifulib/sebastian.py +++ b/scripts/waifulib/sebastian.py @@ -2,10 +2,9 @@ from waflib import TaskGen, Task, Utils import json def configure(conf): - conf.find_program('sebastian.py', var='SEBASTIAN', path_list=[conf.path.abspath()]) - #print(conf) - #print(conf.env) - #conf.env.SEBASTIAN = conf.find_file('sebastian.py') + conf.find_program('sebastian', var='SEBASTIAN', exts='.py', path_list=[conf.path.abspath()]) + if conf.env.DEST_OS == 'win32': + conf.env.SEBASTIAN = ['python'] + conf.env.SEBASTIAN class sebastian(Task.Task): color = 'CYAN' @@ -23,13 +22,14 @@ class sebastian(Task.Task): out = self.outputs[0] cmd = env.SEBASTIAN + [node.abspath(), '--path', out.parent.abspath(), '--depend', '-'] + print(cmd) output = bld.cmd_and_log(cmd, cwd = self.get_cwd(), env = env.env or None, quiet = True) deps = json.loads(output) print(deps) - ndeps = [bld.path.find_resource(dep) for dep in deps] + ndeps = [bld.path.find_resource(str(dep)) for dep in deps] print(ndeps) return (ndeps, []) From 9de96201352f2372b0595a47332afa5f06eef00c Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 13 Nov 2022 16:21:45 -0800 Subject: [PATCH 495/548] seba: impl removeprefix for older py --- ref_vk/sebastian.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index de3b8eed..9feff49b 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -35,6 +35,9 @@ print("src", src_dir, file=sys.stderr) shaders_path = os.path.abspath(args.path if args.path else '.') print("shaders_path", shaders_path, file=sys.stderr) +def removeprefix(s, pre): + return s[len(pre):] if s.startswith(pre) else s + # remove comment lines and fix comma def prepareJSON(path): raw_json = buffer = result = "" @@ -285,7 +288,7 @@ class Binding: def __init__(self, node): self.write = node.name.startswith('out_') - self.name = node.name.removeprefix('out_') + self.name = removeprefix(node.name, 'out_') self.index = node.binding self.descriptor_set = node.descriptor_set self.stages = 0 From 45d5565898098f11659e99edfec9f08fe9720242 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 13 Nov 2022 16:23:50 -0800 Subject: [PATCH 496/548] seba: explicitly find python interprete on windows --- scripts/waifulib/sebastian.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/waifulib/sebastian.py b/scripts/waifulib/sebastian.py index f9b6313f..468c0219 100644 --- a/scripts/waifulib/sebastian.py +++ b/scripts/waifulib/sebastian.py @@ -4,7 +4,8 @@ import json def configure(conf): conf.find_program('sebastian', var='SEBASTIAN', exts='.py', path_list=[conf.path.abspath()]) if conf.env.DEST_OS == 'win32': - conf.env.SEBASTIAN = ['python'] + conf.env.SEBASTIAN + conf.find_program('python') + conf.env.SEBASTIAN = conf.env.PYTHON + conf.env.SEBASTIAN class sebastian(Task.Task): color = 'CYAN' From e5abf365dde6eee1088d8d802cc6cfda9d3b82ff Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 13 Nov 2022 22:55:38 -0800 Subject: [PATCH 497/548] seba: remove debuggery --- ref_vk/sebastian.py | 6 +++--- scripts/waifulib/sebastian.py | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 9feff49b..d169d455 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -24,16 +24,16 @@ spvOpNames = dict() for name, n in spvOp.items(): spvOpNames[n] = name -print("cwd", os.path.abspath('.'), file=sys.stderr) +#print("cwd", os.path.abspath('.'), file=sys.stderr) src_dir = os.path.abspath(os.path.dirname(args.pipelines.name)) -print("src", src_dir, file=sys.stderr) +#print("src", src_dir, file=sys.stderr) # #dst_dir = os.path.abspath(os.path.dirname(args.output.name)) # #print("dst", dst_dir, file=sys.stderr) shaders_path = os.path.abspath(args.path if args.path else '.') -print("shaders_path", shaders_path, file=sys.stderr) +#print("shaders_path", shaders_path, file=sys.stderr) def removeprefix(s, pre): return s[len(pre):] if s.startswith(pre) else s diff --git a/scripts/waifulib/sebastian.py b/scripts/waifulib/sebastian.py index 468c0219..d7ba1538 100644 --- a/scripts/waifulib/sebastian.py +++ b/scripts/waifulib/sebastian.py @@ -23,15 +23,10 @@ class sebastian(Task.Task): out = self.outputs[0] cmd = env.SEBASTIAN + [node.abspath(), '--path', out.parent.abspath(), '--depend', '-'] - - print(cmd) output = bld.cmd_and_log(cmd, cwd = self.get_cwd(), env = env.env or None, quiet = True) deps = json.loads(output) - print(deps) - ndeps = [bld.path.find_resource(str(dep)) for dep in deps] - print(ndeps) return (ndeps, []) From b8bfe3b3a81d6bdc0c6a21210edd9ca41d8e38d1 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 19 Nov 2022 12:02:18 -0800 Subject: [PATCH 498/548] sebameat: serialize and load resources --- ref_vk/TODO.md | 8 ++- ref_vk/sebastian.py | 80 +++++++++++++++++++++-- ref_vk/vk_meatpipe.c | 120 ++++++++++++++++++++++++---------- scripts/waifulib/sebastian.py | 3 +- 4 files changed, 165 insertions(+), 46 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index 5b290760..69ce8b0c 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -2,13 +2,14 @@ - [x] E213: - [x] parse binding types - [x] remove types from resources FIXME -- [ ] E214: ~tentative~ - - [ ] integrate sebastian into waf +- [x] E214: ~tentative~ + - [x] integrate sebastian into waf +- [ ] E215: - [ ] serialize binding image format - [ ] serialize all resources with in/out and formats for images - [ ] create images on meatpipe load ->=E215 +>E215 - [ ] automatic resource creation - [ ] resource management refactoring: - [ ] resource object: name, metadata(type, etc.), producer, status (ready, barriers, etc) @@ -16,6 +17,7 @@ - [ ] register existing resources (tlas, buffers, temp images, ...) - [ ] resource destruction - [ ] create resources on demand +- [ ] Rake Yuri # Programmable render - [x] parse spirv -> get bindings with names diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index d169d455..39eb2677 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -265,6 +265,68 @@ def parseSpirv(raw_data): return ctx +class NameIndex: + def __init__(self): + self.__name_to_index = {} + self.__all = [] + + def getIndex(self, name): + return self.__name_to_index[name] if name in self.__name_to_index else -1 + + def getByName(self, name): + return self.__all[self.__name_to_index[name]] if name in self.__name_to_index else None + + def getByIndex(self, index): + return self.__all[index] + + def put(self, name, value): + if name in self.__name_to_index: + raise Exception('Already have a value for "%s"' % (name)) + + index = len(self.__all) + self.__all.append(value) + self.__name_to_index[name] = index + return index + + def serialize(self, out): + out.writeArray(self.__all) + + +class Resources: + def __init__(self): + self.__storage = NameIndex() + + def getIndex(self, name, node): + index = self.__storage.getIndex(name) + type = node.getType() + + if index >= 0: + res = self.__storage.getByIndex(index) + res.checkSameType(type) + return index + + return self.__storage.put(name, self.Resource(name, type)) + + def serialize(self, out): + self.__storage.serialize(out) + + class Resource: + def __init__(self, name, type): + self.__name = name + self.__type = type + + #TODO: count, etc + + def checkSameType(self, type): + if self.__type != type: + raise Exception('Conflicting types for resource "%s": %s != %s' % (self.__name, self.__type, type)) + + def serialize(self, out): + out.writeString(self.__name) + out.writeU32(self.__type) + +resources = Resources() + class Binding: STAGE_VERTEX_BIT = 0x00000001 STAGE_TESSELLATION_CONTROL_BIT = 0x00000002 @@ -288,11 +350,12 @@ class Binding: def __init__(self, node): self.write = node.name.startswith('out_') - self.name = removeprefix(node.name, 'out_') self.index = node.binding self.descriptor_set = node.descriptor_set self.stages = 0 - self.type = node.getType() + + resource_name = removeprefix(node.name, 'out_') + self.__resource_index = resources.getIndex(resource_name, node) #print(f" {self.name}: ds={self.descriptor_set}, b={self.index}, type={self.type}") @@ -302,13 +365,10 @@ class Binding: assert(self.index >= 0) assert(self.index < 255) - #TODO: type, count, etc - def serialize(self, out): - out.writeString(self.name) header = (Binding.WRITE_BIT if self.write else 0) | (self.descriptor_set << 8) | self.index out.writeU32(header) - out.writeU32(self.type) + out.writeU32(self.__resource_index) out.writeU32(self.stages) class Shader: @@ -337,7 +397,7 @@ class Shader: if self.__bindings: return self.__bindings - spirv = parseSpirv(self.__raw_data) + spirv = parseSpirv(self.getRawData()) bindings = [] for node in spirv.nodes: @@ -378,6 +438,10 @@ class Shaders: return shader + def parse(self): + for s in self.__shaders: + s.getBindings() + def getIndex(self, shader): return self.__map[shader.name] @@ -487,6 +551,7 @@ def writeOutput(file, pipelines): MAGIC = bytearray([ord(c) for c in 'MEAT']) out = Serializer(file) out.write(MAGIC) + resources.serialize(out) shaders.serialize(out) out.writeArray(pipelines.values()) @@ -496,4 +561,5 @@ if args.depend: json.dump([os.path.relpath(file) for file in shaders.getAllFiles()], args.depend) if args.output: + shaders.parse() writeOutput(args.output, pipelines) diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index b4c8c79b..ec15a3a1 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -17,10 +17,19 @@ typedef struct { qboolean error; } cursor_t; +typedef struct { + char name[64]; + uint32_t type; +} res_t; + typedef struct { cursor_t cur; + int shaders_count; VkShaderModule *shaders; + + int res_count; + res_t *res; } load_context_t; const void* curReadPtr(cursor_t *cur, int size) { @@ -164,17 +173,24 @@ static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindi } for (int i = 0; i < count; ++i) { - char name[64]; - READ_STR_RETURN(false, name, "Couldn't read binding name"); - const uint32_t header = READ_U32_RETURN(false, "Couldn't read header for binding %s", name); - const uint32_t type = READ_U32_RETURN(false, "Couldn't read type for binding %s", name); - const uint32_t stages = READ_U32_RETURN(false, "Couldn't read stages for binding %s", name); + const uint32_t header = READ_U32_RETURN(false, "Couldn't read header for binding %d", i); + const uint32_t res_index = READ_U32_RETURN(false, "Couldn't read res index for binding %d", i); + const uint32_t stages = READ_U32_RETURN(false, "Couldn't read stages for binding %d", i); + + if (res_index >= ctx->res_count) { + gEngine.Con_Printf(S_ERROR "Resource %d is out of bound %d for binding %d", res_index, ctx->res_count, i); + return false; + } + + const res_t *res = ctx->res + res_index; #define BINDING_WRITE_BIT 0x80000000u const qboolean write = !!(header & BINDING_WRITE_BIT); const uint32_t descriptor_set = (header >> 8) & 0xffu; const uint32_t binding = header & 0xffu; + const char *name = res->name; + const ray_resource_binding_desc_fixme_t *binding_fixme = RayResouceGetBindingForName_FIXME(name); if (!binding_fixme) { gEngine.Con_Printf(S_ERROR "Couldn't find fixme desc for binding %s\n", name); @@ -183,20 +199,20 @@ static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindi bindings[i] = (VkDescriptorSetLayoutBinding){ .binding = binding, - .descriptorType = type, + .descriptorType = res->type, .descriptorCount = binding_fixme->count, .stageFlags = stages, .pImmutableSamplers = NULL, }; semantics[i] = write ? -binding_fixme->semantic : binding_fixme->semantic; - gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x type=%d semantic=%d\n", i, name, descriptor_set, binding, stages, type, binding_fixme->semantic); + gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x res=%d type=%d semantic=%d\n", i, name, descriptor_set, binding, stages, res_index, res->type, binding_fixme->semantic); } return count; } -static struct ray_pass_s *pipelineLoad(load_context_t *ctx, int i) { +static struct ray_pass_s *readAndCreatePass(load_context_t *ctx, int i) { int semantics[MAX_BINDINGS]; VkDescriptorSetLayoutBinding bindings[MAX_BINDINGS]; ray_pass_layout_t layout = { @@ -235,34 +251,25 @@ finalize: return NULL; } -qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename) { - qboolean ret = false; - fs_offset_t size = 0; - byte* const buf = gEngine.fsapi->LoadFile(filename, &size, false); +static qboolean readResources(load_context_t *ctx) { + ctx->res_count = READ_U32("Couldn't read resources count"); + ctx->res = Mem_Malloc(vk_core.pool, sizeof(ctx->res[0]) * ctx->res_count); - if (!buf) { - gEngine.Con_Printf(S_ERROR "Couldn't read \"%s\"\n", filename); - return false; + for (int i = 0; i < ctx->res_count; ++i) { + res_t *res = ctx->res + i; + READ_STR(res->name, "Couldn't read resource %d name", i); + + res->type = READ_U32("Couldn't read resource %d:%s type", i, res->name); + + gEngine.Con_Reportf("Resource %d:%s = %08x\n", i, res->name, res->type); } - load_context_t context = { - .cur = { .data = buf, .off = 0, .size = size }, - .shaders_count = 0, - .shaders = NULL, - }; - load_context_t *ctx = &context; - - out->passes_count = 0; - - { - const uint32_t magic = READ_U32("Couldn't read magic"); - - if (magic != k_meatpipe_magic) { - gEngine.Con_Printf(S_ERROR "Meatpipe magic invalid for \"%s\": got %08x expected %08x\n", filename, magic, k_meatpipe_magic); - goto finalize; - } - } + return true; +finalize: + return false; +} +static qboolean readAndLoadShaders(load_context_t *ctx) { ctx->shaders_count = READ_U32("Couldn't read shaders count"); ctx->shaders = Mem_Malloc(vk_core.pool, sizeof(VkShaderModule) * ctx->shaders_count); for (int i = 0; i < ctx->shaders_count; ++i) { @@ -282,18 +289,58 @@ qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename) { gEngine.Con_Reportf("%d: Shader loaded %s\n", i, name); } + return true; +finalize: + return false; +} + +qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename) { + qboolean ret = false; + fs_offset_t size = 0; + byte* const buf = gEngine.fsapi->LoadFile(filename, &size, false); + + if (!buf) { + gEngine.Con_Printf(S_ERROR "Couldn't read \"%s\"\n", filename); + return false; + } + + load_context_t context = { + .cur = { .data = buf, .off = 0, .size = size }, + .shaders_count = 0, + .shaders = NULL, + .res_count = 0, + .res = NULL, + }; + load_context_t *ctx = &context; + + out->passes_count = 0; + + { + const uint32_t magic = READ_U32("Couldn't read magic"); + + if (magic != k_meatpipe_magic) { + gEngine.Con_Printf(S_ERROR "Meatpipe magic invalid for \"%s\": got %08x expected %08x\n", filename, magic, k_meatpipe_magic); + goto finalize; + } + } + + if (!readResources(ctx)) + goto finalize; + + if (!readAndLoadShaders(ctx)) + goto finalize; + out->passes_count = READ_U32("Couldn't read pipelines count"); out->passes = Mem_Malloc(vk_core.pool, sizeof(out->passes[0]) * out->passes_count); for (int i = 0; i < out->passes_count; ++i) { - if (!(out->passes[i] = pipelineLoad(ctx, i))) + if (!(out->passes[i] = readAndCreatePass(ctx, i))) goto finalize; } ret = true; finalize: - if (!ret) { + if (!ret) R_VkMeatpipeDestroy(out); - } for (int i = 0; i < ctx->shaders_count; ++i) { if (ctx->shaders[i] == VK_NULL_HANDLE) @@ -305,6 +352,9 @@ finalize: if (ctx->shaders) Mem_Free(ctx->shaders); + if (ctx->res) + Mem_Free(ctx->res); + Mem_Free(buf); return ret; } diff --git a/scripts/waifulib/sebastian.py b/scripts/waifulib/sebastian.py index d7ba1538..f3c89d83 100644 --- a/scripts/waifulib/sebastian.py +++ b/scripts/waifulib/sebastian.py @@ -1,5 +1,5 @@ from waflib import TaskGen, Task, Utils -import json +import json, os def configure(conf): conf.find_program('sebastian', var='SEBASTIAN', exts='.py', path_list=[conf.path.abspath()]) @@ -27,6 +27,7 @@ class sebastian(Task.Task): deps = json.loads(output) ndeps = [bld.path.find_resource(str(dep)) for dep in deps] + ndeps.append(bld.path.find_resource(os.path.relpath(env.SEBASTIAN[0]))) return (ndeps, []) From 2aeac9ac4af9f9f31e48fc9ccefd1748dd50d250 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 19 Nov 2022 13:11:24 -0800 Subject: [PATCH 499/548] sebameat: pass image format to resources --- ref_vk/sebastian.py | 98 +++++++++++++++++++++++++++++--------------- ref_vk/vk_meatpipe.c | 10 ++++- 2 files changed, 75 insertions(+), 33 deletions(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 39eb2677..a960df0f 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -103,8 +103,7 @@ class Serializer: else: i.serialize(self) -class SpirvNode: - # TODO move this to Binding +class TypeInfo: TYPE_SAMPLER = 0 TYPE_COMBINED_IMAGE_SAMPLER = 1 TYPE_SAMPLED_IMAGE = 2 @@ -122,31 +121,60 @@ class SpirvNode: TYPE_SAMPLE_WEIGHT_IMAGE_QCOM = 1000440000 TYPE_BLOCK_MATCH_IMAGE_QCOM = 1000440001 + def __init__(self, type): + self.type = type + # TODO self.writable = None + # TODO self.readable = None + self.is_image = False + self.image_format = None + + def __eq__(self, other): + if self.type != other.type: + return False + + assert(self.is_image == other.is_image) + + if self.is_image: + assert(self.image_format != None) + assert(other.image_format != None) + + if self.image_format != other.image_format and self.image_format != 0 and other.image_format != 0: + return False + + return True + + def serialize(self, out): + out.writeU32(self.type) + if self.is_image: + out.writeU32(self.image_format) + +class SpirvNode: def __init__(self): self.descriptor_set = None self.binding = None self.name = None - self.type_node = None + self.parent_type_node = None self.type = None self.storage_class = None - pass def getType(self): node = self while node: #print(f"Checking node {node.name}, {node.storage_class}, {node.type}") - if node.storage_class == spv['StorageClass']['Uniform']: - return SpirvNode.TYPE_UNIFORM_BUFFER - - if node.storage_class == spv['StorageClass']['StorageBuffer']: - return SpirvNode.TYPE_STORAGE_BUFFER if node.type: return node.type - node = node.type_node + node = node.parent_type_node raise Exception('Couldn\'t find type for node %s' % self.name) + def setStorageClass(self, storage_class): + if storage_class == spv['StorageClass']['Uniform']: + self.type = TypeInfo(TypeInfo.TYPE_UNIFORM_BUFFER) + elif storage_class == spv['StorageClass']['StorageBuffer']: + self.type = TypeInfo(TypeInfo.TYPE_STORAGE_BUFFER) + self.storage_class = storage_class + class SpirvContext: def __init__(self, nodes_count): self.nodes = [SpirvNode() for i in range(0, nodes_count)] @@ -173,28 +201,28 @@ def spvOpDecorate(ctx, args): #print('Decor ', id, decor) def spvOpVariable(ctx, args): - type_node = ctx.getNode(args[0]) + parent_type_node = ctx.getNode(args[0]) node = ctx.getNode(args[1]) storage_class = args[2] - node.type_node = type_node - node.storage_class = storage_class + node.parent_type_node = parent_type_node + node.setStorageClass(storage_class) #node.op_type = 'OpVariable' - #print(node.name, "=(var)>", type_node.name, args[0]) + #print(node.name, "=(var)>", parent_type_node.name, args[0]) def spvOpTypePointer(ctx, args): node = ctx.getNode(args[0]) storage_class = args[1] - type_node = ctx.getNode(args[2]) + parent_type_node = ctx.getNode(args[2]) - node.type_node = type_node - node.storage_class = storage_class + node.parent_type_node = parent_type_node + node.setStorageClass(storage_class) #node.op_type = 'OpTypePointer' - #print(node.name, "=(ptr)>", type_node.name, args[2]) + #print(node.name, "=(ptr)>", parent_type_node.name, args[2]) def spvOpTypeAccelerationStructureKHR(ctx, args): node = ctx.getNode(args[0]) - node.type = SpirvNode.TYPE_ACCELERATION_STRUCTURE_KHR + node.type = TypeInfo(TypeInfo.TYPE_ACCELERATION_STRUCTURE_KHR) def spvOpTypeImage(ctx, args): node = ctx.getNode(args[0]) @@ -205,9 +233,13 @@ def spvOpTypeImage(ctx, args): ms = args[5] sampled = args[6] image_format = args[7] - node.type = SpirvNode.TYPE_STORAGE_IMAGE if sampled == 0 or sampled == 2 else SpirvNode.TYPE_COMBINED_IMAGE_SAMPLER # FIXME ? - node.image_format = image_format + type = TypeInfo.TYPE_STORAGE_IMAGE if sampled == 0 or sampled == 2 else TypeInfo.TYPE_COMBINED_IMAGE_SAMPLER # FIXME ? + + node.type = TypeInfo(type) + node.type.is_image = True + node.type.image_format = image_format + qualifier = None if len(args) < 9 else args[8] #print(f"{args[0]}: Image(type={sampled_type}, dim={dim}, depth={depth}, arrayed={arrayed}, ms={ms}, sampled={sampled}, image_format={image_format}, qualifier={qualifier})") @@ -215,15 +247,18 @@ def spvOpTypeImage(ctx, args): def spvOpTypeSampledImage(ctx, args): node = ctx.getNode(args[0]) image_type = ctx.getNode(args[1]) - node.type_node = image_type - node.type = SpirvNode.TYPE_COMBINED_IMAGE_SAMPLER + node.parent_type_node = image_type + + node.type = TypeInfo(TypeInfo.TYPE_COMBINED_IMAGE_SAMPLER) + node.type.is_image = True + node.type.image_format = spv['ImageFormat']['Unknown'] def spvOpTypeArray(ctx, args): node = ctx.getNode(args[0]) element_type = ctx.getNode(args[1]) length = args[2] - node.type_node = element_type + node.parent_type_node = element_type #print(f"{args[0]}: Array(type={args[1]}, length={length})") spvOpHandlers = { @@ -298,32 +333,31 @@ class Resources: def getIndex(self, name, node): index = self.__storage.getIndex(name) - type = node.getType() if index >= 0: res = self.__storage.getByIndex(index) - res.checkSameType(type) + res.checkSameType(node) return index - return self.__storage.put(name, self.Resource(name, type)) + return self.__storage.put(name, self.Resource(name, node)) def serialize(self, out): self.__storage.serialize(out) class Resource: - def __init__(self, name, type): + def __init__(self, name, node): self.__name = name - self.__type = type + self.__type = node.getType() #TODO: count, etc - def checkSameType(self, type): - if self.__type != type: + def checkSameType(self, node): + if self.__type != node.getType(): raise Exception('Conflicting types for resource "%s": %s != %s' % (self.__name, self.__type, type)) def serialize(self, out): out.writeString(self.__name) - out.writeU32(self.__type) + self.__type.serialize(out) resources = Resources() diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index ec15a3a1..711d606a 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -20,6 +20,8 @@ typedef struct { typedef struct { char name[64]; uint32_t type; + qboolean is_image; + uint32_t image_format; } res_t; typedef struct { @@ -261,7 +263,13 @@ static qboolean readResources(load_context_t *ctx) { res->type = READ_U32("Couldn't read resource %d:%s type", i, res->name); - gEngine.Con_Reportf("Resource %d:%s = %08x\n", i, res->name, res->type); + res->is_image = res->type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE || res->type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + + if (res->is_image) { + res->image_format = READ_U32("Couldn't read image format for res %d:%s", i, res->name); + } + + gEngine.Con_Reportf("Resource %d:%s = %08x is_image=%d image_format=%08x\n", i, res->name, res->type, res->is_image, res->image_format); } return true; From cc85a191f1add05a176c2f3e513c9cff45c9b96d Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 19 Nov 2022 13:28:18 -0800 Subject: [PATCH 500/548] waf: fix depending on sebastian on win32 --- ref_vk/sebastian.py | 1 - scripts/waifulib/sebastian.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index a960df0f..a56ff2bd 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -5,7 +5,6 @@ import argparse import struct import copy from spirv import spv -import sys import os # import sys diff --git a/scripts/waifulib/sebastian.py b/scripts/waifulib/sebastian.py index f3c89d83..a3b946a2 100644 --- a/scripts/waifulib/sebastian.py +++ b/scripts/waifulib/sebastian.py @@ -27,7 +27,8 @@ class sebastian(Task.Task): deps = json.loads(output) ndeps = [bld.path.find_resource(str(dep)) for dep in deps] - ndeps.append(bld.path.find_resource(os.path.relpath(env.SEBASTIAN[0]))) + script_index = 1 if env.DEST_OS == 'win32' else 0 + ndeps.append(bld.path.find_resource(os.path.relpath(env.SEBASTIAN[script_index]))) return (ndeps, []) From 096f123e85266be637e5d1c1158c93ee755a4ed7 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 26 Nov 2022 10:29:59 -0800 Subject: [PATCH 501/548] meat: pass image format to resources and validate also map spirv image formats to vk in sebastian --- ref_vk/TODO.md | 14 ++++--- ref_vk/ray_resources.c | 90 ++++++++++++++++++++++++++++-------------- ref_vk/ray_resources.h | 13 +++++- ref_vk/sebastian.py | 29 +++++++++++++- ref_vk/vk_meatpipe.c | 39 +++++++++++------- 5 files changed, 133 insertions(+), 52 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index 69ce8b0c..773dcd1e 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -4,20 +4,24 @@ - [x] remove types from resources FIXME - [x] E214: ~tentative~ - [x] integrate sebastian into waf -- [ ] E215: - - [ ] serialize binding image format - - [ ] serialize all resources with in/out and formats for images +- [x] E215: + - [x] serialize binding image format +- [ ] E216: + - [ ] meatpipe resource tracking + - [ ] name -> index mapping + - [x] validate image formats - [ ] create images on meatpipe load + - [ ] begin Rake Yuri migration ->E215 +>=E217 - [ ] automatic resource creation + - [ ] serialize all resources with in/out and formats for images - [ ] resource management refactoring: - [ ] resource object: name, metadata(type, etc.), producer, status (ready, barriers, etc) - [ ] resource automatic resolution: prducing, barriers, etc - [ ] register existing resources (tlas, buffers, temp images, ...) - [ ] resource destruction - [ ] create resources on demand -- [ ] Rake Yuri # Programmable render - [x] parse spirv -> get bindings with names diff --git a/ref_vk/ray_resources.c b/ref_vk/ray_resources.c index 7d672d48..9d13035b 100644 --- a/ref_vk/ray_resources.c +++ b/ref_vk/ray_resources.c @@ -1,6 +1,8 @@ #include "ray_resources.h" #include "vk_core.h" +#include "shaders/ray_interop.h" // FIXME temp for type validation + #include #define MAX_BARRIERS 16 @@ -95,46 +97,74 @@ void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill) { 0, 0, NULL, 0, NULL, image_barriers_count, image_barriers); } } - -#define FIXME_DESC(name_, semantic_, count_) \ - { .name = name_, \ - .desc.semantic = (RayResource_##semantic_ + 1), \ - .desc.count = count_, \ - } - static const struct { const char *name; + ray_resource_type_e type; + int image_format; ray_resource_binding_desc_fixme_t desc; } fixme_descs[] = { + +#define FIXME_DESC_BUFFER(name_, semantic_, count_) \ + { .name = name_, \ + .type = ResourceBuffer, \ + .desc.semantic = (RayResource_##semantic_ + 1), \ + .desc.count = count_, \ + } + // External - FIXME_DESC("ubo", ubo, 1), - FIXME_DESC("tlas", tlas, 1), - FIXME_DESC("kusochki", kusochki, 1), - FIXME_DESC("indices", indices, 1), - FIXME_DESC("vertices", vertices, 1), - FIXME_DESC("textures", all_textures, MAX_TEXTURES), - FIXME_DESC("skybox", skybox, 1), - FIXME_DESC("lights", lights, 1), - FIXME_DESC("light_clusters", light_clusters, 1), - FIXME_DESC("light_grid", light_clusters, 1), - FIXME_DESC("dest", denoised, 1), + FIXME_DESC_BUFFER("ubo", ubo, 1), + FIXME_DESC_BUFFER("tlas", tlas, 1), + FIXME_DESC_BUFFER("kusochki", kusochki, 1), + FIXME_DESC_BUFFER("indices", indices, 1), + FIXME_DESC_BUFFER("vertices", vertices, 1), + FIXME_DESC_BUFFER("lights", lights, 1), + FIXME_DESC_BUFFER("light_clusters", light_clusters, 1), + FIXME_DESC_BUFFER("light_grid", light_clusters, 1), + +#define FIXME_DESC_IMAGE(name_, semantic_, image_format_, count_) \ + { .name = name_, \ + .type = ResourceImage, \ + .image_format = image_format_, \ + .desc.semantic = (RayResource_##semantic_ + 1), \ + .desc.count = count_, \ + } + + FIXME_DESC_IMAGE("textures", all_textures, VK_FORMAT_UNDEFINED, MAX_TEXTURES), + FIXME_DESC_IMAGE("skybox", skybox, VK_FORMAT_UNDEFINED, 1), + FIXME_DESC_IMAGE("dest", denoised, VK_FORMAT_UNDEFINED,1), // Internal, temporary - FIXME_DESC("base_color_a", base_color_a, 1), - FIXME_DESC("position_t", position_t, 1), - FIXME_DESC("normals_gs", normals_gs, 1), - FIXME_DESC("material_rmxx", material_rmxx, 1), - FIXME_DESC("emissive", emissive, 1), - FIXME_DESC("light_poly_diffuse", light_poly_diffuse, 1), - FIXME_DESC("light_poly_specular", light_poly_specular, 1), - FIXME_DESC("light_point_diffuse", light_point_diffuse, 1), - FIXME_DESC("light_point_specular", light_point_specular, 1), +#define DECLARE_IMAGE(_, name_, format_) \ + { .name = #name_, \ + .type = ResourceImage, \ + .image_format = format_, \ + .desc.semantic = (RayResource_##name_ + 1), \ + .desc.count = 1, \ + }, +#define rgba8 VK_FORMAT_R8G8B8A8_UNORM +#define rgba32f VK_FORMAT_R32G32B32A32_SFLOAT +#define rgba16f VK_FORMAT_R16G16B16A16_SFLOAT + RAY_PRIMARY_OUTPUTS(DECLARE_IMAGE) + RAY_LIGHT_DIRECT_POLY_OUTPUTS(DECLARE_IMAGE) + RAY_LIGHT_DIRECT_POINT_OUTPUTS(DECLARE_IMAGE) }; -const ray_resource_binding_desc_fixme_t *RayResouceGetBindingForName_FIXME(const char *name) { +const ray_resource_binding_desc_fixme_t *RayResouceGetBindingForName_FIXME(const char *name, ray_resource_desc_t desc) { for (int i = 0; i < COUNTOF(fixme_descs); ++i) { - if (strcmp(name, fixme_descs[i].name) == 0) - return &fixme_descs[i].desc; + if (strcmp(name, fixme_descs[i].name) != 0) + continue; + + if (fixme_descs[i].type != desc.type) { + gEngine.Con_Printf(S_ERROR "Incompatible resource types for name %s: want %d, have %d\n", name, desc.type, fixme_descs[i].type); + return NULL; + } + + if (fixme_descs[i].type == ResourceImage && fixme_descs[i].image_format != VK_FORMAT_UNDEFINED && desc.image_format != fixme_descs[i].image_format) { + gEngine.Con_Printf(S_ERROR "Incompatible image formats for name %s: want %d, have %d\n", name, desc.image_format, fixme_descs[i].image_format); + return NULL; + } + + return &fixme_descs[i].desc; } return NULL; } diff --git a/ref_vk/ray_resources.h b/ref_vk/ray_resources.h index 2451fd66..2fe6b1b6 100644 --- a/ref_vk/ray_resources.h +++ b/ref_vk/ray_resources.h @@ -65,4 +65,15 @@ typedef struct { int count; } ray_resource_binding_desc_fixme_t; -const ray_resource_binding_desc_fixme_t *RayResouceGetBindingForName_FIXME(const char *name); +typedef enum { + ResourceUnknown, + ResourceBuffer, + ResourceImage, +} ray_resource_type_e; + +typedef struct { + ray_resource_type_e type; + int image_format; // if type == ResourceImage +} ray_resource_desc_t; + +const ray_resource_binding_desc_fixme_t *RayResouceGetBindingForName_FIXME(const char *name, ray_resource_desc_t desc); diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index a56ff2bd..0904e929 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -102,6 +102,33 @@ class Serializer: else: i.serialize(self) +class ImageFormat: + VK_FORMAT_UNDEFINED = 0 + VK_FORMAT_R8G8B8A8_UNORM = 37 + VK_FORMAT_R16G16B16A16_SFLOAT = 97 + VK_FORMAT_R32G32B32A32_SFLOAT = 109 + + __map = { + 'Unknown': VK_FORMAT_UNDEFINED, + 'Rgba32f' : VK_FORMAT_R32G32B32A32_SFLOAT, + 'Rgba16f' : VK_FORMAT_R16G16B16A16_SFLOAT, + 'Rgba8' : VK_FORMAT_R8G8B8A8_UNORM, + # TODO map more + } + + __revmap = None + + def mapToVk(fmt): + if not ImageFormat.__revmap: + revmap = {} + formats = spv['ImageFormat'] + for k, v in formats.items(): + if k in ImageFormat.__map: + revmap[v] = ImageFormat.__map[k] + ImageFormat.__revmap = revmap + + return ImageFormat.__revmap[fmt] + class TypeInfo: TYPE_SAMPLER = 0 TYPE_COMBINED_IMAGE_SAMPLER = 1 @@ -145,7 +172,7 @@ class TypeInfo: def serialize(self, out): out.writeU32(self.type) if self.is_image: - out.writeU32(self.image_format) + out.writeU32(ImageFormat.mapToVk(self.image_format)) class SpirvNode: def __init__(self): diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 711d606a..8f0ced44 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -20,8 +20,8 @@ typedef struct { typedef struct { char name[64]; uint32_t type; - qboolean is_image; - uint32_t image_format; + int count; + int semantic; } res_t; typedef struct { @@ -193,22 +193,16 @@ static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindi const char *name = res->name; - const ray_resource_binding_desc_fixme_t *binding_fixme = RayResouceGetBindingForName_FIXME(name); - if (!binding_fixme) { - gEngine.Con_Printf(S_ERROR "Couldn't find fixme desc for binding %s\n", name); - return 0; - } - bindings[i] = (VkDescriptorSetLayoutBinding){ .binding = binding, .descriptorType = res->type, - .descriptorCount = binding_fixme->count, + .descriptorCount = res->count, .stageFlags = stages, .pImmutableSamplers = NULL, }; - semantics[i] = write ? -binding_fixme->semantic : binding_fixme->semantic; + semantics[i] = write ? -res->semantic : res->semantic; - gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x res=%d type=%d semantic=%d\n", i, name, descriptor_set, binding, stages, res_index, res->type, binding_fixme->semantic); + gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x res=%d type=%d semantic=%d\n", i, name, descriptor_set, binding, stages, res_index, res->type, res->semantic); } return count; @@ -263,13 +257,28 @@ static qboolean readResources(load_context_t *ctx) { res->type = READ_U32("Couldn't read resource %d:%s type", i, res->name); - res->is_image = res->type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE || res->type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + const qboolean is_image = res->type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE || res->type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - if (res->is_image) { - res->image_format = READ_U32("Couldn't read image format for res %d:%s", i, res->name); + ray_resource_desc_t rrdesc = { + .type = is_image ? ResourceImage : ResourceBuffer, + .image_format = VK_FORMAT_UNDEFINED, + }; + + + if (is_image) { + rrdesc.image_format = READ_U32("Couldn't read image format for res %d:%s", i, res->name); } - gEngine.Con_Reportf("Resource %d:%s = %08x is_image=%d image_format=%08x\n", i, res->name, res->type, res->is_image, res->image_format); + const ray_resource_binding_desc_fixme_t *binding_fixme = RayResouceGetBindingForName_FIXME(res->name, rrdesc); + if (!binding_fixme) { + gEngine.Con_Printf(S_ERROR "Couldn't find fixme desc for binding %s\n", res->name); + return 0; + } + + res->count = binding_fixme->count; + res->semantic = binding_fixme->semantic; + + gEngine.Con_Reportf("Resource %d:%s = %08x is_image=%d image_format=%08x count=%d semantic=%d\n", i, res->name, res->type, is_image, rrdesc.image_format, res->count, res->semantic); } return true; From c94314d15a5e4ca5dc79a4f8e0bc0cc4cea6462e Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 26 Nov 2022 12:35:22 -0800 Subject: [PATCH 502/548] rt: use rake yuri for direct light shadows make compute shaders compile with newer vulkan env; leave comment in sebastian that we need to support older way of detecting ubo vs sbo env lights shadows are not implemented yet --- ref_vk/sebastian.py | 35 ++++++++++++++++++++++ ref_vk/shaders/light.glsl | 7 ++++- ref_vk/shaders/light_common.glsl | 5 +++- ref_vk/shaders/ray_light_direct.glsl | 16 +++++++--- ref_vk/shaders/ray_light_direct_point.comp | 9 ++++++ ref_vk/shaders/ray_light_direct_point.rgen | 4 +++ ref_vk/shaders/ray_light_direct_poly.comp | 9 ++++++ ref_vk/shaders/ray_light_poly_direct.rgen | 4 +++ ref_vk/shaders/rt.json | 33 ++++++++++++-------- ref_vk/vk_core.c | 9 +++++- ref_vk/wscript | 4 +-- 11 files changed, 113 insertions(+), 22 deletions(-) create mode 100644 ref_vk/shaders/ray_light_direct_point.comp create mode 100644 ref_vk/shaders/ray_light_direct_poly.comp diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 0904e929..5362f7cc 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -196,10 +196,44 @@ class SpirvNode: def setStorageClass(self, storage_class): if storage_class == spv['StorageClass']['Uniform']: + # TODO support older spirv shaders + # E.g. for the same + # layout (set = 0, binding = 7) readonly buffer SBOLights { LightsMetadata m; } lights; + + # Newer, with --target-env=vulkan-1.2: + # OpName %lights "lights" + # OpDecorate %lights DescriptorSet 0 + # OpDecorate %lights Binding 7 + # %lights = OpVariable %_ptr_StorageBuffer_SBOLights StorageBuffer + # + # %_ptr_StorageBuffer_SBOLights = OpTypePointer StorageBuffer %SBOLights + # + # OpName %SBOLights "SBOLights" + # OpMemberName %SBOLights 0 "m" + # OpMemberDecorate %SBOLights 0 NonWritable + # OpMemberDecorate %SBOLights 0 Offset 0 + # OpDecorate %SBOLights Block + + # Older: + # OpName %lights "lights" + # OpDecorate %lights DescriptorSet 0 + # OpDecorate %lights Binding 7 + # %lights = OpVariable %_ptr_Uniform_SBOLights Uniform + # + # %_ptr_Uniform_SBOLights = OpTypePointer Uniform %SBOLights + # + # OpName %SBOLights "SBOLights" + # OpMemberName %SBOLights 0 "m" + # OpMemberDecorate %SBOLights 0 NonWritable + # OpMemberDecorate %SBOLights 0 Offset 0 + # OpDecorate %SBOLights BufferBlock + # %SBOLights = OpTypeStruct %LightsMetadata + self.type = TypeInfo(TypeInfo.TYPE_UNIFORM_BUFFER) elif storage_class == spv['StorageClass']['StorageBuffer']: self.type = TypeInfo(TypeInfo.TYPE_STORAGE_BUFFER) self.storage_class = storage_class + #print(f"Set node {self.name} storage class {storage_class}, type {self.type}") class SpirvContext: def __init__(self, nodes_count): @@ -457,6 +491,7 @@ class Shader: if self.__bindings: return self.__bindings + #print("Parsing", self.name) spirv = parseSpirv(self.getRawData()) bindings = [] diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index 00ace37a..83fefa2c 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -1,4 +1,4 @@ -layout (set = 0, binding = BINDING_LIGHTS) readonly buffer UBOLights { LightsMetadata m; } lights; // TODO this is pretty much static and should be a buffer, not UBO +layout (set = 0, binding = BINDING_LIGHTS) readonly buffer SBOLights { LightsMetadata m; } lights; layout (set = 0, binding = BINDING_LIGHT_CLUSTERS, align = 1) readonly buffer UBOLightClusters { ivec3 grid_min, grid_size; //uint8_t clusters_data[MAX_LIGHT_CLUSTERS * LIGHT_CLUSTER_SIZE + HACK_OFFSET]; @@ -8,6 +8,11 @@ layout (set = 0, binding = BINDING_LIGHT_CLUSTERS, align = 1) readonly buffer UB const float color_culling_threshold = 0;//600./color_factor; const float shadow_offset_fudge = .1; +#ifdef RAY_QUERY +// TODO sync with native code +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; +#endif + #include "brdf.h" #include "light_common.glsl" diff --git a/ref_vk/shaders/light_common.glsl b/ref_vk/shaders/light_common.glsl index 7d2bc4be..3c6d5049 100644 --- a/ref_vk/shaders/light_common.glsl +++ b/ref_vk/shaders/light_common.glsl @@ -6,6 +6,7 @@ layout(location = PAYLOAD_LOCATION_SHADOW) rayPayloadEXT RayPayloadShadow payload_shadow; #endif +#ifdef RAY_TRACE uint traceShadowRay(vec3 pos, vec3 dir, float dist, uint flags) { payload_shadow.hit_type = SHADOW_HIT; traceRayEXT(tlas, @@ -15,6 +16,7 @@ uint traceShadowRay(vec3 pos, vec3 dir, float dist, uint flags) { pos, 0., dir, dist - shadow_offset_fudge, PAYLOAD_LOCATION_SHADOW); return payload_shadow.hit_type; } +#endif bool shadowed(vec3 pos, vec3 dir, float dist) { #ifdef RAY_TRACE @@ -44,7 +46,7 @@ bool shadowed(vec3 pos, vec3 dir, float dist) { // TODO join with just shadowed() bool shadowedSky(vec3 pos, vec3 dir, float dist) { -#if 1 +#ifdef RAY_TRACE const uint flags = 0 //| gl_RayFlagsCullFrontFacingTrianglesEXT //| gl_RayFlagsOpaqueEXT @@ -54,6 +56,7 @@ bool shadowedSky(vec3 pos, vec3 dir, float dist) { const uint hit_type = traceShadowRay(pos, dir, dist, flags); return payload_shadow.hit_type != SHADOW_SKY; #else + // FIXME ray query return false; #endif } diff --git a/ref_vk/shaders/ray_light_direct.glsl b/ref_vk/shaders/ray_light_direct.glsl index dde2126d..92b25f74 100644 --- a/ref_vk/shaders/ray_light_direct.glsl +++ b/ref_vk/shaders/ray_light_direct.glsl @@ -1,5 +1,4 @@ #extension GL_EXT_control_flow_attributes : require -#extension GL_EXT_ray_tracing: require #include "utils.glsl" #include "noise.glsl" @@ -20,8 +19,6 @@ layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; } ubo; #include "ray_kusochki.glsl" -#define RAY_TRACE -#define RAY_TRACE2 #undef SHADER_OFFSET_HIT_SHADOW_BASE #define SHADER_OFFSET_HIT_SHADOW_BASE 0 #undef SHADER_OFFSET_MISS_SHADOW @@ -40,10 +37,21 @@ void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { } void main() { +#ifdef RAY_TRACE const vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.; const ivec2 pix = ivec2(gl_LaunchIDEXT.xy); +#elif defined(RAY_QUERY) + const ivec2 pix = ivec2(gl_GlobalInvocationID); + const ivec2 res = ivec2(imageSize(material_rmxx)); + if (any(greaterThanEqual(pix, res))) { + return; + } + const vec2 uv = (gl_GlobalInvocationID.xy + .5) / res * 2. - 1.; +#else +#error You have two choices here. Ray trace, or Rake Yuri. So what it's gonna be, huh? Choose wisely. +#endif - rand01_state = ubo.ubo.random_seed + gl_LaunchIDEXT.x * 1833 + gl_LaunchIDEXT.y * 31337; + rand01_state = ubo.ubo.random_seed + pix.x * 1833 + pix.y * 31337; // FIXME incorrect for reflection/refraction const vec4 target = ubo.ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); diff --git a/ref_vk/shaders/ray_light_direct_point.comp b/ref_vk/shaders/ray_light_direct_point.comp new file mode 100644 index 00000000..09501387 --- /dev/null +++ b/ref_vk/shaders/ray_light_direct_point.comp @@ -0,0 +1,9 @@ +#version 460 core +#extension GL_GOOGLE_include_directive : require +#extension GL_EXT_ray_query: require + +#define RAY_QUERY + +#define LIGHT_POINT 1 +#define OUTPUTS RAY_LIGHT_DIRECT_POINT_OUTPUTS +#include "ray_light_direct.glsl" diff --git a/ref_vk/shaders/ray_light_direct_point.rgen b/ref_vk/shaders/ray_light_direct_point.rgen index f8bbfc6c..0e67591f 100644 --- a/ref_vk/shaders/ray_light_direct_point.rgen +++ b/ref_vk/shaders/ray_light_direct_point.rgen @@ -1,5 +1,9 @@ #version 460 core #extension GL_GOOGLE_include_directive : require +#extension GL_EXT_ray_tracing: require + +#define RAY_TRACE +#define RAY_TRACE2 #define LIGHT_POINT 1 #define OUTPUTS RAY_LIGHT_DIRECT_POINT_OUTPUTS diff --git a/ref_vk/shaders/ray_light_direct_poly.comp b/ref_vk/shaders/ray_light_direct_poly.comp new file mode 100644 index 00000000..6b03668e --- /dev/null +++ b/ref_vk/shaders/ray_light_direct_poly.comp @@ -0,0 +1,9 @@ +#version 460 core +#extension GL_GOOGLE_include_directive : require +#extension GL_EXT_ray_query: require + +#define RAY_QUERY + +#define LIGHT_POLYGON 1 +#define OUTPUTS RAY_LIGHT_DIRECT_POLY_OUTPUTS +#include "ray_light_direct.glsl" diff --git a/ref_vk/shaders/ray_light_poly_direct.rgen b/ref_vk/shaders/ray_light_poly_direct.rgen index 7fbd1945..105242ea 100644 --- a/ref_vk/shaders/ray_light_poly_direct.rgen +++ b/ref_vk/shaders/ray_light_poly_direct.rgen @@ -1,5 +1,9 @@ #version 460 core #extension GL_GOOGLE_include_directive : require +#extension GL_EXT_ray_tracing: require + +#define RAY_TRACE +#define RAY_TRACE2 #define LIGHT_POLYGON 1 #define OUTPUTS RAY_LIGHT_DIRECT_POLY_OUTPUTS diff --git a/ref_vk/shaders/rt.json b/ref_vk/shaders/rt.json index b67196a4..b8e10225 100644 --- a/ref_vk/shaders/rt.json +++ b/ref_vk/shaders/rt.json @@ -1,4 +1,5 @@ { + "primary_ray": { "rgen": "ray_primary", "miss": [ @@ -9,22 +10,28 @@ {"closest": "ray_primary", "any": "ray_common_alphatest"} ] }, - "light_direct": { - "template": true, - "miss": [ - "ray_shadow" - ], - "hit": [ - {"closest": "ray_shadow", "any": "ray_common_alphatest"} - ] - }, +// "light_direct": { +// "template": true, +// "miss": [ +// "ray_shadow" +// ], +// "hit": [ +// {"closest": "ray_shadow", "any": "ray_common_alphatest"} +// ] +// }, +// "light_direct_poly": { +// "inherit": "light_direct", +// "rgen": "ray_light_poly_direct" +// }, +// "light_direct_point": { +// "inherit": "light_direct", +// "rgen": "ray_light_direct_point" +// }, "light_direct_poly": { - "inherit": "light_direct", - "rgen": "ray_light_poly_direct" + "comp": "ray_light_direct_poly" }, "light_direct_point": { - "inherit": "light_direct", - "rgen": "ray_light_direct_point" + "comp": "ray_light_direct_point" }, "denoiser": { "comp": "denoiser" diff --git a/ref_vk/vk_core.c b/ref_vk/vk_core.c index f2b0911a..e0eaa833 100644 --- a/ref_vk/vk_core.c +++ b/ref_vk/vk_core.c @@ -93,6 +93,7 @@ static const char* device_extensions[] = { VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME, VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME, + VK_KHR_RAY_QUERY_EXTENSION_NAME, // FIXME make this not depend on RTX #ifdef USE_AFTERMATH @@ -493,9 +494,15 @@ static qboolean createDevice( void ) { .rayTracingPipeline = VK_TRUE, // TODO .rayTraversalPrimitiveCulling = VK_TRUE, }; + head = &ray_tracing_pipeline_feature; + VkPhysicalDeviceRayQueryFeaturesKHR ray_query_pipeline_feature = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR, + .pNext = head, + .rayQuery = VK_TRUE, + }; if (vk_core.rtx) { - head = &ray_tracing_pipeline_feature; + head = &ray_query_pipeline_feature; } else { head = NULL; } diff --git a/ref_vk/wscript b/ref_vk/wscript index 151186ee..e2f6bb9b 100644 --- a/ref_vk/wscript +++ b/ref_vk/wscript @@ -86,8 +86,8 @@ def build(bld): libpath = [] source = bld.path.ant_glob(['*.c']) - glsl_source = bld.path.ant_glob(['shaders/*.vert', 'shaders/*.frag', 'shaders/*.comp']) - rtx_glsl_source = bld.path.ant_glob(['shaders/*.rgen', 'shaders/*.rchit', 'shaders/*.rmiss', 'shaders/*.rahit']) + glsl_source = bld.path.ant_glob(['shaders/*.vert', 'shaders/*.frag']) + rtx_glsl_source = bld.path.ant_glob(['shaders/*.rgen', 'shaders/*.rchit', 'shaders/*.rmiss', 'shaders/*.rahit', 'shaders/*.comp']) meatpipes = bld.path.ant_glob(['shaders/*.json']) From b1125675523989a82f98726ef7fc880dc44ac3c5 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 26 Nov 2022 13:01:29 -0800 Subject: [PATCH 503/548] rt: add env light shadows --- ref_vk/shaders/light_common.glsl | 27 ++++++++++++++++++++++++--- ref_vk/shaders/ray_kusochki.glsl | 4 ++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/ref_vk/shaders/light_common.glsl b/ref_vk/shaders/light_common.glsl index 3c6d5049..e4d9c883 100644 --- a/ref_vk/shaders/light_common.glsl +++ b/ref_vk/shaders/light_common.glsl @@ -1,6 +1,8 @@ #ifndef LIGHT_COMMON_GLSL_INCLUDED #define LIGHT_COMMON_GLSL_INCLUDED +#include "ray_kusochki.glsl" + #ifdef RAY_TRACE2 #include "ray_shadow_interface.glsl" layout(location = PAYLOAD_LOCATION_SHADOW) rayPayloadEXT RayPayloadShadow payload_shadow; @@ -37,10 +39,11 @@ bool shadowed(vec3 pos, vec3 dir, float dist) { //| gl_RayFlagsSkipClosestHitShaderEXT ; rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE, pos, 0., dir, dist - shadow_offset_fudge); + // TODO alpha test while (rayQueryProceedEXT(rq)) { } return rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT; #else - return false; +#error RAY_TRACE or RAY_QUERY #endif } @@ -55,9 +58,27 @@ bool shadowedSky(vec3 pos, vec3 dir, float dist) { ; const uint hit_type = traceShadowRay(pos, dir, dist, flags); return payload_shadow.hit_type != SHADOW_SKY; +#elif defined(RAY_QUERY) + rayQueryEXT rq; + const uint flags = 0 + //| gl_RayFlagsCullFrontFacingTrianglesEXT + //| gl_RayFlagsOpaqueEXT + //| gl_RayFlagsTerminateOnFirstHitEXT + //| gl_RayFlagsSkipClosestHitShaderEXT + ; + rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE, pos, 0., dir, dist - shadow_offset_fudge); + // TODO alpha test + while (rayQueryProceedEXT(rq)) { } + + if (rayQueryGetIntersectionTypeEXT(rq, true) != gl_RayQueryCommittedIntersectionTriangleEXT) + return true; + + const int instance_kusochki_offset = rayQueryGetIntersectionInstanceCustomIndexEXT(rq, true); + const int kusok_index = instance_kusochki_offset + rayQueryGetIntersectionGeometryIndexEXT(rq, true); + const uint tex_base_color = getKusok(kusok_index).tex_base_color; + return (tex_base_color & KUSOK_MATERIAL_FLAG_SKYBOX) == 0; #else - // FIXME ray query - return false; +#error RAY_TRACE or RAY_QUERY #endif } diff --git a/ref_vk/shaders/ray_kusochki.glsl b/ref_vk/shaders/ray_kusochki.glsl index 96546e66..1b016ff1 100644 --- a/ref_vk/shaders/ray_kusochki.glsl +++ b/ref_vk/shaders/ray_kusochki.glsl @@ -1,3 +1,5 @@ +#ifndef RAY_KUSOCHKI_GLSL_INCLUDED +#define RAY_KUSOCHKI_GLSL_INCLUDED #extension GL_EXT_shader_16bit_storage : require //#extension GL_EXT_shader_8bit_storage : require @@ -24,3 +26,5 @@ layout(std430, binding = 5, set = 0) readonly buffer Vertices { Vertex a[]; } ve Kusok getKusok(uint index) { return kusochki.a[index]; } uint16_t getIndex(uint index) { return indices.a[index]; } Vertex getVertex(uint index) { return vertices.a[index]; } + +#endif //ifndef RAY_KUSOCHKI_GLSL_INCLUDED From 3f6171bfa92f0ae4c8e153a32343a6caa049fd79 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 18 Dec 2022 21:41:33 -0800 Subject: [PATCH 504/548] update todo --- ref_vk/TODO.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index 773dcd1e..7e0861ad 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,27 +1,17 @@ # Real next -- [x] E213: - - [x] parse binding types - - [x] remove types from resources FIXME -- [x] E214: ~tentative~ - - [x] integrate sebastian into waf -- [x] E215: - - [x] serialize binding image format -- [ ] E216: +>=E217 - [ ] meatpipe resource tracking - [ ] name -> index mapping - - [x] validate image formats - [ ] create images on meatpipe load - - [ ] begin Rake Yuri migration - ->=E217 - [ ] automatic resource creation - [ ] serialize all resources with in/out and formats for images - [ ] resource management refactoring: - - [ ] resource object: name, metadata(type, etc.), producer, status (ready, barriers, etc) - [ ] resource automatic resolution: prducing, barriers, etc - [ ] register existing resources (tlas, buffers, temp images, ...) - [ ] resource destruction - [ ] create resources on demand + - [ ] ? resource object: name, metadata(type, etc.), producer, status (ready, barriers, etc) + - [ ] rake yuri primary ray # Programmable render - [x] parse spirv -> get bindings with names @@ -488,3 +478,17 @@ - BUT: filled on CPU, so it's not properly synchronsized - fix: upload using staging? - [x] double/ring buffering + +- [x] E213: + - [x] parse binding types + - [x] remove types from resources FIXME +- [x] E214: ~tentative~ + - [x] integrate sebastian into waf +- [x] E215: + - [x] serialize binding image format + +# 2022-11-26 E216 rake yuri + - [x] validate meatpipe image formats + - [x] begin Rake Yuri migration + - [x] direct lights + From d96917c693c40e0961d579f7db4ddf350eacbdc9 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Mon, 16 Jan 2023 12:41:37 -0800 Subject: [PATCH 505/548] [WIP] meatpipe: export arrays and meatpipe bindings directly compiles and runs, but doesn't draw anything yet --- ref_vk/ray_pass.c | 13 ++-- ref_vk/ray_pass.h | 14 +++- ref_vk/ray_resources.c | 101 ++++++++++++++++++++++----- ref_vk/ray_resources.h | 54 ++++++++------- ref_vk/sebastian.py | 34 +++++++-- ref_vk/vk_descriptor.h | 3 +- ref_vk/vk_meatpipe.c | 153 ++++++++++++++++++++++------------------- ref_vk/vk_meatpipe.h | 38 ++++++++-- ref_vk/vk_rtx.c | 134 +++++++++++++++++------------------- 9 files changed, 335 insertions(+), 209 deletions(-) diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c index bcf93e76..f062028d 100644 --- a/ref_vk/ray_pass.c +++ b/ref_vk/ray_pass.c @@ -4,6 +4,7 @@ #include "vk_descriptor.h" // FIXME this is only needed for MAX_CONCURRENT_FRAMES +// TODO specify it externally as ctor arg #include "vk_framectl.h" #define MAX_STAGES 16 @@ -22,7 +23,6 @@ typedef struct ray_pass_s { struct { vk_descriptors_t riptors; VkDescriptorSet sets[MAX_CONCURRENT_FRAMES]; - int *binding_semantics; } desc; } ray_pass_t; @@ -49,10 +49,6 @@ static void initPassDescriptors( ray_pass_t *header, const ray_pass_layout_t *la } static void finalizePassDescriptors( ray_pass_t *header, const ray_pass_layout_t *layout ) { - const size_t semantics_size = sizeof(int) * layout->bindings_count; - header->desc.binding_semantics = Mem_Malloc(vk_core.pool, semantics_size); - memcpy(header->desc.binding_semantics, layout->bindings_semantics, semantics_size); - const size_t bindings_size = sizeof(layout->bindings[0]) * layout->bindings_count; VkDescriptorSetLayoutBinding *bindings = Mem_Malloc(vk_core.pool, bindings_size); memcpy(bindings, layout->bindings, bindings_size); @@ -228,7 +224,6 @@ void RayPassDestroy( struct ray_pass_s *pass ) { VK_DescriptorsDestroy(&pass->desc.riptors); Mem_Free(pass->desc.riptors.values); - Mem_Free(pass->desc.binding_semantics); Mem_Free((void*)pass->desc.riptors.bindings); Mem_Free(pass); } @@ -248,12 +243,11 @@ static void performCompute( VkCommandBuffer cmdbuf, int set_slot, const ray_pass vkCmdDispatch(cmdbuf, (res->width + WG_W - 1) / WG_W, (res->height + WG_H - 1) / WG_H, 1); } -void RayPassPerform( VkCommandBuffer cmdbuf, int frame_set_slot, struct ray_pass_s *pass, struct vk_ray_resources_s *res) { +/* +void RayPassPerform( VkCommandBuffer cmdbuf, int frame_set_slot, struct ray_pass_s *pass, const struct vk_ray_resources_s *res) { { ray_resources_fill_t fill = { - .resources = res, .count = pass->desc.riptors.num_bindings, - .indices = pass->desc.binding_semantics, .out_values = pass->desc.riptors.values, }; @@ -292,3 +286,4 @@ void RayPassPerform( VkCommandBuffer cmdbuf, int frame_set_slot, struct ray_pass DEBUG_END(cmdbuf); } +*/ diff --git a/ref_vk/ray_pass.h b/ref_vk/ray_pass.h index 5adbee98..99f1d157 100644 --- a/ref_vk/ray_pass.h +++ b/ref_vk/ray_pass.h @@ -7,7 +7,6 @@ // - expose it as a struct[] interface of the pass // - resource/interface should prepare descriptors outside of pass code and just pass them to pass typedef struct { - const int *bindings_semantics; // RayResource_something const VkDescriptorSetLayoutBinding *bindings; int bindings_count; @@ -19,6 +18,7 @@ struct ray_pass_s; typedef struct ray_pass_s* ray_pass_p; typedef struct { + // TODO int num_frame_slots; const char *debug_name; ray_pass_layout_t layout; @@ -35,6 +35,7 @@ typedef struct { } ray_pass_hit_group_t; typedef struct { + // TODO int num_frame_slots; const char *debug_name; ray_pass_layout_t layout; @@ -57,5 +58,12 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create void RayPassDestroy( struct ray_pass_s *pass ); -struct vk_ray_resources_s; -void RayPassPerform( VkCommandBuffer cmdbuf, int frame_set_slot, struct ray_pass_s *pass, struct vk_ray_resources_s *res); +struct vk_ray_resource_handle_s; +typedef struct ray_pass_perform_args_s { + int frame_set_slot; // 0 or 1, until we do num_frame_slots + int width, height; + const struct vk_ray_resource_handle_s *resources; +} ray_pass_perform_args_t; + +void RayPassPerform(struct ray_pass_s *pass, VkCommandBuffer cmdbuf, ray_pass_perform_args_t args ); + diff --git a/ref_vk/ray_resources.c b/ref_vk/ray_resources.c index 9d13035b..0bb00853 100644 --- a/ref_vk/ray_resources.c +++ b/ref_vk/ray_resources.c @@ -6,6 +6,67 @@ #include #define MAX_BARRIERS 16 +#define MAX_RESOURCES 32 +#define MAX_NAME 32 + +typedef struct vk_named_resource_t { + char name[MAX_NAME]; + int count; + ray_resource_desc_t desc_fixme; +} vk_named_resource_t; + +static struct { + vk_named_resource_t named[MAX_RESOURCES]; + ray_resource_t resources[RayResource__COUNT]; +} g_resources; + +static int findSlot(const char* name) { + // Find the exact match if exists + // There might be gaps, so we need to check everything + for (int i = 0; i < MAX_RESOURCES; ++i) { + if (strcmp(g_resources.named[i].name, name) == 0) + return i; + } + + // Find first free slot + for (int i = 0; i < MAX_RESOURCES; ++i) { + if (!g_resources.named[i].name[0]) + return i; + } + + return -1; +} + +qboolean R_VkResourceSetExternal(const char* name, VkDescriptorType type, vk_descriptor_value_t value, int count, ray_resource_desc_t desc, const xvk_image_t *image) { + if (strlen(name) >= MAX_NAME) + return false; + + const int index = findSlot(name); + if (index < 0) + return false; + + vk_named_resource_t *const r = g_resources.named + index; + ray_resource_t *const rr = g_resources.resources + index; + + if (!r->name[0]) { + strncpy(r->name, name, sizeof(r->name)); + rr->type = type; + r->count = count; + r->desc_fixme = desc; + } else { + ASSERT(rr->type == type); + ASSERT(r->count == count); + ASSERT(r->desc_fixme.type == desc.type); + ASSERT(r->desc_fixme.image_format == desc.image_format); + } + + rr->value = value; + rr->image = image; + memset(&rr->read, 0, sizeof(rr->read)); + memset(&rr->write, 0, sizeof(rr->write)); + + return true; +} void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill) { VkImageMemoryBarrier image_barriers[MAX_BARRIERS]; @@ -15,7 +76,7 @@ void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill) { for (int i = 0; i < fill.count; ++i) { const qboolean write = fill.indices[i] < 0; const int index = abs(fill.indices[i]) - 1; - ray_resource_t *const res = fill.resources->resources + index; + ray_resource_t *const res = g_resources.resources + index; ASSERT(index >= 0); ASSERT(index < RayResource__COUNT); @@ -97,15 +158,14 @@ void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill) { 0, 0, NULL, 0, NULL, image_barriers_count, image_barriers); } } + +#if 0 static const struct { - const char *name; - ray_resource_type_e type; - int image_format; ray_resource_binding_desc_fixme_t desc; } fixme_descs[] = { #define FIXME_DESC_BUFFER(name_, semantic_, count_) \ - { .name = name_, \ + { \ .type = ResourceBuffer, \ .desc.semantic = (RayResource_##semantic_ + 1), \ .desc.count = count_, \ @@ -122,7 +182,7 @@ static const struct { FIXME_DESC_BUFFER("light_grid", light_clusters, 1), #define FIXME_DESC_IMAGE(name_, semantic_, image_format_, count_) \ - { .name = name_, \ + { \ .type = ResourceImage, \ .image_format = image_format_, \ .desc.semantic = (RayResource_##semantic_ + 1), \ @@ -135,7 +195,7 @@ static const struct { // Internal, temporary #define DECLARE_IMAGE(_, name_, format_) \ - { .name = #name_, \ + { \ .type = ResourceImage, \ .image_format = format_, \ .desc.semantic = (RayResource_##name_ + 1), \ @@ -148,23 +208,28 @@ static const struct { RAY_LIGHT_DIRECT_POLY_OUTPUTS(DECLARE_IMAGE) RAY_LIGHT_DIRECT_POINT_OUTPUTS(DECLARE_IMAGE) }; +#endif -const ray_resource_binding_desc_fixme_t *RayResouceGetBindingForName_FIXME(const char *name, ray_resource_desc_t desc) { - for (int i = 0; i < COUNTOF(fixme_descs); ++i) { - if (strcmp(name, fixme_descs[i].name) != 0) +/* +ray_resource_binding_desc_fixme_t RayResouceGetBindingForName_FIXME(const char *name, ray_resource_desc_t desc) { + for (int i = 0; i < MAX_RESOURCES; ++i) { + const vk_named_resource_t *const res = g_resources.named + i; + if (strcmp(name, res->name) != 0) continue; - if (fixme_descs[i].type != desc.type) { - gEngine.Con_Printf(S_ERROR "Incompatible resource types for name %s: want %d, have %d\n", name, desc.type, fixme_descs[i].type); - return NULL; + if (res->desc_fixme.type != desc.type) { + gEngine.Con_Printf(S_ERROR "Incompatible resource types for name %s: want %d, have %d\n", name, desc.type, res->desc_fixme.type); + break; } - if (fixme_descs[i].type == ResourceImage && fixme_descs[i].image_format != VK_FORMAT_UNDEFINED && desc.image_format != fixme_descs[i].image_format) { - gEngine.Con_Printf(S_ERROR "Incompatible image formats for name %s: want %d, have %d\n", name, desc.image_format, fixme_descs[i].image_format); - return NULL; + if (res->desc_fixme.type == ResourceImage && res->desc_fixme.image_format != VK_FORMAT_UNDEFINED && desc.image_format != res->desc_fixme.image_format) { + gEngine.Con_Printf(S_ERROR "Incompatible image formats for name %s: want %d, have %d\n", name, desc.image_format, res->desc_fixme.image_format); + break; } - return &fixme_descs[i].desc; + return (ray_resource_binding_desc_fixme_t){i, res->count}; } - return NULL; + + return (ray_resource_binding_desc_fixme_t){-1,-1}; } +*/ diff --git a/ref_vk/ray_resources.h b/ref_vk/ray_resources.h index 2fe6b1b6..35cd5bd4 100644 --- a/ref_vk/ray_resources.h +++ b/ref_vk/ray_resources.h @@ -1,12 +1,36 @@ #pragma once -#include "vk_rtx.h" #include "vk_const.h" #include "vk_image.h" #include "vk_descriptor.h" #include "shaders/ray_interop.h" +//qboolean R_VkResourceInit(void); +//void R_VkResourceShutdown(); + +typedef enum { + ResourceUnknown, + ResourceBuffer, + ResourceImage, +} ray_resource_type_e; + +typedef struct { + ray_resource_type_e type; + int image_format; // if type == ResourceImage +} ray_resource_desc_t; + +qboolean R_VkResourceSetExternal(const char* name, VkDescriptorType type, vk_descriptor_value_t value, int count, ray_resource_desc_t desc, const xvk_image_t *image); + +/* +typedef struct { + int semantic; + int count; +} ray_resource_binding_desc_fixme_t; + +ray_resource_binding_desc_fixme_t RayResouceGetBindingForName_FIXME(const char *name, ray_resource_desc_t desc); +*/ + #define RAY_SCENE_RESOURCES(X) \ X(TLAS, tlas) \ X(Buffer, ubo) \ @@ -29,6 +53,10 @@ enum { RayResource__COUNT }; +typedef struct vk_ray_resources_s { + uint32_t width, height; +} vk_ray_resources_t; + typedef struct { VkAccessFlags access_mask; VkImageLayout image_layout; @@ -44,13 +72,7 @@ typedef struct { }; } ray_resource_t; -typedef struct vk_ray_resources_s { - uint32_t width, height; - ray_resource_t resources[RayResource__COUNT]; -} vk_ray_resources_t; - typedef struct { - vk_ray_resources_t *resources; const int *indices; int count; VkPipelineStageFlagBits dest_pipeline; @@ -59,21 +81,3 @@ typedef struct { } ray_resources_fill_t; void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill); - -typedef struct { - int semantic; - int count; -} ray_resource_binding_desc_fixme_t; - -typedef enum { - ResourceUnknown, - ResourceBuffer, - ResourceImage, -} ray_resource_type_e; - -typedef struct { - ray_resource_type_e type; - int image_format; // if type == ResourceImage -} ray_resource_desc_t; - -const ray_resource_binding_desc_fixme_t *RayResouceGetBindingForName_FIXME(const char *name, ray_resource_desc_t desc); diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index 5362f7cc..bfc3967d 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -147,17 +147,22 @@ class TypeInfo: TYPE_SAMPLE_WEIGHT_IMAGE_QCOM = 1000440000 TYPE_BLOCK_MATCH_IMAGE_QCOM = 1000440001 - def __init__(self, type): - self.type = type + def __init__(self, type=None, parent=None, count=1): + self.type = parent.type if parent else type + self.is_image = parent.is_image if parent else False + self.image_format = parent.image_format if parent else None + self.count = count + # TODO self.writable = None # TODO self.readable = None - self.is_image = False - self.image_format = None def __eq__(self, other): if self.type != other.type: return False + if self.count != other.count: + return False + assert(self.is_image == other.is_image) if self.is_image: @@ -171,6 +176,7 @@ class TypeInfo: def serialize(self, out): out.writeU32(self.type) + out.writeU32(self.count) if self.is_image: out.writeU32(ImageFormat.mapToVk(self.image_format)) @@ -182,6 +188,8 @@ class SpirvNode: self.parent_type_node = None self.type = None self.storage_class = None + self.value = None + self.count = None def getType(self): node = self @@ -191,6 +199,9 @@ class SpirvNode: if node.type: return node.type + if node.count: + return TypeInfo(parent=node.parent_type_node.getType(), count = node.count) + node = node.parent_type_node raise Exception('Couldn\'t find type for node %s' % self.name) @@ -316,10 +327,20 @@ def spvOpTypeSampledImage(ctx, args): def spvOpTypeArray(ctx, args): node = ctx.getNode(args[0]) element_type = ctx.getNode(args[1]) - length = args[2] + length_node = args[2] + node.count = ctx.getNode(length_node).value node.parent_type_node = element_type - #print(f"{args[0]}: Array(type={args[1]}, length={length})") + + #print(f"{args[0]}: Array(type={args[1]}, length={node.count})") + +def spvOpSpecConstant(ctx, args): + type_node = ctx.getNode(args[0]) + node = ctx.getNode(args[1]) + value = args[2] + + # TODO mind the type + node.value = value spvOpHandlers = { spvOp['OpName']: spvOpName, @@ -330,6 +351,7 @@ spvOpHandlers = { spvOp['OpTypeImage']: spvOpTypeImage, spvOp['OpTypeSampledImage']: spvOpTypeSampledImage, spvOp['OpTypeArray']: spvOpTypeArray, + spvOp['OpSpecConstant']: spvOpSpecConstant, } def parseSpirv(raw_data): diff --git a/ref_vk/vk_descriptor.h b/ref_vk/vk_descriptor.h index 8d4adb7f..2dff5545 100644 --- a/ref_vk/vk_descriptor.h +++ b/ref_vk/vk_descriptor.h @@ -40,13 +40,12 @@ typedef struct { vk_descriptor_value_t *values; VkPushConstantRange push_constants; - int num_sets; VkPipelineLayout pipeline_layout; VkDescriptorSetLayout desc_layout; VkDescriptorPool desc_pool; - int num_desc_sets; + int num_sets; VkDescriptorSet *desc_sets; } vk_descriptors_t; diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 8f0ced44..2adffd6d 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -11,29 +11,27 @@ #define CHAR4UINT(a,b,c,d) (((d)<<24)|((c)<<16)|((b)<<8)|(a)) static const uint32_t k_meatpipe_magic = CHAR4UINT('M', 'E', 'A', 'T'); -typedef struct { +typedef struct cursor_t { const byte *data; int off, size; qboolean error; } cursor_t; -typedef struct { - char name[64]; - uint32_t type; - int count; - int semantic; -} res_t; - -typedef struct { +typedef struct load_context_t { cursor_t cur; int shaders_count; VkShaderModule *shaders; - int res_count; - res_t *res; + vk_meatpipe_t meatpipe; } load_context_t; +typedef struct vk_meatpipe_pass_s { + ray_pass_p pass; + int resource_count; + int *resource_map; +} vk_meatpipe_pass_t; + const void* curReadPtr(cursor_t *cur, int size) { const int left = cur->size - cur->off; if (left < size) { @@ -166,25 +164,27 @@ finalize: } #define MAX_BINDINGS 32 -static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindings, int* semantics) { +static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindings, int** mapping) { const int count = READ_U32_RETURN(false, "Coulnd't read bindings count"); if (count > MAX_BINDINGS) { gEngine.Con_Printf(S_ERROR "Too many binding (%d), max: %d\n", count, MAX_BINDINGS); - return 0; + goto fail; } + *mapping = Mem_Malloc(vk_core.pool, sizeof(int) * count); + for (int i = 0; i < count; ++i) { const uint32_t header = READ_U32_RETURN(false, "Couldn't read header for binding %d", i); const uint32_t res_index = READ_U32_RETURN(false, "Couldn't read res index for binding %d", i); const uint32_t stages = READ_U32_RETURN(false, "Couldn't read stages for binding %d", i); - if (res_index >= ctx->res_count) { - gEngine.Con_Printf(S_ERROR "Resource %d is out of bound %d for binding %d", res_index, ctx->res_count, i); - return false; + if (res_index >= ctx->meatpipe.resources_count) { + gEngine.Con_Printf(S_ERROR "Resource %d is out of bound %d for binding %d", res_index, ctx->meatpipe.resources_count, i); + goto fail; } - const res_t *res = ctx->res + res_index; + vk_meatpipe_resource_t *res = ctx->meatpipe.resources + res_index; #define BINDING_WRITE_BIT 0x80000000u const qboolean write = !!(header & BINDING_WRITE_BIT); @@ -195,28 +195,39 @@ static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindi bindings[i] = (VkDescriptorSetLayoutBinding){ .binding = binding, - .descriptorType = res->type, + .descriptorType = res->descriptor_type, .descriptorCount = res->count, .stageFlags = stages, .pImmutableSamplers = NULL, }; - semantics[i] = write ? -res->semantic : res->semantic; - gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x res=%d type=%d semantic=%d\n", i, name, descriptor_set, binding, stages, res_index, res->type, res->semantic); + (*mapping)[i] = res_index; + + if (write) + res->flags |= MEATPIPE_RES_WRITE | MEATPIPE_RES_CREATE; // TODO distinguish between write and create + + gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x res=%d type=%d\n", + i, name, descriptor_set, binding, stages, res_index, res->descriptor_type); } return count; + +fail: + Mem_Free(*mapping); + *mapping = NULL; + return 0; } -static struct ray_pass_s *readAndCreatePass(load_context_t *ctx, int i) { - int semantics[MAX_BINDINGS]; +static qboolean readAndCreatePass(load_context_t *ctx, int i) { VkDescriptorSetLayoutBinding bindings[MAX_BINDINGS]; ray_pass_layout_t layout = { - .bindings_semantics = semantics, .bindings = bindings, .push_constants = {0}, }; + vk_meatpipe_pass_t *pass = ctx->meatpipe.passes + i; + memset(pass, 0, sizeof(*pass)); + const uint32_t type = READ_U32("Couldn't read pipeline %d type", i); char name[64]; @@ -224,10 +235,10 @@ static struct ray_pass_s *readAndCreatePass(load_context_t *ctx, int i) { gEngine.Con_Reportf("%d: loading pipeline %s\n", i, name); - layout.bindings_count = readBindings(ctx, bindings, semantics); + pass->resource_count = layout.bindings_count = readBindings(ctx, bindings, &pass->resource_map); if (!layout.bindings_count) { gEngine.Con_Printf(S_ERROR "Couldn't read bindings for pipeline %s\n", name); - return NULL; + return false; } #define PIPELINE_COMPUTE 1 @@ -235,50 +246,43 @@ static struct ray_pass_s *readAndCreatePass(load_context_t *ctx, int i) { switch (type) { case PIPELINE_COMPUTE: - return pipelineLoadCompute(ctx, i, name, &layout); + pass->pass = pipelineLoadCompute(ctx, i, name, &layout); + break; case PIPELINE_RAYTRACING: - return pipelineLoadRT(ctx, i, name, &layout); + pass->pass = pipelineLoadRT(ctx, i, name, &layout); + break; default: gEngine.Con_Printf(S_ERROR "Unexpected pipeline type %d\n", type); - return NULL; } + if (pass->pass) + return true; + finalize: - return NULL; + if (pass->resource_map) + Mem_Free(pass->resource_map); + return false; } static qboolean readResources(load_context_t *ctx) { - ctx->res_count = READ_U32("Couldn't read resources count"); - ctx->res = Mem_Malloc(vk_core.pool, sizeof(ctx->res[0]) * ctx->res_count); + ctx->meatpipe.resources_count = READ_U32("Couldn't read resources count"); + ctx->meatpipe.resources = Mem_Malloc(vk_core.pool, sizeof(ctx->meatpipe.resources[0]) * ctx->meatpipe.resources_count); - for (int i = 0; i < ctx->res_count; ++i) { - res_t *res = ctx->res + i; + for (int i = 0; i < ctx->meatpipe.resources_count; ++i) { + vk_meatpipe_resource_t *res = ctx->meatpipe.resources + i; READ_STR(res->name, "Couldn't read resource %d name", i); - res->type = READ_U32("Couldn't read resource %d:%s type", i, res->name); - - const qboolean is_image = res->type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE || res->type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - - ray_resource_desc_t rrdesc = { - .type = is_image ? ResourceImage : ResourceBuffer, - .image_format = VK_FORMAT_UNDEFINED, - }; + res->descriptor_type = READ_U32("Couldn't read resource %d:%s type", i, res->name); + res->count = READ_U32("Couldn't read resource %d:%s count", i, res->name); + const qboolean is_image = res->descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE || res->descriptor_type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; if (is_image) { - rrdesc.image_format = READ_U32("Couldn't read image format for res %d:%s", i, res->name); + res->image_format = READ_U32("Couldn't read image format for res %d:%s", i, res->name); } - const ray_resource_binding_desc_fixme_t *binding_fixme = RayResouceGetBindingForName_FIXME(res->name, rrdesc); - if (!binding_fixme) { - gEngine.Con_Printf(S_ERROR "Couldn't find fixme desc for binding %s\n", res->name); - return 0; - } - - res->count = binding_fixme->count; - res->semantic = binding_fixme->semantic; - - gEngine.Con_Reportf("Resource %d:%s = %08x is_image=%d image_format=%08x count=%d semantic=%d\n", i, res->name, res->type, is_image, rrdesc.image_format, res->count, res->semantic); + gEngine.Con_Reportf("Resource %d:%s = %08x is_image=%d image_format=%08x count=%d\n", + i, res->name, res->descriptor_type, is_image, res->image_format, res->count); } return true; @@ -311,27 +315,24 @@ finalize: return false; } -qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename) { - qboolean ret = false; +vk_meatpipe_t* R_VkMeatpipeCreateFromFile(const char *filename) { + vk_meatpipe_t *ret = NULL; fs_offset_t size = 0; byte* const buf = gEngine.fsapi->LoadFile(filename, &size, false); if (!buf) { gEngine.Con_Printf(S_ERROR "Couldn't read \"%s\"\n", filename); - return false; + return NULL; } load_context_t context = { .cur = { .data = buf, .off = 0, .size = size }, .shaders_count = 0, .shaders = NULL, - .res_count = 0, - .res = NULL, + .meatpipe = (vk_meatpipe_t){0}, }; load_context_t *ctx = &context; - out->passes_count = 0; - { const uint32_t magic = READ_U32("Couldn't read magic"); @@ -347,18 +348,24 @@ qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename) { if (!readAndLoadShaders(ctx)) goto finalize; - out->passes_count = READ_U32("Couldn't read pipelines count"); - out->passes = Mem_Malloc(vk_core.pool, sizeof(out->passes[0]) * out->passes_count); - for (int i = 0; i < out->passes_count; ++i) { - if (!(out->passes[i] = readAndCreatePass(ctx, i))) + ctx->meatpipe.passes_count = READ_U32("Couldn't read pipelines count"); + ctx->meatpipe.passes = Mem_Malloc(vk_core.pool, sizeof(ctx->meatpipe.passes[0]) * ctx->meatpipe.passes_count); + for (int i = 0; i < ctx->meatpipe.passes_count; ++i) { + if (!readAndCreatePass(ctx, i)) { + for (int j = 0; j < i; ++j) { + RayPassDestroy(ctx->meatpipe.passes[j].pass); + Mem_Free(ctx->meatpipe.passes[j].resource_map); + } goto finalize; + } } - ret = true; -finalize: - if (!ret) - R_VkMeatpipeDestroy(out); + // Loading successful, allocate and fill + ret = Mem_Malloc(vk_core.pool, sizeof(*ret)); + memcpy(ret, &ctx->meatpipe, sizeof(*ret)); + ctx->meatpipe.resources = NULL; +finalize: for (int i = 0; i < ctx->shaders_count; ++i) { if (ctx->shaders[i] == VK_NULL_HANDLE) break; @@ -369,14 +376,15 @@ finalize: if (ctx->shaders) Mem_Free(ctx->shaders); - if (ctx->res) - Mem_Free(ctx->res); + if (ctx->meatpipe.resources) + Mem_Free(ctx->meatpipe.resources); Mem_Free(buf); return ret; } void R_VkMeatpipeDestroy(vk_meatpipe_t *mp) { + /* FIXME for (int i = 0; i < mp->passes_count; ++i) { if (!mp->passes[i]) break; @@ -385,10 +393,15 @@ void R_VkMeatpipeDestroy(vk_meatpipe_t *mp) { } mp->passes_count = 0; + */ } -void R_VkMeatpipePerform(vk_meatpipe_t *mp, VkCommandBuffer cmdbuf, int frame_set_slot, struct vk_ray_resources_s *res) { +void R_VkMeatpipePerform(vk_meatpipe_t *mp, VkCommandBuffer cmdbuf, vk_meatpipe_perfrom_args_t args) { + // FIXME +/* +void R_VkMeatpipePerform(vk_meatpipe_t *mp, VkCommandBuffer cmdbuf, int frame_set_slot, const struct vk_ray_resources_s *res) { for (int i = 0; i < mp->passes_count; ++i) { RayPassPerform(cmdbuf, frame_set_slot, mp->passes[i], res); } + */ } diff --git a/ref_vk/vk_meatpipe.h b/ref_vk/vk_meatpipe.h index e0497eca..6473df9c 100644 --- a/ref_vk/vk_meatpipe.h +++ b/ref_vk/vk_meatpipe.h @@ -1,15 +1,41 @@ #pragma once -#include "ray_pass.h" -#include "xash3d_types.h" +#include "vk_core.h" + +enum { + MEATPIPE_RES_WRITE = (1<<0), + MEATPIPE_RES_CREATE = (1<<1), + // TMP .. +}; +typedef struct { + char name[64]; + uint32_t descriptor_type; + int count; + uint32_t flags; + union { + uint32_t image_format; + }; +} vk_meatpipe_resource_t; + + +struct vk_meatpipe_pass_s; typedef struct { int passes_count; - ray_pass_p *passes; + struct vk_meatpipe_pass_s *passes; + + int resources_count; + vk_meatpipe_resource_t *resources; } vk_meatpipe_t; -qboolean R_VkMeatpipeLoad(vk_meatpipe_t *out, const char *filename); +vk_meatpipe_t* R_VkMeatpipeCreateFromFile(const char *filename); void R_VkMeatpipeDestroy(vk_meatpipe_t *mp); -struct vk_ray_resources_s; -void R_VkMeatpipePerform(vk_meatpipe_t *mp, VkCommandBuffer cmdbuf, int frame_set_slot, struct vk_ray_resources_s *res); +struct vk_ray_resource_handle_s; +typedef struct vk_meatpipe_perfrom_args_s { + int frame_set_slot; // 0 or 1, until we do num_frame_slots + int width, height; + const struct vk_ray_resource_handle_s *resources; +} vk_meatpipe_perfrom_args_t; + +void R_VkMeatpipePerform(vk_meatpipe_t *mp, VkCommandBuffer cmdbuf, vk_meatpipe_perfrom_args_t args); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 51261609..4cfc8dec 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -1,6 +1,5 @@ #include "vk_rtx.h" -#include "ray_pass.h" #include "ray_resources.h" #include "vk_ray_accel.h" @@ -39,6 +38,10 @@ #define FRAME_HEIGHT 1080 #endif +#define rgba8 VK_FORMAT_R8G8B8A8_UNORM +#define rgba32f VK_FORMAT_R32G32B32A32_SFLOAT +#define rgba16f VK_FORMAT_R16G16B16A16_SFLOAT + // TODO sync with shaders // TODO optimal values #define WG_W 16 @@ -62,9 +65,6 @@ RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) #undef X - xvk_image_t diffuse_gi; - xvk_image_t specular; - xvk_image_t additive; } xvk_ray_frame_images_t; static struct { @@ -75,7 +75,7 @@ static struct { // TODO with proper intra-cmdbuf sync we don't really need 2x images unsigned frame_number; xvk_ray_frame_images_t frames[MAX_FRAMES_IN_FLIGHT]; - vk_meatpipe_t mainpipe; + vk_meatpipe_t *mainpipe; qboolean reload_pipeline; qboolean reload_lighting; @@ -133,69 +133,45 @@ typedef struct { } perform_tracing_args_t; static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* args) { - vk_ray_resources_t res = { + const vk_ray_resources_t res = { .width = FRAME_WIDTH, .height = FRAME_HEIGHT, - .resources = { - [RayResource_tlas] = { - .type = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, - .value.accel = (VkWriteDescriptorSetAccelerationStructureKHR){ + }; + + R_VkResourceSetExternal("tlas", VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, + (vk_descriptor_value_t){ + .accel = (VkWriteDescriptorSetAccelerationStructureKHR) { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, .accelerationStructureCount = 1, .pAccelerationStructures = &g_accel.tlas, .pNext = NULL, }, - }, + }, 1, (ray_resource_desc_t){ResourceUnknown, 0}, NULL + ); + #define RES_SET_BUFFER(name, type_, source_, offset_, size_) \ - [RayResource_##name] = { \ - .type = type_, \ - .value.buffer = (VkDescriptorBufferInfo) { \ + R_VkResourceSetExternal(#name, type_, (vk_descriptor_value_t){ \ + .buffer = (VkDescriptorBufferInfo) { \ .buffer = (source_), \ .offset = (offset_), \ .range = (size_), \ } \ - } - RES_SET_BUFFER(ubo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, g_rtx.uniform_buffer.buffer, args->frame_index * g_rtx.uniform_unit_size, sizeof(struct UniformBuffer)), + }, 1, (ray_resource_desc_t){ResourceBuffer, 0}, NULL) + + RES_SET_BUFFER(ubo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, g_rtx.uniform_buffer.buffer, args->frame_index * g_rtx.uniform_unit_size, sizeof(struct UniformBuffer)); #define RES_SET_SBUFFER_FULL(name, source_) \ RES_SET_BUFFER(name, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, source_.buffer, 0, source_.size) - RES_SET_SBUFFER_FULL(kusochki, g_ray_model_state.kusochki_buffer), - RES_SET_SBUFFER_FULL(indices, args->render_args->geometry_data), - RES_SET_SBUFFER_FULL(vertices, args->render_args->geometry_data), - RES_SET_BUFFER(lights, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, args->light_bindings->buffer, args->light_bindings->metadata.offset, args->light_bindings->metadata.size), - RES_SET_BUFFER(light_clusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, args->light_bindings->buffer, args->light_bindings->grid.offset, args->light_bindings->grid.size), + + RES_SET_SBUFFER_FULL(kusochki, g_ray_model_state.kusochki_buffer); + RES_SET_SBUFFER_FULL(indices, args->render_args->geometry_data); + RES_SET_SBUFFER_FULL(vertices, args->render_args->geometry_data); + + RES_SET_BUFFER(lights, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, args->light_bindings->buffer, args->light_bindings->metadata.offset, args->light_bindings->metadata.size); + RES_SET_BUFFER(light_clusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, args->light_bindings->buffer, args->light_bindings->grid.offset, args->light_bindings->grid.size); #undef RES_SET_SBUFFER_FULL #undef RES_SET_BUFFER - [RayResource_all_textures] = { - .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .value.image_array = tglob.dii_all_textures, - }, - - [RayResource_skybox] = { - .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .value.image = { - .sampler = vk_core.default_sampler, - .imageView = tglob.skybox_cube.vk.image.view ? tglob.skybox_cube.vk.image.view : tglob.cubemap_placeholder.vk.image.view, - .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - }, - }, - -#define RES_SET_IMAGE(index, name, ...) \ - [RayResource_##name] = { \ - .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, \ - .write = {0}, \ - .read = {0}, \ - .image = &args->current_frame->name, \ - }, - RAY_PRIMARY_OUTPUTS(RES_SET_IMAGE) - RAY_LIGHT_DIRECT_POLY_OUTPUTS(RES_SET_IMAGE) - RAY_LIGHT_DIRECT_POINT_OUTPUTS(RES_SET_IMAGE) - RES_SET_IMAGE(-1, denoised) -#undef RES_SET_IMAGE - }, - }; - // Upload kusochki updates { const VkBufferMemoryBarrier bmb[] = { { @@ -250,7 +226,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } - R_VkMeatpipePerform(&g_rtx.mainpipe, cmdbuf, args->frame_index, &res); + // FIXME R_VkMeatpipePerform(&g_rtx.mainpipe, cmdbuf, args->frame_index, &res); { const r_vkimage_blit_args blit_args = { @@ -297,9 +273,9 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) gEngine.Con_Printf(S_WARN "Reloading RTX shaders/pipelines\n"); XVK_CHECK(vkDeviceWaitIdle(vk_core.device)); - vk_meatpipe_t newpipe; - if (R_VkMeatpipeLoad(&newpipe, "rt.meat")) { - R_VkMeatpipeDestroy(&g_rtx.mainpipe); + vk_meatpipe_t *const newpipe = R_VkMeatpipeCreateFromFile("rt.meat"); + if (newpipe) { + R_VkMeatpipeDestroy(g_rtx.mainpipe); g_rtx.mainpipe = newpipe; } @@ -359,7 +335,8 @@ qboolean VK_RayInit( void ) if (!RT_VkAccelInit()) return false; - ASSERT(R_VkMeatpipeLoad(&g_rtx.mainpipe, "rt.meat")); + g_rtx.mainpipe = R_VkMeatpipeCreateFromFile("rt.meat"); + ASSERT(g_rtx.mainpipe); g_rtx.uniform_unit_size = ALIGN_UP(sizeof(struct UniformBuffer), vk_core.physical_device.properties.limits.minUniformBufferOffsetAlignment); @@ -400,9 +377,6 @@ qboolean VK_RayInit( void ) CREATE_GBUFFER_IMAGE(denoised, VK_FORMAT_R16G16B16A16_SFLOAT, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT); -#define rgba8 VK_FORMAT_R8G8B8A8_UNORM -#define rgba32f VK_FORMAT_R32G32B32A32_SFLOAT -#define rgba16f VK_FORMAT_R16G16B16A16_SFLOAT #define X(index, name, format) CREATE_GBUFFER_IMAGE(name, format, 0); // TODO better format for normals VK_FORMAT_R16G16B16A16_SNORM // TODO make sure this format and usage is suppported @@ -410,12 +384,40 @@ qboolean VK_RayInit( void ) RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) #undef X + + /* + R_VkResourceSetExternal("all_textures", VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + (vk_descriptor_value_t){ + .image_array = tglob.dii_all_textures, + }, MAX_TEXTURES, (ray_resource_desc_t){ResourceImage, VK_FORMAT_UNDEFINED}, NULL + ); + + R_VkResourceSetExternal("skybox", VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + (vk_descriptor_value_t){ + .image = { + .sampler = vk_core.default_sampler, + .imageView = tglob.skybox_cube.vk.image.view ? tglob.skybox_cube.vk.image.view : tglob.cubemap_placeholder.vk.image.view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }, + }, 1, (ray_resource_desc_t){ResourceImage, VK_FORMAT_UNDEFINED}, NULL + ); + +#define RES_SET_IMAGE(index, name, format) \ + R_VkResourceSetExternal(#name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, \ + (vk_descriptor_value_t){0}, \ + 1, (ray_resource_desc_t){ResourceImage, format}, \ + &args->current_frame->name); + + RAY_PRIMARY_OUTPUTS(RES_SET_IMAGE); + RAY_LIGHT_DIRECT_POLY_OUTPUTS(RES_SET_IMAGE); + RAY_LIGHT_DIRECT_POINT_OUTPUTS(RES_SET_IMAGE); + RES_SET_IMAGE(-1, denoised, rgba16f); + */ + +#undef RES_SET_IMAGE #undef rgba8 #undef rgba32f #undef rgba16f - CREATE_GBUFFER_IMAGE(diffuse_gi, VK_FORMAT_R16G16B16A16_SFLOAT, 0); - CREATE_GBUFFER_IMAGE(specular, VK_FORMAT_R16G16B16A16_SFLOAT, 0); - CREATE_GBUFFER_IMAGE(additive, VK_FORMAT_R16G16B16A16_SFLOAT, 0); #undef CREATE_GBUFFER_IMAGE } @@ -429,12 +431,7 @@ qboolean VK_RayInit( void ) void VK_RayShutdown( void ) { ASSERT(vk_core.rtx); - R_VkMeatpipeDestroy(&g_rtx.mainpipe); - - /* RayPassDestroy(g_rtx.pass.denoiser); */ - /* RayPassDestroy(g_rtx.pass.light_direct_poly); */ - /* RayPassDestroy(g_rtx.pass.light_direct_point); */ - /* RayPassDestroy(g_rtx.pass.primary_ray); */ + R_VkMeatpipeDestroy(g_rtx.mainpipe); for (int i = 0; i < ARRAYSIZE(g_rtx.frames); ++i) { XVK_ImageDestroy(&g_rtx.frames[i].denoised); @@ -443,9 +440,6 @@ void VK_RayShutdown( void ) { RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) #undef X - XVK_ImageDestroy(&g_rtx.frames[i].diffuse_gi); - XVK_ImageDestroy(&g_rtx.frames[i].specular); - XVK_ImageDestroy(&g_rtx.frames[i].additive); } VK_BufferDestroy(&g_ray_model_state.kusochki_buffer); From fbf2d7096decb16aa521f8815c5296caa7c29634 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Mon, 16 Jan 2023 22:57:48 -0800 Subject: [PATCH 506/548] [WIP] pass resources from meatpipe to pass, track writability doesn't draw anything yet --- ref_vk/ray_pass.c | 56 ++++++++++++++--------------- ref_vk/ray_pass.h | 8 +++-- ref_vk/ray_resources.c | 12 ++++++- ref_vk/ray_resources.h | 19 +++++----- ref_vk/sebastian.py | 36 ++++++++++++++++--- ref_vk/vk_meatpipe.c | 82 ++++++++++++++++++++++++++---------------- ref_vk/vk_meatpipe.h | 6 ++-- 7 files changed, 140 insertions(+), 79 deletions(-) diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c index f062028d..23e1b19c 100644 --- a/ref_vk/ray_pass.c +++ b/ref_vk/ray_pass.c @@ -17,10 +17,12 @@ typedef enum { } ray_pass_type_t; typedef struct ray_pass_s { - ray_pass_type_t type; + ray_pass_type_t type; // TODO remove this in favor of VkPipelineStageFlagBits + VkPipelineStageFlagBits pipeline_type; char debug_name[32]; struct { + int write_from; vk_descriptors_t riptors; VkDescriptorSet sets[MAX_CONCURRENT_FRAMES]; } desc; @@ -46,6 +48,8 @@ static void initPassDescriptors( ray_pass_t *header, const ray_pass_layout_t *la }; VK_DescriptorsCreate(&header->desc.riptors); + + header->desc.write_from = layout->write_from; } static void finalizePassDescriptors( ray_pass_t *header, const ray_pass_layout_t *layout ) { @@ -175,6 +179,7 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create Q_strncpy(header->debug_name, create->debug_name, sizeof(header->debug_name)); header->type = RayPassType_Tracing; + header->pipeline_type = VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; return header; } @@ -202,6 +207,7 @@ struct ray_pass_s *RayPassCreateCompute( const ray_pass_create_compute_t *create Q_strncpy(header->debug_name, create->debug_name, sizeof(header->debug_name)); header->type = RayPassType_Compute; + header->pipeline_type = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; return header; } @@ -228,62 +234,54 @@ void RayPassDestroy( struct ray_pass_s *pass ) { Mem_Free(pass); } -static void performTracing( VkCommandBuffer cmdbuf, int set_slot, const ray_pass_tracing_impl_t *tracing, const struct vk_ray_resources_s *res) { +static void performTracing( VkCommandBuffer cmdbuf, int set_slot, const ray_pass_tracing_impl_t *tracing, int width, int height ) { vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, tracing->pipeline.pipeline); vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, tracing->header.desc.riptors.pipeline_layout, 0, 1, tracing->header.desc.riptors.desc_sets + set_slot, 0, NULL); - VK_PipelineRayTracingTrace(cmdbuf, &tracing->pipeline, res->width, res->height); + VK_PipelineRayTracingTrace(cmdbuf, &tracing->pipeline, width, height); } -static void performCompute( VkCommandBuffer cmdbuf, int set_slot, const ray_pass_compute_impl_t *compute, const struct vk_ray_resources_s *res) { +static void performCompute( VkCommandBuffer cmdbuf, int set_slot, const ray_pass_compute_impl_t *compute, int width, int height) { const uint32_t WG_W = 8; const uint32_t WG_H = 8; vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, compute->pipeline); vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, compute->header.desc.riptors.pipeline_layout, 0, 1, compute->header.desc.riptors.desc_sets + set_slot, 0, NULL); - vkCmdDispatch(cmdbuf, (res->width + WG_W - 1) / WG_W, (res->height + WG_H - 1) / WG_H, 1); + vkCmdDispatch(cmdbuf, (width + WG_W - 1) / WG_W, (height + WG_H - 1) / WG_H, 1); } -/* -void RayPassPerform( VkCommandBuffer cmdbuf, int frame_set_slot, struct ray_pass_s *pass, const struct vk_ray_resources_s *res) { - { - ray_resources_fill_t fill = { - .count = pass->desc.riptors.num_bindings, - .out_values = pass->desc.riptors.values, - }; +void RayPassPerform(struct ray_pass_s *pass, VkCommandBuffer cmdbuf, ray_pass_perform_args_t args ) { + for (int i = 0; i < pass->desc.riptors.num_bindings; ++i) { + vk_resource_p resource = args.resources[args.resources_map ? args.resources_map[i] : i]; + vk_descriptor_value_t *value = pass->desc.riptors.values + i; - switch (pass->type) { - case RayPassType_Tracing: - fill.dest_pipeline = VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; - break; - case RayPassType_Compute: - fill.dest_pipeline = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; - break; - default: - ASSERT(!"Unexpected pass type"); - } - - RayResourcesFill(cmdbuf, fill); - - VK_DescriptorsWrite(&pass->desc.riptors, frame_set_slot); + R_VkResourceWriteToDescriptorValue(cmdbuf, + (vk_resource_write_descriptor_args_t){ + .pipeline = pass->pipeline_type, + .resource = resource, + .value = value, + .write = i >= pass->desc.write_from, + } + ); } + VK_DescriptorsWrite(&pass->desc.riptors, args.frame_set_slot); + DEBUG_BEGIN(cmdbuf, pass->debug_name); switch (pass->type) { case RayPassType_Tracing: { ray_pass_tracing_impl_t *tracing = (ray_pass_tracing_impl_t*)pass; - performTracing(cmdbuf, frame_set_slot, tracing, res); + performTracing(cmdbuf, args.frame_set_slot, tracing, args.width, args.height); break; } case RayPassType_Compute: { ray_pass_compute_impl_t *compute = (ray_pass_compute_impl_t*)pass; - performCompute(cmdbuf, frame_set_slot, compute, res); + performCompute(cmdbuf, args.frame_set_slot, compute, args.width, args.height); break; } } DEBUG_END(cmdbuf); } -*/ diff --git a/ref_vk/ray_pass.h b/ref_vk/ray_pass.h index 99f1d157..29da3df0 100644 --- a/ref_vk/ray_pass.h +++ b/ref_vk/ray_pass.h @@ -9,6 +9,7 @@ typedef struct { const VkDescriptorSetLayoutBinding *bindings; int bindings_count; + int write_from; VkPushConstantRange push_constants; } ray_pass_layout_t; @@ -58,11 +59,14 @@ struct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create void RayPassDestroy( struct ray_pass_s *pass ); -struct vk_ray_resource_handle_s; +struct vk_resource_s; +typedef struct vk_resource_s *vk_resource_p; + typedef struct ray_pass_perform_args_s { int frame_set_slot; // 0 or 1, until we do num_frame_slots int width, height; - const struct vk_ray_resource_handle_s *resources; + const vk_resource_p *resources; + const int *resources_map; } ray_pass_perform_args_t; void RayPassPerform(struct ray_pass_s *pass, VkCommandBuffer cmdbuf, ray_pass_perform_args_t args ); diff --git a/ref_vk/ray_resources.c b/ref_vk/ray_resources.c index 0bb00853..8308928c 100644 --- a/ref_vk/ray_resources.c +++ b/ref_vk/ray_resources.c @@ -9,6 +9,7 @@ #define MAX_RESOURCES 32 #define MAX_NAME 32 +#if 0 typedef struct vk_named_resource_t { char name[MAX_NAME]; int count; @@ -36,8 +37,13 @@ static int findSlot(const char* name) { return -1; } +#endif qboolean R_VkResourceSetExternal(const char* name, VkDescriptorType type, vk_descriptor_value_t value, int count, ray_resource_desc_t desc, const xvk_image_t *image) { + return false; +} + +#if 0 if (strlen(name) >= MAX_NAME) return false; @@ -112,7 +118,6 @@ void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill) { // Write happened ASSERT(res->write.pipelines != 0); - // No barrier was issued if (!(res->read.pipelines & fill.dest_pipeline)) { res->read.access_mask = VK_ACCESS_SHADER_READ_BIT; @@ -233,3 +238,8 @@ ray_resource_binding_desc_fixme_t RayResouceGetBindingForName_FIXME(const char * return (ray_resource_binding_desc_fixme_t){-1,-1}; } */ +#endif + +void R_VkResourceWriteToDescriptorValue(VkCommandBuffer cmdbuf, vk_resource_write_descriptor_args_t args) { + ASSERT(!"Not implemented"); +} diff --git a/ref_vk/ray_resources.h b/ref_vk/ray_resources.h index 35cd5bd4..25a50512 100644 --- a/ref_vk/ray_resources.h +++ b/ref_vk/ray_resources.h @@ -63,21 +63,22 @@ typedef struct { VkPipelineStageFlagBits pipelines; } ray_resource_state_t; -typedef struct { +typedef struct vk_resource_s { VkDescriptorType type; ray_resource_state_t write, read; union { vk_descriptor_value_t value; const xvk_image_t *image; }; -} ray_resource_t; +} vk_resource_t; + +typedef struct vk_resource_s *vk_resource_p; typedef struct { - const int *indices; - int count; - VkPipelineStageFlagBits dest_pipeline; + VkPipelineStageFlagBits pipeline; + vk_resource_p resource; + vk_descriptor_value_t* value; + qboolean write; +} vk_resource_write_descriptor_args_t; - vk_descriptor_value_t *out_values; -} ray_resources_fill_t; - -void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill); +void R_VkResourceWriteToDescriptorValue(VkCommandBuffer cmdbuf, vk_resource_write_descriptor_args_t args); diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index bfc3967d..a7852c1a 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -473,7 +473,6 @@ class Binding: resource_name = removeprefix(node.name, 'out_') self.__resource_index = resources.getIndex(resource_name, node) - #print(f" {self.name}: ds={self.descriptor_set}, b={self.index}, type={self.type}") assert(self.descriptor_set >= 0) assert(self.descriptor_set < 255) @@ -481,6 +480,31 @@ class Binding: assert(self.index >= 0) assert(self.index < 255) + # For sorting READ-ONLY first, and WRITE later + def __lt__(self, right): + if self.write < right.write: + return True + elif self.write > right.write: + return False + + if self.descriptor_set < right.descriptor_set: + return True + elif self.descriptor_set > right.descriptor_set: + return False + + if self.index < right.index: + return True + elif self.index > right.index: + return False + + return self.__resource_index < right.__resource_index + + def __str__(self): + return f"Binding(resource_index={self.__resource_index}, ds={self.descriptor_set}, b={self.index}, write={self.write}, stages={self.stages})" + + def __repr__(self): + return self.__str__() + def serialize(self, out): header = (Binding.WRITE_BIT if self.write else 0) | (self.descriptor_set << 8) | self.index out.writeU32(header) @@ -600,13 +624,15 @@ class Pipeline: return bindings def serialize(self, out): - bindings = self.__mergeBindings() + bindings = sorted(self.__mergeBindings().values()) + #print(self.name) - #for binding in bindings.values(): - #print(f" {binding.name}: ds={binding.descriptor_set}, b={binding.index}, type={binding.type}, stages={binding.stages:#x}") + #for binding in bindings: + #print(f" ds={binding.descriptor_set}, b={binding.index}, stages={binding.stages:#x}, write={binding.write}") + out.writeU32(self.type) out.writeString(self.name) - out.writeArray(bindings.values()) + out.writeArray(bindings) class PipelineRayTracing(Pipeline): __hit2stage = { diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 2adffd6d..e3237edc 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -28,6 +28,7 @@ typedef struct load_context_t { typedef struct vk_meatpipe_pass_s { ray_pass_p pass; + int write_from; int resource_count; int *resource_map; } vk_meatpipe_pass_t; @@ -164,24 +165,26 @@ finalize: } #define MAX_BINDINGS 32 -static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindings, int** mapping) { - const int count = READ_U32_RETURN(false, "Coulnd't read bindings count"); +static qboolean readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindings, vk_meatpipe_pass_t* pass ) { + pass->resource_map = NULL; + const int count = READ_U32("Coulnd't read bindings count"); if (count > MAX_BINDINGS) { gEngine.Con_Printf(S_ERROR "Too many binding (%d), max: %d\n", count, MAX_BINDINGS); - goto fail; + goto finalize; } - *mapping = Mem_Malloc(vk_core.pool, sizeof(int) * count); + pass->resource_map = Mem_Malloc(vk_core.pool, sizeof(int) * count); + int write_from = -1; for (int i = 0; i < count; ++i) { - const uint32_t header = READ_U32_RETURN(false, "Couldn't read header for binding %d", i); - const uint32_t res_index = READ_U32_RETURN(false, "Couldn't read res index for binding %d", i); - const uint32_t stages = READ_U32_RETURN(false, "Couldn't read stages for binding %d", i); + const uint32_t header = READ_U32("Couldn't read header for binding %d", i); + const uint32_t res_index = READ_U32("Couldn't read res index for binding %d", i); + const uint32_t stages = READ_U32("Couldn't read stages for binding %d", i); if (res_index >= ctx->meatpipe.resources_count) { gEngine.Con_Printf(S_ERROR "Resource %d is out of bound %d for binding %d", res_index, ctx->meatpipe.resources_count, i); - goto fail; + goto finalize; } vk_meatpipe_resource_t *res = ctx->meatpipe.resources + res_index; @@ -191,6 +194,15 @@ static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindi const uint32_t descriptor_set = (header >> 8) & 0xffu; const uint32_t binding = header & 0xffu; + if (write && write_from < 0) + write_from = i; + + if (!write && write_from >= 0) { + gEngine.Con_Printf(S_ERROR "Unsorted non-write binding found at %d(%s), writable started at %d\n", + i, res->name, write_from); + goto finalize; + } + const char *name = res->name; bindings[i] = (VkDescriptorSetLayoutBinding){ @@ -201,21 +213,25 @@ static int readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindi .pImmutableSamplers = NULL, }; - (*mapping)[i] = res_index; + pass->resource_map[i] = res_index; if (write) res->flags |= MEATPIPE_RES_WRITE | MEATPIPE_RES_CREATE; // TODO distinguish between write and create - gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x res=%d type=%d\n", - i, name, descriptor_set, binding, stages, res_index, res->descriptor_type); + gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x res=%d type=%d write=%d\n", + i, name, descriptor_set, binding, stages, res_index, res->descriptor_type, write); } - return count; + pass->write_from = write_from; + pass->resource_count = count; + return true; -fail: - Mem_Free(*mapping); - *mapping = NULL; - return 0; +finalize: + if (pass->resource_map) + Mem_Free(pass->resource_map); + + pass->resource_map = NULL; + return false; } static qboolean readAndCreatePass(load_context_t *ctx, int i) { @@ -235,12 +251,14 @@ static qboolean readAndCreatePass(load_context_t *ctx, int i) { gEngine.Con_Reportf("%d: loading pipeline %s\n", i, name); - pass->resource_count = layout.bindings_count = readBindings(ctx, bindings, &pass->resource_map); - if (!layout.bindings_count) { + if (!readBindings(ctx, bindings, pass)) { gEngine.Con_Printf(S_ERROR "Couldn't read bindings for pipeline %s\n", name); return false; } + layout.bindings_count = pass->resource_count; + layout.write_from = pass->write_from; + #define PIPELINE_COMPUTE 1 #define PIPELINE_RAYTRACING 2 @@ -384,24 +402,28 @@ finalize: } void R_VkMeatpipeDestroy(vk_meatpipe_t *mp) { - /* FIXME for (int i = 0; i < mp->passes_count; ++i) { - if (!mp->passes[i]) - break; - - RayPassDestroy(mp->passes[i]); + vk_meatpipe_pass_t *pass = mp->passes + i; + RayPassDestroy(pass->pass); + Mem_Free(pass->resource_map); } - mp->passes_count = 0; - */ + Mem_Free(mp->passes); + Mem_Free(mp->resources); + Mem_Free(mp); } void R_VkMeatpipePerform(vk_meatpipe_t *mp, VkCommandBuffer cmdbuf, vk_meatpipe_perfrom_args_t args) { - // FIXME -/* -void R_VkMeatpipePerform(vk_meatpipe_t *mp, VkCommandBuffer cmdbuf, int frame_set_slot, const struct vk_ray_resources_s *res) { for (int i = 0; i < mp->passes_count; ++i) { - RayPassPerform(cmdbuf, frame_set_slot, mp->passes[i], res); + const vk_meatpipe_pass_t *pass = mp->passes + i; + RayPassPerform(pass->pass, cmdbuf, + (ray_pass_perform_args_t){ + .frame_set_slot = args.frame_set_slot, + .width = args.width, + .height = args.height, + .resources = args.resources, + .resources_map = pass->resource_map, + } + ); } - */ } diff --git a/ref_vk/vk_meatpipe.h b/ref_vk/vk_meatpipe.h index 6473df9c..37c8c87f 100644 --- a/ref_vk/vk_meatpipe.h +++ b/ref_vk/vk_meatpipe.h @@ -18,7 +18,6 @@ typedef struct { }; } vk_meatpipe_resource_t; - struct vk_meatpipe_pass_s; typedef struct { int passes_count; @@ -31,11 +30,12 @@ typedef struct { vk_meatpipe_t* R_VkMeatpipeCreateFromFile(const char *filename); void R_VkMeatpipeDestroy(vk_meatpipe_t *mp); -struct vk_ray_resource_handle_s; +struct vk_resource_s; +typedef struct vk_resource_s* vk_resource_p; typedef struct vk_meatpipe_perfrom_args_s { int frame_set_slot; // 0 or 1, until we do num_frame_slots int width, height; - const struct vk_ray_resource_handle_s *resources; + const vk_resource_p *resources; } vk_meatpipe_perfrom_args_t; void R_VkMeatpipePerform(vk_meatpipe_t *mp, VkCommandBuffer cmdbuf, vk_meatpipe_perfrom_args_t args); From 1ef9526aff0535ebdcac0c5bcf2602adf7d0dbcf Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Wed, 18 Jan 2023 11:06:35 -0800 Subject: [PATCH 507/548] [wip] begin creating gbuffer resources based on meatpipe description --- ref_vk/ray_pass.c | 1 + ref_vk/ray_resources.c | 66 ------------ ref_vk/ray_resources.h | 62 +---------- ref_vk/shaders/ray_interop.h | 1 + ref_vk/vk_rtx.c | 197 ++++++++++++++++++++--------------- 5 files changed, 117 insertions(+), 210 deletions(-) diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c index 23e1b19c..ae09fbc6 100644 --- a/ref_vk/ray_pass.c +++ b/ref_vk/ray_pass.c @@ -1,4 +1,5 @@ #include "ray_pass.h" +#include "shaders/ray_interop.h" // for SPEC_SBT_RECORD_SIZE_INDEX #include "ray_resources.h" #include "vk_pipeline.h" #include "vk_descriptor.h" diff --git a/ref_vk/ray_resources.c b/ref_vk/ray_resources.c index 8308928c..c0d65a22 100644 --- a/ref_vk/ray_resources.c +++ b/ref_vk/ray_resources.c @@ -6,74 +6,8 @@ #include #define MAX_BARRIERS 16 -#define MAX_RESOURCES 32 -#define MAX_NAME 32 #if 0 -typedef struct vk_named_resource_t { - char name[MAX_NAME]; - int count; - ray_resource_desc_t desc_fixme; -} vk_named_resource_t; - -static struct { - vk_named_resource_t named[MAX_RESOURCES]; - ray_resource_t resources[RayResource__COUNT]; -} g_resources; - -static int findSlot(const char* name) { - // Find the exact match if exists - // There might be gaps, so we need to check everything - for (int i = 0; i < MAX_RESOURCES; ++i) { - if (strcmp(g_resources.named[i].name, name) == 0) - return i; - } - - // Find first free slot - for (int i = 0; i < MAX_RESOURCES; ++i) { - if (!g_resources.named[i].name[0]) - return i; - } - - return -1; -} -#endif - -qboolean R_VkResourceSetExternal(const char* name, VkDescriptorType type, vk_descriptor_value_t value, int count, ray_resource_desc_t desc, const xvk_image_t *image) { - return false; -} - -#if 0 - if (strlen(name) >= MAX_NAME) - return false; - - const int index = findSlot(name); - if (index < 0) - return false; - - vk_named_resource_t *const r = g_resources.named + index; - ray_resource_t *const rr = g_resources.resources + index; - - if (!r->name[0]) { - strncpy(r->name, name, sizeof(r->name)); - rr->type = type; - r->count = count; - r->desc_fixme = desc; - } else { - ASSERT(rr->type == type); - ASSERT(r->count == count); - ASSERT(r->desc_fixme.type == desc.type); - ASSERT(r->desc_fixme.image_format == desc.image_format); - } - - rr->value = value; - rr->image = image; - memset(&rr->read, 0, sizeof(rr->read)); - memset(&rr->write, 0, sizeof(rr->write)); - - return true; -} - void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill) { VkImageMemoryBarrier image_barriers[MAX_BARRIERS]; int image_barriers_count = 0; diff --git a/ref_vk/ray_resources.h b/ref_vk/ray_resources.h index 25a50512..0d4b34a2 100644 --- a/ref_vk/ray_resources.h +++ b/ref_vk/ray_resources.h @@ -1,75 +1,19 @@ #pragma once -#include "vk_const.h" -#include "vk_image.h" +#include "vk_core.h" #include "vk_descriptor.h" -#include "shaders/ray_interop.h" - -//qboolean R_VkResourceInit(void); -//void R_VkResourceShutdown(); - -typedef enum { - ResourceUnknown, - ResourceBuffer, - ResourceImage, -} ray_resource_type_e; - -typedef struct { - ray_resource_type_e type; - int image_format; // if type == ResourceImage -} ray_resource_desc_t; - -qboolean R_VkResourceSetExternal(const char* name, VkDescriptorType type, vk_descriptor_value_t value, int count, ray_resource_desc_t desc, const xvk_image_t *image); - -/* -typedef struct { - int semantic; - int count; -} ray_resource_binding_desc_fixme_t; - -ray_resource_binding_desc_fixme_t RayResouceGetBindingForName_FIXME(const char *name, ray_resource_desc_t desc); -*/ - -#define RAY_SCENE_RESOURCES(X) \ - X(TLAS, tlas) \ - X(Buffer, ubo) \ - X(Buffer, kusochki) \ - X(Buffer, indices) \ - X(Buffer, vertices) \ - X(Buffer, lights) \ - X(Buffer, light_clusters) \ - X(Texture, all_textures) \ - X(Texture, skybox) \ - -enum { -#define X(type, name, ...) RayResource_##name, - RAY_SCENE_RESOURCES(X) - RAY_PRIMARY_OUTPUTS(X) - RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) - RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) - X(-1, denoised) -#undef X - RayResource__COUNT -}; - -typedef struct vk_ray_resources_s { - uint32_t width, height; -} vk_ray_resources_t; - typedef struct { VkAccessFlags access_mask; VkImageLayout image_layout; VkPipelineStageFlagBits pipelines; } ray_resource_state_t; +struct xvk_image_s; typedef struct vk_resource_s { VkDescriptorType type; ray_resource_state_t write, read; - union { - vk_descriptor_value_t value; - const xvk_image_t *image; - }; + vk_descriptor_value_t value; } vk_resource_t; typedef struct vk_resource_s *vk_resource_p; diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index f0cd2622..d07361b3 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -34,6 +34,7 @@ #ifndef GLSL #include "xash3d_types.h" +#include "vk_const.h" #define MAX_EMISSIVE_KUSOCHKI 256 #define uint uint32_t #define vec2 vec2_t diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 4cfc8dec..7b1579bd 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -38,34 +38,7 @@ #define FRAME_HEIGHT 1080 #endif -#define rgba8 VK_FORMAT_R8G8B8A8_UNORM -#define rgba32f VK_FORMAT_R32G32B32A32_SFLOAT -#define rgba16f VK_FORMAT_R16G16B16A16_SFLOAT - -// TODO sync with shaders -// TODO optimal values -#define WG_W 16 -#define WG_H 8 - -typedef struct { - vec3_t pos; - float radius; - vec3_t color; - float padding_; -} vk_light_t; - -typedef struct PushConstants vk_rtx_push_constants_t; - -typedef struct { - xvk_image_t denoised; - -#define X(index, name, ...) xvk_image_t name; -RAY_PRIMARY_OUTPUTS(X) -RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) -RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) -#undef X - -} xvk_ray_frame_images_t; +#define MAX_RESOURCES 32 static struct { // Holds UniformBuffer data @@ -74,13 +47,43 @@ static struct { // TODO with proper intra-cmdbuf sync we don't really need 2x images unsigned frame_number; - xvk_ray_frame_images_t frames[MAX_FRAMES_IN_FLIGHT]; vk_meatpipe_t *mainpipe; + vk_resource_p *mainpipe_resources; + + struct { + char name[64]; + vk_resource_t resource; + xvk_image_t image; + } res[MAX_RESOURCES]; qboolean reload_pipeline; qboolean reload_lighting; } g_rtx = {0}; +static int findResource(const char *name) { + // Find the exact match if exists + // There might be gaps, so we need to check everything + for (int i = 0; i < MAX_RESOURCES; ++i) { + if (strcmp(g_rtx.res[i].name, name) == 0) + return i; + } + + return -1; +} + +static int getResourceSlotForName(const char *name) { + const int index = findResource(name); + if (index >= 0) + return index; + + // Find first free slot + for (int i = 0; i < MAX_RESOURCES; ++i) { + if (!g_rtx.res[i].name[0]) + return i; + } + + return -1; +} void VK_RayNewMap( void ) { RT_VkAccelNewMap(); @@ -127,17 +130,12 @@ static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int fr typedef struct { const vk_ray_frame_render_args_t* render_args; int frame_index; - const xvk_ray_frame_images_t *current_frame; float fov_angle_y; const vk_lights_bindings_t *light_bindings; } perform_tracing_args_t; static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* args) { - const vk_ray_resources_t res = { - .width = FRAME_WIDTH, - .height = FRAME_HEIGHT, - }; - + #if 0 R_VkResourceSetExternal("tlas", VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, (vk_descriptor_value_t){ .accel = (VkWriteDescriptorSetAccelerationStructureKHR) { @@ -171,6 +169,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* RES_SET_BUFFER(light_clusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, args->light_bindings->buffer, args->light_bindings->grid.offset, args->light_bindings->grid.size); #undef RES_SET_SBUFFER_FULL #undef RES_SET_BUFFER + #endif // Upload kusochki updates { @@ -232,7 +231,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* const r_vkimage_blit_args blit_args = { .in_stage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, .src = { - .image = args->current_frame->denoised.image, + // FIXME .image = args->current_frame->denoised.image, .width = FRAME_WIDTH, .height = FRAME_HEIGHT, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, @@ -252,10 +251,76 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* DEBUG_END(cmdbuf); } +static void reloadMainpipe() { + vk_meatpipe_t *const newpipe = R_VkMeatpipeCreateFromFile("rt.meat"); + if (!newpipe) + return; + + const size_t newpipe_resources_size = sizeof(vk_resource_p) * newpipe->resources_count; + vk_resource_p *newpipe_resources = Mem_Calloc(vk_core.pool, newpipe_resources_size); + + for (int i = 0; i < newpipe->resources_count; ++i) { + const vk_meatpipe_resource_t *mr = newpipe->resources + i; + gEngine.Con_Reportf("res %d/%d: %s descriptor=%u count=%d flags=[%c%c] image_format=%u\n", + i, newpipe->resources_count, mr->name, mr->descriptor_type, mr->count, + (mr->flags & MEATPIPE_RES_WRITE) ? 'W' : ' ', + (mr->flags & MEATPIPE_RES_CREATE) ? 'C' : ' ', + mr->image_format); + + const qboolean create = !!(mr->flags & MEATPIPE_RES_CREATE); + + const int index = create ? getResourceSlotForName(mr->name) : findResource(mr->name); + if (index < 0) { + gEngine.Con_Printf(S_ERROR "Couldn't find resource/slot for %s\n", mr->name); + goto fail; + } + + // TODO create if creatable + if (create && g_rtx.res[index].image.image == VK_NULL_HANDLE) { + Q_strncpy(g_rtx.res[index].name, mr->name, sizeof(g_rtx.res[index].name)); + const xvk_image_create_t create = { + .debug_name = mr->name, + .width = FRAME_WIDTH, + .height = FRAME_HEIGHT, + .mips = 1, + .layers = 1, + .format = mr->image_format, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_STORAGE_BIT, + .has_alpha = true, + .is_cubemap = false, + }; + g_rtx.res[index].image = XVK_ImageCreate(&create); + } + + g_rtx.mainpipe_resources[i] = &g_rtx.res[index].resource; + } + + if (g_rtx.mainpipe) { + R_VkMeatpipeDestroy(g_rtx.mainpipe); + + // FIXME also destroy all extra created resources/images + + Mem_Free(g_rtx.mainpipe_resources); + } + + g_rtx.mainpipe = newpipe; + +fail: + if (newpipe_resources) { + for (int i = 0; i < newpipe->resources_count; ++i) { + // FIXME on error we'll leak images + } + Mem_Free(newpipe_resources); + } + + R_VkMeatpipeDestroy(newpipe); +} + void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) { const VkCommandBuffer cmdbuf = args->cmdbuf; - const xvk_ray_frame_images_t* current_frame = g_rtx.frames + (g_rtx.frame_number % 2); + // const xvk_ray_frame_images_t* current_frame = g_rtx.frames + (g_rtx.frame_number % 2); ASSERT(vk_core.rtx); // ubo should contain two matrices @@ -273,11 +338,7 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) gEngine.Con_Printf(S_WARN "Reloading RTX shaders/pipelines\n"); XVK_CHECK(vkDeviceWaitIdle(vk_core.device)); - vk_meatpipe_t *const newpipe = R_VkMeatpipeCreateFromFile("rt.meat"); - if (newpipe) { - R_VkMeatpipeDestroy(g_rtx.mainpipe); - g_rtx.mainpipe = newpipe; - } + reloadMainpipe(); g_rtx.reload_pipeline = false; } @@ -286,7 +347,7 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) const r_vkimage_blit_args blit_args = { .in_stage = VK_PIPELINE_STAGE_TRANSFER_BIT, .src = { - .image = current_frame->denoised.image, + // FIXME .image = current_frame->denoised.image, .width = FRAME_WIDTH, .height = FRAME_HEIGHT, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, @@ -301,13 +362,12 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) }, }; - R_VkImageClear( cmdbuf, current_frame->denoised.image ); + // FIMXE R_VkImageClear( cmdbuf, current_frame->denoised.image ); R_VkImageBlit( cmdbuf, &blit_args ); } else { const perform_tracing_args_t trace_args = { .render_args = args, .frame_index = (g_rtx.frame_number % 2), - .current_frame = current_frame, .fov_angle_y = args->fov_angle_y, .light_bindings = &light_bindings, }; @@ -335,8 +395,9 @@ qboolean VK_RayInit( void ) if (!RT_VkAccelInit()) return false; - g_rtx.mainpipe = R_VkMeatpipeCreateFromFile("rt.meat"); - ASSERT(g_rtx.mainpipe); + reloadMainpipe(); + if (!g_rtx.mainpipe) + return false; g_rtx.uniform_unit_size = ALIGN_UP(sizeof(struct UniformBuffer), vk_core.physical_device.properties.limits.minUniformBufferOffsetAlignment); @@ -355,36 +416,9 @@ qboolean VK_RayInit( void ) } RT_RayModel_Clear(); - for (int i = 0; i < ARRAYSIZE(g_rtx.frames); ++i) { #define CREATE_GBUFFER_IMAGE(name, format_, add_usage_bits) \ - do { \ - char debug_name[64]; \ - const xvk_image_create_t create = { \ - .debug_name = debug_name, \ - .width = FRAME_WIDTH, \ - .height = FRAME_HEIGHT, \ - .mips = 1, \ - .layers = 1, \ - .format = format_, \ - .tiling = VK_IMAGE_TILING_OPTIMAL, \ - .usage = VK_IMAGE_USAGE_STORAGE_BIT | add_usage_bits, \ - .has_alpha = true, \ - .is_cubemap = false, \ - }; \ - Q_snprintf(debug_name, sizeof(debug_name), "rtx frames[%d] " # name, i); \ - g_rtx.frames[i].name = XVK_ImageCreate(&create); \ - } while(0) - CREATE_GBUFFER_IMAGE(denoised, VK_FORMAT_R16G16B16A16_SFLOAT, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT); -#define X(index, name, format) CREATE_GBUFFER_IMAGE(name, format, 0); -// TODO better format for normals VK_FORMAT_R16G16B16A16_SNORM -// TODO make sure this format and usage is suppported - RAY_PRIMARY_OUTPUTS(X) - RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) - RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) -#undef X - /* R_VkResourceSetExternal("all_textures", VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, (vk_descriptor_value_t){ @@ -412,14 +446,9 @@ qboolean VK_RayInit( void ) RAY_LIGHT_DIRECT_POLY_OUTPUTS(RES_SET_IMAGE); RAY_LIGHT_DIRECT_POINT_OUTPUTS(RES_SET_IMAGE); RES_SET_IMAGE(-1, denoised, rgba16f); - */ - #undef RES_SET_IMAGE -#undef rgba8 -#undef rgba32f -#undef rgba16f + */ #undef CREATE_GBUFFER_IMAGE - } gEngine.Cmd_AddCommand("vk_rtx_reload", reloadPipeline, "Reload RTX shader"); gEngine.Cmd_AddCommand("vk_rtx_reload_rad", reloadLighting, "Reload RAD files for static lights"); @@ -433,14 +462,12 @@ void VK_RayShutdown( void ) { R_VkMeatpipeDestroy(g_rtx.mainpipe); + // FIXME destroy mainpipe resources + /* for (int i = 0; i < ARRAYSIZE(g_rtx.frames); ++i) { XVK_ImageDestroy(&g_rtx.frames[i].denoised); -#define X(index, name, ...) XVK_ImageDestroy(&g_rtx.frames[i].name); - RAY_PRIMARY_OUTPUTS(X) - RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) - RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) -#undef X } + */ VK_BufferDestroy(&g_ray_model_state.kusochki_buffer); VK_BufferDestroy(&g_rtx.uniform_buffer); From 59b3bbec080c3634bd0ecea249e28fd51111a960 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 19 Jan 2023 22:51:32 -0800 Subject: [PATCH 508/548] fix linux build --- ref_vk/vk_meatpipe.c | 2 +- ref_vk/vk_rtx.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index e3237edc..7cd99b0e 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -167,6 +167,7 @@ finalize: #define MAX_BINDINGS 32 static qboolean readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindings, vk_meatpipe_pass_t* pass ) { pass->resource_map = NULL; + int write_from = -1; const int count = READ_U32("Coulnd't read bindings count"); if (count > MAX_BINDINGS) { @@ -176,7 +177,6 @@ static qboolean readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding * pass->resource_map = Mem_Malloc(vk_core.pool, sizeof(int) * count); - int write_from = -1; for (int i = 0; i < count; ++i) { const uint32_t header = READ_U32("Couldn't read header for binding %d", i); const uint32_t res_index = READ_U32("Couldn't read res index for binding %d", i); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 7b1579bd..a3c3314c 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -251,7 +251,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* DEBUG_END(cmdbuf); } -static void reloadMainpipe() { +static void reloadMainpipe(void) { vk_meatpipe_t *const newpipe = R_VkMeatpipeCreateFromFile("rt.meat"); if (!newpipe) return; From 3e194c7c6d157d96a56a40d4580fac0903e98ba2 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 21 Jan 2023 11:35:00 -0800 Subject: [PATCH 509/548] [wip] preregister all resources and get meatpipe to compile still doesn't draw anything yet. needs writing descriptor values and barriers --- ref_vk/vk_meatpipe.c | 2 + ref_vk/vk_rtx.c | 168 ++++++++++++++++++++++++++----------------- 2 files changed, 103 insertions(+), 67 deletions(-) diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 7cd99b0e..0c5d4bd6 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -288,6 +288,8 @@ static qboolean readResources(load_context_t *ctx) { for (int i = 0; i < ctx->meatpipe.resources_count; ++i) { vk_meatpipe_resource_t *res = ctx->meatpipe.resources + i; + *res = (vk_meatpipe_resource_t){0}; + READ_STR(res->name, "Couldn't read resource %d name", i); res->descriptor_type = READ_U32("Couldn't read resource %d:%s type", i, res->name); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index a3c3314c..0ed685ff 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -38,8 +38,34 @@ #define FRAME_HEIGHT 1080 #endif + // TODO each of these should be registered by the provider of the resource: +#define EXTERNAL_RESOUCES(X) \ + X(TLAS, tlas) \ + X(Buffer, ubo) \ + X(Buffer, kusochki) \ + X(Buffer, indices) \ + X(Buffer, vertices) \ + X(Buffer, lights) \ + X(Buffer, light_clusters) \ + X(Texture, textures) \ + X(Texture, skybox) + +enum { +#define RES_ENUM(type, name) ExternalResource_##name, + EXTERNAL_RESOUCES(RES_ENUM) +#undef RES_ENUM + ExternalResource_COUNT, +}; + #define MAX_RESOURCES 32 +typedef struct { + char name[64]; + vk_resource_t resource; + xvk_image_t image; + // TODO int refcount +} rt_resource_t; + static struct { // Holds UniformBuffer data vk_buffer_t uniform_buffer; @@ -49,12 +75,9 @@ static struct { unsigned frame_number; vk_meatpipe_t *mainpipe; vk_resource_p *mainpipe_resources; + rt_resource_t *mainpipe_out; - struct { - char name[64]; - vk_resource_t resource; - xvk_image_t image; - } res[MAX_RESOURCES]; + rt_resource_t res[MAX_RESOURCES]; qboolean reload_pipeline; qboolean reload_lighting; @@ -77,7 +100,7 @@ static int getResourceSlotForName(const char *name) { return index; // Find first free slot - for (int i = 0; i < MAX_RESOURCES; ++i) { + for (int i = ExternalResource_COUNT; i < MAX_RESOURCES; ++i) { if (!g_rtx.res[i].name[0]) return i; } @@ -88,10 +111,20 @@ static int getResourceSlotForName(const char *name) { void VK_RayNewMap( void ) { RT_VkAccelNewMap(); RT_RayModel_Clear(); + + g_rtx.res[ExternalResource_skybox].resource = (vk_resource_t){ + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .value = (vk_descriptor_value_t){ + .image = { + .sampler = vk_core.default_sampler, + .imageView = tglob.skybox_cube.vk.image.view ? tglob.skybox_cube.vk.image.view : tglob.cubemap_placeholder.vk.image.view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }, + }, + }; } -void VK_RayFrameBegin( void ) -{ +void VK_RayFrameBegin( void ) { ASSERT(vk_core.rtx); RT_VkAccelFrameBegin(); @@ -135,41 +168,48 @@ typedef struct { } perform_tracing_args_t; static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* args) { - #if 0 - R_VkResourceSetExternal("tlas", VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, - (vk_descriptor_value_t){ - .accel = (VkWriteDescriptorSetAccelerationStructureKHR) { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, - .accelerationStructureCount = 1, - .pAccelerationStructures = &g_accel.tlas, - .pNext = NULL, - }, - }, 1, (ray_resource_desc_t){ResourceUnknown, 0}, NULL - ); + // TODO move this to "TLAS producer" + g_rtx.res[ExternalResource_tlas].resource = (vk_resource_t){ + .type = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, + .value = (vk_descriptor_value_t){ + .accel = (VkWriteDescriptorSetAccelerationStructureKHR) { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, + .accelerationStructureCount = 1, + .pAccelerationStructures = &g_accel.tlas, + .pNext = NULL, + }, + }, + }; #define RES_SET_BUFFER(name, type_, source_, offset_, size_) \ - R_VkResourceSetExternal(#name, type_, (vk_descriptor_value_t){ \ - .buffer = (VkDescriptorBufferInfo) { \ - .buffer = (source_), \ - .offset = (offset_), \ - .range = (size_), \ + g_rtx.res[ExternalResource_##name].resource = (vk_resource_t){ \ + .type = type_, \ + .value = (vk_descriptor_value_t) { \ + .buffer = (VkDescriptorBufferInfo) { \ + .buffer = (source_), \ + .offset = (offset_), \ + .range = (size_), \ + } \ } \ - }, 1, (ray_resource_desc_t){ResourceBuffer, 0}, NULL) + } RES_SET_BUFFER(ubo, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, g_rtx.uniform_buffer.buffer, args->frame_index * g_rtx.uniform_unit_size, sizeof(struct UniformBuffer)); #define RES_SET_SBUFFER_FULL(name, source_) \ RES_SET_BUFFER(name, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, source_.buffer, 0, source_.size) + // TODO move this to ray model producer RES_SET_SBUFFER_FULL(kusochki, g_ray_model_state.kusochki_buffer); + + // TODO move these to vk_geometry RES_SET_SBUFFER_FULL(indices, args->render_args->geometry_data); RES_SET_SBUFFER_FULL(vertices, args->render_args->geometry_data); + // TODO move this to lights RES_SET_BUFFER(lights, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, args->light_bindings->buffer, args->light_bindings->metadata.offset, args->light_bindings->metadata.size); RES_SET_BUFFER(light_clusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, args->light_bindings->buffer, args->light_bindings->grid.offset, args->light_bindings->grid.size); #undef RES_SET_SBUFFER_FULL #undef RES_SET_BUFFER - #endif // Upload kusochki updates { @@ -231,7 +271,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* const r_vkimage_blit_args blit_args = { .in_stage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, .src = { - // FIXME .image = args->current_frame->denoised.image, + .image = g_rtx.mainpipe_out->image.image, .width = FRAME_WIDTH, .height = FRAME_HEIGHT, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, @@ -258,6 +298,7 @@ static void reloadMainpipe(void) { const size_t newpipe_resources_size = sizeof(vk_resource_p) * newpipe->resources_count; vk_resource_p *newpipe_resources = Mem_Calloc(vk_core.pool, newpipe_resources_size); + rt_resource_t *newpipe_out = NULL; for (int i = 0; i < newpipe->resources_count; ++i) { const vk_meatpipe_resource_t *mr = newpipe->resources + i; @@ -269,15 +310,19 @@ static void reloadMainpipe(void) { const qboolean create = !!(mr->flags & MEATPIPE_RES_CREATE); + // FIXME this should be specified as a flag, from rt.json + const qboolean output = Q_strcmp("dest", mr->name) == 0; + const int index = create ? getResourceSlotForName(mr->name) : findResource(mr->name); if (index < 0) { gEngine.Con_Printf(S_ERROR "Couldn't find resource/slot for %s\n", mr->name); goto fail; } - // TODO create if creatable + if (output) + newpipe_out = g_rtx.res + index; + if (create && g_rtx.res[index].image.image == VK_NULL_HANDLE) { - Q_strncpy(g_rtx.res[index].name, mr->name, sizeof(g_rtx.res[index].name)); const xvk_image_create_t create = { .debug_name = mr->name, .width = FRAME_WIDTH, @@ -286,25 +331,34 @@ static void reloadMainpipe(void) { .layers = 1, .format = mr->image_format, .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = VK_IMAGE_USAGE_STORAGE_BIT, + .usage = VK_IMAGE_USAGE_STORAGE_BIT | (output ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT : 0), .has_alpha = true, .is_cubemap = false, }; g_rtx.res[index].image = XVK_ImageCreate(&create); + Q_strncpy(g_rtx.res[index].name, mr->name, sizeof(g_rtx.res[index].name)); } - g_rtx.mainpipe_resources[i] = &g_rtx.res[index].resource; + newpipe_resources[i] = &g_rtx.res[index].resource; + } + + if (!newpipe_out) { + gEngine.Con_Printf(S_ERROR "New rt.json doesn't define an 'dest' output texture\n"); + goto fail; } if (g_rtx.mainpipe) { R_VkMeatpipeDestroy(g_rtx.mainpipe); // FIXME also destroy all extra created resources/images + // use refcounts or something Mem_Free(g_rtx.mainpipe_resources); } g_rtx.mainpipe = newpipe; + g_rtx.mainpipe_resources = newpipe_resources; + g_rtx.mainpipe_out = newpipe_out; fail: if (newpipe_resources) { @@ -343,11 +397,13 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) g_rtx.reload_pipeline = false; } + ASSERT(g_rtx.mainpipe_out); + if (g_ray_model_state.frame.num_models == 0) { const r_vkimage_blit_args blit_args = { .in_stage = VK_PIPELINE_STAGE_TRANSFER_BIT, .src = { - // FIXME .image = current_frame->denoised.image, + .image = g_rtx.mainpipe_out->image.image, .width = FRAME_WIDTH, .height = FRAME_HEIGHT, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, @@ -362,7 +418,7 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) }, }; - // FIMXE R_VkImageClear( cmdbuf, current_frame->denoised.image ); + R_VkImageClear( cmdbuf, g_rtx.mainpipe_out->image.image ); R_VkImageBlit( cmdbuf, &blit_args ); } else { const perform_tracing_args_t trace_args = { @@ -395,6 +451,18 @@ qboolean VK_RayInit( void ) if (!RT_VkAccelInit()) return false; +#define REGISTER_EXTERNAL(type, name_) \ + Q_strncpy(g_rtx.res[ExternalResource_##name_].name, #name_, sizeof(g_rtx.res[0].name)); + EXTERNAL_RESOUCES(REGISTER_EXTERNAL) +#undef REGISTER_EXTERNAL + + g_rtx.res[ExternalResource_textures].resource = (vk_resource_t){ + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .value = (vk_descriptor_value_t){ + .image_array = tglob.dii_all_textures, + } + }; + reloadMainpipe(); if (!g_rtx.mainpipe) return false; @@ -416,40 +484,6 @@ qboolean VK_RayInit( void ) } RT_RayModel_Clear(); -#define CREATE_GBUFFER_IMAGE(name, format_, add_usage_bits) \ - CREATE_GBUFFER_IMAGE(denoised, VK_FORMAT_R16G16B16A16_SFLOAT, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT); - - /* - R_VkResourceSetExternal("all_textures", VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - (vk_descriptor_value_t){ - .image_array = tglob.dii_all_textures, - }, MAX_TEXTURES, (ray_resource_desc_t){ResourceImage, VK_FORMAT_UNDEFINED}, NULL - ); - - R_VkResourceSetExternal("skybox", VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - (vk_descriptor_value_t){ - .image = { - .sampler = vk_core.default_sampler, - .imageView = tglob.skybox_cube.vk.image.view ? tglob.skybox_cube.vk.image.view : tglob.cubemap_placeholder.vk.image.view, - .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - }, - }, 1, (ray_resource_desc_t){ResourceImage, VK_FORMAT_UNDEFINED}, NULL - ); - -#define RES_SET_IMAGE(index, name, format) \ - R_VkResourceSetExternal(#name, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, \ - (vk_descriptor_value_t){0}, \ - 1, (ray_resource_desc_t){ResourceImage, format}, \ - &args->current_frame->name); - - RAY_PRIMARY_OUTPUTS(RES_SET_IMAGE); - RAY_LIGHT_DIRECT_POLY_OUTPUTS(RES_SET_IMAGE); - RAY_LIGHT_DIRECT_POINT_OUTPUTS(RES_SET_IMAGE); - RES_SET_IMAGE(-1, denoised, rgba16f); -#undef RES_SET_IMAGE - */ -#undef CREATE_GBUFFER_IMAGE - gEngine.Cmd_AddCommand("vk_rtx_reload", reloadPipeline, "Reload RTX shader"); gEngine.Cmd_AddCommand("vk_rtx_reload_rad", reloadLighting, "Reload RAD files for static lights"); gEngine.Cmd_AddCommand("vk_rtx_freeze", freezeModels, "Freeze models, do not update/add/delete models from to-draw list"); From 3b77a8474607c26c68dce21aabad58fe95a51cd3 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 21 Jan 2023 13:03:57 -0800 Subject: [PATCH 510/548] rt: make it paint things again missing: - proper resource destruction at exit and at meatpipe recreation time --- ref_vk/ray_pass.c | 23 ++++----- ref_vk/ray_resources.c | 115 +++++++---------------------------------- ref_vk/ray_resources.h | 14 +++-- ref_vk/vk_descriptor.h | 2 + ref_vk/vk_image.h | 2 +- ref_vk/vk_rtx.c | 46 ++++++++++++++--- 6 files changed, 80 insertions(+), 122 deletions(-) diff --git a/ref_vk/ray_pass.c b/ref_vk/ray_pass.c index ae09fbc6..10c51b4f 100644 --- a/ref_vk/ray_pass.c +++ b/ref_vk/ray_pass.c @@ -251,19 +251,16 @@ static void performCompute( VkCommandBuffer cmdbuf, int set_slot, const ray_pass } void RayPassPerform(struct ray_pass_s *pass, VkCommandBuffer cmdbuf, ray_pass_perform_args_t args ) { - for (int i = 0; i < pass->desc.riptors.num_bindings; ++i) { - vk_resource_p resource = args.resources[args.resources_map ? args.resources_map[i] : i]; - vk_descriptor_value_t *value = pass->desc.riptors.values + i; - - R_VkResourceWriteToDescriptorValue(cmdbuf, - (vk_resource_write_descriptor_args_t){ - .pipeline = pass->pipeline_type, - .resource = resource, - .value = value, - .write = i >= pass->desc.write_from, - } - ); - } + R_VkResourcesPrepareDescriptorsValues(cmdbuf, + (vk_resources_write_descriptors_args_t){ + .pipeline = pass->pipeline_type, + .resources = args.resources, + .resources_map = args.resources_map, + .values = pass->desc.riptors.values, + .count = pass->desc.riptors.num_bindings, + .write_begin = pass->desc.write_from, + } + ); VK_DescriptorsWrite(&pass->desc.riptors, args.frame_set_slot); diff --git a/ref_vk/ray_resources.c b/ref_vk/ray_resources.c index c0d65a22..4bd15b70 100644 --- a/ref_vk/ray_resources.c +++ b/ref_vk/ray_resources.c @@ -1,5 +1,6 @@ #include "ray_resources.h" #include "vk_core.h" +#include "vk_image.h" #include "shaders/ray_interop.h" // FIXME temp for type validation @@ -7,19 +8,19 @@ #define MAX_BARRIERS 16 -#if 0 -void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill) { +void R_VkResourcesPrepareDescriptorsValues(VkCommandBuffer cmdbuf, vk_resources_write_descriptors_args_t args) { VkImageMemoryBarrier image_barriers[MAX_BARRIERS]; int image_barriers_count = 0; VkPipelineStageFlags src_stage_mask = VK_PIPELINE_STAGE_NONE_KHR; - for (int i = 0; i < fill.count; ++i) { - const qboolean write = fill.indices[i] < 0; - const int index = abs(fill.indices[i]) - 1; - ray_resource_t *const res = g_resources.resources + index; + for (int i = 0; i < args.count; ++i) { + const int index = args.resources_map ? args.resources_map[i] : i; + vk_resource_t* const res = args.resources[index]; - ASSERT(index >= 0); - ASSERT(index < RayResource__COUNT); + const vk_descriptor_value_t *const src_value = &res->value; + vk_descriptor_value_t *const dst_value = args.values + i; + + const qboolean write = i >= args.write_begin; if (res->type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) { if (write) { @@ -29,12 +30,12 @@ void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill) { res->write = (ray_resource_state_t) { .access_mask = VK_ACCESS_SHADER_WRITE_BIT, .image_layout = VK_IMAGE_LAYOUT_GENERAL, - .pipelines = fill.dest_pipeline, + .pipelines = args.pipeline, }; image_barriers[image_barriers_count++] = (VkImageMemoryBarrier) { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .image = res->image->image, + .image = src_value->image_object->image, .srcAccessMask = 0, .dstAccessMask = res->write.access_mask, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, @@ -53,16 +54,16 @@ void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill) { ASSERT(res->write.pipelines != 0); // No barrier was issued - if (!(res->read.pipelines & fill.dest_pipeline)) { + if (!(res->read.pipelines & args.pipeline)) { res->read.access_mask = VK_ACCESS_SHADER_READ_BIT; - res->read.pipelines |= fill.dest_pipeline; + res->read.pipelines |= args.pipeline; res->read.image_layout = VK_IMAGE_LAYOUT_GENERAL; src_stage_mask |= res->write.pipelines; image_barriers[image_barriers_count++] = (VkImageMemoryBarrier) { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .image = res->image->image, + .image = src_value->image_object->image, .srcAccessMask = res->write.access_mask, .dstAccessMask = res->read.access_mask, .oldLayout = res->write.image_layout, @@ -77,13 +78,14 @@ void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill) { }; } } - fill.out_values[i].image = (VkDescriptorImageInfo) { + + dst_value->image = (VkDescriptorImageInfo) { .imageLayout = write ? res->write.image_layout : res->read.image_layout, - .imageView = res->image->view, + .imageView = src_value->image_object->view, .sampler = VK_NULL_HANDLE, }; } else { - fill.out_values[i] = res->value; + *dst_value = *src_value; } } @@ -93,87 +95,8 @@ void RayResourcesFill(VkCommandBuffer cmdbuf, ray_resources_fill_t fill) { vkCmdPipelineBarrier(cmdbuf, src_stage_mask, - fill.dest_pipeline, + args.pipeline, 0, 0, NULL, 0, NULL, image_barriers_count, image_barriers); } } -#if 0 -static const struct { - ray_resource_binding_desc_fixme_t desc; -} fixme_descs[] = { - -#define FIXME_DESC_BUFFER(name_, semantic_, count_) \ - { \ - .type = ResourceBuffer, \ - .desc.semantic = (RayResource_##semantic_ + 1), \ - .desc.count = count_, \ - } - - // External - FIXME_DESC_BUFFER("ubo", ubo, 1), - FIXME_DESC_BUFFER("tlas", tlas, 1), - FIXME_DESC_BUFFER("kusochki", kusochki, 1), - FIXME_DESC_BUFFER("indices", indices, 1), - FIXME_DESC_BUFFER("vertices", vertices, 1), - FIXME_DESC_BUFFER("lights", lights, 1), - FIXME_DESC_BUFFER("light_clusters", light_clusters, 1), - FIXME_DESC_BUFFER("light_grid", light_clusters, 1), - -#define FIXME_DESC_IMAGE(name_, semantic_, image_format_, count_) \ - { \ - .type = ResourceImage, \ - .image_format = image_format_, \ - .desc.semantic = (RayResource_##semantic_ + 1), \ - .desc.count = count_, \ - } - - FIXME_DESC_IMAGE("textures", all_textures, VK_FORMAT_UNDEFINED, MAX_TEXTURES), - FIXME_DESC_IMAGE("skybox", skybox, VK_FORMAT_UNDEFINED, 1), - FIXME_DESC_IMAGE("dest", denoised, VK_FORMAT_UNDEFINED,1), - - // Internal, temporary -#define DECLARE_IMAGE(_, name_, format_) \ - { \ - .type = ResourceImage, \ - .image_format = format_, \ - .desc.semantic = (RayResource_##name_ + 1), \ - .desc.count = 1, \ - }, -#define rgba8 VK_FORMAT_R8G8B8A8_UNORM -#define rgba32f VK_FORMAT_R32G32B32A32_SFLOAT -#define rgba16f VK_FORMAT_R16G16B16A16_SFLOAT - RAY_PRIMARY_OUTPUTS(DECLARE_IMAGE) - RAY_LIGHT_DIRECT_POLY_OUTPUTS(DECLARE_IMAGE) - RAY_LIGHT_DIRECT_POINT_OUTPUTS(DECLARE_IMAGE) -}; -#endif - -/* -ray_resource_binding_desc_fixme_t RayResouceGetBindingForName_FIXME(const char *name, ray_resource_desc_t desc) { - for (int i = 0; i < MAX_RESOURCES; ++i) { - const vk_named_resource_t *const res = g_resources.named + i; - if (strcmp(name, res->name) != 0) - continue; - - if (res->desc_fixme.type != desc.type) { - gEngine.Con_Printf(S_ERROR "Incompatible resource types for name %s: want %d, have %d\n", name, desc.type, res->desc_fixme.type); - break; - } - - if (res->desc_fixme.type == ResourceImage && res->desc_fixme.image_format != VK_FORMAT_UNDEFINED && desc.image_format != res->desc_fixme.image_format) { - gEngine.Con_Printf(S_ERROR "Incompatible image formats for name %s: want %d, have %d\n", name, desc.image_format, res->desc_fixme.image_format); - break; - } - - return (ray_resource_binding_desc_fixme_t){i, res->count}; - } - - return (ray_resource_binding_desc_fixme_t){-1,-1}; -} -*/ -#endif - -void R_VkResourceWriteToDescriptorValue(VkCommandBuffer cmdbuf, vk_resource_write_descriptor_args_t args) { - ASSERT(!"Not implemented"); -} diff --git a/ref_vk/ray_resources.h b/ref_vk/ray_resources.h index 0d4b34a2..6dc63bb1 100644 --- a/ref_vk/ray_resources.h +++ b/ref_vk/ray_resources.h @@ -20,9 +20,13 @@ typedef struct vk_resource_s *vk_resource_p; typedef struct { VkPipelineStageFlagBits pipeline; - vk_resource_p resource; - vk_descriptor_value_t* value; - qboolean write; -} vk_resource_write_descriptor_args_t; + const vk_resource_p *resources; + const int *resources_map; + vk_descriptor_value_t* values; + int count; + int write_begin; // Entries starting at this index are written into by the pass +} vk_resources_write_descriptors_args_t; + +void R_VkResourcesPrepareDescriptorsValues(VkCommandBuffer cmdbuf, vk_resources_write_descriptors_args_t args); + -void R_VkResourceWriteToDescriptorValue(VkCommandBuffer cmdbuf, vk_resource_write_descriptor_args_t args); diff --git a/ref_vk/vk_descriptor.h b/ref_vk/vk_descriptor.h index 2dff5545..696bb19e 100644 --- a/ref_vk/vk_descriptor.h +++ b/ref_vk/vk_descriptor.h @@ -25,11 +25,13 @@ extern descriptor_pool_t vk_desc; qboolean VK_DescriptorInit( void ); void VK_DescriptorShutdown( void ); +struct xvk_image_s; typedef union { VkDescriptorBufferInfo buffer; VkDescriptorImageInfo image; VkDescriptorImageInfo *image_array; VkWriteDescriptorSetAccelerationStructureKHR accel; + const struct xvk_image_s *image_object; } vk_descriptor_value_t; typedef struct { diff --git a/ref_vk/vk_image.h b/ref_vk/vk_image.h index 27709d53..51529db1 100644 --- a/ref_vk/vk_image.h +++ b/ref_vk/vk_image.h @@ -2,7 +2,7 @@ #include "vk_core.h" #include "vk_devmem.h" -typedef struct { +typedef struct xvk_image_s { vk_devmem_t devmem; VkImage image; VkImageView view; diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 0ed685ff..3255fac2 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -228,6 +228,15 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } + // Clear intra-frame resources + for (int i = ExternalResource_COUNT; i < MAX_RESOURCES; ++i) { + rt_resource_t* const res = g_rtx.res + i; + if (!res->name[0] || !res->image.image) + continue; + + res->resource.read = res->resource.write = (ray_resource_state_t){0}; + } + DEBUG_BEGIN(cmdbuf, "yay tracing"); RT_VkAccelPrepareTlas(cmdbuf); prepareUniformBuffer(args->render_args, args->frame_index, args->fov_angle_y); @@ -265,7 +274,12 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } - // FIXME R_VkMeatpipePerform(&g_rtx.mainpipe, cmdbuf, args->frame_index, &res); + R_VkMeatpipePerform(g_rtx.mainpipe, cmdbuf, (vk_meatpipe_perfrom_args_t) { + .frame_set_slot = args->frame_index, + .width = FRAME_WIDTH, + .height = FRAME_HEIGHT, + .resources = g_rtx.mainpipe_resources, + }); { const r_vkimage_blit_args blit_args = { @@ -310,6 +324,9 @@ static void reloadMainpipe(void) { const qboolean create = !!(mr->flags & MEATPIPE_RES_CREATE); + // FIXME no assert, just complain + ASSERT(!create || mr->descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE); + // FIXME this should be specified as a flag, from rt.json const qboolean output = Q_strcmp("dest", mr->name) == 0; @@ -319,10 +336,12 @@ static void reloadMainpipe(void) { goto fail; } - if (output) - newpipe_out = g_rtx.res + index; + rt_resource_t *const res = g_rtx.res + index; - if (create && g_rtx.res[index].image.image == VK_NULL_HANDLE) { + if (output) + newpipe_out = res; + + if (create && res->image.image == VK_NULL_HANDLE) { const xvk_image_create_t create = { .debug_name = mr->name, .width = FRAME_WIDTH, @@ -335,11 +354,22 @@ static void reloadMainpipe(void) { .has_alpha = true, .is_cubemap = false, }; - g_rtx.res[index].image = XVK_ImageCreate(&create); - Q_strncpy(g_rtx.res[index].name, mr->name, sizeof(g_rtx.res[index].name)); + res->image = XVK_ImageCreate(&create); + Q_strncpy(res->name, mr->name, sizeof(res->name)); } - newpipe_resources[i] = &g_rtx.res[index].resource; + newpipe_resources[i] = &res->resource; + + if (create) { + if (mr->descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) + newpipe_resources[i]->value.image_object = &res->image; + + res->resource.type = mr->descriptor_type; + } else { + // TODO no assert, complain and exit + // can't do before all resources are properly registered by their producers and not all this temp crap we have right now + // ASSERT(res->resource.type == mr->descriptor_type); + } } if (!newpipe_out) { @@ -360,6 +390,8 @@ static void reloadMainpipe(void) { g_rtx.mainpipe_resources = newpipe_resources; g_rtx.mainpipe_out = newpipe_out; + return; + fail: if (newpipe_resources) { for (int i = 0; i < newpipe->resources_count; ++i) { From d0fc84b7dd3e619e3c47977c750db22956d2fcd9 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 21 Jan 2023 13:15:49 -0800 Subject: [PATCH 511/548] update todo --- ref_vk/TODO.md | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index 7e0861ad..b99fd551 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,29 +1,14 @@ # Real next ->=E217 - - [ ] meatpipe resource tracking - - [ ] name -> index mapping - - [ ] create images on meatpipe load -- [ ] automatic resource creation - - [ ] serialize all resources with in/out and formats for images - - [ ] resource management refactoring: - - [ ] resource automatic resolution: prducing, barriers, etc - - [ ] register existing resources (tlas, buffers, temp images, ...) - - [ ] resource destruction - - [ ] create resources on demand - - [ ] ? resource object: name, metadata(type, etc.), producer, status (ready, barriers, etc) +>=E222 + - [ ] refcount meatpipe created images - [ ] rake yuri primary ray + - [ ] resource management refactoring: + - [ ] register existing resources (tlas, buffers, temp images, ...) in their producers + - [ ] resource automatic resolution: prducing, barriers, etc + - [ ] resource destruction + - [ ] ? resource object: name, metadata(type, etc.), producer, status (ready, barriers, etc) # Programmable render -- [x] parse spirv -> get bindings with names - - [x] spirv needs to be compiled with -g, otherwise there are no OpName entries. Need a custom strip util that strips the rest? - - [x] unnamed uniform blocks are uncomfortable to parse. -- [ ] passes "export" their bindings as detailed resource descriptions: - - [ ] images: name, r/w, format, resolution (? not found in spv, needs to be externally supplied) - - [ ] buffers: name, r/w, size, type name (?) -- [ ] name -> index resolver (hashmap kekw) -- [ ] automatic creation of resources - - [ ] images - - [ ] buffers - [ ] implicit dependency tracking. pass defines: - [ ] imports: list of things it needs - [ ] exports: list of things it produces. those get created and registered with this pass as a producer @@ -492,3 +477,20 @@ - [x] begin Rake Yuri migration - [x] direct lights +# 2023-01-21 E217-E221 +- [x] meatpipe resource tracking + - [x] name -> index mapping + - [x] create images on meatpipe load + - [x] automatic resource creation + - [x] serialize all resources with in/out and formats for images + - [x] create resources on demand +- [x] parse spirv -> get bindings with names + - [x] spirv needs to be compiled with -g, otherwise there are no OpName entries. Need a custom strip util that strips the rest? + - [x] unnamed uniform blocks are uncomfortable to parse. +- [x] passes "export" their bindings as detailed resource descriptions: + - [x] images: name, r/w, format, resolution (? not found in spv, needs to be externally supplied) + - [-] buffers: name, r/w, size, type name (?) -- can't really do, too hard for now +- [x] name -> index resolver (hashmap kekw) +- [x] automatic creation of resources + - [x] images + - [-] buffers -- no immediate need for that From 4d370b072ccb6834d6e251c0c2b5d3cac8cc4f94 Mon Sep 17 00:00:00 2001 From: LifeKILLED Date: Sun, 22 Jan 2023 03:45:29 +0400 Subject: [PATCH 512/548] vk rt denoiser: add motion vectors --- ref_vk/shaders/denoiser.comp | 6 ++ ref_vk/shaders/ray_common.glsl | 1 + ref_vk/shaders/ray_interop.h | 4 ++ ref_vk/shaders/ray_kusochki.glsl | 1 + ref_vk/shaders/ray_primary.rchit | 1 + ref_vk/shaders/ray_primary.rgen | 1 + ref_vk/shaders/ray_primary_common.glsl | 1 + ref_vk/shaders/rt_geometry.glsl | 8 +++ ref_vk/vk_brush.c | 46 ++++++++++++++ ref_vk/vk_geometry.h | 1 + ref_vk/vk_ray_model.c | 3 + ref_vk/vk_render.c | 8 +++ ref_vk/vk_render.h | 5 ++ ref_vk/vk_rtx.c | 10 ++- ref_vk/vk_studio.c | 86 ++++++++++++++++++++++---- 15 files changed, 168 insertions(+), 14 deletions(-) diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 293e9718..59a789a9 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -17,6 +17,9 @@ layout(set = 0, binding = 4, rgba16f) uniform readonly image2D light_point_diffu layout(set = 0, binding = 5, rgba16f) uniform readonly image2D light_point_specular; layout(set = 0, binding = 6, rgba16f) uniform readonly image2D emissive; +layout(set = 0, binding = 7, rgba32f) uniform readonly image2D position_t; +layout(set = 0, binding = 8, rgba32f) uniform readonly image2D prev_position_t; + //layout(set = 0, binding = 7, rgba32f) uniform readonly image2D position_t; //layout(set = 0, binding = 8, rgba16f) uniform readonly image2D normals_gs; @@ -204,6 +207,9 @@ void main() { } #endif + // DEBUG motion vectors + //colour = vec3(length(imageLoad(position_t, pix).rgb - imageLoad(prev_position_t, pix).rgb)); + imageStore(out_dest, pix, vec4(colour, 0.)); //imageStore(out_dest, pix, imageLoad(light_poly, pix)); } diff --git a/ref_vk/shaders/ray_common.glsl b/ref_vk/shaders/ray_common.glsl index 652317a8..bf9ad41f 100644 --- a/ref_vk/shaders/ray_common.glsl +++ b/ref_vk/shaders/ray_common.glsl @@ -7,6 +7,7 @@ struct RayPayloadOpaque { float t_offset, pixel_cone_spread_angle; vec4 hit_pos_t; + vec4 prev_pos_t; vec3 normal; vec3 geometry_normal; vec3 base_color; diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index f0cd2622..64de99ea 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -18,6 +18,7 @@ X(12, normals_gs, rgba16f) \ X(13, material_rmxx, rgba8) \ X(14, emissive, rgba16f) \ + X(15, prev_position_t, rgba32f) \ #define RAY_LIGHT_DIRECT_INPUTS(X) \ X(10, position_t, rgba32f) \ @@ -103,6 +104,8 @@ struct Kusok { float roughness; float metalness; PAD(2) + + mat4 prev_transform; }; struct PointLight { @@ -153,6 +156,7 @@ struct PushConstants { struct UniformBuffer { mat4 inv_proj, inv_view; + mat4 prev_inv_proj, prev_inv_view; float ray_cone_width; uint random_seed; PAD(2) diff --git a/ref_vk/shaders/ray_kusochki.glsl b/ref_vk/shaders/ray_kusochki.glsl index 1b016ff1..9ae612b9 100644 --- a/ref_vk/shaders/ray_kusochki.glsl +++ b/ref_vk/shaders/ray_kusochki.glsl @@ -9,6 +9,7 @@ struct Vertex { vec3 pos; + vec3 prev_pos; vec3 normal; vec3 tangent; vec2 gl_tc; diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index c2073d47..4e411b6e 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -27,6 +27,7 @@ void main() { Geometry geom = readHitGeometry(); payload.hit_t = vec4(geom.pos, gl_HitTEXT); + payload.prev_pos_t = vec4(geom.prev_pos, 0.); const Kusok kusok = getKusok(geom.kusok_index); const uint tex_base_color = kusok.tex_base_color; diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index 117268e1..8639f481 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -40,4 +40,5 @@ void main() { imageStore(out_normals_gs, ivec2(gl_LaunchIDEXT.xy), payload.normals_gs); imageStore(out_material_rmxx, ivec2(gl_LaunchIDEXT.xy), payload.material_rmxx); imageStore(out_emissive, ivec2(gl_LaunchIDEXT.xy), payload.emissive); + imageStore(out_prev_position_t, ivec2(gl_LaunchIDEXT.xy), payload.prev_pos_t); } diff --git a/ref_vk/shaders/ray_primary_common.glsl b/ref_vk/shaders/ray_primary_common.glsl index fd593ee0..4611b843 100644 --- a/ref_vk/shaders/ray_primary_common.glsl +++ b/ref_vk/shaders/ray_primary_common.glsl @@ -6,6 +6,7 @@ struct RayPayloadPrimary { vec4 hit_t; + vec4 prev_pos_t; vec4 base_color_a; vec4 normals_gs; vec4 material_rmxx; diff --git a/ref_vk/shaders/rt_geometry.glsl b/ref_vk/shaders/rt_geometry.glsl index f232035e..e1980b6d 100644 --- a/ref_vk/shaders/rt_geometry.glsl +++ b/ref_vk/shaders/rt_geometry.glsl @@ -40,6 +40,7 @@ vec4 computeAnisotropicEllipseAxes(in vec3 P, in vec3 f, struct Geometry { vec3 pos; + vec3 prev_pos; vec2 uv; vec4 uv_lods; @@ -69,6 +70,12 @@ Geometry readHitGeometry() { gl_ObjectToWorldEXT * vec4(getVertex(vi3).pos, 1.f), }; + const vec3 prev_pos[3] = { + (kusok.prev_transform * vec4(getVertex(vi1).prev_pos, 1.f)).xyz, + (kusok.prev_transform * vec4(getVertex(vi2).prev_pos, 1.f)).xyz, + (kusok.prev_transform * vec4(getVertex(vi3).prev_pos, 1.f)).xyz, + }; + const vec2 uvs[3] = { getVertex(vi1).gl_tc, getVertex(vi2).gl_tc, @@ -76,6 +83,7 @@ Geometry readHitGeometry() { }; geom.pos = baryMix(pos[0], pos[1], pos[2], bary); + geom.prev_pos = baryMix(prev_pos[0], prev_pos[1], prev_pos[2], bary); geom.uv = baryMix(uvs[0], uvs[1], uvs[2], bary); //TODO or not TODO? const vec2 texture_uv = texture_uv_stationary + push_constants.time * kusok.uv_speed; diff --git a/ref_vk/vk_brush.c b/ref_vk/vk_brush.c index 43d9ed0a..cdfd59ed 100644 --- a/ref_vk/vk_brush.c +++ b/ref_vk/vk_brush.c @@ -33,6 +33,16 @@ static struct { int rtable[MOD_FRAMES][MOD_FRAMES]; } g_brush; + +#define MAX_BRUSH_ENTITIES_PREV_STATES 1024 + +typedef struct { + matrix4x4 prev_model_transform; + float prev_time; +} brush_entity_prev_state_t; + +static brush_entity_prev_state_t g_brush_prev_states[MAX_BRUSH_ENTITIES_PREV_STATES]; + void VK_InitRandomTable( void ) { int tu, tv; @@ -82,6 +92,7 @@ static void EmitWaterPolys( const cl_entity_t *ent, const msurface_t *warp, qboo { const float time = gpGlobals->time; float *v, nv, waveHeight; + float prev_nv, prev_time; float s, t, os, ot; glpoly_t *p; int i; @@ -90,6 +101,11 @@ static void EmitWaterPolys( const cl_entity_t *ent, const msurface_t *warp, qboo uint16_t *indices; r_geometry_buffer_lock_t buffer; + if (ent->index < MAX_BRUSH_ENTITIES_PREV_STATES) { + prev_time = g_brush_prev_states[ent->index].prev_time; + g_brush_prev_states[ent->index].prev_time = time; + } else gEngine.Con_Printf(S_ERROR "Brush entities previous frame states pool is overflow, increase it. Index is %s\n", ent->index ); + #define MAX_WATER_VERTICES 16 vk_vertex_t poly_vertices[MAX_WATER_VERTICES]; @@ -135,6 +151,10 @@ static void EmitWaterPolys( const cl_entity_t *ent, const msurface_t *warp, qboo nv = r_turbsin[(int)(time * 160.0f + v[1] + v[0]) & 255] + 8.0f; nv = (r_turbsin[(int)(v[0] * 5.0f + time * 171.0f - v[1]) & 255] + 8.0f ) * 0.8f + nv; nv = nv * waveHeight + v[2]; + + prev_nv = r_turbsin[(int)(prev_time * 160.0f + v[1] + v[0]) & 255] + 8.0f; + prev_nv = (r_turbsin[(int)(v[0] * 5.0f + prev_time * 171.0f - v[1]) & 255] + 8.0f ) * 0.8f + prev_nv; + prev_nv = prev_nv * waveHeight + v[2]; } else nv = v[2]; @@ -151,6 +171,10 @@ static void EmitWaterPolys( const cl_entity_t *ent, const msurface_t *warp, qboo poly_vertices[i].pos[1] = v[1]; poly_vertices[i].pos[2] = nv; + poly_vertices[i].prev_pos[0] = v[0]; + poly_vertices[i].prev_pos[1] = v[1]; + poly_vertices[i].prev_pos[2] = prev_nv; + poly_vertices[i].gl_tc[0] = s; poly_vertices[i].gl_tc[1] = t; @@ -265,9 +289,17 @@ void XVK_DrawWaterSurfaces( const cl_entity_t *ent ) EmitWaterPolys( ent, surf, false ); } + int entity_id = ent->index; + + if (entity_id < MAX_BRUSH_ENTITIES_PREV_STATES) + Matrix4x4_Copy( *VK_RenderGetLastFrameTransform(), g_brush_prev_states[entity_id].prev_model_transform ); + // submit as dynamic model VK_RenderModelDynamicCommit(); + if (entity_id < MAX_BRUSH_ENTITIES_PREV_STATES) + Matrix4x4_Copy( g_brush_prev_states[entity_id].prev_model_transform, *VK_RenderGetLastFrameTransform() ); + // TODO: // - upload water geometry only once, animate in compute/vertex shader } @@ -360,8 +392,18 @@ void VK_BrushModelDraw( const cl_entity_t *ent, int render_mode, const matrix4x4 } } + int entity_id = ent->index; + if (entity_id < MAX_BRUSH_ENTITIES_PREV_STATES) { + Matrix4x4_Copy( bmodel->render_model.prev_transform, + g_brush_prev_states[entity_id].prev_model_transform ); + } else gEngine.Con_Printf(S_ERROR "Brush entities previous frame states pool is overflow, increase it. Index is %s\n", ent->index ); + bmodel->render_model.render_mode = render_mode; VK_RenderModelDraw(ent, &bmodel->render_model); + + if (entity_id >= 0 && entity_id < MAX_BRUSH_ENTITIES_PREV_STATES) { + Matrix4x4_Copy( g_brush_prev_states[entity_id].prev_model_transform, bmodel->render_model.prev_transform ); + } } static qboolean renderableSurface( const msurface_t *surf, int i ) { @@ -576,6 +618,10 @@ static qboolean loadBrushSurfaces( model_sizes_t sizes, const model_t *mod ) { {in_vertex->position[0], in_vertex->position[1], in_vertex->position[2]}, }; + vertex.prev_pos[0] = in_vertex->position[0]; + vertex.prev_pos[1] = in_vertex->position[1]; + vertex.prev_pos[2] = in_vertex->position[2]; + float s = DotProduct( in_vertex->position, surf->texinfo->vecs[0] ) + surf->texinfo->vecs[0][3]; float t = DotProduct( in_vertex->position, surf->texinfo->vecs[1] ) + surf->texinfo->vecs[1][3]; diff --git a/ref_vk/vk_geometry.h b/ref_vk/vk_geometry.h index 41165d2b..24388443 100644 --- a/ref_vk/vk_geometry.h +++ b/ref_vk/vk_geometry.h @@ -15,6 +15,7 @@ typedef struct vk_vertex_s { // TODO padding needed for storage buffer reading, figure out how to fix in GLSL/SPV side vec3_t pos; float p0_; + vec3_t prev_pos; float p01_; vec3_t normal; uint32_t flags; vec3_t tangent; uint32_t p1_; vec2_t gl_tc; //float p2_[2]; diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index e360d986..b0586b11 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -301,6 +301,7 @@ vk_ray_model_t* VK_RayModelCreate( vk_ray_model_init_t args ) { const vec3_t color = {1, 1, 1}; applyMaterialToKusok(kusochki + i, mg, color, false); + Matrix4x4_LoadIdentity(kusochki[i].prev_transform); } R_VkStagingUnlock(kusok_staging.handle); @@ -493,6 +494,8 @@ void VK_RayFrameAddModel( vk_ray_model_t *model, const vk_render_model_t *render for (int i = 0; i < render_model->num_geometries; ++i) { const vk_render_geometry_t *geom = render_model->geometries + i; applyMaterialToKusok(kusochki + i, geom, color, HACK_reflective); + + Matrix4x4_Copy((kusochki + i)->prev_transform, render_model->prev_transform); } /* gEngine.Con_Reportf("model %s: geom=%d kuoffs=%d kustoff=%d kustsz=%d sthndl=%d\n", */ diff --git a/ref_vk/vk_render.c b/ref_vk/vk_render.c index ac7df43e..7271db89 100644 --- a/ref_vk/vk_render.c +++ b/ref_vk/vk_render.c @@ -87,6 +87,7 @@ static qboolean createPipelines( void ) {.binding = 0, .location = 3, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(vk_vertex_t, lm_tc)}, {.binding = 0, .location = 4, .format = VK_FORMAT_R8G8B8A8_UNORM, .offset = offsetof(vk_vertex_t, color)}, {.binding = 0, .location = 5, .format = VK_FORMAT_R32_UINT, .offset = offsetof(vk_vertex_t, flags)}, + {.binding = 0, .location = 6, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(vk_vertex_t, prev_pos)}, }; const vk_shader_stage_t shader_stages[] = { @@ -682,6 +683,9 @@ void VK_RenderModelDraw( const cl_entity_t *ent, vk_render_model_t* model ) { if (g_render_state.current_frame_is_ray_traced) { VK_RayFrameAddModel(model->ray_model, model, (const matrix3x4*)g_render_state.model, g_render_state.dirty_uniform_data.color, ent ? ent->curstate.rendercolor : (color24){255,255,255}); + + // store current transform here before it puts to entity history + Matrix4x4_Copy( model->prev_transform, g_render_state.model ); return; } @@ -747,6 +751,10 @@ static struct { vk_render_geometry_t geometries[MAX_DYNAMIC_GEOMETRY]; } g_dynamic_model = {0}; +matrix4x4 *VK_RenderGetLastFrameTransform() { + return &g_dynamic_model.model.prev_transform; +} + void VK_RenderModelDynamicBegin( int render_mode, const char *debug_name_fmt, ... ) { va_list argptr; va_start( argptr, debug_name_fmt ); diff --git a/ref_vk/vk_render.h b/ref_vk/vk_render.h index 5f3323fb..0bdf4f24 100644 --- a/ref_vk/vk_render.h +++ b/ref_vk/vk_render.h @@ -82,6 +82,9 @@ typedef struct vk_render_model_s { struct vk_ray_model_s *ray_model; struct rt_light_add_polygon_s *polylights; int polylights_count; + + // previous frame ObjectToWorld (model) matrix + matrix4x4 prev_transform; } vk_render_model_t; qboolean VK_RenderModelInit( vk_render_model_t* model ); @@ -100,3 +103,5 @@ void VK_RenderEnd( VkCommandBuffer cmdbuf ); void VK_RenderEndRTX( VkCommandBuffer cmdbuf, VkImageView img_dst_view, VkImage img_dst, uint32_t w, uint32_t h ); void VK_Render_FIXME_Barrier( VkCommandBuffer cmdbuf ); + +matrix4x4* VK_RenderGetLastFrameTransform(); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 51261609..e873177d 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -120,6 +120,13 @@ static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int fr Matrix4x4_Invert_Full(view_inv, *args->view); Matrix4x4_ToArrayFloatGL(view_inv, (float*)ubo->inv_view); + // last frame matrices + static matrix4x4 prev_inv_proj, prev_inv_view; + Matrix4x4_ToArrayFloatGL(prev_inv_proj, (float*)ubo->prev_inv_proj); + Matrix4x4_ToArrayFloatGL(prev_inv_view, (float*)ubo->prev_inv_view); + Matrix4x4_Copy(prev_inv_view, view_inv); + Matrix4x4_Copy(prev_inv_proj, proj_inv); + ubo->ray_cone_width = atanf((2.0f*tanf(DEG2RAD(fov_angle_y) * 0.5f)) / (float)FRAME_HEIGHT); ubo->random_seed = (uint32_t)gEngine.COM_RandomLong(0, INT32_MAX); } @@ -273,6 +280,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* R_VkImageBlit( cmdbuf, &blit_args ); } + DEBUG_END(cmdbuf); } @@ -280,7 +288,7 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) { const VkCommandBuffer cmdbuf = args->cmdbuf; const xvk_ray_frame_images_t* current_frame = g_rtx.frames + (g_rtx.frame_number % 2); - + ASSERT(vk_core.rtx); // ubo should contain two matrices // FIXME pass these matrices explicitly to let RTX module handle ubo itself diff --git a/ref_vk/vk_studio.c b/ref_vk/vk_studio.c index 72b9ffc4..435e8c18 100644 --- a/ref_vk/vk_studio.c +++ b/ref_vk/vk_studio.c @@ -47,6 +47,11 @@ typedef struct sortedmesh_s int flags; // face flags } sortedmesh_t; +typedef struct { + matrix3x4 worldtransform[MAXSTUDIOBONES]; + matrix4x4 prev_transform; +} studio_entity_prev_state_t; + typedef struct { double time; @@ -74,6 +79,8 @@ typedef struct vec3_t verts[MAXSTUDIOVERTS]; vec3_t norms[MAXSTUDIOVERTS]; + vec3_t prev_verts[MAXSTUDIOVERTS]; // last frame state for motion vectors + // lighting state float ambientlight; float shadelight; @@ -118,6 +125,9 @@ static cvar_t *cl_himodels; static r_studio_interface_t *pStudioDraw; static studio_draw_state_t g_studio; // global studio state +#define MAX_ENTITIES_PREV_STATES_STUDIO 1024 +static studio_entity_prev_state_t g_entity_prev_states[MAX_ENTITIES_PREV_STATES_STUDIO]; + // global variables static qboolean m_fDoRemap; mstudiomodel_t *m_pSubModel; @@ -254,7 +264,7 @@ static qboolean R_StudioComputeBBox( vec3_t bbox[8] ) return true; // visible } -void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 result ) +void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 *worldtransform, matrix3x4 result ) { float flWeight0, flWeight1, flWeight2, flWeight3; int i, numbones = 0; @@ -268,10 +278,10 @@ void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 resu if( numbones == 4 ) { - vec4_t *boneMat0 = (vec4_t *)g_studio.worldtransform[boneweights->bone[0]]; - vec4_t *boneMat1 = (vec4_t *)g_studio.worldtransform[boneweights->bone[1]]; - vec4_t *boneMat2 = (vec4_t *)g_studio.worldtransform[boneweights->bone[2]]; - vec4_t *boneMat3 = (vec4_t *)g_studio.worldtransform[boneweights->bone[3]]; + vec4_t *boneMat0 = (vec4_t *)worldtransform[boneweights->bone[0]]; + vec4_t *boneMat1 = (vec4_t *)worldtransform[boneweights->bone[1]]; + vec4_t *boneMat2 = (vec4_t *)worldtransform[boneweights->bone[2]]; + vec4_t *boneMat3 = (vec4_t *)worldtransform[boneweights->bone[3]]; flWeight0 = boneweights->weight[0] / 255.0f; flWeight1 = boneweights->weight[1] / 255.0f; flWeight2 = boneweights->weight[2] / 255.0f; @@ -295,9 +305,9 @@ void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 resu } else if( numbones == 3 ) { - vec4_t *boneMat0 = (vec4_t *)g_studio.worldtransform[boneweights->bone[0]]; - vec4_t *boneMat1 = (vec4_t *)g_studio.worldtransform[boneweights->bone[1]]; - vec4_t *boneMat2 = (vec4_t *)g_studio.worldtransform[boneweights->bone[2]]; + vec4_t *boneMat0 = (vec4_t *)worldtransform[boneweights->bone[0]]; + vec4_t *boneMat1 = (vec4_t *)worldtransform[boneweights->bone[1]]; + vec4_t *boneMat2 = (vec4_t *)worldtransform[boneweights->bone[2]]; flWeight0 = boneweights->weight[0] / 255.0f; flWeight1 = boneweights->weight[1] / 255.0f; flWeight2 = boneweights->weight[2] / 255.0f; @@ -320,8 +330,8 @@ void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 resu } else if( numbones == 2 ) { - vec4_t *boneMat0 = (vec4_t *)g_studio.worldtransform[boneweights->bone[0]]; - vec4_t *boneMat1 = (vec4_t *)g_studio.worldtransform[boneweights->bone[1]]; + vec4_t *boneMat0 = (vec4_t *)worldtransform[boneweights->bone[0]]; + vec4_t *boneMat1 = (vec4_t *)worldtransform[boneweights->bone[1]]; flWeight0 = boneweights->weight[0] / 255.0f; flWeight1 = boneweights->weight[1] / 255.0f; flTotal = flWeight0 + flWeight1; @@ -343,7 +353,7 @@ void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 resu } else { - Matrix3x4_Copy( result, g_studio.worldtransform[boneweights->bone[0]] ); + Matrix3x4_Copy( result, worldtransform[boneweights->bone[0]] ); } } @@ -1062,6 +1072,26 @@ static void R_StudioSaveBones( void ) } } +/* +==================== +SaveTransformsForNextFrame + +==================== +*/ + +static void R_StudioSaveTransformsForNextFrame( matrix3x4* bones_transforms ) +{ + if (RI.currententity->index >= MAX_ENTITIES_PREV_STATES_STUDIO) + return; + + studio_entity_prev_state_t *prev_state = &g_entity_prev_states[RI.currententity->index]; + + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + Matrix3x4_Copy(prev_state->worldtransform[i], bones_transforms[i]); + } +} + /* ==================== StudioBuildNormalTable @@ -1946,6 +1976,7 @@ static void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float *dst_vtx = (vk_vertex_t){0}; VectorCopy(g_studio.verts[ptricmds[0]], dst_vtx->pos); + VectorCopy(g_studio.prev_verts[ptricmds[0]], dst_vtx->prev_pos); VectorCopy(g_studio.norms[ptricmds[0]], dst_vtx->normal); dst_vtx->lm_tc[0] = dst_vtx->lm_tc[1] = 0.f; @@ -2127,6 +2158,14 @@ static void R_StudioDrawPoints( void ) pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); if( m_skinnum != 0 ) pskinref += (m_skinnum * m_pStudioHeader->numskinref); + studio_entity_prev_state_t *prev_frame_state = &g_entity_prev_states[RI.currententity->index]; + + if (RI.currententity->index >= MAX_ENTITIES_PREV_STATES_STUDIO) + { + gEngine.Con_Printf(S_ERROR "Studio entities previous frame states pool is overflow, increase it. Index is %s\n", RI.currententity->index); + prev_frame_state = &g_entity_prev_states[MAX_ENTITIES_PREV_STATES_STUDIO - 1]; // fallback to last element + } + if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) && m_pSubModel->blendvertinfoindex != 0 && m_pSubModel->blendnorminfoindex != 0 ) { mstudioboneweight_t *pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendvertinfoindex); @@ -2135,24 +2174,35 @@ static void R_StudioDrawPoints( void ) for( i = 0; i < m_pSubModel->numverts; i++ ) { - R_StudioComputeSkinMatrix( &pvertweight[i], skinMat ); + R_StudioComputeSkinMatrix( &pvertweight[i], g_studio.worldtransform, skinMat ); Matrix3x4_VectorTransform( skinMat, pstudioverts[i], g_studio.verts[i] ); R_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] ); } + for( i = 0; i < m_pSubModel->numverts; i++ ) + { + R_StudioComputeSkinMatrix( &pvertweight[i], prev_frame_state->worldtransform, skinMat ); + Matrix3x4_VectorTransform( skinMat, pstudioverts[i], g_studio.prev_verts[i] ); + } + for( i = 0; i < m_pSubModel->numnorms; i++ ) { - R_StudioComputeSkinMatrix( &pnormweight[i], skinMat ); + R_StudioComputeSkinMatrix( &pnormweight[i], g_studio.worldtransform, skinMat ); Matrix3x4_VectorRotate( skinMat, pstudionorms[i], g_studio.norms[i] ); } + + R_StudioSaveTransformsForNextFrame (g_studio.worldtransform ); } else { for( i = 0; i < m_pSubModel->numverts; i++ ) { Matrix3x4_VectorTransform( g_studio.bonestransform[pvertbone[i]], pstudioverts[i], g_studio.verts[i] ); + Matrix3x4_VectorTransform( prev_frame_state->worldtransform[pvertbone[i]], pstudioverts[i], g_studio.prev_verts[i] ); R_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] ); } + + R_StudioSaveTransformsForNextFrame( g_studio.bonestransform ); } // generate shared normals for properly scaling glowing shell @@ -2278,7 +2328,17 @@ static void R_StudioDrawPoints( void ) */ } + int entity_id = RI.currententity->index; + + if (entity_id < MAX_ENTITIES_PREV_STATES_STUDIO) { + Matrix4x4_Copy( *VK_RenderGetLastFrameTransform(), g_entity_prev_states[entity_id].prev_transform ); + } else gEngine.Con_Printf(S_ERROR "Studio entities last states pool is overflow, increase it. Index is %s\n", entity_id ); + VK_RenderModelDynamicCommit(); + + if (entity_id < MAX_ENTITIES_PREV_STATES_STUDIO) { + Matrix4x4_Copy( g_entity_prev_states[entity_id].prev_transform, *VK_RenderGetLastFrameTransform() ); + } } static void R_StudioSetRemapColors( int newTop, int newBottom ) From 8709f668c4d576e5d5aa360955f0626651826a49 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 22 Jan 2023 13:22:56 -0800 Subject: [PATCH 513/548] rt: track meatpipe-created resources by refcounts --- ref_vk/vk_rtx.c | 118 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 79 insertions(+), 39 deletions(-) diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 3255fac2..bc5ed1b9 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -63,7 +63,7 @@ typedef struct { char name[64]; vk_resource_t resource; xvk_image_t image; - // TODO int refcount + int refcount; } rt_resource_t; static struct { @@ -305,6 +305,42 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* DEBUG_END(cmdbuf); } +static void cleanupResources(void) { + for (int i = 0; i < MAX_RESOURCES; ++i) { + rt_resource_t *const res = g_rtx.res + i; + if (!res->name[0] || res->refcount || !res->image.image) + continue; + + XVK_ImageDestroy(&res->image); + res->name[0] = '\0'; + } +} + +static void destroyMainpipe(void) { + if (!g_rtx.mainpipe) + return; + + ASSERT(g_rtx.mainpipe_resources); + + for (int i = 0; i < g_rtx.mainpipe->resources_count; ++i) { + const vk_meatpipe_resource_t *mr = g_rtx.mainpipe->resources + i; + const int index = findResource(mr->name); + ASSERT(index >= 0); + ASSERT(index < MAX_RESOURCES); + rt_resource_t *const res = g_rtx.res + index; + ASSERT(res->refcount > 0); + res->refcount--; + } + + cleanupResources(); + R_VkMeatpipeDestroy(g_rtx.mainpipe); + g_rtx.mainpipe = NULL; + + Mem_Free(g_rtx.mainpipe_resources); + g_rtx.mainpipe_resources = NULL; + g_rtx.mainpipe_out = NULL; +} + static void reloadMainpipe(void) { vk_meatpipe_t *const newpipe = R_VkMeatpipeCreateFromFile("rt.meat"); if (!newpipe) @@ -325,9 +361,12 @@ static void reloadMainpipe(void) { const qboolean create = !!(mr->flags & MEATPIPE_RES_CREATE); // FIXME no assert, just complain - ASSERT(!create || mr->descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE); + if (create && mr->descriptor_type != VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) { + gEngine.Con_Printf(S_ERROR "Only storage image creation is supported for meatpipes\n"); + goto fail; + } - // FIXME this should be specified as a flag, from rt.json + // TODO this should be specified as a flag, from rt.json const qboolean output = Q_strcmp("dest", mr->name) == 0; const int index = create ? getResourceSlotForName(mr->name) : findResource(mr->name); @@ -341,21 +380,25 @@ static void reloadMainpipe(void) { if (output) newpipe_out = res; - if (create && res->image.image == VK_NULL_HANDLE) { - const xvk_image_create_t create = { - .debug_name = mr->name, - .width = FRAME_WIDTH, - .height = FRAME_HEIGHT, - .mips = 1, - .layers = 1, - .format = mr->image_format, - .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = VK_IMAGE_USAGE_STORAGE_BIT | (output ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT : 0), - .has_alpha = true, - .is_cubemap = false, - }; - res->image = XVK_ImageCreate(&create); - Q_strncpy(res->name, mr->name, sizeof(res->name)); + if (create) { + if (res->image.image == VK_NULL_HANDLE) { + const xvk_image_create_t create = { + .debug_name = mr->name, + .width = FRAME_WIDTH, + .height = FRAME_HEIGHT, + .mips = 1, + .layers = 1, + .format = mr->image_format, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_STORAGE_BIT | (output ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT : 0), + .has_alpha = true, + .is_cubemap = false, + }; + res->image = XVK_ImageCreate(&create); + Q_strncpy(res->name, mr->name, sizeof(res->name)); + } else { + // TODO if (mr->image_format != res->image.format) { S_ERROR and goto fail } + } } newpipe_resources[i] = &res->resource; @@ -377,15 +420,19 @@ static void reloadMainpipe(void) { goto fail; } - if (g_rtx.mainpipe) { - R_VkMeatpipeDestroy(g_rtx.mainpipe); - - // FIXME also destroy all extra created resources/images - // use refcounts or something - - Mem_Free(g_rtx.mainpipe_resources); + // Loading successful + // Update refcounts + for (int i = 0; i < newpipe->resources_count; ++i) { + const vk_meatpipe_resource_t *mr = newpipe->resources + i; + const int index = findResource(mr->name); + ASSERT(index >= 0); + ASSERT(index < MAX_RESOURCES); + rt_resource_t *const res = g_rtx.res + index; + res->refcount++; } + destroyMainpipe(); + g_rtx.mainpipe = newpipe; g_rtx.mainpipe_resources = newpipe_resources; g_rtx.mainpipe_out = newpipe_out; @@ -393,12 +440,10 @@ static void reloadMainpipe(void) { return; fail: - if (newpipe_resources) { - for (int i = 0; i < newpipe->resources_count; ++i) { - // FIXME on error we'll leak images - } + cleanupResources(); + + if (newpipe_resources) Mem_Free(newpipe_resources); - } R_VkMeatpipeDestroy(newpipe); } @@ -484,7 +529,8 @@ qboolean VK_RayInit( void ) return false; #define REGISTER_EXTERNAL(type, name_) \ - Q_strncpy(g_rtx.res[ExternalResource_##name_].name, #name_, sizeof(g_rtx.res[0].name)); + Q_strncpy(g_rtx.res[ExternalResource_##name_].name, #name_, sizeof(g_rtx.res[0].name)); \ + g_rtx.res[ExternalResource_##name_].refcount = 1; EXTERNAL_RESOUCES(REGISTER_EXTERNAL) #undef REGISTER_EXTERNAL @@ -494,6 +540,7 @@ qboolean VK_RayInit( void ) .image_array = tglob.dii_all_textures, } }; + g_rtx.res[ExternalResource_textures].refcount = 1; reloadMainpipe(); if (!g_rtx.mainpipe) @@ -526,14 +573,7 @@ qboolean VK_RayInit( void ) void VK_RayShutdown( void ) { ASSERT(vk_core.rtx); - R_VkMeatpipeDestroy(g_rtx.mainpipe); - - // FIXME destroy mainpipe resources - /* - for (int i = 0; i < ARRAYSIZE(g_rtx.frames); ++i) { - XVK_ImageDestroy(&g_rtx.frames[i].denoised); - } - */ + destroyMainpipe(); VK_BufferDestroy(&g_ray_model_state.kusochki_buffer); VK_BufferDestroy(&g_rtx.uniform_buffer); From 9df38a64873b93f146f93ef224e361e722bad28c Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 22 Jan 2023 13:43:33 -0800 Subject: [PATCH 514/548] update todo --- ref_vk/TODO.md | 6 ++++-- ref_vk/vk_rtx.c | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index b99fd551..ce3fc37b 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,7 +1,9 @@ # Real next >=E222 - - [ ] refcount meatpipe created images - - [ ] rake yuri primary ray + - [x] refcount meatpipe created images + - [ ] previous frame resources reference + - [ ] what if new meatpipe has different image format for a creatable image? + - [ ] rake yuri primary ray - [ ] resource management refactoring: - [ ] register existing resources (tlas, buffers, temp images, ...) in their producers - [ ] resource automatic resolution: prducing, barriers, etc diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index bc5ed1b9..85b42019 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -433,6 +433,11 @@ static void reloadMainpipe(void) { destroyMainpipe(); + // TODO currently changing texture format is not handled. It will try to reuse existing image with the old format + // which will probably fail. To handle it we'd need to refactor this: + // 1. xvk_image_t should have a field with its current format? (or we'd also store if with the resource here) + // 2. do another loop here to detect format mismatch and recreate. + g_rtx.mainpipe = newpipe; g_rtx.mainpipe_resources = newpipe_resources; g_rtx.mainpipe_out = newpipe_out; From 28f40d5ca08d0456f3275ed51d163daf948ddb8d Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 22 Jan 2023 15:00:01 -0800 Subject: [PATCH 515/548] rt: compute the primary ray --- ref_vk/TODO.md | 18 +++---- ref_vk/shaders/ray_common_alphatest.rahit | 3 +- ref_vk/shaders/ray_primary.comp | 58 +++++++++++++++++++++ ref_vk/shaders/ray_primary.rchit | 6 +-- ref_vk/shaders/ray_primary.rgen | 1 + ref_vk/shaders/ray_primary_common.glsl | 5 +- ref_vk/shaders/ray_primary_hit.glsl | 61 +++++++++++++++++++++++ ref_vk/shaders/rt.json | 7 ++- ref_vk/shaders/rt_geometry.glsl | 37 ++++++++++---- ref_vk/vk_rtx.c | 6 +-- 10 files changed, 173 insertions(+), 29 deletions(-) create mode 100644 ref_vk/shaders/ray_primary.comp create mode 100644 ref_vk/shaders/ray_primary_hit.glsl diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index ce3fc37b..15a39078 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,19 +1,19 @@ # Real next >=E222 - - [x] refcount meatpipe created images - - [ ] previous frame resources reference - - [ ] what if new meatpipe has different image format for a creatable image? - - [ ] rake yuri primary ray - - [ ] resource management refactoring: - - [ ] register existing resources (tlas, buffers, temp images, ...) in their producers - - [ ] resource automatic resolution: prducing, barriers, etc - - [ ] resource destruction - - [ ] ? resource object: name, metadata(type, etc.), producer, status (ready, barriers, etc) +- [x] refcount meatpipe created images +- [x] rake yuri primary ray +- [ ] previous frame resources reference +- [ ] what if new meatpipe has different image format for a creatable image? # Programmable render - [ ] implicit dependency tracking. pass defines: - [ ] imports: list of things it needs - [ ] exports: list of things it produces. those get created and registered with this pass as a producer +- [ ] resource management refactoring: + - [ ] register existing resources (tlas, buffers, temp images, ...) in their producers + - [ ] resource automatic resolution: prducing, barriers, etc + - [ ] resource destruction +- [ ] ? resource object: name, metadata(type, etc.), producer, status (ready, barriers, etc) # Parallel frames sync - [ ] light_grid_buffer (+ small lights_buffer): diff --git a/ref_vk/shaders/ray_common_alphatest.rahit b/ref_vk/shaders/ray_common_alphatest.rahit index a864c5e1..c6f92d18 100644 --- a/ref_vk/shaders/ray_common_alphatest.rahit +++ b/ref_vk/shaders/ray_common_alphatest.rahit @@ -1,6 +1,7 @@ #version 460 core #extension GL_EXT_nonuniform_qualifier : enable #extension GL_GOOGLE_include_directive : require +#extension GL_EXT_ray_tracing: require #include "ray_primary_common.glsl" #include "ray_kusochki.glsl" @@ -19,7 +20,7 @@ hitAttributeEXT vec2 bary; const float alpha_mask_threshold = .1f; void main() { - const Geometry geom = readHitGeometry(); + const Geometry geom = readHitGeometry(bary, ubo.ubo.ray_cone_width); const uint tex_index = getKusok(geom.kusok_index).tex_base_color; const vec4 texture_color = texture(textures[nonuniformEXT(tex_index)], geom.uv); diff --git a/ref_vk/shaders/ray_primary.comp b/ref_vk/shaders/ray_primary.comp new file mode 100644 index 00000000..c747cf3c --- /dev/null +++ b/ref_vk/shaders/ray_primary.comp @@ -0,0 +1,58 @@ +#version 460 core +#extension GL_GOOGLE_include_directive : require +#extension GL_EXT_nonuniform_qualifier : enable +#extension GL_EXT_ray_query: require + +#define RAY_QUERY +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +#include "ray_primary_common.glsl" +#include "ray_primary_hit.glsl" + +#define X(index, name, format) layout(set=0,binding=index,format) uniform writeonly image2D out_##name; +RAY_PRIMARY_OUTPUTS(X) +#undef X + +layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; + +void main() { + const ivec2 pix = ivec2(gl_GlobalInvocationID); + const ivec2 res = ivec2(imageSize(out_position_t)); + if (any(greaterThanEqual(pix, res))) { + return; + } + const vec2 uv = (gl_GlobalInvocationID.xy + .5) / res * 2. - 1.; + + // FIXME start on a near plane + const vec3 origin = (ubo.ubo.inv_view * vec4(0, 0, 0, 1)).xyz; + const vec4 target = ubo.ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); + const vec3 direction = normalize((ubo.ubo.inv_view * vec4(target.xyz, 0)).xyz); + + RayPayloadPrimary payload; + payload.hit_t = vec4(0.); + payload.base_color_a = vec4(0.); + payload.normals_gs = vec4(0.); + payload.material_rmxx = vec4(0.); + payload.emissive = vec4(0.); + + rayQueryEXT rq; + const uint flags = 0 + | gl_RayFlagsCullFrontFacingTrianglesEXT + //| gl_RayFlagsOpaqueEXT + //| gl_RayFlagsTerminateOnFirstHitEXT + //| gl_RayFlagsSkipClosestHitShaderEXT + ; + const float L = 10000.; // TODO Why 10k? + rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE, origin, 0., direction, L); + // TODO alpha test + while (rayQueryProceedEXT(rq)) { } + if (rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT) { + primaryRayHit(rq, payload); + } + + imageStore(out_position_t, pix, payload.hit_t); + imageStore(out_base_color_a, pix, payload.base_color_a); + imageStore(out_normals_gs, pix, payload.normals_gs); + imageStore(out_material_rmxx, pix, payload.material_rmxx); + imageStore(out_emissive, pix, payload.emissive); +} diff --git a/ref_vk/shaders/ray_primary.rchit b/ref_vk/shaders/ray_primary.rchit index c2073d47..3d0a02a5 100644 --- a/ref_vk/shaders/ray_primary.rchit +++ b/ref_vk/shaders/ray_primary.rchit @@ -1,13 +1,11 @@ #version 460 core #extension GL_GOOGLE_include_directive : require #extension GL_EXT_nonuniform_qualifier : enable +#extension GL_EXT_ray_tracing: require #include "utils.glsl" - #include "ray_primary_common.glsl" - #include "ray_kusochki.glsl" - #include "color_spaces.glsl" layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; @@ -24,7 +22,7 @@ vec4 sampleTexture(uint tex_index, vec2 uv, vec4 uv_lods) { } void main() { - Geometry geom = readHitGeometry(); + Geometry geom = readHitGeometry(bary, ubo.ubo.ray_cone_width); payload.hit_t = vec4(geom.pos, gl_HitTEXT); diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index 117268e1..ba7fdbcf 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -1,5 +1,6 @@ #version 460 core #extension GL_GOOGLE_include_directive : require +#extension GL_EXT_ray_tracing: require #include "ray_primary_common.glsl" diff --git a/ref_vk/shaders/ray_primary_common.glsl b/ref_vk/shaders/ray_primary_common.glsl index fd593ee0..9662d825 100644 --- a/ref_vk/shaders/ray_primary_common.glsl +++ b/ref_vk/shaders/ray_primary_common.glsl @@ -1,4 +1,5 @@ -#extension GL_EXT_ray_tracing: require +#ifndef RAY_PRIMARY_COMMON_GLSL_INCLUDED +#define RAY_PRIMARY_COMMON_GLSL_INCLUDED #define GLSL #include "ray_interop.h" @@ -13,3 +14,5 @@ struct RayPayloadPrimary { }; #define PAYLOAD_LOCATION_PRIMARY 0 + +#endif //ifndef RAY_PRIMARY_COMMON_GLSL_INCLUDED diff --git a/ref_vk/shaders/ray_primary_hit.glsl b/ref_vk/shaders/ray_primary_hit.glsl new file mode 100644 index 00000000..fd707a84 --- /dev/null +++ b/ref_vk/shaders/ray_primary_hit.glsl @@ -0,0 +1,61 @@ +#ifndef RAY_PRIMARY_HIT_GLSL_INCLUDED +#define RAY_PRIMARY_HIT_GLSL_INCLUDED +#extension GL_EXT_nonuniform_qualifier : enable + +#include "utils.glsl" +#include "ray_primary_common.glsl" +#include "ray_kusochki.glsl" +#include "rt_geometry.glsl" +#include "color_spaces.glsl" + +layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; +layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; } ubo; +layout(set = 0, binding = 7) uniform samplerCube skybox; + +vec4 sampleTexture(uint tex_index, vec2 uv, vec4 uv_lods) { + return textureGrad(textures[nonuniformEXT(tex_index)], uv, uv_lods.xy, uv_lods.zw); +} + +void primaryRayHit(rayQueryEXT rq, inout RayPayloadPrimary payload) { + Geometry geom = readHitGeometry(rq, ubo.ubo.ray_cone_width, rayQueryGetIntersectionBarycentricsEXT(rq, true)); + const float hitT = rayQueryGetIntersectionTEXT(rq, true); //gl_HitTEXT; + const vec3 rayDirection = rayQueryGetWorldRayDirectionEXT(rq); //gl_WorldRayDirectionEXT + payload.hit_t = vec4(geom.pos, hitT); + + const Kusok kusok = getKusok(geom.kusok_index); + const uint tex_base_color = kusok.tex_base_color; + + if ((tex_base_color & KUSOK_MATERIAL_FLAG_SKYBOX) != 0) { + payload.emissive.rgb = SRGBtoLINEAR(texture(skybox, rayDirection).rgb); + return; + } else { + payload.base_color_a = sampleTexture(tex_base_color, geom.uv, geom.uv_lods) * kusok.color; + payload.material_rmxx.r = (kusok.tex_roughness > 0) ? sampleTexture(kusok.tex_roughness, geom.uv, geom.uv_lods).r : kusok.roughness; + payload.material_rmxx.g = (kusok.tex_metalness > 0) ? sampleTexture(kusok.tex_metalness, geom.uv, geom.uv_lods).r : kusok.metalness; + + const uint tex_normal = kusok.tex_normalmap; + vec3 T = geom.tangent; + if (tex_normal > 0 && dot(T,T) > .5) { + T = normalize(T - dot(T, geom.normal_shading) * geom.normal_shading); + const vec3 B = normalize(cross(geom.normal_shading, T)); + const mat3 TBN = mat3(T, B, geom.normal_shading); + const vec3 tnorm = sampleTexture(tex_normal, geom.uv, geom.uv_lods).xyz * 2. - 1.; // TODO is this sampling correct for normal data? + geom.normal_shading = normalize(TBN * tnorm); + } + } + + payload.normals_gs.xy = normalEncode(geom.normal_geometry); + payload.normals_gs.zw = normalEncode(geom.normal_shading); + +#if 1 + // Real correct emissive color + //payload.emissive.rgb = kusok.emissive; + payload.emissive.rgb = clamp(kusok.emissive / (1.0/3.0) / 25, 0, 1.5) * SRGBtoLINEAR(payload.base_color_a.rgb); +#else + // Fake texture color + if (any(greaterThan(kusok.emissive, vec3(0.)))) + payload.emissive.rgb = payload.base_color_a.rgb; +#endif +} + +#endif // ifndef RAY_PRIMARY_HIT_GLSL_INCLUDED diff --git a/ref_vk/shaders/rt.json b/ref_vk/shaders/rt.json index b8e10225..23652e30 100644 --- a/ref_vk/shaders/rt.json +++ b/ref_vk/shaders/rt.json @@ -1,6 +1,6 @@ { - "primary_ray": { + /* "rgen": "ray_primary", "miss": [ "ray_primary" @@ -9,6 +9,8 @@ {"closest": "ray_primary"}, {"closest": "ray_primary", "any": "ray_common_alphatest"} ] + */ + "comp": "ray_primary" }, // "light_direct": { // "template": true, @@ -35,5 +37,6 @@ }, "denoiser": { "comp": "denoiser" - } + }, + //"RESOURCES": { "position_t_prev": { "previous_frame": "position_t" }, }, } diff --git a/ref_vk/shaders/rt_geometry.glsl b/ref_vk/shaders/rt_geometry.glsl index f232035e..a84f3e12 100644 --- a/ref_vk/shaders/rt_geometry.glsl +++ b/ref_vk/shaders/rt_geometry.glsl @@ -1,3 +1,5 @@ +#ifndef RT_GEOMETRY_GLSL_INCLUDED +#define RT_GEOMETRY_GLSL_INCLUDED #include "utils.glsl" // Taken from Journal of Computer Graphics Techniques, Vol. 10, No. 1, 2021. @@ -51,22 +53,38 @@ struct Geometry { int kusok_index; }; -Geometry readHitGeometry() { +#ifdef RAY_QUERY +Geometry readHitGeometry(rayQueryEXT rq, float ray_cone_width, vec2 bary) { + const int instance_kusochki_offset = rayQueryGetIntersectionInstanceCustomIndexEXT(rq, true); + const int geometry_index = rayQueryGetIntersectionGeometryIndexEXT(rq, true); + const int primitive_index = rayQueryGetIntersectionPrimitiveIndexEXT(rq, true); + const mat4x3 objectToWorld = rayQueryGetIntersectionObjectToWorldEXT(rq, true); + const vec3 ray_direction = rayQueryGetWorldRayDirectionEXT(rq); + const float hit_t = rayQueryGetIntersectionTEXT(rq, true); +#else +Geometry readHitGeometry(vec2 bary, float ray_cone_width) { + const int instance_kusochki_offset = gl_InstanceCustomIndexEXT; + const int geometry_index = gl_GeometryIndexEXT; + const int primitive_index = gl_PrimitiveID; + const mat4x3 objectToWorld = gl_ObjectToWorldEXT; + const vec3 ray_direction = gl_WorldRayDirectionEXT; + const float hit_t = gl_HitTEXT; +#endif + Geometry geom; - const int instance_kusochki_offset = gl_InstanceCustomIndexEXT; - geom.kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT; + geom.kusok_index = instance_kusochki_offset + geometry_index; const Kusok kusok = getKusok(geom.kusok_index); - const uint first_index_offset = kusok.index_offset + gl_PrimitiveID * 3; + const uint first_index_offset = kusok.index_offset + primitive_index * 3; const uint vi1 = uint(getIndex(first_index_offset+0)) + kusok.vertex_offset; const uint vi2 = uint(getIndex(first_index_offset+1)) + kusok.vertex_offset; const uint vi3 = uint(getIndex(first_index_offset+2)) + kusok.vertex_offset; const vec3 pos[3] = { - gl_ObjectToWorldEXT * vec4(getVertex(vi1).pos, 1.f), - gl_ObjectToWorldEXT * vec4(getVertex(vi2).pos, 1.f), - gl_ObjectToWorldEXT * vec4(getVertex(vi3).pos, 1.f), + objectToWorld * vec4(getVertex(vi1).pos, 1.f), + objectToWorld * vec4(getVertex(vi2).pos, 1.f), + objectToWorld * vec4(getVertex(vi3).pos, 1.f), }; const vec2 uvs[3] = { @@ -83,7 +101,7 @@ Geometry readHitGeometry() { geom.normal_geometry = normalize(cross(pos[2]-pos[0], pos[1]-pos[0])); // NOTE: only support rotations, for arbitrary transform would need to do transpose(inverse(mat3(gl_ObjectToWorldEXT))) - const mat3 normalTransform = mat3(gl_ObjectToWorldEXT); + const mat3 normalTransform = mat3(objectToWorld); // mat3(gl_ObjectToWorldEXT); geom.normal_shading = normalize(normalTransform * baryMix( getVertex(vi1).normal, getVertex(vi2).normal, @@ -95,7 +113,8 @@ Geometry readHitGeometry() { getVertex(vi3).tangent, bary)); - geom.uv_lods = computeAnisotropicEllipseAxes(geom.pos, geom.normal_geometry, gl_WorldRayDirectionEXT, ubo.ubo.ray_cone_width * gl_HitTEXT, pos, uvs, geom.uv); + geom.uv_lods = computeAnisotropicEllipseAxes(geom.pos, geom.normal_geometry, ray_direction, ray_cone_width * hit_t, pos, uvs, geom.uv); return geom; } +#endif // RT_GEOMETRY_GLSL_INCLUDED diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 85b42019..7f1bba11 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -224,7 +224,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR | VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, + VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR | VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } @@ -253,7 +253,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* } }; vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, - VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, + VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } @@ -270,7 +270,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* }}; vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, + VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } From 308e0962a341f02020cfa6dbee4d1675d3d25068 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sun, 22 Jan 2023 15:11:27 -0800 Subject: [PATCH 516/548] update todo --- ref_vk/TODO.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index 15a39078..9eecd7d2 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,7 +1,5 @@ # Real next ->=E222 -- [x] refcount meatpipe created images -- [x] rake yuri primary ray +>=E223 - [ ] previous frame resources reference - [ ] what if new meatpipe has different image format for a creatable image? @@ -496,3 +494,7 @@ - [x] automatic creation of resources - [x] images - [-] buffers -- no immediate need for that + +# 2023-01-22 E222 +- [x] refcount meatpipe created images +- [x] rake yuri primary ray From 858e3eed5538fe5e141bfc4f9a0a0ed3714c4c00 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 28 Jan 2023 12:55:53 -0800 Subject: [PATCH 517/548] rt/seba: export prev_ frame image resource index --- ref_vk/TODO.md | 7 ++++++ ref_vk/sebastian.py | 53 ++++++++++++++++++++++++++++++++++++-------- ref_vk/vk_meatpipe.c | 2 ++ ref_vk/vk_meatpipe.h | 3 +++ 4 files changed, 56 insertions(+), 9 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index 9eecd7d2..12e42021 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,6 +1,13 @@ # Real next >=E223 - [ ] previous frame resources reference + - specification: + - [x] I: prev_ -> resource flag + pair index + - [ ] II: new section in json + - internals: + - [ ] I: create a new image for prev_, track its source; swap them each frame + - [ ] II: create tightly coupled image pair[2], read from [frame%2] write to [frame%2+1] + - [ ] III: like (I) but with more general resource management: i.e. resource object for prev_ points to its source - [ ] what if new meatpipe has different image format for a creatable image? # Programmable render diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index a7852c1a..c335a706 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -405,6 +405,9 @@ class NameIndex: self.__name_to_index[name] = index return index + def __iter__(self): + return iter(self.__all) + def serialize(self, out): out.writeArray(self.__all) @@ -412,8 +415,9 @@ class NameIndex: class Resources: def __init__(self): self.__storage = NameIndex() + self.__map = None - def getIndex(self, name, node): + def getIndex(self, name, node, dependency = None): index = self.__storage.getIndex(name) if index >= 0: @@ -421,25 +425,54 @@ class Resources: res.checkSameType(node) return index - return self.__storage.put(name, self.Resource(name, node)) + return self.__storage.put(name, self.Resource(name, node, dependency)) + + def getMappedIndex(self, index): + return self.__map.get(index, index) + + def __sortDependencies(self): + assert(not self.__map) + self.__map = dict() + + for i, r in enumerate(self.__storage): + dep = r.dependency + if not dep: + continue + + assert(dep != i) + if dep < i: + continue + + assert(i not in self.__map) + assert(dep not in self.__map) + + self.__map[i] = dep + self.__map[dep] = i + r.dependency = i def serialize(self, out): + self.__sortDependencies() self.__storage.serialize(out) class Resource: - def __init__(self, name, node): + def __init__(self, name, node, dependency = None): self.__name = name - self.__type = node.getType() - - #TODO: count, etc + self.__type = node.getType() if node else None + self.dependency = dependency def checkSameType(self, node): + if not self.__type: + self.__type = node.getType() + return + if self.__type != node.getType(): raise Exception('Conflicting types for resource "%s": %s != %s' % (self.__name, self.__type, type)) def serialize(self, out): out.writeString(self.__name) self.__type.serialize(out) + if self.__type.is_image: + out.writeU32((self.dependency + 1) if self.dependency is not None else 0) resources = Resources() @@ -470,9 +503,11 @@ class Binding: self.descriptor_set = node.descriptor_set self.stages = 0 - resource_name = removeprefix(node.name, 'out_') - self.__resource_index = resources.getIndex(resource_name, node) + prev_name = removeprefix(node.name, 'prev_') if node.name.startswith('prev_') else None + prev_resource_index = resources.getIndex(self.prev, None) if prev_name else None + resource_name = removeprefix(node.name, 'out_') if self.write else node.name + self.__resource_index = resources.getIndex(resource_name, node, prev_resource_index) assert(self.descriptor_set >= 0) assert(self.descriptor_set < 255) @@ -508,7 +543,7 @@ class Binding: def serialize(self, out): header = (Binding.WRITE_BIT if self.write else 0) | (self.descriptor_set << 8) | self.index out.writeU32(header) - out.writeU32(self.__resource_index) + out.writeU32(resources.getMappedIndex(self.__resource_index)) out.writeU32(self.stages) class Shader: diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 0c5d4bd6..1d1ef379 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -299,6 +299,8 @@ static qboolean readResources(load_context_t *ctx) { if (is_image) { res->image_format = READ_U32("Couldn't read image format for res %d:%s", i, res->name); + res->prev_frame_index = READ_U32("Couldn't read resource %d:%s previous frame index", i, res->name); + res->prev_frame_index -= 1; } gEngine.Con_Reportf("Resource %d:%s = %08x is_image=%d image_format=%08x count=%d\n", diff --git a/ref_vk/vk_meatpipe.h b/ref_vk/vk_meatpipe.h index 37c8c87f..f5a303d0 100644 --- a/ref_vk/vk_meatpipe.h +++ b/ref_vk/vk_meatpipe.h @@ -16,6 +16,9 @@ typedef struct { union { uint32_t image_format; }; + + // If this image is supposed to be read from previous frame + int prev_frame_index; } vk_meatpipe_resource_t; struct vk_meatpipe_pass_s; From 3cb9ca0579f1ea0e07aeaf71f04f02be9a13c7e8 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 28 Jan 2023 14:50:43 -0800 Subject: [PATCH 518/548] rt: add test previous frame blur --- ref_vk/TODO.md | 19 +++++---- ref_vk/sebastian.py | 14 ++++++- ref_vk/shaders/denoiser.comp | 4 ++ ref_vk/vk_meatpipe.c | 8 +++- ref_vk/vk_rtx.c | 76 ++++++++++++++++++++++++++++++++++-- 5 files changed, 106 insertions(+), 15 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index 12e42021..f323530e 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,13 +1,5 @@ # Real next >=E223 -- [ ] previous frame resources reference - - specification: - - [x] I: prev_ -> resource flag + pair index - - [ ] II: new section in json - - internals: - - [ ] I: create a new image for prev_, track its source; swap them each frame - - [ ] II: create tightly coupled image pair[2], read from [frame%2] write to [frame%2+1] - - [ ] III: like (I) but with more general resource management: i.e. resource object for prev_ points to its source - [ ] what if new meatpipe has different image format for a creatable image? # Programmable render @@ -505,3 +497,14 @@ # 2023-01-22 E222 - [x] refcount meatpipe created images - [x] rake yuri primary ray + +# 2023-01-28 E223 +- [x] previous frame resources reference + - specification: + - [x] I: prev_ -> resource flag + pair index + - [ ] II: new section in json + - internals: + - [x] I: create a new image for prev_, track its source; swap them each frame + Result is meh: too much indirection, hard to follow, many things need manual fragile updates. + - [ ] II: create tightly coupled image pair[2], read from [frame%2] write to [frame%2+1] + - [ ] III: like (I) but with more general resource management: i.e. resource object for prev_ points to its source diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index c335a706..afcfed4e 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -495,20 +495,26 @@ class Binding: STAGE_MESH_BIT_NV = 0x00000080 STAGE_SUBPASS_SHADING_BIT_HUAWEI = 0x00004000 + # TODO same values for meatpipe.c too WRITE_BIT = 0x80000000 + CREATE_BIT = 0x40000000 def __init__(self, node): self.write = node.name.startswith('out_') + self.create = self.write self.index = node.binding self.descriptor_set = node.descriptor_set self.stages = 0 prev_name = removeprefix(node.name, 'prev_') if node.name.startswith('prev_') else None - prev_resource_index = resources.getIndex(self.prev, None) if prev_name else None + prev_resource_index = resources.getIndex(prev_name, None) if prev_name else None resource_name = removeprefix(node.name, 'out_') if self.write else node.name self.__resource_index = resources.getIndex(resource_name, node, prev_resource_index) + if prev_resource_index is not None: + self.create = True + assert(self.descriptor_set >= 0) assert(self.descriptor_set < 255) @@ -541,7 +547,11 @@ class Binding: return self.__str__() def serialize(self, out): - header = (Binding.WRITE_BIT if self.write else 0) | (self.descriptor_set << 8) | self.index + header = (self.descriptor_set << 8) | self.index + if self.write: + header |= Binding.WRITE_BIT + if self.create: + header |= Binding.CREATE_BIT out.writeU32(header) out.writeU32(resources.getMappedIndex(self.__resource_index)) out.writeU32(self.stages) diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 293e9718..8a6821fa 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -7,6 +7,7 @@ layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; layout(set = 0, binding = 0, rgba16f) uniform image2D out_dest; +layout(set = 0, binding = 9, rgba16f) uniform readonly image2D prev_dest; layout(set = 0, binding = 1, rgba8) uniform readonly image2D base_color_a; @@ -204,6 +205,9 @@ void main() { } #endif + const vec4 prev_colour = imageLoad(prev_dest, pix); + colour = mix(colour, prev_colour.rgb, vec3(.9)); + imageStore(out_dest, pix, vec4(colour, 0.)); //imageStore(out_dest, pix, imageLoad(light_poly, pix)); } diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 1d1ef379..822b37e9 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -190,7 +190,9 @@ static qboolean readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding * vk_meatpipe_resource_t *res = ctx->meatpipe.resources + res_index; #define BINDING_WRITE_BIT 0x80000000u +#define BINDING_CREATE_BIT 0x40000000u const qboolean write = !!(header & BINDING_WRITE_BIT); + const qboolean create = !!(header & BINDING_CREATE_BIT); const uint32_t descriptor_set = (header >> 8) & 0xffu; const uint32_t binding = header & 0xffu; @@ -216,7 +218,10 @@ static qboolean readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding * pass->resource_map[i] = res_index; if (write) - res->flags |= MEATPIPE_RES_WRITE | MEATPIPE_RES_CREATE; // TODO distinguish between write and create + res->flags |= MEATPIPE_RES_WRITE; + + if (create) + res->flags |= MEATPIPE_RES_CREATE; gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x res=%d type=%d write=%d\n", i, name, descriptor_set, binding, stages, res_index, res->descriptor_type, write); @@ -300,7 +305,6 @@ static qboolean readResources(load_context_t *ctx) { if (is_image) { res->image_format = READ_U32("Couldn't read image format for res %d:%s", i, res->name); res->prev_frame_index = READ_U32("Couldn't read resource %d:%s previous frame index", i, res->name); - res->prev_frame_index -= 1; } gEngine.Con_Reportf("Resource %d:%s = %08x is_image=%d image_format=%08x count=%d\n", diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 7f1bba11..abcf3f5b 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -64,6 +64,7 @@ typedef struct { vk_resource_t resource; xvk_image_t image; int refcount; + int source_index; } rt_resource_t; static struct { @@ -228,10 +229,40 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } + // Transfer previous frames before they had a chance of their resource-barrier metadata overwritten (as there's no guaranteed order for them) + for (int i = ExternalResource_COUNT; i < MAX_RESOURCES; ++i) { + rt_resource_t* const res = g_rtx.res + i; + if (!res->name[0] || !res->image.image || res->source_index <= 0) + continue; + + ASSERT(res->source_index <= COUNTOF(g_rtx.res)); + rt_resource_t *const src = g_rtx.res + res->source_index - 1; + + // Swap resources + const vk_resource_t tmp_res = res->resource; + const xvk_image_t tmp_img = res->image; + + res->resource = src->resource; + res->image = src->image; + + // TODO this is slightly incorrect, as they technically can have different resource->type values + src->resource = tmp_res; + src->image = tmp_img; + + // If there was no initial state, prepare it. (this should happen only for the first frame) + if (res->resource.write.pipelines == 0) { + // TODO is there a better way? Can image be cleared w/o explicit clear op? + R_VkImageClear( cmdbuf, res->image.image ); + res->resource.write.pipelines = VK_PIPELINE_STAGE_TRANSFER_BIT; + res->resource.write.image_layout = VK_IMAGE_LAYOUT_GENERAL; + res->resource.write.access_mask = VK_ACCESS_TRANSFER_WRITE_BIT; + } + } + // Clear intra-frame resources for (int i = ExternalResource_COUNT; i < MAX_RESOURCES; ++i) { rt_resource_t* const res = g_rtx.res + i; - if (!res->name[0] || !res->image.image) + if (!res->name[0] || !res->image.image || res->source_index > 0) continue; res->resource.read = res->resource.write = (ray_resource_state_t){0}; @@ -274,6 +305,20 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } + // Update image resource links after the prev_-related swap above + // TODO Preserve the indexes somewhere to avoid searching + for (int i = 0; i < g_rtx.mainpipe->resources_count; ++i) { + const vk_meatpipe_resource_t *mr = g_rtx.mainpipe->resources + i; + const int index = findResource(mr->name); + ASSERT(index >= 0); + ASSERT(index < MAX_RESOURCES); + rt_resource_t *const res = g_rtx.res + index; + const qboolean create = !!(mr->flags & MEATPIPE_RES_CREATE); + if (create && mr->descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) + //ASSERT(g_rtx.mainpipe_resources[i]->value.image_object == &res->image); + g_rtx.mainpipe_resources[i]->value.image_object = &res->image; + } + R_VkMeatpipePerform(g_rtx.mainpipe, cmdbuf, (vk_meatpipe_perfrom_args_t) { .frame_set_slot = args->frame_index, .width = FRAME_WIDTH, @@ -301,6 +346,8 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* }; R_VkImageBlit( cmdbuf, &blit_args ); + + g_rtx.mainpipe_out->resource.write.image_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; } DEBUG_END(cmdbuf); } @@ -360,7 +407,6 @@ static void reloadMainpipe(void) { const qboolean create = !!(mr->flags & MEATPIPE_RES_CREATE); - // FIXME no assert, just complain if (create && mr->descriptor_type != VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) { gEngine.Con_Printf(S_ERROR "Only storage image creation is supported for meatpipes\n"); goto fail; @@ -390,7 +436,9 @@ static void reloadMainpipe(void) { .layers = 1, .format = mr->image_format, .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = VK_IMAGE_USAGE_STORAGE_BIT | (output ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT : 0), + // TODO figure out how to detect this need properly. prev_dest is not defined as "output" + //.usage = VK_IMAGE_USAGE_STORAGE_BIT | (output ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT : 0), + .usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, .has_alpha = true, .is_cubemap = false, }; @@ -420,6 +468,28 @@ static void reloadMainpipe(void) { goto fail; } + // Resolve prev_ frame resources + for (int i = 0; i < newpipe->resources_count; ++i) { + const vk_meatpipe_resource_t *mr = newpipe->resources + i; + if (mr->prev_frame_index <= 0) + continue; + + ASSERT(mr->prev_frame_index < newpipe->resources_count); + + const int index = findResource(mr->name); + ASSERT(index >= 0); + + const vk_meatpipe_resource_t *pr = newpipe->resources + (mr->prev_frame_index - 1); + + const int dest_index = findResource(pr->name); + if (dest_index < 0) { + gEngine.Con_Printf(S_ERROR "Couldn't find prev_ resource/slot %s for resource %s\n", pr->name, mr->name); + goto fail; + } + + g_rtx.res[index].source_index = dest_index + 1; + } + // Loading successful // Update refcounts for (int i = 0; i < newpipe->resources_count; ++i) { From 2627cb97afca53a1048cca2a1550105d8ca607c3 Mon Sep 17 00:00:00 2001 From: LifeKILLED Date: Mon, 30 Jan 2023 05:15:41 +0400 Subject: [PATCH 519/548] rt: reviewer's fixes --- ref_vk/vk_render.c | 2 +- ref_vk/vk_render.h | 2 +- ref_vk/vk_rtx.c | 13 +++++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/ref_vk/vk_render.c b/ref_vk/vk_render.c index 7271db89..54e0e037 100644 --- a/ref_vk/vk_render.c +++ b/ref_vk/vk_render.c @@ -751,7 +751,7 @@ static struct { vk_render_geometry_t geometries[MAX_DYNAMIC_GEOMETRY]; } g_dynamic_model = {0}; -matrix4x4 *VK_RenderGetLastFrameTransform() { +matrix4x4 *VK_RenderGetLastFrameTransform( void ) { return &g_dynamic_model.model.prev_transform; } diff --git a/ref_vk/vk_render.h b/ref_vk/vk_render.h index 0bdf4f24..d6b45fe7 100644 --- a/ref_vk/vk_render.h +++ b/ref_vk/vk_render.h @@ -104,4 +104,4 @@ void VK_RenderEndRTX( VkCommandBuffer cmdbuf, VkImageView img_dst_view, VkImage void VK_Render_FIXME_Barrier( VkCommandBuffer cmdbuf ); -matrix4x4* VK_RenderGetLastFrameTransform(); +matrix4x4* VK_RenderGetLastFrameTransform( void ); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index af52dc22..87e32f33 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -81,6 +81,8 @@ static struct { qboolean reload_pipeline; qboolean reload_lighting; + + matrix4x4 prev_inv_proj, prev_inv_view; } g_rtx = {0}; static int findResource(const char *name) { @@ -156,12 +158,11 @@ static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int fr Matrix4x4_Invert_Full(view_inv, *args->view); Matrix4x4_ToArrayFloatGL(view_inv, (float*)ubo->inv_view); - // last frame matrices - static matrix4x4 prev_inv_proj, prev_inv_view; - Matrix4x4_ToArrayFloatGL(prev_inv_proj, (float*)ubo->prev_inv_proj); - Matrix4x4_ToArrayFloatGL(prev_inv_view, (float*)ubo->prev_inv_view); - Matrix4x4_Copy(prev_inv_view, view_inv); - Matrix4x4_Copy(prev_inv_proj, proj_inv); + // previous frame matrices + Matrix4x4_ToArrayFloatGL(g_rtx.prev_inv_proj, (float*)ubo->prev_inv_proj); + Matrix4x4_ToArrayFloatGL(g_rtx.prev_inv_view, (float*)ubo->prev_inv_view); + Matrix4x4_Copy(g_rtx.prev_inv_view, view_inv); + Matrix4x4_Copy(g_rtx.prev_inv_proj, proj_inv); ubo->ray_cone_width = atanf((2.0f*tanf(DEG2RAD(fov_angle_y) * 0.5f)) / (float)FRAME_HEIGHT); ubo->random_seed = (uint32_t)gEngine.COM_RandomLong(0, INT32_MAX); From 571fc16c3b743a301272a31367fb6046a05c4c9d Mon Sep 17 00:00:00 2001 From: LifeKILLED Date: Mon, 30 Jan 2023 08:53:18 +0400 Subject: [PATCH 520/548] rt denoiser: rework motion vectors, add simple temporal reprojection --- ref_vk/shaders/denoiser.comp | 48 +++++++++++++-- ref_vk/shaders/ray_interop.h | 2 +- ref_vk/shaders/ray_primary.comp | 2 +- ref_vk/shaders/ray_primary.rgen | 2 +- ref_vk/vk_brush.c | 34 +---------- ref_vk/vk_previous_frame.c | 104 ++++++++++++++++++++++++++++++++ ref_vk/vk_previous_frame.h | 10 +++ ref_vk/vk_render.c | 15 +++-- ref_vk/vk_render.h | 2 - ref_vk/vk_rtx.c | 3 + ref_vk/vk_studio.c | 52 +++------------- 11 files changed, 182 insertions(+), 92 deletions(-) create mode 100644 ref_vk/vk_previous_frame.c create mode 100644 ref_vk/vk_previous_frame.h diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 945ab73e..8875871e 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -4,6 +4,10 @@ #include "utils.glsl" #include "color_spaces.glsl" +#define GLSL +#include "ray_interop.h" +#undef GLSL + layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; layout(set = 0, binding = 0, rgba16f) uniform image2D out_dest; @@ -18,10 +22,12 @@ layout(set = 0, binding = 5, rgba16f) uniform readonly image2D light_point_specu layout(set = 0, binding = 6, rgba16f) uniform readonly image2D emissive; layout(set = 0, binding = 7, rgba32f) uniform readonly image2D position_t; -layout(set = 0, binding = 8, rgba32f) uniform readonly image2D prev_position_t; +layout(set = 0, binding = 8, rgba32f) uniform readonly image2D geometry_prev_position; layout(set = 0, binding = 9, rgba16f) uniform readonly image2D prev_dest; +layout(set = 0, binding = 10) uniform UBO { UniformBuffer ubo; } ubo; + //layout(set = 0, binding = 7, rgba32f) uniform readonly image2D position_t; //layout(set = 0, binding = 8, rgba16f) uniform readonly image2D normals_gs; @@ -212,9 +218,43 @@ void main() { // DEBUG motion vectors //colour = vec3(length(imageLoad(position_t, pix).rgb - imageLoad(prev_position_t, pix).rgb)); - const vec4 prev_colour = imageLoad(prev_dest, pix); - colour = mix(colour, prev_colour.rgb, vec3(.9)); + // TODO: need to extract reprojecting from this shader because reprojected stuff need svgf denoising pass after it + const vec3 origin = (ubo.ubo.inv_view * vec4(0., 0., 0., 1.)).xyz; + const float depth = length(origin - imageLoad(position_t, pix).xyz); + const vec3 prev_position = imageLoad(geometry_prev_position, pix).rgb; + const vec4 clip_space = inverse(ubo.ubo.prev_inv_proj) * vec4((inverse(ubo.ubo.prev_inv_view) * vec4(prev_position, 1.)).xyz, 1.); + const vec2 reproj_uv = clip_space.xy / clip_space.w; + const ivec2 reproj_pix = ivec2((reproj_uv * 0.5 + vec2(0.5)) * vec2(res)); + const vec3 prev_origin = (ubo.ubo.prev_inv_view * vec4(0., 0., 0., 1.)).xyz; + const float depth_nessesary = length(prev_position - prev_origin); + const float depth_treshold = 0.01 * clip_space.w; + float better_depth_offset = depth_treshold; + vec3 history_colour = colour; + for(int x = -1; x <=1; x++) { + for(int y = -1; y <=1; y++) { + const ivec2 p = reproj_pix + ivec2(x, y); + if (any(greaterThanEqual(p, res)) || any(lessThan(p, ivec2(0)))) { + continue; + } + const vec4 history_colour_depth = imageLoad( prev_dest, reproj_pix ); + const float history_depth = history_colour_depth.w; + const float depth_offset = abs(history_depth - depth_nessesary); + if ( depth_offset < better_depth_offset ) { + better_depth_offset = depth_offset; + history_colour = history_colour_depth.rgb; + } + } + } + if (better_depth_offset < depth_treshold) { + colour = mix(colour, history_colour, 0.8); + } - imageStore(out_dest, pix, vec4(colour, 0.)); + // DEBUG motion vectors + //colour = vec3(length(imageLoad(position_t, pix).rgb - imageLoad(geometry_prev_position, pix).rgb)); + + //const vec4 prev_colour = imageLoad(prev_dest, pix); + //colour = mix(colour, prev_colour.rgb, vec3(.9)); + + imageStore(out_dest, pix, vec4(colour, depth)); //imageStore(out_dest, pix, imageLoad(light_poly, pix)); } diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index 590e441e..549a317b 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -18,7 +18,7 @@ X(12, normals_gs, rgba16f) \ X(13, material_rmxx, rgba8) \ X(14, emissive, rgba16f) \ - X(15, prev_position_t, rgba32f) \ + X(15, geometry_prev_position, rgba32f) \ #define RAY_LIGHT_DIRECT_INPUTS(X) \ X(10, position_t, rgba32f) \ diff --git a/ref_vk/shaders/ray_primary.comp b/ref_vk/shaders/ray_primary.comp index e95c1937..e1795388 100644 --- a/ref_vk/shaders/ray_primary.comp +++ b/ref_vk/shaders/ray_primary.comp @@ -55,5 +55,5 @@ void main() { imageStore(out_normals_gs, pix, payload.normals_gs); imageStore(out_material_rmxx, pix, payload.material_rmxx); imageStore(out_emissive, pix, payload.emissive); - imageStore(out_prev_position_t, pix, payload.prev_pos_t); + imageStore(out_geometry_prev_position, pix, payload.prev_pos_t); } diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index e19eb21a..8d02453b 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -41,5 +41,5 @@ void main() { imageStore(out_normals_gs, ivec2(gl_LaunchIDEXT.xy), payload.normals_gs); imageStore(out_material_rmxx, ivec2(gl_LaunchIDEXT.xy), payload.material_rmxx); imageStore(out_emissive, ivec2(gl_LaunchIDEXT.xy), payload.emissive); - imageStore(out_prev_position_t, ivec2(gl_LaunchIDEXT.xy), payload.prev_pos_t); + imageStore(out_geometry_prev_position, ivec2(gl_LaunchIDEXT.xy), payload.prev_pos_t); } diff --git a/ref_vk/vk_brush.c b/ref_vk/vk_brush.c index cdfd59ed..903ed6e6 100644 --- a/ref_vk/vk_brush.c +++ b/ref_vk/vk_brush.c @@ -13,6 +13,7 @@ #include "vk_geometry.h" #include "vk_light.h" #include "vk_mapents.h" +#include "vk_previous_frame.h" #include "ref_params.h" #include "eiface.h" @@ -33,16 +34,6 @@ static struct { int rtable[MOD_FRAMES][MOD_FRAMES]; } g_brush; - -#define MAX_BRUSH_ENTITIES_PREV_STATES 1024 - -typedef struct { - matrix4x4 prev_model_transform; - float prev_time; -} brush_entity_prev_state_t; - -static brush_entity_prev_state_t g_brush_prev_states[MAX_BRUSH_ENTITIES_PREV_STATES]; - void VK_InitRandomTable( void ) { int tu, tv; @@ -101,10 +92,7 @@ static void EmitWaterPolys( const cl_entity_t *ent, const msurface_t *warp, qboo uint16_t *indices; r_geometry_buffer_lock_t buffer; - if (ent->index < MAX_BRUSH_ENTITIES_PREV_STATES) { - prev_time = g_brush_prev_states[ent->index].prev_time; - g_brush_prev_states[ent->index].prev_time = time; - } else gEngine.Con_Printf(S_ERROR "Brush entities previous frame states pool is overflow, increase it. Index is %s\n", ent->index ); + prev_time = R_PrevFrame_Time(ent->index); #define MAX_WATER_VERTICES 16 vk_vertex_t poly_vertices[MAX_WATER_VERTICES]; @@ -289,17 +277,9 @@ void XVK_DrawWaterSurfaces( const cl_entity_t *ent ) EmitWaterPolys( ent, surf, false ); } - int entity_id = ent->index; - - if (entity_id < MAX_BRUSH_ENTITIES_PREV_STATES) - Matrix4x4_Copy( *VK_RenderGetLastFrameTransform(), g_brush_prev_states[entity_id].prev_model_transform ); - // submit as dynamic model VK_RenderModelDynamicCommit(); - if (entity_id < MAX_BRUSH_ENTITIES_PREV_STATES) - Matrix4x4_Copy( g_brush_prev_states[entity_id].prev_model_transform, *VK_RenderGetLastFrameTransform() ); - // TODO: // - upload water geometry only once, animate in compute/vertex shader } @@ -392,18 +372,8 @@ void VK_BrushModelDraw( const cl_entity_t *ent, int render_mode, const matrix4x4 } } - int entity_id = ent->index; - if (entity_id < MAX_BRUSH_ENTITIES_PREV_STATES) { - Matrix4x4_Copy( bmodel->render_model.prev_transform, - g_brush_prev_states[entity_id].prev_model_transform ); - } else gEngine.Con_Printf(S_ERROR "Brush entities previous frame states pool is overflow, increase it. Index is %s\n", ent->index ); - bmodel->render_model.render_mode = render_mode; VK_RenderModelDraw(ent, &bmodel->render_model); - - if (entity_id >= 0 && entity_id < MAX_BRUSH_ENTITIES_PREV_STATES) { - Matrix4x4_Copy( g_brush_prev_states[entity_id].prev_model_transform, bmodel->render_model.prev_transform ); - } } static qboolean renderableSurface( const msurface_t *surf, int i ) { diff --git a/ref_vk/vk_previous_frame.c b/ref_vk/vk_previous_frame.c new file mode 100644 index 00000000..d5cc588d --- /dev/null +++ b/ref_vk/vk_previous_frame.c @@ -0,0 +1,104 @@ +#include "vk_studio.h" +#include "vk_common.h" +#include "vk_textures.h" +#include "vk_render.h" +#include "vk_geometry.h" +#include "camera.h" + +#include "xash3d_mathlib.h" +#include "const.h" +#include "r_studioint.h" +#include "triangleapi.h" +#include "studio.h" +#include "pm_local.h" +#include "cl_tent.h" +#include "pmtrace.h" +#include "protocol.h" +#include "enginefeatures.h" +#include "pm_movevars.h" + +#include +#include + +#define PREV_STATES_COUNT 1024 +#define PREV_FRAMES_COUNT 2 + +typedef struct { + matrix3x4 bones_worldtransform[MAXSTUDIOBONES]; + matrix4x4 model_transform; + float time; + int bones_update_frame_index; +} prev_state_t; + +typedef struct { + prev_state_t prev_states[PREV_FRAMES_COUNT][PREV_STATES_COUNT]; + int frame_index; + int current_frame_id; + int previous_frame_id; +} prev_states_storage_t; + +prev_states_storage_t g_prev = { 0 }; + +inline int clampIndex( int index, int array_length ) +{ + if (index < 0) + return 0; + else if (index >= array_length) + return array_length - 1; + + return index; +} + +prev_state_t* prevStateInArrayBounds( int frame_storage_id, int entity_id ) +{ + int clamped_frame_id = clampIndex( frame_storage_id, PREV_FRAMES_COUNT ); + int clamped_entity_id = clampIndex( entity_id, PREV_STATES_COUNT ); + return &g_prev.prev_states[clamped_frame_id][clamped_entity_id]; +} + +#define PREV_FRAME() prevStateInArrayBounds( g_prev.previous_frame_id, entity_id ) +#define CURRENT_FRAME() prevStateInArrayBounds( g_prev.current_frame_id, entity_id ) + +void R_PrevFrame_StartFrame( void ) +{ + g_prev.frame_index++; + g_prev.current_frame_id = g_prev.frame_index % PREV_FRAMES_COUNT; + g_prev.previous_frame_id = g_prev.frame_index - 1; +} + +void R_PrevFrame_SaveCurrentBoneTransforms( int entity_id, matrix3x4* bones_transforms ) +{ + prev_state_t *state = CURRENT_FRAME(); + + if (state->bones_update_frame_index == g_prev.frame_index) + return; // already updated for this entity + + state->bones_update_frame_index = g_prev.frame_index; + + for( int i = 0; i < MAXSTUDIOBONES; i++ ) + { + Matrix3x4_Copy(state->bones_worldtransform[i], bones_transforms[i]); + } +} + +void R_PrevFrame_SaveCurrentState( int entity_id, matrix4x4 model_transform ) +{ + prev_state_t* state = CURRENT_FRAME(); + Matrix4x4_Copy( state->model_transform, model_transform ); + state->time = gpGlobals->time; +} + +matrix3x4* R_PrevFrame_BoneTransforms( int entity_id ) +{ + return PREV_FRAME()->bones_worldtransform; +} + +void R_PrevFrame_ModelTransform( int entity_id, matrix4x4 model_matrix ) +{ + Matrix4x4_Copy(model_matrix, PREV_FRAME()->model_transform); +} + +float R_PrevFrame_Time( int entity_id ) +{ + return PREV_FRAME()->time; +} diff --git a/ref_vk/vk_previous_frame.h b/ref_vk/vk_previous_frame.h new file mode 100644 index 00000000..ce8e27f0 --- /dev/null +++ b/ref_vk/vk_previous_frame.h @@ -0,0 +1,10 @@ +#pragma once + +#include "vk_common.h" + +void R_PrevFrame_StartFrame(void); +void R_PrevFrame_SaveCurrentBoneTransforms(int entity_id, matrix3x4* bones_transforms); +void R_PrevFrame_SaveCurrentState(int entity_id, matrix4x4 model_transform); +matrix3x4* R_PrevFrame_BoneTransforms(int entity_id); +void R_PrevFrame_ModelTransform( int entity_id, matrix4x4 model_matrix ); +float R_PrevFrame_Time(int entity_id); diff --git a/ref_vk/vk_render.c b/ref_vk/vk_render.c index 54e0e037..4b3a2ebc 100644 --- a/ref_vk/vk_render.c +++ b/ref_vk/vk_render.c @@ -12,6 +12,7 @@ #include "vk_rtx.h" #include "vk_descriptor.h" #include "vk_framectl.h" // FIXME needed for dynamic models cmdbuf +#include "vk_previous_frame.h" #include "alolcator.h" #include "eiface.h" @@ -682,10 +683,16 @@ void VK_RenderModelDraw( const cl_entity_t *ent, vk_render_model_t* model ) { int vertex_offset = 0; if (g_render_state.current_frame_is_ray_traced) { + if (ent != NULL && model != NULL) { + R_PrevFrame_ModelTransform( ent->index, model->prev_transform ); + R_PrevFrame_SaveCurrentState( ent->index, g_render_state.model ); + } + else { + Matrix4x4_Copy( model->prev_transform, g_render_state.model ); + } + VK_RayFrameAddModel(model->ray_model, model, (const matrix3x4*)g_render_state.model, g_render_state.dirty_uniform_data.color, ent ? ent->curstate.rendercolor : (color24){255,255,255}); - // store current transform here before it puts to entity history - Matrix4x4_Copy( model->prev_transform, g_render_state.model ); return; } @@ -751,10 +758,6 @@ static struct { vk_render_geometry_t geometries[MAX_DYNAMIC_GEOMETRY]; } g_dynamic_model = {0}; -matrix4x4 *VK_RenderGetLastFrameTransform( void ) { - return &g_dynamic_model.model.prev_transform; -} - void VK_RenderModelDynamicBegin( int render_mode, const char *debug_name_fmt, ... ) { va_list argptr; va_start( argptr, debug_name_fmt ); diff --git a/ref_vk/vk_render.h b/ref_vk/vk_render.h index d6b45fe7..e18a3dcf 100644 --- a/ref_vk/vk_render.h +++ b/ref_vk/vk_render.h @@ -103,5 +103,3 @@ void VK_RenderEnd( VkCommandBuffer cmdbuf ); void VK_RenderEndRTX( VkCommandBuffer cmdbuf, VkImageView img_dst_view, VkImage img_dst, uint32_t w, uint32_t h ); void VK_Render_FIXME_Barrier( VkCommandBuffer cmdbuf ); - -matrix4x4* VK_RenderGetLastFrameTransform( void ); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index d4fa371a..af55e2f8 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -15,6 +15,7 @@ #include "vk_ray_internal.h" #include "vk_staging.h" #include "vk_textures.h" +#include "vk_previous_frame.h" #include "alolcator.h" @@ -137,6 +138,8 @@ void VK_RayFrameBegin( void ) { XVK_RayModel_ClearForNextFrame(); + R_PrevFrame_StartFrame(); + // TODO: move all lighting update to scene? if (g_rtx.reload_lighting) { g_rtx.reload_lighting = false; diff --git a/ref_vk/vk_studio.c b/ref_vk/vk_studio.c index 435e8c18..10042398 100644 --- a/ref_vk/vk_studio.c +++ b/ref_vk/vk_studio.c @@ -3,6 +3,7 @@ #include "vk_textures.h" #include "vk_render.h" #include "vk_geometry.h" +#include "vk_previous_frame.h" #include "camera.h" #include "xash3d_mathlib.h" @@ -125,9 +126,6 @@ static cvar_t *cl_himodels; static r_studio_interface_t *pStudioDraw; static studio_draw_state_t g_studio; // global studio state -#define MAX_ENTITIES_PREV_STATES_STUDIO 1024 -static studio_entity_prev_state_t g_entity_prev_states[MAX_ENTITIES_PREV_STATES_STUDIO]; - // global variables static qboolean m_fDoRemap; mstudiomodel_t *m_pSubModel; @@ -1072,26 +1070,6 @@ static void R_StudioSaveBones( void ) } } -/* -==================== -SaveTransformsForNextFrame - -==================== -*/ - -static void R_StudioSaveTransformsForNextFrame( matrix3x4* bones_transforms ) -{ - if (RI.currententity->index >= MAX_ENTITIES_PREV_STATES_STUDIO) - return; - - studio_entity_prev_state_t *prev_state = &g_entity_prev_states[RI.currententity->index]; - - for( int i = 0; i < m_pStudioHeader->numbones; i++ ) - { - Matrix3x4_Copy(prev_state->worldtransform[i], bones_transforms[i]); - } -} - /* ==================== StudioBuildNormalTable @@ -2158,14 +2136,6 @@ static void R_StudioDrawPoints( void ) pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); if( m_skinnum != 0 ) pskinref += (m_skinnum * m_pStudioHeader->numskinref); - studio_entity_prev_state_t *prev_frame_state = &g_entity_prev_states[RI.currententity->index]; - - if (RI.currententity->index >= MAX_ENTITIES_PREV_STATES_STUDIO) - { - gEngine.Con_Printf(S_ERROR "Studio entities previous frame states pool is overflow, increase it. Index is %s\n", RI.currententity->index); - prev_frame_state = &g_entity_prev_states[MAX_ENTITIES_PREV_STATES_STUDIO - 1]; // fallback to last element - } - if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) && m_pSubModel->blendvertinfoindex != 0 && m_pSubModel->blendnorminfoindex != 0 ) { mstudioboneweight_t *pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendvertinfoindex); @@ -2179,9 +2149,10 @@ static void R_StudioDrawPoints( void ) R_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] ); } + matrix3x4* prev_bones_transforms = R_PrevFrame_BoneTransforms( RI.currententity->index ); for( i = 0; i < m_pSubModel->numverts; i++ ) { - R_StudioComputeSkinMatrix( &pvertweight[i], prev_frame_state->worldtransform, skinMat ); + R_StudioComputeSkinMatrix( &pvertweight[i], prev_bones_transforms, skinMat ); Matrix3x4_VectorTransform( skinMat, pstudioverts[i], g_studio.prev_verts[i] ); } @@ -2191,18 +2162,19 @@ static void R_StudioDrawPoints( void ) Matrix3x4_VectorRotate( skinMat, pstudionorms[i], g_studio.norms[i] ); } - R_StudioSaveTransformsForNextFrame (g_studio.worldtransform ); + R_PrevFrame_SaveCurrentBoneTransforms( RI.currententity->index, g_studio.worldtransform ); } else { + matrix3x4* prev_bones_transforms = R_PrevFrame_BoneTransforms( RI.currententity->index ); for( i = 0; i < m_pSubModel->numverts; i++ ) { Matrix3x4_VectorTransform( g_studio.bonestransform[pvertbone[i]], pstudioverts[i], g_studio.verts[i] ); - Matrix3x4_VectorTransform( prev_frame_state->worldtransform[pvertbone[i]], pstudioverts[i], g_studio.prev_verts[i] ); + Matrix3x4_VectorTransform( prev_bones_transforms[pvertbone[i]], pstudioverts[i], g_studio.prev_verts[i] ); R_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] ); } - R_StudioSaveTransformsForNextFrame( g_studio.bonestransform ); + R_PrevFrame_SaveCurrentBoneTransforms( RI.currententity->index, g_studio.bonestransform ); } // generate shared normals for properly scaling glowing shell @@ -2328,17 +2300,7 @@ static void R_StudioDrawPoints( void ) */ } - int entity_id = RI.currententity->index; - - if (entity_id < MAX_ENTITIES_PREV_STATES_STUDIO) { - Matrix4x4_Copy( *VK_RenderGetLastFrameTransform(), g_entity_prev_states[entity_id].prev_transform ); - } else gEngine.Con_Printf(S_ERROR "Studio entities last states pool is overflow, increase it. Index is %s\n", entity_id ); - VK_RenderModelDynamicCommit(); - - if (entity_id < MAX_ENTITIES_PREV_STATES_STUDIO) { - Matrix4x4_Copy( g_entity_prev_states[entity_id].prev_transform, *VK_RenderGetLastFrameTransform() ); - } } static void R_StudioSetRemapColors( int newTop, int newBottom ) From a5abde2162b4ab36ad99d9e22778b3c24131f257 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 28 Jan 2023 12:55:53 -0800 Subject: [PATCH 521/548] rt/seba: export prev_ frame image resource index --- ref_vk/TODO.md | 7 ++++++ ref_vk/sebastian.py | 53 ++++++++++++++++++++++++++++++++++++-------- ref_vk/vk_meatpipe.c | 2 ++ ref_vk/vk_meatpipe.h | 3 +++ 4 files changed, 56 insertions(+), 9 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index 9eecd7d2..12e42021 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,6 +1,13 @@ # Real next >=E223 - [ ] previous frame resources reference + - specification: + - [x] I: prev_ -> resource flag + pair index + - [ ] II: new section in json + - internals: + - [ ] I: create a new image for prev_, track its source; swap them each frame + - [ ] II: create tightly coupled image pair[2], read from [frame%2] write to [frame%2+1] + - [ ] III: like (I) but with more general resource management: i.e. resource object for prev_ points to its source - [ ] what if new meatpipe has different image format for a creatable image? # Programmable render diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index a7852c1a..c335a706 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -405,6 +405,9 @@ class NameIndex: self.__name_to_index[name] = index return index + def __iter__(self): + return iter(self.__all) + def serialize(self, out): out.writeArray(self.__all) @@ -412,8 +415,9 @@ class NameIndex: class Resources: def __init__(self): self.__storage = NameIndex() + self.__map = None - def getIndex(self, name, node): + def getIndex(self, name, node, dependency = None): index = self.__storage.getIndex(name) if index >= 0: @@ -421,25 +425,54 @@ class Resources: res.checkSameType(node) return index - return self.__storage.put(name, self.Resource(name, node)) + return self.__storage.put(name, self.Resource(name, node, dependency)) + + def getMappedIndex(self, index): + return self.__map.get(index, index) + + def __sortDependencies(self): + assert(not self.__map) + self.__map = dict() + + for i, r in enumerate(self.__storage): + dep = r.dependency + if not dep: + continue + + assert(dep != i) + if dep < i: + continue + + assert(i not in self.__map) + assert(dep not in self.__map) + + self.__map[i] = dep + self.__map[dep] = i + r.dependency = i def serialize(self, out): + self.__sortDependencies() self.__storage.serialize(out) class Resource: - def __init__(self, name, node): + def __init__(self, name, node, dependency = None): self.__name = name - self.__type = node.getType() - - #TODO: count, etc + self.__type = node.getType() if node else None + self.dependency = dependency def checkSameType(self, node): + if not self.__type: + self.__type = node.getType() + return + if self.__type != node.getType(): raise Exception('Conflicting types for resource "%s": %s != %s' % (self.__name, self.__type, type)) def serialize(self, out): out.writeString(self.__name) self.__type.serialize(out) + if self.__type.is_image: + out.writeU32((self.dependency + 1) if self.dependency is not None else 0) resources = Resources() @@ -470,9 +503,11 @@ class Binding: self.descriptor_set = node.descriptor_set self.stages = 0 - resource_name = removeprefix(node.name, 'out_') - self.__resource_index = resources.getIndex(resource_name, node) + prev_name = removeprefix(node.name, 'prev_') if node.name.startswith('prev_') else None + prev_resource_index = resources.getIndex(self.prev, None) if prev_name else None + resource_name = removeprefix(node.name, 'out_') if self.write else node.name + self.__resource_index = resources.getIndex(resource_name, node, prev_resource_index) assert(self.descriptor_set >= 0) assert(self.descriptor_set < 255) @@ -508,7 +543,7 @@ class Binding: def serialize(self, out): header = (Binding.WRITE_BIT if self.write else 0) | (self.descriptor_set << 8) | self.index out.writeU32(header) - out.writeU32(self.__resource_index) + out.writeU32(resources.getMappedIndex(self.__resource_index)) out.writeU32(self.stages) class Shader: diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 0c5d4bd6..1d1ef379 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -299,6 +299,8 @@ static qboolean readResources(load_context_t *ctx) { if (is_image) { res->image_format = READ_U32("Couldn't read image format for res %d:%s", i, res->name); + res->prev_frame_index = READ_U32("Couldn't read resource %d:%s previous frame index", i, res->name); + res->prev_frame_index -= 1; } gEngine.Con_Reportf("Resource %d:%s = %08x is_image=%d image_format=%08x count=%d\n", diff --git a/ref_vk/vk_meatpipe.h b/ref_vk/vk_meatpipe.h index 37c8c87f..f5a303d0 100644 --- a/ref_vk/vk_meatpipe.h +++ b/ref_vk/vk_meatpipe.h @@ -16,6 +16,9 @@ typedef struct { union { uint32_t image_format; }; + + // If this image is supposed to be read from previous frame + int prev_frame_index; } vk_meatpipe_resource_t; struct vk_meatpipe_pass_s; From 3a879344155e4cb14e0908d55e1145d388b32e74 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 28 Jan 2023 14:50:43 -0800 Subject: [PATCH 522/548] rt: add test previous frame blur --- ref_vk/TODO.md | 19 +++++---- ref_vk/sebastian.py | 14 ++++++- ref_vk/shaders/denoiser.comp | 1 + ref_vk/vk_meatpipe.c | 8 +++- ref_vk/vk_rtx.c | 76 ++++++++++++++++++++++++++++++++++-- 5 files changed, 103 insertions(+), 15 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index 12e42021..f323530e 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,13 +1,5 @@ # Real next >=E223 -- [ ] previous frame resources reference - - specification: - - [x] I: prev_ -> resource flag + pair index - - [ ] II: new section in json - - internals: - - [ ] I: create a new image for prev_, track its source; swap them each frame - - [ ] II: create tightly coupled image pair[2], read from [frame%2] write to [frame%2+1] - - [ ] III: like (I) but with more general resource management: i.e. resource object for prev_ points to its source - [ ] what if new meatpipe has different image format for a creatable image? # Programmable render @@ -505,3 +497,14 @@ # 2023-01-22 E222 - [x] refcount meatpipe created images - [x] rake yuri primary ray + +# 2023-01-28 E223 +- [x] previous frame resources reference + - specification: + - [x] I: prev_ -> resource flag + pair index + - [ ] II: new section in json + - internals: + - [x] I: create a new image for prev_, track its source; swap them each frame + Result is meh: too much indirection, hard to follow, many things need manual fragile updates. + - [ ] II: create tightly coupled image pair[2], read from [frame%2] write to [frame%2+1] + - [ ] III: like (I) but with more general resource management: i.e. resource object for prev_ points to its source diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index c335a706..afcfed4e 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -495,20 +495,26 @@ class Binding: STAGE_MESH_BIT_NV = 0x00000080 STAGE_SUBPASS_SHADING_BIT_HUAWEI = 0x00004000 + # TODO same values for meatpipe.c too WRITE_BIT = 0x80000000 + CREATE_BIT = 0x40000000 def __init__(self, node): self.write = node.name.startswith('out_') + self.create = self.write self.index = node.binding self.descriptor_set = node.descriptor_set self.stages = 0 prev_name = removeprefix(node.name, 'prev_') if node.name.startswith('prev_') else None - prev_resource_index = resources.getIndex(self.prev, None) if prev_name else None + prev_resource_index = resources.getIndex(prev_name, None) if prev_name else None resource_name = removeprefix(node.name, 'out_') if self.write else node.name self.__resource_index = resources.getIndex(resource_name, node, prev_resource_index) + if prev_resource_index is not None: + self.create = True + assert(self.descriptor_set >= 0) assert(self.descriptor_set < 255) @@ -541,7 +547,11 @@ class Binding: return self.__str__() def serialize(self, out): - header = (Binding.WRITE_BIT if self.write else 0) | (self.descriptor_set << 8) | self.index + header = (self.descriptor_set << 8) | self.index + if self.write: + header |= Binding.WRITE_BIT + if self.create: + header |= Binding.CREATE_BIT out.writeU32(header) out.writeU32(resources.getMappedIndex(self.__resource_index)) out.writeU32(self.stages) diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 59a789a9..c8c764dd 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -7,6 +7,7 @@ layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; layout(set = 0, binding = 0, rgba16f) uniform image2D out_dest; +layout(set = 0, binding = 9, rgba16f) uniform readonly image2D prev_dest; layout(set = 0, binding = 1, rgba8) uniform readonly image2D base_color_a; diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 1d1ef379..822b37e9 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -190,7 +190,9 @@ static qboolean readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding * vk_meatpipe_resource_t *res = ctx->meatpipe.resources + res_index; #define BINDING_WRITE_BIT 0x80000000u +#define BINDING_CREATE_BIT 0x40000000u const qboolean write = !!(header & BINDING_WRITE_BIT); + const qboolean create = !!(header & BINDING_CREATE_BIT); const uint32_t descriptor_set = (header >> 8) & 0xffu; const uint32_t binding = header & 0xffu; @@ -216,7 +218,10 @@ static qboolean readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding * pass->resource_map[i] = res_index; if (write) - res->flags |= MEATPIPE_RES_WRITE | MEATPIPE_RES_CREATE; // TODO distinguish between write and create + res->flags |= MEATPIPE_RES_WRITE; + + if (create) + res->flags |= MEATPIPE_RES_CREATE; gEngine.Con_Reportf("Binding %d: %s ds=%d b=%d s=%08x res=%d type=%d write=%d\n", i, name, descriptor_set, binding, stages, res_index, res->descriptor_type, write); @@ -300,7 +305,6 @@ static qboolean readResources(load_context_t *ctx) { if (is_image) { res->image_format = READ_U32("Couldn't read image format for res %d:%s", i, res->name); res->prev_frame_index = READ_U32("Couldn't read resource %d:%s previous frame index", i, res->name); - res->prev_frame_index -= 1; } gEngine.Con_Reportf("Resource %d:%s = %08x is_image=%d image_format=%08x count=%d\n", diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 87e32f33..d4fa371a 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -64,6 +64,7 @@ typedef struct { vk_resource_t resource; xvk_image_t image; int refcount; + int source_index; } rt_resource_t; static struct { @@ -236,10 +237,40 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } + // Transfer previous frames before they had a chance of their resource-barrier metadata overwritten (as there's no guaranteed order for them) + for (int i = ExternalResource_COUNT; i < MAX_RESOURCES; ++i) { + rt_resource_t* const res = g_rtx.res + i; + if (!res->name[0] || !res->image.image || res->source_index <= 0) + continue; + + ASSERT(res->source_index <= COUNTOF(g_rtx.res)); + rt_resource_t *const src = g_rtx.res + res->source_index - 1; + + // Swap resources + const vk_resource_t tmp_res = res->resource; + const xvk_image_t tmp_img = res->image; + + res->resource = src->resource; + res->image = src->image; + + // TODO this is slightly incorrect, as they technically can have different resource->type values + src->resource = tmp_res; + src->image = tmp_img; + + // If there was no initial state, prepare it. (this should happen only for the first frame) + if (res->resource.write.pipelines == 0) { + // TODO is there a better way? Can image be cleared w/o explicit clear op? + R_VkImageClear( cmdbuf, res->image.image ); + res->resource.write.pipelines = VK_PIPELINE_STAGE_TRANSFER_BIT; + res->resource.write.image_layout = VK_IMAGE_LAYOUT_GENERAL; + res->resource.write.access_mask = VK_ACCESS_TRANSFER_WRITE_BIT; + } + } + // Clear intra-frame resources for (int i = ExternalResource_COUNT; i < MAX_RESOURCES; ++i) { rt_resource_t* const res = g_rtx.res + i; - if (!res->name[0] || !res->image.image) + if (!res->name[0] || !res->image.image || res->source_index > 0) continue; res->resource.read = res->resource.write = (ray_resource_state_t){0}; @@ -282,6 +313,20 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* 0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL); } + // Update image resource links after the prev_-related swap above + // TODO Preserve the indexes somewhere to avoid searching + for (int i = 0; i < g_rtx.mainpipe->resources_count; ++i) { + const vk_meatpipe_resource_t *mr = g_rtx.mainpipe->resources + i; + const int index = findResource(mr->name); + ASSERT(index >= 0); + ASSERT(index < MAX_RESOURCES); + rt_resource_t *const res = g_rtx.res + index; + const qboolean create = !!(mr->flags & MEATPIPE_RES_CREATE); + if (create && mr->descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) + //ASSERT(g_rtx.mainpipe_resources[i]->value.image_object == &res->image); + g_rtx.mainpipe_resources[i]->value.image_object = &res->image; + } + R_VkMeatpipePerform(g_rtx.mainpipe, cmdbuf, (vk_meatpipe_perfrom_args_t) { .frame_set_slot = args->frame_index, .width = FRAME_WIDTH, @@ -309,6 +354,8 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* }; R_VkImageBlit( cmdbuf, &blit_args ); + + g_rtx.mainpipe_out->resource.write.image_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; } DEBUG_END(cmdbuf); } @@ -368,7 +415,6 @@ static void reloadMainpipe(void) { const qboolean create = !!(mr->flags & MEATPIPE_RES_CREATE); - // FIXME no assert, just complain if (create && mr->descriptor_type != VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) { gEngine.Con_Printf(S_ERROR "Only storage image creation is supported for meatpipes\n"); goto fail; @@ -398,7 +444,9 @@ static void reloadMainpipe(void) { .layers = 1, .format = mr->image_format, .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = VK_IMAGE_USAGE_STORAGE_BIT | (output ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT : 0), + // TODO figure out how to detect this need properly. prev_dest is not defined as "output" + //.usage = VK_IMAGE_USAGE_STORAGE_BIT | (output ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT : 0), + .usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, .has_alpha = true, .is_cubemap = false, }; @@ -428,6 +476,28 @@ static void reloadMainpipe(void) { goto fail; } + // Resolve prev_ frame resources + for (int i = 0; i < newpipe->resources_count; ++i) { + const vk_meatpipe_resource_t *mr = newpipe->resources + i; + if (mr->prev_frame_index <= 0) + continue; + + ASSERT(mr->prev_frame_index < newpipe->resources_count); + + const int index = findResource(mr->name); + ASSERT(index >= 0); + + const vk_meatpipe_resource_t *pr = newpipe->resources + (mr->prev_frame_index - 1); + + const int dest_index = findResource(pr->name); + if (dest_index < 0) { + gEngine.Con_Printf(S_ERROR "Couldn't find prev_ resource/slot %s for resource %s\n", pr->name, mr->name); + goto fail; + } + + g_rtx.res[index].source_index = dest_index + 1; + } + // Loading successful // Update refcounts for (int i = 0; i < newpipe->resources_count; ++i) { From 164259d6b176888d2e80490b96559c82c39b9215 Mon Sep 17 00:00:00 2001 From: LifeKILLED Date: Mon, 30 Jan 2023 08:53:18 +0400 Subject: [PATCH 523/548] rt denoiser: rework motion vectors, add simple temporal reprojection --- ref_vk/shaders/denoiser.comp | 47 ++++++++++++++- ref_vk/shaders/ray_interop.h | 2 +- ref_vk/shaders/ray_primary.comp | 2 +- ref_vk/shaders/ray_primary.rgen | 2 +- ref_vk/vk_brush.c | 34 +---------- ref_vk/vk_previous_frame.c | 104 ++++++++++++++++++++++++++++++++ ref_vk/vk_previous_frame.h | 10 +++ ref_vk/vk_render.c | 15 +++-- ref_vk/vk_render.h | 2 - ref_vk/vk_rtx.c | 3 + ref_vk/vk_studio.c | 52 +++------------- 11 files changed, 183 insertions(+), 90 deletions(-) create mode 100644 ref_vk/vk_previous_frame.c create mode 100644 ref_vk/vk_previous_frame.h diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index c8c764dd..eb1b6202 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -4,6 +4,10 @@ #include "utils.glsl" #include "color_spaces.glsl" +#define GLSL +#include "ray_interop.h" +#undef GLSL + layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; layout(set = 0, binding = 0, rgba16f) uniform image2D out_dest; @@ -19,7 +23,9 @@ layout(set = 0, binding = 5, rgba16f) uniform readonly image2D light_point_specu layout(set = 0, binding = 6, rgba16f) uniform readonly image2D emissive; layout(set = 0, binding = 7, rgba32f) uniform readonly image2D position_t; -layout(set = 0, binding = 8, rgba32f) uniform readonly image2D prev_position_t; +layout(set = 0, binding = 8, rgba32f) uniform readonly image2D geometry_prev_position; + +layout(set = 0, binding = 10) uniform UBO { UniformBuffer ubo; } ubo; //layout(set = 0, binding = 7, rgba32f) uniform readonly image2D position_t; //layout(set = 0, binding = 8, rgba16f) uniform readonly image2D normals_gs; @@ -211,6 +217,43 @@ void main() { // DEBUG motion vectors //colour = vec3(length(imageLoad(position_t, pix).rgb - imageLoad(prev_position_t, pix).rgb)); - imageStore(out_dest, pix, vec4(colour, 0.)); + // TODO: need to extract reprojecting from this shader because reprojected stuff need svgf denoising pass after it + const vec3 origin = (ubo.ubo.inv_view * vec4(0., 0., 0., 1.)).xyz; + const float depth = length(origin - imageLoad(position_t, pix).xyz); + const vec3 prev_position = imageLoad(geometry_prev_position, pix).rgb; + const vec4 clip_space = inverse(ubo.ubo.prev_inv_proj) * vec4((inverse(ubo.ubo.prev_inv_view) * vec4(prev_position, 1.)).xyz, 1.); + const vec2 reproj_uv = clip_space.xy / clip_space.w; + const ivec2 reproj_pix = ivec2((reproj_uv * 0.5 + vec2(0.5)) * vec2(res)); + const vec3 prev_origin = (ubo.ubo.prev_inv_view * vec4(0., 0., 0., 1.)).xyz; + const float depth_nessesary = length(prev_position - prev_origin); + const float depth_treshold = 0.01 * clip_space.w; + float better_depth_offset = depth_treshold; + vec3 history_colour = colour; + for(int x = -1; x <=1; x++) { + for(int y = -1; y <=1; y++) { + const ivec2 p = reproj_pix + ivec2(x, y); + if (any(greaterThanEqual(p, res)) || any(lessThan(p, ivec2(0)))) { + continue; + } + const vec4 history_colour_depth = imageLoad( prev_dest, reproj_pix ); + const float history_depth = history_colour_depth.w; + const float depth_offset = abs(history_depth - depth_nessesary); + if ( depth_offset < better_depth_offset ) { + better_depth_offset = depth_offset; + history_colour = history_colour_depth.rgb; + } + } + } + if (better_depth_offset < depth_treshold) { + colour = mix(colour, history_colour, 0.8); + } + + // DEBUG motion vectors + //colour = vec3(length(imageLoad(position_t, pix).rgb - imageLoad(geometry_prev_position, pix).rgb)); + + //const vec4 prev_colour = imageLoad(prev_dest, pix); + //colour = mix(colour, prev_colour.rgb, vec3(.9)); + + imageStore(out_dest, pix, vec4(colour, depth)); //imageStore(out_dest, pix, imageLoad(light_poly, pix)); } diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index 590e441e..549a317b 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -18,7 +18,7 @@ X(12, normals_gs, rgba16f) \ X(13, material_rmxx, rgba8) \ X(14, emissive, rgba16f) \ - X(15, prev_position_t, rgba32f) \ + X(15, geometry_prev_position, rgba32f) \ #define RAY_LIGHT_DIRECT_INPUTS(X) \ X(10, position_t, rgba32f) \ diff --git a/ref_vk/shaders/ray_primary.comp b/ref_vk/shaders/ray_primary.comp index e95c1937..e1795388 100644 --- a/ref_vk/shaders/ray_primary.comp +++ b/ref_vk/shaders/ray_primary.comp @@ -55,5 +55,5 @@ void main() { imageStore(out_normals_gs, pix, payload.normals_gs); imageStore(out_material_rmxx, pix, payload.material_rmxx); imageStore(out_emissive, pix, payload.emissive); - imageStore(out_prev_position_t, pix, payload.prev_pos_t); + imageStore(out_geometry_prev_position, pix, payload.prev_pos_t); } diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index e19eb21a..8d02453b 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -41,5 +41,5 @@ void main() { imageStore(out_normals_gs, ivec2(gl_LaunchIDEXT.xy), payload.normals_gs); imageStore(out_material_rmxx, ivec2(gl_LaunchIDEXT.xy), payload.material_rmxx); imageStore(out_emissive, ivec2(gl_LaunchIDEXT.xy), payload.emissive); - imageStore(out_prev_position_t, ivec2(gl_LaunchIDEXT.xy), payload.prev_pos_t); + imageStore(out_geometry_prev_position, ivec2(gl_LaunchIDEXT.xy), payload.prev_pos_t); } diff --git a/ref_vk/vk_brush.c b/ref_vk/vk_brush.c index cdfd59ed..903ed6e6 100644 --- a/ref_vk/vk_brush.c +++ b/ref_vk/vk_brush.c @@ -13,6 +13,7 @@ #include "vk_geometry.h" #include "vk_light.h" #include "vk_mapents.h" +#include "vk_previous_frame.h" #include "ref_params.h" #include "eiface.h" @@ -33,16 +34,6 @@ static struct { int rtable[MOD_FRAMES][MOD_FRAMES]; } g_brush; - -#define MAX_BRUSH_ENTITIES_PREV_STATES 1024 - -typedef struct { - matrix4x4 prev_model_transform; - float prev_time; -} brush_entity_prev_state_t; - -static brush_entity_prev_state_t g_brush_prev_states[MAX_BRUSH_ENTITIES_PREV_STATES]; - void VK_InitRandomTable( void ) { int tu, tv; @@ -101,10 +92,7 @@ static void EmitWaterPolys( const cl_entity_t *ent, const msurface_t *warp, qboo uint16_t *indices; r_geometry_buffer_lock_t buffer; - if (ent->index < MAX_BRUSH_ENTITIES_PREV_STATES) { - prev_time = g_brush_prev_states[ent->index].prev_time; - g_brush_prev_states[ent->index].prev_time = time; - } else gEngine.Con_Printf(S_ERROR "Brush entities previous frame states pool is overflow, increase it. Index is %s\n", ent->index ); + prev_time = R_PrevFrame_Time(ent->index); #define MAX_WATER_VERTICES 16 vk_vertex_t poly_vertices[MAX_WATER_VERTICES]; @@ -289,17 +277,9 @@ void XVK_DrawWaterSurfaces( const cl_entity_t *ent ) EmitWaterPolys( ent, surf, false ); } - int entity_id = ent->index; - - if (entity_id < MAX_BRUSH_ENTITIES_PREV_STATES) - Matrix4x4_Copy( *VK_RenderGetLastFrameTransform(), g_brush_prev_states[entity_id].prev_model_transform ); - // submit as dynamic model VK_RenderModelDynamicCommit(); - if (entity_id < MAX_BRUSH_ENTITIES_PREV_STATES) - Matrix4x4_Copy( g_brush_prev_states[entity_id].prev_model_transform, *VK_RenderGetLastFrameTransform() ); - // TODO: // - upload water geometry only once, animate in compute/vertex shader } @@ -392,18 +372,8 @@ void VK_BrushModelDraw( const cl_entity_t *ent, int render_mode, const matrix4x4 } } - int entity_id = ent->index; - if (entity_id < MAX_BRUSH_ENTITIES_PREV_STATES) { - Matrix4x4_Copy( bmodel->render_model.prev_transform, - g_brush_prev_states[entity_id].prev_model_transform ); - } else gEngine.Con_Printf(S_ERROR "Brush entities previous frame states pool is overflow, increase it. Index is %s\n", ent->index ); - bmodel->render_model.render_mode = render_mode; VK_RenderModelDraw(ent, &bmodel->render_model); - - if (entity_id >= 0 && entity_id < MAX_BRUSH_ENTITIES_PREV_STATES) { - Matrix4x4_Copy( g_brush_prev_states[entity_id].prev_model_transform, bmodel->render_model.prev_transform ); - } } static qboolean renderableSurface( const msurface_t *surf, int i ) { diff --git a/ref_vk/vk_previous_frame.c b/ref_vk/vk_previous_frame.c new file mode 100644 index 00000000..d5cc588d --- /dev/null +++ b/ref_vk/vk_previous_frame.c @@ -0,0 +1,104 @@ +#include "vk_studio.h" +#include "vk_common.h" +#include "vk_textures.h" +#include "vk_render.h" +#include "vk_geometry.h" +#include "camera.h" + +#include "xash3d_mathlib.h" +#include "const.h" +#include "r_studioint.h" +#include "triangleapi.h" +#include "studio.h" +#include "pm_local.h" +#include "cl_tent.h" +#include "pmtrace.h" +#include "protocol.h" +#include "enginefeatures.h" +#include "pm_movevars.h" + +#include +#include + +#define PREV_STATES_COUNT 1024 +#define PREV_FRAMES_COUNT 2 + +typedef struct { + matrix3x4 bones_worldtransform[MAXSTUDIOBONES]; + matrix4x4 model_transform; + float time; + int bones_update_frame_index; +} prev_state_t; + +typedef struct { + prev_state_t prev_states[PREV_FRAMES_COUNT][PREV_STATES_COUNT]; + int frame_index; + int current_frame_id; + int previous_frame_id; +} prev_states_storage_t; + +prev_states_storage_t g_prev = { 0 }; + +inline int clampIndex( int index, int array_length ) +{ + if (index < 0) + return 0; + else if (index >= array_length) + return array_length - 1; + + return index; +} + +prev_state_t* prevStateInArrayBounds( int frame_storage_id, int entity_id ) +{ + int clamped_frame_id = clampIndex( frame_storage_id, PREV_FRAMES_COUNT ); + int clamped_entity_id = clampIndex( entity_id, PREV_STATES_COUNT ); + return &g_prev.prev_states[clamped_frame_id][clamped_entity_id]; +} + +#define PREV_FRAME() prevStateInArrayBounds( g_prev.previous_frame_id, entity_id ) +#define CURRENT_FRAME() prevStateInArrayBounds( g_prev.current_frame_id, entity_id ) + +void R_PrevFrame_StartFrame( void ) +{ + g_prev.frame_index++; + g_prev.current_frame_id = g_prev.frame_index % PREV_FRAMES_COUNT; + g_prev.previous_frame_id = g_prev.frame_index - 1; +} + +void R_PrevFrame_SaveCurrentBoneTransforms( int entity_id, matrix3x4* bones_transforms ) +{ + prev_state_t *state = CURRENT_FRAME(); + + if (state->bones_update_frame_index == g_prev.frame_index) + return; // already updated for this entity + + state->bones_update_frame_index = g_prev.frame_index; + + for( int i = 0; i < MAXSTUDIOBONES; i++ ) + { + Matrix3x4_Copy(state->bones_worldtransform[i], bones_transforms[i]); + } +} + +void R_PrevFrame_SaveCurrentState( int entity_id, matrix4x4 model_transform ) +{ + prev_state_t* state = CURRENT_FRAME(); + Matrix4x4_Copy( state->model_transform, model_transform ); + state->time = gpGlobals->time; +} + +matrix3x4* R_PrevFrame_BoneTransforms( int entity_id ) +{ + return PREV_FRAME()->bones_worldtransform; +} + +void R_PrevFrame_ModelTransform( int entity_id, matrix4x4 model_matrix ) +{ + Matrix4x4_Copy(model_matrix, PREV_FRAME()->model_transform); +} + +float R_PrevFrame_Time( int entity_id ) +{ + return PREV_FRAME()->time; +} diff --git a/ref_vk/vk_previous_frame.h b/ref_vk/vk_previous_frame.h new file mode 100644 index 00000000..ce8e27f0 --- /dev/null +++ b/ref_vk/vk_previous_frame.h @@ -0,0 +1,10 @@ +#pragma once + +#include "vk_common.h" + +void R_PrevFrame_StartFrame(void); +void R_PrevFrame_SaveCurrentBoneTransforms(int entity_id, matrix3x4* bones_transforms); +void R_PrevFrame_SaveCurrentState(int entity_id, matrix4x4 model_transform); +matrix3x4* R_PrevFrame_BoneTransforms(int entity_id); +void R_PrevFrame_ModelTransform( int entity_id, matrix4x4 model_matrix ); +float R_PrevFrame_Time(int entity_id); diff --git a/ref_vk/vk_render.c b/ref_vk/vk_render.c index 54e0e037..4b3a2ebc 100644 --- a/ref_vk/vk_render.c +++ b/ref_vk/vk_render.c @@ -12,6 +12,7 @@ #include "vk_rtx.h" #include "vk_descriptor.h" #include "vk_framectl.h" // FIXME needed for dynamic models cmdbuf +#include "vk_previous_frame.h" #include "alolcator.h" #include "eiface.h" @@ -682,10 +683,16 @@ void VK_RenderModelDraw( const cl_entity_t *ent, vk_render_model_t* model ) { int vertex_offset = 0; if (g_render_state.current_frame_is_ray_traced) { + if (ent != NULL && model != NULL) { + R_PrevFrame_ModelTransform( ent->index, model->prev_transform ); + R_PrevFrame_SaveCurrentState( ent->index, g_render_state.model ); + } + else { + Matrix4x4_Copy( model->prev_transform, g_render_state.model ); + } + VK_RayFrameAddModel(model->ray_model, model, (const matrix3x4*)g_render_state.model, g_render_state.dirty_uniform_data.color, ent ? ent->curstate.rendercolor : (color24){255,255,255}); - // store current transform here before it puts to entity history - Matrix4x4_Copy( model->prev_transform, g_render_state.model ); return; } @@ -751,10 +758,6 @@ static struct { vk_render_geometry_t geometries[MAX_DYNAMIC_GEOMETRY]; } g_dynamic_model = {0}; -matrix4x4 *VK_RenderGetLastFrameTransform( void ) { - return &g_dynamic_model.model.prev_transform; -} - void VK_RenderModelDynamicBegin( int render_mode, const char *debug_name_fmt, ... ) { va_list argptr; va_start( argptr, debug_name_fmt ); diff --git a/ref_vk/vk_render.h b/ref_vk/vk_render.h index d6b45fe7..e18a3dcf 100644 --- a/ref_vk/vk_render.h +++ b/ref_vk/vk_render.h @@ -103,5 +103,3 @@ void VK_RenderEnd( VkCommandBuffer cmdbuf ); void VK_RenderEndRTX( VkCommandBuffer cmdbuf, VkImageView img_dst_view, VkImage img_dst, uint32_t w, uint32_t h ); void VK_Render_FIXME_Barrier( VkCommandBuffer cmdbuf ); - -matrix4x4* VK_RenderGetLastFrameTransform( void ); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index d4fa371a..af55e2f8 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -15,6 +15,7 @@ #include "vk_ray_internal.h" #include "vk_staging.h" #include "vk_textures.h" +#include "vk_previous_frame.h" #include "alolcator.h" @@ -137,6 +138,8 @@ void VK_RayFrameBegin( void ) { XVK_RayModel_ClearForNextFrame(); + R_PrevFrame_StartFrame(); + // TODO: move all lighting update to scene? if (g_rtx.reload_lighting) { g_rtx.reload_lighting = false; diff --git a/ref_vk/vk_studio.c b/ref_vk/vk_studio.c index 435e8c18..10042398 100644 --- a/ref_vk/vk_studio.c +++ b/ref_vk/vk_studio.c @@ -3,6 +3,7 @@ #include "vk_textures.h" #include "vk_render.h" #include "vk_geometry.h" +#include "vk_previous_frame.h" #include "camera.h" #include "xash3d_mathlib.h" @@ -125,9 +126,6 @@ static cvar_t *cl_himodels; static r_studio_interface_t *pStudioDraw; static studio_draw_state_t g_studio; // global studio state -#define MAX_ENTITIES_PREV_STATES_STUDIO 1024 -static studio_entity_prev_state_t g_entity_prev_states[MAX_ENTITIES_PREV_STATES_STUDIO]; - // global variables static qboolean m_fDoRemap; mstudiomodel_t *m_pSubModel; @@ -1072,26 +1070,6 @@ static void R_StudioSaveBones( void ) } } -/* -==================== -SaveTransformsForNextFrame - -==================== -*/ - -static void R_StudioSaveTransformsForNextFrame( matrix3x4* bones_transforms ) -{ - if (RI.currententity->index >= MAX_ENTITIES_PREV_STATES_STUDIO) - return; - - studio_entity_prev_state_t *prev_state = &g_entity_prev_states[RI.currententity->index]; - - for( int i = 0; i < m_pStudioHeader->numbones; i++ ) - { - Matrix3x4_Copy(prev_state->worldtransform[i], bones_transforms[i]); - } -} - /* ==================== StudioBuildNormalTable @@ -2158,14 +2136,6 @@ static void R_StudioDrawPoints( void ) pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); if( m_skinnum != 0 ) pskinref += (m_skinnum * m_pStudioHeader->numskinref); - studio_entity_prev_state_t *prev_frame_state = &g_entity_prev_states[RI.currententity->index]; - - if (RI.currententity->index >= MAX_ENTITIES_PREV_STATES_STUDIO) - { - gEngine.Con_Printf(S_ERROR "Studio entities previous frame states pool is overflow, increase it. Index is %s\n", RI.currententity->index); - prev_frame_state = &g_entity_prev_states[MAX_ENTITIES_PREV_STATES_STUDIO - 1]; // fallback to last element - } - if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) && m_pSubModel->blendvertinfoindex != 0 && m_pSubModel->blendnorminfoindex != 0 ) { mstudioboneweight_t *pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendvertinfoindex); @@ -2179,9 +2149,10 @@ static void R_StudioDrawPoints( void ) R_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] ); } + matrix3x4* prev_bones_transforms = R_PrevFrame_BoneTransforms( RI.currententity->index ); for( i = 0; i < m_pSubModel->numverts; i++ ) { - R_StudioComputeSkinMatrix( &pvertweight[i], prev_frame_state->worldtransform, skinMat ); + R_StudioComputeSkinMatrix( &pvertweight[i], prev_bones_transforms, skinMat ); Matrix3x4_VectorTransform( skinMat, pstudioverts[i], g_studio.prev_verts[i] ); } @@ -2191,18 +2162,19 @@ static void R_StudioDrawPoints( void ) Matrix3x4_VectorRotate( skinMat, pstudionorms[i], g_studio.norms[i] ); } - R_StudioSaveTransformsForNextFrame (g_studio.worldtransform ); + R_PrevFrame_SaveCurrentBoneTransforms( RI.currententity->index, g_studio.worldtransform ); } else { + matrix3x4* prev_bones_transforms = R_PrevFrame_BoneTransforms( RI.currententity->index ); for( i = 0; i < m_pSubModel->numverts; i++ ) { Matrix3x4_VectorTransform( g_studio.bonestransform[pvertbone[i]], pstudioverts[i], g_studio.verts[i] ); - Matrix3x4_VectorTransform( prev_frame_state->worldtransform[pvertbone[i]], pstudioverts[i], g_studio.prev_verts[i] ); + Matrix3x4_VectorTransform( prev_bones_transforms[pvertbone[i]], pstudioverts[i], g_studio.prev_verts[i] ); R_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] ); } - R_StudioSaveTransformsForNextFrame( g_studio.bonestransform ); + R_PrevFrame_SaveCurrentBoneTransforms( RI.currententity->index, g_studio.bonestransform ); } // generate shared normals for properly scaling glowing shell @@ -2328,17 +2300,7 @@ static void R_StudioDrawPoints( void ) */ } - int entity_id = RI.currententity->index; - - if (entity_id < MAX_ENTITIES_PREV_STATES_STUDIO) { - Matrix4x4_Copy( *VK_RenderGetLastFrameTransform(), g_entity_prev_states[entity_id].prev_transform ); - } else gEngine.Con_Printf(S_ERROR "Studio entities last states pool is overflow, increase it. Index is %s\n", entity_id ); - VK_RenderModelDynamicCommit(); - - if (entity_id < MAX_ENTITIES_PREV_STATES_STUDIO) { - Matrix4x4_Copy( g_entity_prev_states[entity_id].prev_transform, *VK_RenderGetLastFrameTransform() ); - } } static void R_StudioSetRemapColors( int newTop, int newBottom ) From 615a10c0054450eec1d808a201d7c265540d5548 Mon Sep 17 00:00:00 2001 From: LifeKILLED Date: Mon, 30 Jan 2023 16:36:44 +0400 Subject: [PATCH 524/548] vk rt: remove unused struct from vk_studio.c --- ref_vk/vk_studio.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ref_vk/vk_studio.c b/ref_vk/vk_studio.c index 10042398..9b5d7d6e 100644 --- a/ref_vk/vk_studio.c +++ b/ref_vk/vk_studio.c @@ -48,11 +48,6 @@ typedef struct sortedmesh_s int flags; // face flags } sortedmesh_t; -typedef struct { - matrix3x4 worldtransform[MAXSTUDIOBONES]; - matrix4x4 prev_transform; -} studio_entity_prev_state_t; - typedef struct { double time; From 71f7449a8e8bcb1c94e9f048dee477c8fc75a2e2 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Mon, 30 Jan 2023 10:21:18 -0800 Subject: [PATCH 525/548] comment and add additional checks for the previous commit --- ref_vk/sebastian.py | 38 +++++++++++++++++++++++++++----------- ref_vk/vk_meatpipe.c | 2 +- ref_vk/vk_meatpipe.h | 5 +++-- ref_vk/vk_rtx.c | 23 +++++++++++++---------- 4 files changed, 44 insertions(+), 24 deletions(-) diff --git a/ref_vk/sebastian.py b/ref_vk/sebastian.py index afcfed4e..f6e8b564 100755 --- a/ref_vk/sebastian.py +++ b/ref_vk/sebastian.py @@ -174,6 +174,9 @@ class TypeInfo: return True + def __repr__(self): + return 'Type(type=%x is_image=%d image_format=%x count=%d)' % (self.type, self.is_image, self.image_format, self.count) + def serialize(self, out): out.writeU32(self.type) out.writeU32(self.count) @@ -422,7 +425,7 @@ class Resources: if index >= 0: res = self.__storage.getByIndex(index) - res.checkSameType(node) + res.checkSameTypeNode(node) return index return self.__storage.put(name, self.Resource(name, node, dependency)) @@ -431,21 +434,34 @@ class Resources: return self.__map.get(index, index) def __sortDependencies(self): + # We should sort only once at export time assert(not self.__map) self.__map = dict() + # We need to make sure that all the images that are referenced by their prev_ counterparts + # have been created (i.e. listed in resources) earlier than all the referencees for i, r in enumerate(self.__storage): dep = r.dependency if not dep: continue + # Cannot R/W the same resource assert(dep != i) + + # Check that their formats are congruent + depr = self.__storage.getByIndex(dep) + if depr.type != r.type: + raise Exception('Conflicting types for resource %s (%s) and %s (%s)' % (depr.name, depr.type, r.name, r.type)) + if dep < i: continue + # It is an error to have multiple entries for the same pair assert(i not in self.__map) assert(dep not in self.__map) + # Just swap their externally-referenced indexes, don't swap entries in the array itself + # This should be enough so that the writer index is less than the reader one self.__map[i] = dep self.__map[dep] = i r.dependency = i @@ -456,22 +472,22 @@ class Resources: class Resource: def __init__(self, name, node, dependency = None): - self.__name = name - self.__type = node.getType() if node else None + self.name = name + self.type = node.getType() if node else None self.dependency = dependency - def checkSameType(self, node): - if not self.__type: - self.__type = node.getType() + def checkSameTypeNode(self, node): + if not self.type: + self.type = node.getType() return - if self.__type != node.getType(): - raise Exception('Conflicting types for resource "%s": %s != %s' % (self.__name, self.__type, type)) + if self.type != node.getType(): + raise Exception('Conflicting types for resource "%s": %s != %s' % (self.name, self.type, type)) def serialize(self, out): - out.writeString(self.__name) - self.__type.serialize(out) - if self.__type.is_image: + out.writeString(self.name) + self.type.serialize(out) + if self.type.is_image: out.writeU32((self.dependency + 1) if self.dependency is not None else 0) resources = Resources() diff --git a/ref_vk/vk_meatpipe.c b/ref_vk/vk_meatpipe.c index 822b37e9..d43bb5f5 100644 --- a/ref_vk/vk_meatpipe.c +++ b/ref_vk/vk_meatpipe.c @@ -304,7 +304,7 @@ static qboolean readResources(load_context_t *ctx) { if (is_image) { res->image_format = READ_U32("Couldn't read image format for res %d:%s", i, res->name); - res->prev_frame_index = READ_U32("Couldn't read resource %d:%s previous frame index", i, res->name); + res->prev_frame_index_plus_1 = READ_U32("Couldn't read resource %d:%s previous frame index", i, res->name); } gEngine.Con_Reportf("Resource %d:%s = %08x is_image=%d image_format=%08x count=%d\n", diff --git a/ref_vk/vk_meatpipe.h b/ref_vk/vk_meatpipe.h index f5a303d0..68327e9d 100644 --- a/ref_vk/vk_meatpipe.h +++ b/ref_vk/vk_meatpipe.h @@ -17,8 +17,9 @@ typedef struct { uint32_t image_format; }; - // If this image is supposed to be read from previous frame - int prev_frame_index; + // Index+1 of resource image to read data from if this resource is a "previous frame" contents of another one. + // Value of zero means that it is a standalone resource. The real index is the value - 1. + int prev_frame_index_plus_1; } vk_meatpipe_resource_t; struct vk_meatpipe_pass_s; diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index abcf3f5b..46cf3b7c 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -64,7 +64,7 @@ typedef struct { vk_resource_t resource; xvk_image_t image; int refcount; - int source_index; + int source_index_plus_1; } rt_resource_t; static struct { @@ -232,11 +232,11 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* // Transfer previous frames before they had a chance of their resource-barrier metadata overwritten (as there's no guaranteed order for them) for (int i = ExternalResource_COUNT; i < MAX_RESOURCES; ++i) { rt_resource_t* const res = g_rtx.res + i; - if (!res->name[0] || !res->image.image || res->source_index <= 0) + if (!res->name[0] || !res->image.image || res->source_index_plus_1 <= 0) continue; - ASSERT(res->source_index <= COUNTOF(g_rtx.res)); - rt_resource_t *const src = g_rtx.res + res->source_index - 1; + ASSERT(res->source_index_plus_1 <= COUNTOF(g_rtx.res)); + rt_resource_t *const src = g_rtx.res + res->source_index_plus_1 - 1; // Swap resources const vk_resource_t tmp_res = res->resource; @@ -262,7 +262,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* // Clear intra-frame resources for (int i = ExternalResource_COUNT; i < MAX_RESOURCES; ++i) { rt_resource_t* const res = g_rtx.res + i; - if (!res->name[0] || !res->image.image || res->source_index > 0) + if (!res->name[0] || !res->image.image || res->source_index_plus_1 > 0) continue; res->resource.read = res->resource.write = (ray_resource_state_t){0}; @@ -307,6 +307,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* // Update image resource links after the prev_-related swap above // TODO Preserve the indexes somewhere to avoid searching + // FIXME I don't really get why we need this, the pointers should have been preserved ?! for (int i = 0; i < g_rtx.mainpipe->resources_count; ++i) { const vk_meatpipe_resource_t *mr = g_rtx.mainpipe->resources + i; const int index = findResource(mr->name); @@ -315,7 +316,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* rt_resource_t *const res = g_rtx.res + index; const qboolean create = !!(mr->flags & MEATPIPE_RES_CREATE); if (create && mr->descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) - //ASSERT(g_rtx.mainpipe_resources[i]->value.image_object == &res->image); + // THIS FAILS WHY?! ASSERT(g_rtx.mainpipe_resources[i]->value.image_object == &res->image); g_rtx.mainpipe_resources[i]->value.image_object = &res->image; } @@ -347,6 +348,8 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* R_VkImageBlit( cmdbuf, &blit_args ); + // TODO this is to make sure we remember image layout after image_blit + // The proper way to do this would be to teach R_VkImageBlit to properly track the image metadata (i.e. vk_resource_t state) g_rtx.mainpipe_out->resource.write.image_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; } DEBUG_END(cmdbuf); @@ -471,15 +474,15 @@ static void reloadMainpipe(void) { // Resolve prev_ frame resources for (int i = 0; i < newpipe->resources_count; ++i) { const vk_meatpipe_resource_t *mr = newpipe->resources + i; - if (mr->prev_frame_index <= 0) + if (mr->prev_frame_index_plus_1 <= 0) continue; - ASSERT(mr->prev_frame_index < newpipe->resources_count); + ASSERT(mr->prev_frame_index_plus_1 < newpipe->resources_count); const int index = findResource(mr->name); ASSERT(index >= 0); - const vk_meatpipe_resource_t *pr = newpipe->resources + (mr->prev_frame_index - 1); + const vk_meatpipe_resource_t *pr = newpipe->resources + (mr->prev_frame_index_plus_1 - 1); const int dest_index = findResource(pr->name); if (dest_index < 0) { @@ -487,7 +490,7 @@ static void reloadMainpipe(void) { goto fail; } - g_rtx.res[index].source_index = dest_index + 1; + g_rtx.res[index].source_index_plus_1 = dest_index + 1; } // Loading successful From c1cfe1008d228290c6ad373bae485c703afc69fd Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Mon, 30 Jan 2023 11:31:46 -0800 Subject: [PATCH 526/548] rt: minor rt pipelines and pre-meatpipe cleanup --- ref_vk/shaders/denoiser.comp | 12 +++++------- ref_vk/shaders/ray_interop.h | 20 -------------------- ref_vk/shaders/ray_light_direct.glsl | 5 +++++ ref_vk/shaders/ray_light_direct_point.comp | 4 +++- ref_vk/shaders/ray_light_direct_point.rgen | 10 ---------- ref_vk/shaders/ray_light_direct_poly.comp | 4 +++- ref_vk/shaders/ray_light_poly_direct.rgen | 10 ---------- ref_vk/shaders/ray_primary.comp | 7 +++++++ ref_vk/shaders/ray_primary.rgen | 7 +++++++ 9 files changed, 30 insertions(+), 49 deletions(-) delete mode 100644 ref_vk/shaders/ray_light_direct_point.rgen delete mode 100644 ref_vk/shaders/ray_light_poly_direct.rgen diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 8a6821fa..f025db39 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -18,8 +18,8 @@ layout(set = 0, binding = 4, rgba16f) uniform readonly image2D light_point_diffu layout(set = 0, binding = 5, rgba16f) uniform readonly image2D light_point_specular; layout(set = 0, binding = 6, rgba16f) uniform readonly image2D emissive; -//layout(set = 0, binding = 7, rgba32f) uniform readonly image2D position_t; -//layout(set = 0, binding = 8, rgba16f) uniform readonly image2D normals_gs; +layout(set = 0, binding = 7, rgba32f) uniform readonly image2D position_t; +layout(set = 0, binding = 8, rgba16f) uniform readonly image2D normals_gs; //layout(set = 0, binding = 2, rgba16f) uniform readonly image2D light_poly_diffuse; /* layout(set = 0, binding = 3, rgba16f) uniform readonly image2D specular; */ @@ -57,13 +57,11 @@ vec3 reinhard02(vec3 c, vec3 Cwhite2) { float normpdf2(in float x2, in float sigma) { return 0.39894*exp(-0.5*x2/(sigma*sigma))/sigma; } float normpdf(in float x, in float sigma) { return normpdf2(x*x, sigma); } -/* void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { const vec4 n = imageLoad(normals_gs, uv); geometry_normal = normalDecode(n.xy); shading_normal = normalDecode(n.zw); } -*/ void main() { ivec2 res = ivec2(imageSize(base_color_a)); @@ -104,7 +102,7 @@ void main() { /* imageStore(out_dest, pix, vec4(rand3_f01(uvec3(mi,mi+1,mi+2)), 0.)); */ /* return; */ -#if 1 +#if 0 vec3 colour = vec3(0.); colour += imageLoad(light_poly_diffuse, pix).rgb; colour += imageLoad(light_poly_specular, pix).rgb; @@ -113,7 +111,7 @@ void main() { #else float total_scale = 0.; vec3 colour = vec3(0.); - const int KERNEL_SIZE = 8; + const int KERNEL_SIZE = 4; float specular_total_scale = 0.; vec3 speculour = vec3(0.); @@ -206,7 +204,7 @@ void main() { #endif const vec4 prev_colour = imageLoad(prev_dest, pix); - colour = mix(colour, prev_colour.rgb, vec3(.9)); + colour = mix(colour, prev_colour.rgb, vec3(.5)); imageStore(out_dest, pix, vec4(colour, 0.)); //imageStore(out_dest, pix, imageLoad(light_poly, pix)); diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index d07361b3..e54ce1f2 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -12,26 +12,6 @@ X(6, uint, MAX_TEXTURES, 4096) \ X(7, uint, SBT_RECORD_SIZE, 32) \ -#define RAY_PRIMARY_OUTPUTS(X) \ - X(10, base_color_a, rgba8) \ - X(11, position_t, rgba32f) \ - X(12, normals_gs, rgba16f) \ - X(13, material_rmxx, rgba8) \ - X(14, emissive, rgba16f) \ - -#define RAY_LIGHT_DIRECT_INPUTS(X) \ - X(10, position_t, rgba32f) \ - X(11, normals_gs, rgba16f) \ - X(12, material_rmxx, rgba8) \ - -#define RAY_LIGHT_DIRECT_POLY_OUTPUTS(X) \ - X(20, light_poly_diffuse, rgba16f) \ - X(21, light_poly_specular, rgba16f) \ - -#define RAY_LIGHT_DIRECT_POINT_OUTPUTS(X) \ - X(20, light_point_diffuse, rgba16f) \ - X(21, light_point_specular, rgba16f) \ - #ifndef GLSL #include "xash3d_types.h" #include "vk_const.h" diff --git a/ref_vk/shaders/ray_light_direct.glsl b/ref_vk/shaders/ray_light_direct.glsl index 92b25f74..cc54e8bd 100644 --- a/ref_vk/shaders/ray_light_direct.glsl +++ b/ref_vk/shaders/ray_light_direct.glsl @@ -7,6 +7,11 @@ #include "ray_interop.h" #undef GLSL +#define RAY_LIGHT_DIRECT_INPUTS(X) \ + X(10, position_t, rgba32f) \ + X(11, normals_gs, rgba16f) \ + X(12, material_rmxx, rgba8) \ + #define X(index, name, format) layout(set=0,binding=index,format) uniform readonly image2D name; RAY_LIGHT_DIRECT_INPUTS(X) #undef X diff --git a/ref_vk/shaders/ray_light_direct_point.comp b/ref_vk/shaders/ray_light_direct_point.comp index 09501387..63c9c786 100644 --- a/ref_vk/shaders/ray_light_direct_point.comp +++ b/ref_vk/shaders/ray_light_direct_point.comp @@ -5,5 +5,7 @@ #define RAY_QUERY #define LIGHT_POINT 1 -#define OUTPUTS RAY_LIGHT_DIRECT_POINT_OUTPUTS +#define OUTPUTS(X) \ + X(20, light_point_diffuse, rgba16f) \ + X(21, light_point_specular, rgba16f) #include "ray_light_direct.glsl" diff --git a/ref_vk/shaders/ray_light_direct_point.rgen b/ref_vk/shaders/ray_light_direct_point.rgen deleted file mode 100644 index 0e67591f..00000000 --- a/ref_vk/shaders/ray_light_direct_point.rgen +++ /dev/null @@ -1,10 +0,0 @@ -#version 460 core -#extension GL_GOOGLE_include_directive : require -#extension GL_EXT_ray_tracing: require - -#define RAY_TRACE -#define RAY_TRACE2 - -#define LIGHT_POINT 1 -#define OUTPUTS RAY_LIGHT_DIRECT_POINT_OUTPUTS -#include "ray_light_direct.glsl" diff --git a/ref_vk/shaders/ray_light_direct_poly.comp b/ref_vk/shaders/ray_light_direct_poly.comp index 6b03668e..1b26625a 100644 --- a/ref_vk/shaders/ray_light_direct_poly.comp +++ b/ref_vk/shaders/ray_light_direct_poly.comp @@ -5,5 +5,7 @@ #define RAY_QUERY #define LIGHT_POLYGON 1 -#define OUTPUTS RAY_LIGHT_DIRECT_POLY_OUTPUTS +#define OUTPUTS(X) \ + X(20, light_poly_diffuse, rgba16f) \ + X(21, light_poly_specular, rgba16f) #include "ray_light_direct.glsl" diff --git a/ref_vk/shaders/ray_light_poly_direct.rgen b/ref_vk/shaders/ray_light_poly_direct.rgen deleted file mode 100644 index 105242ea..00000000 --- a/ref_vk/shaders/ray_light_poly_direct.rgen +++ /dev/null @@ -1,10 +0,0 @@ -#version 460 core -#extension GL_GOOGLE_include_directive : require -#extension GL_EXT_ray_tracing: require - -#define RAY_TRACE -#define RAY_TRACE2 - -#define LIGHT_POLYGON 1 -#define OUTPUTS RAY_LIGHT_DIRECT_POLY_OUTPUTS -#include "ray_light_direct.glsl" diff --git a/ref_vk/shaders/ray_primary.comp b/ref_vk/shaders/ray_primary.comp index c747cf3c..cd5d415f 100644 --- a/ref_vk/shaders/ray_primary.comp +++ b/ref_vk/shaders/ray_primary.comp @@ -9,6 +9,13 @@ layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; #include "ray_primary_common.glsl" #include "ray_primary_hit.glsl" +#define RAY_PRIMARY_OUTPUTS(X) \ + X(10, base_color_a, rgba8) \ + X(11, position_t, rgba32f) \ + X(12, normals_gs, rgba16f) \ + X(13, material_rmxx, rgba8) \ + X(14, emissive, rgba16f) \ + #define X(index, name, format) layout(set=0,binding=index,format) uniform writeonly image2D out_##name; RAY_PRIMARY_OUTPUTS(X) #undef X diff --git a/ref_vk/shaders/ray_primary.rgen b/ref_vk/shaders/ray_primary.rgen index ba7fdbcf..866a84dc 100644 --- a/ref_vk/shaders/ray_primary.rgen +++ b/ref_vk/shaders/ray_primary.rgen @@ -4,6 +4,13 @@ #include "ray_primary_common.glsl" +#define RAY_PRIMARY_OUTPUTS(X) \ + X(10, base_color_a, rgba8) \ + X(11, position_t, rgba32f) \ + X(12, normals_gs, rgba16f) \ + X(13, material_rmxx, rgba8) \ + X(14, emissive, rgba16f) \ + #define X(index, name, format) layout(set=0,binding=index,format) uniform writeonly image2D out_##name; RAY_PRIMARY_OUTPUTS(X) #undef X From 1d0ebbcc335e42efe1b1c510e094df5e81f28e14 Mon Sep 17 00:00:00 2001 From: LifeKILLED Date: Thu, 2 Feb 2023 00:02:36 +0400 Subject: [PATCH 527/548] vk rt denoiser: merge --- ref_vk/shaders/denoiser.comp | 3 --- 1 file changed, 3 deletions(-) diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 2c6c3c3d..05e68864 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -28,9 +28,6 @@ layout(set = 0, binding = 10, rgba32f) uniform readonly image2D geometry_prev_po layout(set = 0, binding = 11) uniform UBO { UniformBuffer ubo; } ubo; -//layout(set = 0, binding = 7, rgba32f) uniform readonly image2D position_t; -//layout(set = 0, binding = 8, rgba16f) uniform readonly image2D normals_gs; - //layout(set = 0, binding = 2, rgba16f) uniform readonly image2D light_poly_diffuse; /* layout(set = 0, binding = 3, rgba16f) uniform readonly image2D specular; */ /* layout(set = 0, binding = 4, rgba16f) uniform readonly image2D additive; */ From 889846f774b7508c42d0246fbb32989dc30983a1 Mon Sep 17 00:00:00 2001 From: LifeKILLED Date: Thu, 2 Feb 2023 00:35:15 +0400 Subject: [PATCH 528/548] vk rt denoiser: fix variable initialize for linux build --- ref_vk/vk_brush.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_vk/vk_brush.c b/ref_vk/vk_brush.c index 903ed6e6..adb3629f 100644 --- a/ref_vk/vk_brush.c +++ b/ref_vk/vk_brush.c @@ -144,7 +144,7 @@ static void EmitWaterPolys( const cl_entity_t *ent, const msurface_t *warp, qboo prev_nv = (r_turbsin[(int)(v[0] * 5.0f + prev_time * 171.0f - v[1]) & 255] + 8.0f ) * 0.8f + prev_nv; prev_nv = prev_nv * waveHeight + v[2]; } - else nv = v[2]; + else prev_nv = nv = v[2]; os = v[3]; ot = v[4]; From 395d5c4fcb0d3779a50c992232835bd30a763ec6 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Thu, 2 Feb 2023 10:22:52 -0800 Subject: [PATCH 529/548] rt: add alpha test for primary ray and for shadows shadows are particularly slow --- ref_vk/shaders/light_common.glsl | 62 ++++++++++++++++++++++++++------ ref_vk/shaders/ray_primary.comp | 38 ++++++++++++++++++-- 2 files changed, 88 insertions(+), 12 deletions(-) diff --git a/ref_vk/shaders/light_common.glsl b/ref_vk/shaders/light_common.glsl index e4d9c883..d6d6e104 100644 --- a/ref_vk/shaders/light_common.glsl +++ b/ref_vk/shaders/light_common.glsl @@ -1,8 +1,11 @@ #ifndef LIGHT_COMMON_GLSL_INCLUDED #define LIGHT_COMMON_GLSL_INCLUDED +#extension GL_EXT_nonuniform_qualifier : enable #include "ray_kusochki.glsl" +layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; + #ifdef RAY_TRACE2 #include "ray_shadow_interface.glsl" layout(location = PAYLOAD_LOCATION_SHADOW) rayPayloadEXT RayPayloadShadow payload_shadow; @@ -20,6 +23,49 @@ uint traceShadowRay(vec3 pos, vec3 dir, float dist, uint flags) { } #endif +#if defined(RAY_QUERY) +void shadowRayQuery(rayQueryEXT rq, vec3 pos, vec3 dir, float dist, uint flags) { + rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE, pos, 0., dir, dist - shadow_offset_fudge); + + while (rayQueryProceedEXT(rq)) { + if (0 != (rayQueryGetRayFlagsEXT(rq) & gl_RayFlagsOpaqueEXT)) + continue; + + // Alpha test, takes 10ms + // TODO check other possible ways of doing alpha test. They might be more efficient: + // 1. Do a separate ray query for alpha masked geometry. Reason: here we might accidentally do the expensive + // texture sampling for geometry that's ultimately invisible (i.e. behind walls). Also, shader threads congruence. + // Separate pass could be more efficient as it'd be doing the same thing for every invocation. + // 2. Same as the above, but also with a completely independent TLAS. Why: no need to mask-check geometry for opaque-vs-alpha +#if 1 + const uint instance_kusochki_offset = rayQueryGetIntersectionInstanceCustomIndexEXT(rq, false); + const uint geometry_index = rayQueryGetIntersectionGeometryIndexEXT(rq, false); + const uint kusok_index = instance_kusochki_offset + geometry_index; + const Kusok kusok = getKusok(kusok_index); + + const uint primitive_index = rayQueryGetIntersectionPrimitiveIndexEXT(rq, false); + const uint first_index_offset = kusok.index_offset + primitive_index * 3; + const uint vi1 = uint(getIndex(first_index_offset+0)) + kusok.vertex_offset; + const uint vi2 = uint(getIndex(first_index_offset+1)) + kusok.vertex_offset; + const uint vi3 = uint(getIndex(first_index_offset+2)) + kusok.vertex_offset; + const vec2 uvs[3] = { + getVertex(vi1).gl_tc, + getVertex(vi2).gl_tc, + getVertex(vi3).gl_tc, + }; + const vec2 bary = rayQueryGetIntersectionBarycentricsEXT(rq, false); + const vec2 uv = baryMix(uvs[0], uvs[1], uvs[2], bary); + const vec4 texture_color = texture(textures[nonuniformEXT(kusok.tex_base_color)], uv); + + const float alpha_mask_threshold = .1f; + if (texture_color.a >= alpha_mask_threshold) { + rayQueryConfirmIntersectionEXT(rq); + } +#endif + } +} +#endif + bool shadowed(vec3 pos, vec3 dir, float dist) { #ifdef RAY_TRACE const uint flags = 0 @@ -31,16 +77,14 @@ bool shadowed(vec3 pos, vec3 dir, float dist) { const uint hit_type = traceShadowRay(pos, dir, dist, flags); return payload_shadow.hit_type == SHADOW_HIT; #elif defined(RAY_QUERY) - rayQueryEXT rq; const uint flags = 0 //| gl_RayFlagsCullFrontFacingTrianglesEXT //| gl_RayFlagsOpaqueEXT | gl_RayFlagsTerminateOnFirstHitEXT //| gl_RayFlagsSkipClosestHitShaderEXT ; - rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE, pos, 0., dir, dist - shadow_offset_fudge); - // TODO alpha test - while (rayQueryProceedEXT(rq)) { } + rayQueryEXT rq; + shadowRayQuery(rq, pos, dir, dist, flags); return rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT; #else #error RAY_TRACE or RAY_QUERY @@ -50,7 +94,7 @@ bool shadowed(vec3 pos, vec3 dir, float dist) { // TODO join with just shadowed() bool shadowedSky(vec3 pos, vec3 dir, float dist) { #ifdef RAY_TRACE - const uint flags = 0 + const uint flags = 0 //| gl_RayFlagsCullFrontFacingTrianglesEXT //| gl_RayFlagsOpaqueEXT //| gl_RayFlagsTerminateOnFirstHitEXT @@ -59,16 +103,14 @@ bool shadowedSky(vec3 pos, vec3 dir, float dist) { const uint hit_type = traceShadowRay(pos, dir, dist, flags); return payload_shadow.hit_type != SHADOW_SKY; #elif defined(RAY_QUERY) - rayQueryEXT rq; - const uint flags = 0 + const uint flags = 0 //| gl_RayFlagsCullFrontFacingTrianglesEXT //| gl_RayFlagsOpaqueEXT //| gl_RayFlagsTerminateOnFirstHitEXT //| gl_RayFlagsSkipClosestHitShaderEXT ; - rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE, pos, 0., dir, dist - shadow_offset_fudge); - // TODO alpha test - while (rayQueryProceedEXT(rq)) { } + rayQueryEXT rq; + shadowRayQuery(rq, pos, dir, dist, flags); if (rayQueryGetIntersectionTypeEXT(rq, true) != gl_RayQueryCommittedIntersectionTriangleEXT) return true; diff --git a/ref_vk/shaders/ray_primary.comp b/ref_vk/shaders/ray_primary.comp index 355d13a7..96eb77ac 100644 --- a/ref_vk/shaders/ray_primary.comp +++ b/ref_vk/shaders/ray_primary.comp @@ -52,8 +52,42 @@ void main() { ; const float L = 10000.; // TODO Why 10k? rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE, origin, 0., direction, L); - // TODO alpha test - while (rayQueryProceedEXT(rq)) { } + while (rayQueryProceedEXT(rq)) { + if (0 != (rayQueryGetRayFlagsEXT(rq) & gl_RayFlagsOpaqueEXT)) + continue; + + // alpha test + // TODO check other possible ways of doing alpha test. They might be more efficient + // (although in this particular primary ray case it's not taht important): + // 1. Do a separate ray query for alpha masked geometry. Reason: here we might accidentally do the expensive + // texture sampling for geometry that's ultimately invisible (i.e. behind walls). Also, shader threads congruence. + // Separate pass could be more efficient as it'd be doing the same thing for every invocation. + // 2. Same as the above, but also with a completely independent TLAS. Why: no need to mask-check geometry for opaque-vs-alpha + const uint instance_kusochki_offset = rayQueryGetIntersectionInstanceCustomIndexEXT(rq, false); + const uint geometry_index = rayQueryGetIntersectionGeometryIndexEXT(rq, false); + const uint kusok_index = instance_kusochki_offset + geometry_index; + const Kusok kusok = getKusok(kusok_index); + + const uint primitive_index = rayQueryGetIntersectionPrimitiveIndexEXT(rq, false); + const uint first_index_offset = kusok.index_offset + primitive_index * 3; + const uint vi1 = uint(getIndex(first_index_offset+0)) + kusok.vertex_offset; + const uint vi2 = uint(getIndex(first_index_offset+1)) + kusok.vertex_offset; + const uint vi3 = uint(getIndex(first_index_offset+2)) + kusok.vertex_offset; + const vec2 uvs[3] = { + getVertex(vi1).gl_tc, + getVertex(vi2).gl_tc, + getVertex(vi3).gl_tc, + }; + const vec2 bary = rayQueryGetIntersectionBarycentricsEXT(rq, false); + const vec2 uv = baryMix(uvs[0], uvs[1], uvs[2], bary); + const vec4 texture_color = texture(textures[nonuniformEXT(kusok.tex_base_color)], uv); + + const float alpha_mask_threshold = .1f; + if (texture_color.a >= alpha_mask_threshold) { + rayQueryConfirmIntersectionEXT(rq); + } + } + if (rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT) { primaryRayHit(rq, payload); } From 3ebfb728191f71027e3a25e262ae2850f20b9357 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Thu, 2 Feb 2023 11:07:41 -0800 Subject: [PATCH 530/548] fix linux linking --- ref_vk/vk_previous_frame.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_vk/vk_previous_frame.c b/ref_vk/vk_previous_frame.c index d5cc588d..003c35b6 100644 --- a/ref_vk/vk_previous_frame.c +++ b/ref_vk/vk_previous_frame.c @@ -39,7 +39,7 @@ typedef struct { prev_states_storage_t g_prev = { 0 }; -inline int clampIndex( int index, int array_length ) +static inline int clampIndex( int index, int array_length ) { if (index < 0) return 0; From 8ec8502a5376fffa1cc02cbe26ec49904b8e93ff Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Fri, 3 Feb 2023 10:12:12 -0800 Subject: [PATCH 531/548] rt: unify shadowed with shadowedSky --- ref_vk/shaders/light.glsl | 20 +++++++------------ ref_vk/shaders/light_common.glsl | 33 +++++-------------------------- ref_vk/shaders/light_polygon.glsl | 6 +++--- 3 files changed, 15 insertions(+), 44 deletions(-) diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index 83fefa2c..d880e271 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -42,9 +42,9 @@ void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec const float stopdot = lights.m.point_lights[i].color_stopdot.a; const vec3 dir = lights.m.point_lights[i].dir_stopdot2.xyz; const float stopdot2 = lights.m.point_lights[i].dir_stopdot2.a; - const bool not_environment = (lights.m.point_lights[i].environment == 0); + const bool is_environment = (lights.m.point_lights[i].environment != 0); - const vec3 light_dir = not_environment ? (origin_r.xyz - P) : -dir; // TODO need to randomize sampling direction for environment soft shadow + const vec3 light_dir = is_environment ? -dir : (origin_r.xyz - P); // TODO need to randomize sampling direction for environment soft shadow const float radius = origin_r.w; const vec3 light_dir_norm = normalize(light_dir); @@ -64,7 +64,9 @@ void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec float light_dist = 1e5; // TODO this is supposedly not the right way to do shadows for environment lights.m. qrad checks for hitting SURF_SKY, and maybe we should too? const float d2 = dot(light_dir, light_dir); const float r2 = origin_r.w * origin_r.w; - if (not_environment) { + if (is_environment) { + color *= 2; // TODO WHY? + } else { if (radius < 1e-3) continue; @@ -86,8 +88,6 @@ void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec //const float pdf = TWO_PI / asin(radius / dist); const float pdf = 1. / ((1. - sqrt(d2 - r2) / dist) * spot_attenuation); color /= pdf; - } else { - color *= 2; } // if (dot(color,color) < color_culling_threshold) @@ -104,14 +104,8 @@ void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec continue; // FIXME split environment and other lights - if (not_environment) { - if (shadowed(P, light_dir_norm, light_dist + shadow_offset_fudge)) - continue; - } else { - // for environment light check that we've hit SURF_SKY - if (shadowedSky(P, light_dir_norm, light_dist + shadow_offset_fudge)) - continue; - } + if (shadowed(P, light_dir_norm, light_dist + shadow_offset_fudge, is_environment)) + continue; diffuse += ldiffuse; specular += lspecular; diff --git a/ref_vk/shaders/light_common.glsl b/ref_vk/shaders/light_common.glsl index d6d6e104..0a7b2054 100644 --- a/ref_vk/shaders/light_common.glsl +++ b/ref_vk/shaders/light_common.glsl @@ -66,7 +66,7 @@ void shadowRayQuery(rayQueryEXT rq, vec3 pos, vec3 dir, float dist, uint flags) } #endif -bool shadowed(vec3 pos, vec3 dir, float dist) { +bool shadowed(vec3 pos, vec3 dir, float dist, bool check_sky) { #ifdef RAY_TRACE const uint flags = 0 //| gl_RayFlagsCullFrontFacingTrianglesEXT @@ -75,42 +75,19 @@ bool shadowed(vec3 pos, vec3 dir, float dist) { | gl_RayFlagsSkipClosestHitShaderEXT ; const uint hit_type = traceShadowRay(pos, dir, dist, flags); - return payload_shadow.hit_type == SHADOW_HIT; + return check_sky ? payload_shadow.hit_type != SHADOW_SKY : payload_shadow.hit_type == SHADOW_HIT; #elif defined(RAY_QUERY) const uint flags = 0 //| gl_RayFlagsCullFrontFacingTrianglesEXT //| gl_RayFlagsOpaqueEXT | gl_RayFlagsTerminateOnFirstHitEXT - //| gl_RayFlagsSkipClosestHitShaderEXT ; rayQueryEXT rq; shadowRayQuery(rq, pos, dir, dist, flags); - return rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT; -#else -#error RAY_TRACE or RAY_QUERY -#endif -} -// TODO join with just shadowed() -bool shadowedSky(vec3 pos, vec3 dir, float dist) { -#ifdef RAY_TRACE - const uint flags = 0 - //| gl_RayFlagsCullFrontFacingTrianglesEXT - //| gl_RayFlagsOpaqueEXT - //| gl_RayFlagsTerminateOnFirstHitEXT - //| gl_RayFlagsSkipClosestHitShaderEXT - ; - const uint hit_type = traceShadowRay(pos, dir, dist, flags); - return payload_shadow.hit_type != SHADOW_SKY; -#elif defined(RAY_QUERY) - const uint flags = 0 - //| gl_RayFlagsCullFrontFacingTrianglesEXT - //| gl_RayFlagsOpaqueEXT - //| gl_RayFlagsTerminateOnFirstHitEXT - //| gl_RayFlagsSkipClosestHitShaderEXT - ; - rayQueryEXT rq; - shadowRayQuery(rq, pos, dir, dist, flags); + if (!check_sky) { + return rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT; + } if (rayQueryGetIntersectionTypeEXT(rq, true) != gl_RayQueryCommittedIntersectionTriangleEXT) return true; diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 02dd56eb..2b2a986d 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -177,7 +177,7 @@ void sampleSinglePolygonLight(in vec3 P, in vec3 N, in vec3 view_dir, in SampleC const float dist = - dot(vec4(P, 1.f), poly.plane) / dot(light_sample_dir.xyz, poly.plane.xyz); - if (shadowed(P, light_sample_dir.xyz, dist)) + if (shadowed(P, light_sample_dir.xyz, dist, false)) return; vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); @@ -244,7 +244,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate const float dist = - plane_dist / dot(light_sample_dir.xyz, poly.plane.xyz); const vec3 emissive = poly.emissive; - if (!shadowed(P, light_sample_dir.xyz, dist)) { + if (!shadowed(P, light_sample_dir.xyz, dist, false)) { //const float estimate = total_contrib; const float estimate = light_sample_dir.w; vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); @@ -320,7 +320,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate const vec3 emissive = poly.emissive; //if (true) {//!shadowed(P, light_sample_dir.xyz, dist)) { - if (!shadowed(P, light_sample_dir.xyz, dist)) { + if (!shadowed(P, light_sample_dir.xyz, dist, false)) { //const float estimate = total_contrib; const float estimate = light_sample_dir.w; vec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.); From c615b9355a934a87a4b701583218e8448b5ff57c Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Fri, 3 Feb 2023 11:00:34 -0800 Subject: [PATCH 532/548] rt: only do alpha test shadows after opaque geometry this honestly barely helps ;_; --- ref_vk/shaders/light_common.glsl | 52 ++++++++++++++++++-------------- ref_vk/shaders/ray_interop.h | 5 +-- ref_vk/shaders/ray_primary.comp | 2 +- ref_vk/vk_ray_accel.c | 2 +- 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/ref_vk/shaders/light_common.glsl b/ref_vk/shaders/light_common.glsl index 0a7b2054..5c3c2190 100644 --- a/ref_vk/shaders/light_common.glsl +++ b/ref_vk/shaders/light_common.glsl @@ -24,20 +24,22 @@ uint traceShadowRay(vec3 pos, vec3 dir, float dist, uint flags) { #endif #if defined(RAY_QUERY) -void shadowRayQuery(rayQueryEXT rq, vec3 pos, vec3 dir, float dist, uint flags) { - rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE, pos, 0., dir, dist - shadow_offset_fudge); +bool shadowTestAlphaMask(vec3 pos, vec3 dir, float dist) { + rayQueryEXT rq; + const uint flags = 0 + | gl_RayFlagsCullFrontFacingTrianglesEXT + //| gl_RayFlagsNoOpaqueEXT + | gl_RayFlagsTerminateOnFirstHitEXT + ; + rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_ALPHA_TEST, pos, 0., dir, dist); while (rayQueryProceedEXT(rq)) { - if (0 != (rayQueryGetRayFlagsEXT(rq) & gl_RayFlagsOpaqueEXT)) - continue; - // Alpha test, takes 10ms // TODO check other possible ways of doing alpha test. They might be more efficient: // 1. Do a separate ray query for alpha masked geometry. Reason: here we might accidentally do the expensive // texture sampling for geometry that's ultimately invisible (i.e. behind walls). Also, shader threads congruence. // Separate pass could be more efficient as it'd be doing the same thing for every invocation. // 2. Same as the above, but also with a completely independent TLAS. Why: no need to mask-check geometry for opaque-vs-alpha -#if 1 const uint instance_kusochki_offset = rayQueryGetIntersectionInstanceCustomIndexEXT(rq, false); const uint geometry_index = rayQueryGetIntersectionGeometryIndexEXT(rq, false); const uint kusok_index = instance_kusochki_offset + geometry_index; @@ -61,8 +63,9 @@ void shadowRayQuery(rayQueryEXT rq, vec3 pos, vec3 dir, float dist, uint flags) if (texture_color.a >= alpha_mask_threshold) { rayQueryConfirmIntersectionEXT(rq); } -#endif } + + return rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT; } #endif @@ -77,25 +80,30 @@ bool shadowed(vec3 pos, vec3 dir, float dist, bool check_sky) { const uint hit_type = traceShadowRay(pos, dir, dist, flags); return check_sky ? payload_shadow.hit_type != SHADOW_SKY : payload_shadow.hit_type == SHADOW_HIT; #elif defined(RAY_QUERY) - const uint flags = 0 - //| gl_RayFlagsCullFrontFacingTrianglesEXT - //| gl_RayFlagsOpaqueEXT - | gl_RayFlagsTerminateOnFirstHitEXT - ; - rayQueryEXT rq; - shadowRayQuery(rq, pos, dir, dist, flags); + { + const uint flags = 0 + | gl_RayFlagsCullFrontFacingTrianglesEXT + | gl_RayFlagsOpaqueEXT + | gl_RayFlagsTerminateOnFirstHitEXT + ; + rayQueryEXT rq; + rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE, pos, 0., dir, dist - shadow_offset_fudge); + while (rayQueryProceedEXT(rq)) {} - if (!check_sky) { - return rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT; + if (rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT) { + if (!check_sky) + return true; + + const int instance_kusochki_offset = rayQueryGetIntersectionInstanceCustomIndexEXT(rq, true); + const int kusok_index = instance_kusochki_offset + rayQueryGetIntersectionGeometryIndexEXT(rq, true); + const uint tex_base_color = getKusok(kusok_index).tex_base_color; + if ((tex_base_color & KUSOK_MATERIAL_FLAG_SKYBOX) == 0) + return true; + } } - if (rayQueryGetIntersectionTypeEXT(rq, true) != gl_RayQueryCommittedIntersectionTriangleEXT) - return true; + return shadowTestAlphaMask(pos, dir, dist); - const int instance_kusochki_offset = rayQueryGetIntersectionInstanceCustomIndexEXT(rq, true); - const int kusok_index = instance_kusochki_offset + rayQueryGetIntersectionGeometryIndexEXT(rq, true); - const uint tex_base_color = getKusok(kusok_index).tex_base_color; - return (tex_base_color & KUSOK_MATERIAL_FLAG_SKYBOX) == 0; #else #error RAY_TRACE or RAY_QUERY #endif diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index f4307ad7..88ad0a4c 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -47,8 +47,9 @@ LIST_SPECIALIZATION_CONSTANTS(DECLARE_SPECIALIZATION_CONSTANT) #endif // not GLSL #define GEOMETRY_BIT_OPAQUE 0x01 -#define GEOMETRY_BIT_ADDITIVE 0x02 -#define GEOMETRY_BIT_REFRACTIVE 0x04 +#define GEOMETRY_BIT_ALPHA_TEST 0x02 +#define GEOMETRY_BIT_ADDITIVE 0x04 +#define GEOMETRY_BIT_REFRACTIVE 0x08 #define SHADER_OFFSET_MISS_REGULAR 0 #define SHADER_OFFSET_MISS_SHADOW 1 diff --git a/ref_vk/shaders/ray_primary.comp b/ref_vk/shaders/ray_primary.comp index 96eb77ac..6c9fc3e9 100644 --- a/ref_vk/shaders/ray_primary.comp +++ b/ref_vk/shaders/ray_primary.comp @@ -51,7 +51,7 @@ void main() { //| gl_RayFlagsSkipClosestHitShaderEXT ; const float L = 10000.; // TODO Why 10k? - rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE, origin, 0., direction, L); + rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE | GEOMETRY_BIT_ALPHA_TEST, origin, 0., direction, L); while (rayQueryProceedEXT(rq)) { if (0 != (rayQueryGetRayFlagsEXT(rq) & gl_RayFlagsOpaqueEXT)) continue; diff --git a/ref_vk/vk_ray_accel.c b/ref_vk/vk_ray_accel.c index da6638a9..efa00818 100644 --- a/ref_vk/vk_ray_accel.c +++ b/ref_vk/vk_ray_accel.c @@ -179,7 +179,7 @@ void RT_VkAccelPrepareTlas(VkCommandBuffer cmdbuf) { inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR; break; case MaterialMode_Opaque_AlphaTest: - inst[i].mask = GEOMETRY_BIT_OPAQUE; + inst[i].mask = GEOMETRY_BIT_ALPHA_TEST; inst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_ALPHA_TEST, inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR; break; From de6da4f03f3ae002b04741e0388a7ac9d56af619 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Fri, 3 Feb 2023 11:52:39 -0800 Subject: [PATCH 533/548] rt: add additive transparency known issues: - colors are incorrect (probably because of kusok.color having the wrong value) - mixes weirdly with denoiser --- ref_vk/shaders/ray_primary.comp | 51 +++++++++++++++++++++------------ ref_vk/shaders/rt_geometry.glsl | 33 +++++++++++++++++++++ 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/ref_vk/shaders/ray_primary.comp b/ref_vk/shaders/ray_primary.comp index 6c9fc3e9..a0f20c77 100644 --- a/ref_vk/shaders/ray_primary.comp +++ b/ref_vk/shaders/ray_primary.comp @@ -23,6 +23,31 @@ RAY_PRIMARY_OUTPUTS(X) layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; +vec3 traceAdditive(vec3 pos, vec3 dir, float L) { + const float additive_soft_overshoot = 16.; + vec3 ret = vec3(0., 0., 0.); + rayQueryEXT rq; + const uint flags = 0 + | gl_RayFlagsCullFrontFacingTrianglesEXT + //| gl_RayFlagsSkipClosestHitShaderEXT + | gl_RayFlagsNoOpaqueEXT // force all to be non-opaque + ; + rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_ADDITIVE, pos, 0., dir, L + additive_soft_overshoot); + while (rayQueryProceedEXT(rq)) { + const MiniGeometry geom = readCandidateMiniGeometry(rq); + const uint tex_base_color = getKusok(geom.kusok_index).tex_base_color; + const vec4 texture_color = texture(textures[nonuniformEXT(tex_base_color)], geom.uv); + const vec4 kusok_color = getKusok(geom.kusok_index).color; + const vec3 color = texture_color.rgb * kusok_color.rgb * texture_color.a; // * kusok_color.a; + //const vec3 color = texture_color.rgb * kusok_color.rgb * texture_color.a * kusok_color.a; + + const float hit_t = rayQueryGetIntersectionTEXT(rq, false); + const float overshoot = hit_t - L; + ret += color * smoothstep(additive_soft_overshoot, 0., overshoot); + } + return ret; +} + void main() { const ivec2 pix = ivec2(gl_GlobalInvocationID); const ivec2 res = ivec2(imageSize(out_position_t)); @@ -50,7 +75,7 @@ void main() { //| gl_RayFlagsTerminateOnFirstHitEXT //| gl_RayFlagsSkipClosestHitShaderEXT ; - const float L = 10000.; // TODO Why 10k? + float L = 10000.; // TODO Why 10k? rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE | GEOMETRY_BIT_ALPHA_TEST, origin, 0., direction, L); while (rayQueryProceedEXT(rq)) { if (0 != (rayQueryGetRayFlagsEXT(rq) & gl_RayFlagsOpaqueEXT)) @@ -63,24 +88,9 @@ void main() { // texture sampling for geometry that's ultimately invisible (i.e. behind walls). Also, shader threads congruence. // Separate pass could be more efficient as it'd be doing the same thing for every invocation. // 2. Same as the above, but also with a completely independent TLAS. Why: no need to mask-check geometry for opaque-vs-alpha - const uint instance_kusochki_offset = rayQueryGetIntersectionInstanceCustomIndexEXT(rq, false); - const uint geometry_index = rayQueryGetIntersectionGeometryIndexEXT(rq, false); - const uint kusok_index = instance_kusochki_offset + geometry_index; - const Kusok kusok = getKusok(kusok_index); - - const uint primitive_index = rayQueryGetIntersectionPrimitiveIndexEXT(rq, false); - const uint first_index_offset = kusok.index_offset + primitive_index * 3; - const uint vi1 = uint(getIndex(first_index_offset+0)) + kusok.vertex_offset; - const uint vi2 = uint(getIndex(first_index_offset+1)) + kusok.vertex_offset; - const uint vi3 = uint(getIndex(first_index_offset+2)) + kusok.vertex_offset; - const vec2 uvs[3] = { - getVertex(vi1).gl_tc, - getVertex(vi2).gl_tc, - getVertex(vi3).gl_tc, - }; - const vec2 bary = rayQueryGetIntersectionBarycentricsEXT(rq, false); - const vec2 uv = baryMix(uvs[0], uvs[1], uvs[2], bary); - const vec4 texture_color = texture(textures[nonuniformEXT(kusok.tex_base_color)], uv); + const MiniGeometry geom = readCandidateMiniGeometry(rq); + const uint tex_base_color = getKusok(geom.kusok_index).tex_base_color; + const vec4 texture_color = texture(textures[nonuniformEXT(tex_base_color)], geom.uv); const float alpha_mask_threshold = .1f; if (texture_color.a >= alpha_mask_threshold) { @@ -90,8 +100,11 @@ void main() { if (rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT) { primaryRayHit(rq, payload); + L = rayQueryGetIntersectionTEXT(rq, true); } + payload.emissive.rgb += traceAdditive(origin, direction, L); + imageStore(out_position_t, pix, payload.hit_t); imageStore(out_base_color_a, pix, payload.base_color_a); imageStore(out_normals_gs, pix, payload.normals_gs); diff --git a/ref_vk/shaders/rt_geometry.glsl b/ref_vk/shaders/rt_geometry.glsl index 5fbee368..87b42e72 100644 --- a/ref_vk/shaders/rt_geometry.glsl +++ b/ref_vk/shaders/rt_geometry.glsl @@ -125,4 +125,37 @@ Geometry readHitGeometry(vec2 bary, float ray_cone_width) { return geom; } + +#ifdef RAY_QUERY +struct MiniGeometry { + vec2 uv; + uint kusok_index; +}; + +MiniGeometry readCandidateMiniGeometry(rayQueryEXT rq) { + const uint instance_kusochki_offset = rayQueryGetIntersectionInstanceCustomIndexEXT(rq, false); + const uint geometry_index = rayQueryGetIntersectionGeometryIndexEXT(rq, false); + const uint kusok_index = instance_kusochki_offset + geometry_index; + const Kusok kusok = getKusok(kusok_index); + + const uint primitive_index = rayQueryGetIntersectionPrimitiveIndexEXT(rq, false); + const uint first_index_offset = kusok.index_offset + primitive_index * 3; + const uint vi1 = uint(getIndex(first_index_offset+0)) + kusok.vertex_offset; + const uint vi2 = uint(getIndex(first_index_offset+1)) + kusok.vertex_offset; + const uint vi3 = uint(getIndex(first_index_offset+2)) + kusok.vertex_offset; + const vec2 uvs[3] = { + getVertex(vi1).gl_tc, + getVertex(vi2).gl_tc, + getVertex(vi3).gl_tc, + }; + const vec2 bary = rayQueryGetIntersectionBarycentricsEXT(rq, false); + const vec2 uv = baryMix(uvs[0], uvs[1], uvs[2], bary); + + MiniGeometry ret; + ret.uv = uv; + ret.kusok_index = kusok_index; + return ret; +} +#endif // #ifdef RAY_QUERY + #endif // RT_GEOMETRY_GLSL_INCLUDED From 51641234ab9547234c20fa72dd1000eb17f6c7e4 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Fri, 3 Feb 2023 12:00:29 -0800 Subject: [PATCH 534/548] rt: fixup additive color --- ref_vk/shaders/ray_primary.comp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ref_vk/shaders/ray_primary.comp b/ref_vk/shaders/ray_primary.comp index a0f20c77..b4fd7aa3 100644 --- a/ref_vk/shaders/ray_primary.comp +++ b/ref_vk/shaders/ray_primary.comp @@ -37,9 +37,8 @@ vec3 traceAdditive(vec3 pos, vec3 dir, float L) { const MiniGeometry geom = readCandidateMiniGeometry(rq); const uint tex_base_color = getKusok(geom.kusok_index).tex_base_color; const vec4 texture_color = texture(textures[nonuniformEXT(tex_base_color)], geom.uv); - const vec4 kusok_color = getKusok(geom.kusok_index).color; - const vec3 color = texture_color.rgb * kusok_color.rgb * texture_color.a; // * kusok_color.a; - //const vec3 color = texture_color.rgb * kusok_color.rgb * texture_color.a * kusok_color.a; + const vec3 kusok_emissive = getKusok(geom.kusok_index).emissive; + const vec3 color = texture_color.rgb * kusok_emissive * texture_color.a; // * kusok_color.a; const float hit_t = rayQueryGetIntersectionTEXT(rq, false); const float overshoot = hit_t - L; From 5da5a1bc94fa7d039e35e271e52f01f5e1e12acb Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Sat, 4 Feb 2023 10:32:27 -0800 Subject: [PATCH 535/548] rt: collect stats on dirty light cells --- ref_vk/vk_framectl.c | 10 +++++++++- ref_vk/vk_light.c | 9 +++++++++ ref_vk/vk_light.h | 6 ++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/ref_vk/vk_framectl.c b/ref_vk/vk_framectl.c index beb2148e..590c52c6 100644 --- a/ref_vk/vk_framectl.c +++ b/ref_vk/vk_framectl.c @@ -11,6 +11,9 @@ #include "vk_staging.h" #include "vk_commandpool.h" +#include "vk_light.h" // For stats +#include "shaders/ray_interop.h" // stats: struct LightCluster + #include "profiler.h" #include "eiface.h" // ARRAYSIZE @@ -182,8 +185,13 @@ static void updateGamma( void ) { } } -// FIXME move this to r print speeds or something like that +// FIXME move this to r_speeds or something like that static void showProfilingData( void ) { + { + const int dirty = g_lights.stats.dirty_cells; + gEngine.Con_NPrintf(4, "Dirty light cells: %d, estimated size = %dKiB\n", dirty, (int)(dirty * sizeof(struct LightCluster) / 1024)); + } + gEngine.Con_NPrintf(5, "Perf scopes:"); for (int i = 0; i < g_aprof.num_scopes; ++i) { const aprof_scope_t *const scope = g_aprof.scopes + i; diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index cb9aa086..9a5a80cb 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -536,6 +536,7 @@ void RT_LightsNewMapBegin( const struct model_s *map ) { vk_lights_cell_t *const cell = g_lights.cells + i; cell->num_point_lights = cell->num_static.point_lights = 0; cell->num_polygons = cell->num_static.polygons = 0; + cell->dirty = true; } } } @@ -545,10 +546,16 @@ void RT_LightsFrameBegin( void ) { g_lights_.num_point_lights = g_lights_.num_static.point_lights; g_lights_.num_polygon_vertices = g_lights_.num_static.polygon_vertices; + g_lights.stats.dirty_cells = 0; + for (int i = 0; i < g_lights.map.grid_cells; ++i) { vk_lights_cell_t *const cell = g_lights.cells + i; cell->num_polygons = cell->num_static.polygons; cell->num_point_lights = cell->num_static.point_lights; + + if (cell->dirty) + ++g_lights.stats.dirty_cells; + cell->dirty = false; } } @@ -564,6 +571,7 @@ static qboolean addSurfaceLightToCell( int cell_index, int polygon_light_index ) } cluster->polygons[cluster->num_polygons++] = polygon_light_index; + cluster->dirty = true; return true; } @@ -578,6 +586,7 @@ static qboolean addLightToCell( int cell_index, int light_index ) { } cluster->point_lights[cluster->num_point_lights++] = light_index; + cluster->dirty = true; return true; } diff --git a/ref_vk/vk_light.h b/ref_vk/vk_light.h index 0aee4eee..9bc4a424 100644 --- a/ref_vk/vk_light.h +++ b/ref_vk/vk_light.h @@ -16,6 +16,8 @@ typedef struct { uint8_t point_lights; uint8_t polygons; } num_static; + + qboolean dirty; } vk_lights_cell_t; typedef struct { @@ -57,6 +59,10 @@ typedef struct { } map; vk_lights_cell_t cells[MAX_LIGHT_CLUSTERS]; + + struct { + int dirty_cells; + } stats; } vk_lights_t; extern vk_lights_t g_lights; From 05731863341a9719c911abf8558efb8918799cbb Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sun, 5 Feb 2023 23:35:08 -0800 Subject: [PATCH 536/548] rt: upload dirty light clusters; use them in shaders known issues: - -vkvalidate performance is bad, as it has to validate dozens-hundreds of buffer uploads - still doesn't solve the case where entire map ends up being updated --- ref_vk/shaders/light.glsl | 14 ++-- ref_vk/shaders/light_polygon.glsl | 14 ++-- ref_vk/shaders/ray_interop.h | 5 ++ ref_vk/vk_framectl.c | 2 +- ref_vk/vk_light.c | 103 +++++++++++++++++++++--------- ref_vk/vk_light.h | 5 +- ref_vk/vk_rtx.c | 6 +- 7 files changed, 98 insertions(+), 51 deletions(-) diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index d880e271..101f52b7 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -1,6 +1,6 @@ layout (set = 0, binding = BINDING_LIGHTS) readonly buffer SBOLights { LightsMetadata m; } lights; layout (set = 0, binding = BINDING_LIGHT_CLUSTERS, align = 1) readonly buffer UBOLightClusters { - ivec3 grid_min, grid_size; + //ivec3 grid_min, grid_size; //uint8_t clusters_data[MAX_LIGHT_CLUSTERS * LIGHT_CLUSTER_SIZE + HACK_OFFSET]; LightCluster clusters_[MAX_LIGHT_CLUSTERS]; } light_grid; @@ -25,11 +25,11 @@ void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec diffuse = specular = vec3(0.); //diffuse = vec3(1.);//float(lights.m.num_point_lights) / 64.); -//#define USE_CLUSTERS +#define USE_CLUSTERS #ifdef USE_CLUSTERS - const uint num_point_lights = uint(light_grid.clusters[cluster_index].num_point_lights); + const uint num_point_lights = uint(light_grid.clusters_[cluster_index].num_point_lights); for (uint j = 0; j < num_point_lights; ++j) { - const uint i = uint(light_grid.clusters[cluster_index].point_lights[j]); + const uint i = uint(light_grid.clusters_[cluster_index].point_lights[j]); #else for (uint i = 0; i < lights.m.num_point_lights; ++i) { #endif @@ -116,11 +116,11 @@ void computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 throughput, vec void computeLighting(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) { diffuse = specular = vec3(0.); - const ivec3 light_cell = ivec3(floor(P / LIGHT_GRID_CELL_SIZE)) - light_grid.grid_min; - const uint cluster_index = uint(dot(light_cell, ivec3(1, light_grid.grid_size.x, light_grid.grid_size.x * light_grid.grid_size.y))); + const ivec3 light_cell = ivec3(floor(P / LIGHT_GRID_CELL_SIZE)) - lights.m.grid_min_cell; + const uint cluster_index = uint(dot(light_cell, ivec3(1, lights.m.grid_size.x, lights.m.grid_size.x * lights.m.grid_size.y))); #ifdef USE_CLUSTERS - if (any(greaterThanEqual(light_cell, light_grid.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) + if (any(greaterThanEqual(light_cell, lights.m.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) return; // throughput * vec3(1., 0., 0.); #endif diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 2b2a986d..42d091e1 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -191,12 +191,12 @@ void sampleSinglePolygonLight(in vec3 P, in vec3 N, in vec3 view_dir, in SampleC #if 0 // Sample random one void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) { - const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); + const uint num_polygons = uint(light_grid.clusters_[cluster_index].num_polygons); if (num_polygons == 0) return; - const uint selected = uint(light_grid.clusters[cluster_index].polygons[rand_range(num_polygons)]); + const uint selected = uint(light_grid.clusters_[cluster_index].polygons[rand_range(num_polygons)]); const PolygonLight poly = lights.m.polygons[selected]; const SampleContext ctx = buildSampleContext(P, N, view_dir); @@ -212,11 +212,11 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate #if DO_ALL_IN_CLUSTER const SampleContext ctx = buildSampleContext(P, N, view_dir); -//#define USE_CLUSTERS +#define USE_CLUSTERS #ifdef USE_CLUSTERS - const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); + const uint num_polygons = uint(light_grid.clusters_[cluster_index].num_polygons); for (uint i = 0; i < num_polygons; ++i) { - const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); + const uint index = uint(light_grid.clusters_[cluster_index].polygons[i]); #else for (uint index = 0; index < lights.m.num_polygons; ++index) { #endif @@ -257,7 +257,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate #ifdef USE_CLUSTERS // TODO move this to pickPolygonLight function - const uint num_polygons = uint(light_grid.clusters[cluster_index].num_polygons); + const uint num_polygons = uint(light_grid.clusters_[cluster_index].num_polygons); #else const uint num_polygons = lights.m.num_polygons; #endif @@ -267,7 +267,7 @@ void sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, Mate float eps1 = rand01(); for (uint i = 0; i < num_polygons; ++i) { #ifdef USE_CLUSTERS - const uint index = uint(light_grid.clusters[cluster_index].polygons[i]); + const uint index = uint(light_grid.clusters_[cluster_index].polygons[i]); #else const uint index = i; #endif diff --git a/ref_vk/shaders/ray_interop.h b/ref_vk/shaders/ray_interop.h index 88ad0a4c..4e52f7d9 100644 --- a/ref_vk/shaders/ray_interop.h +++ b/ref_vk/shaders/ray_interop.h @@ -21,6 +21,7 @@ #define vec3 vec3_t #define vec4 vec4_t #define mat4 matrix4x4 +typedef int ivec3[3]; #define TOKENPASTE(x, y) x ## y #define TOKENPASTE2(x, y) TOKENPASTE(x, y) #define PAD(x) float TOKENPASTE2(pad_, __LINE__)[x]; @@ -111,6 +112,10 @@ struct LightsMetadata { uint num_polygons; uint num_point_lights; PAD(2) + ivec3 grid_min_cell; + PAD(1) + ivec3 grid_size; + PAD(1) STRUCT PointLight point_lights[MAX_POINT_LIGHTS]; STRUCT PolygonLight polygons[MAX_EMISSIVE_KUSOCHKI]; vec4 polygon_vertices[MAX_EMISSIVE_KUSOCHKI * 7]; // vec3 but aligned diff --git a/ref_vk/vk_framectl.c b/ref_vk/vk_framectl.c index 590c52c6..68f31a75 100644 --- a/ref_vk/vk_framectl.c +++ b/ref_vk/vk_framectl.c @@ -189,7 +189,7 @@ static void updateGamma( void ) { static void showProfilingData( void ) { { const int dirty = g_lights.stats.dirty_cells; - gEngine.Con_NPrintf(4, "Dirty light cells: %d, estimated size = %dKiB\n", dirty, (int)(dirty * sizeof(struct LightCluster) / 1024)); + gEngine.Con_NPrintf(4, "Dirty light cells: %d, size = %dKiB, ranges = %d\n", dirty, (int)(dirty * sizeof(struct LightCluster) / 1024), g_lights.stats.ranges_uploaded); } gEngine.Con_NPrintf(5, "Perf scopes:"); diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index 9a5a80cb..41e7cac6 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -38,16 +38,6 @@ typedef struct { qboolean set; } vk_emissive_texture_t; -typedef struct { - int min_cell[4], size[3]; // 4th element is padding - struct LightCluster cells[MAX_LIGHT_CLUSTERS]; -} vk_ray_shader_light_grid_t; - -struct Lights { - struct LightsMetadata metadata; - vk_ray_shader_light_grid_t grid; -}; - static struct { struct { vk_emissive_texture_t emissive_textures[MAX_TEXTURES]; @@ -72,6 +62,7 @@ static struct { bit_array_t visited_cells; + uint32_t frame_sequence; } g_lights_; static struct { @@ -95,7 +86,9 @@ qboolean VK_LightsInit( void ) { gEngine.Cmd_AddCommand("vk_lights_dump", debugDumpLights, "Dump all light sources for next frame"); - if (!VK_BufferCreate("rt lights buffer", &g_lights_.buffer, sizeof(struct Lights), + const int buffer_size = sizeof(struct LightsMetadata) + sizeof(struct LightCluster) * MAX_LIGHT_CLUSTERS; + + if (!VK_BufferCreate("rt lights buffer", &g_lights_.buffer, buffer_size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { // FIXME complain, handle @@ -536,7 +529,7 @@ void RT_LightsNewMapBegin( const struct model_s *map ) { vk_lights_cell_t *const cell = g_lights.cells + i; cell->num_point_lights = cell->num_static.point_lights = 0; cell->num_polygons = cell->num_static.polygons = 0; - cell->dirty = true; + cell->frame_sequence = g_lights_.frame_sequence; } } } @@ -552,10 +545,6 @@ void RT_LightsFrameBegin( void ) { vk_lights_cell_t *const cell = g_lights.cells + i; cell->num_polygons = cell->num_static.polygons; cell->num_point_lights = cell->num_static.point_lights; - - if (cell->dirty) - ++g_lights.stats.dirty_cells; - cell->dirty = false; } } @@ -571,7 +560,10 @@ static qboolean addSurfaceLightToCell( int cell_index, int polygon_light_index ) } cluster->polygons[cluster->num_polygons++] = polygon_light_index; - cluster->dirty = true; + if (cluster->frame_sequence != g_lights_.frame_sequence) { + ++g_lights.stats.dirty_cells; + cluster->frame_sequence = g_lights_.frame_sequence; + } return true; } @@ -586,7 +578,11 @@ static qboolean addLightToCell( int cell_index, int light_index ) { } cluster->point_lights[cluster->num_point_lights++] = light_index; - cluster->dirty = true; + + if (cluster->frame_sequence != g_lights_.frame_sequence) { + ++g_lights.stats.dirty_cells; + cluster->frame_sequence = g_lights_.frame_sequence; + } return true; } @@ -952,6 +948,8 @@ void RT_LightsNewMapEnd( const struct model_s *map ) { cell->num_static.polygons = cell->num_polygons; } } + + g_lights.stats.dirty_cells = g_lights.map.grid_cells; } qboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id ) { @@ -1133,21 +1131,59 @@ int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) { } } -static void uploadGrid( vk_ray_shader_light_grid_t *grid ) { - ASSERT(g_lights.map.grid_cells <= MAX_LIGHT_CLUSTERS); +static void uploadGridRange( int begin, int end ) { + const int count = end - begin; + ASSERT( count > 0 ); - VectorCopy(g_lights.map.grid_min_cell, grid->min_cell); - VectorCopy(g_lights.map.grid_size, grid->size); + const int size = count * sizeof(struct LightCluster); + const vk_staging_region_t locked = R_VkStagingLockForBuffer( (vk_staging_buffer_args_t) { + .buffer = g_lights_.buffer.buffer, + .offset = sizeof(struct LightsMetadata) + begin * sizeof(struct LightCluster), + .size = size, + .alignment = 16, // WHY? + } ); - for (int i = 0; i < g_lights.map.grid_cells; ++i) { - const vk_lights_cell_t *const src = g_lights.cells + i; - struct LightCluster *const dst = grid->cells + i; + ASSERT(locked.ptr); + + struct LightCluster *const grid = locked.ptr; + memset(grid, 0, size); + + for (int i = 0; i < count; ++i) { + const vk_lights_cell_t *const src = g_lights.cells + i + begin; + struct LightCluster *const dst = grid + i; dst->num_point_lights = src->num_point_lights; dst->num_polygons = src->num_polygons; memcpy(dst->point_lights, src->point_lights, sizeof(uint8_t) * src->num_point_lights); memcpy(dst->polygons, src->polygons, sizeof(uint8_t) * src->num_polygons); } + + R_VkStagingUnlock( locked.handle ); + + g_lights.stats.ranges_uploaded++; +} + +static void uploadGrid( void ) { + ASSERT(g_lights.map.grid_cells <= MAX_LIGHT_CLUSTERS); + + g_lights.stats.ranges_uploaded = 0; + + int begin = -1; + for (int i = 0; i < g_lights.map.grid_cells; ++i) { + const vk_lights_cell_t *const cell = g_lights.cells + i; + + const qboolean dirty = cell->frame_sequence == g_lights_.frame_sequence; + if (dirty && begin < 0) + begin = i; + + if (!dirty && begin >= 0) { + uploadGridRange(begin, i); + begin = -1; + } + } + + if (begin >= 0) + uploadGridRange(begin, g_lights.map.grid_cells); } static void uploadPolygonLights( struct LightsMetadata *metadata ) { @@ -1198,26 +1234,31 @@ static void uploadPointLights( struct LightsMetadata *metadata ) { } } -vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { - +vk_lights_bindings_t VK_LightsUpload( void ) { const vk_staging_region_t locked = R_VkStagingLockForBuffer( (vk_staging_buffer_args_t) { .buffer = g_lights_.buffer.buffer, .offset = 0, .size = sizeof(struct LightsMetadata), - .alignment = 16, + .alignment = 16, // WHY? } ); ASSERT(locked.ptr); struct LightsMetadata *metadata = locked.ptr; + memset(metadata, 0, sizeof(*metadata)); + + VectorCopy(g_lights.map.grid_min_cell, metadata->grid_min_cell); + VectorCopy(g_lights.map.grid_size, metadata->grid_size); uploadPolygonLights( metadata ); uploadPointLights( metadata ); - // FIXME uploadGrid( &lights->grid ); - R_VkStagingUnlock( locked.handle ); + uploadGrid(); + + g_lights_.frame_sequence++; + return (vk_lights_bindings_t){ .buffer = g_lights_.buffer.buffer, .metadata = { @@ -1226,7 +1267,7 @@ vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer cmdbuf ) { }, .grid = { .offset = sizeof(struct LightsMetadata), - .size = sizeof(vk_ray_shader_light_grid_t), + .size = sizeof(struct LightCluster) * MAX_LIGHT_CLUSTERS, }, }; } diff --git a/ref_vk/vk_light.h b/ref_vk/vk_light.h index 9bc4a424..6d83c0a5 100644 --- a/ref_vk/vk_light.h +++ b/ref_vk/vk_light.h @@ -17,7 +17,7 @@ typedef struct { uint8_t polygons; } num_static; - qboolean dirty; + uint32_t frame_sequence; } vk_lights_cell_t; typedef struct { @@ -62,6 +62,7 @@ typedef struct { struct { int dirty_cells; + int ranges_uploaded; } stats; } vk_lights_t; @@ -83,7 +84,7 @@ typedef struct { uint32_t offset, size; } metadata, grid; } vk_lights_bindings_t; -vk_lights_bindings_t VK_LightsUpload( VkCommandBuffer ); +vk_lights_bindings_t VK_LightsUpload( void ); qboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id ); diff --git a/ref_vk/vk_rtx.c b/ref_vk/vk_rtx.c index 2ac2ad2b..05ed3ecb 100644 --- a/ref_vk/vk_rtx.c +++ b/ref_vk/vk_rtx.c @@ -47,7 +47,7 @@ X(Buffer, indices) \ X(Buffer, vertices) \ X(Buffer, lights) \ - X(Buffer, light_clusters) \ + X(Buffer, light_grid) \ X(Texture, textures) \ X(Texture, skybox) @@ -219,7 +219,7 @@ static void performTracing(VkCommandBuffer cmdbuf, const perform_tracing_args_t* // TODO move this to lights RES_SET_BUFFER(lights, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, args->light_bindings->buffer, args->light_bindings->metadata.offset, args->light_bindings->metadata.size); - RES_SET_BUFFER(light_clusters, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, args->light_bindings->buffer, args->light_bindings->grid.offset, args->light_bindings->grid.size); + RES_SET_BUFFER(light_grid, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, args->light_bindings->buffer, args->light_bindings->grid.offset, args->light_bindings->grid.size); #undef RES_SET_SBUFFER_FULL #undef RES_SET_BUFFER @@ -547,7 +547,7 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args) // FIXME pass these matrices explicitly to let RTX module handle ubo itself RT_LightsFrameEnd(); - const vk_lights_bindings_t light_bindings = VK_LightsUpload(cmdbuf); + const vk_lights_bindings_t light_bindings = VK_LightsUpload(); g_rtx.frame_number++; From 718871145114cd92559e33bf8d430cfbb0b1bc63 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Tue, 7 Feb 2023 10:46:57 -0800 Subject: [PATCH 537/548] rt: add test color-only bounce no lighting yet, just base color --- ref_vk/shaders/bounce.comp | 134 +++++++++++++++++++++++++++++++++++ ref_vk/shaders/denoiser.comp | 6 +- ref_vk/shaders/rt.json | 3 + 3 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 ref_vk/shaders/bounce.comp diff --git a/ref_vk/shaders/bounce.comp b/ref_vk/shaders/bounce.comp new file mode 100644 index 00000000..29242454 --- /dev/null +++ b/ref_vk/shaders/bounce.comp @@ -0,0 +1,134 @@ +#version 460 core +#extension GL_GOOGLE_include_directive : require +#extension GL_EXT_nonuniform_qualifier : enable +#extension GL_EXT_ray_query: require + +#define RAY_QUERY +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set=0, binding=1) uniform accelerationStructureEXT tlas; + +#define RAY_LIGHT_DIRECT_INPUTS(X) \ + X(10, position_t, rgba32f) \ + X(11, normals_gs, rgba16f) \ + X(12, material_rmxx, rgba8) +#define X(index, name, format) layout(set=0,binding=index,format) uniform readonly image2D name; +RAY_LIGHT_DIRECT_INPUTS(X) +#undef X + +layout(set=0, binding=20, rgba16f) uniform writeonly image2D out_indirect_diffuse; + +#include "ray_primary_common.glsl" +#include "ray_primary_hit.glsl" +#include "brdf.h" +#include "noise.glsl" + +void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { + const vec4 n = imageLoad(normals_gs, uv); + geometry_normal = normalDecode(n.xy); + shading_normal = normalDecode(n.zw); +} + + +bool getHit(vec3 origin, vec3 direction, inout RayPayloadPrimary payload) { + rayQueryEXT rq; + const uint flags = 0 + | gl_RayFlagsCullFrontFacingTrianglesEXT + //| gl_RayFlagsOpaqueEXT + //| gl_RayFlagsTerminateOnFirstHitEXT + //| gl_RayFlagsSkipClosestHitShaderEXT + ; + const float L = 10000.; // TODO Why 10k? + rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE | GEOMETRY_BIT_ALPHA_TEST, origin, 0., direction, L); + while (rayQueryProceedEXT(rq)) { + if (0 != (rayQueryGetRayFlagsEXT(rq) & gl_RayFlagsOpaqueEXT)) + continue; + + // alpha test + // TODO check other possible ways of doing alpha test. They might be more efficient + // (although in this particular primary ray case it's not taht important): + // 1. Do a separate ray query for alpha masked geometry. Reason: here we might accidentally do the expensive + // texture sampling for geometry that's ultimately invisible (i.e. behind walls). Also, shader threads congruence. + // Separate pass could be more efficient as it'd be doing the same thing for every invocation. + // 2. Same as the above, but also with a completely independent TLAS. Why: no need to mask-check geometry for opaque-vs-alpha + const MiniGeometry geom = readCandidateMiniGeometry(rq); + const uint tex_base_color = getKusok(geom.kusok_index).tex_base_color; + const vec4 texture_color = texture(textures[nonuniformEXT(tex_base_color)], geom.uv); + + const float alpha_mask_threshold = .1f; + if (texture_color.a >= alpha_mask_threshold) { + rayQueryConfirmIntersectionEXT(rq); + } + } + + if (rayQueryGetIntersectionTypeEXT(rq, true) != gl_RayQueryCommittedIntersectionTriangleEXT) + return false; + + primaryRayHit(rq, payload); + //L = rayQueryGetIntersectionTEXT(rq, true); + return true; +} + +vec3 computeBounce(ivec2 pix, vec3 direction) { + const vec4 material_data = imageLoad(material_rmxx, pix); + + MaterialProperties material; + material.baseColor = vec3(1.); + material.emissive = vec3(0.f); + material.metalness = material_data.g; + material.roughness = material_data.r; + + vec3 geometry_normal, shading_normal; + readNormals(pix, geometry_normal, shading_normal); + + const float ray_normal_fudge = .01; + const vec3 pos = imageLoad(position_t, pix).xyz + geometry_normal * ray_normal_fudge; + vec3 throughput = vec3(1.); + + // 1. Make a "random" material-based ray for diffuse lighting + vec3 bounce_direction = vec3(0.); + vec3 brdf_weight = vec3(0.); + const int brdf_type = DIFFUSE_TYPE; + //const int brdf_type = SPECULAR_TYPE; // FIXME test + const vec2 u = vec2(rand01(), rand01()); + if (!evalIndirectCombinedBRDF(u, shading_normal, geometry_normal, -direction, material, brdf_type, bounce_direction, brdf_weight)) + return vec3(0.);//vec3(1., 0., 0.); + + const float throughput_threshold = 1e-3; + throughput *= brdf_weight; + if (dot(throughput, throughput) < throughput_threshold) + return vec3(0.);//, 1., 0.); + + // 2. Rake yuri it, get hit + // 3. Get relevant Geometry data + RayPayloadPrimary payload; + payload.base_color_a = vec4(0.); + if (!getHit(pos, bounce_direction, payload)) + return vec3(0.);//, 0., 1.); + + // FIXME test + return payload.base_color_a.rgb * throughput; + + // TODO + // 4. Sample light sources + //vec3 diffuse = vec3(0.), specular = vec3(0.); + //computeLighting(pos + geometry_normal * .001, shading_normal, throughput, -direction, material, diffuse, specular); +} + +void main() { + const ivec2 pix = ivec2(gl_GlobalInvocationID); + const ivec2 res = ivec2(imageSize(out_indirect_diffuse)); + if (any(greaterThanEqual(pix, res))) { + return; + } + const vec2 uv = (gl_GlobalInvocationID.xy + .5) / res * 2. - 1.; + + const vec3 origin = (ubo.ubo.inv_view * vec4(0, 0, 0, 1)).xyz; + const vec4 target = ubo.ubo.inv_proj * vec4(uv.x, uv.y, 1, 1); + const vec3 direction = normalize((ubo.ubo.inv_view * vec4(target.xyz, 0)).xyz); + + rand01_state = ubo.ubo.random_seed + pix.x * 1833 + pix.y * 31337 + 12; + + const vec3 diffuse = computeBounce(pix, direction); + imageStore(out_indirect_diffuse, pix, vec4(diffuse, 0.f)); +} diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 05e68864..1495e351 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -11,7 +11,6 @@ layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; layout(set = 0, binding = 0, rgba16f) uniform image2D out_dest; -layout(set = 0, binding = 9, rgba16f) uniform readonly image2D prev_dest; layout(set = 0, binding = 1, rgba8) uniform readonly image2D base_color_a; @@ -24,10 +23,13 @@ layout(set = 0, binding = 6, rgba16f) uniform readonly image2D emissive; layout(set = 0, binding = 7, rgba32f) uniform readonly image2D position_t; layout(set = 0, binding = 8, rgba16f) uniform readonly image2D normals_gs; +layout(set = 0, binding = 9, rgba16f) uniform readonly image2D prev_dest; layout(set = 0, binding = 10, rgba32f) uniform readonly image2D geometry_prev_position; layout(set = 0, binding = 11) uniform UBO { UniformBuffer ubo; } ubo; +layout(set = 0, binding = 12, rgba16f) uniform readonly image2D indirect_diffuse; + //layout(set = 0, binding = 2, rgba16f) uniform readonly image2D light_poly_diffuse; /* layout(set = 0, binding = 3, rgba16f) uniform readonly image2D specular; */ /* layout(set = 0, binding = 4, rgba16f) uniform readonly image2D additive; */ @@ -97,6 +99,7 @@ void main() { //imageStore(out_dest, pix, vec4(imageLoad(specular, pix)*.5 + .5f)); return; //imageStore(out_dest, pix, vec4(aces_tonemap(imageLoad(light_poly, pix).rgb), 0.)); return; //imageStore(out_dest, pix, vec4(aces_tonemap(imageLoad(specular, pix).rgb), 0.)); return; + //imageStore(out_dest, pix, vec4(aces_tonemap(imageLoad(indirect_diffuse, pix).rgb), 0.)); return; /* vec3 geometry_normal, shading_normal; @@ -151,6 +154,7 @@ void main() { vec3 diffuse = vec3(0.); diffuse += imageLoad(light_point_diffuse, p).rgb; diffuse += imageLoad(light_poly_diffuse, p).rgb; + diffuse += imageLoad(indirect_diffuse, p).rgb; vec3 specular = vec3(0.); specular += imageLoad(light_poly_specular, p).rgb; diff --git a/ref_vk/shaders/rt.json b/ref_vk/shaders/rt.json index 23652e30..f66eb0cd 100644 --- a/ref_vk/shaders/rt.json +++ b/ref_vk/shaders/rt.json @@ -35,6 +35,9 @@ "light_direct_point": { "comp": "ray_light_direct_point" }, + "bounce": { + "comp": "bounce" + }, "denoiser": { "comp": "denoiser" }, From ea7879bff8f49fe7607b712bd6ba0d484e66f49b Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Tue, 7 Feb 2023 11:13:10 -0800 Subject: [PATCH 538/548] rt: add lighting to bounce no specular just yet --- ref_vk/shaders/bounce.comp | 37 +++++++++++++++++++++++----- ref_vk/shaders/light.glsl | 9 ++++++- ref_vk/shaders/light_common.glsl | 2 ++ ref_vk/shaders/ray_light_direct.glsl | 14 +++++------ 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/ref_vk/shaders/bounce.comp b/ref_vk/shaders/bounce.comp index 29242454..386ce8a5 100644 --- a/ref_vk/shaders/bounce.comp +++ b/ref_vk/shaders/bounce.comp @@ -20,9 +20,23 @@ layout(set=0, binding=20, rgba16f) uniform writeonly image2D out_indirect_diffus #include "ray_primary_common.glsl" #include "ray_primary_hit.glsl" -#include "brdf.h" +//#include "brdf.h" // already in light.glsl below #include "noise.glsl" +#define TEXTURES_INCLUDED_ALREADY_FIXME +#define LIGHT_POLYGON 1 +#define LIGHT_POINT 1 +#undef SHADER_OFFSET_HIT_SHADOW_BASE +#define SHADER_OFFSET_HIT_SHADOW_BASE 0 +#undef SHADER_OFFSET_MISS_SHADOW +#define SHADER_OFFSET_MISS_SHADOW 0 +#undef PAYLOAD_LOCATION_SHADOW +#define PAYLOAD_LOCATION_SHADOW 0 + +#define BINDING_LIGHTS 7 +#define BINDING_LIGHT_CLUSTERS 8 +#include "light.glsl" + void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { const vec4 n = imageLoad(normals_gs, uv); geometry_normal = normalDecode(n.xy); @@ -106,13 +120,24 @@ vec3 computeBounce(ivec2 pix, vec3 direction) { if (!getHit(pos, bounce_direction, payload)) return vec3(0.);//, 0., 1.); - // FIXME test - return payload.base_color_a.rgb * throughput; + throughput *= payload.base_color_a.rgb; - // TODO // 4. Sample light sources - //vec3 diffuse = vec3(0.), specular = vec3(0.); - //computeLighting(pos + geometry_normal * .001, shading_normal, throughput, -direction, material, diffuse, specular); + vec3 diffuse = vec3(0.); + { + vec3 specular = vec3(0.); + const vec3 hit_pos = payload.hit_t.xyz; + const vec3 hit_shading_normal = normalDecode(payload.normals_gs.zw); + + MaterialProperties hit_material; + hit_material.baseColor = vec3(1.); + hit_material.emissive = vec3(0.f); + hit_material.metalness = payload.material_rmxx.g; + hit_material.roughness = payload.material_rmxx.r; + computeLighting(hit_pos, hit_shading_normal, throughput, -bounce_direction, hit_material, diffuse, specular); + } + + return diffuse; } void main() { diff --git a/ref_vk/shaders/light.glsl b/ref_vk/shaders/light.glsl index 101f52b7..69ac2bfe 100644 --- a/ref_vk/shaders/light.glsl +++ b/ref_vk/shaders/light.glsl @@ -1,3 +1,4 @@ +#extension GL_EXT_control_flow_attributes : require layout (set = 0, binding = BINDING_LIGHTS) readonly buffer SBOLights { LightsMetadata m; } lights; layout (set = 0, binding = BINDING_LIGHT_CLUSTERS, align = 1) readonly buffer UBOLightClusters { //ivec3 grid_min, grid_size; @@ -138,12 +139,18 @@ void computeLighting(vec3 P, vec3 N, vec3 throughput, vec3 view_dir, MaterialPro #if LIGHT_POLYGON sampleEmissiveSurfaces(P, N, throughput, view_dir, material, cluster_index, diffuse, specular); + // These constants are empirical. There's no known math reason behind them + diffuse /= 25.0; + specular /= 25.0; #endif - #if LIGHT_POINT vec3 ldiffuse = vec3(0.), lspecular = vec3(0.); computePointLights(P, N, cluster_index, throughput, view_dir, material, ldiffuse, lspecular); + // These constants are empirical. There's no known math reason behind them + ldiffuse /= 4.; + lspecular /= 4.; + diffuse += ldiffuse; specular += lspecular; #endif diff --git a/ref_vk/shaders/light_common.glsl b/ref_vk/shaders/light_common.glsl index 5c3c2190..ee2cba92 100644 --- a/ref_vk/shaders/light_common.glsl +++ b/ref_vk/shaders/light_common.glsl @@ -4,7 +4,9 @@ #include "ray_kusochki.glsl" +#ifndef TEXTURES_INCLUDED_ALREADY_FIXME layout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES]; +#endif #ifdef RAY_TRACE2 #include "ray_shadow_interface.glsl" diff --git a/ref_vk/shaders/ray_light_direct.glsl b/ref_vk/shaders/ray_light_direct.glsl index cc54e8bd..888578a2 100644 --- a/ref_vk/shaders/ray_light_direct.glsl +++ b/ref_vk/shaders/ray_light_direct.glsl @@ -1,5 +1,3 @@ -#extension GL_EXT_control_flow_attributes : require - #include "utils.glsl" #include "noise.glsl" @@ -80,10 +78,12 @@ void main() { computeLighting(pos + geometry_normal * .001, shading_normal, throughput, -direction, material, diffuse, specular); #if LIGHT_POINT - imageStore(out_light_point_diffuse, pix, vec4(diffuse / 4.0, 0.f)); - imageStore(out_light_point_specular, pix, vec4(specular / 4.0, 0.f)); -#else - imageStore(out_light_poly_diffuse, pix, vec4(diffuse / 25.0, 0.f)); - imageStore(out_light_poly_specular, pix, vec4(specular/ 25.0, 0.f)); + imageStore(out_light_point_diffuse, pix, vec4(diffuse, 0.f)); + imageStore(out_light_point_specular, pix, vec4(specular, 0.f)); +#endif + +#if LIGHT_POLYGON + imageStore(out_light_poly_diffuse, pix, vec4(diffuse, 0.f)); + imageStore(out_light_poly_specular, pix, vec4(specular, 0.f)); #endif } From 19c4e2a1d8a5cb7211217f443a68411fdafa058d Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Wed, 8 Feb 2023 09:25:40 -0800 Subject: [PATCH 539/548] rt: add reflections i was trying to add specular properly: replace them with specular bounce. but it doesn't really work that easily. need some kind of color mapping and figuring out how to make them look consistent. this commit still does them separately, i.e. specular is done in direct lighting pass. no emissive is added on bounce. this probably makes it not reflect skybox ;_;. --- ref_vk/shaders/bounce.comp | 51 ++++++++++++++++++++++++++---------- ref_vk/shaders/denoiser.comp | 2 ++ 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/ref_vk/shaders/bounce.comp b/ref_vk/shaders/bounce.comp index 386ce8a5..ec2f099e 100644 --- a/ref_vk/shaders/bounce.comp +++ b/ref_vk/shaders/bounce.comp @@ -17,6 +17,7 @@ RAY_LIGHT_DIRECT_INPUTS(X) #undef X layout(set=0, binding=20, rgba16f) uniform writeonly image2D out_indirect_diffuse; +layout(set=0, binding=21, rgba16f) uniform writeonly image2D out_indirect_specular; #include "ray_primary_common.glsl" #include "ray_primary_hit.glsl" @@ -33,8 +34,8 @@ layout(set=0, binding=20, rgba16f) uniform writeonly image2D out_indirect_diffus #undef PAYLOAD_LOCATION_SHADOW #define PAYLOAD_LOCATION_SHADOW 0 -#define BINDING_LIGHTS 7 -#define BINDING_LIGHT_CLUSTERS 8 +#define BINDING_LIGHTS 19 +#define BINDING_LIGHT_CLUSTERS 18 #include "light.glsl" void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { @@ -83,7 +84,10 @@ bool getHit(vec3 origin, vec3 direction, inout RayPayloadPrimary payload) { return true; } -vec3 computeBounce(ivec2 pix, vec3 direction) { +void computeBounce(ivec2 pix, vec3 direction, out vec3 diffuse, out vec3 specular) { + diffuse = vec3(0.); + specular = vec3(0.); + const vec4 material_data = imageLoad(material_rmxx, pix); MaterialProperties material; @@ -102,30 +106,42 @@ vec3 computeBounce(ivec2 pix, vec3 direction) { // 1. Make a "random" material-based ray for diffuse lighting vec3 bounce_direction = vec3(0.); vec3 brdf_weight = vec3(0.); - const int brdf_type = DIFFUSE_TYPE; - //const int brdf_type = SPECULAR_TYPE; // FIXME test + int brdf_type = DIFFUSE_TYPE; + + if (material.metalness == 1.0f && material.roughness == 0.0f) { + // Fast path for mirrors + brdf_type = SPECULAR_TYPE; + } else { + // Decide whether to sample diffuse or specular BRDF (based on Fresnel term) + const float brdf_probability = getBrdfProbability(material, -direction, shading_normal); + if (rand01() < brdf_probability) { + brdf_type = SPECULAR_TYPE; + throughput /= brdf_probability; + } + } const vec2 u = vec2(rand01(), rand01()); if (!evalIndirectCombinedBRDF(u, shading_normal, geometry_normal, -direction, material, brdf_type, bounce_direction, brdf_weight)) - return vec3(0.);//vec3(1., 0., 0.); + return; const float throughput_threshold = 1e-3; throughput *= brdf_weight; if (dot(throughput, throughput) < throughput_threshold) - return vec3(0.);//, 1., 0.); + return; // 2. Rake yuri it, get hit // 3. Get relevant Geometry data RayPayloadPrimary payload; payload.base_color_a = vec4(0.); + payload.emissive = vec4(0.); if (!getHit(pos, bounce_direction, payload)) - return vec3(0.);//, 0., 1.); + return; throughput *= payload.base_color_a.rgb; // 4. Sample light sources - vec3 diffuse = vec3(0.); { - vec3 specular = vec3(0.); + vec3 ldiffuse = vec3(0.); + vec3 lspecular = vec3(0.); const vec3 hit_pos = payload.hit_t.xyz; const vec3 hit_shading_normal = normalDecode(payload.normals_gs.zw); @@ -134,10 +150,15 @@ vec3 computeBounce(ivec2 pix, vec3 direction) { hit_material.emissive = vec3(0.f); hit_material.metalness = payload.material_rmxx.g; hit_material.roughness = payload.material_rmxx.r; - computeLighting(hit_pos, hit_shading_normal, throughput, -bounce_direction, hit_material, diffuse, specular); - } + computeLighting(hit_pos, hit_shading_normal, throughput, -bounce_direction, hit_material, ldiffuse, lspecular); - return diffuse; + const vec3 final_color = ldiffuse + lspecular; + + if (brdf_type == DIFFUSE_TYPE) + diffuse += final_color; + else + specular += final_color;// + payload.emissive.rgb * 75; + } } void main() { @@ -154,6 +175,8 @@ void main() { rand01_state = ubo.ubo.random_seed + pix.x * 1833 + pix.y * 31337 + 12; - const vec3 diffuse = computeBounce(pix, direction); + vec3 diffuse, specular; + computeBounce(pix, direction, diffuse, specular); imageStore(out_indirect_diffuse, pix, vec4(diffuse, 0.f)); + imageStore(out_indirect_specular, pix, vec4(specular, 0.f)); } diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 1495e351..82ab8d52 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -29,6 +29,7 @@ layout(set = 0, binding = 10, rgba32f) uniform readonly image2D geometry_prev_po layout(set = 0, binding = 11) uniform UBO { UniformBuffer ubo; } ubo; layout(set = 0, binding = 12, rgba16f) uniform readonly image2D indirect_diffuse; +layout(set = 0, binding = 13, rgba16f) uniform readonly image2D indirect_specular; //layout(set = 0, binding = 2, rgba16f) uniform readonly image2D light_poly_diffuse; /* layout(set = 0, binding = 3, rgba16f) uniform readonly image2D specular; */ @@ -159,6 +160,7 @@ void main() { vec3 specular = vec3(0.); specular += imageLoad(light_poly_specular, p).rgb; specular += imageLoad(light_point_specular, p).rgb; + specular += imageLoad(indirect_specular, p).rgb; { const float sigma = KERNEL_SIZE / 2.; From 62720a7ed333a6c512ceb98453f4497d9a6d8df1 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Wed, 8 Feb 2023 09:34:40 -0800 Subject: [PATCH 540/548] rt: fix uninitialized memory denoiser glitches was reading garbage when ray doesn't hit anything --- ref_vk/shaders/ray_primary.comp | 1 + 1 file changed, 1 insertion(+) diff --git a/ref_vk/shaders/ray_primary.comp b/ref_vk/shaders/ray_primary.comp index b4fd7aa3..f4d9fe9f 100644 --- a/ref_vk/shaders/ray_primary.comp +++ b/ref_vk/shaders/ray_primary.comp @@ -62,6 +62,7 @@ void main() { RayPayloadPrimary payload; payload.hit_t = vec4(0.); + payload.prev_pos_t = vec4(0.); payload.base_color_a = vec4(0.); payload.normals_gs = vec4(0.); payload.material_rmxx = vec4(0.); From 5b59c387c17261b02deb3b876007076a303260a1 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Wed, 8 Feb 2023 09:35:21 -0800 Subject: [PATCH 541/548] rt: add emissive to specular bounce makes it sample skybox, but is probably incorrect in other circumstances --- ref_vk/shaders/bounce.comp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref_vk/shaders/bounce.comp b/ref_vk/shaders/bounce.comp index ec2f099e..d6c1b0c9 100644 --- a/ref_vk/shaders/bounce.comp +++ b/ref_vk/shaders/bounce.comp @@ -157,7 +157,7 @@ void computeBounce(ivec2 pix, vec3 direction, out vec3 diffuse, out vec3 specula if (brdf_type == DIFFUSE_TYPE) diffuse += final_color; else - specular += final_color;// + payload.emissive.rgb * 75; + specular += final_color + payload.emissive.rgb; } } From c5a1343fc698d39e8c828a866d00a13c77a6b2b6 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Wed, 8 Feb 2023 09:44:28 -0800 Subject: [PATCH 542/548] rt: add additive to bounces --- ref_vk/shaders/bounce.comp | 4 +++- ref_vk/shaders/ray_primary.comp | 24 +----------------------- ref_vk/shaders/trace_additive.glsl | 28 ++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 24 deletions(-) create mode 100644 ref_vk/shaders/trace_additive.glsl diff --git a/ref_vk/shaders/bounce.comp b/ref_vk/shaders/bounce.comp index d6c1b0c9..f5cfe794 100644 --- a/ref_vk/shaders/bounce.comp +++ b/ref_vk/shaders/bounce.comp @@ -38,6 +38,8 @@ layout(set=0, binding=21, rgba16f) uniform writeonly image2D out_indirect_specul #define BINDING_LIGHT_CLUSTERS 18 #include "light.glsl" +#include "trace_additive.glsl" + void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { const vec4 n = imageLoad(normals_gs, uv); geometry_normal = normalDecode(n.xy); @@ -152,7 +154,7 @@ void computeBounce(ivec2 pix, vec3 direction, out vec3 diffuse, out vec3 specula hit_material.roughness = payload.material_rmxx.r; computeLighting(hit_pos, hit_shading_normal, throughput, -bounce_direction, hit_material, ldiffuse, lspecular); - const vec3 final_color = ldiffuse + lspecular; + const vec3 final_color = ldiffuse + lspecular + traceAdditive(pos, bounce_direction, payload.hit_t.w); if (brdf_type == DIFFUSE_TYPE) diffuse += final_color; diff --git a/ref_vk/shaders/ray_primary.comp b/ref_vk/shaders/ray_primary.comp index f4d9fe9f..4f45b9a1 100644 --- a/ref_vk/shaders/ray_primary.comp +++ b/ref_vk/shaders/ray_primary.comp @@ -23,29 +23,7 @@ RAY_PRIMARY_OUTPUTS(X) layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; -vec3 traceAdditive(vec3 pos, vec3 dir, float L) { - const float additive_soft_overshoot = 16.; - vec3 ret = vec3(0., 0., 0.); - rayQueryEXT rq; - const uint flags = 0 - | gl_RayFlagsCullFrontFacingTrianglesEXT - //| gl_RayFlagsSkipClosestHitShaderEXT - | gl_RayFlagsNoOpaqueEXT // force all to be non-opaque - ; - rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_ADDITIVE, pos, 0., dir, L + additive_soft_overshoot); - while (rayQueryProceedEXT(rq)) { - const MiniGeometry geom = readCandidateMiniGeometry(rq); - const uint tex_base_color = getKusok(geom.kusok_index).tex_base_color; - const vec4 texture_color = texture(textures[nonuniformEXT(tex_base_color)], geom.uv); - const vec3 kusok_emissive = getKusok(geom.kusok_index).emissive; - const vec3 color = texture_color.rgb * kusok_emissive * texture_color.a; // * kusok_color.a; - - const float hit_t = rayQueryGetIntersectionTEXT(rq, false); - const float overshoot = hit_t - L; - ret += color * smoothstep(additive_soft_overshoot, 0., overshoot); - } - return ret; -} +#include "trace_additive.glsl" void main() { const ivec2 pix = ivec2(gl_GlobalInvocationID); diff --git a/ref_vk/shaders/trace_additive.glsl b/ref_vk/shaders/trace_additive.glsl new file mode 100644 index 00000000..a8d1143e --- /dev/null +++ b/ref_vk/shaders/trace_additive.glsl @@ -0,0 +1,28 @@ +#ifndef TRACE_ADDITIVE_GLSL_INCLUDED +#define TRACE_ADDITIVE_GLSL_INCLUDED + +vec3 traceAdditive(vec3 pos, vec3 dir, float L) { + const float additive_soft_overshoot = 16.; + vec3 ret = vec3(0., 0., 0.); + rayQueryEXT rq; + const uint flags = 0 + | gl_RayFlagsCullFrontFacingTrianglesEXT + //| gl_RayFlagsSkipClosestHitShaderEXT + | gl_RayFlagsNoOpaqueEXT // force all to be non-opaque + ; + rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_ADDITIVE, pos, 0., dir, L + additive_soft_overshoot); + while (rayQueryProceedEXT(rq)) { + const MiniGeometry geom = readCandidateMiniGeometry(rq); + const uint tex_base_color = getKusok(geom.kusok_index).tex_base_color; + const vec4 texture_color = texture(textures[nonuniformEXT(tex_base_color)], geom.uv); + const vec3 kusok_emissive = getKusok(geom.kusok_index).emissive; + const vec3 color = texture_color.rgb * kusok_emissive * texture_color.a; // * kusok_color.a; + + const float hit_t = rayQueryGetIntersectionTEXT(rq, false); + const float overshoot = hit_t - L; + ret += color * smoothstep(additive_soft_overshoot, 0., overshoot); + } + return ret; +} + +#endif //ifndef TRACE_ADDITIVE_GLSL_INCLUDED From 3ecd21a25bea9d8fe7873e75687c148ecba8ed66 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Wed, 8 Feb 2023 10:11:00 -0800 Subject: [PATCH 543/548] rt: simplify bounce computations use simpler polygon sampling use smaller texture lods --- ref_vk/shaders/bounce.comp | 1 + ref_vk/shaders/light_polygon.glsl | 17 ++++++++++++----- ref_vk/shaders/ray_primary_hit.glsl | 6 ++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/ref_vk/shaders/bounce.comp b/ref_vk/shaders/bounce.comp index f5cfe794..762236a8 100644 --- a/ref_vk/shaders/bounce.comp +++ b/ref_vk/shaders/bounce.comp @@ -3,6 +3,7 @@ #extension GL_EXT_nonuniform_qualifier : enable #extension GL_EXT_ray_query: require +#define RAY_BOUNCE #define RAY_QUERY layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; diff --git a/ref_vk/shaders/light_polygon.glsl b/ref_vk/shaders/light_polygon.glsl index 42d091e1..8998bade 100644 --- a/ref_vk/shaders/light_polygon.glsl +++ b/ref_vk/shaders/light_polygon.glsl @@ -6,6 +6,18 @@ #include "noise.glsl" #include "utils.glsl" +#define DO_ALL_IN_CLUSTER 1 + +#ifndef RAY_BOUNCE +#define PROJECTED +//#define SOLID +//#define SIMPLE_SOLID +#else +//#define PROJECTED +//#define SOLID +//#define SIMPLE_SOLID +#endif + struct SampleContext { mat4x3 world_to_shading; }; @@ -159,11 +171,6 @@ vec4 getPolygonLightSampleSolid(vec3 P, vec3 view_dir, SampleContext ctx, const return vec4(light_dir, contrib); } -#define DO_ALL_IN_CLUSTER 1 -//#define PROJECTED -//#define SOLID -#define SIMPLE_SOLID - void sampleSinglePolygonLight(in vec3 P, in vec3 N, in vec3 view_dir, in SampleContext ctx, in MaterialProperties material, in PolygonLight poly, inout vec3 diffuse, inout vec3 specular) { // TODO cull by poly plane diff --git a/ref_vk/shaders/ray_primary_hit.glsl b/ref_vk/shaders/ray_primary_hit.glsl index 2e7ecf9a..f315c729 100644 --- a/ref_vk/shaders/ray_primary_hit.glsl +++ b/ref_vk/shaders/ray_primary_hit.glsl @@ -13,7 +13,11 @@ layout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; } ubo; layout(set = 0, binding = 7) uniform samplerCube skybox; vec4 sampleTexture(uint tex_index, vec2 uv, vec4 uv_lods) { +#ifndef RAY_BOUNCE return textureGrad(textures[nonuniformEXT(tex_index)], uv, uv_lods.xy, uv_lods.zw); +#else + return textureLod(textures[nonuniformEXT(tex_index)], uv, 2.); +#endif } void primaryRayHit(rayQueryEXT rq, inout RayPayloadPrimary payload) { @@ -34,6 +38,7 @@ void primaryRayHit(rayQueryEXT rq, inout RayPayloadPrimary payload) { payload.material_rmxx.r = (kusok.tex_roughness > 0) ? sampleTexture(kusok.tex_roughness, geom.uv, geom.uv_lods).r : kusok.roughness; payload.material_rmxx.g = (kusok.tex_metalness > 0) ? sampleTexture(kusok.tex_metalness, geom.uv, geom.uv_lods).r : kusok.metalness; +#ifndef RAY_BOUNCE const uint tex_normal = kusok.tex_normalmap; vec3 T = geom.tangent; if (tex_normal > 0 && dot(T,T) > .5) { @@ -43,6 +48,7 @@ void primaryRayHit(rayQueryEXT rq, inout RayPayloadPrimary payload) { const vec3 tnorm = sampleTexture(tex_normal, geom.uv, geom.uv_lods).xyz * 2. - 1.; // TODO is this sampling correct for normal data? geom.normal_shading = normalize(TBN * tnorm); } +#endif } payload.normals_gs.xy = normalEncode(geom.normal_geometry); From 780b2257352e42b19662db2e2054b8b6d5b34198 Mon Sep 17 00:00:00 2001 From: Ivan Avdeev Date: Wed, 8 Feb 2023 14:55:17 -0800 Subject: [PATCH 544/548] mark a bunch of things as done --- ref_vk/TODO.md | 53 +++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/ref_vk/TODO.md b/ref_vk/TODO.md index f323530e..a3f9a484 100644 --- a/ref_vk/TODO.md +++ b/ref_vk/TODO.md @@ -1,8 +1,5 @@ -# Real next ->=E223 -- [ ] what if new meatpipe has different image format for a creatable image? - # Programmable render +- [ ] what if new meatpipe has different image format for a creatable image? - [ ] implicit dependency tracking. pass defines: - [ ] imports: list of things it needs - [ ] exports: list of things it produces. those get created and registered with this pass as a producer @@ -13,13 +10,6 @@ - [ ] ? resource object: name, metadata(type, etc.), producer, status (ready, barriers, etc) # Parallel frames sync -- [ ] light_grid_buffer (+ small lights_buffer): - - lifetime: single frame - - BUT: populated by CPU, needs sync; can't just ring-buffer it - - fixes: double-buffering? - - staging + sync upload? staging needs to be huge or done in chunks. also, cpu needs to wait on staging upload - - 2x size + wait: won't fit into device-local-host-visible mem - - decrease size first? - [ ] models_cache - lifetimes: - static; entire map @@ -40,9 +30,6 @@ - vec4(v0xyz, e_r) - vec4(v1xyz, e_g) - vec4(v2xyz, e_b) -- [ ] additive transparency -- [ ] bounces -- [ ] skybox shadows # Next - [ ] remove surface visibility cache @@ -54,7 +41,6 @@ uint8_t data[]; # Planned -- [ ] rtx: shrink payload between shaders - [ ] improve nonuniformEXT usage: https://github.com/KhronosGroup/Vulkan-Samples/pull/243/files#diff-262568ff21d7a618c0069d6a4ddf78e715fe5326c71dd2f5cdf8fc8da929bc4eR31 - [ ] rtx: experiment with refraction index and "refraction roughness" - [ ] emissive beams @@ -71,7 +57,6 @@ - [ ] "Color" should use solid color instead of texture - [ ] "Color", "Texture", ("Glow"?) should be able to reflect and refract, likely not universally though, as they might be used for different intended effects in game. figure this out on case-by-case basis. maybe we could control it based on texture names and such. - [ ] "Additive" should just be emissive and not reflective/refractive -- [ ] rtx: split ray tracing into modules: pipeline mgmt, buffer mgmt - [ ] rtx: filter things to render, e.g.: some sprites are there to fake bloom, we don't need to draw them in rtx mode - [ ] possibly split vk_render into (a) rendering/pipeline, (b) buffer management/allocation, (c) render state - [ ] rtx: light styles: need static lights data, not clear how and what to do @@ -83,7 +68,6 @@ - [x] chrome - [ ] more beams types - [ ] more particle types -- [ ] rtx: better mip lods: there's a weird math that operates on fov degrees (not radians) that we copypasted from ray tracing gems 2 chapter 7. When the book is available, get through the math and figure this out. - [ ] sane texture memory management: do not allocate VKDeviceMemory for every texture - [ ] rtx: transparency layering issue, possible approaches: - [ ] trace a special transparent-only ray separately from opaque. This can at least be used to remove black texture areas @@ -105,10 +89,9 @@ - [ ] studio models: pre-compute buffer sizes and allocate them at once - [ ] rtx: denoise - [ ] non local means ? - - [ ] reprojection + - [x] reprojection - [ ] SVG+ - [ ] ... -- [ ] rtx: add fps: rasterize into G-buffer, and only then compute lighting with rtx - [ ] rtx: bake light visibility in compute shader - [ ] dlight for flashlight seems to be broken - [ ] make 2nd commad buffer for resource upload @@ -124,9 +107,7 @@ - [ ] what is GL_Backend*/GL_RenderFrame ??? - [ ] particles - [ ] decals -- [ ] render skybox - [ ] lightmap dynamic styles -- [ ] better flashlight: spotlight instead of dlight point - [ ] fog - [ ] studio models survive NewMap; need to compactify buffers after removing all brushes - [ ] sometimes it gets very slow (1fps) when ran under lldb (only on stream?) @@ -136,16 +117,11 @@ # Someday - [ ] more than one lightmap texture. E.g. sponza ends up having 3 lightmaps -- [ ] nvnsight into buffer memory and stuff - [ ] start building command buffers in beginframe -- [ ] multiple frames in flight (#nd cmdbuf, ...) - [ ] cleanup unused stuff in vk_studio.c -- [ ] embed shaders into binary -- [ ] verify resources lifetime: make sure we don't leak and delete all textures, brushes, models, etc between maps -- [ ] custom allocator for vulkan - [ ] stats - [ ] better 2d renderer: fill DRAWQUAD(texture, color, ...) command into storage buffer instead of 4 vertices -- [ ] auto-atlas lots of smol textures: most of model texture are tiny (64x64 or less), can we not rebind them all the time? alt: bindless texture array +- [-] auto-atlas lots of smol textures: most of model texture are tiny (64x64 or less), can we not rebind them all the time? alt: bindless texture array - [ ] can we also try to coalesce sprite draw calls? - [ ] brush geometry is not watertight - [ ] collect render_draw_t w/o submitting them to cmdbuf, then sort by render_mode, trans depth, and other parameters, trying to batch as much stuff as possible; only then submit @@ -508,3 +484,26 @@ Result is meh: too much indirection, hard to follow, many things need manual fragile updates. - [ ] II: create tightly coupled image pair[2], read from [frame%2] write to [frame%2+1] - [ ] III: like (I) but with more general resource management: i.e. resource object for prev_ points to its source + +# 2023-01-28-02-08 E224-229 +- [x] light_grid_buffer (+ small lights_buffer): + - lifetime: single frame + - BUT: populated by CPU, needs sync; can't just ring-buffer it + - fixes: double-buffering? + - staging + sync upload? staging needs to be huge or done in chunks. also, cpu needs to wait on staging upload + - 2x size + wait: won't fit into device-local-host-visible mem + - decrease size first? +- [x] additive transparency +- [x] bounces +- [x] skybox shadows +- [-] rtx: shrink payload between shaders +- [x] rtx: split ray tracing into modules: pipeline mgmt, buffer mgmt +- [x] nvnsight into buffer memory and stuff +- [x] multiple frames in flight (#nd cmdbuf, ...) +- [x] embed shaders into binary +- [x] verify resources lifetime: make sure we don't leak and delete all textures, brushes, models, etc between maps +- [x] custom allocator for vulkan +- [x] rtx: better mip lods: there's a weird math that operates on fov degrees (not radians) that we copypasted from ray tracing gems 2 chapter 7. When the book is available, get through the math and figure this out. +- [x] render skybox +- [x] better flashlight: spotlight instead of dlight point +- [x] rtx: add fps: rasterize into G-buffer, and only then compute lighting with rtx From e9712f36e3c07a66eb3cac9848e82e589e131af8 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 11 Feb 2023 11:06:33 -0800 Subject: [PATCH 545/548] rt/denoiser: split into diffuse/specular/direct/indirect channels apply to lighting only, not to the final color --- ref_vk/shaders/denoiser.comp | 252 +++++++++++++++++------------------ 1 file changed, 120 insertions(+), 132 deletions(-) diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 82ab8d52..c1787fc5 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -23,7 +23,6 @@ layout(set = 0, binding = 6, rgba16f) uniform readonly image2D emissive; layout(set = 0, binding = 7, rgba32f) uniform readonly image2D position_t; layout(set = 0, binding = 8, rgba16f) uniform readonly image2D normals_gs; -layout(set = 0, binding = 9, rgba16f) uniform readonly image2D prev_dest; layout(set = 0, binding = 10, rgba32f) uniform readonly image2D geometry_prev_position; layout(set = 0, binding = 11) uniform UBO { UniformBuffer ubo; } ubo; @@ -31,6 +30,13 @@ layout(set = 0, binding = 11) uniform UBO { UniformBuffer ubo; } ubo; layout(set = 0, binding = 12, rgba16f) uniform readonly image2D indirect_diffuse; layout(set = 0, binding = 13, rgba16f) uniform readonly image2D indirect_specular; +layout(set = 0, binding = 14, rgba16f) uniform image2D out_temporal_diffuse; +layout(set = 0, binding = 15, rgba16f) uniform image2D prev_temporal_diffuse; + +layout(set = 0, binding = 16, rgba16f) uniform image2D out_temporal_specular; +layout(set = 0, binding = 17, rgba16f) uniform image2D prev_temporal_specular; + + //layout(set = 0, binding = 2, rgba16f) uniform readonly image2D light_poly_diffuse; /* layout(set = 0, binding = 3, rgba16f) uniform readonly image2D specular; */ /* layout(set = 0, binding = 4, rgba16f) uniform readonly image2D additive; */ @@ -73,6 +79,70 @@ void readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) { shading_normal = normalDecode(n.zw); } +struct Components { + vec3 direct_diffuse, direct_specular, indirect_diffuse, indirect_specular; +}; + +Components blurSamples(const ivec2 res, const ivec2 pix) { + Components c; + c.direct_diffuse = c.direct_specular = c.indirect_diffuse = c.indirect_specular = vec3(0.); + + const vec4 center_pos = imageLoad(position_t, pix); + const int KERNEL_SIZE = 3; + const float sigma = KERNEL_SIZE / 2.; + + float total_scale = 0.; + + for (int x = -KERNEL_SIZE; x <= KERNEL_SIZE; ++x) + for (int y = -KERNEL_SIZE; y <= KERNEL_SIZE; ++y) { + const ivec2 p = pix + ivec2(x, y); + if (any(greaterThanEqual(p, res)) || any(lessThan(p, ivec2(0)))) { + continue; + } + + float scale = 1.f; + + // const vec4 c = imageLoad(light_poly, p); + // if (c.a != material_index) + // continue; + + vec3 sample_geometry_normal, sample_shading_normal; + readNormals(p, sample_geometry_normal, sample_shading_normal); + + // FIXME also filter by depth, (kusok index?), etc + //scale *= smoothstep(.9, 1., dot(sample_geometry_normal, geometry_normal)); + + const vec4 sample_pos = imageLoad(position_t, p); + // FIXME what are these magic numbers? + scale *= smoothstep(4. * center_pos.w / 100., 0., distance(center_pos.xyz, sample_pos.xyz)); + + if ( scale <= 0. ) + continue; + + const float cscale = scale * normpdf(x, sigma) * normpdf(y, sigma); + total_scale += cscale; + + c.direct_diffuse += imageLoad(light_point_diffuse, p).rgb * cscale; + c.direct_diffuse += imageLoad(light_poly_diffuse, p).rgb * cscale; + + c.direct_specular += imageLoad(light_poly_specular, p).rgb * cscale; + c.direct_specular += imageLoad(light_point_specular, p).rgb * cscale; + + c.indirect_diffuse += imageLoad(indirect_diffuse, p).rgb * cscale; + c.indirect_specular += imageLoad(indirect_specular, p).rgb * cscale; + } + + if (total_scale > 0.) { + const float rtotal = 1. / total_scale; + c.direct_diffuse *= rtotal; + c.direct_specular *= rtotal; + c.indirect_diffuse *= rtotal; + c.indirect_specular *= rtotal; + } + + return c; +} + void main() { ivec2 res = ivec2(imageSize(base_color_a)); ivec2 pix = ivec2(gl_GlobalInvocationID); @@ -113,149 +183,67 @@ void main() { /* imageStore(out_dest, pix, vec4(rand3_f01(uvec3(mi,mi+1,mi+2)), 0.)); */ /* return; */ -#if 0 + const Components c = blurSamples(res, pix); + //imageStore(out_dest, pix, vec4(aces_tonemap(c.direct_diffuse), 0.)); return; + //imageStore(out_dest, pix, vec4(aces_tonemap(c.direct_specular), 0.)); return; + //imageStore(out_dest, pix, vec4(aces_tonemap(c.indirect_diffuse), 0.)); return; + //imageStore(out_dest, pix, vec4(aces_tonemap(c.indirect_specular), 0.)); return; + vec3 colour = vec3(0.); - colour += imageLoad(light_poly_diffuse, pix).rgb; - colour += imageLoad(light_poly_specular, pix).rgb; - colour += imageLoad(light_point_diffuse, pix).rgb; - colour += imageLoad(light_point_specular, pix).rgb; -#else - float total_scale = 0.; - vec3 colour = vec3(0.); - const int KERNEL_SIZE = 2; - float specular_total_scale = 0.; - vec3 speculour = vec3(0.); + { + // DEBUG motion vectors + //colour = vec3(length(imageLoad(position_t, pix).rgb - imageLoad(prev_position_t, pix).rgb)); - const vec4 center_pos = imageLoad(position_t, pix); - for (int x = -KERNEL_SIZE; x <= KERNEL_SIZE; ++x) - for (int y = -KERNEL_SIZE; y <= KERNEL_SIZE; ++y) { - const ivec2 p = pix + ivec2(x, y); - if (any(greaterThanEqual(p, res)) || any(lessThan(p, ivec2(0)))) { - continue; - } + // TODO: need to extract reprojecting from this shader because reprojected stuff need svgf denoising pass after it + const vec3 origin = (ubo.ubo.inv_view * vec4(0., 0., 0., 1.)).xyz; + const float depth = length(origin - imageLoad(position_t, pix).xyz); + const vec3 prev_position = imageLoad(geometry_prev_position, pix).rgb; + const vec4 clip_space = inverse(ubo.ubo.prev_inv_proj) * vec4((inverse(ubo.ubo.prev_inv_view) * vec4(prev_position, 1.)).xyz, 1.); + const vec2 reproj_uv = clip_space.xy / clip_space.w; + const ivec2 reproj_pix = ivec2((reproj_uv * 0.5 + vec2(0.5)) * vec2(res)); + const vec3 prev_origin = (ubo.ubo.prev_inv_view * vec4(0., 0., 0., 1.)).xyz; + const float depth_nessesary = length(prev_position - prev_origin); + const float depth_treshold = 0.01 * clip_space.w; - float scale = 1.f; + float better_depth_offset = depth_treshold; + vec3 diffuse = c.direct_diffuse + c.indirect_diffuse; + vec3 specular = c.direct_specular + c.indirect_specular; + vec3 history_diffuse = diffuse; + vec3 history_specular = specular; + for(int x = -1; x <=1; x++) { + for(int y = -1; y <=1; y++) { + const ivec2 p = reproj_pix + ivec2(x, y); + if (any(greaterThanEqual(p, res)) || any(lessThan(p, ivec2(0)))) { + continue; + } + const vec4 history_diffuse_depth = imageLoad( prev_temporal_diffuse, reproj_pix ); + const vec4 history_specular_sample = imageLoad( prev_temporal_specular, reproj_pix ); - // const vec4 c = imageLoad(light_poly, p); - // if (c.a != material_index) - // continue; - - vec3 sample_geometry_normal, sample_shading_normal; - readNormals(p, sample_geometry_normal, sample_shading_normal); - - // FIXME also filter by depth, (kusok index?), etc - //scale *= smoothstep(.9, 1., dot(sample_geometry_normal, geometry_normal)); - - const vec4 sample_pos = imageLoad(position_t, p); - scale *= smoothstep(4. * center_pos.w / 100., 0., distance(center_pos.xyz, sample_pos.xyz)); - - if ( scale <= 0. ) - continue; - - vec3 diffuse = vec3(0.); - diffuse += imageLoad(light_point_diffuse, p).rgb; - diffuse += imageLoad(light_poly_diffuse, p).rgb; - diffuse += imageLoad(indirect_diffuse, p).rgb; - - vec3 specular = vec3(0.); - specular += imageLoad(light_poly_specular, p).rgb; - specular += imageLoad(light_point_specular, p).rgb; - specular += imageLoad(indirect_specular, p).rgb; - - { - const float sigma = KERNEL_SIZE / 2.; - const float dscale = scale * normpdf(x, sigma) * normpdf(y, sigma); - colour += dscale * diffuse; - total_scale += dscale; - } - - const int SPECULAR_KERNEL_SIZE = 4; - if (all(lessThan(abs(ivec2(x, y)), ivec2(SPECULAR_KERNEL_SIZE)))) { - const float spigma = SPECULAR_KERNEL_SIZE / 2.; - const float specuale = scale * normpdf(x, spigma) * normpdf(y, spigma); - speculour += specuale * specular; - specular_total_scale += specuale; + const float history_depth = history_diffuse_depth.w; + const float depth_offset = abs(history_depth - depth_nessesary); + if ( depth_offset < better_depth_offset ) { + better_depth_offset = depth_offset; + history_diffuse = history_diffuse_depth.rgb; + history_specular = history_specular_sample.rgb; + } } } + if (better_depth_offset < depth_treshold) { + diffuse = mix(diffuse, history_diffuse, 0.8); + specular = mix(specular, history_specular, 0.3); + } - if (total_scale > 0.) { - colour /= total_scale; - } + imageStore(out_temporal_diffuse, pix, vec4(diffuse, depth)); + imageStore(out_temporal_specular, pix, vec4(specular, 0./*unused*/)); + colour = diffuse + specular; - if (specular_total_scale > 0.) { - speculour /= specular_total_scale; - colour += speculour; + //imageStore(out_dest, pix, vec4(LINEARtoSRGB(colour), 0.)); return; } -#endif const vec4 base_color_a = imageLoad(base_color_a, pix); colour *= SRGBtoLINEAR(base_color_a.rgb); colour += imageLoad(emissive, pix).rgb; - //colour += imageLoad(additive, pix).rgb; + colour = LINEARtoSRGB(colour); - // HACK: exposure - // TODO: should be dynamic based on previous frames brightness -#if 0 - if (pix.x >= res.x / 2) { - colour *= 8.; - } -#else - //colour *= .25; -#endif - - //colour = aces_tonemap(colour); - //colour = reinhard02(colour, vec3(400.)); - //colour = reinhard02(colour, vec3(1.)); - -#if 0 - if (pix.x < res.x / 2) { -#endif - //colour *= .25; - colour = LINEARtoSRGB(colour); // gamma-correction -#if 0 - } -#endif - - // DEBUG motion vectors - //colour = vec3(length(imageLoad(position_t, pix).rgb - imageLoad(prev_position_t, pix).rgb)); - - // TODO: need to extract reprojecting from this shader because reprojected stuff need svgf denoising pass after it - const vec3 origin = (ubo.ubo.inv_view * vec4(0., 0., 0., 1.)).xyz; - const float depth = length(origin - imageLoad(position_t, pix).xyz); - const vec3 prev_position = imageLoad(geometry_prev_position, pix).rgb; - const vec4 clip_space = inverse(ubo.ubo.prev_inv_proj) * vec4((inverse(ubo.ubo.prev_inv_view) * vec4(prev_position, 1.)).xyz, 1.); - const vec2 reproj_uv = clip_space.xy / clip_space.w; - const ivec2 reproj_pix = ivec2((reproj_uv * 0.5 + vec2(0.5)) * vec2(res)); - const vec3 prev_origin = (ubo.ubo.prev_inv_view * vec4(0., 0., 0., 1.)).xyz; - const float depth_nessesary = length(prev_position - prev_origin); - const float depth_treshold = 0.01 * clip_space.w; - float better_depth_offset = depth_treshold; - vec3 history_colour = colour; - for(int x = -1; x <=1; x++) { - for(int y = -1; y <=1; y++) { - const ivec2 p = reproj_pix + ivec2(x, y); - if (any(greaterThanEqual(p, res)) || any(lessThan(p, ivec2(0)))) { - continue; - } - const vec4 history_colour_depth = imageLoad( prev_dest, reproj_pix ); - const float history_depth = history_colour_depth.w; - const float depth_offset = abs(history_depth - depth_nessesary); - if ( depth_offset < better_depth_offset ) { - better_depth_offset = depth_offset; - history_colour = history_colour_depth.rgb; - } - } - } - if (better_depth_offset < depth_treshold) { - colour = mix(colour, history_colour, 0.8); - } - - // DEBUG motion vectors - //colour = vec3(length(imageLoad(position_t, pix).rgb - imageLoad(geometry_prev_position, pix).rgb)); - - //const vec4 prev_colour = imageLoad(prev_dest, pix); - //colour = mix(colour, prev_colour.rgb, vec3(.9)); - - imageStore(out_dest, pix, vec4(colour, depth)); - //imageStore(out_dest, pix, imageLoad(light_poly, pix)); + imageStore(out_dest, pix, vec4(colour, 0./*unused*/)); } From 218bd0a9c28b85184207fe6e2d98541fc7e98a6c Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 11 Feb 2023 11:36:45 -0800 Subject: [PATCH 546/548] rt/denoiser: blur different channels differently --- ref_vk/shaders/denoiser.comp | 69 +++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index c1787fc5..71e4e684 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -88,10 +88,19 @@ Components blurSamples(const ivec2 res, const ivec2 pix) { c.direct_diffuse = c.direct_specular = c.indirect_diffuse = c.indirect_specular = vec3(0.); const vec4 center_pos = imageLoad(position_t, pix); - const int KERNEL_SIZE = 3; - const float sigma = KERNEL_SIZE / 2.; - float total_scale = 0.; + const int DIRECT_DIFFUSE_KERNEL = 3; + const int INDIRECT_DIFFUSE_KERNEL = 5; + const int SPECULAR_KERNEL = 2; + const int KERNEL_SIZE = max(max(DIRECT_DIFFUSE_KERNEL, INDIRECT_DIFFUSE_KERNEL), SPECULAR_KERNEL); + + const float direct_diffuse_sigma = DIRECT_DIFFUSE_KERNEL / 2.; + const float indirect_diffuse_sigma = INDIRECT_DIFFUSE_KERNEL / 2.; + const float specular_sigma = SPECULAR_KERNEL / 2.; + + float direct_diffuse_total = 0.; + float indirect_diffuse_total = 0.; + float specular_total = 0.; for (int x = -KERNEL_SIZE; x <= KERNEL_SIZE; ++x) for (int y = -KERNEL_SIZE; y <= KERNEL_SIZE; ++y) { @@ -100,8 +109,6 @@ Components blurSamples(const ivec2 res, const ivec2 pix) { continue; } - float scale = 1.f; - // const vec4 c = imageLoad(light_poly, p); // if (c.a != material_index) // continue; @@ -109,6 +116,7 @@ Components blurSamples(const ivec2 res, const ivec2 pix) { vec3 sample_geometry_normal, sample_shading_normal; readNormals(p, sample_geometry_normal, sample_shading_normal); + float scale = 1.f; // FIXME also filter by depth, (kusok index?), etc //scale *= smoothstep(.9, 1., dot(sample_geometry_normal, geometry_normal)); @@ -119,25 +127,43 @@ Components blurSamples(const ivec2 res, const ivec2 pix) { if ( scale <= 0. ) continue; - const float cscale = scale * normpdf(x, sigma) * normpdf(y, sigma); - total_scale += cscale; + if (all(lessThan(abs(ivec2(x, y)), ivec2(DIRECT_DIFFUSE_KERNEL)))) + { + const float direct_diffuse_scale = scale * normpdf(x, direct_diffuse_sigma) * normpdf(y, direct_diffuse_sigma); + direct_diffuse_total += direct_diffuse_scale; - c.direct_diffuse += imageLoad(light_point_diffuse, p).rgb * cscale; - c.direct_diffuse += imageLoad(light_poly_diffuse, p).rgb * cscale; + c.direct_diffuse += imageLoad(light_point_diffuse, p).rgb * direct_diffuse_scale; + c.direct_diffuse += imageLoad(light_poly_diffuse, p).rgb * direct_diffuse_scale; + } - c.direct_specular += imageLoad(light_poly_specular, p).rgb * cscale; - c.direct_specular += imageLoad(light_point_specular, p).rgb * cscale; + if (all(lessThan(abs(ivec2(x, y)), ivec2(INDIRECT_DIFFUSE_KERNEL)))) + { + const float indirect_diffuse_scale = scale * normpdf(x, indirect_diffuse_sigma) * normpdf(y, indirect_diffuse_sigma); + indirect_diffuse_total += indirect_diffuse_scale; - c.indirect_diffuse += imageLoad(indirect_diffuse, p).rgb * cscale; - c.indirect_specular += imageLoad(indirect_specular, p).rgb * cscale; + c.indirect_diffuse += imageLoad(indirect_diffuse, p).rgb * indirect_diffuse_scale; + } + + if (all(lessThan(abs(ivec2(x, y)), ivec2(SPECULAR_KERNEL)))) + { + const float specular_scale = scale * normpdf(x, specular_sigma) * normpdf(y, specular_sigma); + specular_total += specular_scale; + + c.direct_specular += imageLoad(light_poly_specular, p).rgb * specular_scale; + c.direct_specular += imageLoad(light_point_specular, p).rgb * specular_scale; + c.indirect_specular += imageLoad(indirect_specular, p).rgb * specular_scale; + } } - if (total_scale > 0.) { - const float rtotal = 1. / total_scale; - c.direct_diffuse *= rtotal; - c.direct_specular *= rtotal; - c.indirect_diffuse *= rtotal; - c.indirect_specular *= rtotal; + if (direct_diffuse_total > 0.) + c.direct_diffuse /= direct_diffuse_total; + + if (indirect_diffuse_total > 0.) + c.indirect_diffuse *= indirect_diffuse_total; + + if (specular_total > 0.) { + c.direct_specular *= specular_total; + c.indirect_specular *= specular_total; } return c; @@ -210,8 +236,9 @@ void main() { vec3 specular = c.direct_specular + c.indirect_specular; vec3 history_diffuse = diffuse; vec3 history_specular = specular; - for(int x = -1; x <=1; x++) { - for(int y = -1; y <=1; y++) { + const int TEMPORAL_KERNEL = 1; // lifekilled says it should be fixed + for(int x = -TEMPORAL_KERNEL; x <=TEMPORAL_KERNEL; x++) { + for(int y = -TEMPORAL_KERNEL; y <=TEMPORAL_KERNEL; y++) { const ivec2 p = reproj_pix + ivec2(x, y); if (any(greaterThanEqual(p, res)) || any(lessThan(p, ivec2(0)))) { continue; From 64ac964dfc06c56653847bb74984db6346bf5216 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Sat, 11 Feb 2023 12:12:57 -0800 Subject: [PATCH 547/548] rt: make indirect light run at half res --- ref_vk/shaders/bounce.comp | 6 ++++-- ref_vk/shaders/denoiser.comp | 20 ++++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/ref_vk/shaders/bounce.comp b/ref_vk/shaders/bounce.comp index 762236a8..a3f9c2e8 100644 --- a/ref_vk/shaders/bounce.comp +++ b/ref_vk/shaders/bounce.comp @@ -164,9 +164,11 @@ void computeBounce(ivec2 pix, vec3 direction, out vec3 diffuse, out vec3 specula } } +const int INDIRECT_SCALE = 2; + void main() { const ivec2 pix = ivec2(gl_GlobalInvocationID); - const ivec2 res = ivec2(imageSize(out_indirect_diffuse)); + const ivec2 res = ivec2(imageSize(out_indirect_diffuse)) / INDIRECT_SCALE; if (any(greaterThanEqual(pix, res))) { return; } @@ -179,7 +181,7 @@ void main() { rand01_state = ubo.ubo.random_seed + pix.x * 1833 + pix.y * 31337 + 12; vec3 diffuse, specular; - computeBounce(pix, direction, diffuse, specular); + computeBounce(pix * INDIRECT_SCALE, direction, diffuse, specular); imageStore(out_indirect_diffuse, pix, vec4(diffuse, 0.f)); imageStore(out_indirect_specular, pix, vec4(specular, 0.f)); } diff --git a/ref_vk/shaders/denoiser.comp b/ref_vk/shaders/denoiser.comp index 71e4e684..0c1429b2 100644 --- a/ref_vk/shaders/denoiser.comp +++ b/ref_vk/shaders/denoiser.comp @@ -36,6 +36,7 @@ layout(set = 0, binding = 15, rgba16f) uniform image2D prev_temporal_diffuse; layout(set = 0, binding = 16, rgba16f) uniform image2D out_temporal_specular; layout(set = 0, binding = 17, rgba16f) uniform image2D prev_temporal_specular; +const int INDIRECT_SCALE = 2; //layout(set = 0, binding = 2, rgba16f) uniform readonly image2D light_poly_diffuse; /* layout(set = 0, binding = 3, rgba16f) uniform readonly image2D specular; */ @@ -102,6 +103,7 @@ Components blurSamples(const ivec2 res, const ivec2 pix) { float indirect_diffuse_total = 0.; float specular_total = 0.; + const ivec2 res_scaled = res / INDIRECT_SCALE; for (int x = -KERNEL_SIZE; x <= KERNEL_SIZE; ++x) for (int y = -KERNEL_SIZE; y <= KERNEL_SIZE; ++y) { const ivec2 p = pix + ivec2(x, y); @@ -122,7 +124,7 @@ Components blurSamples(const ivec2 res, const ivec2 pix) { const vec4 sample_pos = imageLoad(position_t, p); // FIXME what are these magic numbers? - scale *= smoothstep(4. * center_pos.w / 100., 0., distance(center_pos.xyz, sample_pos.xyz)); + scale *= smoothstep(center_pos.w * 4. / 100., 0., distance(center_pos.xyz, sample_pos.xyz)); if ( scale <= 0. ) continue; @@ -138,10 +140,14 @@ Components blurSamples(const ivec2 res, const ivec2 pix) { if (all(lessThan(abs(ivec2(x, y)), ivec2(INDIRECT_DIFFUSE_KERNEL)))) { - const float indirect_diffuse_scale = scale * normpdf(x, indirect_diffuse_sigma) * normpdf(y, indirect_diffuse_sigma); - indirect_diffuse_total += indirect_diffuse_scale; + const ivec2 pscaled = pix / INDIRECT_SCALE + ivec2(x, y); - c.indirect_diffuse += imageLoad(indirect_diffuse, p).rgb * indirect_diffuse_scale; + if (all(lessThan(pscaled, res_scaled)) && all(greaterThanEqual(pscaled, ivec2(0)))) { + const float indirect_diffuse_scale = scale * normpdf(x, indirect_diffuse_sigma) * normpdf(y, indirect_diffuse_sigma); + indirect_diffuse_total += indirect_diffuse_scale; + + c.indirect_diffuse += imageLoad(indirect_diffuse, pscaled).rgb * indirect_diffuse_scale; + } } if (all(lessThan(abs(ivec2(x, y)), ivec2(SPECULAR_KERNEL)))) @@ -151,7 +157,8 @@ Components blurSamples(const ivec2 res, const ivec2 pix) { c.direct_specular += imageLoad(light_poly_specular, p).rgb * specular_scale; c.direct_specular += imageLoad(light_point_specular, p).rgb * specular_scale; - c.indirect_specular += imageLoad(indirect_specular, p).rgb * specular_scale; + + c.indirect_specular += imageLoad(indirect_specular, p / INDIRECT_SCALE).rgb * specular_scale; } } @@ -194,6 +201,7 @@ void main() { //imageStore(out_dest, pix, vec4((imageLoad(light_poly, pix).rgb * base_color_a.rgb), 0.)); return; //imageStore(out_dest, pix, vec4(imageLoad(normals, pix)*.5 + .5f)); return; //imageStore(out_dest, pix, vec4(imageLoad(specular, pix)*.5 + .5f)); return; + //imageStore(out_dest, pix, vec4(imageLoad(indirect_diffuse, pix)*.5 + .5f)); return; //imageStore(out_dest, pix, vec4(aces_tonemap(imageLoad(light_poly, pix).rgb), 0.)); return; //imageStore(out_dest, pix, vec4(aces_tonemap(imageLoad(specular, pix).rgb), 0.)); return; //imageStore(out_dest, pix, vec4(aces_tonemap(imageLoad(indirect_diffuse, pix).rgb), 0.)); return; @@ -264,7 +272,7 @@ void main() { imageStore(out_temporal_specular, pix, vec4(specular, 0./*unused*/)); colour = diffuse + specular; - //imageStore(out_dest, pix, vec4(LINEARtoSRGB(colour), 0.)); return; + //imageStore(out_dest, pix, vec4(LINEARtoSRGB(diffuse), 0.)); return; } const vec4 base_color_a = imageLoad(base_color_a, pix); From 4dd793569c014b6e22b0124d81d40e2d6a8126e0 Mon Sep 17 00:00:00 2001 From: Ivan 'provod' Avdeev Date: Tue, 14 Feb 2023 12:35:23 -0800 Subject: [PATCH 548/548] rt: add alpha-quality alpha blending Do translucency in bounce stage. It's not great, but lets us see thing through. Also fix reading OOB uninitialized memory for color/alpha. --- ref_vk/shaders/bounce.comp | 40 +++++++++++++++++++++------------ ref_vk/shaders/ray_primary.comp | 2 +- ref_vk/vk_ray_model.c | 4 ++-- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/ref_vk/shaders/bounce.comp b/ref_vk/shaders/bounce.comp index a3f9c2e8..550c0216 100644 --- a/ref_vk/shaders/bounce.comp +++ b/ref_vk/shaders/bounce.comp @@ -12,7 +12,8 @@ layout(set=0, binding=1) uniform accelerationStructureEXT tlas; #define RAY_LIGHT_DIRECT_INPUTS(X) \ X(10, position_t, rgba32f) \ X(11, normals_gs, rgba16f) \ - X(12, material_rmxx, rgba8) + X(12, material_rmxx, rgba8) \ + X(13, base_color_a, rgba8) #define X(index, name, format) layout(set=0,binding=index,format) uniform readonly image2D name; RAY_LIGHT_DIRECT_INPUTS(X) #undef X @@ -92,9 +93,10 @@ void computeBounce(ivec2 pix, vec3 direction, out vec3 diffuse, out vec3 specula specular = vec3(0.); const vec4 material_data = imageLoad(material_rmxx, pix); + const vec4 base_a = imageLoad(base_color_a, pix); MaterialProperties material; - material.baseColor = vec3(1.); + material.baseColor = vec3(1.f); material.emissive = vec3(0.f); material.metalness = material_data.g; material.roughness = material_data.r; @@ -103,31 +105,40 @@ void computeBounce(ivec2 pix, vec3 direction, out vec3 diffuse, out vec3 specula readNormals(pix, geometry_normal, shading_normal); const float ray_normal_fudge = .01; - const vec3 pos = imageLoad(position_t, pix).xyz + geometry_normal * ray_normal_fudge; vec3 throughput = vec3(1.); // 1. Make a "random" material-based ray for diffuse lighting vec3 bounce_direction = vec3(0.); - vec3 brdf_weight = vec3(0.); int brdf_type = DIFFUSE_TYPE; - if (material.metalness == 1.0f && material.roughness == 0.0f) { - // Fast path for mirrors + const float alpha = (base_a.a); + if (1. > alpha && rand01() > alpha) { brdf_type = SPECULAR_TYPE; + // TODO: when not sampling randomly: throughput *= (1. - base_a.a); + bounce_direction = normalize(refract(direction, geometry_normal, .95)); + geometry_normal = -geometry_normal; + //throughput /= base_a.rgb; } else { - // Decide whether to sample diffuse or specular BRDF (based on Fresnel term) - const float brdf_probability = getBrdfProbability(material, -direction, shading_normal); - if (rand01() < brdf_probability) { + if (material.metalness == 1.0f && material.roughness == 0.0f) { + // Fast path for mirrors brdf_type = SPECULAR_TYPE; - throughput /= brdf_probability; + } else { + // Decide whether to sample diffuse or specular BRDF (based on Fresnel term) + const float brdf_probability = getBrdfProbability(material, -direction, shading_normal); + if (rand01() < brdf_probability) { + brdf_type = SPECULAR_TYPE; + throughput /= brdf_probability; + } } + + const vec2 u = vec2(rand01(), rand01()); + vec3 brdf_weight = vec3(0.); + if (!evalIndirectCombinedBRDF(u, shading_normal, geometry_normal, -direction, material, brdf_type, bounce_direction, brdf_weight)) + return; + throughput *= brdf_weight; } - const vec2 u = vec2(rand01(), rand01()); - if (!evalIndirectCombinedBRDF(u, shading_normal, geometry_normal, -direction, material, brdf_type, bounce_direction, brdf_weight)) - return; const float throughput_threshold = 1e-3; - throughput *= brdf_weight; if (dot(throughput, throughput) < throughput_threshold) return; @@ -136,6 +147,7 @@ void computeBounce(ivec2 pix, vec3 direction, out vec3 diffuse, out vec3 specula RayPayloadPrimary payload; payload.base_color_a = vec4(0.); payload.emissive = vec4(0.); + const vec3 pos = imageLoad(position_t, pix).xyz + geometry_normal * ray_normal_fudge; if (!getHit(pos, bounce_direction, payload)) return; diff --git a/ref_vk/shaders/ray_primary.comp b/ref_vk/shaders/ray_primary.comp index 4f45b9a1..fc34f2e3 100644 --- a/ref_vk/shaders/ray_primary.comp +++ b/ref_vk/shaders/ray_primary.comp @@ -54,7 +54,7 @@ void main() { //| gl_RayFlagsSkipClosestHitShaderEXT ; float L = 10000.; // TODO Why 10k? - rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE | GEOMETRY_BIT_ALPHA_TEST, origin, 0., direction, L); + rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE | GEOMETRY_BIT_ALPHA_TEST | GEOMETRY_BIT_REFRACTIVE, origin, 0., direction, L); while (rayQueryProceedEXT(rq)) { if (0 != (rayQueryGetRayFlagsEXT(rq) & gl_RayFlagsOpaqueEXT)) continue; diff --git a/ref_vk/vk_ray_model.c b/ref_vk/vk_ray_model.c index b0586b11..f55a55a6 100644 --- a/ref_vk/vk_ray_model.c +++ b/ref_vk/vk_ray_model.c @@ -153,7 +153,7 @@ void XVK_RayModel_Validate( void ) { } } -static void applyMaterialToKusok(vk_kusok_data_t* kusok, const vk_render_geometry_t *geom, const vec3_t color, qboolean HACK_reflective) { +static void applyMaterialToKusok(vk_kusok_data_t* kusok, const vk_render_geometry_t *geom, const vec4_t color, qboolean HACK_reflective) { const xvk_material_t *const mat = XVK_GetMaterialForTextureIndex( geom->texture ); ASSERT(mat); @@ -299,7 +299,7 @@ vk_ray_model_t* VK_RayModelCreate( vk_ray_model_init_t args ) { kusochki[i].tex_base_color &= (~KUSOK_MATERIAL_FLAG_SKYBOX); } - const vec3_t color = {1, 1, 1}; + const vec4_t color = {1, 1, 1, 1}; applyMaterialToKusok(kusochki + i, mg, color, false); Matrix4x4_LoadIdentity(kusochki[i].prev_transform); }