mirror of
https://github.com/s0liton/ps5-romm.git
synced 2026-10-28 22:01:45 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
659005ce58 | ||
|
|
a1446f4706 | ||
|
|
b5d3e954f8 | ||
|
|
9066fb38ac | ||
|
|
46aa1710d2 | ||
|
|
bf479d045b | ||
|
|
7a5a07a13f | ||
|
|
7b426b8273 | ||
|
|
d309c16cb2 | ||
|
|
c632774407 | ||
|
|
754d4d60f2 | ||
|
|
afd9406603 | ||
|
|
f2a3871b4b | ||
|
|
0894ec8df9 | ||
|
|
5b01d95ef5 | ||
|
|
87476147a0 | ||
|
|
694119b10f | ||
|
|
a590964998 | ||
|
|
0ccf61ebd7 | ||
|
|
220f606256 | ||
|
|
842e32cc73 | ||
|
|
44472365fc | ||
|
|
82d10ee0f3 | ||
|
|
fe8e3ba197 | ||
|
|
e84ddf7f5d | ||
|
|
200b3eecb0 | ||
|
|
5c9e5f866d |
No files matched your search
@@ -12,5 +12,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: sudo apt-get update && sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev
|
||||
- run: make host
|
||||
- run: make host tools
|
||||
- run: tests/e2e.sh
|
||||
- run: tests/autostart.sh
|
||||
+110
-12
@@ -1,44 +1,142 @@
|
||||
# Builds the PS5 payload and publishes a GitHub release when a version tag is pushed.
|
||||
# Builds the PS5 and PS4 payloads and publishes a GitHub release. Triggered either by:
|
||||
#
|
||||
# git tag v1.0.0 && git push origin v1.0.0
|
||||
# - merging a PR into main from a branch named with a version, e.g.
|
||||
# release/v1.0.2 or v1.0.2-some-fix (the tag is created on the merge commit)
|
||||
# - pushing a version tag manually: git tag v1.0.0 && git push origin v1.0.0
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["v*.*.*"]
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: >-
|
||||
github.event_name == 'push' ||
|
||||
(github.event.pull_request.merged &&
|
||||
contains(github.event.pull_request.head.ref, 'v'))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Resolve version
|
||||
id: version
|
||||
env:
|
||||
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||
run: |
|
||||
if [ "$GITHUB_EVENT_NAME" = push ]; then
|
||||
tag="$GITHUB_REF_NAME"
|
||||
sha="$GITHUB_SHA"
|
||||
else
|
||||
tag=$(grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' <<< "$HEAD_REF" | head -n1 || true)
|
||||
sha="${{ github.event.pull_request.merge_commit_sha }}"
|
||||
fi
|
||||
if [ -z "$tag" ]; then
|
||||
echo "Branch '$HEAD_REF' has no vX.Y.Z version; skipping release."
|
||||
exit 0
|
||||
fi
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
echo "sha=$sha" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
if: steps.version.outputs.tag
|
||||
with:
|
||||
ref: ${{ steps.version.outputs.sha }}
|
||||
|
||||
# RommPS (built with tools/release-app.sh, outside this workflow) is
|
||||
# offered as an update by the homebrew catalog only if its contentVersion
|
||||
# goes up, so the tag has to match it: v1.1.0 is 01.001.000.
|
||||
- name: Check RommPS's contentVersion
|
||||
if: steps.version.outputs.tag
|
||||
env:
|
||||
TAG: ${{ steps.version.outputs.tag }}
|
||||
run: |
|
||||
IFS=. read -r major minor patch <<< "${TAG#v}"
|
||||
want=$(printf "%02d.%03d.%03d" "$major" "$minor" "$patch")
|
||||
have=$(jq -r .contentVersion platform/ps5/app/rommps/ps5/sce_sys/param.json)
|
||||
if [ "$have" != "$want" ]; then
|
||||
echo "::error::RommPS's contentVersion is $have, $TAG needs $want (platform/ps5/app/rommps/ps5/sce_sys/param.json)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
if: steps.version.outputs.tag
|
||||
|
||||
# Same image tools/ps5-build.sh uses, cached between runs.
|
||||
# Same images tools/ps5-build.sh and tools/ps4-build.sh use, cached between
|
||||
# runs. The cache only saves time, so failing to write it must not fail the release.
|
||||
- uses: docker/build-push-action@v6
|
||||
if: steps.version.outputs.tag
|
||||
with:
|
||||
context: tools/docker
|
||||
context: platform/ps5/docker
|
||||
tags: romm-sync-ps5sdk:v0.43
|
||||
load: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
cache-from: type=gha,scope=ps5
|
||||
cache-to: type=gha,mode=max,scope=ps5,ignore-error=true
|
||||
|
||||
- name: Build payload
|
||||
run: tools/ps5-build.sh VERSION="${GITHUB_REF_NAME#v}"
|
||||
- uses: docker/build-push-action@v6
|
||||
if: steps.version.outputs.tag
|
||||
with:
|
||||
context: platform/ps4/docker
|
||||
tags: romm-sync-ps4sdk:v0.9-oo0.5.4
|
||||
load: true
|
||||
cache-from: type=gha,scope=ps4
|
||||
cache-to: type=gha,mode=max,scope=ps4,ignore-error=true
|
||||
|
||||
- name: Package
|
||||
- name: Build payloads
|
||||
if: steps.version.outputs.tag
|
||||
env:
|
||||
TAG: ${{ steps.version.outputs.tag }}
|
||||
run: |
|
||||
tools/ps5-build.sh VERSION="${TAG#v}"
|
||||
tools/ps4-build.sh ps4-pkg VERSION="${TAG#v}"
|
||||
|
||||
# A manifest and signature per console let the payload verify in-app
|
||||
# updates (src/update.c): manifest.json/.sig for romm-sync.elf (PS5),
|
||||
# manifest-ps4.json/.sig for romm-sync-ps4.elf. The secret is the
|
||||
# 64-hex-character key from tools/release-sign.c; "check" fails unless
|
||||
# the payload trusts that key.
|
||||
- name: Package and sign
|
||||
if: steps.version.outputs.tag
|
||||
env:
|
||||
RELEASE_SIGNING_KEY: ${{ secrets.RELEASE_SIGNING_KEY }}
|
||||
TAG: ${{ steps.version.outputs.tag }}
|
||||
run: |
|
||||
if [ -z "$RELEASE_SIGNING_KEY" ]; then
|
||||
echo "::error::RELEASE_SIGNING_KEY secret is not set; in-app updates need a signed release."
|
||||
exit 1
|
||||
fi
|
||||
make tools
|
||||
mkdir dist
|
||||
cp build/ps5/romm-sync.elf build/ps5/cacert.pem dist/
|
||||
cp build/ps5/romm-sync.elf build/ps4/romm-sync-ps4.elf build/ps4/romm-sync-ps4.pkg dist/
|
||||
sign() {
|
||||
build/host/release-sign manifest "$TAG" "dist/$1" > "dist/$2.json"
|
||||
build/host/release-sign sign "dist/$2.json" > "dist/$2.sig"
|
||||
build/host/release-sign check "dist/$2.json" "dist/$2.sig"
|
||||
}
|
||||
sign romm-sync.elf manifest
|
||||
sign romm-sync-ps4.elf manifest-ps4
|
||||
cd dist && sha256sum * > SHA256SUMS
|
||||
|
||||
# Creates the tag on the target commit if it doesn't exist yet. A tag pushed
|
||||
# by GITHUB_TOKEN does not re-trigger this workflow, so there's no double release.
|
||||
- name: Publish release
|
||||
if: steps.version.outputs.tag
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ steps.version.outputs.tag }}
|
||||
SHA: ${{ steps.version.outputs.sha }}
|
||||
# The notes are written by hand, in docs/releases/<tag>.md: the homebrew
|
||||
# catalog shows them as RommPS's release notes, and drops GitHub's
|
||||
# generated list.
|
||||
run: |
|
||||
gh release create "$GITHUB_REF_NAME" dist/* \
|
||||
--title "$GITHUB_REF_NAME" --generate-notes --verify-tag
|
||||
notes="docs/releases/$TAG.md"
|
||||
if [ ! -s "$notes" ]; then
|
||||
echo "::error::$notes is missing; write the release notes before tagging"
|
||||
exit 1
|
||||
fi
|
||||
gh release create "$TAG" dist/* \
|
||||
--target "$SHA" --title "$TAG" --notes-file "$notes"
|
||||
@@ -3,3 +3,10 @@ host-data/
|
||||
*.part
|
||||
.DS_Store
|
||||
.vscode/
|
||||
*.elf
|
||||
|
||||
# MGS interface sounds: used with permission in release builds, never published
|
||||
mgs-licensed-sounds.zip
|
||||
mgs-licensed-sounds/
|
||||
mgs1-menu-move.mp3
|
||||
platform/ps5/app/assets/sounds/
|
||||
@@ -1,36 +1,54 @@
|
||||
# romm-sync
|
||||
#
|
||||
# make host macOS/Linux build for development, in build/host/
|
||||
# make ps5 PS5 payload, needs the SDK (see tools/ps5-build.sh)
|
||||
# tools/ps5-build.sh "make ps5" inside the Docker SDK image
|
||||
# make deploy PS5_HOST=<ip> send the payload to the loader on port 9021
|
||||
# make install PS5_HOST=<ip> same, plus copy it into etaHEN's autostart over FTP
|
||||
# make ps5 PS5 payload, needs the SDK (see platform/ps5/ps5.mk)
|
||||
# make ps4 PS4 payload, needs the SDK (see platform/ps4/ps4.mk)
|
||||
# tools/ps5-build.sh, tools/ps4-build.sh the same inside the Docker SDK image
|
||||
# make deploy PS5_HOST=<ip> send the PS5 payload to the loader on port 9021
|
||||
# make install PS5_HOST=<ip> same, plus copy it into the HEN's autostart over FTP (PS5_HEN_DIR)
|
||||
# make ps4-pkg PS4 home screen app (OpenOrbis), with the payload inside
|
||||
# make install-ps4 PS4_HOST=<ip> copy the PS4 payload to /data/payloads over FTP (run it from GoldHEN's payload menu)
|
||||
# make ps5 VERSION=1.2.3 override the version baked into the build
|
||||
# make tools release-sign, for signing releases (tools/release-sign.c)
|
||||
#
|
||||
# Code shared by every console is in src/. Each console has its own folder in
|
||||
# platform/: platform.c (src/platform.h), its make rules and its SDK image.
|
||||
|
||||
# libchdr (BSD-3-Clause, third_party/libchdr), for PS1 serials in .chd games:
|
||||
# its CD codecs, with its LZMA and zstd decoders and the system's zlib.
|
||||
CHDR_SRCS := $(filter-out %microflac.cpp,$(wildcard third_party/libchdr/src/*.c)) \
|
||||
third_party/libchdr/deps/lzma/src/LzmaDec.c third_party/libchdr/deps/zstd/zstddeclib.c
|
||||
SRCS := src/main.c src/util.c src/http.c src/romm.c src/config.c src/state.c \
|
||||
src/profiles.c src/sync.c src/watch.c src/detect.c src/autostart.c src/memcard.c src/zip.c src/bundle.c src/gameid.c src/library.c src/pair.c src/web.c \
|
||||
third_party/cJSON.c third_party/md5.c
|
||||
HDRS := $(wildcard src/*.h) third_party/cJSON.h third_party/md5.h
|
||||
UI_HEADER := build/gen/ui_index.h build/gen/icon_png.h
|
||||
COMMON_CFLAGS := -std=gnu11 -O2 -Wall -Wextra -Wno-unused-parameter -Wno-unknown-warning-option -Wno-unreachable-code-generic-assoc -Isrc -Ithird_party -Ibuild/gen
|
||||
src/profiles.c src/sync.c src/watch.c src/detect.c src/autostart.c src/memcard.c src/zip.c src/bundle.c src/gameid.c src/covers.c src/library.c src/pair.c src/web.c src/update.c \
|
||||
third_party/cJSON.c third_party/md5.c third_party/monocypher.c third_party/monocypher-ed25519.c \
|
||||
$(CHDR_SRCS)
|
||||
HDRS := $(wildcard src/*.h) third_party/cJSON.h third_party/md5.h third_party/monocypher.h third_party/monocypher-ed25519.h
|
||||
UI_HEADER := build/gen/ui_index.h
|
||||
COMMON_CFLAGS := -std=gnu11 -O2 -Wall -Wextra -Wno-unused-parameter -Wno-unknown-warning-option -Wno-unreachable-code-generic-assoc -Isrc -Ithird_party -Ibuild/gen \
|
||||
-Ithird_party/libchdr/include -DCHDR_SYSTEM_ZLIB
|
||||
ifdef VERSION
|
||||
COMMON_CFLAGS += -DAPP_VERSION='"$(VERSION)"'
|
||||
endif
|
||||
# Shown in logs and diagnostics. The Docker builds pass it in, as git can't
|
||||
# read the repo from inside the container.
|
||||
BUILD_ID ?= $(shell git describe --always --dirty 2>/dev/null)
|
||||
ifneq ($(BUILD_ID),)
|
||||
COMMON_CFLAGS += -DAPP_BUILD='"$(BUILD_ID)"'
|
||||
endif
|
||||
COMMON_CFLAGS += $(EXTRA_CFLAGS)
|
||||
|
||||
PS5_HOST ?= ps5
|
||||
PS5_PORT ?= 9021
|
||||
|
||||
.PHONY: all host ps5 deploy install run clean
|
||||
.PHONY: all host ps5 ps4 tools deploy install install-ps4 run clean
|
||||
|
||||
all: host
|
||||
|
||||
build/gen/ui_index.h: ui/index.html third_party/qrcode.js tools/embed.py
|
||||
build/gen/ui_index.h: ui/index.html ui/style.css $(wildcard ui/themes/*.css) build/gen/inter-font.css third_party/qrcode.js tools/embed.py
|
||||
@mkdir -p $(dir $@)
|
||||
python3 tools/embed.py $< $@ UI_INDEX_HTML
|
||||
|
||||
build/gen/icon_png.h: assets/icon0.png tools/embed.py
|
||||
# Inter (SIL Open Font License, third_party/Inter-LICENSE.txt), inlined into the UI.
|
||||
build/gen/inter-font.css: third_party/inter-latin.woff2 tools/font_css.py
|
||||
@mkdir -p $(dir $@)
|
||||
python3 tools/embed.py $< $@ ICON_PNG
|
||||
python3 tools/font_css.py $< $@ Inter
|
||||
|
||||
# host build
|
||||
HOST_CC ?= cc
|
||||
@@ -38,37 +56,22 @@ HOST_BIN := build/host/romm-sync
|
||||
|
||||
host: $(HOST_BIN)
|
||||
|
||||
$(HOST_BIN): $(SRCS) src/platform_host.c $(HDRS) $(UI_HEADER)
|
||||
# Host builds also trust the test key in tests/, so the e2e test can sign updates.
|
||||
$(HOST_BIN): $(SRCS) platform/host/platform.c $(HDRS) $(UI_HEADER)
|
||||
@mkdir -p $(dir $@)
|
||||
$(HOST_CC) $(COMMON_CFLAGS) -o $@ $(SRCS) src/platform_host.c -lcurl -lz -lpthread
|
||||
$(HOST_CC) $(COMMON_CFLAGS) -DROMM_UPDATE_TEST_KEY -o $@ $(SRCS) platform/host/platform.c -lcurl -lz -lpthread
|
||||
|
||||
tools: build/host/release-sign
|
||||
|
||||
build/host/release-sign: tools/release-sign.c src/update_keys.h third_party/monocypher.c third_party/monocypher-ed25519.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(HOST_CC) -std=gnu11 -O2 -Wall -Isrc -Ithird_party -o $@ $(filter %.c,$^)
|
||||
|
||||
run: host
|
||||
ROMM_SYNC_DATA=$${ROMM_SYNC_DATA:-./host-data} $(HOST_BIN)
|
||||
|
||||
# PS5 build
|
||||
PS5_PAYLOAD_SDK ?= /opt/ps5-payload-sdk
|
||||
PS5_CC := $(PS5_PAYLOAD_SDK)/bin/prospero-clang
|
||||
PS5_PKGCONF := $(PS5_PAYLOAD_SDK)/bin/prospero-pkg-config
|
||||
PS5_ELF := build/ps5/romm-sync.elf
|
||||
|
||||
ps5: $(PS5_ELF)
|
||||
|
||||
$(PS5_ELF): $(SRCS) src/platform_ps5.c $(HDRS) $(UI_HEADER)
|
||||
@mkdir -p $(dir $@)
|
||||
$(PS5_CC) $(COMMON_CFLAGS) $$($(PS5_PKGCONF) --cflags libcurl) -DCURL_STATICLIB \
|
||||
-o $@ $(SRCS) src/platform_ps5.c \
|
||||
$$($(PS5_PKGCONF) --static --libs libcurl) -pthread -lSceSystemService -lSceAppInstUtil
|
||||
@cp $(PS5_PAYLOAD_SDK)/target/user/homebrew/etc/ca-bundle.crt build/ps5/cacert.pem 2>/dev/null || true
|
||||
|
||||
deploy: $(PS5_ELF)
|
||||
nc -w 1 $(PS5_HOST) $(PS5_PORT) < $(PS5_ELF)
|
||||
|
||||
# Copy the payload into etaHEN's autostart folder over FTP (port 2121), then run it.
|
||||
PS5_FTP ?= ftp://$(PS5_HOST):2121
|
||||
install: $(PS5_ELF)
|
||||
curl -sS --ftp-create-dirs -T $(PS5_ELF) $(PS5_FTP)/data/etaHEN/payloads/romm-sync.elf
|
||||
printf '' | curl -sS -T - $(PS5_FTP)/data/etaHEN/payloads/romm-sync.elf.auto_start
|
||||
nc -w 1 $(PS5_HOST) $(PS5_PORT) < $(PS5_ELF)
|
||||
include platform/ps5/ps5.mk
|
||||
include platform/ps4/ps4.mk
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
@@ -1,19 +1,57 @@
|
||||
# RomM Sync for PS5
|
||||
# RomM Server Client for PS4 and PS5
|
||||
|
||||

|
||||
|
||||
A [RomM](https://romm.app) companion client for jailbroken PS5 consoles (etaHEN + kstuff). Through my insatiable need to mod everything electronic I encounter, I had my 12.70 PS5 jailbroken the minute that P2JB was out. I maintain a RomM server to centralize all my _legal_ ROMs, saves, and savestates which all my devices sync with, and the PS5 needs similar treatment.
|
||||
A [RomM](https://romm.app) companion client for jailbroken PS5 consoles (etaHEN or onionHEN, with kstuff) and PS4 consoles (GoldHEN). Through my insatiable need to mod everything electronic I encounter, I had my 12.70 PS5 jailbroken the minute that P2JB was out. I maintain a RomM server to centralize all my _legal_ ROMs, saves, and savestates, which all my devices sync with, and the PS5 needs similar treatment.
|
||||
|
||||
- **Background save sync**
|
||||
- Monitors save file changes in the background, and uploads them as soon as they've changed. Periodic syncs also ensure that your PS5 always has the latest saves and savestates.
|
||||
- **Save and sate sync**
|
||||
- Monitors save file changes in the background, and uploads them as soon as they've changed. Periodic syncs also ensure that your PS4/5 always has the latest saves and save states.
|
||||
- **Interactive library**
|
||||
- Browse RomM from the PS5 browser (or some other device), download games and BIOS files into the right emulator folders, and resolve save conflicts.
|
||||
- **Pluggable emulator profiles**
|
||||
- RetroArch is supported out of the box. Mednafen and a PS2-ISO profile are included but disabled. I'll make sure we update as more emulators become available on the console.
|
||||
- Browse your RomM library from the RommPS app (or some other device via the Web UI), download games and BIOS files into the right emulator folders, and resolve save conflicts.
|
||||
- **Pluggable emulator profiles and multiple emulators per platform**
|
||||
- Fully customizable sync destinations, but includes frequently updated preset profiles for existing homebrew emulators.
|
||||
- Can sync games and saves and states for multiple emulators per platform (PS2 on RetroArch and PS5SX2 as an example) using hardlinks.
|
||||
- **Beautiful and Responsive UI**
|
||||
- Based on mihawk-99's Vulkan homebrew apps and BlackBearReloaded's UI kit, we get a nice, responsive feel. So worth it, it looks 1000x better. 15+ different themes to pick from.
|
||||
- **Background Service**
|
||||
- All operations are handled by a backend payload, which means syncing, downloading, and updating keep working in the background, so you can keep **gaming**.
|
||||
- **RomM API Compliant**
|
||||
- RommPS is meant to strictly stick to RomM's API and method of syncing saves and states, ensuring we don't break anything and support RomM's latest features.
|
||||
- **Secure and Human-Coded**
|
||||
- Each release is signed. The payload checks the signature before it updates itself, and ProsperoStore checks every download against the release's checksum. This app was primarily coded by a **human**, and will be frequently updated and maintained. AI was primarily used for explaining concepts, creating documentation, and converting data. Ya know, the tedious stuff.
|
||||
- **Easter Eggs**
|
||||
- Check in the Settings, scroll the settings list, see if you find the X. This **hint** won't be here forever.
|
||||
|
||||
## Supported Emulators and Games
|
||||
|
||||
Technically, this RomM client will work with all emulators, but the PS5 is currently limited on emulators available. This is simply a matter of time, and the client will be updated frequently as new emulators are released. Currently, we have focused on using mihawk-99's unofficial implementation of RetroArch. It works beautifully!
|
||||
Technically, RomM Sync on both PS4 and PS5 will support **any** emulator as long as you map the game, save, and save state folders correctly during emulator setup. Most should just work, but some emulators/consoles may have weird formats, or Romm may not fully support all features.
|
||||
|
||||
As a general rule, Game library syncing will pretty much work with anything. Saves/States are the more complex side of things.
|
||||
|
||||
### PS5 ###
|
||||
|
||||
| Emulator | Systems | Game Downloads | Saves | Comments |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| [RetroArch](https://github.com/mihawk-99/PS5_RetroArch) | NES, Famicom Disk System, SNES, Game Boy, Game Boy Color, Game Boy Advance, Genesis/Mega Drive, Master System, Game Gear, Sega CD, SG-1000, arcade (FinalBurn Neo), PS2 (LRPS2), PSP (PPSSPP), GameCube and Wii (Dolphin) | Full Support | Full Support | - |
|
||||
| [PSXS5](https://github.com/SynoPiia/PSXS5) | PS1 | Full Support | One memory card per game, and states | - |
|
||||
| [SwanStationPS5](https://github.com/darkxex/SwanStationPS5) | PS1 | Full Support | One memory card per game, and states | - |
|
||||
| [PS5SX2](https://github.com/Swordpdf/PS5SX2) | PS2 | Full Support | Its two shared memory cards, backed up to RomM (5.3 or newer) | - |
|
||||
| [PS5N64](https://github.com/Globaleliteee/PS5N64) | N64 | Full Support | Full Support | - |
|
||||
| [PS5NES](https://github.com/Globaleliteee/PS5NES) | NES | Broken | Broken | Broken, tried to report issue, but GH page gone |
|
||||
| [Porpoise](https://github.com/elripalda/Porpoise-Dolphin-Emulator-for-PS5) | Wii/GameCube | N/A | N/A | Couldn't exit sandbox w/o crash. [Issue](https://github.com/elripalda/Porpoise-Dolphin-Emulator-for-PS5/issues/18) reported |
|
||||
| [ProsperoEden](https://github.com/blackbearreloaded/ProsperoEden) | Switch | Manual Support | Researching | - |
|
||||
| [PS5X360](https://github.com/BrinooTk/PS5X360) | Xbox 360 | Manual Support | Researching | - |
|
||||
|
||||
To give some meaning to the table above. **Full support** means RommPS can detect and properly set game/save directories for that emulator. **Manual Support** means you can use it to sync games and/or saves, but you'll need to point it to the directories yourself. This typically means that full support is coming shortly. Broken or N/A means there was a problem keeping us from sync games and saves, or the emulator just fall out doesn't work or crashes. **Researching** means I am seeing what is possible when it comes to that sync type for the given emulator or game system.
|
||||
|
||||
### PS4 (experimental) ###
|
||||
|
||||
| Emulator | Systems |
|
||||
| --- | --- |
|
||||
| RetroArch | The RetroArch systems above, wherever the PS4 build has the core |
|
||||
| pSNES, pNES, pGEN, pGBA, pFBN | SNES; NES; Genesis, Master System, Game Gear, Sega CD, SG-1000; GBA, GBC, GB; arcade |
|
||||
|
||||
Only RetroArch has been tested on the PS4 so far.
|
||||
|
||||
## PS4/PS5 Games
|
||||
|
||||
@@ -21,22 +59,50 @@ If you are interested in downloading PS4 and PS5 games from your RomM server, Ro
|
||||
|
||||
## Requirements
|
||||
|
||||
- A PS5 running etaHEN
|
||||
- RomM 5.0 or newer. (5.3.0 if you want to do shared memory card syncing instead of per-game)
|
||||
- RetroArch installed as a homebrew app
|
||||
- A PS5 running etaHEN or onionHEN, or a PS4 running GoldHEN
|
||||
- RomM 5.2.0 or newer (5.3.0 if you want to do shared memory card syncing instead of per-game)
|
||||
- RetroArch installed
|
||||
- Whichever other emulators you want
|
||||
|
||||
## Install
|
||||
|
||||
Install **RommPS** from [ProsperoStore](https://homebrew.page) and open it. It'll set up the background daemon, and take you through a setup wizard. That's it: RommPS carries the RomM Sync payload and starts it for you. From then on it starts with your HEN, so after a jailbreak or waking the console it's ready to go.
|
||||
|
||||
Not using the store? Download `PPSA76677.zip` from the releases page, unzip it and copy the `PPSA76677` folder to `/data/homebrew/` on the console.
|
||||
|
||||
If you'd rather run just the payload, without the app:
|
||||
|
||||
1. Download the latest `romm-sync.elf` from the releases page.
|
||||
2. Send it to the console (or use whichever favorite tool you wish):
|
||||
```sh
|
||||
nc -w 1 <ps5-ip> 9021 < romm-sync.elf
|
||||
```
|
||||
3. A RomM Sync tile appears on the home screen after about 30 seconds. Open it, or go to `http://<ps5-ip>:8780` from another device.
|
||||
3. Set it up at `http://<ps5-ip>:8780` from another device.
|
||||
|
||||
To start RomM Sync with the console, copy the payload to etaHEN's autostart folder or use the autoloader. You can do this from the last setup step, or with `make install PS5_HOST=<ps5-ip>` if you build it yourself.
|
||||
On the PS5 the payload doesn't update itself: new versions come with RommPS. Without the app, download the new `romm-sync.elf` yourself.
|
||||
|
||||
If your RomM server uses HTTPS, copy `cacert.pem` from the release to `/data/romm-sync/cacert.pem`.
|
||||
RomM Sync starts with your HEN, and setup turns this on for you. Both HENs start `payloads/romm-sync.elf` when `romm-sync.elf.auto_start` sits next to it, and RomM Sync puts both in every HEN's folder on the console (`/data/etaHEN/payloads` and `/data/OnionHEN/payloads`), so switching HENs keeps working. If a payload manager like pldmgr has its own copy, that copy is kept up to date too. If you build it yourself, `make install PS5_HOST=<ps5-ip>` copies it over FTP (add `PS5_FTP=ftp://<ps5-ip>:1337 PS5_HEN_DIR=/data/OnionHEN` for onionHEN's FTP plugin).
|
||||
|
||||
### PS4
|
||||
|
||||
> **Experimental.** The PS4 build has only been tested with RetroArch so far. Standalone emulators may work, but haven't been confirmed. I spent all my focus on the PS5 build, so I didn't get to test as much, so consider it unstable and there may be gremlins for now.
|
||||
|
||||
Needs GoldHEN 2.4b18.5 or newer, whose payload menu runs ELF payloads.
|
||||
|
||||
1. Download the latest `romm-sync-ps4.pkg` from the releases page and install it with your package installer.
|
||||
2. Open **RomM Sync** from the home screen. It copies its payload, `romm-sync-ps4.elf`, to `/data/payloads/`.
|
||||
3. In GoldHEN Settings, open the payloader, pick `romm-sync-ps4.elf` and run it. Add it to the AutoRun queue there and it starts on every jailbreak.
|
||||
4. Open **RomM Sync** again: it shows the web UI. It's also at `http://<ps4-ip>:8780` from another device.
|
||||
|
||||
The app only installs the payload and shows the web UI; the payload does the syncing, in the background, whether or not the app is open. Without the package, copy `romm-sync-ps4.elf` to `/data/payloads/` yourself (GoldHEN's FTP server, or `make install-ps4 PS4_HOST=<ps4-ip>`).
|
||||
|
||||
Don't send the payload to GoldHEN's network loader on port 9090: GoldHEN 2.4 crashes on ELFs sent there. Make sure the console's date and time are set (GoldHEN can keep them updated), or HTTPS won't work. RetroArch is found in `/data/retroarch`, with its cores in `/data/self/retroarch/cores`, as its `retroarch.cfg` says.
|
||||
|
||||
## Updates
|
||||
|
||||
On the PS5, just update RommPS: from ProsperoStore (or other homebrew storefronts eventually), or by copying the new zip's folder over the old one. The background service comes with it, and RommPS swaps it in the next time you open it, once nothing is syncing or downloading. An older version never replaces a newer one.
|
||||
|
||||
On the PS4, the payload updates itself from Settings.
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -55,13 +121,17 @@ The setup walks you through five steps:
|
||||
LRPS2 keeps every game on one shared memory card by default. RomM stores saves per game, so pick one of these during setup:
|
||||
|
||||
- **One card per game.** Works with any RomM version. In RetroArch, open the LRPS2 core options and turn off _Shared Memory Cards_. After that, each game's card syncs like any other save. Saves already on the shared card stay there.
|
||||
- **Back up the shared card.** Needs RomM 5.3.0 or newer. The whole card is uploaded when it changes, and you can restore it from Settings. It isn't merged with other devices.
|
||||
- **Back up the shared card.** Needs RomM 5.3.0 or newer. The whole card is uploaded when it changes, and you can restore it from Settings in the web UI. It isn't merged with other devices.
|
||||
|
||||
PSP, GameCube and Wii saves are synced as one zip per game, the same format other RomM clients use. Depending on the client, they may or may not support unzipping the saves (I don't truly know), but this client does!
|
||||
|
||||
My opinion? One card per game is better since 8MB memory cards fill up quick.
|
||||
|
||||
## Emulator Profiles
|
||||
|
||||
Settings has a JSON editor for emulator profiles, and a table showing which folders are used for each platform. A profile lists an emulator's folders and the RomM platforms it plays:
|
||||
I've included direct support for most of the emulators that are out right now, except for the ones that won't even launch... you can sync games, states, and saves anywhere you want, but the presets should do it for most people.
|
||||
|
||||
Settings in the web UI has a JSON editor for emulator profiles, and a table showing which folders are used for each platform. A profile lists an emulator's folders and the RomM platforms it plays:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -78,16 +148,28 @@ Settings has a JSON editor for emulator profiles, and a table showing which fold
|
||||
}
|
||||
```
|
||||
|
||||
For RetroArch, it reads save and state folders from its own config, including its "sort by core" and "sort by content" options. RomM Sync never changes the emulator's settings. A save found outside the folder the emulator reads is listed on the status page and left alone.
|
||||
|
||||
## Building
|
||||
|
||||
```sh
|
||||
make host # macOS or Linux build for development
|
||||
make host tools # macOS or Linux build for development, plus tools/release-sign
|
||||
tools/ps5-build.sh # PS5 build in Docker, output in build/ps5/
|
||||
tools/ps4-build.sh # PS4 payload in Docker, output in build/ps4/
|
||||
tools/ps4-build.sh ps4-pkg # plus the PS4 home screen app (platform/ps4/app)
|
||||
tests/e2e.sh # end-to-end tests against a mock RomM server
|
||||
tests/autostart.sh # autostart, version checks and HEN folders
|
||||
```
|
||||
|
||||
Both consoles build from the same code in `src/`. What differs lives in `platform/<console>/`: `platform.c` (the `src/platform.h` interface), the make rules and the Docker SDK image. `platform/console/` has the parts the PS4 and PS5 share, and `platform/host/` is the development build.
|
||||
|
||||
## P.S to the industry
|
||||
|
||||
If buying isn't **owning**, then piracy **isn't** stealing. 🖕
|
||||
|
||||
## Shameless Plug
|
||||
|
||||
<a href='https://ko-fi.com/M7G128GENT' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi4.png?v=6' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||
|
||||
## License
|
||||
|
||||
GPL-3.0. Parts of the PS5 code are adapted from [ftpsrv](https://github.com/ps5-payload-dev/ftpsrv) and [websrv](https://github.com/ps5-payload-dev/websrv) by John Törnblom. [cJSON](https://github.com/DaveGamble/cJSON) and [qrcode-generator](https://github.com/kazuhikoarase/qrcode-generator) are MIT licensed.
|
||||
GPL-3.0. Parts of the console code are adapted from [ftpsrv](https://github.com/ps5-payload-dev/ftpsrv) and [websrv](https://github.com/ps5-payload-dev/websrv) by John Törnblom. [cJSON](https://github.com/DaveGamble/cJSON) and [qrcode-generator](https://github.com/kazuhikoarase/qrcode-generator) are MIT licensed. [Monocypher](https://monocypher.org) is CC0 or BSD-2-Clause.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 1.5 MiB |
@@ -0,0 +1,49 @@
|
||||
# Release checklist
|
||||
|
||||
Run all of this on a real console before tagging. Tests in CI (`tests/e2e.sh`, `tests/autostart.sh`) cover the logic, not the console.
|
||||
|
||||
## Before
|
||||
|
||||
- `kMinPayload` in `rommps_app.hpp`, `contentVersion` in RommPS's `param.json` and `APP_VERSION` in `src/util.h` all match the tag.
|
||||
- `docs/releases/<tag>.md` is written. ProsperoStore shows it as the release notes.
|
||||
- If this release changes who owns something on the console (the home screen icon, starting the payload, the autostart files, updates, the data folder), the old one gets removed or migrated in this same release.
|
||||
|
||||
## Upgrades
|
||||
|
||||
For each one, start from the old version actually installed and set up, then update. Check afterwards:
|
||||
|
||||
- one icon on the home screen (RommPS), no "RomM Sync" tile
|
||||
- one RomM Sync running, the new version (`/api/status`), lock file says the same
|
||||
- every HEN folder that exists has the new `romm-sync.elf` and `.auto_start` (`/api/autostart` lists the copies and their versions)
|
||||
- a pldmgr copy, if there was one, is the new version
|
||||
- no `.bak` files left in the payload folders
|
||||
- a sync runs and finishes without errors
|
||||
- no RomM Sync notifications while RommPS is installed
|
||||
|
||||
| From | How | Also check |
|
||||
| --- | --- | --- |
|
||||
| Nothing | ProsperoStore install, open RommPS | setup turns autostart on by default |
|
||||
| Nothing | zip copied to `/data/homebrew` | same as above |
|
||||
| 1.0.2 payload only, tile, etaHEN folder | in-app update from the web UI | tile stays (no RommPS), "Install RommPS" notice |
|
||||
| 1.0.2 as above | install RommPS, open it | tile goes away |
|
||||
| previous RommPS + payload | store update of RommPS, open it | payload swapped once idle, copies updated |
|
||||
| previous RommPS + payload | payload's own update first, RommPS later | "RommPS is out of date" notice |
|
||||
| any, autostart off | update | autostart stays off, nothing added to HEN folders |
|
||||
| any, old copy in pldmgr's autoload | update | the pldmgr copy is updated, its autoload list untouched |
|
||||
|
||||
## Boot and rest mode
|
||||
|
||||
- Reboot, jailbreak with onionHEN only, don't open RommPS: RomM Sync starts by itself, one copy.
|
||||
- Same with etaHEN.
|
||||
- Rest mode, then wake: same process keeps running (uptime carries on), web UI answers.
|
||||
- Open RommPS right after a jailbreak: it waits for RomM Sync instead of starting a second copy.
|
||||
|
||||
## Never downgrade
|
||||
|
||||
- Send an older `romm-sync.elf` to port 9021 while a newer one runs: it exits, the newer one keeps running.
|
||||
- Upload an older payload in the web UI's autostart: the newer copies on disk stay.
|
||||
|
||||
## After
|
||||
|
||||
- The release workflow published the payloads, then `tools/release-app.sh <tag> --upload` attached `PPSA76677.zip`.
|
||||
- The catalog bot's pull request for the new version shows up the next day.
|
||||
@@ -0,0 +1,31 @@
|
||||
Install RommPS from ProsperoStore, open it, and you're set up: it carries RomM Sync and starts it for you. Everything below is new since 1.0.2.
|
||||
|
||||
### What's new
|
||||
|
||||
- RommPS, a native PS5 app. Your whole RomM library on the TV, browsable with the controller: platforms, an A to Z bar, search across everything, game details (year, genre, play time, How Long to Beat) and one-press downloads. It runs on the same Vulkan homebrew UI as mihawk-99's apps, so it's fast and looks great.
|
||||
- Standalone PS5 emulators. PSXS5 and SwanStationPS5 for PS1, PS5SX2 for PS2 and PS5N64 for N64 now sync saves and states, not just RetroArch.
|
||||
- More than one emulator per system. Pick a main one, and the others get the same games. Where the save format matches, they share one save.
|
||||
- Your own folders. If setup doesn't recognise an emulator (or doesn't know a system), point it at the folders yourself.
|
||||
- Covers are kept on the console, so the library opens fast and scrolling through hundreds of games doesn't wait on the server.
|
||||
- Download speeds per game and in total, and you can cancel a download (hold Cross).
|
||||
- Games with more than one version in RomM now say what sets each one apart, like the region or the disc.
|
||||
|
||||
### Fixed and changed
|
||||
|
||||
- Junk your RomM scan picked up, like a frontend's metadata.txt, no longer shows up as a game or downloads. Neither do manuals and soundtracks inside a game's folder.
|
||||
- Downloads show the game's name right away instead of "Game #id".
|
||||
- Pairing with a different RomM server no longer mixes up the old server's games and saves.
|
||||
- The PS4 build is experimental. I've only tested it with RetroArch so far.
|
||||
|
||||
### Good to know
|
||||
|
||||
- PS5NES and Porpoise don't have presets yet, since neither works right on my console. If yours does, set it up with your own folders.
|
||||
- You need RomM 5.2.0 or newer (5.3.0 to back up shared PS2 memory cards), and a PS5 with etaHEN or onionHEN plus kstuff.
|
||||
- RommPS starts RomM Sync by itself and keeps it up to date with the app. Setup's last step makes it start with every jailbreak.
|
||||
|
||||
### Which file do I need?
|
||||
|
||||
- PS5: install RommPS from ProsperoStore. Or unzip PPSA76677.zip and copy the PPSA76677 folder to /data/homebrew/. That's all you need.
|
||||
- PS5 without the app: send romm-sync.elf to your HEN's payload loader (port 9021) and set it up from a browser at port 8780.
|
||||
- PS4 (experimental): install romm-sync-ps4.pkg and open RomM Sync, then run romm-sync-ps4.elf from GoldHEN's payloader.
|
||||
- The manifest files are for the in-app updater, and SHA256SUMS is for checking downloads. You don't need either.
|
||||
@@ -0,0 +1,20 @@
|
||||
Update RommPS and open it once. It takes care of the rest.
|
||||
|
||||
### Fixed
|
||||
|
||||
- The extra "RomM Sync" icon is gone.
|
||||
- RomM Sync now starts with onionHEN too.
|
||||
- Old copies of RomM Sync no longer start or replace newer ones.
|
||||
- RommPS shows the right time for the last sync.
|
||||
- Saving settings in the web UI no longer undoes changes made in RommPS.
|
||||
- The library picks up emulator changes right away, without restarting RommPS.
|
||||
|
||||
### Added
|
||||
|
||||
- RomM Sync starts with the console by default.
|
||||
- New home screen art.
|
||||
|
||||
### Changed
|
||||
|
||||
- RomM Sync updates come with RommPS.
|
||||
- No more RomM Sync notifications while RommPS is installed, except sync results.
|
||||
@@ -0,0 +1,320 @@
|
||||
/* platform.h parts shared by the PS4 and PS5 builds (console.h).
|
||||
*
|
||||
* Adapted from John Törnblom's GPLv3 ftpsrv and websrv: process lookup and the
|
||||
* single-instance check (ftpsrv main-orbis.c and main-prospero.c, which use the
|
||||
* same kinfo_proc offsets), notifications and the LAN address (websrv). */
|
||||
#include "console.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <netinet/in.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/file.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
/* libkernel: same layout as ftpsrv/websrv notify.c, on both consoles */
|
||||
typedef struct notify_request {
|
||||
char useless1[45];
|
||||
char message[3075];
|
||||
} notify_request_t;
|
||||
int sceKernelSendNotificationRequest(int, notify_request_t *, size_t, int);
|
||||
|
||||
|
||||
int console_mkdir_p(const char *path) {
|
||||
char tmp[256];
|
||||
size_t len = strlen(path);
|
||||
if (len == 0 || len >= sizeof tmp) return -1;
|
||||
memcpy(tmp, path, len + 1);
|
||||
for (char *p = tmp + 1; *p; p++) {
|
||||
if (*p != '/') continue;
|
||||
*p = 0;
|
||||
if (mkdir(tmp, 0755) && errno != EEXIST) return -1;
|
||||
*p = '/';
|
||||
}
|
||||
if (mkdir(tmp, 0755) && errno != EEXIST) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* sysctl(KERN_PROC, KERN_PROC_PROC), read with the offsets ftpsrv uses for the
|
||||
* console kinfo_proc: ki_structsize @0, ki_pid @72, ki_tdname @447
|
||||
* (TDNAMLEN+1 = 17 bytes). */
|
||||
int console_for_each_proc(console_proc_cb cb, void *ctx) {
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0};
|
||||
size_t buf_size = 0;
|
||||
uint8_t *buf;
|
||||
int ret = 0;
|
||||
|
||||
if (sysctl(mib, 4, NULL, &buf_size, NULL, 0) || buf_size == 0) return -1;
|
||||
buf_size += buf_size / 4; /* processes may appear between the two calls */
|
||||
if (!(buf = malloc(buf_size))) return -1;
|
||||
if (sysctl(mib, 4, buf, &buf_size, NULL, 0)) {
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
for (uint8_t *ptr = buf; ptr + 464 <= buf + buf_size;) {
|
||||
int ki_structsize = *(int *)ptr;
|
||||
pid_t ki_pid = *(pid_t *)&ptr[72];
|
||||
char tdname[17];
|
||||
if (ki_structsize < 464 || ptr + ki_structsize > buf + buf_size) break;
|
||||
memcpy(tdname, &ptr[447], 16);
|
||||
tdname[16] = 0;
|
||||
ptr += ki_structsize;
|
||||
if ((ret = cb(ki_pid, tdname, ctx))) break;
|
||||
}
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct find_ctx { const char *name; pid_t self; pid_t found; };
|
||||
|
||||
static int find_cb(pid_t pid, const char *tdname, void *vctx) {
|
||||
struct find_ctx *c = vctx;
|
||||
if (pid != c->self && !strcmp(tdname, c->name)) {
|
||||
c->found = pid;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static pid_t find_pid(const char *name) {
|
||||
struct find_ctx c = {name, getpid(), -1};
|
||||
if (console_for_each_proc(find_cb, &c) < 0) return -1;
|
||||
return c.found;
|
||||
}
|
||||
|
||||
/* Kills every other process called CONSOLE_PROC_NAME, giving up after 10 s. */
|
||||
static void kill_by_name(void) {
|
||||
pid_t pid;
|
||||
int tries = 0;
|
||||
while ((pid = find_pid(CONSOLE_PROC_NAME)) > 0) {
|
||||
if (kill(pid, SIGKILL) && errno != ESRCH) break;
|
||||
sleep(1);
|
||||
if (++tries >= 10) break;
|
||||
}
|
||||
}
|
||||
|
||||
struct pid_ctx { pid_t pid; };
|
||||
|
||||
static int has_pid_cb(pid_t pid, const char *tdname, void *vctx) {
|
||||
struct pid_ctx *c = vctx;
|
||||
return pid == c->pid && !strcmp(tdname, CONSOLE_PROC_NAME);
|
||||
}
|
||||
|
||||
/* "1.2.3" into numbers; anything after the patch is ignored. */
|
||||
static int parse_version(const char *s, int v[3]) {
|
||||
v[0] = v[1] = v[2] = 0;
|
||||
if (*s == 'v') s++;
|
||||
return sscanf(s, "%d.%d.%d", &v[0], &v[1], &v[2]) >= 2 ? 0 : -1;
|
||||
}
|
||||
|
||||
/* Whether this copy should take over from one running `have`. A version that
|
||||
* doesn't parse (a hand-made build) is sent on purpose, so it takes over. */
|
||||
static int takes_over(const char *mine, const char *have) {
|
||||
int x[3], y[3];
|
||||
if (parse_version(mine, x) || parse_version(have, y)) return 1;
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (x[i] != y[i]) return x[i] > y[i];
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Held for as long as this copy runs; the kernel lets go when the process
|
||||
* ends, however it ends. The file says which version holds it and its pid. */
|
||||
static int g_lock_fd = -1;
|
||||
|
||||
/* One line in the log, written before the log is set up: a copy that quits
|
||||
* here would otherwise leave no trace. */
|
||||
static void note(const char *fmt, ...) {
|
||||
char line[256], when[32];
|
||||
time_t now = time(NULL);
|
||||
struct tm tm;
|
||||
localtime_r(&now, &tm);
|
||||
strftime(when, sizeof when, "%Y-%m-%d %H:%M:%S", &tm);
|
||||
int n = snprintf(line, sizeof line, "%s [INFO] ", when);
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
n += vsnprintf(line + n, sizeof line - (size_t)n - 1, fmt, ap);
|
||||
va_end(ap);
|
||||
if (n > (int)sizeof line - 2) n = (int)sizeof line - 2;
|
||||
line[n++] = '\n';
|
||||
int fd = open(CONSOLE_DATA_DIR "/romm-sync.log", O_WRONLY | O_APPEND | O_CREAT, 0666);
|
||||
if (fd < 0) return;
|
||||
write(fd, line, (size_t)n);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static int lock_wait(int fd, int seconds) {
|
||||
for (int i = 0; i <= seconds * 4; i++) {
|
||||
if (!flock(fd, LOCK_EX | LOCK_NB)) return 0;
|
||||
usleep(250 * 1000);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int console_single_instance(const char *version) {
|
||||
console_mkdir_p(CONSOLE_DATA_DIR);
|
||||
int fd = open(CONSOLE_LOCK_FILE, O_RDWR | O_CREAT, 0644);
|
||||
if (fd < 0) {
|
||||
/* No lock to be had: run, as versions before the lock did. */
|
||||
kill_by_name();
|
||||
syscall(SYS_thr_set_name, -1, CONSOLE_PROC_NAME);
|
||||
return 0;
|
||||
}
|
||||
if (flock(fd, LOCK_EX | LOCK_NB)) {
|
||||
char have[32] = "";
|
||||
pid_t holder = 0;
|
||||
int alive = 0;
|
||||
/* A copy that took the lock a moment ago may not have said who it is
|
||||
* yet; until it does, the file still names the copy before it. */
|
||||
for (int tries = 0; tries < 8 && !alive; tries++) {
|
||||
char buf[64] = "";
|
||||
ssize_t n = pread(fd, buf, sizeof buf - 1, 0);
|
||||
if (n > 0) buf[n] = 0;
|
||||
have[0] = 0;
|
||||
holder = 0;
|
||||
sscanf(buf, "%31s %d", have, &holder);
|
||||
struct pid_ctx c = {holder};
|
||||
alive = holder > 0 && console_for_each_proc(has_pid_cb, &c) == 1;
|
||||
if (!alive) usleep(250 * 1000);
|
||||
}
|
||||
/* Held, but by no copy of ours: RommPS checking whether one runs.
|
||||
* It lets go at once. */
|
||||
if (!alive && !lock_wait(fd, 3)) goto locked;
|
||||
if (!takes_over(version, have)) {
|
||||
note("romm-sync %s not started: %s is already running (pid %d)", version, have, (int)holder);
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
/* Older: stop it, but only if that pid is still RomM Sync. */
|
||||
note("romm-sync %s replacing %s (pid %d)", version, have, (int)holder);
|
||||
struct pid_ctx c = {holder};
|
||||
if (holder > 0 && console_for_each_proc(has_pid_cb, &c) == 1) kill(holder, SIGKILL);
|
||||
if (lock_wait(fd, 10)) {
|
||||
note("romm-sync %s not started: %s didn't stop", version, have);
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
locked:;
|
||||
/* Say who holds the lock first, for any copy starting right now. */
|
||||
char line[64];
|
||||
int len = snprintf(line, sizeof line, "%s %d\n", version, (int)getpid());
|
||||
if (!ftruncate(fd, 0) && len > 0) pwrite(fd, line, (size_t)len, 0);
|
||||
fchmod(fd, 0644);
|
||||
g_lock_fd = fd;
|
||||
syscall(SYS_thr_set_name, -1, CONSOLE_PROC_NAME);
|
||||
/* Copies from before the lock don't hold it: find them by name. A copy
|
||||
* still deciding hasn't named itself yet, so it's safe from this. */
|
||||
kill_by_name();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int console_send_elf(int port, const void *elf, size_t len) {
|
||||
struct sockaddr_in sa;
|
||||
memset(&sa, 0, sizeof sa);
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons((uint16_t)port);
|
||||
sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (fd < 0) return -1;
|
||||
if (connect(fd, (struct sockaddr *)&sa, sizeof sa) != 0) {
|
||||
int e = errno;
|
||||
close(fd);
|
||||
errno = e;
|
||||
return -1;
|
||||
}
|
||||
const char *p = elf;
|
||||
while (len) {
|
||||
ssize_t w = write(fd, p, len);
|
||||
if (w < 0 && errno == EINTR) continue;
|
||||
if (w <= 0) {
|
||||
int e = w < 0 ? errno : EPIPE;
|
||||
close(fd);
|
||||
errno = e;
|
||||
return -1;
|
||||
}
|
||||
p += w;
|
||||
len -= (size_t)w;
|
||||
}
|
||||
shutdown(fd, SHUT_WR);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int console_valid_title_id(const char *s) {
|
||||
for (int i = 0; i < 9; i++) {
|
||||
char ch = s[i];
|
||||
if (i < 4 ? !(ch >= 'A' && ch <= 'Z') : !(ch >= '0' && ch <= '9')) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* kern.sdk_version is 0xMMmmxxxx with the firmware version in BCD: 0x09008031 is 9.00. */
|
||||
void console_describe(char *buf, size_t n, const char *host_title) {
|
||||
uint32_t sdk = 0;
|
||||
size_t len = sizeof sdk;
|
||||
char fw[32] = "unknown";
|
||||
if (!sysctlbyname("kern.sdk_version", &sdk, &len, NULL, 0) && sdk)
|
||||
snprintf(fw, sizeof fw, "%x.%02x (0x%08x)", sdk >> 24, (sdk >> 16) & 0xff, sdk);
|
||||
snprintf(buf, n, "%s firmware %s, pid %d, uid %d, host process %s", plat_info()->console, fw,
|
||||
(int)getpid(), (int)getuid(), host_title && host_title[0] ? host_title : "none");
|
||||
}
|
||||
|
||||
void plat_notify(const char *fmt, ...) {
|
||||
notify_request_t req;
|
||||
va_list ap;
|
||||
|
||||
memset(&req, 0, sizeof req);
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(req.message, sizeof req.message, fmt, ap);
|
||||
va_end(ap);
|
||||
sceKernelSendNotificationRequest(0, &req, sizeof req, 0);
|
||||
}
|
||||
|
||||
const char *plat_data_dir(void) {
|
||||
console_mkdir_p(CONSOLE_DATA_DIR);
|
||||
return CONSOLE_DATA_DIR;
|
||||
}
|
||||
|
||||
const char *plat_ca_bundle(void) {
|
||||
struct stat st;
|
||||
if (!stat(CONSOLE_CA_BUNDLE, &st) && st.st_size > 0) return CONSOLE_CA_BUNDLE;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *plat_name(void) { return plat_info()->name; }
|
||||
|
||||
void plat_local_ip(char *buf, size_t n) {
|
||||
struct ifaddrs *ifaddr = NULL;
|
||||
char ip[INET_ADDRSTRLEN];
|
||||
|
||||
if (!buf || !n) return;
|
||||
buf[0] = 0;
|
||||
if (getifaddrs(&ifaddr) == -1) return;
|
||||
|
||||
/* websrv sys.c: first AF_INET that is not loopback and not 0.x */
|
||||
for (struct ifaddrs *ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
|
||||
if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET) continue;
|
||||
if (ifa->ifa_name && !strncmp("lo", ifa->ifa_name, 2)) continue;
|
||||
struct sockaddr_in *in = (struct sockaddr_in *)ifa->ifa_addr;
|
||||
if (!inet_ntop(AF_INET, &in->sin_addr, ip, sizeof ip)) continue;
|
||||
if (!strncmp("0.", ip, 2)) continue;
|
||||
snprintf(buf, n, "%s", ip);
|
||||
break;
|
||||
}
|
||||
freeifaddrs(ifaddr);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/* Shared by platform/ps4 and platform/ps5. Both consoles run the same kind of
|
||||
* FreeBSD userland, so notifications, the process list, the data folder and
|
||||
* the ELF loader protocol work the same way; console.c implements those parts
|
||||
* of platform.h. Only plain libc here, so it builds on its own. */
|
||||
#ifndef ROMM_CONSOLE_H
|
||||
#define ROMM_CONSOLE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#define CONSOLE_PROC_NAME "romm-sync.elf"
|
||||
#define CONSOLE_DATA_DIR "/data/romm-sync"
|
||||
#define CONSOLE_CA_BUNDLE CONSOLE_DATA_DIR "/cacert.pem"
|
||||
/* Held by the running copy; holds "<version> <pid>". RommPS reads it too. */
|
||||
#define CONSOLE_LOCK_FILE CONSOLE_DATA_DIR "/romm-sync.lock"
|
||||
|
||||
int console_mkdir_p(const char *path);
|
||||
|
||||
/* Visits every process. cb returns nonzero to stop; that value is returned,
|
||||
* or -1 if the list can't be read. */
|
||||
typedef int (*console_proc_cb)(pid_t pid, const char *tdname, void *ctx);
|
||||
int console_for_each_proc(console_proc_cb cb, void *ctx);
|
||||
|
||||
/* Makes this the only copy running. A copy holding the lock keeps running if
|
||||
* it's the same version or newer, and 1 is returned: this copy should quit.
|
||||
* Otherwise it's stopped, as is any copy from before the lock, and this one
|
||||
* takes the lock and the name CONSOLE_PROC_NAME. */
|
||||
int console_single_instance(const char *version);
|
||||
|
||||
/* Sends an ELF to a loader listening on 127.0.0.1:port. -1 with errno set. */
|
||||
int console_send_elf(int port, const void *elf, size_t len);
|
||||
|
||||
/* PS4/PS5 title ids: 4 uppercase letters + 5 digits (CUSA00001, PPSA01234). */
|
||||
int console_valid_title_id(const char *s);
|
||||
|
||||
/* plat_describe() with the title id of the process we run in ("" if none). */
|
||||
void console_describe(char *buf, size_t n, const char *host_title);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,118 @@
|
||||
/* platform.h for macOS and Linux, used for development and tests.
|
||||
* ROMM_SYNC_DATA sets the data folder (default ./host-data). Writing a title id
|
||||
* to <data>/foreground pretends a game is running. It presents itself as a PS5,
|
||||
* so the end-to-end tests cover the PS5 paths and release assets. */
|
||||
#include <arpa/inet.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <netinet/in.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "util.h"
|
||||
|
||||
static char g_data[PATH_MAX_LEN];
|
||||
|
||||
static const char *const HOMEBREW_DIRS[] = {NULL}; /* ROMM_SYNC_HOMEBREW */
|
||||
static const char *const EMULATOR_DIRS[] = {NULL};
|
||||
|
||||
static plat_info_t INFO = {
|
||||
.name = "host",
|
||||
.console = "PlayStation 5",
|
||||
.client = "romm-sync-ps5",
|
||||
.loader = "etaHEN",
|
||||
.payload = "romm-sync.elf",
|
||||
.manifest = "manifest.json",
|
||||
.manifest_sig = "manifest.sig",
|
||||
.autostart_dir = "./host-data/etaHEN/payloads", /* ROMM_SYNC_AUTOSTART in tests */
|
||||
.autostart_flag = "romm-sync.elf.auto_start",
|
||||
.homebrew_dirs = HOMEBREW_DIRS,
|
||||
.emulator_dirs = EMULATOR_DIRS,
|
||||
.retroarch_root = "/data/homebrew/PPSA99169",
|
||||
.mednafen_root = "/data/homebrew/Mednafen",
|
||||
.can_relaunch = 1,
|
||||
.self_update = 1, /* ROMM_SYNC_SELF_UPDATE=0 tests the PS5's way */
|
||||
};
|
||||
|
||||
const plat_info_t *plat_info(void) { return &INFO; }
|
||||
|
||||
int plat_init(void) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
const char *su = getenv("ROMM_SYNC_SELF_UPDATE");
|
||||
if (su && *su) INFO.self_update = atoi(su);
|
||||
const char *d = getenv("ROMM_SYNC_DATA");
|
||||
str_copy(g_data, sizeof g_data, d && *d ? d : "./host-data");
|
||||
mkdir_p(g_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void plat_notify(const char *fmt, ...) {
|
||||
char msg[512];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(msg, sizeof msg, fmt, ap);
|
||||
va_end(ap);
|
||||
fprintf(stderr, "[NOTIFY] %s\n", msg);
|
||||
}
|
||||
|
||||
int plat_running_title(char *title_id, size_t n) {
|
||||
char p[PATH_MAX_LEN];
|
||||
path_join(p, sizeof p, g_data, "foreground");
|
||||
char *txt = read_file(p, NULL);
|
||||
if (!txt) return 0;
|
||||
txt[strcspn(txt, "\r\n")] = 0;
|
||||
str_copy(title_id, n, txt);
|
||||
free(txt);
|
||||
return title_id[0] ? 1 : 0;
|
||||
}
|
||||
|
||||
const char *plat_data_dir(void) { return g_data; }
|
||||
|
||||
const char *plat_ca_bundle(void) {
|
||||
const char *c = getenv("ROMM_SYNC_CA");
|
||||
return c && *c ? c : NULL;
|
||||
}
|
||||
|
||||
void plat_remove_old_tile(void) {}
|
||||
|
||||
int plat_app_installed(void) {
|
||||
const char *e = getenv("ROMM_SYNC_APP_INSTALLED");
|
||||
return e && *e == '1';
|
||||
}
|
||||
|
||||
void plat_app_version(char *out, size_t n) {
|
||||
const char *e = getenv("ROMM_SYNC_APP_VERSION");
|
||||
str_copy(out, n, e ? e : "");
|
||||
}
|
||||
|
||||
int plat_launch_elf(const void *elf, size_t len) {
|
||||
char p[PATH_MAX_LEN];
|
||||
path_join(p, sizeof p, g_data, "launched.elf");
|
||||
return write_file_atomic(p, elf, len);
|
||||
}
|
||||
|
||||
const char *plat_name(void) { return INFO.name; }
|
||||
|
||||
void plat_describe(char *buf, size_t n) {
|
||||
struct utsname u;
|
||||
if (uname(&u)) snprintf(buf, n, "host build");
|
||||
else snprintf(buf, n, "host build on %s %s %s", u.sysname, u.release, u.machine);
|
||||
}
|
||||
|
||||
void plat_local_ip(char *buf, size_t n) {
|
||||
buf[0] = 0;
|
||||
struct ifaddrs *ifs, *i;
|
||||
if (getifaddrs(&ifs) != 0) return;
|
||||
for (i = ifs; i; i = i->ifa_next) {
|
||||
if (!i->ifa_addr || i->ifa_addr->sa_family != AF_INET) continue;
|
||||
const struct sockaddr_in *a = (const struct sockaddr_in *)i->ifa_addr;
|
||||
if (ntohl(a->sin_addr.s_addr) >> 24 == 127) continue;
|
||||
inet_ntop(AF_INET, &a->sin_addr, buf, (socklen_t)n);
|
||||
break;
|
||||
}
|
||||
freeifaddrs(ifs);
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
/* RomM Sync for PS4: the home screen app. The sync itself is the payload,
|
||||
* which GoldHEN runs from /data/payloads (platform/ps4/platform.c); this app
|
||||
* only installs that payload and shows its web UI, so it never needs to stay
|
||||
* running. Built with OpenOrbis (platform/ps4/ps4.mk, "make ps4-pkg").
|
||||
*
|
||||
* When opened it:
|
||||
* 1. copies the payload bundled in the package to /data/payloads when it's
|
||||
* missing or older than the bundled one. Versions come from the
|
||||
* ROMM_SYNC_VERSION= tag in each payload (src/main.c), so an older app
|
||||
* never undoes a newer payload from an update or a manual copy. This app
|
||||
* runs as a regular user, so it only relies on /data/payloads being
|
||||
* writable. Files are read in full: stat() is unreliable in the sandbox;
|
||||
* 2. checks that RomM Sync answers on its web port;
|
||||
* 3. shows the web UI in the system web browser dialog, or the Browser app
|
||||
* if the dialog won't open; if RomM Sync isn't running, says how to start it.
|
||||
*
|
||||
* Each step goes to /data/romm-sync/launcher.log, for bug reports.
|
||||
*
|
||||
* The web browser dialog has no OpenOrbis types; its layout is Sony's
|
||||
* SceWebBrowserDialogParam as used by Nuvio-PS5 and EVO Player. */
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <orbis/CommonDialog.h>
|
||||
#include <orbis/MsgDialog.h>
|
||||
#include <orbis/Sysmodule.h>
|
||||
#include <orbis/SystemService.h>
|
||||
#include <orbis/UserService.h>
|
||||
|
||||
#ifndef APP_VERSION
|
||||
#define APP_VERSION "dev"
|
||||
#endif
|
||||
|
||||
#define BUNDLED_PAYLOAD "/app0/romm-sync-ps4.elf"
|
||||
#define PAYLOAD_DIR "/data/payloads"
|
||||
#define PAYLOAD_PATH PAYLOAD_DIR "/romm-sync-ps4.elf"
|
||||
#define DATA_DIR "/data/romm-sync"
|
||||
#define LOG_PATH DATA_DIR "/launcher.log"
|
||||
#define CONFIG_PATH DATA_DIR "/config.json"
|
||||
#define VERSION_TAG "ROMM_SYNC_VERSION="
|
||||
#define DEFAULT_PORT 8780
|
||||
|
||||
/* libSceWebBrowserDialog */
|
||||
typedef struct {
|
||||
OrbisCommonDialogBaseParam base;
|
||||
uint64_t size;
|
||||
int32_t mode; /* 1: the browser's own layout, 2: the rectangle below */
|
||||
int32_t user_id;
|
||||
const char *url;
|
||||
void *callback_init;
|
||||
uint16_t width, height, pos_x, pos_y;
|
||||
uint32_t parts;
|
||||
uint16_t header_width, header_x, header_y, pad0;
|
||||
uint32_t control;
|
||||
void *ime_param;
|
||||
void *webview_param;
|
||||
uint32_t animation;
|
||||
uint8_t reserved[202];
|
||||
uint16_t tail_pad;
|
||||
} web_dialog_param;
|
||||
_Static_assert(sizeof(web_dialog_param) == 328, "SceWebBrowserDialogParam is 328 bytes");
|
||||
|
||||
int sceWebBrowserDialogInitialize(void);
|
||||
int sceWebBrowserDialogOpen(web_dialog_param *param);
|
||||
int sceWebBrowserDialogUpdateStatus(void);
|
||||
int sceWebBrowserDialogClose(void);
|
||||
int sceWebBrowserDialogTerminate(void);
|
||||
/* OpenOrbis declares it without parameters; this is the real signature. */
|
||||
int launch_web_browser(const char *uri, void *param) __asm__("sceSystemServiceLaunchWebBrowser");
|
||||
|
||||
static void logf_(const char *fmt, ...) {
|
||||
FILE *f = fopen(LOG_PATH, "a");
|
||||
char ts[32];
|
||||
time_t now = time(NULL);
|
||||
va_list ap;
|
||||
if (!f) return;
|
||||
strftime(ts, sizeof ts, "%Y-%m-%d %H:%M:%S", localtime(&now));
|
||||
fprintf(f, "%s ", ts);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(f, fmt, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', f);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
static int user_id(void) {
|
||||
int id = -1;
|
||||
if (sceUserServiceGetForegroundUser(&id) == 0 && id != -1 && id != 0xff) return id;
|
||||
if (sceUserServiceGetInitialUser(&id) == 0) return id;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Whole file, by reading: stat() sizes can't be trusted in the sandbox. */
|
||||
static unsigned char *slurp(const char *path, size_t *len) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
unsigned char *buf = NULL;
|
||||
size_t cap = 0, n = 0, r;
|
||||
if (!f) return NULL;
|
||||
do {
|
||||
if (n == cap) {
|
||||
unsigned char *nb = realloc(buf, cap = cap ? cap * 2 : 1 << 20);
|
||||
if (!nb) {
|
||||
free(buf);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
buf = nb;
|
||||
}
|
||||
r = fread(buf + n, 1, cap - n, f);
|
||||
n += r;
|
||||
} while (r > 0);
|
||||
fclose(f);
|
||||
*len = n;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* The version in a payload's ROMM_SYNC_VERSION= tag, or "" if it has none
|
||||
* (payloads older than the tag). */
|
||||
static void payload_tag(const unsigned char *elf, size_t len, char *out, size_t n) {
|
||||
size_t tl = strlen(VERSION_TAG);
|
||||
out[0] = 0;
|
||||
for (size_t i = 0; i + tl < len; i++) {
|
||||
if (elf[i] != 'R' || memcmp(elf + i, VERSION_TAG, tl) != 0) continue;
|
||||
size_t j = 0;
|
||||
for (const unsigned char *v = elf + i + tl; v < elf + len && *v != '\n' && *v && j + 1 < n; v++) out[j++] = (char)*v;
|
||||
out[j] = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* "1.2.3" or "1.2.3-ps4-prototype.6": the three numbers, and the last number
|
||||
* of the pre-release part (-1 for a release). 0 if it isn't a version. */
|
||||
static int parse_version(const char *s, int v[4]) {
|
||||
v[3] = -1;
|
||||
if (sscanf(s, "%d.%d.%d", &v[0], &v[1], &v[2]) != 3) return 0;
|
||||
const char *pre = strchr(s, '-');
|
||||
if (pre) {
|
||||
v[3] = 0;
|
||||
for (const char *c = pre; *c; c++)
|
||||
if (*c >= '0' && *c <= '9' && (c == pre || c[-1] < '0' || c[-1] > '9')) v[3] = atoi(c);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* >0 if a is newer than b. A release is newer than its pre-releases. */
|
||||
static int compare_versions(const int a[4], const int b[4]) {
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (a[i] != b[i]) return a[i] - b[i];
|
||||
if ((a[3] < 0) != (b[3] < 0)) return a[3] < 0 ? 1 : -1;
|
||||
return a[3] - b[3];
|
||||
}
|
||||
|
||||
/* 1 if installed or replaced, 0 if left as it is, -1 on failure. */
|
||||
static int install_payload(void) {
|
||||
size_t want_len = 0, have_len = 0;
|
||||
unsigned char *want = slurp(BUNDLED_PAYLOAD, &want_len), *have;
|
||||
char want_ver[64], have_ver[64];
|
||||
int wv[4], hv[4], rc = -1;
|
||||
if (!want || want_len == 0) {
|
||||
logf_("can't read the bundled payload %s", BUNDLED_PAYLOAD);
|
||||
free(want);
|
||||
return -1;
|
||||
}
|
||||
payload_tag(want, want_len, want_ver, sizeof want_ver);
|
||||
have = slurp(PAYLOAD_PATH, &have_len);
|
||||
if (have) payload_tag(have, have_len, have_ver, sizeof have_ver);
|
||||
else have_ver[0] = 0;
|
||||
int replace;
|
||||
if (!have) replace = 1;
|
||||
else if (have_len == want_len && !memcmp(have, want, want_len)) replace = 0;
|
||||
else if (!parse_version(want_ver, wv)) replace = 0; /* an untagged test build: never overwrite */
|
||||
else if (!parse_version(have_ver, hv)) replace = 1; /* older than version tags */
|
||||
else replace = compare_versions(wv, hv) > 0;
|
||||
if (!replace) {
|
||||
logf_("payload %s left as it is (installed %s, this app has %s)", PAYLOAD_PATH, have_ver[0] ? have_ver : "?",
|
||||
want_ver[0] ? want_ver : "?");
|
||||
rc = 0;
|
||||
} else {
|
||||
FILE *f;
|
||||
mkdir(PAYLOAD_DIR, 0777);
|
||||
if ((f = fopen(PAYLOAD_PATH ".tmp", "wb"))) {
|
||||
int ok = fwrite(want, 1, want_len, f) == want_len;
|
||||
if (fclose(f) == 0 && ok) rc = rename(PAYLOAD_PATH ".tmp", PAYLOAD_PATH) == 0 ? 1 : -1;
|
||||
}
|
||||
logf_("%s %s with %s (was %s): %s", have ? "replaced" : "installed", PAYLOAD_PATH,
|
||||
want_ver[0] ? want_ver : "?", have ? (have_ver[0] ? have_ver : "untagged") : "missing",
|
||||
rc == 1 ? "ok" : "failed");
|
||||
}
|
||||
free(want);
|
||||
free(have);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* The web_port from RomM Sync's config.json, if it was changed. */
|
||||
static int web_port(void) {
|
||||
size_t len = 0;
|
||||
char *cfg = (char *)slurp(CONFIG_PATH, &len), *p;
|
||||
int port = DEFAULT_PORT;
|
||||
if (cfg) {
|
||||
cfg = realloc(cfg, len + 1);
|
||||
cfg[len] = 0;
|
||||
if ((p = strstr(cfg, "\"web_port\"")) && (p = strchr(p, ':'))) port = atoi(p + 1);
|
||||
free(cfg);
|
||||
}
|
||||
return port > 0 && port < 65536 ? port : DEFAULT_PORT;
|
||||
}
|
||||
|
||||
/* The running payload's version from /api/status, or 0 if nothing answers. */
|
||||
static int payload_version(int port, char *version, size_t n) {
|
||||
struct sockaddr_in sa;
|
||||
char buf[2048], req[128], *v;
|
||||
int fd, got = 0, r;
|
||||
version[0] = 0;
|
||||
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) return 0;
|
||||
memset(&sa, 0, sizeof sa);
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons((uint16_t)port);
|
||||
sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
if (connect(fd, (struct sockaddr *)&sa, sizeof sa) != 0) {
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
snprintf(req, sizeof req, "GET /api/status HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n");
|
||||
write(fd, req, strlen(req));
|
||||
while (got < (int)sizeof buf - 1 && (r = (int)read(fd, buf + got, sizeof buf - 1 - got)) > 0) got += r;
|
||||
close(fd);
|
||||
buf[got] = 0;
|
||||
if ((v = strstr(buf, "\"version\":\"")) || (v = strstr(buf, "\"version\": \""))) {
|
||||
v = strchr(v + 9, '"') + 1;
|
||||
snprintf(version, n, "%.*s", (int)strcspn(v, "\""), v);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void message(const char *text) {
|
||||
OrbisMsgDialogParam param;
|
||||
OrbisMsgDialogUserMessageParam user;
|
||||
memset(¶m, 0, sizeof param);
|
||||
memset(&user, 0, sizeof user);
|
||||
param.baseParam.size = sizeof param.baseParam;
|
||||
param.baseParam.magic = (uint32_t)(ORBIS_COMMON_DIALOG_MAGIC_NUMBER + (uint64_t)(uintptr_t)¶m.baseParam);
|
||||
param.size = sizeof param;
|
||||
param.mode = ORBIS_MSG_DIALOG_MODE_USER_MSG;
|
||||
param.userMsgParam = &user;
|
||||
param.userId = user_id();
|
||||
user.buttonType = ORBIS_MSG_DIALOG_BUTTON_TYPE_OK;
|
||||
user.msg = text;
|
||||
if (sceMsgDialogInitialize() < 0 && sceMsgDialogGetStatus() == ORBIS_COMMON_DIALOG_STATUS_NONE) {
|
||||
logf_("message dialog unavailable: %s", text);
|
||||
return;
|
||||
}
|
||||
if (sceMsgDialogOpen(¶m) < 0) {
|
||||
logf_("message dialog didn't open: %s", text);
|
||||
return;
|
||||
}
|
||||
while (sceMsgDialogUpdateStatus() == ORBIS_COMMON_DIALOG_STATUS_RUNNING) usleep(16 * 1000);
|
||||
sceMsgDialogTerminate();
|
||||
}
|
||||
|
||||
/* The dialog's magic encodes this block's address, so it must not move. */
|
||||
static web_dialog_param g_web __attribute__((aligned(16)));
|
||||
|
||||
static int open_web_dialog(const char *url, int mode) {
|
||||
memset(&g_web, 0, sizeof g_web);
|
||||
g_web.base.size = sizeof g_web.base;
|
||||
g_web.base.magic = (uint32_t)(ORBIS_COMMON_DIALOG_MAGIC_NUMBER + (uint64_t)(uintptr_t)&g_web.base);
|
||||
g_web.size = sizeof g_web;
|
||||
g_web.mode = mode;
|
||||
g_web.user_id = user_id();
|
||||
g_web.url = url;
|
||||
if (mode == 2) {
|
||||
g_web.width = 1920;
|
||||
g_web.height = 1080;
|
||||
g_web.header_width = 1920;
|
||||
}
|
||||
return sceWebBrowserDialogOpen(&g_web);
|
||||
}
|
||||
|
||||
/* Full screen, then the browser's own layout, then the Browser app. Returns
|
||||
* when the dialog is closed, or straight away for the Browser app. */
|
||||
static void show_web_ui(const char *url) {
|
||||
int rc = sceSysmoduleLoadModule(ORBIS_SYSMODULE_WEB_BROWSER_DIALOG);
|
||||
logf_("web browser dialog module: 0x%08x", (unsigned)rc);
|
||||
rc = sceWebBrowserDialogInitialize();
|
||||
logf_("web browser dialog init: 0x%08x", (unsigned)rc);
|
||||
if ((rc = open_web_dialog(url, 2)) != 0) {
|
||||
logf_("full-screen web dialog: 0x%08x, trying the default layout", (unsigned)rc);
|
||||
rc = open_web_dialog(url, 1);
|
||||
}
|
||||
if (rc == 0) {
|
||||
logf_("web dialog open on %s", url);
|
||||
int status;
|
||||
while ((status = sceWebBrowserDialogUpdateStatus()) == ORBIS_COMMON_DIALOG_STATUS_RUNNING ||
|
||||
status == ORBIS_COMMON_DIALOG_STATUS_INITIALIZED)
|
||||
usleep(16 * 1000);
|
||||
logf_("web dialog closed (status %d)", status);
|
||||
sceWebBrowserDialogClose();
|
||||
sceWebBrowserDialogTerminate();
|
||||
return;
|
||||
}
|
||||
logf_("web dialog: 0x%08x, opening the Browser app instead", (unsigned)rc);
|
||||
rc = launch_web_browser(url, NULL);
|
||||
logf_("Browser app: 0x%08x", (unsigned)rc);
|
||||
if (rc != 0) {
|
||||
char text[256];
|
||||
snprintf(text, sizeof text, "Open %s in the web browser to use RomM Sync.", url);
|
||||
message(text);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
char url[64], running[32], text[512];
|
||||
int installed, port;
|
||||
|
||||
sceSystemServiceHideSplashScreen();
|
||||
mkdir(DATA_DIR, 0777);
|
||||
logf_("RomM Sync launcher %s", APP_VERSION);
|
||||
sceUserServiceInitialize(NULL);
|
||||
sceSysmoduleLoadModule(ORBIS_SYSMODULE_MESSAGE_DIALOG);
|
||||
sceCommonDialogInitialize();
|
||||
|
||||
installed = install_payload();
|
||||
port = web_port();
|
||||
snprintf(url, sizeof url, "http://127.0.0.1:%d/", port);
|
||||
int up = payload_version(port, running, sizeof running);
|
||||
logf_("RomM Sync on port %d: %s%s", port, up ? "running " : "not running", running);
|
||||
|
||||
if (installed < 0) {
|
||||
message("RomM Sync couldn't copy its payload to /data/payloads. Copy romm-sync-ps4.elf there yourself over FTP, then run it from GoldHEN Settings > Payloader.");
|
||||
} else if (!up) {
|
||||
snprintf(text, sizeof text,
|
||||
"RomM Sync isn't running.\n\n%sIn GoldHEN Settings > Payloader, run romm-sync-ps4.elf, then add it to the "
|
||||
"AutoRun queue so it starts on every jailbreak. Open this app again afterwards.",
|
||||
installed ? "Its payload is now in /data/payloads. " : "");
|
||||
message(text);
|
||||
} else {
|
||||
if (installed)
|
||||
message("A new version of RomM Sync was copied to /data/payloads. It takes over after the next jailbreak; "
|
||||
"until then the running version stays in use.");
|
||||
show_web_ui(url);
|
||||
}
|
||||
|
||||
logf_("launcher done");
|
||||
sceSystemServiceLoadExec("exit", NULL);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
# Build image for the PS4: the ps4-payload-dev SDK plus its pacbrew static
|
||||
# libraries (curl, OpenSSL, zlib) for the payload, and the OpenOrbis toolchain
|
||||
# for the launcher package (platform/ps4/app). amd64 only, as OpenOrbis's
|
||||
# packaging tools are x86-64 binaries; Docker emulates it on arm64.
|
||||
FROM --platform=linux/amd64 ubuntu:24.04
|
||||
|
||||
ARG SDK_VERSION=v0.9
|
||||
ARG PACBREW_VERSION=v0.3.3
|
||||
ARG OPENORBIS_VERSION=v0.5.4
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
bash ca-certificates wget unzip xz-utils make pkg-config python3 git \
|
||||
clang-18 lld-18 llvm-18 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# SDK
|
||||
RUN wget -q -O /tmp/sdk.zip \
|
||||
https://github.com/ps4-payload-dev/sdk/releases/download/${SDK_VERSION}/ps4-payload-sdk.zip \
|
||||
&& unzip -q /tmp/sdk.zip -d /opt \
|
||||
&& rm /tmp/sdk.zip \
|
||||
&& chmod +x /opt/ps4-payload-sdk/bin/*
|
||||
|
||||
# Libraries land in the SDK's target/user/homebrew, next to the SDK itself.
|
||||
RUN wget -q -O /tmp/pb.tar.gz \
|
||||
https://github.com/ps4-payload-dev/pacbrew/releases/download/${PACBREW_VERSION}/ps4-payload-dev.tar.gz \
|
||||
&& tar -xzf /tmp/pb.tar.gz -C / opt/ps4-payload-sdk/target/user \
|
||||
&& rm /tmp/pb.tar.gz \
|
||||
&& cp /opt/ps4-payload-sdk/target/user/homebrew/etc/ca-bundle.crt /opt/ca-bundle.crt
|
||||
|
||||
# OpenOrbis, for the launcher package. PkgTool.Core is .NET Core 3.0: it wants
|
||||
# ICU unless told not to, and OpenSSL 1.1, which only Ubuntu 20.04 still has.
|
||||
RUN wget -q -O /tmp/libssl.deb \
|
||||
http://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.24_amd64.deb \
|
||||
&& dpkg -i /tmp/libssl.deb \
|
||||
&& rm /tmp/libssl.deb
|
||||
RUN wget -q -O /tmp/oo.tar.gz \
|
||||
https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/releases/download/${OPENORBIS_VERSION}/toolchain-llvm-18.tar.gz \
|
||||
&& mkdir -p /opt/openorbis \
|
||||
&& tar -xzf /tmp/oo.tar.gz -C /opt/openorbis --strip-components=2 \
|
||||
&& rm /tmp/oo.tar.gz \
|
||||
&& chmod +x /opt/openorbis/bin/linux/*
|
||||
ENV OO_PS4_TOOLCHAIN=/opt/openorbis
|
||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||
|
||||
# Every package carries sce_module/libc.prx, sce_module/libSceFios2.prx and
|
||||
# sce_sys/about/right.sprx. OpenOrbis builds them from its source tree rather
|
||||
# than shipping them, so build them here into /opt/openorbis/modules.
|
||||
RUN ln -sf /usr/bin/clang-18 /usr/local/bin/clang \
|
||||
&& ln -sf /usr/bin/clang++-18 /usr/local/bin/clang++ \
|
||||
&& ln -sf /usr/bin/ld.lld-18 /usr/local/bin/ld.lld \
|
||||
&& git clone -q --depth 1 --branch ${OPENORBIS_VERSION} --filter=blob:none --sparse \
|
||||
https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain /tmp/oo-src \
|
||||
&& git -C /tmp/oo-src sparse-checkout set src/modules \
|
||||
&& mkdir -p /opt/openorbis/modules \
|
||||
&& for m in libc libSceFios2 right; do \
|
||||
make -C /tmp/oo-src/src/modules/$m >/dev/null && \
|
||||
cp /tmp/oo-src/src/modules/$m/$m.*prx /opt/openorbis/modules/; \
|
||||
done \
|
||||
&& rm -rf /tmp/oo-src \
|
||||
&& ls -l /opt/openorbis/modules
|
||||
|
||||
ENV PS4_PAYLOAD_SDK=/opt/ps4-payload-sdk
|
||||
ENV PATH=/opt/ps4-payload-sdk/bin:${PATH}
|
||||
ENV LLVM_CONFIG=/usr/bin/llvm-config-18
|
||||
|
||||
# Fail early if static libcurl does not resolve.
|
||||
RUN orbis-pkg-config --static --libs libcurl \
|
||||
&& orbis-pkg-config --cflags libcurl
|
||||
|
||||
WORKDIR /src
|
||||
@@ -0,0 +1,52 @@
|
||||
[
|
||||
{
|
||||
"id": "psnes", "name": "pSNES", "title_ids": ["PSNE00001"], "detect": ["/data/psnes"],
|
||||
"profile": {
|
||||
"root": "/data/psnes", "rom_dir": "{root}/roms", "save_dir": "{root}/sram", "bios_dir": "{root}/bios",
|
||||
"save_exts": ".srm", "state_dir": "{root}/saves", "state_exts": "",
|
||||
"platforms": [{"romm": ["snes", "sfam"], "dir": "snes"}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pnes", "name": "pNES", "title_ids": ["PNES00001"], "detect": ["/data/pnes"],
|
||||
"profile": {
|
||||
"root": "/data/pnes", "rom_dir": "{root}/roms", "save_dir": "{root}/save", "bios_dir": "{root}/bios",
|
||||
"save_exts": ".sav", "state_dir": "{root}/saves", "state_exts": "",
|
||||
"platforms": [{"romm": ["nes", "famicom"], "dir": "nes"}, {"romm": ["fds"], "dir": "fds"}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pgen", "name": "pGEN", "title_ids": ["PGEN00001"], "detect": ["/data/pgen"],
|
||||
"profile": {
|
||||
"root": "/data/pgen", "rom_dir": "{root}/{platform}", "save_dir": "{root}/rams", "bios_dir": "{root}/bios",
|
||||
"save_exts": ".srm", "state_dir": "{root}/saves", "state_exts": "",
|
||||
"platforms": [
|
||||
{"romm": ["genesis", "genesis-slash-megadrive", "megadrive"], "dir": "megadrive"},
|
||||
{"romm": ["sms", "mastersystem"], "dir": "mastersystem"},
|
||||
{"romm": ["gamegear", "gg"], "dir": "gamegear"},
|
||||
{"romm": ["segacd", "sega-cd"], "dir": "megacd", "save_exts": ".brm"},
|
||||
{"romm": ["sg1000"], "dir": "sg1000"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pgba", "name": "pGBA", "title_ids": ["PGBA00001"], "detect": ["/data/pgba"],
|
||||
"profile": {
|
||||
"root": "/data/pgba", "rom_dir": "{root}/{platform}", "save_dir": "{root}/saves", "bios_dir": "{root}/bios",
|
||||
"save_exts": ".sav", "state_dir": "{root}/saves", "state_exts": "",
|
||||
"platforms": [
|
||||
{"romm": ["gba"], "dir": "gba"},
|
||||
{"romm": ["gbc"], "dir": "gbc"},
|
||||
{"romm": ["gb"], "dir": "gb"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pfbn", "name": "pFBN", "title_ids": ["PFBN00001"], "detect": ["/data/pfba"],
|
||||
"profile": {
|
||||
"root": "/data/pfba", "rom_dir": "{root}/arcade", "save_dir": "{root}/eeproms", "bios_dir": "{root}/arcade",
|
||||
"save_exts": ".fs", "state_dir": "{root}/saves", "state_exts": "",
|
||||
"platforms": [{"romm": ["arcade", "fbneo", "neogeoaes", "neogeomvs", "cps1", "cps2", "cps3"], "dir": "arcade"}]
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,165 @@
|
||||
/* platform.h for the PS4 (GoldHEN, ps4-payload-dev SDK). The parts shared with
|
||||
* the PS5 are in platform/console/console.c.
|
||||
*
|
||||
* GoldHEN (2.4b18.5 or newer) runs romm-sync-ps4.elf from its payload menu,
|
||||
* which reads /data/payloads and can AutoRun it on every jailbreak. The payload
|
||||
* runs inside a GoldHEN system process (NPXS21002 on 2.4b18.9), as root, so
|
||||
* main() hands that thread back (plat_info detach) and an older copy is never
|
||||
* killed: it would take the host process with it. A lock file keeps it to one.
|
||||
* The SDK's startup code escapes the sandbox, so there's nothing to raise here.
|
||||
*
|
||||
* The foreground app follows the SDK's samples/ps. Links against SceSystemService. */
|
||||
#include "platform.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "console.h"
|
||||
#include "emulators_json.h" /* generated from platform/ps4/emulators.json */
|
||||
|
||||
#define LOCK_FILE CONSOLE_LOCK_FILE
|
||||
|
||||
|
||||
/* libkernel: layout from sdk/samples/ps/main.c (title_id is wider than the PS5's) */
|
||||
typedef struct app_info {
|
||||
uint32_t app_id;
|
||||
uint64_t unknown1;
|
||||
char title_id[14];
|
||||
char unknown2[0x3c];
|
||||
} app_info_t;
|
||||
int sceKernelGetAppInfo(pid_t pid, app_info_t *info);
|
||||
|
||||
/* libSceSystemService: no GetAppIdOfRunningBigApp or GetAppTitleId on the PS4 */
|
||||
int sceSystemServiceGetAppIdOfBigApp(void);
|
||||
|
||||
|
||||
static const char *const HOMEBREW_DIRS[] = {
|
||||
"/data/homebrew", "/mnt/usb0/homebrew", "/mnt/usb1/homebrew", NULL};
|
||||
/* RetroArch keeps its config, saves and core .info files here; the cores
|
||||
* themselves are in /data/self/retroarch/cores (see retroarch.cfg). */
|
||||
static const char *const EMULATOR_DIRS[] = {"/data/retroarch", NULL};
|
||||
|
||||
static const plat_info_t INFO = {
|
||||
.name = "ps4",
|
||||
.console = "PlayStation 4",
|
||||
.client = "romm-sync-ps4",
|
||||
.loader = "GoldHEN",
|
||||
.payload = "romm-sync-ps4.elf",
|
||||
.manifest = "manifest-ps4.json",
|
||||
.manifest_sig = "manifest-ps4.sig",
|
||||
/* GoldHEN's payload menu lists this folder; the user turns on AutoRun
|
||||
* for the payload there once (the queue is the menu's own). */
|
||||
.autostart_dir = "/data/payloads",
|
||||
.autostart_flag = NULL,
|
||||
.autostart_hint = "Turn on AutoRun for it in GoldHEN Settings, Payloader, AutoRun queue.",
|
||||
.loader_dir = "/data/GoldHEN",
|
||||
.homebrew_dirs = HOMEBREW_DIRS,
|
||||
.emulator_dirs = EMULATOR_DIRS,
|
||||
.retroarch_root = "/data/retroarch",
|
||||
.mednafen_root = "/data/mednafen",
|
||||
.emulator_catalog = EMULATORS_JSON,
|
||||
/* GoldHEN runs the payload on a thread of its own process. */
|
||||
.detach = 1,
|
||||
.self_update = 1,
|
||||
};
|
||||
|
||||
const plat_info_t *plat_info(void) { return &INFO; }
|
||||
|
||||
|
||||
/* Startup steps go to <data>/boot.log before they run, so a payload that
|
||||
* hangs or dies inside the loader still says how far it got. */
|
||||
static void boot_step(const char *fmt, ...) {
|
||||
FILE *f = fopen(CONSOLE_DATA_DIR "/boot.log", "a");
|
||||
va_list ap;
|
||||
if (!f) return;
|
||||
va_start(ap, fmt);
|
||||
vfprintf(f, fmt, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', f);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/* Held for as long as this copy runs. flock is per open file, so a second copy
|
||||
* in the same process (the usual case under GoldHEN) is refused too. */
|
||||
static int g_lock_fd = -1;
|
||||
|
||||
int plat_init(void) {
|
||||
int rc = console_mkdir_p(CONSOLE_DATA_DIR);
|
||||
/* The home screen app runs as a regular user and logs here. */
|
||||
chmod(CONSOLE_DATA_DIR, 0777);
|
||||
unlink(CONSOLE_DATA_DIR "/boot.log");
|
||||
boot_step("start: pid %d uid %d", getpid(), getuid());
|
||||
g_lock_fd = open(LOCK_FILE, O_RDWR | O_CREAT, 0666);
|
||||
if (g_lock_fd >= 0 && flock(g_lock_fd, LOCK_EX | LOCK_NB) != 0 && errno == EWOULDBLOCK) {
|
||||
boot_step("already running");
|
||||
close(g_lock_fd);
|
||||
g_lock_fd = -1;
|
||||
return 1;
|
||||
}
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
boot_step("platform ready");
|
||||
return rc ? -1 : 0;
|
||||
}
|
||||
|
||||
struct appid_ctx { int app_id; char title_id[16]; };
|
||||
|
||||
static int appid_cb(pid_t pid, const char *tdname, void *vctx) {
|
||||
struct appid_ctx *c = vctx;
|
||||
app_info_t info;
|
||||
(void)tdname;
|
||||
memset(&info, 0, sizeof info);
|
||||
if (sceKernelGetAppInfo(pid, &info)) return 0;
|
||||
if ((int)info.app_id != c->app_id) return 0;
|
||||
memcpy(c->title_id, info.title_id, 9);
|
||||
c->title_id[9] = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* The big app's id, then the process that carries it (sdk samples/ps). */
|
||||
int plat_running_title(char *title_id, size_t n) {
|
||||
struct appid_ctx c;
|
||||
|
||||
if (!title_id || n < 10) return -1;
|
||||
title_id[0] = 0;
|
||||
|
||||
memset(&c, 0, sizeof c);
|
||||
c.app_id = sceSystemServiceGetAppIdOfBigApp();
|
||||
if (c.app_id <= 0) return 0; /* < 0: no big app running */
|
||||
if (console_for_each_proc(appid_cb, &c) != 1 || !console_valid_title_id(c.title_id)) return -1;
|
||||
snprintf(title_id, n, "%s", c.title_id);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void plat_describe(char *buf, size_t n) {
|
||||
app_info_t info;
|
||||
char host[16] = "";
|
||||
memset(&info, 0, sizeof info);
|
||||
if (!sceKernelGetAppInfo(getpid(), &info)) snprintf(host, sizeof host, "%.9s", info.title_id);
|
||||
console_describe(buf, n, host);
|
||||
}
|
||||
|
||||
void plat_remove_old_tile(void) {}
|
||||
|
||||
int plat_app_installed(void) { return 0; }
|
||||
|
||||
void plat_app_version(char *out, size_t n) {
|
||||
if (n) out[0] = 0;
|
||||
}
|
||||
|
||||
/* Not used (can_relaunch is 0): GoldHEN's network loader (9090) crashes on
|
||||
* ELFs in 2.4b18.x, so a new version starts with the next jailbreak. */
|
||||
int plat_launch_elf(const void *elf, size_t len) {
|
||||
(void)elf;
|
||||
(void)len;
|
||||
errno = ENOTSUP;
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
# PS4 payload (GoldHEN), built with the ps4-payload-dev SDK. Included by the
|
||||
# top-level Makefile; platform/ps4/docker/Dockerfile is the SDK image.
|
||||
#
|
||||
# GoldHEN 2.4b18.5+ runs ELF payloads from /data/payloads through its payload
|
||||
# menu, which can AutoRun them on every jailbreak. Its network loader (9090)
|
||||
# crashes on ELFs, so "make install-ps4" copies the payload there over FTP and
|
||||
# it's started from the menu.
|
||||
#
|
||||
# "make ps4-pkg" builds the home screen app (platform/ps4/app) with OpenOrbis:
|
||||
# a package that copies the bundled payload to /data/payloads and shows the
|
||||
# web UI. It needs OO_PS4_TOOLCHAIN, which the Docker image sets.
|
||||
|
||||
PS4_HOST ?= ps4
|
||||
PS4_FTP ?= ftp://$(PS4_HOST):2121
|
||||
|
||||
PS4_PAYLOAD_SDK ?= /opt/ps4-payload-sdk
|
||||
PS4_CC := $(PS4_PAYLOAD_SDK)/bin/orbis-clang
|
||||
PS4_PKGCONF := $(PS4_PAYLOAD_SDK)/bin/orbis-pkg-config
|
||||
PS4_ELF := build/ps4/romm-sync-ps4.elf
|
||||
# pacbrew's Mozilla CA bundle, built into the payload so HTTPS works out of the box.
|
||||
PS4_CA := $(PS4_PAYLOAD_SDK)/target/user/homebrew/etc/ca-bundle.crt
|
||||
PS4_PLAT := platform/ps4/platform.c platform/console/console.c
|
||||
|
||||
ps4: $(PS4_ELF)
|
||||
|
||||
build/ps4/gen/cacert_pem.h: $(PS4_CA) tools/embed.py
|
||||
@mkdir -p $(dir $@)
|
||||
python3 tools/embed.py $< $@ CACERT_PEM
|
||||
|
||||
# Standalone emulators setup can offer (src/detect.c).
|
||||
build/ps4/gen/emulators_json.h: platform/ps4/emulators.json tools/embed.py
|
||||
@mkdir -p $(dir $@)
|
||||
python3 -c "import json,sys; json.load(open(sys.argv[1]))" $<
|
||||
python3 tools/embed.py $< $@ EMULATORS_JSON
|
||||
|
||||
$(PS4_ELF): $(SRCS) $(PS4_PLAT) platform/console/console.h $(HDRS) $(UI_HEADER) build/ps4/gen/cacert_pem.h build/ps4/gen/emulators_json.h
|
||||
@mkdir -p $(dir $@)
|
||||
$(PS4_CC) $(COMMON_CFLAGS) -Ibuild/ps4/gen -Iplatform/console -DROMM_EMBED_CA $$($(PS4_PKGCONF) --cflags libcurl) -DCURL_STATICLIB \
|
||||
-o $@ $(SRCS) $(PS4_PLAT) \
|
||||
$$($(PS4_PKGCONF) --static --libs libcurl) -pthread -lSceSystemService
|
||||
|
||||
install-ps4: $(PS4_ELF)
|
||||
curl -sS --ftp-create-dirs -T $(PS4_ELF) $(PS4_FTP)/data/payloads/romm-sync-ps4.elf
|
||||
|
||||
# Home screen app (OpenOrbis)
|
||||
OO ?= $(OO_PS4_TOOLCHAIN)
|
||||
PS4_TITLE_ID := ROMM00002
|
||||
PS4_CONTENT_ID := IV0000-$(PS4_TITLE_ID)_00-ROMMSYNCPS400000
|
||||
PS4_PKG_VER := 1.00
|
||||
PS4_PKG_DIR := build/ps4/pkg
|
||||
PS4_PKG := build/ps4/romm-sync-ps4.pkg
|
||||
PS4_APP_LIBS := -lc -lkernel -lSceSysmodule -lSceCommonDialog -lSceMsgDialog -lSceWebBrowserDialog \
|
||||
-lSceSystemService -lSceUserService
|
||||
PKGTOOL := $(OO)/bin/linux/PkgTool.Core
|
||||
|
||||
.PHONY: ps4-pkg
|
||||
ps4-pkg: $(PS4_PKG)
|
||||
|
||||
build/ps4/launcher.elf: platform/ps4/app/launcher.c
|
||||
@mkdir -p $(dir $@)
|
||||
clang --target=x86_64-pc-freebsd12-elf -fPIC -funwind-tables -O2 -Wall -Wextra -Wno-unused-parameter \
|
||||
-isysroot $(OO) -isystem $(OO)/include -DAPP_VERSION='"$(or $(VERSION),dev)"' -c -o build/ps4/launcher.o $<
|
||||
ld.lld -m elf_x86_64 -pie --script $(OO)/link.x --eh-frame-hdr -L$(OO)/lib $(PS4_APP_LIBS) $(OO)/lib/crt1.o \
|
||||
-o $@ build/ps4/launcher.o
|
||||
|
||||
# The package tree: the app, its metadata, OpenOrbis's modules and the payload.
|
||||
$(PS4_PKG): build/ps4/launcher.elf $(PS4_ELF) assets/icon0.png
|
||||
rm -rf $(PS4_PKG_DIR) && mkdir -p $(PS4_PKG_DIR)/sce_sys/about $(PS4_PKG_DIR)/sce_module
|
||||
cd $(PS4_PKG_DIR) && $(OO)/bin/linux/create-fself -in=$(CURDIR)/build/ps4/launcher.elf \
|
||||
-out=launcher.oelf --eboot eboot.bin --paid 0x3800000000000011 && rm launcher.oelf
|
||||
cp assets/icon0.png $(PS4_PKG_DIR)/sce_sys/icon0.png
|
||||
cp $(OO)/modules/right.sprx $(PS4_PKG_DIR)/sce_sys/about/
|
||||
cp $(OO)/modules/libc.prx $(OO)/modules/libSceFios2.prx $(PS4_PKG_DIR)/sce_module/
|
||||
cp $(PS4_ELF) $(PS4_PKG_DIR)/
|
||||
cd $(PS4_PKG_DIR) && f=sce_sys/param.sfo && $(PKGTOOL) sfo_new $$f \
|
||||
&& $(PKGTOOL) sfo_setentry $$f APP_TYPE --type Integer --maxsize 4 --value 1 \
|
||||
&& $(PKGTOOL) sfo_setentry $$f APP_VER --type Utf8 --maxsize 8 --value '$(PS4_PKG_VER)' \
|
||||
&& $(PKGTOOL) sfo_setentry $$f ATTRIBUTE --type Integer --maxsize 4 --value 0 \
|
||||
&& $(PKGTOOL) sfo_setentry $$f CATEGORY --type Utf8 --maxsize 4 --value 'gd' \
|
||||
&& $(PKGTOOL) sfo_setentry $$f CONTENT_ID --type Utf8 --maxsize 48 --value '$(PS4_CONTENT_ID)' \
|
||||
&& $(PKGTOOL) sfo_setentry $$f DOWNLOAD_DATA_SIZE --type Integer --maxsize 4 --value 0 \
|
||||
&& $(PKGTOOL) sfo_setentry $$f SYSTEM_VER --type Integer --maxsize 4 --value 0 \
|
||||
&& $(PKGTOOL) sfo_setentry $$f TITLE --type Utf8 --maxsize 128 --value 'RomM Sync' \
|
||||
&& $(PKGTOOL) sfo_setentry $$f TITLE_ID --type Utf8 --maxsize 12 --value '$(PS4_TITLE_ID)' \
|
||||
&& $(PKGTOOL) sfo_setentry $$f VERSION --type Utf8 --maxsize 8 --value '$(PS4_PKG_VER)'
|
||||
cd $(PS4_PKG_DIR) && $(OO)/bin/linux/create-gp4 -out pkg.gp4 --content-id=$(PS4_CONTENT_ID) \
|
||||
--files "eboot.bin sce_sys/param.sfo sce_sys/icon0.png sce_sys/about/right.sprx sce_module/libc.prx sce_module/libSceFios2.prx romm-sync-ps4.elf" \
|
||||
&& $(PKGTOOL) pkg_build pkg.gp4 .
|
||||
mv $(PS4_PKG_DIR)/$(PS4_CONTENT_ID).pkg $@
|
||||
@@ -0,0 +1,16 @@
|
||||
# Build image for the native PS5 app (RommPS): the host tools mihawk-99's
|
||||
# PS5_VulkanTemplate stack expects (ps5/tools/bootstrap.sh --check). Arch,
|
||||
# like the stack's own build host; amd64 only, emulated on arm64.
|
||||
FROM --platform=linux/amd64 archlinux:latest
|
||||
|
||||
# pacman's download sandbox needs a seccomp call Docker's amd64 emulation lacks.
|
||||
RUN sed -i 's/^#\?DisableSandbox.*/DisableSandbox/; /^\[options\]/a DisableSandbox' /etc/pacman.conf \
|
||||
&& pacman -Syu --noconfirm --needed \
|
||||
base-devel git cmake clang lld llvm python python-numpy python-pillow python-mako \
|
||||
python-packaging python-yaml python-pyelftools glslang spirv-tools rsync curl zstd ninja meson \
|
||||
bison flex unzip wget which \
|
||||
spirv-llvm-translator libclc spirv-headers libdrm \
|
||||
&& pacman -Scc --noconfirm
|
||||
|
||||
# The stack and the app are mounted here (tools/ps5-app-build.sh).
|
||||
WORKDIR /stack
|
||||
@@ -0,0 +1,9 @@
|
||||
build/
|
||||
dist/
|
||||
klog/
|
||||
.deps/
|
||||
imgui.ini
|
||||
|
||||
# Dashboard backgrounds, made by platform/ps5/app/tools/title-art.sh
|
||||
ps5/sce_sys/pic0.dds
|
||||
ps5/sce_sys/pic1.dds
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Sascha Willems
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,21 @@
|
||||
# RommPS
|
||||
|
||||
The native PS5 app for RomM Sync (`PPSA76677`): your RomM library, downloads, save sync status and settings, on the TV with the controller. It talks to the RomM Sync payload on the console (its local API on port 8780), so the payload has to be running; RommPS shows how to load it when it isn't.
|
||||
|
||||
It's built on mihawk-99's [PS5 Vulkan Template](https://github.com/mihawk-99/PS5_VulkanTemplate) (at `b577e950`): Sascha Willems' Vulkan example base class with its PS5 hooks, RADV for the GPU, and BlackBearReloaded's UI kit drawn with Vulkan through mihawk-99's PS5_VKHomebrewUI. The app itself is `examples/rommps/rommps.cpp` and the screens in `examples/rommps/kit/`.
|
||||
|
||||
## Building and running
|
||||
|
||||
It builds against mihawk-99's PS5 stack, checked out in `../ps5-stack` beside this repository (PS5_Vulkan, the payload SDK fork, PS5_Mesa), inside the Docker image from `platform/ps5/app/docker`. From the repository's root:
|
||||
|
||||
```bash
|
||||
tools/ps5-app-build.sh bash -c 'cd /repo/platform/ps5/app/rommps && bash ps5/tools/build.sh rommps' # dist/PPSA76677/
|
||||
tools/ps5-app-build.sh bash -c 'cd /repo/platform/ps5/app/rommps && bash ps5/tools/run.sh rommps' # deploy and a test run
|
||||
tools/release-app.sh v1.2.3 # the release zip
|
||||
```
|
||||
|
||||
The menu sounds aren't in git. Put them in `platform/ps5/app/assets/sounds/` (`focus_01.wav`, `back_01.wav` and so on, named after the kit's cues) before building; without them the kit's own sounds are used.
|
||||
|
||||
## Licences
|
||||
|
||||
The UI kit is GPL-3.0-or-later, and so is RommPS as distributed: its screens, the UI module (`ps5/ui/`) and the kit's sounds (`ps5/licenses/GPL-3.0.txt`). The kit's fonts are OFL-1.1 and Bitstream Vera. The rest of the template is MIT (`LICENSE.md`: Sascha Willems', and mihawk-99's PS5 layer). The menu sounds are Konami's, used with permission (`ps5/SOUNDS-LICENCE.txt`). Every asset and its licence is listed in `ps5/ASSETS.md`, and the built app carries all of it in `LEGAL.txt` and `licenses/`.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Copyright (c) 2016-2026, Sascha Willems
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
file(GLOB BASE_SRC "*.cpp" "*.hpp" "*.h" "../external/imgui/*.cpp")
|
||||
file(GLOB BASE_HEADERS "*.hpp" "*.h")
|
||||
|
||||
set(KTX_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../external/ktx)
|
||||
set(KTX_SOURCES
|
||||
${KTX_DIR}/lib/texture.c
|
||||
${KTX_DIR}/lib/hashlist.c
|
||||
${KTX_DIR}/lib/checkheader.c
|
||||
${KTX_DIR}/lib/swap.c
|
||||
${KTX_DIR}/lib/memstream.c
|
||||
${KTX_DIR}/lib/filestream.c
|
||||
${KTX_DIR}/lib/vkloader.c)
|
||||
|
||||
# Add shaders
|
||||
set(SHADER_DIR_GLSL "../shaders/glsl/base")
|
||||
file(GLOB SHADERS_GLSL "${SHADER_DIR_GLSL}/*.vert" "${SHADER_DIR_GLSL}/*.frag" "${SHADER_DIR_GLSL}/*.comp" "${SHADER_DIR_GLSL}/*.geom" "${SHADER_DIR_GLSL}/*.tesc" "${SHADER_DIR_GLSL}/*.tese" "${SHADER_DIR_GLSL}/*.mesh" "${SHADER_DIR_GLSL}/*.task" "${SHADER_DIR_GLSL}/*.rgen" "${SHADER_DIR_GLSL}/*.rchit" "${SHADER_DIR_GLSL}/*.rmiss" "${SHADER_DIR_GLSL}/*.rcall" "${SHADER_DIR_GLSL}/*.rahit" "${SHADER_DIR_GLSL}/*.rint" "${SHADER_DIR_GLSL}/*.glsl")
|
||||
set(SHADER_DIR_HLSL "../shaders/hlsl/base")
|
||||
file(GLOB SHADERS_HLSL "${SHADER_DIR_HLSL}/*.vert" "${SHADER_DIR_HLSL}/*.frag" "${SHADER_DIR_HLSL}/*.comp" "${SHADER_DIR_HLSL}/*.geom" "${SHADER_DIR_HLSL}/*.tesc" "${SHADER_DIR_HLSL}/*.tese" "${SHADER_DIR_HLSL}/*.mesh" "${SHADER_DIR_HLSL}/*.task" "${SHADER_DIR_HLSL}/*.rgen" "${SHADER_DIR_HLSL}/*.rchit" "${SHADER_DIR_HLSL}/*.rmiss" "${SHADER_DIR_HLSL}/*.rcall" "${SHADER_DIR_HLSL}/*.rahit" "${SHADER_DIR_HLSL}/*.rint")
|
||||
set(SHADER_DIR_SLANG "../shaders/slang/base")
|
||||
file(GLOB SHADERS_SLANG "${SHADER_DIR_SLANG}/*.slang")
|
||||
source_group("Shaders\\GLSL" FILES ${SHADERS_GLSL})
|
||||
source_group("Shaders\\HLSL" FILES ${SHADERS_HLSL})
|
||||
source_group("Shaders\\SLANG" FILES ${SHADERS_SLANG})
|
||||
message(STATUS ${SHADERS_GLSL})
|
||||
|
||||
add_library(base STATIC ${BASE_SRC} ${KTX_SOURCES} ${SHADERS_GLSL} ${SHADERS_HLSL} ${SHADERS_SLANG})
|
||||
if(WIN32)
|
||||
target_link_libraries(base ${Vulkan_LIBRARY} ${WINLIBS})
|
||||
else(WIN32)
|
||||
target_link_libraries(base ${Vulkan_LIBRARY} ${XCB_LIBRARIES} ${WAYLAND_CLIENT_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif(WIN32)
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Simple command line parse
|
||||
*
|
||||
* Copyright (C) 2016-2022 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
class CommandLineParser
|
||||
{
|
||||
public:
|
||||
struct CommandLineOption {
|
||||
std::vector<std::string> commands;
|
||||
std::string value;
|
||||
bool hasValue = false;
|
||||
std::string help;
|
||||
bool set = false;
|
||||
};
|
||||
std::unordered_map<std::string, CommandLineOption> options;
|
||||
|
||||
void add(std::string name, std::vector<std::string> commands, bool hasValue, std::string help)
|
||||
{
|
||||
options[name].commands = commands;
|
||||
options[name].help = help;
|
||||
options[name].set = false;
|
||||
options[name].hasValue = hasValue;
|
||||
options[name].value = "";
|
||||
}
|
||||
|
||||
void printHelp()
|
||||
{
|
||||
std::cout << "Available command line options:\n";
|
||||
for (auto option : options) {
|
||||
std::cout << " ";
|
||||
for (size_t i = 0; i < option.second.commands.size(); i++) {
|
||||
std::cout << option.second.commands[i];
|
||||
if (i < option.second.commands.size() - 1) {
|
||||
std::cout << ", ";
|
||||
}
|
||||
}
|
||||
std::cout << ": " << option.second.help << "\n";
|
||||
}
|
||||
std::cout << "Press any key to close...";
|
||||
}
|
||||
|
||||
void parse(std::vector<const char*> arguments)
|
||||
{
|
||||
bool printHelp = false;
|
||||
// Known arguments
|
||||
for (auto& option : options) {
|
||||
for (auto& command : option.second.commands) {
|
||||
for (size_t i = 0; i < arguments.size(); i++) {
|
||||
if (strcmp(arguments[i], command.c_str()) == 0) {
|
||||
option.second.set = true;
|
||||
// Get value
|
||||
if (option.second.hasValue) {
|
||||
if (arguments.size() > i + 1) {
|
||||
option.second.value = arguments[i + 1];
|
||||
}
|
||||
if (option.second.value == "") {
|
||||
printHelp = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Print help for unknown arguments or missing argument values
|
||||
if (printHelp) {
|
||||
options["help"].set = true;
|
||||
}
|
||||
}
|
||||
|
||||
void parse(int argc, char* argv[])
|
||||
{
|
||||
std::vector<const char*> args;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
args.push_back(argv[i]);
|
||||
};
|
||||
parse(args);
|
||||
}
|
||||
|
||||
bool isSet(std::string name)
|
||||
{
|
||||
return ((options.find(name) != options.end()) && options[name].set);
|
||||
}
|
||||
|
||||
std::string getValueAsString(std::string name, std::string defaultValue)
|
||||
{
|
||||
assert(options.find(name) != options.end());
|
||||
std::string value = options[name].value;
|
||||
return (value != "") ? value : defaultValue;
|
||||
}
|
||||
|
||||
int32_t getValueAsInt(std::string name, int32_t defaultValue)
|
||||
{
|
||||
assert(options.find(name) != options.end());
|
||||
std::string value = options[name].value;
|
||||
if (value != "") {
|
||||
char* numConvPtr;
|
||||
int32_t intVal = (int32_t)strtol(value.c_str(), &numConvPtr, 10);
|
||||
return (intVal > 0) ? intVal : defaultValue;
|
||||
}
|
||||
else {
|
||||
return defaultValue;
|
||||
}
|
||||
return int32_t();
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Vulkan entry points
|
||||
*
|
||||
* Platform specific macros for the example main entry points
|
||||
*
|
||||
* Copyright (C) 2024-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#if defined(VK_EXAMPLE_PS5)
|
||||
/*
|
||||
* PlayStation 5 (ps5/README.md)
|
||||
*
|
||||
* Every sample is linked into one title, each in a namespace of its own
|
||||
* (ps5/src/wrap.cpp.in), and the title's launcher creates them by name: a
|
||||
* sample has no main of its own.
|
||||
*/
|
||||
#define VULKAN_EXAMPLE_MAIN() \
|
||||
VulkanExample *vulkanExample; \
|
||||
VulkanExampleBase *createExample() \
|
||||
{ \
|
||||
vulkanExample = new VulkanExample(); \
|
||||
return vulkanExample; \
|
||||
}
|
||||
|
||||
#elif defined(_WIN32)
|
||||
/*
|
||||
* Windows
|
||||
*/
|
||||
#define VULKAN_EXAMPLE_MAIN() \
|
||||
VulkanExample *vulkanExample; \
|
||||
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) \
|
||||
{ \
|
||||
if (vulkanExample != NULL) \
|
||||
{ \
|
||||
vulkanExample->handleMessages(hWnd, uMsg, wParam, lParam); \
|
||||
} \
|
||||
return (DefWindowProc(hWnd, uMsg, wParam, lParam)); \
|
||||
} \
|
||||
int APIENTRY WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR, _In_ int) \
|
||||
{ \
|
||||
for (int32_t i = 0; i < __argc; i++) { VulkanExample::args.push_back(__argv[i]); }; \
|
||||
vulkanExample = new VulkanExample(); \
|
||||
vulkanExample->initVulkan(); \
|
||||
vulkanExample->setupWindow(hInstance, WndProc); \
|
||||
vulkanExample->prepare(); \
|
||||
vulkanExample->renderLoop(); \
|
||||
delete(vulkanExample); \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||
/*
|
||||
* Android
|
||||
*/
|
||||
#define VULKAN_EXAMPLE_MAIN() \
|
||||
VulkanExample *vulkanExample; \
|
||||
void android_main(android_app* state) \
|
||||
{ \
|
||||
androidApp = state; \
|
||||
vks::android::getDeviceConfig(); \
|
||||
vulkanExample = new VulkanExample(); \
|
||||
state->userData = vulkanExample; \
|
||||
state->onAppCmd = VulkanExample::handleAppCommand; \
|
||||
state->onInputEvent = VulkanExample::handleAppInput; \
|
||||
vulkanExample->renderLoop(); \
|
||||
delete(vulkanExample); \
|
||||
}
|
||||
|
||||
#elif defined(VK_USE_PLATFORM_OHOS)
|
||||
/**
|
||||
* OHOS
|
||||
*/
|
||||
#define VULKAN_EXAMPLE_MAIN() \
|
||||
VulkanExample vulkanExample; \
|
||||
\
|
||||
void OnSurfaceCreatedCB(OH_NativeXComponent *component, void *window) { \
|
||||
LOGD("OnSurfaceCreatedCB"); \
|
||||
uint64_t width, height; \
|
||||
int32_t ret = OH_NativeXComponent_GetXComponentSize(component, window, &width, &height); \
|
||||
\
|
||||
if (!vulkanExample.initVulkan()) { \
|
||||
LOGE("PluginRender::OnSurfaceCreated vulkanExample initVulkan FALSE"); \
|
||||
return; \
|
||||
} \
|
||||
nativeWindow = (static_cast<OHNativeWindow *>(window)); \
|
||||
vulkanExample.prepare(); \
|
||||
uv_async_send(&msgSignal); \
|
||||
} \
|
||||
\
|
||||
void OnSurfaceChangedCB(OH_NativeXComponent *component, void *window) { \
|
||||
LOGD("OnSurfaceChangedCB"); \
|
||||
} \
|
||||
\
|
||||
void OnSurfaceDestroyedCB(OH_NativeXComponent *component, void *window) { \
|
||||
LOGD("OnSurfaceDestroyedCB"); \
|
||||
} \
|
||||
\
|
||||
void DispatchTouchEventCB(OH_NativeXComponent *component, void *window) { \
|
||||
VulkanExample::handleAppInput(component, window, &vulkanExample); \
|
||||
} \
|
||||
\
|
||||
extern "C" napi_value Init(napi_env env, napi_value exports) { \
|
||||
napi_status status; \
|
||||
napi_value exportInstance = nullptr; \
|
||||
OH_NativeXComponent *nativeXComponent = nullptr; \
|
||||
int32_t ret; \
|
||||
char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {}; \
|
||||
uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; \
|
||||
status = napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance); \
|
||||
if (status != napi_ok) { \
|
||||
LOGE("Export: napi_get_named_property fail"); \
|
||||
} \
|
||||
status = napi_unwrap(env, exportInstance, reinterpret_cast<void **>(&nativeXComponent)); \
|
||||
if (status != napi_ok) { \
|
||||
LOGE("Export: napi_unwrap failed status[%{public}d]", status); \
|
||||
} \
|
||||
napi_value global; \
|
||||
status = napi_get_global(env, &global); \
|
||||
status = napi_get_named_property(env, global, "globalThis", &global); \
|
||||
napi_value densityDPI; \
|
||||
status = napi_get_named_property(env, global, "densityDPI", &densityDPI); \
|
||||
double density = 0.0f; \
|
||||
napi_get_value_double(env, densityDPI, &density); \
|
||||
vks::OHOS::setDeviceConfig(density); \
|
||||
napi_value context; \
|
||||
status = napi_get_named_property(env, global, "context", &context); \
|
||||
napi_value resourceManager = NULL; \
|
||||
status = napi_get_named_property(env, context, "resourceManager", &resourceManager); \
|
||||
NativeResourceManager *nativeResourceManager = OH_ResourceManager_InitNativeResourceManager(env, resourceManager); \
|
||||
ResourceManager::getInstance().initialize(nativeResourceManager); \
|
||||
status = napi_get_uv_event_loop(env, &loop); \
|
||||
msgSignal.data = &vulkanExample; \
|
||||
\
|
||||
int uv_status = uv_async_init(loop, &msgSignal, VulkanExampleBase::onMessageCallbackStatic); \
|
||||
static OH_NativeXComponent_Callback callback; \
|
||||
callback.OnSurfaceCreated = OnSurfaceCreatedCB; \
|
||||
callback.OnSurfaceChanged = OnSurfaceChangedCB; \
|
||||
callback.OnSurfaceDestroyed = OnSurfaceDestroyedCB; \
|
||||
callback.DispatchTouchEvent = DispatchTouchEventCB; \
|
||||
\
|
||||
OH_NativeXComponent_RegisterCallback(nativeXComponent, &callback); \
|
||||
return exports; \
|
||||
} \
|
||||
\
|
||||
static napi_module demoModule = { \
|
||||
.nm_version = 1, \
|
||||
.nm_flags = 0, \
|
||||
.nm_filename = nullptr, \
|
||||
.nm_register_func = Init, \
|
||||
.nm_modname = "nativerender_triangle", \
|
||||
.nm_priv = ((void *)0), \
|
||||
.reserved = {0}, \
|
||||
}; \
|
||||
\
|
||||
extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&demoModule); }
|
||||
|
||||
#elif defined(_DIRECT2DISPLAY)
|
||||
/*
|
||||
* Direct-to-display
|
||||
*/
|
||||
#define VULKAN_EXAMPLE_MAIN() \
|
||||
VulkanExample *vulkanExample; \
|
||||
static void handleEvent() \
|
||||
{ \
|
||||
} \
|
||||
int main(const int argc, const char *argv[]) \
|
||||
{ \
|
||||
for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); }; \
|
||||
vulkanExample = new VulkanExample(); \
|
||||
vulkanExample->initVulkan(); \
|
||||
vulkanExample->prepare(); \
|
||||
vulkanExample->renderLoop(); \
|
||||
delete(vulkanExample); \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
|
||||
/*
|
||||
* Direct FB
|
||||
*/
|
||||
#define VULKAN_EXAMPLE_MAIN() \
|
||||
VulkanExample *vulkanExample; \
|
||||
static void handleEvent(const DFBWindowEvent *event) \
|
||||
{ \
|
||||
if (vulkanExample != NULL) \
|
||||
{ \
|
||||
vulkanExample->handleEvent(event); \
|
||||
} \
|
||||
} \
|
||||
int main(const int argc, const char *argv[]) \
|
||||
{ \
|
||||
for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); }; \
|
||||
vulkanExample = new VulkanExample(); \
|
||||
vulkanExample->initVulkan(); \
|
||||
vulkanExample->setupWindow(); \
|
||||
vulkanExample->prepare(); \
|
||||
vulkanExample->renderLoop(); \
|
||||
delete(vulkanExample); \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
#elif (defined(VK_USE_PLATFORM_WAYLAND_KHR) || defined(VK_USE_PLATFORM_HEADLESS_EXT))
|
||||
/*
|
||||
* Wayland / headless
|
||||
*/
|
||||
#define VULKAN_EXAMPLE_MAIN() \
|
||||
VulkanExample *vulkanExample; \
|
||||
int main(const int argc, const char *argv[]) \
|
||||
{ \
|
||||
for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); }; \
|
||||
vulkanExample = new VulkanExample(); \
|
||||
vulkanExample->initVulkan(); \
|
||||
vulkanExample->setupWindow(); \
|
||||
vulkanExample->prepare(); \
|
||||
vulkanExample->renderLoop(); \
|
||||
delete(vulkanExample); \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
||||
/*
|
||||
* X11 Xcb
|
||||
*/
|
||||
#define VULKAN_EXAMPLE_MAIN() \
|
||||
VulkanExample *vulkanExample; \
|
||||
static void handleEvent(const xcb_generic_event_t *event) \
|
||||
{ \
|
||||
if (vulkanExample != NULL) \
|
||||
{ \
|
||||
vulkanExample->handleEvent(event); \
|
||||
} \
|
||||
} \
|
||||
int main(const int argc, const char *argv[]) \
|
||||
{ \
|
||||
for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); }; \
|
||||
vulkanExample = new VulkanExample(); \
|
||||
vulkanExample->initVulkan(); \
|
||||
vulkanExample->setupWindow(); \
|
||||
vulkanExample->prepare(); \
|
||||
vulkanExample->renderLoop(); \
|
||||
delete(vulkanExample); \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT))
|
||||
/*
|
||||
* iOS and macOS (using MoltenVK)
|
||||
*/
|
||||
#if defined(VK_EXAMPLE_XCODE_GENERATED)
|
||||
#define VULKAN_EXAMPLE_MAIN() \
|
||||
VulkanExample *vulkanExample; \
|
||||
int main(const int argc, const char *argv[]) \
|
||||
{ \
|
||||
@autoreleasepool \
|
||||
{ \
|
||||
for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); }; \
|
||||
vulkanExample = new VulkanExample(); \
|
||||
vulkanExample->initVulkan(); \
|
||||
vulkanExample->setupWindow(nullptr); \
|
||||
vulkanExample->prepare(); \
|
||||
vulkanExample->renderLoop(); \
|
||||
delete(vulkanExample); \
|
||||
} \
|
||||
return 0; \
|
||||
}
|
||||
#else
|
||||
#define VULKAN_EXAMPLE_MAIN()
|
||||
#endif
|
||||
|
||||
#elif defined(VK_USE_PLATFORM_SCREEN_QNX)
|
||||
/*
|
||||
* QNX Screen
|
||||
*/
|
||||
#define VULKAN_EXAMPLE_MAIN() \
|
||||
VulkanExample *vulkanExample; \
|
||||
int main(const int argc, const char *argv[]) \
|
||||
{ \
|
||||
for (int i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); }; \
|
||||
vulkanExample = new VulkanExample(); \
|
||||
vulkanExample->initVulkan(); \
|
||||
vulkanExample->setupWindow(); \
|
||||
vulkanExample->prepare(); \
|
||||
vulkanExample->renderLoop(); \
|
||||
delete(vulkanExample); \
|
||||
return 0; \
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Vulkan buffer class
|
||||
*
|
||||
* Encapsulates a Vulkan buffer
|
||||
*
|
||||
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#include "VulkanBuffer.h"
|
||||
|
||||
namespace vks
|
||||
{
|
||||
/**
|
||||
* Map a memory range of this buffer. If successful, mapped points to the specified buffer range.
|
||||
*
|
||||
* @param size (Optional) Size of the memory range to map. Pass VK_WHOLE_SIZE to map the complete buffer range.
|
||||
* @param offset (Optional) Byte offset from beginning
|
||||
*
|
||||
* @return VkResult of the buffer mapping call
|
||||
*/
|
||||
VkResult Buffer::map(VkDeviceSize size, VkDeviceSize offset)
|
||||
{
|
||||
return vkMapMemory(device, memory, offset, size, 0, &mapped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmap a mapped memory range
|
||||
*
|
||||
* @note Does not return a result as vkUnmapMemory can't fail
|
||||
*/
|
||||
void Buffer::unmap()
|
||||
{
|
||||
if (mapped)
|
||||
{
|
||||
vkUnmapMemory(device, memory);
|
||||
mapped = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the allocated memory block to the buffer
|
||||
*
|
||||
* @param offset (Optional) Byte offset (from the beginning) for the memory region to bind
|
||||
*
|
||||
* @return VkResult of the bindBufferMemory call
|
||||
*/
|
||||
VkResult Buffer::bind(VkDeviceSize offset)
|
||||
{
|
||||
return vkBindBufferMemory(device, buffer, memory, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the default descriptor for this buffer
|
||||
*
|
||||
* @param size (Optional) Size of the memory range of the descriptor
|
||||
* @param offset (Optional) Byte offset from beginning
|
||||
*
|
||||
*/
|
||||
void Buffer::setupDescriptor(VkDeviceSize size, VkDeviceSize offset)
|
||||
{
|
||||
descriptor.offset = offset;
|
||||
descriptor.buffer = buffer;
|
||||
descriptor.range = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the specified data to the mapped buffer
|
||||
*
|
||||
* @param data Pointer to the data to copy
|
||||
* @param size Size of the data to copy in machine units
|
||||
*
|
||||
*/
|
||||
void Buffer::copyTo(void* data, VkDeviceSize size)
|
||||
{
|
||||
assert(mapped);
|
||||
memcpy(mapped, data, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush a memory range of the buffer to make it visible to the device
|
||||
*
|
||||
* @note Only required for non-coherent memory
|
||||
*
|
||||
* @param size (Optional) Size of the memory range to flush. Pass VK_WHOLE_SIZE to flush the complete buffer range.
|
||||
* @param offset (Optional) Byte offset from beginning
|
||||
*
|
||||
* @return VkResult of the flush call
|
||||
*/
|
||||
VkResult Buffer::flush(VkDeviceSize size, VkDeviceSize offset)
|
||||
{
|
||||
VkMappedMemoryRange mappedRange{
|
||||
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
|
||||
.memory = memory,
|
||||
.offset = offset,
|
||||
.size = size
|
||||
};
|
||||
return vkFlushMappedMemoryRanges(device, 1, &mappedRange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate a memory range of the buffer to make it visible to the host
|
||||
*
|
||||
* @note Only required for non-coherent memory
|
||||
*
|
||||
* @param size (Optional) Size of the memory range to invalidate. Pass VK_WHOLE_SIZE to invalidate the complete buffer range.
|
||||
* @param offset (Optional) Byte offset from beginning
|
||||
*
|
||||
* @return VkResult of the invalidate call
|
||||
*/
|
||||
VkResult Buffer::invalidate(VkDeviceSize size, VkDeviceSize offset)
|
||||
{
|
||||
VkMappedMemoryRange mappedRange{
|
||||
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
|
||||
.memory = memory,
|
||||
.offset = offset,
|
||||
.size = size
|
||||
};
|
||||
return vkInvalidateMappedMemoryRanges(device, 1, &mappedRange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release all Vulkan resources held by this buffer
|
||||
*/
|
||||
void Buffer::destroy()
|
||||
{
|
||||
if (buffer)
|
||||
{
|
||||
vkDestroyBuffer(device, buffer, nullptr);
|
||||
buffer = VK_NULL_HANDLE;
|
||||
}
|
||||
if (memory)
|
||||
{
|
||||
vkFreeMemory(device, memory, nullptr);
|
||||
memory = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Vulkan buffer class
|
||||
*
|
||||
* Encapsulates a Vulkan buffer
|
||||
*
|
||||
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "vulkan/vulkan.h"
|
||||
#include "VulkanTools.h"
|
||||
|
||||
namespace vks
|
||||
{
|
||||
/**
|
||||
* @brief Encapsulates access to a Vulkan buffer backed up by device memory
|
||||
* @note To be filled by an external source like the VulkanDevice
|
||||
*/
|
||||
struct Buffer
|
||||
{
|
||||
VkDevice device;
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
VkDescriptorBufferInfo descriptor;
|
||||
VkDeviceSize size = 0;
|
||||
VkDeviceSize alignment = 0;
|
||||
void* mapped = nullptr;
|
||||
/** @brief Usage flags to be filled by external source at buffer creation (to query at some later point) */
|
||||
VkBufferUsageFlags usageFlags;
|
||||
/** @brief Memory property flags to be filled by external source at buffer creation (to query at some later point) */
|
||||
VkMemoryPropertyFlags memoryPropertyFlags;
|
||||
uint64_t deviceAddress;
|
||||
VkResult map(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
|
||||
void unmap();
|
||||
VkResult bind(VkDeviceSize offset = 0);
|
||||
void setupDescriptor(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
|
||||
void copyTo(void* data, VkDeviceSize size);
|
||||
VkResult flush(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
|
||||
VkResult invalidate(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
|
||||
void destroy();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Vulkan examples debug wrapper
|
||||
*
|
||||
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#include "VulkanDebug.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace vks
|
||||
{
|
||||
namespace debug
|
||||
{
|
||||
bool logToFile{ false };
|
||||
std::string logFileName{ "validation_output.txt" };
|
||||
|
||||
PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT;
|
||||
PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT;
|
||||
VkDebugUtilsMessengerEXT debugUtilsMessenger;
|
||||
|
||||
VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsMessageCallback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT messageType,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||
void* pUserData)
|
||||
{
|
||||
// Select prefix depending on flags passed to the callback
|
||||
std::string prefix;
|
||||
|
||||
if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) {
|
||||
prefix = "VERBOSE: ";
|
||||
#if defined(_WIN32)
|
||||
if (!logToFile) {
|
||||
prefix = "\033[32m" + prefix + "\033[0m";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) {
|
||||
prefix = "INFO: ";
|
||||
#if defined(_WIN32)
|
||||
if (!logToFile) {
|
||||
prefix = "\033[36m" + prefix + "\033[0m";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
|
||||
prefix = "WARNING: ";
|
||||
#if defined(_WIN32)
|
||||
if (!logToFile) {
|
||||
prefix = "\033[33m" + prefix + "\033[0m";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
|
||||
prefix = "ERROR: ";
|
||||
#if defined(_WIN32)
|
||||
if (!logToFile) {
|
||||
prefix = "\033[31m" + prefix + "\033[0m";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// Display message to default output (console/logcat)
|
||||
std::stringstream debugMessage;
|
||||
if (pCallbackData->pMessageIdName) {
|
||||
debugMessage << prefix << "[" << pCallbackData->messageIdNumber << "][" << pCallbackData->pMessageIdName << "] : " << pCallbackData->pMessage;
|
||||
}
|
||||
else {
|
||||
debugMessage << prefix << "[" << pCallbackData->messageIdNumber << "] : " << pCallbackData->pMessage;
|
||||
}
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
|
||||
LOGE("%s", debugMessage.str().c_str());
|
||||
} else {
|
||||
LOGD("%s", debugMessage.str().c_str());
|
||||
}
|
||||
#elif defined(__OHOS__)
|
||||
if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
|
||||
LOGE("%{public}s", debugMessage.str().c_str());
|
||||
} else {
|
||||
LOGD("%{public}s", debugMessage.str().c_str());
|
||||
}
|
||||
#else
|
||||
if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
|
||||
std::cerr << debugMessage.str() << "\n\n";
|
||||
} else {
|
||||
std::cout << debugMessage.str() << "\n\n";
|
||||
}
|
||||
if (logToFile) {
|
||||
log(debugMessage.str());
|
||||
}
|
||||
fflush(stdout);
|
||||
#endif
|
||||
|
||||
|
||||
// The return value of this callback controls whether the Vulkan call that caused the validation message will be aborted or not
|
||||
// We return VK_FALSE as we DON'T want Vulkan calls that cause a validation message to abort
|
||||
// If you instead want to have calls abort, pass in VK_TRUE and the function will return VK_ERROR_VALIDATION_FAILED_EXT
|
||||
return VK_FALSE;
|
||||
}
|
||||
|
||||
void setupDebugingMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& debugUtilsMessengerCI)
|
||||
{
|
||||
debugUtilsMessengerCI.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
|
||||
debugUtilsMessengerCI.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
|
||||
debugUtilsMessengerCI.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
|
||||
debugUtilsMessengerCI.pfnUserCallback = debugUtilsMessageCallback;
|
||||
}
|
||||
|
||||
void log(std::string message)
|
||||
{
|
||||
if (logToFile) {
|
||||
time_t timestamp;
|
||||
time(×tamp);
|
||||
std::ofstream logfile;
|
||||
logfile.open(logFileName, std::ios_base::app);
|
||||
logfile << strtok(ctime(×tamp), "\n") << ": " << message << std::endl;
|
||||
logfile.close();
|
||||
}
|
||||
}
|
||||
|
||||
void setupDebugging(VkInstance instance)
|
||||
{
|
||||
vkCreateDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"));
|
||||
vkDestroyDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkDestroyDebugUtilsMessengerEXT>(vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"));
|
||||
|
||||
VkDebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCI{};
|
||||
setupDebugingMessengerCreateInfo(debugUtilsMessengerCI);
|
||||
VkResult result = vkCreateDebugUtilsMessengerEXT(instance, &debugUtilsMessengerCI, nullptr, &debugUtilsMessenger);
|
||||
assert(result == VK_SUCCESS);
|
||||
}
|
||||
|
||||
void freeDebugCallback(VkInstance instance)
|
||||
{
|
||||
if (debugUtilsMessenger != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyDebugUtilsMessengerEXT(instance, debugUtilsMessenger, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace debugutils
|
||||
{
|
||||
PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT{ nullptr };
|
||||
PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{ nullptr };
|
||||
PFN_vkCmdInsertDebugUtilsLabelEXT vkCmdInsertDebugUtilsLabelEXT{ nullptr };
|
||||
|
||||
void setup(VkInstance instance)
|
||||
{
|
||||
vkCmdBeginDebugUtilsLabelEXT = reinterpret_cast<PFN_vkCmdBeginDebugUtilsLabelEXT>(vkGetInstanceProcAddr(instance, "vkCmdBeginDebugUtilsLabelEXT"));
|
||||
vkCmdEndDebugUtilsLabelEXT = reinterpret_cast<PFN_vkCmdEndDebugUtilsLabelEXT>(vkGetInstanceProcAddr(instance, "vkCmdEndDebugUtilsLabelEXT"));
|
||||
vkCmdInsertDebugUtilsLabelEXT = reinterpret_cast<PFN_vkCmdInsertDebugUtilsLabelEXT>(vkGetInstanceProcAddr(instance, "vkCmdInsertDebugUtilsLabelEXT"));
|
||||
}
|
||||
|
||||
void cmdBeginLabel(VkCommandBuffer cmdbuffer, std::string caption, glm::vec4 color)
|
||||
{
|
||||
if (!vkCmdBeginDebugUtilsLabelEXT) {
|
||||
return;
|
||||
}
|
||||
VkDebugUtilsLabelEXT labelInfo{};
|
||||
labelInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
|
||||
labelInfo.pLabelName = caption.c_str();
|
||||
memcpy(labelInfo.color, &color[0], sizeof(float) * 4);
|
||||
vkCmdBeginDebugUtilsLabelEXT(cmdbuffer, &labelInfo);
|
||||
}
|
||||
|
||||
void cmdEndLabel(VkCommandBuffer cmdbuffer)
|
||||
{
|
||||
if (!vkCmdEndDebugUtilsLabelEXT) {
|
||||
return;
|
||||
}
|
||||
vkCmdEndDebugUtilsLabelEXT(cmdbuffer);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Vulkan examples debug wrapper
|
||||
*
|
||||
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "vulkan/vulkan.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
#include "VulkanAndroid.h"
|
||||
#endif
|
||||
#ifdef __OHOS__
|
||||
#include "VulkanOHOS.h"
|
||||
#endif
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace vks
|
||||
{
|
||||
namespace debug
|
||||
{
|
||||
extern bool logToFile;
|
||||
extern std::string logFileName;
|
||||
|
||||
// Default debug callback
|
||||
VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsMessageCallback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT messageType,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||
void* pUserData);
|
||||
|
||||
// Load debug function pointers and set debug callback
|
||||
void setupDebugging(VkInstance instance);
|
||||
// Clear debug callback
|
||||
void freeDebugCallback(VkInstance instance);
|
||||
// Used to populate a VkDebugUtilsMessengerCreateInfoEXT with our example messenger function and desired flags
|
||||
void setupDebugingMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& debugUtilsMessengerCI);
|
||||
void log(std::string message);
|
||||
}
|
||||
|
||||
// Wrapper for the VK_EXT_debug_utils extension
|
||||
// These can be used to name Vulkan objects for debugging tools like RenderDoc
|
||||
namespace debugutils
|
||||
{
|
||||
void setup(VkInstance instance);
|
||||
void cmdBeginLabel(VkCommandBuffer cmdbuffer, std::string caption, glm::vec4 color);
|
||||
void cmdEndLabel(VkCommandBuffer cmdbuffer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,597 @@
|
||||
/*
|
||||
* Vulkan device class
|
||||
*
|
||||
* Encapsulates a physical Vulkan device and its logical representation
|
||||
*
|
||||
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT))
|
||||
// SRS - Enable beta extensions and make VK_KHR_portability_subset visible
|
||||
#define VK_ENABLE_BETA_EXTENSIONS
|
||||
#endif
|
||||
#include <VulkanDevice.h>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace vks
|
||||
{
|
||||
/**
|
||||
* Default constructor
|
||||
*
|
||||
* @param physicalDevice Physical device that is to be used
|
||||
*/
|
||||
VulkanDevice::VulkanDevice(VkPhysicalDevice physicalDevice)
|
||||
{
|
||||
assert(physicalDevice);
|
||||
this->physicalDevice = physicalDevice;
|
||||
|
||||
// Store Properties features, limits and properties of the physical device for later use
|
||||
// Device properties also contain limits and sparse properties
|
||||
vkGetPhysicalDeviceProperties(physicalDevice, &properties);
|
||||
// Features should be checked by the examples before using them
|
||||
vkGetPhysicalDeviceFeatures(physicalDevice, &features);
|
||||
// Memory properties are used regularly for creating all kinds of buffers
|
||||
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
|
||||
// Queue family properties, used for setting up requested queues upon device creation
|
||||
uint32_t queueFamilyCount;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
|
||||
assert(queueFamilyCount > 0);
|
||||
queueFamilyProperties.resize(queueFamilyCount);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilyProperties.data());
|
||||
|
||||
// Get list of supported extensions
|
||||
uint32_t extCount = 0;
|
||||
vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extCount, nullptr);
|
||||
if (extCount > 0)
|
||||
{
|
||||
std::vector<VkExtensionProperties> extensions(extCount);
|
||||
if (vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extCount, &extensions.front()) == VK_SUCCESS)
|
||||
{
|
||||
for (auto& ext : extensions)
|
||||
{
|
||||
supportedExtensions.push_back(ext.extensionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default destructor
|
||||
*
|
||||
* @note Frees the logical device
|
||||
*/
|
||||
VulkanDevice::~VulkanDevice()
|
||||
{
|
||||
if (commandPool)
|
||||
{
|
||||
vkDestroyCommandPool(logicalDevice, commandPool, nullptr);
|
||||
}
|
||||
if (logicalDevice)
|
||||
{
|
||||
vkDestroyDevice(logicalDevice, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of a memory type that has all the requested property bits set
|
||||
*
|
||||
* @param typeBits Bit mask with bits set for each memory type supported by the resource to request for (from VkMemoryRequirements)
|
||||
* @param properties Bit mask of properties for the memory type to request
|
||||
* @param (Optional) memTypeFound Pointer to a bool that is set to true if a matching memory type has been found
|
||||
*
|
||||
* @return Index of the requested memory type
|
||||
*
|
||||
* @throw Throws an exception if memTypeFound is null and no memory type could be found that supports the requested properties
|
||||
*/
|
||||
uint32_t VulkanDevice::getMemoryType(uint32_t typeBits, VkMemoryPropertyFlags properties, VkBool32 *memTypeFound) const
|
||||
{
|
||||
for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++)
|
||||
{
|
||||
if ((typeBits & 1) == 1)
|
||||
{
|
||||
if ((memoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
|
||||
{
|
||||
if (memTypeFound)
|
||||
{
|
||||
*memTypeFound = true;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
typeBits >>= 1;
|
||||
}
|
||||
|
||||
if (memTypeFound)
|
||||
{
|
||||
*memTypeFound = false;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Could not find a matching memory type");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of a queue family that supports the requested queue flags
|
||||
* SRS - support VkQueueFlags parameter for requesting multiple flags vs. VkQueueFlagBits for a single flag only
|
||||
*
|
||||
* @param queueFlags Queue flags to find a queue family index for
|
||||
*
|
||||
* @return Index of the queue family index that matches the flags
|
||||
*
|
||||
* @throw Throws an exception if no queue family index could be found that supports the requested flags
|
||||
*/
|
||||
uint32_t VulkanDevice::getQueueFamilyIndex(VkQueueFlags queueFlags) const
|
||||
{
|
||||
// Dedicated queue for compute
|
||||
// Try to find a queue family index that supports compute but not graphics
|
||||
if ((queueFlags & VK_QUEUE_COMPUTE_BIT) == queueFlags)
|
||||
{
|
||||
for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilyProperties.size()); i++)
|
||||
{
|
||||
if ((queueFamilyProperties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) && ((queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dedicated queue for transfer
|
||||
// Try to find a queue family index that supports transfer but not graphics and compute
|
||||
if ((queueFlags & VK_QUEUE_TRANSFER_BIT) == queueFlags)
|
||||
{
|
||||
for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilyProperties.size()); i++)
|
||||
{
|
||||
if ((queueFamilyProperties[i].queueFlags & VK_QUEUE_TRANSFER_BIT) && ((queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0) && ((queueFamilyProperties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) == 0))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For other queue types or if no separate compute queue is present, return the first one to support the requested flags
|
||||
for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilyProperties.size()); i++)
|
||||
{
|
||||
if ((queueFamilyProperties[i].queueFlags & queueFlags) == queueFlags)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("Could not find a matching queue family index");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the logical device based on the assigned physical device, also gets default queue family indices
|
||||
*
|
||||
* @param enabledFeatures Can be used to enable certain features upon device creation
|
||||
* @param pNextChain Optional chain of pointer to extension structures
|
||||
* @param useSwapChain Set to false for headless rendering to omit the swapchain device extensions
|
||||
* @param requestedQueueTypes Bit flags specifying the queue types to be requested from the device
|
||||
*
|
||||
* @return VkResult of the device creation call
|
||||
*/
|
||||
VkResult VulkanDevice::createLogicalDevice(VkPhysicalDeviceFeatures enabledFeatures, std::vector<const char*> enabledExtensions, void* pNextChain, bool useSwapChain, VkQueueFlags requestedQueueTypes)
|
||||
{
|
||||
// Desired queues need to be requested upon logical device creation
|
||||
// Due to differing queue family configurations of Vulkan implementations this can be a bit tricky, especially if the application
|
||||
// requests different queue types
|
||||
|
||||
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos{};
|
||||
|
||||
// Get queue family indices for the requested queue family types
|
||||
// Note that the indices may overlap depending on the implementation
|
||||
|
||||
const float defaultQueuePriority(0.0f);
|
||||
|
||||
// Graphics queue
|
||||
if (requestedQueueTypes & VK_QUEUE_GRAPHICS_BIT)
|
||||
{
|
||||
queueFamilyIndices.graphics = getQueueFamilyIndex(VK_QUEUE_GRAPHICS_BIT);
|
||||
VkDeviceQueueCreateInfo queueInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
.queueFamilyIndex = queueFamilyIndices.graphics,
|
||||
.queueCount = 1,
|
||||
.pQueuePriorities = &defaultQueuePriority
|
||||
};
|
||||
queueCreateInfos.push_back(queueInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
queueFamilyIndices.graphics = 0;
|
||||
}
|
||||
|
||||
// Dedicated compute queue
|
||||
if (requestedQueueTypes & VK_QUEUE_COMPUTE_BIT)
|
||||
{
|
||||
queueFamilyIndices.compute = getQueueFamilyIndex(VK_QUEUE_COMPUTE_BIT);
|
||||
if (queueFamilyIndices.compute != queueFamilyIndices.graphics)
|
||||
{
|
||||
// If compute family index differs, we need an additional queue create info for the compute queue
|
||||
VkDeviceQueueCreateInfo queueInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
.queueFamilyIndex = queueFamilyIndices.compute,
|
||||
.queueCount = 1,
|
||||
.pQueuePriorities = &defaultQueuePriority,
|
||||
};
|
||||
queueCreateInfos.push_back(queueInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Else we use the same queue
|
||||
queueFamilyIndices.compute = queueFamilyIndices.graphics;
|
||||
}
|
||||
|
||||
// Dedicated transfer queue
|
||||
if (requestedQueueTypes & VK_QUEUE_TRANSFER_BIT)
|
||||
{
|
||||
queueFamilyIndices.transfer = getQueueFamilyIndex(VK_QUEUE_TRANSFER_BIT);
|
||||
if ((queueFamilyIndices.transfer != queueFamilyIndices.graphics) && (queueFamilyIndices.transfer != queueFamilyIndices.compute))
|
||||
{
|
||||
// If transfer family index differs, we need an additional queue create info for the transfer queue
|
||||
VkDeviceQueueCreateInfo queueInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
.queueFamilyIndex = queueFamilyIndices.transfer,
|
||||
.queueCount = 1,
|
||||
.pQueuePriorities = &defaultQueuePriority
|
||||
};
|
||||
queueCreateInfos.push_back(queueInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Else we use the same queue
|
||||
queueFamilyIndices.transfer = queueFamilyIndices.graphics;
|
||||
}
|
||||
|
||||
// Create the logical device representation
|
||||
std::vector<const char*> deviceExtensions(enabledExtensions);
|
||||
if (useSwapChain)
|
||||
{
|
||||
// If the device will be used for presenting to a display via a swapchain we need to request the swapchain extension
|
||||
deviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
VkDeviceCreateInfo deviceCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
||||
.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size()),
|
||||
.pQueueCreateInfos = queueCreateInfos.data(),
|
||||
.pEnabledFeatures = &enabledFeatures
|
||||
};
|
||||
|
||||
// If a pNext(Chain) has been passed, we need to add it to the device creation info
|
||||
VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{};
|
||||
if (pNextChain) {
|
||||
physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
||||
physicalDeviceFeatures2.features = enabledFeatures;
|
||||
physicalDeviceFeatures2.pNext = pNextChain;
|
||||
deviceCreateInfo.pEnabledFeatures = nullptr;
|
||||
deviceCreateInfo.pNext = &physicalDeviceFeatures2;
|
||||
}
|
||||
|
||||
#if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) && defined(VK_KHR_portability_subset)
|
||||
// SRS - When running on iOS/macOS with MoltenVK and VK_KHR_portability_subset is defined and supported by the device, enable the extension
|
||||
if (extensionSupported(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME))
|
||||
{
|
||||
deviceExtensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (deviceExtensions.size() > 0)
|
||||
{
|
||||
for (const char* enabledExtension : deviceExtensions)
|
||||
{
|
||||
if (!extensionSupported(enabledExtension)) {
|
||||
std::cerr << "Enabled device extension \"" << enabledExtension << "\" is not present at device level\n";
|
||||
}
|
||||
}
|
||||
|
||||
deviceCreateInfo.enabledExtensionCount = (uint32_t)deviceExtensions.size();
|
||||
deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data();
|
||||
}
|
||||
|
||||
this->enabledFeatures = enabledFeatures;
|
||||
|
||||
VkResult result = vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &logicalDevice);
|
||||
if (result != VK_SUCCESS)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Create a default command pool for graphics command buffers
|
||||
commandPool = createCommandPool(queueFamilyIndices.graphics);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a buffer on the device
|
||||
*
|
||||
* @param usageFlags Usage flag bit mask for the buffer (i.e. index, vertex, uniform buffer)
|
||||
* @param memoryPropertyFlags Memory properties for this buffer (i.e. device local, host visible, coherent)
|
||||
* @param size Size of the buffer in byes
|
||||
* @param buffer Pointer to the buffer handle acquired by the function
|
||||
* @param memory Pointer to the memory handle acquired by the function
|
||||
* @param data Pointer to the data that should be copied to the buffer after creation (optional, if not set, no data is copied over)
|
||||
*
|
||||
* @return VK_SUCCESS if buffer handle and memory have been created and (optionally passed) data has been copied
|
||||
*/
|
||||
VkResult VulkanDevice::createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, VkDeviceSize size, VkBuffer *buffer, VkDeviceMemory *memory, void *data)
|
||||
{
|
||||
// Create the buffer handle
|
||||
VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(usageFlags, size);
|
||||
bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_CHECK_RESULT(vkCreateBuffer(logicalDevice, &bufferCreateInfo, nullptr, buffer));
|
||||
|
||||
// Create the memory backing up the buffer handle
|
||||
VkMemoryRequirements memReqs;
|
||||
VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
|
||||
vkGetBufferMemoryRequirements(logicalDevice, *buffer, &memReqs);
|
||||
memAlloc.allocationSize = memReqs.size;
|
||||
// Find a memory type index that fits the properties of the buffer
|
||||
memAlloc.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, memoryPropertyFlags);
|
||||
// If the buffer has VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT set we also need to enable the appropriate flag during allocation
|
||||
VkMemoryAllocateFlagsInfoKHR allocFlagsInfo{};
|
||||
if (usageFlags & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) {
|
||||
allocFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR;
|
||||
allocFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
|
||||
memAlloc.pNext = &allocFlagsInfo;
|
||||
}
|
||||
VK_CHECK_RESULT(vkAllocateMemory(logicalDevice, &memAlloc, nullptr, memory));
|
||||
|
||||
// If a pointer to the buffer data has been passed, map the buffer and copy over the data
|
||||
if (data != nullptr)
|
||||
{
|
||||
void *mapped;
|
||||
VK_CHECK_RESULT(vkMapMemory(logicalDevice, *memory, 0, size, 0, &mapped));
|
||||
memcpy(mapped, data, size);
|
||||
// If host coherency hasn't been requested, do a manual flush to make writes visible
|
||||
if ((memoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0)
|
||||
{
|
||||
VkMappedMemoryRange mappedRange{
|
||||
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
|
||||
.memory = *memory,
|
||||
.size = size
|
||||
};
|
||||
vkFlushMappedMemoryRanges(logicalDevice, 1, &mappedRange);
|
||||
}
|
||||
vkUnmapMemory(logicalDevice, *memory);
|
||||
}
|
||||
|
||||
// Attach the memory to the buffer object
|
||||
VK_CHECK_RESULT(vkBindBufferMemory(logicalDevice, *buffer, *memory, 0));
|
||||
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a buffer on the device
|
||||
*
|
||||
* @param usageFlags Usage flag bit mask for the buffer (i.e. index, vertex, uniform buffer)
|
||||
* @param memoryPropertyFlags Memory properties for this buffer (i.e. device local, host visible, coherent)
|
||||
* @param buffer Pointer to a vk::Vulkan buffer object
|
||||
* @param size Size of the buffer in bytes
|
||||
* @param data Pointer to the data that should be copied to the buffer after creation (optional, if not set, no data is copied over)
|
||||
*
|
||||
* @return VK_SUCCESS if buffer handle and memory have been created and (optionally passed) data has been copied
|
||||
*/
|
||||
VkResult VulkanDevice::createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, vks::Buffer *buffer, VkDeviceSize size, void *data)
|
||||
{
|
||||
buffer->device = logicalDevice;
|
||||
|
||||
// Create the buffer handle
|
||||
VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(usageFlags, size);
|
||||
VK_CHECK_RESULT(vkCreateBuffer(logicalDevice, &bufferCreateInfo, nullptr, &buffer->buffer));
|
||||
|
||||
// Create the memory backing up the buffer handle
|
||||
VkMemoryRequirements memReqs;
|
||||
VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
|
||||
vkGetBufferMemoryRequirements(logicalDevice, buffer->buffer, &memReqs);
|
||||
memAlloc.allocationSize = memReqs.size;
|
||||
// Find a memory type index that fits the properties of the buffer
|
||||
memAlloc.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, memoryPropertyFlags);
|
||||
// If the buffer has VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT set we also need to enable the appropriate flag during allocation
|
||||
VkMemoryAllocateFlagsInfoKHR allocFlagsInfo{};
|
||||
if (usageFlags & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) {
|
||||
allocFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR;
|
||||
allocFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
|
||||
memAlloc.pNext = &allocFlagsInfo;
|
||||
}
|
||||
VK_CHECK_RESULT(vkAllocateMemory(logicalDevice, &memAlloc, nullptr, &buffer->memory));
|
||||
|
||||
buffer->alignment = memReqs.alignment;
|
||||
buffer->size = size;
|
||||
buffer->usageFlags = usageFlags;
|
||||
buffer->memoryPropertyFlags = memoryPropertyFlags;
|
||||
|
||||
// If a pointer to the buffer data has been passed, map the buffer and copy over the data
|
||||
if (data != nullptr)
|
||||
{
|
||||
VK_CHECK_RESULT(buffer->map());
|
||||
memcpy(buffer->mapped, data, size);
|
||||
if ((memoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0)
|
||||
buffer->flush();
|
||||
|
||||
buffer->unmap();
|
||||
}
|
||||
|
||||
// Initialize a default descriptor that covers the whole buffer size
|
||||
buffer->setupDescriptor();
|
||||
|
||||
// Attach the memory to the buffer object
|
||||
return buffer->bind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy buffer data from src to dst using VkCmdCopyBuffer
|
||||
*
|
||||
* @param src Pointer to the source buffer to copy from
|
||||
* @param dst Pointer to the destination buffer to copy to
|
||||
* @param queue Pointer
|
||||
* @param copyRegion (Optional) Pointer to a copy region, if NULL, the whole buffer is copied
|
||||
*
|
||||
* @note Source and destination pointers must have the appropriate transfer usage flags set (TRANSFER_SRC / TRANSFER_DST)
|
||||
*/
|
||||
void VulkanDevice::copyBuffer(vks::Buffer *src, vks::Buffer *dst, VkQueue queue, VkBufferCopy *copyRegion)
|
||||
{
|
||||
assert(dst->size >= src->size);
|
||||
assert(src->buffer);
|
||||
VkCommandBuffer copyCmd = createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
VkBufferCopy bufferCopy{};
|
||||
if (copyRegion == nullptr)
|
||||
{
|
||||
bufferCopy.size = src->size;
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferCopy = *copyRegion;
|
||||
}
|
||||
|
||||
vkCmdCopyBuffer(copyCmd, src->buffer, dst->buffer, 1, &bufferCopy);
|
||||
|
||||
flushCommandBuffer(copyCmd, queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a command pool for allocation command buffers from
|
||||
*
|
||||
* @param queueFamilyIndex Family index of the queue to create the command pool for
|
||||
* @param createFlags (Optional) Command pool creation flags (Defaults to VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT)
|
||||
*
|
||||
* @note Command buffers allocated from the created pool can only be submitted to a queue with the same family index
|
||||
*
|
||||
* @return A handle to the created command buffer
|
||||
*/
|
||||
VkCommandPool VulkanDevice::createCommandPool(uint32_t queueFamilyIndex, VkCommandPoolCreateFlags createFlags)
|
||||
{
|
||||
VkCommandPoolCreateInfo cmdPoolInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
.flags = createFlags,
|
||||
.queueFamilyIndex = queueFamilyIndex
|
||||
};
|
||||
VkCommandPool cmdPool;
|
||||
VK_CHECK_RESULT(vkCreateCommandPool(logicalDevice, &cmdPoolInfo, nullptr, &cmdPool));
|
||||
return cmdPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a command buffer from the command pool
|
||||
*
|
||||
* @param level Level of the new command buffer (primary or secondary)
|
||||
* @param pool Command pool from which the command buffer will be allocated
|
||||
* @param (Optional) begin If true, recording on the new command buffer will be started (vkBeginCommandBuffer) (Defaults to false)
|
||||
*
|
||||
* @return A handle to the allocated command buffer
|
||||
*/
|
||||
VkCommandBuffer VulkanDevice::createCommandBuffer(VkCommandBufferLevel level, VkCommandPool pool, bool begin)
|
||||
{
|
||||
VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(pool, level, 1);
|
||||
VkCommandBuffer cmdBuffer;
|
||||
VK_CHECK_RESULT(vkAllocateCommandBuffers(logicalDevice, &cmdBufAllocateInfo, &cmdBuffer));
|
||||
// If requested, also start recording for the new command buffer
|
||||
if (begin)
|
||||
{
|
||||
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
|
||||
VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffer, &cmdBufInfo));
|
||||
}
|
||||
return cmdBuffer;
|
||||
}
|
||||
|
||||
VkCommandBuffer VulkanDevice::createCommandBuffer(VkCommandBufferLevel level, bool begin)
|
||||
{
|
||||
return createCommandBuffer(level, commandPool, begin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish command buffer recording and submit it to a queue
|
||||
*
|
||||
* @param commandBuffer Command buffer to flush
|
||||
* @param queue Queue to submit the command buffer to
|
||||
* @param pool Command pool on which the command buffer has been created
|
||||
* @param free (Optional) Free the command buffer once it has been submitted (Defaults to true)
|
||||
*
|
||||
* @note The queue that the command buffer is submitted to must be from the same family index as the pool it was allocated from
|
||||
* @note Uses a fence to ensure command buffer has finished executing
|
||||
*/
|
||||
void VulkanDevice::flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, VkCommandPool pool, bool free)
|
||||
{
|
||||
if (commandBuffer == VK_NULL_HANDLE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
|
||||
|
||||
VkSubmitInfo submitInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &commandBuffer
|
||||
};
|
||||
// Create fence to ensure that the command buffer has finished executing
|
||||
VkFenceCreateInfo fenceInfo = vks::initializers::fenceCreateInfo(VK_FLAGS_NONE);
|
||||
VkFence fence;
|
||||
VK_CHECK_RESULT(vkCreateFence(logicalDevice, &fenceInfo, nullptr, &fence));
|
||||
// Submit to the queue
|
||||
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, fence));
|
||||
// Wait for the fence to signal that command buffer has finished executing
|
||||
VK_CHECK_RESULT(vkWaitForFences(logicalDevice, 1, &fence, VK_TRUE, DEFAULT_FENCE_TIMEOUT));
|
||||
vkDestroyFence(logicalDevice, fence, nullptr);
|
||||
if (free)
|
||||
{
|
||||
vkFreeCommandBuffers(logicalDevice, pool, 1, &commandBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDevice::flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, bool free)
|
||||
{
|
||||
return flushCommandBuffer(commandBuffer, queue, commandPool, free);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an extension is supported by the (physical device)
|
||||
*
|
||||
* @param extension Name of the extension to check
|
||||
*
|
||||
* @return True if the extension is supported (present in the list read at device creation time)
|
||||
*/
|
||||
bool VulkanDevice::extensionSupported(std::string extension)
|
||||
{
|
||||
return (std::find(supportedExtensions.begin(), supportedExtensions.end(), extension) != supportedExtensions.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the best-fit depth format for this device from a list of possible depth (and stencil) formats
|
||||
*
|
||||
* @param checkSamplingSupport Check if the format can be sampled from (e.g. for shader reads)
|
||||
*
|
||||
* @return The depth format that best fits for the current device
|
||||
*
|
||||
* @throw Throws an exception if no depth format fits the requirements
|
||||
*/
|
||||
VkFormat VulkanDevice::getSupportedDepthFormat(bool checkSamplingSupport)
|
||||
{
|
||||
// All depth formats may be optional, so we need to find a suitable depth format to use
|
||||
std::vector<VkFormat> depthFormats = { VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D32_SFLOAT, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D16_UNORM_S8_UINT, VK_FORMAT_D16_UNORM };
|
||||
for (auto& format : depthFormats)
|
||||
{
|
||||
VkFormatProperties formatProperties;
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties);
|
||||
// Format must support depth stencil attachment for optimal tiling
|
||||
if (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
|
||||
{
|
||||
if (checkSamplingSupport) {
|
||||
if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return format;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Could not find a matching depth format");
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Vulkan device class
|
||||
*
|
||||
* Encapsulates a physical Vulkan device and its logical representation
|
||||
*
|
||||
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "VulkanBuffer.h"
|
||||
#include "VulkanTools.h"
|
||||
#include "vulkan/vulkan.h"
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <exception>
|
||||
|
||||
namespace vks
|
||||
{
|
||||
struct VulkanDevice
|
||||
{
|
||||
/** @brief Physical device representation */
|
||||
VkPhysicalDevice physicalDevice{ VK_NULL_HANDLE };;
|
||||
/** @brief Logical device representation (application's view of the device) */
|
||||
VkDevice logicalDevice{ VK_NULL_HANDLE };
|
||||
/** @brief Properties of the physical device including limits that the application can check against */
|
||||
VkPhysicalDeviceProperties properties{};
|
||||
/** @brief Features of the physical device that an application can use to check if a feature is supported */
|
||||
VkPhysicalDeviceFeatures features{};
|
||||
/** @brief Features that have been enabled for use on the physical device */
|
||||
VkPhysicalDeviceFeatures enabledFeatures{};
|
||||
/** @brief Memory types and heaps of the physical device */
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties{};
|
||||
/** @brief Queue family properties of the physical device */
|
||||
std::vector<VkQueueFamilyProperties> queueFamilyProperties{};
|
||||
/** @brief List of extensions supported by the device */
|
||||
std::vector<std::string> supportedExtensions{};
|
||||
/** @brief Default command pool for the graphics queue family index */
|
||||
VkCommandPool commandPool{ VK_NULL_HANDLE };;
|
||||
/** @brief Contains queue family indices */
|
||||
struct
|
||||
{
|
||||
uint32_t graphics;
|
||||
uint32_t compute;
|
||||
uint32_t transfer;
|
||||
} queueFamilyIndices;
|
||||
operator VkDevice() const
|
||||
{
|
||||
return logicalDevice;
|
||||
};
|
||||
explicit VulkanDevice(VkPhysicalDevice physicalDevice);
|
||||
~VulkanDevice();
|
||||
uint32_t getMemoryType(uint32_t typeBits, VkMemoryPropertyFlags properties, VkBool32 *memTypeFound = nullptr) const;
|
||||
uint32_t getQueueFamilyIndex(VkQueueFlags queueFlags) const;
|
||||
VkResult createLogicalDevice(VkPhysicalDeviceFeatures enabledFeatures, std::vector<const char *> enabledExtensions, void *pNextChain, bool useSwapChain = true, VkQueueFlags requestedQueueTypes = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT);
|
||||
VkResult createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, VkDeviceSize size, VkBuffer *buffer, VkDeviceMemory *memory, void *data = nullptr);
|
||||
VkResult createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, vks::Buffer *buffer, VkDeviceSize size, void *data = nullptr);
|
||||
void copyBuffer(vks::Buffer *src, vks::Buffer *dst, VkQueue queue, VkBufferCopy *copyRegion = nullptr);
|
||||
VkCommandPool createCommandPool(uint32_t queueFamilyIndex, VkCommandPoolCreateFlags createFlags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT);
|
||||
VkCommandBuffer createCommandBuffer(VkCommandBufferLevel level, VkCommandPool pool, bool begin = false);
|
||||
VkCommandBuffer createCommandBuffer(VkCommandBufferLevel level, bool begin = false);
|
||||
void flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, VkCommandPool pool, bool free = true);
|
||||
void flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, bool free = true);
|
||||
bool extensionSupported(std::string extension);
|
||||
VkFormat getSupportedDepthFormat(bool checkSamplingSupport);
|
||||
};
|
||||
} // namespace vks
|
||||
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* Vulkan framebuffer class
|
||||
*
|
||||
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
#include "vulkan/vulkan.h"
|
||||
#include "VulkanDevice.h"
|
||||
#include "VulkanTools.h"
|
||||
|
||||
namespace vks
|
||||
{
|
||||
/**
|
||||
* @brief Encapsulates a single frame buffer attachment
|
||||
*/
|
||||
struct FramebufferAttachment
|
||||
{
|
||||
VkImage image;
|
||||
VkDeviceMemory memory;
|
||||
VkImageView view;
|
||||
VkFormat format;
|
||||
VkImageSubresourceRange subresourceRange;
|
||||
VkAttachmentDescription description;
|
||||
|
||||
/**
|
||||
* @brief Returns true if the attachment has a depth component
|
||||
*/
|
||||
bool hasDepth()
|
||||
{
|
||||
std::vector<VkFormat> formats =
|
||||
{
|
||||
VK_FORMAT_D16_UNORM,
|
||||
VK_FORMAT_X8_D24_UNORM_PACK32,
|
||||
VK_FORMAT_D32_SFLOAT,
|
||||
VK_FORMAT_D16_UNORM_S8_UINT,
|
||||
VK_FORMAT_D24_UNORM_S8_UINT,
|
||||
VK_FORMAT_D32_SFLOAT_S8_UINT,
|
||||
};
|
||||
return std::find(formats.begin(), formats.end(), format) != std::end(formats);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if the attachment has a stencil component
|
||||
*/
|
||||
bool hasStencil()
|
||||
{
|
||||
std::vector<VkFormat> formats =
|
||||
{
|
||||
VK_FORMAT_S8_UINT,
|
||||
VK_FORMAT_D16_UNORM_S8_UINT,
|
||||
VK_FORMAT_D24_UNORM_S8_UINT,
|
||||
VK_FORMAT_D32_SFLOAT_S8_UINT,
|
||||
};
|
||||
return std::find(formats.begin(), formats.end(), format) != std::end(formats);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if the attachment is a depth and/or stencil attachment
|
||||
*/
|
||||
bool isDepthStencil()
|
||||
{
|
||||
return(hasDepth() || hasStencil());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Describes the attributes of an attachment to be created
|
||||
*/
|
||||
struct AttachmentCreateInfo
|
||||
{
|
||||
uint32_t width, height;
|
||||
uint32_t layerCount;
|
||||
VkFormat format;
|
||||
VkImageUsageFlags usage;
|
||||
VkSampleCountFlagBits imageSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Encapsulates a complete Vulkan framebuffer with an arbitrary number and combination of attachments
|
||||
*/
|
||||
struct Framebuffer
|
||||
{
|
||||
private:
|
||||
vks::VulkanDevice *vulkanDevice;
|
||||
public:
|
||||
uint32_t width, height;
|
||||
VkFramebuffer framebuffer;
|
||||
VkRenderPass renderPass;
|
||||
VkSampler sampler;
|
||||
std::vector<vks::FramebufferAttachment> attachments;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*
|
||||
* @param vulkanDevice Pointer to a valid VulkanDevice
|
||||
*/
|
||||
Framebuffer(vks::VulkanDevice *vulkanDevice)
|
||||
{
|
||||
assert(vulkanDevice);
|
||||
this->vulkanDevice = vulkanDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy and free Vulkan resources used for the framebuffer and all of its attachments
|
||||
*/
|
||||
~Framebuffer()
|
||||
{
|
||||
assert(vulkanDevice);
|
||||
for (auto attachment : attachments)
|
||||
{
|
||||
vkDestroyImage(vulkanDevice->logicalDevice, attachment.image, nullptr);
|
||||
vkDestroyImageView(vulkanDevice->logicalDevice, attachment.view, nullptr);
|
||||
vkFreeMemory(vulkanDevice->logicalDevice, attachment.memory, nullptr);
|
||||
}
|
||||
vkDestroySampler(vulkanDevice->logicalDevice, sampler, nullptr);
|
||||
vkDestroyRenderPass(vulkanDevice->logicalDevice, renderPass, nullptr);
|
||||
vkDestroyFramebuffer(vulkanDevice->logicalDevice, framebuffer, nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new attachment described by createinfo to the framebuffer's attachment list
|
||||
*
|
||||
* @param createinfo Structure that specifies the framebuffer to be constructed
|
||||
*
|
||||
* @return Index of the new attachment
|
||||
*/
|
||||
uint32_t addAttachment(vks::AttachmentCreateInfo createinfo)
|
||||
{
|
||||
vks::FramebufferAttachment attachment;
|
||||
|
||||
attachment.format = createinfo.format;
|
||||
|
||||
VkImageAspectFlags aspectMask = VK_FLAGS_NONE;
|
||||
|
||||
// Select aspect mask and layout depending on usage
|
||||
|
||||
// Color attachment
|
||||
if (createinfo.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
|
||||
{
|
||||
aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
}
|
||||
|
||||
// Depth (and/or stencil) attachment
|
||||
if (createinfo.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
|
||||
{
|
||||
if (attachment.hasDepth())
|
||||
{
|
||||
aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
}
|
||||
if (attachment.hasStencil())
|
||||
{
|
||||
aspectMask = aspectMask | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
}
|
||||
|
||||
assert(aspectMask > 0);
|
||||
|
||||
VkImageCreateInfo image = vks::initializers::imageCreateInfo();
|
||||
image.imageType = VK_IMAGE_TYPE_2D;
|
||||
image.format = createinfo.format;
|
||||
image.extent.width = createinfo.width;
|
||||
image.extent.height = createinfo.height;
|
||||
image.extent.depth = 1;
|
||||
image.mipLevels = 1;
|
||||
image.arrayLayers = createinfo.layerCount;
|
||||
image.samples = createinfo.imageSampleCount;
|
||||
image.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image.usage = createinfo.usage;
|
||||
|
||||
VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
|
||||
VkMemoryRequirements memReqs;
|
||||
|
||||
// Create image for this attachment
|
||||
VK_CHECK_RESULT(vkCreateImage(vulkanDevice->logicalDevice, &image, nullptr, &attachment.image));
|
||||
vkGetImageMemoryRequirements(vulkanDevice->logicalDevice, attachment.image, &memReqs);
|
||||
memAlloc.allocationSize = memReqs.size;
|
||||
memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &memAlloc, nullptr, &attachment.memory));
|
||||
VK_CHECK_RESULT(vkBindImageMemory(vulkanDevice->logicalDevice, attachment.image, attachment.memory, 0));
|
||||
|
||||
attachment.subresourceRange = {};
|
||||
attachment.subresourceRange.aspectMask = aspectMask;
|
||||
attachment.subresourceRange.levelCount = 1;
|
||||
attachment.subresourceRange.layerCount = createinfo.layerCount;
|
||||
|
||||
VkImageViewCreateInfo imageView = vks::initializers::imageViewCreateInfo();
|
||||
imageView.viewType = (createinfo.layerCount == 1) ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||||
imageView.format = createinfo.format;
|
||||
imageView.subresourceRange = attachment.subresourceRange;
|
||||
imageView.subresourceRange.aspectMask = (attachment.hasDepth()) ? VK_IMAGE_ASPECT_DEPTH_BIT : aspectMask;
|
||||
imageView.image = attachment.image;
|
||||
VK_CHECK_RESULT(vkCreateImageView(vulkanDevice->logicalDevice, &imageView, nullptr, &attachment.view));
|
||||
|
||||
// Fill attachment description
|
||||
attachment.description = {};
|
||||
attachment.description.samples = createinfo.imageSampleCount;
|
||||
attachment.description.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachment.description.storeOp = (createinfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachment.description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachment.description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachment.description.format = createinfo.format;
|
||||
attachment.description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
// Final layout
|
||||
// If not, final layout depends on attachment type
|
||||
if (attachment.hasDepth() || attachment.hasStencil())
|
||||
{
|
||||
attachment.description.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
attachment.description.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
}
|
||||
|
||||
attachments.push_back(attachment);
|
||||
|
||||
return static_cast<uint32_t>(attachments.size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default sampler for sampling from any of the framebuffer attachments
|
||||
* Applications are free to create their own samplers for different use cases
|
||||
*
|
||||
* @param magFilter Magnification filter for lookups
|
||||
* @param minFilter Minification filter for lookups
|
||||
* @param adressMode Addressing mode for the U,V and W coordinates
|
||||
*
|
||||
* @return VkResult for the sampler creation
|
||||
*/
|
||||
VkResult createSampler(VkFilter magFilter, VkFilter minFilter, VkSamplerAddressMode adressMode)
|
||||
{
|
||||
VkSamplerCreateInfo samplerInfo = vks::initializers::samplerCreateInfo();
|
||||
samplerInfo.magFilter = magFilter;
|
||||
samplerInfo.minFilter = minFilter;
|
||||
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
samplerInfo.addressModeU = adressMode;
|
||||
samplerInfo.addressModeV = adressMode;
|
||||
samplerInfo.addressModeW = adressMode;
|
||||
samplerInfo.mipLodBias = 0.0f;
|
||||
samplerInfo.maxAnisotropy = 1.0f;
|
||||
samplerInfo.minLod = 0.0f;
|
||||
samplerInfo.maxLod = 1.0f;
|
||||
samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||
return vkCreateSampler(vulkanDevice->logicalDevice, &samplerInfo, nullptr, &sampler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default render pass setup with one sub pass
|
||||
*
|
||||
* @return VK_SUCCESS if all resources have been created successfully
|
||||
*/
|
||||
VkResult createRenderPass()
|
||||
{
|
||||
std::vector<VkAttachmentDescription> attachmentDescriptions;
|
||||
for (auto& attachment : attachments)
|
||||
{
|
||||
attachmentDescriptions.push_back(attachment.description);
|
||||
};
|
||||
|
||||
// Collect attachment references
|
||||
std::vector<VkAttachmentReference> colorReferences;
|
||||
VkAttachmentReference depthReference = {};
|
||||
bool hasDepth = false;
|
||||
bool hasColor = false;
|
||||
|
||||
uint32_t attachmentIndex = 0;
|
||||
|
||||
for (auto& attachment : attachments)
|
||||
{
|
||||
if (attachment.isDepthStencil())
|
||||
{
|
||||
// Only one depth attachment allowed
|
||||
assert(!hasDepth);
|
||||
depthReference.attachment = attachmentIndex;
|
||||
depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
hasDepth = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
colorReferences.push_back({ attachmentIndex, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
|
||||
hasColor = true;
|
||||
}
|
||||
attachmentIndex++;
|
||||
};
|
||||
|
||||
// Default render pass setup uses only one subpass
|
||||
VkSubpassDescription subpass = {};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
if (hasColor)
|
||||
{
|
||||
subpass.pColorAttachments = colorReferences.data();
|
||||
subpass.colorAttachmentCount = static_cast<uint32_t>(colorReferences.size());
|
||||
}
|
||||
if (hasDepth)
|
||||
{
|
||||
subpass.pDepthStencilAttachment = &depthReference;
|
||||
}
|
||||
|
||||
// Use subpass dependencies for attachment layout transitions
|
||||
std::array<VkSubpassDependency, 4> dependencies{};
|
||||
|
||||
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[0].dstSubpass = 0;
|
||||
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
dependencies[0].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
dependencies[0].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
|
||||
|
||||
|
||||
dependencies[1].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[1].dstSubpass = 0;
|
||||
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependencies[1].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||
dependencies[1].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
|
||||
dependencies[2].srcSubpass = 0;
|
||||
dependencies[2].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[2].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
dependencies[2].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
dependencies[2].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
dependencies[2].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||
|
||||
dependencies[3].srcSubpass = 0;
|
||||
dependencies[3].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[3].srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
dependencies[3].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
dependencies[3].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
dependencies[3].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
|
||||
// Create render pass
|
||||
VkRenderPassCreateInfo renderPassInfo = {};
|
||||
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
renderPassInfo.pAttachments = attachmentDescriptions.data();
|
||||
renderPassInfo.attachmentCount = static_cast<uint32_t>(attachmentDescriptions.size());
|
||||
renderPassInfo.subpassCount = 1;
|
||||
renderPassInfo.pSubpasses = &subpass;
|
||||
renderPassInfo.dependencyCount = 4;
|
||||
renderPassInfo.pDependencies = dependencies.data();
|
||||
VK_CHECK_RESULT(vkCreateRenderPass(vulkanDevice->logicalDevice, &renderPassInfo, nullptr, &renderPass));
|
||||
|
||||
std::vector<VkImageView> attachmentViews;
|
||||
for (auto& attachment : attachments)
|
||||
{
|
||||
attachmentViews.push_back(attachment.view);
|
||||
}
|
||||
|
||||
// Find. max number of layers across attachments
|
||||
uint32_t maxLayers = 0;
|
||||
for (auto& attachment : attachments)
|
||||
{
|
||||
if (attachment.subresourceRange.layerCount > maxLayers)
|
||||
{
|
||||
maxLayers = attachment.subresourceRange.layerCount;
|
||||
}
|
||||
}
|
||||
|
||||
VkFramebufferCreateInfo framebufferInfo = {};
|
||||
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
framebufferInfo.renderPass = renderPass;
|
||||
framebufferInfo.pAttachments = attachmentViews.data();
|
||||
framebufferInfo.attachmentCount = static_cast<uint32_t>(attachmentViews.size());
|
||||
framebufferInfo.width = width;
|
||||
framebufferInfo.height = height;
|
||||
framebufferInfo.layers = maxLayers;
|
||||
VK_CHECK_RESULT(vkCreateFramebuffer(vulkanDevice->logicalDevice, &framebufferInfo, nullptr, &framebuffer));
|
||||
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,650 @@
|
||||
/*
|
||||
* Initializers for Vulkan structures and objects used by the examples
|
||||
* Saves lot of VK_STRUCTURE_TYPE assignments
|
||||
* Some initializers are parameterized for convenience
|
||||
*
|
||||
* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "vulkan/vulkan.h"
|
||||
|
||||
namespace vks
|
||||
{
|
||||
namespace initializers
|
||||
{
|
||||
|
||||
inline VkMemoryAllocateInfo memoryAllocateInfo()
|
||||
{
|
||||
VkMemoryAllocateInfo memAllocInfo {};
|
||||
memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
return memAllocInfo;
|
||||
}
|
||||
|
||||
inline VkMappedMemoryRange mappedMemoryRange()
|
||||
{
|
||||
VkMappedMemoryRange mappedMemoryRange {};
|
||||
mappedMemoryRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
||||
return mappedMemoryRange;
|
||||
}
|
||||
|
||||
inline VkCommandBufferAllocateInfo commandBufferAllocateInfo(
|
||||
VkCommandPool commandPool,
|
||||
VkCommandBufferLevel level,
|
||||
uint32_t bufferCount)
|
||||
{
|
||||
VkCommandBufferAllocateInfo commandBufferAllocateInfo {};
|
||||
commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
commandBufferAllocateInfo.commandPool = commandPool;
|
||||
commandBufferAllocateInfo.level = level;
|
||||
commandBufferAllocateInfo.commandBufferCount = bufferCount;
|
||||
return commandBufferAllocateInfo;
|
||||
}
|
||||
|
||||
inline VkCommandPoolCreateInfo commandPoolCreateInfo()
|
||||
{
|
||||
VkCommandPoolCreateInfo cmdPoolCreateInfo {};
|
||||
cmdPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
return cmdPoolCreateInfo;
|
||||
}
|
||||
|
||||
inline VkCommandBufferBeginInfo commandBufferBeginInfo()
|
||||
{
|
||||
VkCommandBufferBeginInfo cmdBufferBeginInfo {};
|
||||
cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
return cmdBufferBeginInfo;
|
||||
}
|
||||
|
||||
inline VkCommandBufferInheritanceInfo commandBufferInheritanceInfo()
|
||||
{
|
||||
VkCommandBufferInheritanceInfo cmdBufferInheritanceInfo {};
|
||||
cmdBufferInheritanceInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
|
||||
return cmdBufferInheritanceInfo;
|
||||
}
|
||||
|
||||
inline VkRenderPassBeginInfo renderPassBeginInfo()
|
||||
{
|
||||
VkRenderPassBeginInfo renderPassBeginInfo {};
|
||||
renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
||||
return renderPassBeginInfo;
|
||||
}
|
||||
|
||||
inline VkRenderPassCreateInfo renderPassCreateInfo()
|
||||
{
|
||||
VkRenderPassCreateInfo renderPassCreateInfo {};
|
||||
renderPassCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
return renderPassCreateInfo;
|
||||
}
|
||||
|
||||
/** @brief Initialize an image memory barrier with no image transfer ownership */
|
||||
inline VkImageMemoryBarrier imageMemoryBarrier()
|
||||
{
|
||||
VkImageMemoryBarrier imageMemoryBarrier {};
|
||||
imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
return imageMemoryBarrier;
|
||||
}
|
||||
|
||||
/** @brief Initialize a buffer memory barrier with no image transfer ownership */
|
||||
inline VkBufferMemoryBarrier bufferMemoryBarrier()
|
||||
{
|
||||
VkBufferMemoryBarrier bufferMemoryBarrier {};
|
||||
bufferMemoryBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
|
||||
bufferMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
bufferMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
return bufferMemoryBarrier;
|
||||
}
|
||||
|
||||
inline VkMemoryBarrier memoryBarrier()
|
||||
{
|
||||
VkMemoryBarrier memoryBarrier {};
|
||||
memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
|
||||
return memoryBarrier;
|
||||
}
|
||||
|
||||
inline VkImageCreateInfo imageCreateInfo()
|
||||
{
|
||||
VkImageCreateInfo imageCreateInfo {};
|
||||
imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
return imageCreateInfo;
|
||||
}
|
||||
|
||||
inline VkSamplerCreateInfo samplerCreateInfo()
|
||||
{
|
||||
VkSamplerCreateInfo samplerCreateInfo {};
|
||||
samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
samplerCreateInfo.maxAnisotropy = 1.0f;
|
||||
return samplerCreateInfo;
|
||||
}
|
||||
|
||||
inline VkImageViewCreateInfo imageViewCreateInfo()
|
||||
{
|
||||
VkImageViewCreateInfo imageViewCreateInfo {};
|
||||
imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
return imageViewCreateInfo;
|
||||
}
|
||||
|
||||
inline VkFramebufferCreateInfo framebufferCreateInfo()
|
||||
{
|
||||
VkFramebufferCreateInfo framebufferCreateInfo {};
|
||||
framebufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
return framebufferCreateInfo;
|
||||
}
|
||||
|
||||
inline VkSemaphoreCreateInfo semaphoreCreateInfo()
|
||||
{
|
||||
VkSemaphoreCreateInfo semaphoreCreateInfo {};
|
||||
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||
return semaphoreCreateInfo;
|
||||
}
|
||||
|
||||
inline VkFenceCreateInfo fenceCreateInfo(VkFenceCreateFlags flags = 0)
|
||||
{
|
||||
VkFenceCreateInfo fenceCreateInfo {};
|
||||
fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
fenceCreateInfo.flags = flags;
|
||||
return fenceCreateInfo;
|
||||
}
|
||||
|
||||
inline VkSubmitInfo submitInfo()
|
||||
{
|
||||
return { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO };
|
||||
}
|
||||
|
||||
inline VkViewport viewport(
|
||||
float width,
|
||||
float height,
|
||||
float minDepth,
|
||||
float maxDepth)
|
||||
{
|
||||
VkViewport viewport {};
|
||||
viewport.width = width;
|
||||
viewport.height = height;
|
||||
viewport.minDepth = minDepth;
|
||||
viewport.maxDepth = maxDepth;
|
||||
return viewport;
|
||||
}
|
||||
|
||||
inline VkRect2D rect2D(
|
||||
int32_t width,
|
||||
int32_t height,
|
||||
int32_t offsetX,
|
||||
int32_t offsetY)
|
||||
{
|
||||
VkRect2D rect2D {};
|
||||
rect2D.extent.width = width;
|
||||
rect2D.extent.height = height;
|
||||
rect2D.offset.x = offsetX;
|
||||
rect2D.offset.y = offsetY;
|
||||
return rect2D;
|
||||
}
|
||||
|
||||
inline VkBufferCreateInfo bufferCreateInfo()
|
||||
{
|
||||
VkBufferCreateInfo bufCreateInfo {};
|
||||
bufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
return bufCreateInfo;
|
||||
}
|
||||
|
||||
inline VkBufferCreateInfo bufferCreateInfo(
|
||||
VkBufferUsageFlags usage,
|
||||
VkDeviceSize size)
|
||||
{
|
||||
VkBufferCreateInfo bufCreateInfo {};
|
||||
bufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
bufCreateInfo.usage = usage;
|
||||
bufCreateInfo.size = size;
|
||||
return bufCreateInfo;
|
||||
}
|
||||
|
||||
inline VkDescriptorPoolCreateInfo descriptorPoolCreateInfo(
|
||||
uint32_t poolSizeCount,
|
||||
VkDescriptorPoolSize* pPoolSizes,
|
||||
uint32_t maxSets)
|
||||
{
|
||||
VkDescriptorPoolCreateInfo descriptorPoolInfo {};
|
||||
descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||
descriptorPoolInfo.poolSizeCount = poolSizeCount;
|
||||
descriptorPoolInfo.pPoolSizes = pPoolSizes;
|
||||
descriptorPoolInfo.maxSets = maxSets;
|
||||
return descriptorPoolInfo;
|
||||
}
|
||||
|
||||
inline VkDescriptorPoolCreateInfo descriptorPoolCreateInfo(
|
||||
const std::vector<VkDescriptorPoolSize>& poolSizes,
|
||||
uint32_t maxSets)
|
||||
{
|
||||
VkDescriptorPoolCreateInfo descriptorPoolInfo{};
|
||||
descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||
descriptorPoolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
|
||||
descriptorPoolInfo.pPoolSizes = poolSizes.data();
|
||||
descriptorPoolInfo.maxSets = maxSets;
|
||||
return descriptorPoolInfo;
|
||||
}
|
||||
|
||||
inline VkDescriptorPoolSize descriptorPoolSize(
|
||||
VkDescriptorType type,
|
||||
uint32_t descriptorCount)
|
||||
{
|
||||
VkDescriptorPoolSize descriptorPoolSize {};
|
||||
descriptorPoolSize.type = type;
|
||||
descriptorPoolSize.descriptorCount = descriptorCount;
|
||||
return descriptorPoolSize;
|
||||
}
|
||||
|
||||
inline VkDescriptorSetLayoutBinding descriptorSetLayoutBinding(
|
||||
VkDescriptorType type,
|
||||
VkShaderStageFlags stageFlags,
|
||||
uint32_t binding,
|
||||
uint32_t descriptorCount = 1)
|
||||
{
|
||||
VkDescriptorSetLayoutBinding setLayoutBinding {};
|
||||
setLayoutBinding.descriptorType = type;
|
||||
setLayoutBinding.stageFlags = stageFlags;
|
||||
setLayoutBinding.binding = binding;
|
||||
setLayoutBinding.descriptorCount = descriptorCount;
|
||||
return setLayoutBinding;
|
||||
}
|
||||
|
||||
inline VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo(
|
||||
const VkDescriptorSetLayoutBinding* pBindings,
|
||||
uint32_t bindingCount)
|
||||
{
|
||||
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo {};
|
||||
descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
||||
descriptorSetLayoutCreateInfo.pBindings = pBindings;
|
||||
descriptorSetLayoutCreateInfo.bindingCount = bindingCount;
|
||||
return descriptorSetLayoutCreateInfo;
|
||||
}
|
||||
|
||||
inline VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo(
|
||||
const std::vector<VkDescriptorSetLayoutBinding>& bindings)
|
||||
{
|
||||
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{};
|
||||
descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
||||
descriptorSetLayoutCreateInfo.pBindings = bindings.data();
|
||||
descriptorSetLayoutCreateInfo.bindingCount = static_cast<uint32_t>(bindings.size());
|
||||
return descriptorSetLayoutCreateInfo;
|
||||
}
|
||||
|
||||
inline VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo(
|
||||
const VkDescriptorSetLayout* pSetLayouts,
|
||||
uint32_t setLayoutCount = 1)
|
||||
{
|
||||
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo {};
|
||||
pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||
pipelineLayoutCreateInfo.setLayoutCount = setLayoutCount;
|
||||
pipelineLayoutCreateInfo.pSetLayouts = pSetLayouts;
|
||||
return pipelineLayoutCreateInfo;
|
||||
}
|
||||
|
||||
inline VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo(
|
||||
uint32_t setLayoutCount = 1)
|
||||
{
|
||||
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{};
|
||||
pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||
pipelineLayoutCreateInfo.setLayoutCount = setLayoutCount;
|
||||
return pipelineLayoutCreateInfo;
|
||||
}
|
||||
|
||||
inline VkDescriptorSetAllocateInfo descriptorSetAllocateInfo(
|
||||
VkDescriptorPool descriptorPool,
|
||||
const VkDescriptorSetLayout* pSetLayouts,
|
||||
uint32_t descriptorSetCount)
|
||||
{
|
||||
VkDescriptorSetAllocateInfo descriptorSetAllocateInfo {};
|
||||
descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
descriptorSetAllocateInfo.descriptorPool = descriptorPool;
|
||||
descriptorSetAllocateInfo.pSetLayouts = pSetLayouts;
|
||||
descriptorSetAllocateInfo.descriptorSetCount = descriptorSetCount;
|
||||
return descriptorSetAllocateInfo;
|
||||
}
|
||||
|
||||
inline VkDescriptorImageInfo descriptorImageInfo(VkSampler sampler, VkImageView imageView, VkImageLayout imageLayout)
|
||||
{
|
||||
VkDescriptorImageInfo descriptorImageInfo {};
|
||||
descriptorImageInfo.sampler = sampler;
|
||||
descriptorImageInfo.imageView = imageView;
|
||||
descriptorImageInfo.imageLayout = imageLayout;
|
||||
return descriptorImageInfo;
|
||||
}
|
||||
|
||||
inline VkWriteDescriptorSet writeDescriptorSet(
|
||||
VkDescriptorSet dstSet,
|
||||
VkDescriptorType type,
|
||||
uint32_t binding,
|
||||
VkDescriptorBufferInfo* bufferInfo,
|
||||
uint32_t descriptorCount = 1)
|
||||
{
|
||||
VkWriteDescriptorSet writeDescriptorSet {};
|
||||
writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writeDescriptorSet.dstSet = dstSet;
|
||||
writeDescriptorSet.descriptorType = type;
|
||||
writeDescriptorSet.dstBinding = binding;
|
||||
writeDescriptorSet.pBufferInfo = bufferInfo;
|
||||
writeDescriptorSet.descriptorCount = descriptorCount;
|
||||
return writeDescriptorSet;
|
||||
}
|
||||
|
||||
inline VkWriteDescriptorSet writeDescriptorSet(
|
||||
VkDescriptorSet dstSet,
|
||||
VkDescriptorType type,
|
||||
uint32_t binding,
|
||||
VkDescriptorImageInfo *imageInfo,
|
||||
uint32_t descriptorCount = 1)
|
||||
{
|
||||
VkWriteDescriptorSet writeDescriptorSet {};
|
||||
writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writeDescriptorSet.dstSet = dstSet;
|
||||
writeDescriptorSet.descriptorType = type;
|
||||
writeDescriptorSet.dstBinding = binding;
|
||||
writeDescriptorSet.pImageInfo = imageInfo;
|
||||
writeDescriptorSet.descriptorCount = descriptorCount;
|
||||
return writeDescriptorSet;
|
||||
}
|
||||
|
||||
inline VkVertexInputBindingDescription vertexInputBindingDescription(
|
||||
uint32_t binding,
|
||||
uint32_t stride,
|
||||
VkVertexInputRate inputRate)
|
||||
{
|
||||
VkVertexInputBindingDescription vInputBindDescription {};
|
||||
vInputBindDescription.binding = binding;
|
||||
vInputBindDescription.stride = stride;
|
||||
vInputBindDescription.inputRate = inputRate;
|
||||
return vInputBindDescription;
|
||||
}
|
||||
|
||||
inline VkVertexInputAttributeDescription vertexInputAttributeDescription(
|
||||
uint32_t binding,
|
||||
uint32_t location,
|
||||
VkFormat format,
|
||||
uint32_t offset)
|
||||
{
|
||||
VkVertexInputAttributeDescription vInputAttribDescription {};
|
||||
vInputAttribDescription.location = location;
|
||||
vInputAttribDescription.binding = binding;
|
||||
vInputAttribDescription.format = format;
|
||||
vInputAttribDescription.offset = offset;
|
||||
return vInputAttribDescription;
|
||||
}
|
||||
|
||||
inline VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo()
|
||||
{
|
||||
VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo {};
|
||||
pipelineVertexInputStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
||||
return pipelineVertexInputStateCreateInfo;
|
||||
}
|
||||
|
||||
inline VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo(
|
||||
const std::vector<VkVertexInputBindingDescription> &vertexBindingDescriptions,
|
||||
const std::vector<VkVertexInputAttributeDescription> &vertexAttributeDescriptions
|
||||
)
|
||||
{
|
||||
VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo{};
|
||||
pipelineVertexInputStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
||||
pipelineVertexInputStateCreateInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexBindingDescriptions.size());
|
||||
pipelineVertexInputStateCreateInfo.pVertexBindingDescriptions = vertexBindingDescriptions.data();
|
||||
pipelineVertexInputStateCreateInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexAttributeDescriptions.size());
|
||||
pipelineVertexInputStateCreateInfo.pVertexAttributeDescriptions = vertexAttributeDescriptions.data();
|
||||
return pipelineVertexInputStateCreateInfo;
|
||||
}
|
||||
|
||||
inline VkPipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo(
|
||||
VkPrimitiveTopology topology,
|
||||
VkPipelineInputAssemblyStateCreateFlags flags,
|
||||
VkBool32 primitiveRestartEnable)
|
||||
{
|
||||
VkPipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo {};
|
||||
pipelineInputAssemblyStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
||||
pipelineInputAssemblyStateCreateInfo.topology = topology;
|
||||
pipelineInputAssemblyStateCreateInfo.flags = flags;
|
||||
pipelineInputAssemblyStateCreateInfo.primitiveRestartEnable = primitiveRestartEnable;
|
||||
return pipelineInputAssemblyStateCreateInfo;
|
||||
}
|
||||
|
||||
inline VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo(
|
||||
VkPolygonMode polygonMode,
|
||||
VkCullModeFlags cullMode,
|
||||
VkFrontFace frontFace,
|
||||
VkPipelineRasterizationStateCreateFlags flags = 0)
|
||||
{
|
||||
VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo {};
|
||||
pipelineRasterizationStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
||||
pipelineRasterizationStateCreateInfo.polygonMode = polygonMode;
|
||||
pipelineRasterizationStateCreateInfo.cullMode = cullMode;
|
||||
pipelineRasterizationStateCreateInfo.frontFace = frontFace;
|
||||
pipelineRasterizationStateCreateInfo.flags = flags;
|
||||
pipelineRasterizationStateCreateInfo.depthClampEnable = VK_FALSE;
|
||||
pipelineRasterizationStateCreateInfo.lineWidth = 1.0f;
|
||||
return pipelineRasterizationStateCreateInfo;
|
||||
}
|
||||
|
||||
inline VkPipelineColorBlendAttachmentState pipelineColorBlendAttachmentState(
|
||||
VkColorComponentFlags colorWriteMask,
|
||||
VkBool32 blendEnable)
|
||||
{
|
||||
VkPipelineColorBlendAttachmentState pipelineColorBlendAttachmentState {};
|
||||
pipelineColorBlendAttachmentState.colorWriteMask = colorWriteMask;
|
||||
pipelineColorBlendAttachmentState.blendEnable = blendEnable;
|
||||
return pipelineColorBlendAttachmentState;
|
||||
}
|
||||
|
||||
inline VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo(
|
||||
uint32_t attachmentCount,
|
||||
const VkPipelineColorBlendAttachmentState * pAttachments)
|
||||
{
|
||||
VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo {};
|
||||
pipelineColorBlendStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
||||
pipelineColorBlendStateCreateInfo.attachmentCount = attachmentCount;
|
||||
pipelineColorBlendStateCreateInfo.pAttachments = pAttachments;
|
||||
return pipelineColorBlendStateCreateInfo;
|
||||
}
|
||||
|
||||
inline VkPipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo(
|
||||
VkBool32 depthTestEnable,
|
||||
VkBool32 depthWriteEnable,
|
||||
VkCompareOp depthCompareOp)
|
||||
{
|
||||
VkPipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo {};
|
||||
pipelineDepthStencilStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
|
||||
pipelineDepthStencilStateCreateInfo.depthTestEnable = depthTestEnable;
|
||||
pipelineDepthStencilStateCreateInfo.depthWriteEnable = depthWriteEnable;
|
||||
pipelineDepthStencilStateCreateInfo.depthCompareOp = depthCompareOp;
|
||||
pipelineDepthStencilStateCreateInfo.back.compareOp = VK_COMPARE_OP_ALWAYS;
|
||||
return pipelineDepthStencilStateCreateInfo;
|
||||
}
|
||||
|
||||
inline VkPipelineViewportStateCreateInfo pipelineViewportStateCreateInfo(
|
||||
uint32_t viewportCount,
|
||||
uint32_t scissorCount,
|
||||
VkPipelineViewportStateCreateFlags flags = 0)
|
||||
{
|
||||
VkPipelineViewportStateCreateInfo pipelineViewportStateCreateInfo {};
|
||||
pipelineViewportStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
||||
pipelineViewportStateCreateInfo.viewportCount = viewportCount;
|
||||
pipelineViewportStateCreateInfo.scissorCount = scissorCount;
|
||||
pipelineViewportStateCreateInfo.flags = flags;
|
||||
return pipelineViewportStateCreateInfo;
|
||||
}
|
||||
|
||||
inline VkPipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo(
|
||||
VkSampleCountFlagBits rasterizationSamples,
|
||||
VkPipelineMultisampleStateCreateFlags flags = 0)
|
||||
{
|
||||
VkPipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo {};
|
||||
pipelineMultisampleStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
||||
pipelineMultisampleStateCreateInfo.rasterizationSamples = rasterizationSamples;
|
||||
pipelineMultisampleStateCreateInfo.flags = flags;
|
||||
return pipelineMultisampleStateCreateInfo;
|
||||
}
|
||||
|
||||
inline VkPipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo(
|
||||
const VkDynamicState * pDynamicStates,
|
||||
uint32_t dynamicStateCount,
|
||||
VkPipelineDynamicStateCreateFlags flags = 0)
|
||||
{
|
||||
VkPipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo {};
|
||||
pipelineDynamicStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
||||
pipelineDynamicStateCreateInfo.pDynamicStates = pDynamicStates;
|
||||
pipelineDynamicStateCreateInfo.dynamicStateCount = dynamicStateCount;
|
||||
pipelineDynamicStateCreateInfo.flags = flags;
|
||||
return pipelineDynamicStateCreateInfo;
|
||||
}
|
||||
|
||||
inline VkPipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo(
|
||||
const std::vector<VkDynamicState>& pDynamicStates,
|
||||
VkPipelineDynamicStateCreateFlags flags = 0)
|
||||
{
|
||||
VkPipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo{};
|
||||
pipelineDynamicStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
||||
pipelineDynamicStateCreateInfo.pDynamicStates = pDynamicStates.data();
|
||||
pipelineDynamicStateCreateInfo.dynamicStateCount = static_cast<uint32_t>(pDynamicStates.size());
|
||||
pipelineDynamicStateCreateInfo.flags = flags;
|
||||
return pipelineDynamicStateCreateInfo;
|
||||
}
|
||||
|
||||
inline VkPipelineTessellationStateCreateInfo pipelineTessellationStateCreateInfo(uint32_t patchControlPoints)
|
||||
{
|
||||
VkPipelineTessellationStateCreateInfo pipelineTessellationStateCreateInfo {};
|
||||
pipelineTessellationStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
|
||||
pipelineTessellationStateCreateInfo.patchControlPoints = patchControlPoints;
|
||||
return pipelineTessellationStateCreateInfo;
|
||||
}
|
||||
|
||||
inline VkGraphicsPipelineCreateInfo pipelineCreateInfo(
|
||||
VkPipelineLayout layout,
|
||||
VkRenderPass renderPass,
|
||||
VkPipelineCreateFlags flags = 0)
|
||||
{
|
||||
VkGraphicsPipelineCreateInfo pipelineCreateInfo {};
|
||||
pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
||||
pipelineCreateInfo.layout = layout;
|
||||
pipelineCreateInfo.renderPass = renderPass;
|
||||
pipelineCreateInfo.flags = flags;
|
||||
pipelineCreateInfo.basePipelineIndex = -1;
|
||||
pipelineCreateInfo.basePipelineHandle = VK_NULL_HANDLE;
|
||||
return pipelineCreateInfo;
|
||||
}
|
||||
|
||||
inline VkGraphicsPipelineCreateInfo pipelineCreateInfo()
|
||||
{
|
||||
VkGraphicsPipelineCreateInfo pipelineCreateInfo{};
|
||||
pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
||||
pipelineCreateInfo.basePipelineIndex = -1;
|
||||
pipelineCreateInfo.basePipelineHandle = VK_NULL_HANDLE;
|
||||
return pipelineCreateInfo;
|
||||
}
|
||||
|
||||
inline VkComputePipelineCreateInfo computePipelineCreateInfo(
|
||||
VkPipelineLayout layout,
|
||||
VkPipelineCreateFlags flags = 0)
|
||||
{
|
||||
VkComputePipelineCreateInfo computePipelineCreateInfo {};
|
||||
computePipelineCreateInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
|
||||
computePipelineCreateInfo.layout = layout;
|
||||
computePipelineCreateInfo.flags = flags;
|
||||
return computePipelineCreateInfo;
|
||||
}
|
||||
|
||||
inline VkPushConstantRange pushConstantRange(
|
||||
VkShaderStageFlags stageFlags,
|
||||
uint32_t size,
|
||||
uint32_t offset)
|
||||
{
|
||||
VkPushConstantRange pushConstantRange {};
|
||||
pushConstantRange.stageFlags = stageFlags;
|
||||
pushConstantRange.offset = offset;
|
||||
pushConstantRange.size = size;
|
||||
return pushConstantRange;
|
||||
}
|
||||
|
||||
inline VkBindSparseInfo bindSparseInfo()
|
||||
{
|
||||
VkBindSparseInfo bindSparseInfo{};
|
||||
bindSparseInfo.sType = VK_STRUCTURE_TYPE_BIND_SPARSE_INFO;
|
||||
return bindSparseInfo;
|
||||
}
|
||||
|
||||
/** @brief Initialize a map entry for a shader specialization constant */
|
||||
inline VkSpecializationMapEntry specializationMapEntry(uint32_t constantID, uint32_t offset, size_t size)
|
||||
{
|
||||
VkSpecializationMapEntry specializationMapEntry{};
|
||||
specializationMapEntry.constantID = constantID;
|
||||
specializationMapEntry.offset = offset;
|
||||
specializationMapEntry.size = size;
|
||||
return specializationMapEntry;
|
||||
}
|
||||
|
||||
/** @brief Initialize a specialization constant info structure to pass to a shader stage */
|
||||
inline VkSpecializationInfo specializationInfo(uint32_t mapEntryCount, const VkSpecializationMapEntry* mapEntries, size_t dataSize, const void* data)
|
||||
{
|
||||
VkSpecializationInfo specializationInfo{};
|
||||
specializationInfo.mapEntryCount = mapEntryCount;
|
||||
specializationInfo.pMapEntries = mapEntries;
|
||||
specializationInfo.dataSize = dataSize;
|
||||
specializationInfo.pData = data;
|
||||
return specializationInfo;
|
||||
}
|
||||
|
||||
/** @brief Initialize a specialization constant info structure to pass to a shader stage */
|
||||
inline VkSpecializationInfo specializationInfo(const std::vector<VkSpecializationMapEntry> &mapEntries, size_t dataSize, const void* data)
|
||||
{
|
||||
VkSpecializationInfo specializationInfo{};
|
||||
specializationInfo.mapEntryCount = static_cast<uint32_t>(mapEntries.size());
|
||||
specializationInfo.pMapEntries = mapEntries.data();
|
||||
specializationInfo.dataSize = dataSize;
|
||||
specializationInfo.pData = data;
|
||||
return specializationInfo;
|
||||
}
|
||||
|
||||
// Ray tracing related
|
||||
inline VkAccelerationStructureGeometryKHR accelerationStructureGeometryKHR()
|
||||
{
|
||||
VkAccelerationStructureGeometryKHR accelerationStructureGeometryKHR{};
|
||||
accelerationStructureGeometryKHR.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
|
||||
return accelerationStructureGeometryKHR;
|
||||
}
|
||||
|
||||
inline VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfoKHR()
|
||||
{
|
||||
VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfoKHR{};
|
||||
accelerationStructureBuildGeometryInfoKHR.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
|
||||
return accelerationStructureBuildGeometryInfoKHR;
|
||||
}
|
||||
|
||||
inline VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfoKHR()
|
||||
{
|
||||
VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfoKHR{};
|
||||
accelerationStructureBuildSizesInfoKHR.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR;
|
||||
return accelerationStructureBuildSizesInfoKHR;
|
||||
}
|
||||
|
||||
inline VkRayTracingShaderGroupCreateInfoKHR rayTracingShaderGroupCreateInfoKHR()
|
||||
{
|
||||
VkRayTracingShaderGroupCreateInfoKHR rayTracingShaderGroupCreateInfoKHR{};
|
||||
rayTracingShaderGroupCreateInfoKHR.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
|
||||
return rayTracingShaderGroupCreateInfoKHR;
|
||||
}
|
||||
|
||||
inline VkRayTracingPipelineCreateInfoKHR rayTracingPipelineCreateInfoKHR()
|
||||
{
|
||||
VkRayTracingPipelineCreateInfoKHR rayTracingPipelineCreateInfoKHR{};
|
||||
rayTracingPipelineCreateInfoKHR.sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR;
|
||||
return rayTracingPipelineCreateInfoKHR;
|
||||
}
|
||||
|
||||
inline VkWriteDescriptorSetAccelerationStructureKHR writeDescriptorSetAccelerationStructureKHR()
|
||||
{
|
||||
VkWriteDescriptorSetAccelerationStructureKHR writeDescriptorSetAccelerationStructureKHR{};
|
||||
writeDescriptorSetAccelerationStructureKHR.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR;
|
||||
return writeDescriptorSetAccelerationStructureKHR;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
/*
|
||||
* Extended sample base class for ray tracing based samples
|
||||
*
|
||||
* Copyright (C) 2020-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#include "VulkanRaytracingSample.h"
|
||||
|
||||
void VulkanRaytracingSample::setupRenderPass()
|
||||
{
|
||||
// Update the default render pass with different color attachment load ops to keep attachment contents
|
||||
// With this change, we can e.g. draw an UI on top of the ray traced scene
|
||||
|
||||
vkDestroyRenderPass(device, renderPass, nullptr);
|
||||
|
||||
VkAttachmentLoadOp colorLoadOp{ VK_ATTACHMENT_LOAD_OP_LOAD };
|
||||
VkImageLayout colorInitialLayout{ VK_IMAGE_LAYOUT_PRESENT_SRC_KHR };
|
||||
|
||||
if (rayQueryOnly) {
|
||||
colorLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
colorInitialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
}
|
||||
|
||||
std::array<VkAttachmentDescription, 2> attachments{
|
||||
VkAttachmentDescription{
|
||||
.format = swapChain.colorFormat,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.loadOp = colorLoadOp,
|
||||
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||
.initialLayout = colorInitialLayout,
|
||||
.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
|
||||
},
|
||||
VkAttachmentDescription{
|
||||
.format = depthFormat,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
|
||||
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
|
||||
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
|
||||
}
|
||||
};
|
||||
|
||||
VkAttachmentReference colorReference{ .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
|
||||
VkAttachmentReference depthReference{ .attachment = 1,.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
|
||||
|
||||
VkSubpassDescription subpassDescription{
|
||||
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
.colorAttachmentCount = 1,
|
||||
.pColorAttachments = &colorReference,
|
||||
.pDepthStencilAttachment = &depthReference,
|
||||
};
|
||||
|
||||
std::array<VkSubpassDependency, 2> dependencies{
|
||||
VkSubpassDependency{
|
||||
.srcSubpass = VK_SUBPASS_EXTERNAL,
|
||||
.dstSubpass = 0,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
|
||||
.srcAccessMask = VK_ACCESS_NONE_KHR,
|
||||
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
||||
.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT
|
||||
},
|
||||
VkSubpassDependency{
|
||||
.srcSubpass = 0,
|
||||
.dstSubpass = VK_SUBPASS_EXTERNAL,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
||||
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
|
||||
.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT
|
||||
}
|
||||
};
|
||||
|
||||
VkRenderPassCreateInfo renderPassInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
||||
.attachmentCount = static_cast<uint32_t>(attachments.size()),
|
||||
.pAttachments = attachments.data(),
|
||||
.subpassCount = 1,
|
||||
.pSubpasses = &subpassDescription,
|
||||
.dependencyCount = static_cast<uint32_t>(dependencies.size()),
|
||||
.pDependencies = dependencies.data()
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass));
|
||||
}
|
||||
|
||||
void VulkanRaytracingSample::setupFrameBuffer()
|
||||
{
|
||||
|
||||
frameBuffers.resize(swapChain.images.size());
|
||||
for (uint32_t i = 0; i < frameBuffers.size(); i++) {
|
||||
VkImageView attachments[2] { swapChain.imageViews[i], depthStencil.view };
|
||||
VkFramebufferCreateInfo frameBufferCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||
.renderPass = renderPass,
|
||||
.attachmentCount = 2,
|
||||
.pAttachments = attachments,
|
||||
.width = width,
|
||||
.height = height,
|
||||
.layers = 1
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateFramebuffer(device, &frameBufferCreateInfo, nullptr, &frameBuffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRaytracingSample::enableExtensions()
|
||||
{
|
||||
// Require Vulkan 1.1
|
||||
apiVersion = VK_API_VERSION_1_1;
|
||||
|
||||
// Ray tracing related extensions required by this sample
|
||||
enabledDeviceExtensions.push_back(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME);
|
||||
if (!rayQueryOnly) {
|
||||
enabledDeviceExtensions.push_back(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
// Required by VK_KHR_acceleration_structure
|
||||
enabledDeviceExtensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
|
||||
enabledDeviceExtensions.push_back(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME);
|
||||
enabledDeviceExtensions.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
|
||||
|
||||
// Required for VK_KHR_ray_tracing_pipeline
|
||||
enabledDeviceExtensions.push_back(VK_KHR_SPIRV_1_4_EXTENSION_NAME);
|
||||
|
||||
// Required by VK_KHR_spirv_1_4
|
||||
enabledDeviceExtensions.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
|
||||
|
||||
// Format for the storage image is decided at runtime, so we can't explicilily state it in the shader
|
||||
enabledFeatures.shaderStorageImageWriteWithoutFormat = VK_TRUE;
|
||||
enabledFeatures.shaderStorageImageReadWithoutFormat = VK_TRUE;
|
||||
}
|
||||
|
||||
VulkanRaytracingSample::ScratchBuffer VulkanRaytracingSample::createScratchBuffer(VkDeviceSize size)
|
||||
{
|
||||
ScratchBuffer scratchBuffer{};
|
||||
VkBufferCreateInfo bufferCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.flags = 0,
|
||||
.size = size,
|
||||
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateBuffer(vulkanDevice->logicalDevice, &bufferCreateInfo, nullptr, &scratchBuffer.handle));
|
||||
VkMemoryRequirements memoryRequirements{};
|
||||
vkGetBufferMemoryRequirements(vulkanDevice->logicalDevice, scratchBuffer.handle, &memoryRequirements);
|
||||
VkMemoryAllocateFlagsInfo memoryAllocateFlagsInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO,
|
||||
.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR
|
||||
};
|
||||
VkMemoryAllocateInfo memoryAllocateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.pNext = &memoryAllocateFlagsInfo,
|
||||
.allocationSize = memoryRequirements.size,
|
||||
.memoryTypeIndex = vulkanDevice->getMemoryType(memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
|
||||
};
|
||||
VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &memoryAllocateInfo, nullptr, &scratchBuffer.memory));
|
||||
VK_CHECK_RESULT(vkBindBufferMemory(vulkanDevice->logicalDevice, scratchBuffer.handle, scratchBuffer.memory, 0));
|
||||
VkBufferDeviceAddressInfoKHR bufferDeviceAddresInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
|
||||
.buffer = scratchBuffer.handle
|
||||
};
|
||||
scratchBuffer.deviceAddress = vkGetBufferDeviceAddressKHR(vulkanDevice->logicalDevice, &bufferDeviceAddresInfo);
|
||||
return scratchBuffer;
|
||||
}
|
||||
|
||||
void VulkanRaytracingSample::deleteScratchBuffer(ScratchBuffer& scratchBuffer)
|
||||
{
|
||||
if (scratchBuffer.memory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(vulkanDevice->logicalDevice, scratchBuffer.memory, nullptr);
|
||||
}
|
||||
if (scratchBuffer.handle != VK_NULL_HANDLE) {
|
||||
vkDestroyBuffer(vulkanDevice->logicalDevice, scratchBuffer.handle, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRaytracingSample::createAccelerationStructure(AccelerationStructure& accelerationStructure, VkAccelerationStructureTypeKHR type, VkAccelerationStructureBuildSizesInfoKHR buildSizeInfo)
|
||||
{
|
||||
VkBufferCreateInfo bufferCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = buildSizeInfo.accelerationStructureSize,
|
||||
.usage = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateBuffer(vulkanDevice->logicalDevice, &bufferCreateInfo, nullptr, &accelerationStructure.buffer));
|
||||
VkMemoryRequirements memoryRequirements{};
|
||||
vkGetBufferMemoryRequirements(vulkanDevice->logicalDevice, accelerationStructure.buffer, &memoryRequirements);
|
||||
VkMemoryAllocateFlagsInfo memoryAllocateFlagsInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO,
|
||||
.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR
|
||||
};
|
||||
VkMemoryAllocateInfo memoryAllocateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.pNext = &memoryAllocateFlagsInfo,
|
||||
.allocationSize = memoryRequirements.size,
|
||||
.memoryTypeIndex = vulkanDevice->getMemoryType(memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
|
||||
};
|
||||
VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &memoryAllocateInfo, nullptr, &accelerationStructure.memory));
|
||||
VK_CHECK_RESULT(vkBindBufferMemory(vulkanDevice->logicalDevice, accelerationStructure.buffer, accelerationStructure.memory, 0));
|
||||
VkAccelerationStructureCreateInfoKHR accelerationStructureCreate_info{
|
||||
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR,
|
||||
.buffer = accelerationStructure.buffer,
|
||||
.size = buildSizeInfo.accelerationStructureSize,
|
||||
.type = type
|
||||
};
|
||||
vkCreateAccelerationStructureKHR(vulkanDevice->logicalDevice, &accelerationStructureCreate_info, nullptr, &accelerationStructure.handle);
|
||||
VkAccelerationStructureDeviceAddressInfoKHR accelerationDeviceAddressInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR,
|
||||
.accelerationStructure = accelerationStructure.handle
|
||||
};
|
||||
accelerationStructure.deviceAddress = vkGetAccelerationStructureDeviceAddressKHR(vulkanDevice->logicalDevice, &accelerationDeviceAddressInfo);
|
||||
}
|
||||
|
||||
void VulkanRaytracingSample::deleteAccelerationStructure(AccelerationStructure& accelerationStructure)
|
||||
{
|
||||
vkFreeMemory(device, accelerationStructure.memory, nullptr);
|
||||
vkDestroyBuffer(device, accelerationStructure.buffer, nullptr);
|
||||
vkDestroyAccelerationStructureKHR(device, accelerationStructure.handle, nullptr);
|
||||
}
|
||||
|
||||
uint64_t VulkanRaytracingSample::getBufferDeviceAddress(VkBuffer buffer)
|
||||
{
|
||||
VkBufferDeviceAddressInfoKHR bufferDeviceAI{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
|
||||
.buffer = buffer
|
||||
};
|
||||
return vkGetBufferDeviceAddressKHR(vulkanDevice->logicalDevice, &bufferDeviceAI);
|
||||
}
|
||||
|
||||
void VulkanRaytracingSample::createStorageImage(VkFormat format, VkExtent3D extent)
|
||||
{
|
||||
// Release ressources if image is to be recreated
|
||||
if (storageImage.image != VK_NULL_HANDLE) {
|
||||
vkDestroyImageView(device, storageImage.view, nullptr);
|
||||
vkDestroyImage(device, storageImage.image, nullptr);
|
||||
vkFreeMemory(device, storageImage.memory, nullptr);
|
||||
storageImage = {};
|
||||
}
|
||||
|
||||
VkImageCreateInfo image{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = format,
|
||||
.extent = extent,
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_STORAGE_BIT,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateImage(vulkanDevice->logicalDevice, &image, nullptr, &storageImage.image));
|
||||
|
||||
VkMemoryRequirements memReqs;
|
||||
vkGetImageMemoryRequirements(vulkanDevice->logicalDevice, storageImage.image, &memReqs);
|
||||
VkMemoryAllocateInfo memoryAllocateInfo = vks::initializers::memoryAllocateInfo();
|
||||
memoryAllocateInfo.allocationSize = memReqs.size;
|
||||
memoryAllocateInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &memoryAllocateInfo, nullptr, &storageImage.memory));
|
||||
VK_CHECK_RESULT(vkBindImageMemory(vulkanDevice->logicalDevice, storageImage.image, storageImage.memory, 0));
|
||||
|
||||
VkImageViewCreateInfo colorImageView{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = storageImage.image,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = format,
|
||||
.subresourceRange = {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
},
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateImageView(vulkanDevice->logicalDevice, &colorImageView, nullptr, &storageImage.view));
|
||||
|
||||
VkCommandBuffer cmdBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
vks::tools::setImageLayout(cmdBuffer, storageImage.image,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_GENERAL,
|
||||
{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
|
||||
vulkanDevice->flushCommandBuffer(cmdBuffer, queue);
|
||||
}
|
||||
|
||||
void VulkanRaytracingSample::deleteStorageImage()
|
||||
{
|
||||
vkDestroyImageView(vulkanDevice->logicalDevice, storageImage.view, nullptr);
|
||||
vkDestroyImage(vulkanDevice->logicalDevice, storageImage.image, nullptr);
|
||||
vkFreeMemory(vulkanDevice->logicalDevice, storageImage.memory, nullptr);
|
||||
}
|
||||
|
||||
void VulkanRaytracingSample::prepare()
|
||||
{
|
||||
VulkanExampleBase::prepare();
|
||||
// Get properties and features
|
||||
rayTracingPipelineProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR;
|
||||
VkPhysicalDeviceProperties2 deviceProperties2{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
|
||||
.pNext = &rayTracingPipelineProperties
|
||||
};
|
||||
vkGetPhysicalDeviceProperties2(physicalDevice, &deviceProperties2);
|
||||
accelerationStructureFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR;
|
||||
VkPhysicalDeviceFeatures2 deviceFeatures2{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
||||
.pNext = &accelerationStructureFeatures
|
||||
};
|
||||
vkGetPhysicalDeviceFeatures2(physicalDevice, &deviceFeatures2);
|
||||
// Get the function pointers required for ray tracing
|
||||
vkGetBufferDeviceAddressKHR = reinterpret_cast<PFN_vkGetBufferDeviceAddressKHR>(vkGetDeviceProcAddr(device, "vkGetBufferDeviceAddressKHR"));
|
||||
vkCmdBuildAccelerationStructuresKHR = reinterpret_cast<PFN_vkCmdBuildAccelerationStructuresKHR>(vkGetDeviceProcAddr(device, "vkCmdBuildAccelerationStructuresKHR"));
|
||||
vkBuildAccelerationStructuresKHR = reinterpret_cast<PFN_vkBuildAccelerationStructuresKHR>(vkGetDeviceProcAddr(device, "vkBuildAccelerationStructuresKHR"));
|
||||
vkCreateAccelerationStructureKHR = reinterpret_cast<PFN_vkCreateAccelerationStructureKHR>(vkGetDeviceProcAddr(device, "vkCreateAccelerationStructureKHR"));
|
||||
vkDestroyAccelerationStructureKHR = reinterpret_cast<PFN_vkDestroyAccelerationStructureKHR>(vkGetDeviceProcAddr(device, "vkDestroyAccelerationStructureKHR"));
|
||||
vkGetAccelerationStructureBuildSizesKHR = reinterpret_cast<PFN_vkGetAccelerationStructureBuildSizesKHR>(vkGetDeviceProcAddr(device, "vkGetAccelerationStructureBuildSizesKHR"));
|
||||
vkGetAccelerationStructureDeviceAddressKHR = reinterpret_cast<PFN_vkGetAccelerationStructureDeviceAddressKHR>(vkGetDeviceProcAddr(device, "vkGetAccelerationStructureDeviceAddressKHR"));
|
||||
vkCmdTraceRaysKHR = reinterpret_cast<PFN_vkCmdTraceRaysKHR>(vkGetDeviceProcAddr(device, "vkCmdTraceRaysKHR"));
|
||||
vkGetRayTracingShaderGroupHandlesKHR = reinterpret_cast<PFN_vkGetRayTracingShaderGroupHandlesKHR>(vkGetDeviceProcAddr(device, "vkGetRayTracingShaderGroupHandlesKHR"));
|
||||
vkCreateRayTracingPipelinesKHR = reinterpret_cast<PFN_vkCreateRayTracingPipelinesKHR>(vkGetDeviceProcAddr(device, "vkCreateRayTracingPipelinesKHR"));
|
||||
}
|
||||
|
||||
VkStridedDeviceAddressRegionKHR VulkanRaytracingSample::getSbtEntryStridedDeviceAddressRegion(VkBuffer buffer, uint32_t handleCount)
|
||||
{
|
||||
const uint32_t handleSizeAligned = vks::tools::alignedSize(rayTracingPipelineProperties.shaderGroupHandleSize, rayTracingPipelineProperties.shaderGroupHandleAlignment);
|
||||
VkStridedDeviceAddressRegionKHR stridedDeviceAddressRegionKHR{
|
||||
.deviceAddress = getBufferDeviceAddress(buffer),
|
||||
.stride = handleSizeAligned
|
||||
};
|
||||
stridedDeviceAddressRegionKHR.size = handleCount * handleSizeAligned;
|
||||
return stridedDeviceAddressRegionKHR;
|
||||
}
|
||||
|
||||
void VulkanRaytracingSample::createShaderBindingTable(ShaderBindingTable& shaderBindingTable, uint32_t handleCount)
|
||||
{
|
||||
// Create buffer to hold all shader handles for the SBT
|
||||
VK_CHECK_RESULT(vulkanDevice->createBuffer(
|
||||
VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
||||
&shaderBindingTable,
|
||||
rayTracingPipelineProperties.shaderGroupHandleSize * handleCount));
|
||||
// Get the strided address to be used when dispatching the rays
|
||||
shaderBindingTable.stridedDeviceAddressRegion = getSbtEntryStridedDeviceAddressRegion(shaderBindingTable.buffer, handleCount);
|
||||
// Map persistent
|
||||
shaderBindingTable.map();
|
||||
}
|
||||
|
||||
void VulkanRaytracingSample::drawUI(VkCommandBuffer commandBuffer, VkFramebuffer framebuffer)
|
||||
{
|
||||
VkClearValue clearValues[2]{};
|
||||
clearValues[0].color = defaultClearColor;
|
||||
clearValues[1].depthStencil = { 1.0f, 0 };
|
||||
|
||||
VkRenderPassBeginInfo renderPassBeginInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
.renderPass = renderPass,
|
||||
.framebuffer = framebuffer,
|
||||
.renderArea = {.offset = {.x = 0, .y = 0, }, .extent = {.width = width, .height = height } },
|
||||
.clearValueCount = 2,
|
||||
.pClearValues = clearValues
|
||||
};
|
||||
vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
|
||||
VulkanExampleBase::drawUI(commandBuffer);
|
||||
vkCmdEndRenderPass(commandBuffer);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Extended sample base class for ray tracing based samples
|
||||
*
|
||||
* Copyright (C) 2020-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "vulkan/vulkan.h"
|
||||
#include "vulkanexamplebase.h"
|
||||
#include "VulkanTools.h"
|
||||
#include "VulkanDevice.h"
|
||||
|
||||
// Base sample class with added features specific to hardware ray traced samples
|
||||
class VulkanRaytracingSample : public VulkanExampleBase
|
||||
{
|
||||
protected:
|
||||
// Update the default render pass with different color attachment load ops
|
||||
virtual void setupRenderPass();
|
||||
virtual void setupFrameBuffer();
|
||||
public:
|
||||
// Function pointers for ray tracing related stuff
|
||||
PFN_vkGetBufferDeviceAddressKHR vkGetBufferDeviceAddressKHR{ nullptr };
|
||||
PFN_vkCreateAccelerationStructureKHR vkCreateAccelerationStructureKHR{ nullptr };
|
||||
PFN_vkDestroyAccelerationStructureKHR vkDestroyAccelerationStructureKHR{ nullptr };
|
||||
PFN_vkGetAccelerationStructureBuildSizesKHR vkGetAccelerationStructureBuildSizesKHR{ nullptr };
|
||||
PFN_vkGetAccelerationStructureDeviceAddressKHR vkGetAccelerationStructureDeviceAddressKHR{ nullptr };
|
||||
PFN_vkBuildAccelerationStructuresKHR vkBuildAccelerationStructuresKHR{ nullptr };
|
||||
PFN_vkCmdBuildAccelerationStructuresKHR vkCmdBuildAccelerationStructuresKHR{ nullptr };
|
||||
PFN_vkCmdTraceRaysKHR vkCmdTraceRaysKHR{ nullptr };
|
||||
PFN_vkGetRayTracingShaderGroupHandlesKHR vkGetRayTracingShaderGroupHandlesKHR{ nullptr };
|
||||
PFN_vkCreateRayTracingPipelinesKHR vkCreateRayTracingPipelinesKHR{ nullptr };
|
||||
|
||||
// Available features and properties
|
||||
VkPhysicalDeviceRayTracingPipelinePropertiesKHR rayTracingPipelineProperties{};
|
||||
VkPhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeatures{};
|
||||
|
||||
// Enabled features and properties
|
||||
VkPhysicalDeviceBufferDeviceAddressFeatures enabledBufferDeviceAddresFeatures{};
|
||||
VkPhysicalDeviceRayTracingPipelineFeaturesKHR enabledRayTracingPipelineFeatures{};
|
||||
VkPhysicalDeviceAccelerationStructureFeaturesKHR enabledAccelerationStructureFeatures{};
|
||||
|
||||
// Holds information for a ray tracing scratch buffer that is used as a temporary storage
|
||||
struct ScratchBuffer
|
||||
{
|
||||
uint64_t deviceAddress{ 0 };
|
||||
VkBuffer handle{ VK_NULL_HANDLE };
|
||||
VkDeviceMemory memory{ VK_NULL_HANDLE };
|
||||
};
|
||||
|
||||
// Holds information for a ray tracing acceleration structure
|
||||
struct AccelerationStructure {
|
||||
VkAccelerationStructureKHR handle{ VK_NULL_HANDLE };
|
||||
uint64_t deviceAddress{ 0 };
|
||||
VkDeviceMemory memory{ VK_NULL_HANDLE };
|
||||
VkBuffer buffer{ VK_NULL_HANDLE };
|
||||
};
|
||||
|
||||
// Holds information for a storage image that the ray tracing shaders output to
|
||||
struct StorageImage {
|
||||
VkDeviceMemory memory{ VK_NULL_HANDLE };
|
||||
VkImage image{ VK_NULL_HANDLE };
|
||||
VkImageView view{ VK_NULL_HANDLE };
|
||||
VkFormat format;
|
||||
} storageImage;
|
||||
|
||||
// Extends the buffer class and holds information for a shader binding table
|
||||
class ShaderBindingTable : public vks::Buffer {
|
||||
public:
|
||||
VkStridedDeviceAddressRegionKHR stridedDeviceAddressRegion{};
|
||||
};
|
||||
|
||||
// Set to true, to denote that the sample only uses ray queries (changes extension and render pass handling)
|
||||
bool rayQueryOnly = false;
|
||||
|
||||
void enableExtensions();
|
||||
ScratchBuffer createScratchBuffer(VkDeviceSize size);
|
||||
void deleteScratchBuffer(ScratchBuffer& scratchBuffer);
|
||||
void createAccelerationStructure(AccelerationStructure& accelerationStructure, VkAccelerationStructureTypeKHR type, VkAccelerationStructureBuildSizesInfoKHR buildSizeInfo);
|
||||
void deleteAccelerationStructure(AccelerationStructure& accelerationStructure);
|
||||
uint64_t getBufferDeviceAddress(VkBuffer buffer);
|
||||
void createStorageImage(VkFormat format, VkExtent3D extent);
|
||||
void deleteStorageImage();
|
||||
VkStridedDeviceAddressRegionKHR getSbtEntryStridedDeviceAddressRegion(VkBuffer buffer, uint32_t handleCount);
|
||||
void createShaderBindingTable(ShaderBindingTable& shaderBindingTable, uint32_t handleCount);
|
||||
// Draw the ImGUI UI overlay using a render pass
|
||||
void drawUI(VkCommandBuffer commandBuffer, VkFramebuffer framebuffer);
|
||||
|
||||
virtual void prepare();
|
||||
};
|
||||
@@ -0,0 +1,515 @@
|
||||
/*
|
||||
* Class wrapping access to the swap chain
|
||||
*
|
||||
* A swap chain is a collection of framebuffers used for rendering and presentation to the windowing system
|
||||
*
|
||||
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#include "VulkanSwapChain.h"
|
||||
|
||||
/** @brief Creates the platform specific surface abstraction of the native platform window used for presentation */
|
||||
#if defined(VK_USE_PLATFORM_WIN32_KHR)
|
||||
void VulkanSwapChain::initSurface(void* platformHandle, void* platformWindow)
|
||||
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||
void VulkanSwapChain::initSurface(ANativeWindow* window)
|
||||
#elif defined(VK_USE_PLATFORM_OHOS)
|
||||
void VulkanSwapChain::initSurface(NativeWindow* window)
|
||||
#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
|
||||
void VulkanSwapChain::initSurface(IDirectFB* dfb, IDirectFBSurface* window)
|
||||
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
void VulkanSwapChain::initSurface(wl_display *display, wl_surface *window)
|
||||
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
||||
void VulkanSwapChain::initSurface(xcb_connection_t* connection, xcb_window_t window)
|
||||
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
|
||||
void VulkanSwapChain::initSurface(void* view)
|
||||
#elif defined(VK_USE_PLATFORM_METAL_EXT)
|
||||
void VulkanSwapChain::initSurface(CAMetalLayer* metalLayer)
|
||||
#elif (defined(_DIRECT2DISPLAY) || defined(VK_USE_PLATFORM_HEADLESS_EXT))
|
||||
void VulkanSwapChain::initSurface(uint32_t width, uint32_t height)
|
||||
#elif defined(VK_USE_PLATFORM_SCREEN_QNX)
|
||||
void VulkanSwapChain::initSurface(screen_context_t screen_context, screen_window_t screen_window)
|
||||
#endif
|
||||
{
|
||||
VkResult err = VK_SUCCESS;
|
||||
|
||||
// Create the os-specific surface
|
||||
#if defined(VK_USE_PLATFORM_WIN32_KHR)
|
||||
VkWin32SurfaceCreateInfoKHR surfaceCreateInfo{};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
|
||||
surfaceCreateInfo.hinstance = (HINSTANCE)platformHandle;
|
||||
surfaceCreateInfo.hwnd = (HWND)platformWindow;
|
||||
err = vkCreateWin32SurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||
VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
|
||||
surfaceCreateInfo.window = window;
|
||||
err = vkCreateAndroidSurfaceKHR(instance, &surfaceCreateInfo, NULL, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_OHOS)
|
||||
VkSurfaceCreateInfoOHOS surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_SURFACE_CREATE_INFO_OHOS;
|
||||
surfaceCreateInfo.pNext = NULL;
|
||||
surfaceCreateInfo.flags = 0;
|
||||
surfaceCreateInfo.window = window;
|
||||
err = vkCreateSurfaceOHOS(instance, &surfaceCreateInfo, NULL, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_IOS_MVK)
|
||||
VkIOSSurfaceCreateInfoMVK surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
|
||||
surfaceCreateInfo.pNext = NULL;
|
||||
surfaceCreateInfo.flags = 0;
|
||||
surfaceCreateInfo.pView = view;
|
||||
err = vkCreateIOSSurfaceMVK(instance, &surfaceCreateInfo, nullptr, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_MACOS_MVK)
|
||||
VkMacOSSurfaceCreateInfoMVK surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
|
||||
surfaceCreateInfo.pNext = NULL;
|
||||
surfaceCreateInfo.flags = 0;
|
||||
surfaceCreateInfo.pView = view;
|
||||
err = vkCreateMacOSSurfaceMVK(instance, &surfaceCreateInfo, NULL, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_METAL_EXT)
|
||||
VkMetalSurfaceCreateInfoEXT surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
|
||||
surfaceCreateInfo.pNext = NULL;
|
||||
surfaceCreateInfo.flags = 0;
|
||||
surfaceCreateInfo.pLayer = metalLayer;
|
||||
err = vkCreateMetalSurfaceEXT(instance, &surfaceCreateInfo, NULL, &surface);
|
||||
#elif defined(_DIRECT2DISPLAY)
|
||||
createDirect2DisplaySurface(width, height);
|
||||
#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
|
||||
VkDirectFBSurfaceCreateInfoEXT surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_DIRECTFB_SURFACE_CREATE_INFO_EXT;
|
||||
surfaceCreateInfo.dfb = dfb;
|
||||
surfaceCreateInfo.surface = window;
|
||||
err = vkCreateDirectFBSurfaceEXT(instance, &surfaceCreateInfo, nullptr, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
VkWaylandSurfaceCreateInfoKHR surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
|
||||
surfaceCreateInfo.display = display;
|
||||
surfaceCreateInfo.surface = window;
|
||||
err = vkCreateWaylandSurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
||||
VkXcbSurfaceCreateInfoKHR surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
|
||||
surfaceCreateInfo.connection = connection;
|
||||
surfaceCreateInfo.window = window;
|
||||
err = vkCreateXcbSurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_HEADLESS_EXT)
|
||||
VkHeadlessSurfaceCreateInfoEXT surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT;
|
||||
PFN_vkCreateHeadlessSurfaceEXT fpCreateHeadlessSurfaceEXT = (PFN_vkCreateHeadlessSurfaceEXT)vkGetInstanceProcAddr(instance, "vkCreateHeadlessSurfaceEXT");
|
||||
if (!fpCreateHeadlessSurfaceEXT){
|
||||
vks::tools::exitFatal("Could not fetch function pointer for the headless extension!", -1);
|
||||
}
|
||||
err = fpCreateHeadlessSurfaceEXT(instance, &surfaceCreateInfo, nullptr, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_SCREEN_QNX)
|
||||
VkScreenSurfaceCreateInfoQNX surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_SCREEN_SURFACE_CREATE_INFO_QNX;
|
||||
surfaceCreateInfo.pNext = NULL;
|
||||
surfaceCreateInfo.flags = 0;
|
||||
surfaceCreateInfo.context = screen_context;
|
||||
surfaceCreateInfo.window = screen_window;
|
||||
err = vkCreateScreenSurfaceQNX(instance, &surfaceCreateInfo, NULL, &surface);
|
||||
#endif
|
||||
|
||||
if (err != VK_SUCCESS) {
|
||||
vks::tools::exitFatal("Could not create surface!", err);
|
||||
}
|
||||
|
||||
// Get available queue family properties
|
||||
uint32_t queueCount;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, nullptr);
|
||||
assert(queueCount >= 1);
|
||||
std::vector<VkQueueFamilyProperties> queueProps(queueCount);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, queueProps.data());
|
||||
|
||||
// Iterate over each queue to learn whether it supports presenting:
|
||||
// Find a queue with present support
|
||||
// Will be used to present the swap chain images to the windowing system
|
||||
std::vector<VkBool32> supportsPresent(queueCount);
|
||||
for (uint32_t i = 0; i < queueCount; i++)
|
||||
{
|
||||
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface, &supportsPresent[i]);
|
||||
}
|
||||
|
||||
// Search for a graphics and a present queue in the array of queue
|
||||
// families, try to find one that supports both
|
||||
uint32_t graphicsQueueNodeIndex = UINT32_MAX;
|
||||
uint32_t presentQueueNodeIndex = UINT32_MAX;
|
||||
for (uint32_t i = 0; i < queueCount; i++) {
|
||||
if ((queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
|
||||
if (graphicsQueueNodeIndex == UINT32_MAX) {
|
||||
graphicsQueueNodeIndex = i;
|
||||
}
|
||||
if (supportsPresent[i] == VK_TRUE) {
|
||||
graphicsQueueNodeIndex = i;
|
||||
presentQueueNodeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (presentQueueNodeIndex == UINT32_MAX)
|
||||
{
|
||||
// If there's no queue that supports both present and graphics
|
||||
// try to find a separate present queue
|
||||
for (uint32_t i = 0; i < queueCount; ++i) {
|
||||
if (supportsPresent[i] == VK_TRUE) {
|
||||
presentQueueNodeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit if either a graphics or a presenting queue hasn't been found
|
||||
if (graphicsQueueNodeIndex == UINT32_MAX || presentQueueNodeIndex == UINT32_MAX) {
|
||||
vks::tools::exitFatal("Could not find a graphics and/or presenting queue!", -1);
|
||||
}
|
||||
if (graphicsQueueNodeIndex != presentQueueNodeIndex) {
|
||||
vks::tools::exitFatal("Separate graphics and presenting queues are not supported yet!", -1);
|
||||
}
|
||||
queueNodeIndex = graphicsQueueNodeIndex;
|
||||
|
||||
// Get list of supported surface formats
|
||||
uint32_t formatCount;
|
||||
VK_CHECK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, NULL));
|
||||
assert(formatCount > 0);
|
||||
std::vector<VkSurfaceFormatKHR> surfaceFormats(formatCount);
|
||||
VK_CHECK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, surfaceFormats.data()));
|
||||
|
||||
// We want to get a format that best suits our needs, so we try to get one from a set of preferred formats
|
||||
// Initialize the format to the first one returned by the implementation in case we can't find one of the preffered formats
|
||||
VkSurfaceFormatKHR selectedFormat = surfaceFormats[0];
|
||||
std::vector<VkFormat> preferredImageFormats = {
|
||||
VK_FORMAT_B8G8R8A8_UNORM,
|
||||
VK_FORMAT_R8G8B8A8_UNORM,
|
||||
VK_FORMAT_A8B8G8R8_UNORM_PACK32
|
||||
};
|
||||
for (auto& availableFormat : surfaceFormats) {
|
||||
if (std::find(preferredImageFormats.begin(), preferredImageFormats.end(), availableFormat.format) != preferredImageFormats.end()) {
|
||||
selectedFormat = availableFormat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
colorFormat = selectedFormat.format;
|
||||
colorSpace = selectedFormat.colorSpace;
|
||||
}
|
||||
|
||||
void VulkanSwapChain::setContext(VkInstance instance, VkPhysicalDevice physicalDevice, VkDevice device)
|
||||
{
|
||||
this->instance = instance;
|
||||
this->physicalDevice = physicalDevice;
|
||||
this->device = device;
|
||||
}
|
||||
|
||||
void VulkanSwapChain::create(uint32_t& width, uint32_t& height, bool vsync, bool fullscreen)
|
||||
{
|
||||
assert(physicalDevice);
|
||||
assert(device);
|
||||
assert(instance);
|
||||
|
||||
// Store the current swap chain handle so we can use it later on to ease up recreation
|
||||
VkSwapchainKHR oldSwapchain = swapChain;
|
||||
|
||||
// Get physical device surface properties and formats
|
||||
VkSurfaceCapabilitiesKHR surfaceCaps;
|
||||
VK_CHECK_RESULT(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &surfaceCaps));
|
||||
|
||||
VkExtent2D swapchainExtent = {};
|
||||
// If width (and height) equals the special value 0xFFFFFFFF, the size of the surface will be set by the swapchain
|
||||
if (surfaceCaps.currentExtent.width == (uint32_t)-1) {
|
||||
// If the surface size is undefined, the size is set to the size of the images requested
|
||||
swapchainExtent.width = width;
|
||||
swapchainExtent.height = height;
|
||||
} else {
|
||||
// If the surface size is defined, the swap chain size must match
|
||||
swapchainExtent = surfaceCaps.currentExtent;
|
||||
width = surfaceCaps.currentExtent.width;
|
||||
height = surfaceCaps.currentExtent.height;
|
||||
}
|
||||
|
||||
|
||||
// Select a present mode for the swapchain
|
||||
uint32_t presentModeCount;
|
||||
VK_CHECK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, nullptr));
|
||||
assert(presentModeCount > 0);
|
||||
|
||||
std::vector<VkPresentModeKHR> presentModes(presentModeCount);
|
||||
VK_CHECK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, presentModes.data()));
|
||||
|
||||
// The VK_PRESENT_MODE_FIFO_KHR mode must always be present as per spec
|
||||
// This mode waits for the vertical blank ("v-sync")
|
||||
VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR;
|
||||
|
||||
// If v-sync is not requested, try to find a mailbox mode
|
||||
// It's the lowest latency non-tearing present mode available
|
||||
if (!vsync)
|
||||
{
|
||||
for (size_t i = 0; i < presentModeCount; i++)
|
||||
{
|
||||
if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR)
|
||||
{
|
||||
swapchainPresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
|
||||
break;
|
||||
}
|
||||
if (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||||
{
|
||||
swapchainPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the number of images
|
||||
uint32_t desiredNumberOfSwapchainImages = surfaceCaps.minImageCount + 1;
|
||||
if ((surfaceCaps.maxImageCount > 0) && (desiredNumberOfSwapchainImages > surfaceCaps.maxImageCount)) {
|
||||
desiredNumberOfSwapchainImages = surfaceCaps.maxImageCount;
|
||||
}
|
||||
|
||||
// Find the transformation of the surface
|
||||
VkSurfaceTransformFlagsKHR preTransform;
|
||||
if (surfaceCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
|
||||
// We prefer a non-rotated transform
|
||||
preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||
} else {
|
||||
preTransform = surfaceCaps.currentTransform;
|
||||
}
|
||||
|
||||
// Find a supported composite alpha format (not all devices support alpha opaque)
|
||||
VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
// Simply select the first composite alpha format available
|
||||
std::vector<VkCompositeAlphaFlagBitsKHR> compositeAlphaFlags = {
|
||||
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
|
||||
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
|
||||
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
|
||||
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
|
||||
};
|
||||
for (auto& compositeAlphaFlag : compositeAlphaFlags) {
|
||||
if (surfaceCaps.supportedCompositeAlpha & compositeAlphaFlag) {
|
||||
compositeAlpha = compositeAlphaFlag;
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
VkSwapchainCreateInfoKHR swapchainCI{
|
||||
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
||||
.surface = surface,
|
||||
.minImageCount = desiredNumberOfSwapchainImages,
|
||||
.imageFormat = colorFormat,
|
||||
.imageColorSpace = colorSpace,
|
||||
.imageExtent = { swapchainExtent.width, swapchainExtent.height },
|
||||
.imageArrayLayers = 1,
|
||||
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
|
||||
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.preTransform = (VkSurfaceTransformFlagBitsKHR)preTransform,
|
||||
.compositeAlpha = compositeAlpha,
|
||||
.presentMode = swapchainPresentMode,
|
||||
// Setting clipped to VK_TRUE allows the implementation to discard rendering outside of the surface area
|
||||
.clipped = VK_TRUE,
|
||||
// Setting oldSwapChain to the saved handle of the previous swapchain aids in resource reuse and makes sure that we can still present already acquired images
|
||||
.oldSwapchain = oldSwapchain,
|
||||
};
|
||||
// Enable transfer source on swap chain images if supported
|
||||
if (surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) {
|
||||
swapchainCI.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
}
|
||||
// Enable transfer destination on swap chain images if supported
|
||||
if (surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) {
|
||||
swapchainCI.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
VK_CHECK_RESULT(vkCreateSwapchainKHR(device, &swapchainCI, nullptr, &swapChain));
|
||||
|
||||
// If an existing swap chain is re-created, destroy the old swap chain and the ressources owned by the application (image views, images are owned by the swap chain)
|
||||
if (oldSwapchain != VK_NULL_HANDLE) {
|
||||
for (auto i = 0; i < images.size(); i++) {
|
||||
vkDestroyImageView(device, imageViews[i], nullptr);
|
||||
}
|
||||
vkDestroySwapchainKHR(device, oldSwapchain, nullptr);
|
||||
}
|
||||
// Get the (new) swap chain images
|
||||
VK_CHECK_RESULT(vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr));
|
||||
images.resize(imageCount);
|
||||
VK_CHECK_RESULT(vkGetSwapchainImagesKHR(device, swapChain, &imageCount, images.data()));
|
||||
|
||||
// Get the swap chain buffers containing the image and imageview
|
||||
imageViews.resize(imageCount);
|
||||
for (auto i = 0; i < images.size(); i++)
|
||||
{
|
||||
VkImageViewCreateInfo colorAttachmentView{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = images[i],
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = colorFormat,
|
||||
.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A },
|
||||
.subresourceRange = {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
},
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateImageView(device, &colorAttachmentView, nullptr, &imageViews[i]));
|
||||
}
|
||||
}
|
||||
|
||||
VkResult VulkanSwapChain::acquireNextImage(VkSemaphore presentCompleteSemaphore, uint32_t& imageIndex)
|
||||
{
|
||||
// By setting timeout to UINT64_MAX we will always wait until the next image has been acquired or an actual error is thrown
|
||||
// With that we don't have to handle VK_NOT_READY
|
||||
return vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, presentCompleteSemaphore, (VkFence)nullptr, &imageIndex);
|
||||
}
|
||||
void VulkanSwapChain::cleanup()
|
||||
{
|
||||
if (swapChain != VK_NULL_HANDLE) {
|
||||
for (auto i = 0; i < images.size(); i++) {
|
||||
vkDestroyImageView(device, imageViews[i], nullptr);
|
||||
}
|
||||
vkDestroySwapchainKHR(device, swapChain, nullptr);
|
||||
}
|
||||
if (surface != VK_NULL_HANDLE) {
|
||||
vkDestroySurfaceKHR(instance, surface, nullptr);
|
||||
}
|
||||
surface = VK_NULL_HANDLE;
|
||||
swapChain = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
#if defined(_DIRECT2DISPLAY)
|
||||
/**
|
||||
* Create direct to display surface
|
||||
*/
|
||||
void VulkanSwapChain::createDirect2DisplaySurface(uint32_t width, uint32_t height)
|
||||
{
|
||||
uint32_t displayPropertyCount;
|
||||
|
||||
// Get display property
|
||||
vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayPropertyCount, NULL);
|
||||
VkDisplayPropertiesKHR* pDisplayProperties = new VkDisplayPropertiesKHR[displayPropertyCount];
|
||||
vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayPropertyCount, pDisplayProperties);
|
||||
|
||||
// Get plane property
|
||||
uint32_t planePropertyCount;
|
||||
vkGetPhysicalDeviceDisplayPlanePropertiesKHR(physicalDevice, &planePropertyCount, NULL);
|
||||
VkDisplayPlanePropertiesKHR* pPlaneProperties = new VkDisplayPlanePropertiesKHR[planePropertyCount];
|
||||
vkGetPhysicalDeviceDisplayPlanePropertiesKHR(physicalDevice, &planePropertyCount, pPlaneProperties);
|
||||
|
||||
VkDisplayKHR display = VK_NULL_HANDLE;
|
||||
VkDisplayModeKHR displayMode;
|
||||
VkDisplayModePropertiesKHR* pModeProperties;
|
||||
bool foundMode = false;
|
||||
|
||||
for(uint32_t i = 0; i < displayPropertyCount;++i)
|
||||
{
|
||||
display = pDisplayProperties[i].display;
|
||||
uint32_t modeCount;
|
||||
vkGetDisplayModePropertiesKHR(physicalDevice, display, &modeCount, NULL);
|
||||
pModeProperties = new VkDisplayModePropertiesKHR[modeCount];
|
||||
vkGetDisplayModePropertiesKHR(physicalDevice, display, &modeCount, pModeProperties);
|
||||
|
||||
for (uint32_t j = 0; j < modeCount; ++j)
|
||||
{
|
||||
const VkDisplayModePropertiesKHR* mode = &pModeProperties[j];
|
||||
|
||||
if (mode->parameters.visibleRegion.width == width && mode->parameters.visibleRegion.height == height)
|
||||
{
|
||||
displayMode = mode->displayMode;
|
||||
foundMode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundMode)
|
||||
{
|
||||
break;
|
||||
}
|
||||
delete [] pModeProperties;
|
||||
}
|
||||
|
||||
if(!foundMode)
|
||||
{
|
||||
vks::tools::exitFatal("Can't find a display and a display mode!", -1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for a best plane we can use
|
||||
uint32_t bestPlaneIndex = UINT32_MAX;
|
||||
VkDisplayKHR* pDisplays = NULL;
|
||||
for(uint32_t i = 0; i < planePropertyCount; i++)
|
||||
{
|
||||
uint32_t planeIndex=i;
|
||||
uint32_t displayCount;
|
||||
vkGetDisplayPlaneSupportedDisplaysKHR(physicalDevice, planeIndex, &displayCount, NULL);
|
||||
if (pDisplays)
|
||||
{
|
||||
delete [] pDisplays;
|
||||
}
|
||||
pDisplays = new VkDisplayKHR[displayCount];
|
||||
vkGetDisplayPlaneSupportedDisplaysKHR(physicalDevice, planeIndex, &displayCount, pDisplays);
|
||||
|
||||
// Find a display that matches the current plane
|
||||
bestPlaneIndex = UINT32_MAX;
|
||||
for(uint32_t j = 0; j < displayCount; j++)
|
||||
{
|
||||
if(display == pDisplays[j])
|
||||
{
|
||||
bestPlaneIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(bestPlaneIndex != UINT32_MAX)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(bestPlaneIndex == UINT32_MAX)
|
||||
{
|
||||
vks::tools::exitFatal("Can't find a plane for displaying!", -1);
|
||||
return;
|
||||
}
|
||||
|
||||
VkDisplayPlaneCapabilitiesKHR planeCap;
|
||||
vkGetDisplayPlaneCapabilitiesKHR(physicalDevice, displayMode, bestPlaneIndex, &planeCap);
|
||||
VkDisplayPlaneAlphaFlagBitsKHR alphaMode = (VkDisplayPlaneAlphaFlagBitsKHR)0;
|
||||
|
||||
if (planeCap.supportedAlpha & VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR)
|
||||
{
|
||||
alphaMode = VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR;
|
||||
}
|
||||
else if (planeCap.supportedAlpha & VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR)
|
||||
{
|
||||
alphaMode = VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR;
|
||||
}
|
||||
else if (planeCap.supportedAlpha & VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR)
|
||||
{
|
||||
alphaMode = VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR;
|
||||
}
|
||||
else if (planeCap.supportedAlpha & VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR)
|
||||
{
|
||||
alphaMode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR;
|
||||
}
|
||||
|
||||
VkDisplaySurfaceCreateInfoKHR surfaceInfo{};
|
||||
surfaceInfo.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR;
|
||||
surfaceInfo.pNext = NULL;
|
||||
surfaceInfo.flags = 0;
|
||||
surfaceInfo.displayMode = displayMode;
|
||||
surfaceInfo.planeIndex = bestPlaneIndex;
|
||||
surfaceInfo.planeStackIndex = pPlaneProperties[bestPlaneIndex].currentStackIndex;
|
||||
surfaceInfo.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||
surfaceInfo.globalAlpha = 1.0;
|
||||
surfaceInfo.alphaMode = alphaMode;
|
||||
surfaceInfo.imageExtent.width = width;
|
||||
surfaceInfo.imageExtent.height = height;
|
||||
|
||||
VkResult result = vkCreateDisplayPlaneSurfaceKHR(instance, &surfaceInfo, NULL, &surface);
|
||||
if (result !=VK_SUCCESS) {
|
||||
vks::tools::exitFatal("Failed to create surface!", result);
|
||||
}
|
||||
|
||||
delete[] pDisplays;
|
||||
delete[] pModeProperties;
|
||||
delete[] pDisplayProperties;
|
||||
delete[] pPlaneProperties;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Class wrapping access to the swap chain
|
||||
*
|
||||
* A swap chain is a collection of framebuffers used for rendering and presentation to the windowing system
|
||||
*
|
||||
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#include "VulkanTools.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include "VulkanAndroid.h"
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <sys/utsname.h>
|
||||
#endif
|
||||
|
||||
class VulkanSwapChain
|
||||
{
|
||||
private:
|
||||
VkInstance instance{ VK_NULL_HANDLE };
|
||||
VkDevice device{ VK_NULL_HANDLE };
|
||||
VkPhysicalDevice physicalDevice{ VK_NULL_HANDLE };
|
||||
VkSurfaceKHR surface{ VK_NULL_HANDLE };
|
||||
public:
|
||||
VkFormat colorFormat{};
|
||||
VkColorSpaceKHR colorSpace{};
|
||||
VkSwapchainKHR swapChain{ VK_NULL_HANDLE };
|
||||
std::vector<VkImage> images{};
|
||||
std::vector<VkImageView> imageViews{};
|
||||
uint32_t queueNodeIndex{ UINT32_MAX };
|
||||
uint32_t imageCount{ 0 };
|
||||
|
||||
#if defined(VK_USE_PLATFORM_WIN32_KHR)
|
||||
void initSurface(void* platformHandle, void* platformWindow);
|
||||
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||
void initSurface(ANativeWindow* window);
|
||||
#elif defined (VK_USE_PLATFORM_OHOS)
|
||||
void initSurface(NativeWindow* window);
|
||||
#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
|
||||
void initSurface(IDirectFB* dfb, IDirectFBSurface* window);
|
||||
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
void initSurface(wl_display* display, wl_surface* window);
|
||||
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
||||
void initSurface(xcb_connection_t* connection, xcb_window_t window);
|
||||
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
|
||||
void initSurface(void* view);
|
||||
#elif defined(VK_USE_PLATFORM_METAL_EXT)
|
||||
void initSurface(CAMetalLayer* metalLayer);
|
||||
#elif (defined(_DIRECT2DISPLAY) || defined(VK_USE_PLATFORM_HEADLESS_EXT))
|
||||
void initSurface(uint32_t width, uint32_t height);
|
||||
#if defined(_DIRECT2DISPLAY)
|
||||
void createDirect2DisplaySurface(uint32_t width, uint32_t height);
|
||||
#endif
|
||||
#elif defined(VK_USE_PLATFORM_SCREEN_QNX)
|
||||
void initSurface(screen_context_t screen_context, screen_window_t screen_window);
|
||||
#endif
|
||||
/* Set the Vulkan objects required for swapchain creation and management, must be called before swapchain creation */
|
||||
void setContext(VkInstance instance, VkPhysicalDevice physicalDevice, VkDevice device);
|
||||
/**
|
||||
* Create the swapchain and get its images with given width and height
|
||||
*
|
||||
* @param width Pointer to the width of the swapchain (may be adjusted to fit the requirements of the swapchain)
|
||||
* @param height Pointer to the height of the swapchain (may be adjusted to fit the requirements of the swapchain)
|
||||
* @param vsync (Optional, default = false) Can be used to force vsync-ed rendering (by using VK_PRESENT_MODE_FIFO_KHR as presentation mode)
|
||||
*/
|
||||
void create(uint32_t& width, uint32_t& height, bool vsync = false, bool fullscreen = false);
|
||||
/**
|
||||
* Acquires the next image in the swap chain
|
||||
*
|
||||
* @param presentCompleteSemaphore (Optional) Semaphore that is signaled when the image is ready for use
|
||||
* @param imageIndex Pointer to the image index that will be increased if the next image could be acquired
|
||||
*
|
||||
* @note The function will always wait until the next image has been acquired by setting timeout to UINT64_MAX
|
||||
*
|
||||
* @return VkResult of the image acquisition
|
||||
*/
|
||||
VkResult acquireNextImage(VkSemaphore presentCompleteSemaphore, uint32_t& imageIndex);
|
||||
/* Free all Vulkan resources acquired by the swapchain */
|
||||
void cleanup();
|
||||
};
|
||||
@@ -0,0 +1,723 @@
|
||||
/*
|
||||
* Vulkan texture loader for KTX files
|
||||
*
|
||||
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license(MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#include <VulkanTexture.h>
|
||||
|
||||
namespace vks
|
||||
{
|
||||
void Texture::updateDescriptor()
|
||||
{
|
||||
descriptor.sampler = sampler;
|
||||
descriptor.imageView = view;
|
||||
descriptor.imageLayout = imageLayout;
|
||||
}
|
||||
|
||||
void Texture::destroy()
|
||||
{
|
||||
vkDestroyImageView(device->logicalDevice, view, nullptr);
|
||||
vkDestroyImage(device->logicalDevice, image, nullptr);
|
||||
if (sampler)
|
||||
{
|
||||
vkDestroySampler(device->logicalDevice, sampler, nullptr);
|
||||
}
|
||||
vkFreeMemory(device->logicalDevice, deviceMemory, nullptr);
|
||||
}
|
||||
|
||||
ktxResult Texture::loadKTXFile(std::string filename, ktxTexture **target)
|
||||
{
|
||||
ktxResult result = KTX_SUCCESS;
|
||||
#if defined(__ANDROID__)
|
||||
AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING);
|
||||
if (!asset) {
|
||||
vks::tools::exitFatal("Could not load texture from " + filename + "\n\nMake sure the assets submodule has been checked out and is up-to-date.", -1);
|
||||
}
|
||||
size_t size = AAsset_getLength(asset);
|
||||
assert(size > 0);
|
||||
ktx_uint8_t *textureData = new ktx_uint8_t[size];
|
||||
AAsset_read(asset, textureData, size);
|
||||
AAsset_close(asset);
|
||||
result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, target);
|
||||
delete[] textureData;
|
||||
#elif defined (__OHOS__)
|
||||
// Load texture from HarmonyOS raw resources using ResourceManager singleton
|
||||
NativeResourceManager* resourceMgr = ResourceManager::getInstance().getNativeResourceManager();
|
||||
if (!resourceMgr) {
|
||||
vks::tools::exitFatal("ResourceManager not initialized. Call ResourceManager::getInstance().initialize() first.", -1);
|
||||
}
|
||||
|
||||
RawFile* rawFile = OH_ResourceManager_OpenRawFile(resourceMgr, filename.c_str());
|
||||
if (!rawFile) {
|
||||
vks::tools::exitFatal("Could not load texture from " + filename + "\n\nMake sure the assets submodule has been checked out and is up-to-date.", -1);
|
||||
}
|
||||
size_t size = OH_ResourceManager_GetRawFileSize(rawFile);
|
||||
assert(size > 0);
|
||||
|
||||
ktx_uint8_t *textureData = new ktx_uint8_t[size];
|
||||
OH_ResourceManager_ReadRawFile(rawFile, textureData, size);
|
||||
OH_ResourceManager_CloseRawFile(rawFile);
|
||||
result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, target);
|
||||
delete[] textureData;
|
||||
#else
|
||||
if (!vks::tools::fileExists(filename)) {
|
||||
vks::tools::exitFatal("Could not load texture from " + filename + "\n\nMake sure the assets submodule has been checked out and is up-to-date.", -1);
|
||||
}
|
||||
result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, target);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a 2D texture including all mip levels
|
||||
*
|
||||
* @param filename File to load (supports .ktx)
|
||||
* @param format Vulkan format of the image data stored in the file
|
||||
* @param device Vulkan device to create the texture on
|
||||
* @param copyQueue Queue used for the texture staging copy commands (must support transfer)
|
||||
* @param (Optional) imageUsageFlags Usage flags for the texture's image (defaults to VK_IMAGE_USAGE_SAMPLED_BIT)
|
||||
* @param (Optional) imageLayout Usage layout for the texture (defaults VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
*
|
||||
*/
|
||||
void Texture2D::loadFromFile(std::string filename, VkFormat format, vks::VulkanDevice *device, VkQueue copyQueue, VkImageUsageFlags imageUsageFlags, VkImageLayout imageLayout)
|
||||
{
|
||||
ktxTexture* ktxTexture;
|
||||
ktxResult result = loadKTXFile(filename, &ktxTexture);
|
||||
assert(result == KTX_SUCCESS);
|
||||
|
||||
this->device = device;
|
||||
width = ktxTexture->baseWidth;
|
||||
height = ktxTexture->baseHeight;
|
||||
mipLevels = ktxTexture->numLevels;
|
||||
this->format = format;
|
||||
|
||||
ktx_uint8_t *ktxTextureData = ktxTexture_GetData(ktxTexture);
|
||||
ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture);
|
||||
|
||||
// Get device properties for the requested texture format
|
||||
VkFormatProperties formatProperties;
|
||||
vkGetPhysicalDeviceFormatProperties(device->physicalDevice, format, &formatProperties);
|
||||
|
||||
// Use a separate command buffer for texture loading
|
||||
VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
|
||||
// Create a host-visible staging buffer that contains the raw image data
|
||||
VkBuffer stagingBuffer;
|
||||
VkDeviceMemory stagingMemory;
|
||||
|
||||
VkBufferCreateInfo bufferCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = ktxTextureSize,
|
||||
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer));
|
||||
|
||||
// Get memory requirements for the staging buffer (alignment, memory type bits)
|
||||
VkMemoryRequirements memReqs;
|
||||
vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs);
|
||||
VkMemoryAllocateInfo memAllocInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.allocationSize = memReqs.size,
|
||||
// Get memory type index for a host visible buffer
|
||||
.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
|
||||
};
|
||||
VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory));
|
||||
VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0));
|
||||
|
||||
// Copy texture data into staging buffer
|
||||
uint8_t* data{ nullptr };
|
||||
VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void **)&data));
|
||||
memcpy(data, ktxTextureData, ktxTextureSize);
|
||||
vkUnmapMemory(device->logicalDevice, stagingMemory);
|
||||
|
||||
// Setup buffer copy regions for each mip level
|
||||
std::vector<VkBufferImageCopy> bufferCopyRegions;
|
||||
|
||||
for (uint32_t i = 0; i < mipLevels; i++) {
|
||||
ktx_size_t offset;
|
||||
KTX_error_code result = ktxTexture_GetImageOffset(ktxTexture, i, 0, 0, &offset);
|
||||
assert(result == KTX_SUCCESS);
|
||||
VkBufferImageCopy bufferCopyRegion{
|
||||
.bufferOffset = offset,
|
||||
.imageSubresource = {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.mipLevel = i,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
.imageExtent = {
|
||||
.width = std::max(1u, ktxTexture->baseWidth >> i),
|
||||
.height = std::max(1u, ktxTexture->baseHeight >> i),
|
||||
.depth = 1
|
||||
}
|
||||
};
|
||||
bufferCopyRegions.push_back(bufferCopyRegion);
|
||||
}
|
||||
|
||||
// Create optimal tiled target image
|
||||
VkImageCreateInfo imageCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = format,
|
||||
.extent = {.width = width, .height = height, .depth = 1 },
|
||||
.mipLevels = mipLevels,
|
||||
.arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = imageUsageFlags,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
};
|
||||
// Ensure that the TRANSFER_DST bit is set for staging
|
||||
if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT)) {
|
||||
imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &image));
|
||||
vkGetImageMemoryRequirements(device->logicalDevice, image, &memReqs);
|
||||
memAllocInfo.allocationSize = memReqs.size;
|
||||
memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &deviceMemory));
|
||||
VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, image, deviceMemory, 0));
|
||||
|
||||
VkImageSubresourceRange subresourceRange{ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = mipLevels, .layerCount = 1, };
|
||||
|
||||
// Image barrier for optimal image (target)
|
||||
// Optimal image will be used as destination for the copy
|
||||
vks::tools::setImageLayout(
|
||||
copyCmd,
|
||||
image,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
subresourceRange);
|
||||
|
||||
// Copy mip levels from staging buffer
|
||||
vkCmdCopyBufferToImage(
|
||||
copyCmd,
|
||||
stagingBuffer,
|
||||
image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
static_cast<uint32_t>(bufferCopyRegions.size()),
|
||||
bufferCopyRegions.data()
|
||||
);
|
||||
|
||||
// Change texture image layout to shader read after all mip levels have been copied
|
||||
this->imageLayout = imageLayout;
|
||||
vks::tools::setImageLayout(
|
||||
copyCmd,
|
||||
image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
imageLayout,
|
||||
subresourceRange);
|
||||
|
||||
device->flushCommandBuffer(copyCmd, copyQueue);
|
||||
|
||||
// Clean up staging resources
|
||||
vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr);
|
||||
vkFreeMemory(device->logicalDevice, stagingMemory, nullptr);
|
||||
|
||||
ktxTexture_Destroy(ktxTexture);
|
||||
|
||||
// Create a default sampler
|
||||
VkSamplerCreateInfo samplerCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.magFilter = VK_FILTER_LINEAR,
|
||||
.minFilter = VK_FILTER_LINEAR,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
||||
.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
||||
.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
||||
.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
||||
.mipLodBias = 0.0f,
|
||||
.anisotropyEnable = device->enabledFeatures.samplerAnisotropy,
|
||||
.maxAnisotropy = device->enabledFeatures.samplerAnisotropy ? device->properties.limits.maxSamplerAnisotropy : 1.0f,
|
||||
.compareOp = VK_COMPARE_OP_NEVER,
|
||||
.minLod = 0.0f,
|
||||
.maxLod = (float)mipLevels,
|
||||
.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerCreateInfo, nullptr, &sampler));
|
||||
|
||||
// Create image view
|
||||
// Textures are not directly accessed by the shaders and
|
||||
// are abstracted by image views containing additional
|
||||
// information and sub resource ranges
|
||||
VkImageViewCreateInfo viewCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = image,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = format,
|
||||
.subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = mipLevels, .baseArrayLayer = 0, .layerCount = 1 },
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &view));
|
||||
|
||||
// Update descriptor image info member that can be used for setting up descriptor sets
|
||||
updateDescriptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a 2D texture from a buffer
|
||||
*
|
||||
* @param buffer Buffer containing texture data to upload
|
||||
* @param bufferSize Size of the buffer in machine units
|
||||
* @param width Width of the texture to create
|
||||
* @param height Height of the texture to create
|
||||
* @param format Vulkan format of the image data stored in the file
|
||||
* @param device Vulkan device to create the texture on
|
||||
* @param copyQueue Queue used for the texture staging copy commands (must support transfer)
|
||||
* @param (Optional) filter Texture filtering for the sampler (defaults to VK_FILTER_LINEAR)
|
||||
* @param (Optional) imageUsageFlags Usage flags for the texture's image (defaults to VK_IMAGE_USAGE_SAMPLED_BIT)
|
||||
* @param (Optional) imageLayout Usage layout for the texture (defaults VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
*/
|
||||
void Texture2D::fromBuffer(void* buffer, VkDeviceSize bufferSize, VkFormat format, uint32_t texWidth, uint32_t texHeight, vks::VulkanDevice *device, VkQueue copyQueue, VkFilter filter, VkImageUsageFlags imageUsageFlags, VkImageLayout imageLayout)
|
||||
{
|
||||
assert(buffer);
|
||||
|
||||
this->device = device;
|
||||
width = texWidth;
|
||||
height = texHeight;
|
||||
mipLevels = 1;
|
||||
|
||||
VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
|
||||
VkMemoryRequirements memReqs;
|
||||
|
||||
// Create a host-visible staging buffer that contains the raw image data
|
||||
VkBuffer stagingBuffer;
|
||||
VkDeviceMemory stagingMemory;
|
||||
|
||||
VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
|
||||
bufferCreateInfo.size = bufferSize;
|
||||
// This buffer is used as a transfer source for the buffer copy
|
||||
bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
|
||||
VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer));
|
||||
|
||||
// Get memory requirements for the staging buffer (alignment, memory type bits)
|
||||
vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs);
|
||||
|
||||
memAllocInfo.allocationSize = memReqs.size;
|
||||
// Get memory type index for a host visible buffer
|
||||
memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
|
||||
VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory));
|
||||
VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0));
|
||||
|
||||
// Copy texture data into staging buffer
|
||||
uint8_t *data{ nullptr };
|
||||
VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void **)&data));
|
||||
memcpy(data, buffer, bufferSize);
|
||||
vkUnmapMemory(device->logicalDevice, stagingMemory);
|
||||
|
||||
VkBufferImageCopy bufferCopyRegion{
|
||||
.bufferOffset = 0,
|
||||
.imageSubresource = {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.mipLevel = 0,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
},
|
||||
.imageExtent = {
|
||||
.width = width,
|
||||
.height = height,
|
||||
.depth = 1,
|
||||
}
|
||||
};
|
||||
|
||||
// Create optimal tiled target image
|
||||
VkImageCreateInfo imageCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = format,
|
||||
.extent = {.width = width, .height = height, .depth = 1 },
|
||||
.mipLevels = mipLevels,
|
||||
.arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = imageUsageFlags,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
|
||||
};
|
||||
// Ensure that the TRANSFER_DST bit is set for staging
|
||||
if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT)) {
|
||||
imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &image));
|
||||
|
||||
vkGetImageMemoryRequirements(device->logicalDevice, image, &memReqs);
|
||||
|
||||
memAllocInfo.allocationSize = memReqs.size;
|
||||
|
||||
memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &deviceMemory));
|
||||
VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, image, deviceMemory, 0));
|
||||
|
||||
VkImageSubresourceRange subresourceRange{ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = mipLevels, .layerCount = 1 };
|
||||
|
||||
// Use a separate command buffer for texture loading
|
||||
VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
// Image barrier for optimal image (target)
|
||||
// Optimal image will be used as destination for the copy
|
||||
vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange);
|
||||
// Copy mip levels from staging buffer
|
||||
vkCmdCopyBufferToImage(copyCmd, stagingBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferCopyRegion);
|
||||
// Change texture image layout to shader read after all mip levels have been copied
|
||||
this->imageLayout = imageLayout;
|
||||
vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageLayout, subresourceRange);
|
||||
device->flushCommandBuffer(copyCmd, copyQueue);
|
||||
|
||||
// Clean up staging resources
|
||||
vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr);
|
||||
vkFreeMemory(device->logicalDevice, stagingMemory, nullptr);
|
||||
|
||||
// Create sampler
|
||||
VkSamplerCreateInfo samplerCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.magFilter = filter,
|
||||
.minFilter = filter,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
||||
.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
||||
.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
||||
.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
||||
.mipLodBias = 0.0f,
|
||||
.maxAnisotropy = 1.0f,
|
||||
.compareOp = VK_COMPARE_OP_NEVER,
|
||||
.minLod = 0.0f,
|
||||
.maxLod = 0.0f,
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerCreateInfo, nullptr, &sampler));
|
||||
|
||||
// Create image view
|
||||
VkImageViewCreateInfo viewCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = image,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = format,
|
||||
.subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1 },
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &view));
|
||||
|
||||
// Update descriptor image info member that can be used for setting up descriptor sets
|
||||
updateDescriptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a 2D texture array including all mip levels
|
||||
*
|
||||
* @param filename File to load (supports .ktx)
|
||||
* @param format Vulkan format of the image data stored in the file
|
||||
* @param device Vulkan device to create the texture on
|
||||
* @param copyQueue Queue used for the texture staging copy commands (must support transfer)
|
||||
* @param (Optional) imageUsageFlags Usage flags for the texture's image (defaults to VK_IMAGE_USAGE_SAMPLED_BIT)
|
||||
* @param (Optional) imageLayout Usage layout for the texture (defaults VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
*
|
||||
*/
|
||||
void Texture2DArray::loadFromFile(std::string filename, VkFormat format, vks::VulkanDevice *device, VkQueue copyQueue, VkImageUsageFlags imageUsageFlags, VkImageLayout imageLayout)
|
||||
{
|
||||
ktxTexture* ktxTexture;
|
||||
ktxResult result = loadKTXFile(filename, &ktxTexture);
|
||||
assert(result == KTX_SUCCESS);
|
||||
|
||||
this->device = device;
|
||||
width = ktxTexture->baseWidth;
|
||||
height = ktxTexture->baseHeight;
|
||||
layerCount = ktxTexture->numLayers;
|
||||
mipLevels = ktxTexture->numLevels;
|
||||
|
||||
ktx_uint8_t *ktxTextureData = ktxTexture_GetData(ktxTexture);
|
||||
ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture);
|
||||
|
||||
// Create a host-visible staging buffer that contains the raw image data
|
||||
VkBuffer stagingBuffer;
|
||||
VkDeviceMemory stagingMemory;
|
||||
|
||||
VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
|
||||
bufferCreateInfo.size = ktxTextureSize;
|
||||
// This buffer is used as a transfer source for the buffer copy
|
||||
bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
|
||||
VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer));
|
||||
|
||||
// Get memory requirements for the staging buffer (alignment, memory type bits)
|
||||
VkMemoryRequirements memReqs;
|
||||
vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs);
|
||||
VkMemoryAllocateInfo memAllocInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.allocationSize = memReqs.size,
|
||||
.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
|
||||
};
|
||||
VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory));
|
||||
VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0));
|
||||
|
||||
// Copy texture data into staging buffer
|
||||
uint8_t *data{ nullptr };
|
||||
VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void **)&data));
|
||||
memcpy(data, ktxTextureData, ktxTextureSize);
|
||||
vkUnmapMemory(device->logicalDevice, stagingMemory);
|
||||
|
||||
// Setup buffer copy regions for each layer including all of its miplevels
|
||||
std::vector<VkBufferImageCopy> bufferCopyRegions;
|
||||
|
||||
for (uint32_t layer = 0; layer < layerCount; layer++) {
|
||||
for (uint32_t level = 0; level < mipLevels; level++) {
|
||||
ktx_size_t offset;
|
||||
KTX_error_code result = ktxTexture_GetImageOffset(ktxTexture, level, layer, 0, &offset);
|
||||
assert(result == KTX_SUCCESS);
|
||||
VkBufferImageCopy bufferCopyRegion{
|
||||
.bufferOffset = offset,
|
||||
.imageSubresource {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.mipLevel = level,
|
||||
.baseArrayLayer = layer,
|
||||
.layerCount = 1,
|
||||
},
|
||||
.imageExtent {
|
||||
.width = ktxTexture->baseWidth >> level,
|
||||
.height = ktxTexture->baseHeight >> level,
|
||||
.depth = 1,
|
||||
}
|
||||
};
|
||||
bufferCopyRegions.push_back(bufferCopyRegion);
|
||||
}
|
||||
}
|
||||
|
||||
// Create optimal tiled target image
|
||||
VkImageCreateInfo imageCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = format,
|
||||
.extent = {.width = width, .height = height, .depth = 1 },
|
||||
.mipLevels = mipLevels,
|
||||
.arrayLayers = layerCount,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = imageUsageFlags,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
};
|
||||
// Ensure that the TRANSFER_DST bit is set for staging
|
||||
if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT)) {
|
||||
imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &image));
|
||||
|
||||
vkGetImageMemoryRequirements(device->logicalDevice, image, &memReqs);
|
||||
memAllocInfo.allocationSize = memReqs.size;
|
||||
memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &deviceMemory));
|
||||
VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, image, deviceMemory, 0));
|
||||
|
||||
// Use a separate command buffer for texture loading
|
||||
VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
// Image barrier for optimal image (target)
|
||||
// Set initial layout for all array layers (faces) of the optimal (target) tiled texture
|
||||
VkImageSubresourceRange subresourceRange{ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = mipLevels, .layerCount = layerCount };
|
||||
vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange);
|
||||
// Copy the layers and mip levels from the staging buffer to the optimal tiled image
|
||||
vkCmdCopyBufferToImage(copyCmd, stagingBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast<uint32_t>(bufferCopyRegions.size()), bufferCopyRegions.data());
|
||||
// Change texture image layout to shader read after all faces have been copied
|
||||
this->imageLayout = imageLayout;
|
||||
vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageLayout, subresourceRange);
|
||||
device->flushCommandBuffer(copyCmd, copyQueue);
|
||||
|
||||
// Create sampler
|
||||
VkSamplerCreateInfo samplerCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.magFilter = VK_FILTER_LINEAR,
|
||||
.minFilter = VK_FILTER_LINEAR,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
||||
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||
.addressModeV = samplerCreateInfo.addressModeU,
|
||||
.addressModeW = samplerCreateInfo.addressModeU,
|
||||
.mipLodBias = 0.0f,
|
||||
.anisotropyEnable = device->enabledFeatures.samplerAnisotropy,
|
||||
.maxAnisotropy = device->enabledFeatures.samplerAnisotropy ? device->properties.limits.maxSamplerAnisotropy : 1.0f,
|
||||
.compareOp = VK_COMPARE_OP_NEVER,
|
||||
.minLod = 0.0f,
|
||||
.maxLod = (float)mipLevels,
|
||||
.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE,
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerCreateInfo, nullptr, &sampler));
|
||||
|
||||
// Create image view
|
||||
VkImageViewCreateInfo viewCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = image,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
|
||||
.format = format,
|
||||
.subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = mipLevels, .baseArrayLayer = 0, .layerCount = layerCount },
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &view));
|
||||
|
||||
// Clean up staging resources
|
||||
ktxTexture_Destroy(ktxTexture);
|
||||
vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr);
|
||||
vkFreeMemory(device->logicalDevice, stagingMemory, nullptr);
|
||||
|
||||
// Update descriptor image info member that can be used for setting up descriptor sets
|
||||
updateDescriptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a cubemap texture including all mip levels from a single file
|
||||
*
|
||||
* @param filename File to load (supports .ktx)
|
||||
* @param format Vulkan format of the image data stored in the file
|
||||
* @param device Vulkan device to create the texture on
|
||||
* @param copyQueue Queue used for the texture staging copy commands (must support transfer)
|
||||
* @param (Optional) imageUsageFlags Usage flags for the texture's image (defaults to VK_IMAGE_USAGE_SAMPLED_BIT)
|
||||
* @param (Optional) imageLayout Usage layout for the texture (defaults VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
*
|
||||
*/
|
||||
void TextureCubeMap::loadFromFile(std::string filename, VkFormat format, vks::VulkanDevice *device, VkQueue copyQueue, VkImageUsageFlags imageUsageFlags, VkImageLayout imageLayout)
|
||||
{
|
||||
ktxTexture* ktxTexture;
|
||||
ktxResult result = loadKTXFile(filename, &ktxTexture);
|
||||
assert(result == KTX_SUCCESS);
|
||||
|
||||
this->device = device;
|
||||
width = ktxTexture->baseWidth;
|
||||
height = ktxTexture->baseHeight;
|
||||
mipLevels = ktxTexture->numLevels;
|
||||
|
||||
ktx_uint8_t *ktxTextureData = ktxTexture_GetData(ktxTexture);
|
||||
ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture);
|
||||
|
||||
// Create a host-visible staging buffer that contains the raw image data
|
||||
VkBuffer stagingBuffer;
|
||||
VkDeviceMemory stagingMemory;
|
||||
|
||||
VkBufferCreateInfo bufferCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = ktxTextureSize,
|
||||
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer));
|
||||
|
||||
// Get memory requirements for the staging buffer (alignment, memory type bits)
|
||||
VkMemoryRequirements memReqs;
|
||||
vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs);
|
||||
VkMemoryAllocateInfo memAllocInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.allocationSize = memReqs.size,
|
||||
.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
|
||||
};
|
||||
VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory));
|
||||
VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0));
|
||||
|
||||
// Copy texture data into staging buffer
|
||||
uint8_t *data{ nullptr };
|
||||
VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void **)&data));
|
||||
memcpy(data, ktxTextureData, ktxTextureSize);
|
||||
vkUnmapMemory(device->logicalDevice, stagingMemory);
|
||||
|
||||
// Setup buffer copy regions for each face including all of its mip levels
|
||||
std::vector<VkBufferImageCopy> bufferCopyRegions;
|
||||
for (uint32_t face = 0; face < 6; face++) {
|
||||
for (uint32_t level = 0; level < mipLevels; level++) {
|
||||
ktx_size_t offset;
|
||||
KTX_error_code result = ktxTexture_GetImageOffset(ktxTexture, level, 0, face, &offset);
|
||||
assert(result == KTX_SUCCESS);
|
||||
VkBufferImageCopy bufferCopyRegion{
|
||||
.bufferOffset = offset,
|
||||
.imageSubresource = {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.mipLevel = level,
|
||||
.baseArrayLayer = face,
|
||||
.layerCount = 1
|
||||
},
|
||||
.imageExtent = {
|
||||
.width = ktxTexture->baseWidth >> level,
|
||||
.height = ktxTexture->baseHeight >> level,
|
||||
.depth = 1
|
||||
},
|
||||
};
|
||||
bufferCopyRegions.push_back(bufferCopyRegion);
|
||||
}
|
||||
}
|
||||
|
||||
// Create optimal tiled target image
|
||||
VkImageCreateInfo imageCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
// This flag is required for cube map images
|
||||
.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = format,
|
||||
.extent = { .width = width, .height = height, .depth = 1 },
|
||||
.mipLevels = mipLevels,
|
||||
// Cube faces count as array layers in Vulkan
|
||||
.arrayLayers = 6,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = imageUsageFlags,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
};
|
||||
// Ensure that the TRANSFER_DST bit is set for staging
|
||||
if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT)){
|
||||
imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &image));
|
||||
|
||||
vkGetImageMemoryRequirements(device->logicalDevice, image, &memReqs);
|
||||
memAllocInfo.allocationSize = memReqs.size;
|
||||
memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &deviceMemory));
|
||||
VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, image, deviceMemory, 0));
|
||||
|
||||
// Use a separate command buffer for texture loading
|
||||
VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
// Image barrier for optimal image (target)
|
||||
// Set initial layout for all array layers (faces) of the optimal (target) tiled texture
|
||||
VkImageSubresourceRange subresourceRange{ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = mipLevels, .layerCount = 6 };
|
||||
vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange);
|
||||
// Copy the cube map faces from the staging buffer to the optimal tiled image
|
||||
vkCmdCopyBufferToImage(copyCmd, stagingBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast<uint32_t>(bufferCopyRegions.size()), bufferCopyRegions.data());
|
||||
// Change texture image layout to shader read after all faces have been copied
|
||||
this->imageLayout = imageLayout;
|
||||
vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageLayout, subresourceRange);
|
||||
device->flushCommandBuffer(copyCmd, copyQueue);
|
||||
|
||||
// Create sampler
|
||||
VkSamplerCreateInfo samplerCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.magFilter = VK_FILTER_LINEAR,
|
||||
.minFilter = VK_FILTER_LINEAR,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
||||
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||
.addressModeV = samplerCreateInfo.addressModeU,
|
||||
.addressModeW = samplerCreateInfo.addressModeU,
|
||||
.mipLodBias = 0.0f,
|
||||
.anisotropyEnable = device->enabledFeatures.samplerAnisotropy,
|
||||
.maxAnisotropy = device->enabledFeatures.samplerAnisotropy ? device->properties.limits.maxSamplerAnisotropy : 1.0f,
|
||||
.compareOp = VK_COMPARE_OP_NEVER,
|
||||
.minLod = 0.0f,
|
||||
.maxLod = (float)mipLevels,
|
||||
.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerCreateInfo, nullptr, &sampler));
|
||||
|
||||
// Create image view
|
||||
VkImageViewCreateInfo viewCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = image,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_CUBE,
|
||||
.format = format,
|
||||
.subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = mipLevels, .baseArrayLayer = 0, .layerCount = 6 },
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &view));
|
||||
|
||||
// Clean up staging resources
|
||||
ktxTexture_Destroy(ktxTexture);
|
||||
vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr);
|
||||
vkFreeMemory(device->logicalDevice, stagingMemory, nullptr);
|
||||
|
||||
// Update descriptor image info member that can be used for setting up descriptor sets
|
||||
updateDescriptor();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Vulkan texture loader for KTX files
|
||||
*
|
||||
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license(MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fstream>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "vulkan/vulkan.h"
|
||||
|
||||
#include <ktx.h>
|
||||
#include <ktxvulkan.h>
|
||||
|
||||
#include "VulkanBuffer.h"
|
||||
#include "VulkanDevice.h"
|
||||
#include "VulkanTools.h"
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
# include <android/asset_manager.h>
|
||||
#endif
|
||||
|
||||
namespace vks
|
||||
{
|
||||
class Texture
|
||||
{
|
||||
public:
|
||||
vks::VulkanDevice * device;
|
||||
VkImage image;
|
||||
VkImageLayout imageLayout;
|
||||
VkDeviceMemory deviceMemory;
|
||||
VkImageView view;
|
||||
uint32_t width, height;
|
||||
uint32_t mipLevels;
|
||||
uint32_t layerCount;
|
||||
VkDescriptorImageInfo descriptor;
|
||||
VkSampler sampler;
|
||||
VkFormat format;
|
||||
|
||||
void updateDescriptor();
|
||||
void destroy();
|
||||
ktxResult loadKTXFile(std::string filename, ktxTexture **target);
|
||||
};
|
||||
|
||||
class Texture2D : public Texture
|
||||
{
|
||||
public:
|
||||
void loadFromFile(
|
||||
std::string filename,
|
||||
VkFormat format,
|
||||
vks::VulkanDevice *device,
|
||||
VkQueue copyQueue,
|
||||
VkImageUsageFlags imageUsageFlags = VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
VkImageLayout imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
void fromBuffer(
|
||||
void * buffer,
|
||||
VkDeviceSize bufferSize,
|
||||
VkFormat format,
|
||||
uint32_t texWidth,
|
||||
uint32_t texHeight,
|
||||
vks::VulkanDevice *device,
|
||||
VkQueue copyQueue,
|
||||
VkFilter filter = VK_FILTER_LINEAR,
|
||||
VkImageUsageFlags imageUsageFlags = VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
VkImageLayout imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
};
|
||||
|
||||
class Texture2DArray : public Texture
|
||||
{
|
||||
public:
|
||||
void loadFromFile(
|
||||
std::string filename,
|
||||
VkFormat format,
|
||||
vks::VulkanDevice *device,
|
||||
VkQueue copyQueue,
|
||||
VkImageUsageFlags imageUsageFlags = VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
VkImageLayout imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
};
|
||||
|
||||
class TextureCubeMap : public Texture
|
||||
{
|
||||
public:
|
||||
void loadFromFile(
|
||||
std::string filename,
|
||||
VkFormat format,
|
||||
vks::VulkanDevice *device,
|
||||
VkQueue copyQueue,
|
||||
VkImageUsageFlags imageUsageFlags = VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
VkImageLayout imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
};
|
||||
} // namespace vks
|
||||
@@ -0,0 +1,502 @@
|
||||
/*
|
||||
* Assorted commonly used Vulkan helper functions
|
||||
*
|
||||
* Copyright (C) 2016-2026 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#include "VulkanTools.h"
|
||||
|
||||
#if !(defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT))
|
||||
// iOS & macOS: getAssetPath() and getShaderBasePath() implemented externally for access to Obj-C++ path utilities
|
||||
const std::string getAssetPath()
|
||||
{
|
||||
if (vks::tools::resourcePath != "") {
|
||||
return vks::tools::resourcePath + "/assets/";
|
||||
}
|
||||
#if defined(VK_USE_PLATFORM_ANDROID_KHR) || defined (VK_USE_PLATFORM_OHOS)
|
||||
return "";
|
||||
#elif defined(VK_EXAMPLE_ASSETS_DIR)
|
||||
return VK_EXAMPLE_ASSETS_DIR;
|
||||
#else
|
||||
return "./../assets/";
|
||||
#endif
|
||||
}
|
||||
|
||||
const std::string getShaderBasePath()
|
||||
{
|
||||
if (vks::tools::resourcePath != "") {
|
||||
return vks::tools::resourcePath + "/shaders/";
|
||||
}
|
||||
#if defined(VK_USE_PLATFORM_ANDROID_KHR) || defined (VK_USE_PLATFORM_OHOS)
|
||||
return "shaders/";
|
||||
#elif defined(VK_EXAMPLE_SHADERS_DIR)
|
||||
return VK_EXAMPLE_SHADERS_DIR;
|
||||
#else
|
||||
return "./../shaders/";
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace vks
|
||||
{
|
||||
namespace tools
|
||||
{
|
||||
bool errorModeSilent = false;
|
||||
std::string resourcePath = "";
|
||||
|
||||
std::string errorString(VkResult errorCode)
|
||||
{
|
||||
switch (errorCode)
|
||||
{
|
||||
#define STR(r) case VK_ ##r: return #r
|
||||
STR(NOT_READY);
|
||||
STR(TIMEOUT);
|
||||
STR(EVENT_SET);
|
||||
STR(EVENT_RESET);
|
||||
STR(INCOMPLETE);
|
||||
STR(ERROR_OUT_OF_HOST_MEMORY);
|
||||
STR(ERROR_OUT_OF_DEVICE_MEMORY);
|
||||
STR(ERROR_INITIALIZATION_FAILED);
|
||||
STR(ERROR_DEVICE_LOST);
|
||||
STR(ERROR_MEMORY_MAP_FAILED);
|
||||
STR(ERROR_LAYER_NOT_PRESENT);
|
||||
STR(ERROR_EXTENSION_NOT_PRESENT);
|
||||
STR(ERROR_FEATURE_NOT_PRESENT);
|
||||
STR(ERROR_INCOMPATIBLE_DRIVER);
|
||||
STR(ERROR_TOO_MANY_OBJECTS);
|
||||
STR(ERROR_FORMAT_NOT_SUPPORTED);
|
||||
STR(ERROR_SURFACE_LOST_KHR);
|
||||
STR(ERROR_NATIVE_WINDOW_IN_USE_KHR);
|
||||
STR(SUBOPTIMAL_KHR);
|
||||
STR(ERROR_OUT_OF_DATE_KHR);
|
||||
STR(ERROR_INCOMPATIBLE_DISPLAY_KHR);
|
||||
STR(ERROR_VALIDATION_FAILED_EXT);
|
||||
STR(ERROR_INVALID_SHADER_NV);
|
||||
STR(ERROR_INCOMPATIBLE_SHADER_BINARY_EXT);
|
||||
#undef STR
|
||||
default:
|
||||
return "UNKNOWN_ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
std::string physicalDeviceTypeString(VkPhysicalDeviceType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
#define STR(r) case VK_PHYSICAL_DEVICE_TYPE_ ##r: return #r
|
||||
STR(OTHER);
|
||||
STR(INTEGRATED_GPU);
|
||||
STR(DISCRETE_GPU);
|
||||
STR(VIRTUAL_GPU);
|
||||
STR(CPU);
|
||||
#undef STR
|
||||
default: return "UNKNOWN_DEVICE_TYPE";
|
||||
}
|
||||
}
|
||||
|
||||
VkBool32 getSupportedDepthFormat(VkPhysicalDevice physicalDevice, VkFormat *depthFormat)
|
||||
{
|
||||
// Since all depth formats may be optional, we need to find a suitable depth format to use
|
||||
// Start with the highest precision packed format
|
||||
std::vector<VkFormat> formatList = {
|
||||
VK_FORMAT_D32_SFLOAT_S8_UINT,
|
||||
VK_FORMAT_D32_SFLOAT,
|
||||
VK_FORMAT_D24_UNORM_S8_UINT,
|
||||
VK_FORMAT_D16_UNORM_S8_UINT,
|
||||
VK_FORMAT_D16_UNORM
|
||||
};
|
||||
|
||||
for (auto& format : formatList)
|
||||
{
|
||||
VkFormatProperties formatProps;
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProps);
|
||||
if (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
|
||||
{
|
||||
*depthFormat = format;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
VkBool32 getSupportedDepthStencilFormat(VkPhysicalDevice physicalDevice, VkFormat* depthStencilFormat)
|
||||
{
|
||||
std::vector<VkFormat> formatList = {
|
||||
VK_FORMAT_D32_SFLOAT_S8_UINT,
|
||||
VK_FORMAT_D24_UNORM_S8_UINT,
|
||||
VK_FORMAT_D16_UNORM_S8_UINT,
|
||||
};
|
||||
|
||||
for (auto& format : formatList)
|
||||
{
|
||||
VkFormatProperties formatProps;
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProps);
|
||||
if (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
|
||||
{
|
||||
*depthStencilFormat = format;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
VkBool32 formatHasStencil(VkFormat format)
|
||||
{
|
||||
std::vector<VkFormat> stencilFormats = {
|
||||
VK_FORMAT_S8_UINT,
|
||||
VK_FORMAT_D16_UNORM_S8_UINT,
|
||||
VK_FORMAT_D24_UNORM_S8_UINT,
|
||||
VK_FORMAT_D32_SFLOAT_S8_UINT,
|
||||
};
|
||||
return std::find(stencilFormats.begin(), stencilFormats.end(), format) != std::end(stencilFormats);
|
||||
}
|
||||
|
||||
// Returns if a given format support LINEAR filtering
|
||||
VkBool32 formatIsFilterable(VkPhysicalDevice physicalDevice, VkFormat format, VkImageTiling tiling)
|
||||
{
|
||||
VkFormatProperties formatProps;
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProps);
|
||||
|
||||
if (tiling == VK_IMAGE_TILING_OPTIMAL)
|
||||
return formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
|
||||
|
||||
if (tiling == VK_IMAGE_TILING_LINEAR)
|
||||
return formatProps.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create an image memory barrier for changing the layout of
|
||||
// an image and put it into an active command buffer
|
||||
// See chapter 11.4 "Image Layout" for details
|
||||
|
||||
void setImageLayout(
|
||||
VkCommandBuffer cmdbuffer,
|
||||
VkImage image,
|
||||
VkImageLayout oldImageLayout,
|
||||
VkImageLayout newImageLayout,
|
||||
VkImageSubresourceRange subresourceRange,
|
||||
VkPipelineStageFlags srcStageMask,
|
||||
VkPipelineStageFlags dstStageMask)
|
||||
{
|
||||
// Create an image barrier object
|
||||
VkImageMemoryBarrier imageMemoryBarrier = vks::initializers::imageMemoryBarrier();
|
||||
imageMemoryBarrier.oldLayout = oldImageLayout;
|
||||
imageMemoryBarrier.newLayout = newImageLayout;
|
||||
imageMemoryBarrier.image = image;
|
||||
imageMemoryBarrier.subresourceRange = subresourceRange;
|
||||
|
||||
// Source layouts (old)
|
||||
// Source access mask controls actions that have to be finished on the old layout
|
||||
// before it will be transitioned to the new layout
|
||||
switch (oldImageLayout)
|
||||
{
|
||||
case VK_IMAGE_LAYOUT_UNDEFINED:
|
||||
// Image layout is undefined (or does not matter)
|
||||
// Only valid as initial layout
|
||||
// No flags required, listed only for completeness
|
||||
imageMemoryBarrier.srcAccessMask = 0;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_PREINITIALIZED:
|
||||
// Image is preinitialized
|
||||
// Only valid as initial layout for linear images, preserves memory contents
|
||||
// Make sure host writes have been finished
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
||||
// Image is a color attachment
|
||||
// Make sure any writes to the color buffer have been finished
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
|
||||
// Image is a depth/stencil attachment
|
||||
// Make sure any writes to the depth/stencil buffer have been finished
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
||||
// Image is a transfer source
|
||||
// Make sure any reads from the image have been finished
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
||||
// Image is a transfer destination
|
||||
// Make sure any writes to the image have been finished
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
||||
// Image is read by a shader
|
||||
// Make sure any shader reads from the image have been finished
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
break;
|
||||
default:
|
||||
// Other source layouts aren't handled (yet)
|
||||
break;
|
||||
}
|
||||
|
||||
// Target layouts (new)
|
||||
// Destination access mask controls the dependency for the new image layout
|
||||
switch (newImageLayout)
|
||||
{
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
||||
// Image will be used as a transfer destination
|
||||
// Make sure any writes to the image have been finished
|
||||
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
||||
// Image will be used as a transfer source
|
||||
// Make sure any reads from the image have been finished
|
||||
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
||||
// Image will be used as a color attachment
|
||||
// Make sure any writes to the color buffer have been finished
|
||||
imageMemoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
|
||||
// Image layout will be used as a depth/stencil attachment
|
||||
// Make sure any writes to depth/stencil buffer have been finished
|
||||
imageMemoryBarrier.dstAccessMask = imageMemoryBarrier.dstAccessMask | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
break;
|
||||
|
||||
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
||||
// Image will be read in a shader (sampler, input attachment)
|
||||
// Make sure any writes to the image have been finished
|
||||
if (imageMemoryBarrier.srcAccessMask == 0)
|
||||
{
|
||||
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
}
|
||||
imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
break;
|
||||
default:
|
||||
// Other source layouts aren't handled (yet)
|
||||
break;
|
||||
}
|
||||
|
||||
// Put barrier inside setup command buffer
|
||||
vkCmdPipelineBarrier(
|
||||
cmdbuffer,
|
||||
srcStageMask,
|
||||
dstStageMask,
|
||||
0,
|
||||
0, nullptr,
|
||||
0, nullptr,
|
||||
1, &imageMemoryBarrier);
|
||||
}
|
||||
|
||||
// Fixed sub resource on first mip level and layer
|
||||
void setImageLayout(
|
||||
VkCommandBuffer cmdbuffer,
|
||||
VkImage image,
|
||||
VkImageAspectFlags aspectMask,
|
||||
VkImageLayout oldImageLayout,
|
||||
VkImageLayout newImageLayout,
|
||||
VkPipelineStageFlags srcStageMask,
|
||||
VkPipelineStageFlags dstStageMask)
|
||||
{
|
||||
VkImageSubresourceRange subresourceRange = {};
|
||||
subresourceRange.aspectMask = aspectMask;
|
||||
subresourceRange.baseMipLevel = 0;
|
||||
subresourceRange.levelCount = 1;
|
||||
subresourceRange.layerCount = 1;
|
||||
setImageLayout(cmdbuffer, image, oldImageLayout, newImageLayout, subresourceRange, srcStageMask, dstStageMask);
|
||||
}
|
||||
|
||||
void insertImageMemoryBarrier(
|
||||
VkCommandBuffer cmdbuffer,
|
||||
VkImage image,
|
||||
VkAccessFlags srcAccessMask,
|
||||
VkAccessFlags dstAccessMask,
|
||||
VkImageLayout oldImageLayout,
|
||||
VkImageLayout newImageLayout,
|
||||
VkPipelineStageFlags srcStageMask,
|
||||
VkPipelineStageFlags dstStageMask,
|
||||
VkImageSubresourceRange subresourceRange)
|
||||
{
|
||||
VkImageMemoryBarrier imageMemoryBarrier = vks::initializers::imageMemoryBarrier();
|
||||
imageMemoryBarrier.srcAccessMask = srcAccessMask;
|
||||
imageMemoryBarrier.dstAccessMask = dstAccessMask;
|
||||
imageMemoryBarrier.oldLayout = oldImageLayout;
|
||||
imageMemoryBarrier.newLayout = newImageLayout;
|
||||
imageMemoryBarrier.image = image;
|
||||
imageMemoryBarrier.subresourceRange = subresourceRange;
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
cmdbuffer,
|
||||
srcStageMask,
|
||||
dstStageMask,
|
||||
0,
|
||||
0, nullptr,
|
||||
0, nullptr,
|
||||
1, &imageMemoryBarrier);
|
||||
}
|
||||
|
||||
void exitFatal(const std::string& message, int32_t exitCode)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
if (!errorModeSilent) {
|
||||
MessageBox(NULL, message.c_str(), NULL, MB_OK | MB_ICONERROR);
|
||||
}
|
||||
#elif defined(__ANDROID__)
|
||||
LOGE("Fatal error: %s", message.c_str());
|
||||
vks::android::showAlert(message.c_str());
|
||||
#elif defined (__OHOS__)
|
||||
LOGE("Fatal error: %{public}s", message.c_str());
|
||||
#endif
|
||||
std::cerr << message << "\n";
|
||||
#if defined(VK_EXAMPLE_PS5)
|
||||
// A title never calls exit (the console reports it as a crash): the launcher catches this, ends the sample and reports it
|
||||
throw std::runtime_error(message);
|
||||
#elif !defined(__ANDROID__) && !defined(__OHOS__)
|
||||
exit(exitCode);
|
||||
#endif
|
||||
}
|
||||
|
||||
void exitFatal(const std::string& message, VkResult resultCode)
|
||||
{
|
||||
exitFatal(message, (int32_t)resultCode);
|
||||
}
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
// Android shaders are stored as assets in the apk
|
||||
// So they need to be loaded via the asset manager
|
||||
VkShaderModule loadShader(AAssetManager* assetManager, const char *fileName, VkDevice device)
|
||||
{
|
||||
// Load shader from compressed asset
|
||||
AAsset* asset = AAssetManager_open(assetManager, fileName, AASSET_MODE_STREAMING);
|
||||
assert(asset);
|
||||
size_t size = AAsset_getLength(asset);
|
||||
assert(size > 0);
|
||||
|
||||
char *shaderCode = new char[size];
|
||||
AAsset_read(asset, shaderCode, size);
|
||||
AAsset_close(asset);
|
||||
|
||||
VkShaderModule shaderModule;
|
||||
VkShaderModuleCreateInfo moduleCreateInfo{};
|
||||
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
moduleCreateInfo.pNext = NULL;
|
||||
moduleCreateInfo.codeSize = size;
|
||||
moduleCreateInfo.pCode = (uint32_t*)shaderCode;
|
||||
moduleCreateInfo.flags = 0;
|
||||
|
||||
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
|
||||
|
||||
delete[] shaderCode;
|
||||
|
||||
return shaderModule;
|
||||
}
|
||||
#elif defined (__OHOS__)
|
||||
/**
|
||||
* Load shader from HarmonyOS raw resources using ResourceManager singleton
|
||||
* This is the preferred method - no need to pass NativeResourceManager explicitly
|
||||
*/
|
||||
VkShaderModule loadShader(const char *fileName, VkDevice device) {
|
||||
NativeResourceManager* resourceMgr = ResourceManager::getInstance().getNativeResourceManager();
|
||||
if (!resourceMgr) {
|
||||
std::string msg = "ResourceManager not initialized. Call ResourceManager::getInstance().initialize() first.";
|
||||
// vks::tools::exitFatal(msg, -1);
|
||||
}
|
||||
|
||||
RawFile *rawfile = OH_ResourceManager_OpenRawFile(resourceMgr, fileName);
|
||||
if (!rawfile) {
|
||||
std::string msg = "Could not load shader from " + std::string(fileName);
|
||||
vks::tools::exitFatal(msg, -1);
|
||||
}
|
||||
|
||||
size_t size = OH_ResourceManager_GetRawFileSize(rawfile);
|
||||
assert(size > 0);
|
||||
|
||||
char *shaderCode = new char[size];
|
||||
OH_ResourceManager_ReadRawFile(rawfile, shaderCode, size);
|
||||
OH_ResourceManager_CloseRawFile(rawfile);
|
||||
|
||||
VkShaderModule shaderModule;
|
||||
VkShaderModuleCreateInfo moduleCreateInfo;
|
||||
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
moduleCreateInfo.pNext = NULL;
|
||||
moduleCreateInfo.codeSize = size;
|
||||
moduleCreateInfo.pCode = (uint32_t *)shaderCode;
|
||||
moduleCreateInfo.flags = 0;
|
||||
|
||||
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
|
||||
delete[] shaderCode;
|
||||
|
||||
return shaderModule;
|
||||
}
|
||||
#else
|
||||
VkShaderModule loadShader(const char *fileName, VkDevice device)
|
||||
{
|
||||
std::ifstream is(fileName, std::ios::binary | std::ios::in | std::ios::ate);
|
||||
|
||||
if (is.is_open())
|
||||
{
|
||||
size_t size = is.tellg();
|
||||
is.seekg(0, std::ios::beg);
|
||||
char* shaderCode = new char[size];
|
||||
is.read(shaderCode, size);
|
||||
is.close();
|
||||
|
||||
assert(size > 0);
|
||||
|
||||
VkShaderModule shaderModule;
|
||||
VkShaderModuleCreateInfo moduleCreateInfo{};
|
||||
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
moduleCreateInfo.codeSize = size;
|
||||
moduleCreateInfo.pCode = (uint32_t*)shaderCode;
|
||||
|
||||
VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
|
||||
|
||||
delete[] shaderCode;
|
||||
|
||||
return shaderModule;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Error: Could not open shader file \"" << fileName << "\"" << "\n";
|
||||
#if defined(VK_EXAMPLE_PS5)
|
||||
// A null module would reach the driver: end the sample instead
|
||||
exitFatal(std::string("Could not open shader file ") + fileName, -1);
|
||||
#endif
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool fileExists(const std::string &filename)
|
||||
{
|
||||
std::ifstream f(filename.c_str());
|
||||
return !f.fail();
|
||||
}
|
||||
|
||||
uint32_t alignedSize(uint32_t value, uint32_t alignment)
|
||||
{
|
||||
return (value + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
size_t alignedSize(size_t value, size_t alignment)
|
||||
{
|
||||
return (value + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
|
||||
VkDeviceSize alignedVkSize(VkDeviceSize value, VkDeviceSize alignment)
|
||||
{
|
||||
return (value + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Assorted Vulkan helper functions
|
||||
*
|
||||
* Copyright (C) 2016-2026 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "vulkan/vulkan.h"
|
||||
#include "VulkanInitializers.hpp"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <format>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#elif defined(__ANDROID__)
|
||||
#include "VulkanAndroid.h"
|
||||
#include <android/asset_manager.h>
|
||||
#elif defined(__OHOS__)
|
||||
#include "VulkanOHOS.h"
|
||||
#endif
|
||||
|
||||
// Custom define for better code readability
|
||||
#define VK_FLAGS_NONE 0
|
||||
// Default fence timeout in nanoseconds
|
||||
#define DEFAULT_FENCE_TIMEOUT 100000000000
|
||||
|
||||
// Macro to check and display Vulkan return results
|
||||
#if defined(__ANDROID__)
|
||||
#define VK_CHECK_RESULT(f) \
|
||||
{ \
|
||||
VkResult res = (f); \
|
||||
if (res != VK_SUCCESS) \
|
||||
{ \
|
||||
std::string message = std::format("Fatal : VkResult is {} in {} at line {}", vks::tools::errorString(res), __FILE__, __LINE__); \
|
||||
LOGE("%s", message.c_str()); \
|
||||
vks::tools::exitFatal(message, -1); \
|
||||
} \
|
||||
}
|
||||
#elif defined (__OHOS__)
|
||||
#define VK_CHECK_RESULT(f) \
|
||||
{ \
|
||||
VkResult res = (f); \
|
||||
if (res != VK_SUCCESS) \
|
||||
{ \
|
||||
LOGE("Fatal : VkResult is \" %s \" in %s at line %d", \
|
||||
vks::tools::errorString(res).c_str(), __FILE__, __LINE__); \
|
||||
assert(res == VK_SUCCESS); \
|
||||
} \
|
||||
}
|
||||
#else
|
||||
#define VK_CHECK_RESULT(f) \
|
||||
{ \
|
||||
VkResult res = (f); \
|
||||
if (res != VK_SUCCESS) \
|
||||
{ \
|
||||
std::string message = std::format("Fatal : VkResult is {} in {} at line {}", vks::tools::errorString(res), __FILE__, __LINE__); \
|
||||
vks::tools::exitFatal(message, -1); \
|
||||
} \
|
||||
}
|
||||
#endif
|
||||
|
||||
const std::string getAssetPath();
|
||||
const std::string getShaderBasePath();
|
||||
|
||||
namespace vks
|
||||
{
|
||||
namespace tools
|
||||
{
|
||||
/** @brief Setting this path chnanges the place where the samples looks for assets and shaders */
|
||||
extern std::string resourcePath;
|
||||
|
||||
/** @brief Disable message boxes on fatal errors */
|
||||
extern bool errorModeSilent;
|
||||
|
||||
/** @brief Returns an error code as a string */
|
||||
std::string errorString(VkResult errorCode);
|
||||
|
||||
/** @brief Returns the device type as a string */
|
||||
std::string physicalDeviceTypeString(VkPhysicalDeviceType type);
|
||||
|
||||
// Selected a suitable supported depth format starting with 32 bit down to 16 bit
|
||||
// Returns false if none of the depth formats in the list is supported by the device
|
||||
VkBool32 getSupportedDepthFormat(VkPhysicalDevice physicalDevice, VkFormat *depthFormat);
|
||||
// Same as getSupportedDepthFormat but will only select formats that also have stencil
|
||||
VkBool32 getSupportedDepthStencilFormat(VkPhysicalDevice physicalDevice, VkFormat* depthStencilFormat);
|
||||
|
||||
// Returns true a given format support LINEAR filtering
|
||||
VkBool32 formatIsFilterable(VkPhysicalDevice physicalDevice, VkFormat format, VkImageTiling tiling);
|
||||
// Returns true if a given format has a stencil part
|
||||
VkBool32 formatHasStencil(VkFormat format);
|
||||
|
||||
// Put an image memory barrier for setting an image layout on the sub resource into the given command buffer
|
||||
void setImageLayout(
|
||||
VkCommandBuffer cmdbuffer,
|
||||
VkImage image,
|
||||
VkImageLayout oldImageLayout,
|
||||
VkImageLayout newImageLayout,
|
||||
VkImageSubresourceRange subresourceRange,
|
||||
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
|
||||
// Uses a fixed sub resource layout with first mip level and layer
|
||||
void setImageLayout(
|
||||
VkCommandBuffer cmdbuffer,
|
||||
VkImage image,
|
||||
VkImageAspectFlags aspectMask,
|
||||
VkImageLayout oldImageLayout,
|
||||
VkImageLayout newImageLayout,
|
||||
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
|
||||
|
||||
/** @brief Insert an image memory barrier into the command buffer */
|
||||
void insertImageMemoryBarrier(
|
||||
VkCommandBuffer cmdbuffer,
|
||||
VkImage image,
|
||||
VkAccessFlags srcAccessMask,
|
||||
VkAccessFlags dstAccessMask,
|
||||
VkImageLayout oldImageLayout,
|
||||
VkImageLayout newImageLayout,
|
||||
VkPipelineStageFlags srcStageMask,
|
||||
VkPipelineStageFlags dstStageMask,
|
||||
VkImageSubresourceRange subresourceRange);
|
||||
|
||||
// Display error message and exit on fatal error
|
||||
void exitFatal(const std::string& message, int32_t exitCode);
|
||||
void exitFatal(const std::string& message, VkResult resultCode);
|
||||
|
||||
// Load a SPIR-V shader (binary)
|
||||
#if defined(__ANDROID__)
|
||||
VkShaderModule loadShader(AAssetManager* assetManager, const char *fileName, VkDevice device);
|
||||
#elif defined (__OHOS__)
|
||||
VkShaderModule loadShader(const char *fileName, VkDevice device);
|
||||
#else
|
||||
VkShaderModule loadShader(const char *fileName, VkDevice device);
|
||||
#endif
|
||||
|
||||
/** @brief Checks if a file exists */
|
||||
bool fileExists(const std::string &filename);
|
||||
|
||||
uint32_t alignedSize(uint32_t value, uint32_t alignment);
|
||||
VkDeviceSize alignedVkSize(VkDeviceSize value, VkDeviceSize alignment);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,514 @@
|
||||
|
||||
/*
|
||||
* UI overlay class using ImGui
|
||||
*
|
||||
* Copyright (C) 2017-2026 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#include "VulkanUIOverlay.h"
|
||||
|
||||
namespace vks
|
||||
{
|
||||
UIOverlay::UIOverlay()
|
||||
{
|
||||
#if defined(__ANDROID__)
|
||||
if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XXHIGH) {
|
||||
scale = 3.5f;
|
||||
}
|
||||
else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XHIGH) {
|
||||
scale = 2.5f;
|
||||
}
|
||||
else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_HIGH) {
|
||||
scale = 2.0f;
|
||||
};
|
||||
#endif
|
||||
#if defined (__OHOS__)
|
||||
if (vks::OHOS::screenDensity >= 480) {
|
||||
scale = 3.5f;
|
||||
} else if (vks::OHOS::screenDensity >= 320) {
|
||||
scale = 2.5f;
|
||||
} else if (vks::OHOS::screenDensity >= 240) {
|
||||
scale = 2.0f;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Init ImGui
|
||||
ImGui::CreateContext();
|
||||
// Color scheme
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
style.Colors[ImGuiCol_TitleBg] = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
style.Colors[ImGuiCol_TitleBgActive] = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.0f, 0.0f, 0.0f, 0.1f);
|
||||
style.Colors[ImGuiCol_MenuBarBg] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
|
||||
style.Colors[ImGuiCol_Header] = ImVec4(0.8f, 0.0f, 0.0f, 0.4f);
|
||||
style.Colors[ImGuiCol_HeaderActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
|
||||
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
|
||||
style.Colors[ImGuiCol_FrameBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.8f);
|
||||
style.Colors[ImGuiCol_CheckMark] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f);
|
||||
style.Colors[ImGuiCol_SliderGrab] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
|
||||
style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f);
|
||||
style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(1.0f, 1.0f, 1.0f, 0.1f);
|
||||
style.Colors[ImGuiCol_FrameBgActive] = ImVec4(1.0f, 1.0f, 1.0f, 0.2f);
|
||||
style.Colors[ImGuiCol_Button] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
|
||||
style.Colors[ImGuiCol_ButtonHovered] = ImVec4(1.0f, 0.0f, 0.0f, 0.6f);
|
||||
style.Colors[ImGuiCol_ButtonActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f);
|
||||
#if defined(VK_EXAMPLE_PS5)
|
||||
// PS5: in the title's theme, when it has the UI module
|
||||
ps5StyleOverlay(style);
|
||||
#endif
|
||||
// Dimensions
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.FontGlobalScale = scale;
|
||||
}
|
||||
|
||||
UIOverlay::~UIOverlay() {
|
||||
if (ImGui::GetCurrentContext()) {
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
}
|
||||
|
||||
/** Prepare all vulkan resources required to render the UI overlay */
|
||||
void UIOverlay::prepare()
|
||||
{
|
||||
assert(maxConcurrentFrames > 0);
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
// Create font texture
|
||||
unsigned char* fontData;
|
||||
int texWidth, texHeight;
|
||||
#if defined(__ANDROID__)
|
||||
float scale = (float)vks::android::screenDensity / (float)ACONFIGURATION_DENSITY_MEDIUM;
|
||||
AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, "Roboto-Medium.ttf", AASSET_MODE_STREAMING);
|
||||
if (asset) {
|
||||
size_t size = AAsset_getLength(asset);
|
||||
assert(size > 0);
|
||||
char *fontAsset = new char[size];
|
||||
AAsset_read(asset, fontAsset, size);
|
||||
AAsset_close(asset);
|
||||
io.Fonts->AddFontFromMemoryTTF(fontAsset, size, 14.0f * scale);
|
||||
delete[] fontAsset;
|
||||
}
|
||||
#elif defined (__OHOS__)
|
||||
float scale = (float)vks::OHOS::screenDensity / 160.0f;
|
||||
NativeResourceManager* resourceMgr = ResourceManager::getInstance().getNativeResourceManager();
|
||||
if (!resourceMgr) {
|
||||
std::string msg = "ResourceManager not initialized. Call ResourceManager::getInstance().initialize() first.";
|
||||
// vks::tools::exitFatal(msg, -1);
|
||||
}
|
||||
RawFile *rawfile = OH_ResourceManager_OpenRawFile(resourceMgr, "Roboto-Medium.ttf");
|
||||
if (rawfile) {
|
||||
size_t size = OH_ResourceManager_GetRawFileSize(rawfile);
|
||||
assert(size > 0);
|
||||
|
||||
char *fontAsset = new char[size];
|
||||
OH_ResourceManager_ReadRawFile(rawfile, fontAsset, size);
|
||||
OH_ResourceManager_CloseRawFile(rawfile);
|
||||
io.Fonts->AddFontFromMemoryTTF(fontAsset, size, 14.0f * scale);
|
||||
delete[] fontAsset;
|
||||
}
|
||||
#else
|
||||
std::string filename = getAssetPath() + "Roboto-Medium.ttf";
|
||||
float fontSize = 16.0f;
|
||||
#if defined(VK_EXAMPLE_PS5)
|
||||
// PS5: the theme's typeface, when it has one
|
||||
ps5OverlayFont(filename, fontSize);
|
||||
#endif
|
||||
io.Fonts->AddFontFromFileTTF(filename.c_str(), fontSize * scale);
|
||||
#endif
|
||||
io.Fonts->GetTexDataAsRGBA32(&fontData, &texWidth, &texHeight);
|
||||
VkDeviceSize uploadSize = texWidth*texHeight * 4 * sizeof(char);
|
||||
|
||||
// Set ImGui style scale factor to handle retina and other HiDPI displays (same as font scaling above)
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
style.ScaleAllSizes(scale);
|
||||
|
||||
// Create target image for copy
|
||||
VkImageCreateInfo imageInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = VK_FORMAT_R8G8B8A8_UNORM,
|
||||
.extent = {.width = (uint32_t)texWidth, .height = (uint32_t)texHeight, .depth = 1 },
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageInfo, nullptr, &fontImage));
|
||||
VkMemoryRequirements memReqs;
|
||||
vkGetImageMemoryRequirements(device->logicalDevice, fontImage, &memReqs);
|
||||
VkMemoryAllocateInfo memAllocInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.allocationSize = memReqs.size,
|
||||
.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
|
||||
};
|
||||
VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &fontMemory));
|
||||
VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, fontImage, fontMemory, 0));
|
||||
|
||||
// Image view
|
||||
VkImageViewCreateInfo viewInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = fontImage,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = VK_FORMAT_R8G8B8A8_UNORM,
|
||||
.subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .levelCount = 1, .layerCount = 1 }
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewInfo, nullptr, &fontView));
|
||||
|
||||
// Staging buffers for font data upload
|
||||
vks::Buffer stagingBuffer;
|
||||
VK_CHECK_RESULT(device->createBuffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, uploadSize));
|
||||
stagingBuffer.map();
|
||||
memcpy(stagingBuffer.mapped, fontData, uploadSize);
|
||||
|
||||
// Copy buffer data to font image
|
||||
VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
// Prepare for transfer
|
||||
vks::tools::setImageLayout(
|
||||
copyCmd,
|
||||
fontImage,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_HOST_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
// Copy
|
||||
VkBufferImageCopy bufferCopyRegion{
|
||||
.imageSubresource = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .layerCount = 1 },
|
||||
.imageExtent = {.width = (uint32_t)texWidth, .height = (uint32_t)texHeight, .depth = 1 }
|
||||
};
|
||||
vkCmdCopyBufferToImage(copyCmd, stagingBuffer.buffer, fontImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferCopyRegion);
|
||||
// Prepare for shader read
|
||||
vks::tools::setImageLayout(
|
||||
copyCmd,
|
||||
fontImage,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
||||
device->flushCommandBuffer(copyCmd, queue, true);
|
||||
|
||||
stagingBuffer.destroy();
|
||||
|
||||
// Font texture Sampler
|
||||
VkSamplerCreateInfo samplerInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.magFilter = VK_FILTER_LINEAR,
|
||||
.minFilter = VK_FILTER_LINEAR,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
||||
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||
.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||
.maxAnisotropy = 1.0f,
|
||||
.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE,
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerInfo, nullptr, &sampler));
|
||||
|
||||
// Descriptor pool
|
||||
VkDescriptorPoolSize poolSize{ .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1 };
|
||||
VkDescriptorPoolCreateInfo descriptorPoolInfo{ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .maxSets = 2, .poolSizeCount = 1, .pPoolSizes = &poolSize };
|
||||
VK_CHECK_RESULT(vkCreateDescriptorPool(device->logicalDevice, &descriptorPoolInfo, nullptr, &descriptorPool));
|
||||
|
||||
// Descriptor set layout
|
||||
VkDescriptorSetLayoutBinding setLayoutBinding = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0);
|
||||
VkDescriptorSetLayoutCreateInfo descriptorLayout{ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = 1, .pBindings = &setLayoutBinding };
|
||||
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device->logicalDevice, &descriptorLayout, nullptr, &descriptorSetLayout));
|
||||
|
||||
// Descriptor set
|
||||
VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
|
||||
VK_CHECK_RESULT(vkAllocateDescriptorSets(device->logicalDevice, &allocInfo, &descriptorSet));
|
||||
VkDescriptorImageInfo fontDescriptor{ .sampler = sampler, .imageView = fontView, .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL };
|
||||
VkWriteDescriptorSet writeDescriptorSets = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &fontDescriptor);
|
||||
vkUpdateDescriptorSets(device->logicalDevice, 1, &writeDescriptorSets, 0, nullptr);
|
||||
|
||||
// Buffers per max. frames-in-flight
|
||||
buffers.resize(maxConcurrentFrames);
|
||||
|
||||
// Pipeline layout
|
||||
// Push constants for UI rendering parameters
|
||||
VkPushConstantRange pushConstantRange{ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, .size = sizeof(PushConstBlock) };
|
||||
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||
.setLayoutCount = 1,
|
||||
.pSetLayouts = &descriptorSetLayout,
|
||||
.pushConstantRangeCount = 1,
|
||||
.pPushConstantRanges = &pushConstantRange
|
||||
};
|
||||
VK_CHECK_RESULT(vkCreatePipelineLayout(device->logicalDevice, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
|
||||
|
||||
// Setup graphics pipeline for UI rendering
|
||||
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
|
||||
VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE);
|
||||
|
||||
// Enable blending
|
||||
VkPipelineColorBlendAttachmentState blendAttachmentState{
|
||||
.blendEnable = VK_TRUE,
|
||||
.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
|
||||
.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
|
||||
.colorBlendOp = VK_BLEND_OP_ADD,
|
||||
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
|
||||
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
|
||||
.alphaBlendOp = VK_BLEND_OP_ADD,
|
||||
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT
|
||||
};
|
||||
|
||||
// Vertex bindings an attributes based on ImGui vertex definition
|
||||
std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
|
||||
{ .binding = 0, .stride = sizeof(ImDrawVert), .inputRate = VK_VERTEX_INPUT_RATE_VERTEX }
|
||||
};
|
||||
std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
|
||||
{ .location = 0, .binding = 0, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(ImDrawVert, pos) },
|
||||
{ .location = 1, .binding = 0, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(ImDrawVert, uv) },
|
||||
{ .location = 2, .binding = 0, .format = VK_FORMAT_R8G8B8A8_UNORM, .offset = offsetof(ImDrawVert, col) },
|
||||
};
|
||||
VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
|
||||
vertexInputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size());
|
||||
vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data();
|
||||
vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
|
||||
vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data();
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
|
||||
VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_ALWAYS);
|
||||
VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
|
||||
VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(rasterizationSamples);
|
||||
std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
|
||||
VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
|
||||
VkGraphicsPipelineCreateInfo pipelineCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.stageCount = static_cast<uint32_t>(shaders.size()),
|
||||
.pStages = shaders.data(),
|
||||
.pVertexInputState = &vertexInputState,
|
||||
.pInputAssemblyState = &inputAssemblyState,
|
||||
.pViewportState = &viewportState,
|
||||
.pRasterizationState = &rasterizationState,
|
||||
.pMultisampleState = &multisampleState,
|
||||
.pDepthStencilState = &depthStencilState,
|
||||
.pColorBlendState = &colorBlendState,
|
||||
.pDynamicState = &dynamicState,
|
||||
.layout = pipelineLayout,
|
||||
.renderPass = renderPass,
|
||||
.subpass = subpass,
|
||||
};
|
||||
#if defined(VK_KHR_dynamic_rendering)
|
||||
// If we are using dynamic rendering (renderPass is null), we must define color, depth and stencil attachments at pipeline create time
|
||||
VkPipelineRenderingCreateInfo pipelineRenderingCreateInfo{};
|
||||
if (renderPass == VK_NULL_HANDLE) {
|
||||
pipelineRenderingCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
|
||||
.colorAttachmentCount = 1,
|
||||
.pColorAttachmentFormats = &colorFormat,
|
||||
.depthAttachmentFormat = depthFormat,
|
||||
.stencilAttachmentFormat = depthFormat
|
||||
};
|
||||
pipelineCreateInfo.pNext = &pipelineRenderingCreateInfo;
|
||||
}
|
||||
#endif
|
||||
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device->logicalDevice, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &pipeline));
|
||||
}
|
||||
|
||||
/** Update vertex and index buffer containing the imGui elements when required */
|
||||
void UIOverlay::update(uint32_t currentBuffer)
|
||||
{
|
||||
ImDrawData* imDrawData = ImGui::GetDrawData();
|
||||
|
||||
if (!imDrawData) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: Alignment is done inside buffer creation
|
||||
VkDeviceSize vertexBufferSize = imDrawData->TotalVtxCount * sizeof(ImDrawVert);
|
||||
VkDeviceSize indexBufferSize = imDrawData->TotalIdxCount * sizeof(ImDrawIdx);
|
||||
|
||||
// Update buffers only if vertex or index count has been changed compared to current buffer size
|
||||
if ((vertexBufferSize == 0) || (indexBufferSize == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create buffers with multiple of a chunk size to minimize the need to recreate them
|
||||
const VkDeviceSize chunkSize = 16384;
|
||||
vertexBufferSize = ((vertexBufferSize + chunkSize - 1) / chunkSize) * chunkSize;
|
||||
indexBufferSize = ((indexBufferSize + chunkSize - 1) / chunkSize) * chunkSize;
|
||||
|
||||
// Recreate vertex buffer only if necessary
|
||||
if ((buffers[currentBuffer].vertexBuffer.buffer == VK_NULL_HANDLE) || (buffers[currentBuffer].vertexBuffer.size < vertexBufferSize)) {
|
||||
buffers[currentBuffer].vertexBuffer.unmap();
|
||||
buffers[currentBuffer].vertexBuffer.destroy();
|
||||
VK_CHECK_RESULT(device->createBuffer(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &buffers[currentBuffer].vertexBuffer, vertexBufferSize));
|
||||
buffers[currentBuffer].vertexCount = imDrawData->TotalVtxCount;
|
||||
buffers[currentBuffer].vertexBuffer.map();
|
||||
}
|
||||
|
||||
// Recreate index buffer only if necessary
|
||||
if ((buffers[currentBuffer].indexBuffer.buffer == VK_NULL_HANDLE) || (buffers[currentBuffer].indexBuffer.size < indexBufferSize)) {
|
||||
buffers[currentBuffer].indexBuffer.unmap();
|
||||
buffers[currentBuffer].indexBuffer.destroy();
|
||||
VK_CHECK_RESULT(device->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &buffers[currentBuffer].indexBuffer, indexBufferSize));
|
||||
buffers[currentBuffer].indexCount = imDrawData->TotalIdxCount;
|
||||
buffers[currentBuffer].indexBuffer.map();
|
||||
}
|
||||
|
||||
// Upload data
|
||||
ImDrawVert* vtxDst = (ImDrawVert*)buffers[currentBuffer].vertexBuffer.mapped;
|
||||
ImDrawIdx* idxDst = (ImDrawIdx*)buffers[currentBuffer].indexBuffer.mapped;
|
||||
|
||||
for (int n = 0; n < imDrawData->CmdListsCount; n++) {
|
||||
const ImDrawList* cmd_list = imDrawData->CmdLists[n];
|
||||
memcpy(vtxDst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
|
||||
memcpy(idxDst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
|
||||
vtxDst += cmd_list->VtxBuffer.Size;
|
||||
idxDst += cmd_list->IdxBuffer.Size;
|
||||
}
|
||||
|
||||
// Flush to make writes visible to GPU
|
||||
buffers[currentBuffer].vertexBuffer.flush();
|
||||
buffers[currentBuffer].indexBuffer.flush();
|
||||
}
|
||||
|
||||
void UIOverlay::draw(const VkCommandBuffer commandBuffer, uint32_t currentBuffer)
|
||||
{
|
||||
ImDrawData* imDrawData = ImGui::GetDrawData();
|
||||
int32_t vertexOffset = 0;
|
||||
int32_t indexOffset = 0;
|
||||
|
||||
if ((!imDrawData) || (imDrawData->CmdListsCount == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffers[currentBuffer].vertexBuffer.buffer == VK_NULL_HANDLE || buffers[currentBuffer].indexBuffer.buffer == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
|
||||
|
||||
pushConstBlock.scale = glm::vec2(2.0f / io.DisplaySize.x, 2.0f / io.DisplaySize.y);
|
||||
pushConstBlock.translate = glm::vec2(-1.0f);
|
||||
vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
|
||||
|
||||
assert(buffers[currentBuffer].vertexBuffer.buffer != VK_NULL_HANDLE && buffers[currentBuffer].indexBuffer.buffer != VK_NULL_HANDLE);
|
||||
|
||||
VkDeviceSize offsets[1] = { 0 };
|
||||
vkCmdBindVertexBuffers(commandBuffer, 0, 1, &buffers[currentBuffer].vertexBuffer.buffer, offsets);
|
||||
vkCmdBindIndexBuffer(commandBuffer, buffers[currentBuffer].indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT16);
|
||||
|
||||
for (int32_t i = 0; i < imDrawData->CmdListsCount; i++) {
|
||||
const ImDrawList* cmd_list = imDrawData->CmdLists[i];
|
||||
for (int32_t j = 0; j < cmd_list->CmdBuffer.Size; j++) {
|
||||
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[j];
|
||||
VkRect2D scissorRect{
|
||||
.offset = {.x = std::max((int32_t)(pcmd->ClipRect.x), 0), .y = std::max((int32_t)(pcmd->ClipRect.y), 0) },
|
||||
.extent = {.width = (uint32_t)(pcmd->ClipRect.z - pcmd->ClipRect.x), .height = (uint32_t)(pcmd->ClipRect.w - pcmd->ClipRect.y) }
|
||||
};
|
||||
vkCmdSetScissor(commandBuffer, 0, 1, &scissorRect);
|
||||
vkCmdDrawIndexed(commandBuffer, pcmd->ElemCount, 1, indexOffset, vertexOffset, 0);
|
||||
indexOffset += pcmd->ElemCount;
|
||||
}
|
||||
#if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) && TARGET_OS_SIMULATOR
|
||||
// Apple Device Simulator does not support vkCmdDrawIndexed() with vertexOffset > 0, so rebind vertex buffer instead
|
||||
offsets[0] += cmd_list->VtxBuffer.Size * sizeof(ImDrawVert);
|
||||
vkCmdBindVertexBuffers(commandBuffer, 0, 1, &buffers[currentBuffer].vertexBuffer.buffer, offsets);
|
||||
#else
|
||||
vertexOffset += cmd_list->VtxBuffer.Size;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void UIOverlay::resize(uint32_t width, uint32_t height)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.DisplaySize = ImVec2((float)(width), (float)(height));
|
||||
}
|
||||
|
||||
void UIOverlay::freeResources()
|
||||
{
|
||||
for (auto& buffer : buffers) {
|
||||
buffer.vertexBuffer.destroy();
|
||||
buffer.indexBuffer.destroy();
|
||||
}
|
||||
vkDestroyImageView(device->logicalDevice, fontView, nullptr);
|
||||
vkDestroyImage(device->logicalDevice, fontImage, nullptr);
|
||||
vkFreeMemory(device->logicalDevice, fontMemory, nullptr);
|
||||
vkDestroySampler(device->logicalDevice, sampler, nullptr);
|
||||
vkDestroyDescriptorSetLayout(device->logicalDevice, descriptorSetLayout, nullptr);
|
||||
vkDestroyDescriptorPool(device->logicalDevice, descriptorPool, nullptr);
|
||||
vkDestroyPipelineLayout(device->logicalDevice, pipelineLayout, nullptr);
|
||||
vkDestroyPipeline(device->logicalDevice, pipeline, nullptr);
|
||||
}
|
||||
|
||||
bool UIOverlay::header(const char *caption)
|
||||
{
|
||||
return ImGui::CollapsingHeader(caption, ImGuiTreeNodeFlags_DefaultOpen);
|
||||
}
|
||||
|
||||
bool UIOverlay::checkBox(const char *caption, bool *value)
|
||||
{
|
||||
return ImGui::Checkbox(caption, value);
|
||||
}
|
||||
|
||||
bool UIOverlay::checkBox(const char *caption, int32_t *value)
|
||||
{
|
||||
bool val = (*value == 1);
|
||||
bool res = ImGui::Checkbox(caption, &val);
|
||||
*value = val;
|
||||
return res;
|
||||
}
|
||||
|
||||
bool UIOverlay::radioButton(const char* caption, bool value)
|
||||
{
|
||||
return ImGui::RadioButton(caption, value);
|
||||
}
|
||||
|
||||
bool UIOverlay::inputFloat(const char *caption, float *value, float step, uint32_t precision)
|
||||
{
|
||||
return ImGui::InputFloat(caption, value, step, step * 10.0f, precision);
|
||||
}
|
||||
|
||||
bool UIOverlay::sliderFloat(const char* caption, float* value, float min, float max)
|
||||
{
|
||||
return ImGui::SliderFloat(caption, value, min, max);
|
||||
}
|
||||
|
||||
bool UIOverlay::sliderInt(const char* caption, int32_t* value, int32_t min, int32_t max)
|
||||
{
|
||||
return ImGui::SliderInt(caption, value, min, max);
|
||||
}
|
||||
|
||||
bool UIOverlay::comboBox(const char *caption, int32_t *itemindex, std::vector<std::string> items)
|
||||
{
|
||||
if (items.empty()) {
|
||||
return false;
|
||||
}
|
||||
std::vector<const char*> charitems;
|
||||
charitems.reserve(items.size());
|
||||
for (size_t i = 0; i < items.size(); i++) {
|
||||
charitems.push_back(items[i].c_str());
|
||||
}
|
||||
uint32_t itemCount = static_cast<uint32_t>(charitems.size());
|
||||
return ImGui::Combo(caption, itemindex, &charitems[0], itemCount, itemCount);
|
||||
}
|
||||
|
||||
bool UIOverlay::button(const char *caption)
|
||||
{
|
||||
return ImGui::Button(caption);
|
||||
}
|
||||
|
||||
bool UIOverlay::colorPicker(const char* caption, float* color) {
|
||||
return ImGui::ColorEdit4(caption, color, ImGuiColorEditFlags_NoInputs);
|
||||
}
|
||||
|
||||
void UIOverlay::text(const char *formatstr, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, formatstr);
|
||||
ImGui::TextV(formatstr, args);
|
||||
va_end(args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* UI overlay class using ImGui
|
||||
*
|
||||
* Copyright (C) 2017-2026 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#include "VulkanTools.h"
|
||||
#include "VulkanDebug.h"
|
||||
#include "VulkanBuffer.h"
|
||||
#include "VulkanDevice.h"
|
||||
|
||||
#include "../external/imgui/imgui.h"
|
||||
|
||||
#if defined(VK_EXAMPLE_PS5)
|
||||
#include <string>
|
||||
// PS5: the title's theme for the overlay. The UI module (ps5/ui/overlay_theme.cpp)
|
||||
// defines both; without it ps5/src/example_ps5.cpp's weak ones keep upstream's look.
|
||||
void ps5StyleOverlay(ImGuiStyle &style);
|
||||
bool ps5OverlayFont(std::string &path, float &size);
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include "VulkanAndroid.h"
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
namespace vks
|
||||
{
|
||||
class UIOverlay
|
||||
{
|
||||
public:
|
||||
vks::VulkanDevice* device{ nullptr };
|
||||
VkQueue queue{ VK_NULL_HANDLE };
|
||||
|
||||
VkSampleCountFlagBits rasterizationSamples{ VK_SAMPLE_COUNT_1_BIT };
|
||||
uint32_t subpass{ 0 };
|
||||
|
||||
struct Buffers {
|
||||
vks::Buffer vertexBuffer;
|
||||
vks::Buffer indexBuffer;
|
||||
int32_t vertexCount{ 0 };
|
||||
int32_t indexCount{ 0 };
|
||||
};
|
||||
std::vector<Buffers> buffers;
|
||||
uint32_t maxConcurrentFrames{ 0 };
|
||||
uint32_t currentBuffer{ 0 };
|
||||
|
||||
std::vector<VkPipelineShaderStageCreateInfo> shaders;
|
||||
|
||||
VkDescriptorPool descriptorPool{ VK_NULL_HANDLE };
|
||||
VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE };
|
||||
VkDescriptorSet descriptorSet{ VK_NULL_HANDLE };
|
||||
VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE };
|
||||
VkPipeline pipeline{ VK_NULL_HANDLE };
|
||||
|
||||
VkDeviceMemory fontMemory{ VK_NULL_HANDLE };
|
||||
VkImage fontImage{ VK_NULL_HANDLE };
|
||||
VkImageView fontView{ VK_NULL_HANDLE };
|
||||
VkSampler sampler{ VK_NULL_HANDLE };
|
||||
|
||||
struct PushConstBlock {
|
||||
glm::vec2 scale;
|
||||
glm::vec2 translate;
|
||||
} pushConstBlock;
|
||||
|
||||
bool visible{ true };
|
||||
float scale{ 1.0f };
|
||||
|
||||
VkRenderPass renderPass{ VK_NULL_HANDLE };
|
||||
VkFormat colorFormat{ VK_FORMAT_UNDEFINED };
|
||||
VkFormat depthFormat{ VK_FORMAT_UNDEFINED };
|
||||
|
||||
UIOverlay();
|
||||
~UIOverlay();
|
||||
|
||||
void prepare();
|
||||
|
||||
void update(uint32_t currentBuffer);
|
||||
void draw(const VkCommandBuffer commandBuffer, uint32_t currentBuffer);
|
||||
void resize(uint32_t width, uint32_t height);
|
||||
|
||||
void freeResources();
|
||||
|
||||
bool header(const char* caption);
|
||||
bool checkBox(const char* caption, bool* value);
|
||||
bool checkBox(const char* caption, int32_t* value);
|
||||
bool radioButton(const char* caption, bool value);
|
||||
bool inputFloat(const char* caption, float* value, float step, uint32_t precision);
|
||||
bool sliderFloat(const char* caption, float* value, float min, float max);
|
||||
bool sliderInt(const char* caption, int32_t* value, int32_t min, int32_t max);
|
||||
bool comboBox(const char* caption, int32_t* itemindex, std::vector<std::string> items);
|
||||
bool button(const char* caption);
|
||||
bool colorPicker(const char* caption, float* color);
|
||||
void text(const char* formatstr, ...);
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Vulkan glTF model and texture loading class based on tinyglTF (https://github.com/syoyo/tinygltf)
|
||||
*
|
||||
* Copyright (C) 2018-2026 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
/*
|
||||
* Note that this isn't a complete glTF loader and not all features of the glTF 2.0 spec are supported
|
||||
* For details on how glTF 2.0 works, see the official spec at https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
|
||||
*
|
||||
* If you are looking for a complete glTF implementation, check out https://github.com/SaschaWillems/Vulkan-glTF-PBR/
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
#include "vulkan/vulkan.h"
|
||||
#include "VulkanDevice.h"
|
||||
|
||||
#include <ktx.h>
|
||||
#include <ktxvulkan.h>
|
||||
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#define TINYGLTF_NO_STB_IMAGE_WRITE
|
||||
#ifdef VK_USE_PLATFORM_ANDROID_KHR
|
||||
#define TINYGLTF_ANDROID_LOAD_FROM_ASSETS
|
||||
#endif
|
||||
#include "tiny_gltf.h"
|
||||
|
||||
#ifdef __OHOS__
|
||||
#include <rawfile/raw_file_manager.h>
|
||||
#endif
|
||||
#if defined(__ANDROID__)
|
||||
#include <android/asset_manager.h>
|
||||
#endif
|
||||
|
||||
namespace vkglTF
|
||||
{
|
||||
enum DescriptorBindingFlags {
|
||||
ImageBaseColor = 0x00000001,
|
||||
ImageNormalMap = 0x00000002
|
||||
};
|
||||
|
||||
extern VkDescriptorSetLayout descriptorSetLayoutImage;
|
||||
extern VkDescriptorSetLayout descriptorSetLayoutUbo;
|
||||
extern VkMemoryPropertyFlags memoryPropertyFlags;
|
||||
extern uint32_t descriptorBindingFlags;
|
||||
|
||||
struct Node;
|
||||
|
||||
/*
|
||||
glTF texture loading class
|
||||
*/
|
||||
struct Texture {
|
||||
vks::VulkanDevice* device = nullptr;
|
||||
VkImage image;
|
||||
VkImageLayout imageLayout;
|
||||
VkDeviceMemory deviceMemory;
|
||||
VkImageView view;
|
||||
uint32_t width, height;
|
||||
uint32_t mipLevels;
|
||||
uint32_t layerCount;
|
||||
VkDescriptorImageInfo descriptor;
|
||||
VkSampler sampler;
|
||||
uint32_t index;
|
||||
void updateDescriptor();
|
||||
void destroy();
|
||||
void fromglTfImage(tinygltf::Image& gltfimage, std::string path, vks::VulkanDevice* device, VkQueue copyQueue);
|
||||
};
|
||||
|
||||
/*
|
||||
glTF material class
|
||||
*/
|
||||
struct Material {
|
||||
vks::VulkanDevice* device = nullptr;
|
||||
enum AlphaMode { ALPHAMODE_OPAQUE, ALPHAMODE_MASK, ALPHAMODE_BLEND };
|
||||
AlphaMode alphaMode = ALPHAMODE_OPAQUE;
|
||||
float alphaCutoff = 1.0f;
|
||||
float metallicFactor = 1.0f;
|
||||
float roughnessFactor = 1.0f;
|
||||
glm::vec4 baseColorFactor = glm::vec4(1.0f);
|
||||
vkglTF::Texture* baseColorTexture = nullptr;
|
||||
vkglTF::Texture* metallicRoughnessTexture = nullptr;
|
||||
vkglTF::Texture* normalTexture = nullptr;
|
||||
vkglTF::Texture* occlusionTexture = nullptr;
|
||||
vkglTF::Texture* emissiveTexture = nullptr;
|
||||
|
||||
vkglTF::Texture* specularGlossinessTexture;
|
||||
vkglTF::Texture* diffuseTexture;
|
||||
|
||||
VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
|
||||
|
||||
Material(vks::VulkanDevice* device) : device(device) {};
|
||||
void createDescriptorSet(VkDescriptorPool descriptorPool, VkDescriptorSetLayout descriptorSetLayout, uint32_t descriptorBindingFlags);
|
||||
};
|
||||
|
||||
/*
|
||||
glTF primitive
|
||||
*/
|
||||
struct Primitive {
|
||||
uint32_t firstIndex;
|
||||
uint32_t indexCount;
|
||||
uint32_t firstVertex;
|
||||
uint32_t vertexCount;
|
||||
Material& material;
|
||||
|
||||
struct Dimensions {
|
||||
glm::vec3 min = glm::vec3(FLT_MAX);
|
||||
glm::vec3 max = glm::vec3(-FLT_MAX);
|
||||
glm::vec3 size;
|
||||
glm::vec3 center;
|
||||
float radius;
|
||||
} dimensions;
|
||||
|
||||
void setDimensions(glm::vec3 min, glm::vec3 max);
|
||||
Primitive(uint32_t firstIndex, uint32_t indexCount, Material& material) : firstIndex(firstIndex), indexCount(indexCount), material(material) {};
|
||||
};
|
||||
|
||||
/*
|
||||
glTF mesh
|
||||
*/
|
||||
struct Mesh {
|
||||
vks::VulkanDevice* device;
|
||||
|
||||
std::vector<Primitive*> primitives;
|
||||
std::string name;
|
||||
|
||||
struct UniformBuffer {
|
||||
VkBuffer buffer;
|
||||
VkDeviceMemory memory;
|
||||
VkDescriptorBufferInfo descriptor;
|
||||
VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
|
||||
void* mapped;
|
||||
} uniformBuffer;
|
||||
|
||||
struct UniformBlock {
|
||||
glm::mat4 matrix;
|
||||
glm::mat4 jointMatrix[64]{};
|
||||
float jointcount{ 0 };
|
||||
} uniformBlock;
|
||||
|
||||
Mesh(vks::VulkanDevice* device, glm::mat4 matrix);
|
||||
~Mesh();
|
||||
};
|
||||
|
||||
/*
|
||||
glTF skin
|
||||
*/
|
||||
struct Skin {
|
||||
std::string name;
|
||||
Node* skeletonRoot = nullptr;
|
||||
std::vector<glm::mat4> inverseBindMatrices;
|
||||
std::vector<Node*> joints;
|
||||
};
|
||||
|
||||
/*
|
||||
glTF node
|
||||
*/
|
||||
struct Node {
|
||||
Node* parent;
|
||||
uint32_t index;
|
||||
std::vector<Node*> children;
|
||||
glm::mat4 matrix;
|
||||
std::string name;
|
||||
Mesh* mesh;
|
||||
Skin* skin;
|
||||
int32_t skinIndex = -1;
|
||||
glm::vec3 translation{};
|
||||
glm::vec3 scale{ 1.0f };
|
||||
glm::quat rotation{};
|
||||
glm::mat4 localMatrix();
|
||||
glm::mat4 getMatrix();
|
||||
void update();
|
||||
~Node();
|
||||
};
|
||||
|
||||
/*
|
||||
glTF animation channel
|
||||
*/
|
||||
struct AnimationChannel {
|
||||
enum PathType { TRANSLATION, ROTATION, SCALE };
|
||||
PathType path;
|
||||
Node* node;
|
||||
uint32_t samplerIndex;
|
||||
};
|
||||
|
||||
/*
|
||||
glTF animation sampler
|
||||
*/
|
||||
struct AnimationSampler {
|
||||
enum InterpolationType { LINEAR, STEP, CUBICSPLINE };
|
||||
InterpolationType interpolation;
|
||||
std::vector<float> inputs;
|
||||
std::vector<glm::vec4> outputsVec4;
|
||||
};
|
||||
|
||||
/*
|
||||
glTF animation
|
||||
*/
|
||||
struct Animation {
|
||||
std::string name;
|
||||
std::vector<AnimationSampler> samplers;
|
||||
std::vector<AnimationChannel> channels;
|
||||
float start = std::numeric_limits<float>::max();
|
||||
float end = std::numeric_limits<float>::min();
|
||||
};
|
||||
|
||||
/*
|
||||
glTF default vertex layout with easy Vulkan mapping functions
|
||||
*/
|
||||
enum class VertexComponent { Position, Normal, UV, Color, Tangent, Joint0, Weight0 };
|
||||
|
||||
struct Vertex {
|
||||
glm::vec3 pos;
|
||||
glm::vec3 normal;
|
||||
glm::vec2 uv;
|
||||
glm::vec4 color;
|
||||
glm::vec4 joint0;
|
||||
glm::vec4 weight0;
|
||||
glm::vec4 tangent;
|
||||
static VkVertexInputBindingDescription vertexInputBindingDescription;
|
||||
static std::vector<VkVertexInputAttributeDescription> vertexInputAttributeDescriptions;
|
||||
static VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo;
|
||||
static VkVertexInputBindingDescription inputBindingDescription(uint32_t binding);
|
||||
static VkVertexInputAttributeDescription inputAttributeDescription(uint32_t binding, uint32_t location, VertexComponent component);
|
||||
static std::vector<VkVertexInputAttributeDescription> inputAttributeDescriptions(uint32_t binding, const std::vector<VertexComponent> components);
|
||||
/** @brief Returns the default pipeline vertex input state create info structure for the requested vertex components */
|
||||
static VkPipelineVertexInputStateCreateInfo* getPipelineVertexInputState(const std::vector<VertexComponent> components);
|
||||
};
|
||||
|
||||
enum FileLoadingFlags {
|
||||
None = 0x00000000,
|
||||
PreTransformVertices = 0x00000001,
|
||||
PreMultiplyVertexColors = 0x00000002,
|
||||
FlipY = 0x00000004,
|
||||
DontLoadImages = 0x00000008,
|
||||
FlipUV = 0x00000010
|
||||
};
|
||||
|
||||
enum RenderFlags {
|
||||
BindImages = 0x00000001,
|
||||
RenderOpaqueNodes = 0x00000002,
|
||||
RenderAlphaMaskedNodes = 0x00000004,
|
||||
RenderAlphaBlendedNodes = 0x00000008
|
||||
};
|
||||
|
||||
/*
|
||||
glTF model loading and rendering class
|
||||
*/
|
||||
class Model {
|
||||
private:
|
||||
vkglTF::Texture* getTexture(uint32_t index);
|
||||
vkglTF::Texture emptyTexture;
|
||||
void createEmptyTexture(VkQueue transferQueue);
|
||||
public:
|
||||
vks::VulkanDevice* device;
|
||||
VkDescriptorPool descriptorPool;
|
||||
|
||||
struct Vertices {
|
||||
int count;
|
||||
VkBuffer buffer;
|
||||
VkDeviceMemory memory;
|
||||
} vertices;
|
||||
struct Indices {
|
||||
int count;
|
||||
VkBuffer buffer;
|
||||
VkDeviceMemory memory;
|
||||
} indices;
|
||||
|
||||
std::vector<Node*> nodes;
|
||||
std::vector<Node*> linearNodes;
|
||||
|
||||
std::vector<Skin*> skins;
|
||||
|
||||
std::vector<Texture> textures;
|
||||
std::vector<Material> materials;
|
||||
std::vector<Animation> animations;
|
||||
|
||||
struct Dimensions {
|
||||
glm::vec3 min = glm::vec3(FLT_MAX);
|
||||
glm::vec3 max = glm::vec3(-FLT_MAX);
|
||||
glm::vec3 size;
|
||||
glm::vec3 center;
|
||||
float radius;
|
||||
} dimensions;
|
||||
|
||||
bool metallicRoughnessWorkflow = true;
|
||||
bool buffersBound = false;
|
||||
std::string path;
|
||||
|
||||
Model() {};
|
||||
~Model();
|
||||
void loadNode(vkglTF::Node* parent, const tinygltf::Node& node, uint32_t nodeIndex, const tinygltf::Model& model, std::vector<uint32_t>& indexBuffer, std::vector<Vertex>& vertexBuffer, float globalscale);
|
||||
void loadSkins(tinygltf::Model& gltfModel);
|
||||
void loadImages(tinygltf::Model& gltfModel, vks::VulkanDevice* device, VkQueue transferQueue);
|
||||
void loadMaterials(tinygltf::Model& gltfModel);
|
||||
void loadAnimations(tinygltf::Model& gltfModel);
|
||||
void loadFromFile(std::string filename, vks::VulkanDevice* device, VkQueue transferQueue, uint32_t fileLoadingFlags = vkglTF::FileLoadingFlags::None, float scale = 1.0f);
|
||||
void bindBuffers(VkCommandBuffer commandBuffer);
|
||||
void drawNode(Node* node, VkCommandBuffer commandBuffer, uint32_t renderFlags = 0, VkPipelineLayout pipelineLayout = VK_NULL_HANDLE, uint32_t bindImageSet = 1);
|
||||
void draw(VkCommandBuffer commandBuffer, uint32_t renderFlags = 0, VkPipelineLayout pipelineLayout = VK_NULL_HANDLE, uint32_t bindImageSet = 1);
|
||||
void getNodeDimensions(Node* node, glm::vec3& min, glm::vec3& max);
|
||||
void getSceneDimensions();
|
||||
void updateAnimation(uint32_t index, float time);
|
||||
Node* findNode(Node* parent, uint32_t index);
|
||||
Node* nodeFromIndex(uint32_t index);
|
||||
Node* nodeFromName(const std::string name);
|
||||
void prepareNodeDescriptor(vkglTF::Node* node, VkDescriptorSetLayout descriptorSetLayout);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Benchmark class - Can be used for automated benchmarks
|
||||
*
|
||||
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <functional>
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
|
||||
namespace vks
|
||||
{
|
||||
class Benchmark {
|
||||
private:
|
||||
FILE* stream{ nullptr };
|
||||
VkPhysicalDeviceProperties deviceProps{};
|
||||
public:
|
||||
bool active = false;
|
||||
bool outputFrameTimes = false;
|
||||
int outputFrames = -1; // -1 means no frames limit
|
||||
uint32_t warmup = 1; // Default to 1 sec of warm-up
|
||||
uint32_t duration = 10;
|
||||
std::vector<double> frameTimes;
|
||||
std::string filename = "";
|
||||
|
||||
double runtime = 0.0;
|
||||
uint32_t frameCount = 0;
|
||||
|
||||
void run(std::function<void()> renderFunc, VkPhysicalDeviceProperties deviceProps) {
|
||||
active = true;
|
||||
this->deviceProps = deviceProps;
|
||||
#if defined(_WIN32)
|
||||
bool consoleAttached = AttachConsole(ATTACH_PARENT_PROCESS);
|
||||
if (!consoleAttached) {
|
||||
consoleAttached = AttachConsole(GetCurrentProcessId());
|
||||
}
|
||||
if (consoleAttached) {
|
||||
freopen_s(&stream, "CONOUT$", "w+", stdout);
|
||||
freopen_s(&stream, "CONOUT$", "w+", stderr);
|
||||
}
|
||||
#endif
|
||||
// Warm up phase to get more stable frame rates
|
||||
{
|
||||
double tMeasured = 0.0;
|
||||
while (tMeasured < (warmup * 1000)) {
|
||||
auto tStart = std::chrono::high_resolution_clock::now();
|
||||
renderFunc();
|
||||
auto tDiff = std::chrono::duration<double, std::milli>(std::chrono::high_resolution_clock::now() - tStart).count();
|
||||
tMeasured += tDiff;
|
||||
};
|
||||
}
|
||||
|
||||
// Benchmark phase
|
||||
{
|
||||
while (runtime < (duration * 1000.0)) {
|
||||
auto tStart = std::chrono::high_resolution_clock::now();
|
||||
renderFunc();
|
||||
auto tDiff = std::chrono::duration<double, std::milli>(std::chrono::high_resolution_clock::now() - tStart).count();
|
||||
runtime += tDiff;
|
||||
frameTimes.push_back(tDiff);
|
||||
frameCount++;
|
||||
if (outputFrames != -1 && outputFrames == frameCount) break;
|
||||
};
|
||||
std::cout << std::fixed << std::setprecision(3);
|
||||
std::cout << "Benchmark finished\n";
|
||||
std::cout << "device : " << deviceProps.deviceName << " (driver version: " << deviceProps.driverVersion << ")" << "\n";
|
||||
std::cout << "runtime: " << (runtime / 1000.0) << "\n";
|
||||
std::cout << "frames : " << frameCount << "\n";
|
||||
std::cout << "fps : " << frameCount / (runtime / 1000.0) << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void saveResults() {
|
||||
std::ofstream result(filename, std::ios::out);
|
||||
if (result.is_open()) {
|
||||
result << std::fixed << std::setprecision(4);
|
||||
|
||||
result << "device,driverversion,duration (ms),frames,fps" << "\n";
|
||||
result << deviceProps.deviceName << "," << deviceProps.driverVersion << "," << runtime << "," << frameCount << "," << frameCount / (runtime / 1000.0) << "\n";
|
||||
|
||||
if (outputFrameTimes) {
|
||||
result << "\n" << "frame,ms" << "\n";
|
||||
for (size_t i = 0; i < frameTimes.size(); i++) {
|
||||
result << i << "," << frameTimes[i] << "\n";
|
||||
}
|
||||
double tMin = *std::min_element(frameTimes.begin(), frameTimes.end());
|
||||
double tMax = *std::max_element(frameTimes.begin(), frameTimes.end());
|
||||
double tAvg = std::accumulate(frameTimes.begin(), frameTimes.end(), 0.0) / (double)frameTimes.size();
|
||||
std::cout << "best : " << (1000.0 / tMin) << " fps (" << tMin << " ms)" << "\n";
|
||||
std::cout << "worst : " << (1000.0 / tMax) << " fps (" << tMax << " ms)" << "\n";
|
||||
std::cout << "avg : " << (1000.0 / tAvg) << " fps (" << tAvg << " ms)" << "\n";
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
result.flush();
|
||||
#if defined(_WIN32)
|
||||
FreeConsole();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Basic camera class providing a look-at and first-person camera
|
||||
*
|
||||
* Copyright (C) 2016-2026 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
class Camera
|
||||
{
|
||||
private:
|
||||
float fov{ 60.0f };
|
||||
float znear{ 0.1f }, zfar{ 1024.0f };
|
||||
|
||||
void updateViewMatrix()
|
||||
{
|
||||
glm::mat4 currentMatrix = matrices.view;
|
||||
|
||||
glm::mat4 rotM = glm::mat4(1.0f);
|
||||
glm::mat4 transM;
|
||||
|
||||
rotM = glm::rotate(rotM, glm::radians(rotation.x * (flipY ? -1.0f : 1.0f)), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
rotM = glm::rotate(rotM, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
rotM = glm::rotate(rotM, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
|
||||
glm::vec3 translation = position;
|
||||
if (flipY) {
|
||||
translation.y *= -1.0f;
|
||||
}
|
||||
transM = glm::translate(glm::mat4(1.0f), translation);
|
||||
|
||||
if (type == CameraType::firstperson)
|
||||
{
|
||||
matrices.view = rotM * transM;
|
||||
}
|
||||
else
|
||||
{
|
||||
matrices.view = transM * rotM;
|
||||
}
|
||||
|
||||
viewPos = glm::vec4(position, 0.0f) * glm::vec4(-1.0f, 1.0f, -1.0f, 1.0f);
|
||||
|
||||
if (matrices.view != currentMatrix) {
|
||||
updated = true;
|
||||
}
|
||||
};
|
||||
public:
|
||||
enum CameraType { lookat, firstperson };
|
||||
CameraType type = CameraType::lookat;
|
||||
|
||||
glm::vec3 rotation = glm::vec3();
|
||||
glm::vec3 position = glm::vec3();
|
||||
glm::vec4 viewPos = glm::vec4();
|
||||
|
||||
float rotationSpeed = 1.0f;
|
||||
float movementSpeed = 1.0f;
|
||||
|
||||
bool updated = true;
|
||||
bool flipY = false;
|
||||
|
||||
struct
|
||||
{
|
||||
glm::mat4 perspective;
|
||||
glm::mat4 view;
|
||||
} matrices;
|
||||
|
||||
struct
|
||||
{
|
||||
bool left = false;
|
||||
bool right = false;
|
||||
bool up = false;
|
||||
bool down = false;
|
||||
} keys;
|
||||
|
||||
bool moving() const
|
||||
{
|
||||
return keys.left || keys.right || keys.up || keys.down;
|
||||
}
|
||||
|
||||
float getNearClip() const {
|
||||
return znear;
|
||||
}
|
||||
|
||||
float getFarClip() const {
|
||||
return zfar;
|
||||
}
|
||||
|
||||
void setPerspective(float fov, float aspect, float znear, float zfar)
|
||||
{
|
||||
glm::mat4 currentMatrix = matrices.perspective;
|
||||
this->fov = fov;
|
||||
this->znear = znear;
|
||||
this->zfar = zfar;
|
||||
matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar);
|
||||
if (flipY) {
|
||||
matrices.perspective[1][1] *= -1.0f;
|
||||
}
|
||||
if (matrices.view != currentMatrix) {
|
||||
updated = true;
|
||||
}
|
||||
};
|
||||
|
||||
void updateAspectRatio(float aspect)
|
||||
{
|
||||
glm::mat4 currentMatrix = matrices.perspective;
|
||||
matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar);
|
||||
if (flipY) {
|
||||
matrices.perspective[1][1] *= -1.0f;
|
||||
}
|
||||
if (matrices.view != currentMatrix) {
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
void setPosition(glm::vec3 position)
|
||||
{
|
||||
this->position = position;
|
||||
updateViewMatrix();
|
||||
}
|
||||
|
||||
void setRotation(glm::vec3 rotation)
|
||||
{
|
||||
this->rotation = rotation;
|
||||
updateViewMatrix();
|
||||
}
|
||||
|
||||
void rotate(glm::vec3 delta)
|
||||
{
|
||||
this->rotation += delta;
|
||||
updateViewMatrix();
|
||||
}
|
||||
|
||||
void setTranslation(glm::vec3 translation)
|
||||
{
|
||||
this->position = translation;
|
||||
updateViewMatrix();
|
||||
};
|
||||
|
||||
void translate(glm::vec3 delta)
|
||||
{
|
||||
this->position += delta;
|
||||
updateViewMatrix();
|
||||
}
|
||||
|
||||
void setRotationSpeed(float rotationSpeed)
|
||||
{
|
||||
this->rotationSpeed = rotationSpeed;
|
||||
}
|
||||
|
||||
void setMovementSpeed(float movementSpeed)
|
||||
{
|
||||
this->movementSpeed = movementSpeed;
|
||||
}
|
||||
|
||||
void update(float deltaTime)
|
||||
{
|
||||
updated = false;
|
||||
if (type == CameraType::firstperson)
|
||||
{
|
||||
if (moving())
|
||||
{
|
||||
glm::vec3 camFront;
|
||||
camFront.x = -cos(glm::radians(rotation.x)) * sin(glm::radians(rotation.y));
|
||||
camFront.y = sin(glm::radians(rotation.x));
|
||||
camFront.z = cos(glm::radians(rotation.x)) * cos(glm::radians(rotation.y));
|
||||
camFront = glm::normalize(camFront);
|
||||
|
||||
float moveSpeed = deltaTime * movementSpeed;
|
||||
|
||||
if (keys.up)
|
||||
position += camFront * moveSpeed;
|
||||
if (keys.down)
|
||||
position -= camFront * moveSpeed;
|
||||
if (keys.left)
|
||||
position -= glm::normalize(glm::cross(camFront, glm::vec3(0.0f, 1.0f, 0.0f))) * moveSpeed;
|
||||
if (keys.right)
|
||||
position += glm::normalize(glm::cross(camFront, glm::vec3(0.0f, 1.0f, 0.0f))) * moveSpeed;
|
||||
}
|
||||
}
|
||||
updateViewMatrix();
|
||||
};
|
||||
|
||||
// Update camera passing separate axis data (gamepad)
|
||||
// Returns true if view or position has been changed
|
||||
bool updatePad(glm::vec2 axisLeft, glm::vec2 axisRight, float deltaTime)
|
||||
{
|
||||
bool retVal = false;
|
||||
|
||||
if (type == CameraType::firstperson)
|
||||
{
|
||||
// Use the common console thumbstick layout
|
||||
// Left = view, right = move
|
||||
|
||||
const float deadZone = 0.0015f;
|
||||
const float range = 1.0f - deadZone;
|
||||
|
||||
glm::vec3 camFront;
|
||||
camFront.x = -cos(glm::radians(rotation.x)) * sin(glm::radians(rotation.y));
|
||||
camFront.y = sin(glm::radians(rotation.x));
|
||||
camFront.z = cos(glm::radians(rotation.x)) * cos(glm::radians(rotation.y));
|
||||
camFront = glm::normalize(camFront);
|
||||
|
||||
float moveSpeed = deltaTime * movementSpeed * 2.0f;
|
||||
float rotSpeed = deltaTime * rotationSpeed * 50.0f;
|
||||
|
||||
// Move
|
||||
if (fabsf(axisLeft.y) > deadZone)
|
||||
{
|
||||
float pos = (fabsf(axisLeft.y) - deadZone) / range;
|
||||
position -= camFront * pos * ((axisLeft.y < 0.0f) ? -1.0f : 1.0f) * moveSpeed;
|
||||
retVal = true;
|
||||
}
|
||||
if (fabsf(axisLeft.x) > deadZone)
|
||||
{
|
||||
float pos = (fabsf(axisLeft.x) - deadZone) / range;
|
||||
position += glm::normalize(glm::cross(camFront, glm::vec3(0.0f, 1.0f, 0.0f))) * pos * ((axisLeft.x < 0.0f) ? -1.0f : 1.0f) * moveSpeed;
|
||||
retVal = true;
|
||||
}
|
||||
|
||||
// Rotate
|
||||
if (fabsf(axisRight.x) > deadZone)
|
||||
{
|
||||
float pos = (fabsf(axisRight.x) - deadZone) / range;
|
||||
rotation.y += pos * ((axisRight.x < 0.0f) ? -1.0f : 1.0f) * rotSpeed;
|
||||
retVal = true;
|
||||
}
|
||||
if (fabsf(axisRight.y) > deadZone)
|
||||
{
|
||||
float pos = (fabsf(axisRight.y) - deadZone) / range;
|
||||
rotation.x -= pos * ((axisRight.y < 0.0f) ? -1.0f : 1.0f) * rotSpeed;
|
||||
retVal = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo: move code from example base class for look-at
|
||||
}
|
||||
|
||||
if (retVal)
|
||||
{
|
||||
updateViewMatrix();
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* View frustum culling class
|
||||
*
|
||||
* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <math.h>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace vks
|
||||
{
|
||||
class Frustum
|
||||
{
|
||||
public:
|
||||
enum side { LEFT = 0, RIGHT = 1, TOP = 2, BOTTOM = 3, BACK = 4, FRONT = 5 };
|
||||
std::array<glm::vec4, 6> planes;
|
||||
|
||||
void update(glm::mat4 matrix)
|
||||
{
|
||||
planes[LEFT].x = matrix[0].w + matrix[0].x;
|
||||
planes[LEFT].y = matrix[1].w + matrix[1].x;
|
||||
planes[LEFT].z = matrix[2].w + matrix[2].x;
|
||||
planes[LEFT].w = matrix[3].w + matrix[3].x;
|
||||
|
||||
planes[RIGHT].x = matrix[0].w - matrix[0].x;
|
||||
planes[RIGHT].y = matrix[1].w - matrix[1].x;
|
||||
planes[RIGHT].z = matrix[2].w - matrix[2].x;
|
||||
planes[RIGHT].w = matrix[3].w - matrix[3].x;
|
||||
|
||||
planes[TOP].x = matrix[0].w - matrix[0].y;
|
||||
planes[TOP].y = matrix[1].w - matrix[1].y;
|
||||
planes[TOP].z = matrix[2].w - matrix[2].y;
|
||||
planes[TOP].w = matrix[3].w - matrix[3].y;
|
||||
|
||||
planes[BOTTOM].x = matrix[0].w + matrix[0].y;
|
||||
planes[BOTTOM].y = matrix[1].w + matrix[1].y;
|
||||
planes[BOTTOM].z = matrix[2].w + matrix[2].y;
|
||||
planes[BOTTOM].w = matrix[3].w + matrix[3].y;
|
||||
|
||||
planes[BACK].x = matrix[0].w + matrix[0].z;
|
||||
planes[BACK].y = matrix[1].w + matrix[1].z;
|
||||
planes[BACK].z = matrix[2].w + matrix[2].z;
|
||||
planes[BACK].w = matrix[3].w + matrix[3].z;
|
||||
|
||||
planes[FRONT].x = matrix[0].w - matrix[0].z;
|
||||
planes[FRONT].y = matrix[1].w - matrix[1].z;
|
||||
planes[FRONT].z = matrix[2].w - matrix[2].z;
|
||||
planes[FRONT].w = matrix[3].w - matrix[3].z;
|
||||
|
||||
for (auto i = 0; i < planes.size(); i++)
|
||||
{
|
||||
float length = sqrtf(planes[i].x * planes[i].x + planes[i].y * planes[i].y + planes[i].z * planes[i].z);
|
||||
planes[i] /= length;
|
||||
}
|
||||
}
|
||||
|
||||
bool checkSphere(glm::vec3 pos, float radius)
|
||||
{
|
||||
for (auto i = 0; i < planes.size(); i++)
|
||||
{
|
||||
if ((planes[i].x * pos.x) + (planes[i].y * pos.y) + (planes[i].z * pos.z) + planes[i].w <= -radius)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Key codes for multiple platforms
|
||||
*
|
||||
* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define KEY_ESCAPE VK_ESCAPE
|
||||
#define KEY_F1 VK_F1
|
||||
#define KEY_F2 VK_F2
|
||||
#define KEY_F3 VK_F3
|
||||
#define KEY_F4 VK_F4
|
||||
#define KEY_F5 VK_F5
|
||||
#define KEY_W 0x57
|
||||
#define KEY_A 0x41
|
||||
#define KEY_S 0x53
|
||||
#define KEY_D 0x44
|
||||
#define KEY_P 0x50
|
||||
#define KEY_SPACE 0x20
|
||||
#define KEY_KPADD 0x6B
|
||||
#define KEY_KPSUB 0x6D
|
||||
#define KEY_B 0x42
|
||||
#define KEY_F 0x46
|
||||
#define KEY_L 0x4C
|
||||
#define KEY_N 0x4E
|
||||
#define KEY_O 0x4F
|
||||
#define KEY_T 0x54
|
||||
|
||||
#elif defined(VK_USE_PLATFORM_ANDROID_KHR) || defined(VK_USE_PLATFORM_OHOS)
|
||||
#define GAMEPAD_BUTTON_A 0x1000
|
||||
#define GAMEPAD_BUTTON_B 0x1001
|
||||
#define GAMEPAD_BUTTON_X 0x1002
|
||||
#define GAMEPAD_BUTTON_Y 0x1003
|
||||
#define GAMEPAD_BUTTON_L1 0x1004
|
||||
#define GAMEPAD_BUTTON_R1 0x1005
|
||||
#define GAMEPAD_BUTTON_START 0x1006
|
||||
#define TOUCH_DOUBLE_TAP 0x1100
|
||||
|
||||
// for textoverlay example
|
||||
#define KEY_SPACE 0x3E // AKEYCODE_SPACE
|
||||
#define KEY_KPADD 0x9D // AKEYCODE_NUMPAD_ADD
|
||||
|
||||
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT))
|
||||
#if !defined(VK_EXAMPLE_XCODE_GENERATED)
|
||||
// For iOS and macOS pre-configured Xcode example project: Use character keycodes
|
||||
// - Use numeric keys as optional alternative to function keys
|
||||
#define KEY_DELETE 0x7F
|
||||
#define KEY_ESCAPE 0x1B
|
||||
#define KEY_F1 0xF704 // NSF1FunctionKey
|
||||
#define KEY_F2 0xF705 // NSF2FunctionKey
|
||||
#define KEY_F3 0xF706 // NSF3FunctionKey
|
||||
#define KEY_F4 0xF707 // NSF4FunctionKey
|
||||
#define KEY_1 '1'
|
||||
#define KEY_2 '2'
|
||||
#define KEY_3 '3'
|
||||
#define KEY_4 '4'
|
||||
#define KEY_W 'w'
|
||||
#define KEY_A 'a'
|
||||
#define KEY_S 's'
|
||||
#define KEY_D 'd'
|
||||
#define KEY_P 'p'
|
||||
#define KEY_SPACE ' '
|
||||
#define KEY_KPADD '+'
|
||||
#define KEY_KPSUB '-'
|
||||
#define KEY_B 'b'
|
||||
#define KEY_F 'f'
|
||||
#define KEY_L 'l'
|
||||
#define KEY_N 'n'
|
||||
#define KEY_O 'o'
|
||||
#define KEY_Q 'q'
|
||||
#define KEY_T 't'
|
||||
#define KEY_Z 'z'
|
||||
|
||||
#else // defined(VK_EXAMPLE_XCODE_GENERATED)
|
||||
// For cross-platform cmake-generated Xcode project: Use ANSI keyboard keycodes
|
||||
// - Use numeric keys as optional alternative to function keys
|
||||
// - Use main keyboard plus/minus instead of keypad plus/minus
|
||||
#include <Carbon/Carbon.h>
|
||||
#define KEY_DELETE kVK_Delete
|
||||
#define KEY_ESCAPE kVK_Escape
|
||||
#define KEY_F1 kVK_F1
|
||||
#define KEY_F2 kVK_F2
|
||||
#define KEY_F3 kVK_F3
|
||||
#define KEY_F4 kVK_F4
|
||||
#define KEY_1 kVK_ANSI_1
|
||||
#define KEY_2 kVK_ANSI_2
|
||||
#define KEY_3 kVK_ANSI_3
|
||||
#define KEY_4 kVK_ANSI_4
|
||||
#define KEY_W kVK_ANSI_W
|
||||
#define KEY_A kVK_ANSI_A
|
||||
#define KEY_S kVK_ANSI_S
|
||||
#define KEY_D kVK_ANSI_D
|
||||
#define KEY_P kVK_ANSI_P
|
||||
#define KEY_SPACE kVK_Space
|
||||
#define KEY_KPADD kVK_ANSI_Equal
|
||||
#define KEY_KPSUB kVK_ANSI_Minus
|
||||
#define KEY_B kVK_ANSI_B
|
||||
#define KEY_F kVK_ANSI_F
|
||||
#define KEY_L kVK_ANSI_L
|
||||
#define KEY_N kVK_ANSI_N
|
||||
#define KEY_O kVK_ANSI_O
|
||||
#define KEY_Q kVK_ANSI_Q
|
||||
#define KEY_T kVK_ANSI_T
|
||||
#define KEY_Z kVK_ANSI_Z
|
||||
#endif
|
||||
|
||||
#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
|
||||
#define KEY_ESCAPE DIKS_ESCAPE
|
||||
#define KEY_F1 DIKS_F1
|
||||
#define KEY_F2 DIKS_F2
|
||||
#define KEY_F3 DIKS_F3
|
||||
#define KEY_F4 DIKS_F4
|
||||
#define KEY_W DIKS_SMALL_W
|
||||
#define KEY_A DIKS_SMALL_A
|
||||
#define KEY_S DIKS_SMALL_S
|
||||
#define KEY_D DIKS_SMALL_D
|
||||
#define KEY_P DIKS_SMALL_P
|
||||
#define KEY_SPACE DIKS_SPACE
|
||||
#define KEY_KPADD DIKS_PLUS_SIGN
|
||||
#define KEY_KPSUB DIKS_MINUS_SIGN
|
||||
#define KEY_B DIKS_SMALL_B
|
||||
#define KEY_F DIKS_SMALL_F
|
||||
#define KEY_L DIKS_SMALL_L
|
||||
#define KEY_N DIKS_SMALL_N
|
||||
#define KEY_O DIKS_SMALL_O
|
||||
#define KEY_T DIKS_SMALL_T
|
||||
|
||||
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
#include <linux/input.h>
|
||||
|
||||
// todo: hack for bloom example
|
||||
#define KEY_ESCAPE KEY_ESC
|
||||
#define KEY_KPADD KEY_KPPLUS
|
||||
#define KEY_KPSUB KEY_KPMINUS
|
||||
|
||||
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||
#define KEY_ESCAPE 0x9
|
||||
#define KEY_F1 0x43
|
||||
#define KEY_F2 0x44
|
||||
#define KEY_F3 0x45
|
||||
#define KEY_F4 0x46
|
||||
#define KEY_W 0x19
|
||||
#define KEY_A 0x26
|
||||
#define KEY_S 0x27
|
||||
#define KEY_D 0x28
|
||||
#define KEY_P 0x21
|
||||
#define KEY_SPACE 0x41
|
||||
#define KEY_KPADD 0x56
|
||||
#define KEY_KPSUB 0x52
|
||||
#define KEY_B 0x38
|
||||
#define KEY_F 0x29
|
||||
#define KEY_L 0x2E
|
||||
#define KEY_N 0x39
|
||||
#define KEY_O 0x20
|
||||
#define KEY_T 0x1C
|
||||
|
||||
#elif defined(VK_USE_PLATFORM_SCREEN_QNX)
|
||||
#include <sys/keycodes.h>
|
||||
|
||||
#define KEY_ESCAPE KEYCODE_ESCAPE
|
||||
#define KEY_F1 KEYCODE_F1
|
||||
#define KEY_F2 KEYCODE_F2
|
||||
#define KEY_F3 KEYCODE_F3
|
||||
#define KEY_F4 KEYCODE_F4
|
||||
#define KEY_W KEYCODE_W
|
||||
#define KEY_A KEYCODE_A
|
||||
#define KEY_S KEYCODE_S
|
||||
#define KEY_D KEYCODE_D
|
||||
#define KEY_P KEYCODE_P
|
||||
#define KEY_SPACE KEYCODE_SPACE
|
||||
#define KEY_KPADD KEYCODE_KP_PLUS
|
||||
#define KEY_KPSUB KEYCODE_KP_MINUS
|
||||
#define KEY_B KEYCODE_B
|
||||
#define KEY_F KEYCODE_F
|
||||
#define KEY_L KEYCODE_L
|
||||
#define KEY_N KEYCODE_N
|
||||
#define KEY_O KEYCODE_O
|
||||
#define KEY_T KEYCODE_T
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Basic C++11 based thread pool with per-thread job queues
|
||||
*
|
||||
* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
|
||||
// make_unique is not available in C++11
|
||||
// Taken from Herb Sutter's blog (https://herbsutter.com/gotw/_102/)
|
||||
template<typename T, typename ...Args>
|
||||
std::unique_ptr<T> make_unique(Args&& ...args)
|
||||
{
|
||||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
namespace vks
|
||||
{
|
||||
class Thread
|
||||
{
|
||||
private:
|
||||
bool destroying = false;
|
||||
std::thread worker;
|
||||
std::queue<std::function<void()>> jobQueue;
|
||||
std::mutex queueMutex;
|
||||
std::condition_variable condition;
|
||||
|
||||
// Loop through all remaining jobs
|
||||
void queueLoop()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
std::function<void()> job;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queueMutex);
|
||||
condition.wait(lock, [this] { return !jobQueue.empty() || destroying; });
|
||||
if (destroying)
|
||||
{
|
||||
break;
|
||||
}
|
||||
job = jobQueue.front();
|
||||
}
|
||||
|
||||
job();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
jobQueue.pop();
|
||||
condition.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
Thread()
|
||||
{
|
||||
worker = std::thread(&Thread::queueLoop, this);
|
||||
}
|
||||
|
||||
~Thread()
|
||||
{
|
||||
if (worker.joinable())
|
||||
{
|
||||
wait();
|
||||
queueMutex.lock();
|
||||
destroying = true;
|
||||
condition.notify_one();
|
||||
queueMutex.unlock();
|
||||
worker.join();
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new job to the thread's queue
|
||||
void addJob(std::function<void()> function)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
jobQueue.push(std::move(function));
|
||||
condition.notify_one();
|
||||
}
|
||||
|
||||
// Wait until all work items have been finished
|
||||
void wait()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queueMutex);
|
||||
condition.wait(lock, [this]() { return jobQueue.empty(); });
|
||||
}
|
||||
};
|
||||
|
||||
class ThreadPool
|
||||
{
|
||||
public:
|
||||
std::vector<std::unique_ptr<Thread>> threads;
|
||||
|
||||
// Sets the number of threads to be allocated in this pool
|
||||
void setThreadCount(uint32_t count)
|
||||
{
|
||||
threads.clear();
|
||||
for (uint32_t i = 0; i < count; i++)
|
||||
{
|
||||
threads.push_back(make_unique<Thread>());
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until all threads have finished their work items
|
||||
void wait()
|
||||
{
|
||||
for (auto &thread : threads)
|
||||
{
|
||||
thread->wait();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,458 @@
|
||||
/*
|
||||
* Vulkan Example base class
|
||||
*
|
||||
* Copyright (C) 2016-2026 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma comment(linker, "/subsystem:windows")
|
||||
#include <windows.h>
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <ShellScalingAPI.h>
|
||||
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||
#include <android/native_activity.h>
|
||||
#include <android/asset_manager.h>
|
||||
#include <android_native_app_glue.h>
|
||||
#include <sys/system_properties.h>
|
||||
#include "VulkanAndroid.h"
|
||||
#elif defined(VK_USE_PLATFORM_OHOS)
|
||||
#include <ace/xcomponent/native_interface_xcomponent.h>
|
||||
#include <rawfile/raw_file_manager.h>
|
||||
#include "uv.h"
|
||||
#include "VulkanOHOS.h"
|
||||
extern NativeWindow* nativeWindow;
|
||||
extern uv_loop_t* loop;
|
||||
extern uv_async_t msgSignal;
|
||||
#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
|
||||
#include <directfb.h>
|
||||
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
#include <wayland-client.h>
|
||||
#include "xdg-shell-client-protocol.h"
|
||||
#elif defined(_DIRECT2DISPLAY)
|
||||
//
|
||||
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
||||
#include <xcb/xcb.h>
|
||||
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT))
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
#include <numeric>
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/matrix_inverse.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <string>
|
||||
|
||||
#include "vulkan/vulkan.h"
|
||||
|
||||
#include "CommandLineParser.hpp"
|
||||
#include "keycodes.hpp"
|
||||
#include "VulkanTools.h"
|
||||
#include "VulkanDebug.h"
|
||||
#include "VulkanUIOverlay.h"
|
||||
#include "VulkanSwapChain.h"
|
||||
#include "VulkanBuffer.h"
|
||||
#include "VulkanDevice.h"
|
||||
#include "VulkanTexture.h"
|
||||
|
||||
#include "VulkanInitializers.hpp"
|
||||
#include "camera.hpp"
|
||||
#include "benchmark.hpp"
|
||||
|
||||
constexpr uint32_t maxConcurrentFrames{ 2 };
|
||||
|
||||
class VulkanExampleBase
|
||||
{
|
||||
private:
|
||||
std::string getWindowTitle() const;
|
||||
uint32_t destWidth{};
|
||||
uint32_t destHeight{};
|
||||
bool resizing = false;
|
||||
void handleMouseMove(int32_t x, int32_t y);
|
||||
void nextFrame();
|
||||
void updateOverlay();
|
||||
void createPipelineCache();
|
||||
void createCommandPool();
|
||||
void createSynchronizationPrimitives();
|
||||
void createSurface();
|
||||
void createSwapChain();
|
||||
void createCommandBuffers();
|
||||
void destroyCommandBuffers();
|
||||
std::string shaderDir = "glsl";
|
||||
#if defined(VK_EXAMPLE_PS5)
|
||||
VkSemaphore ps5CaptureComplete{ VK_NULL_HANDLE };
|
||||
VkSemaphore ps5CaptureSwapchainImage(VkSemaphore renderComplete);
|
||||
/** @brief The host reference build is recording every frame (PS5_RECORD, ps5/tools/record-reel.sh) */
|
||||
static bool ps5Recording();
|
||||
#endif
|
||||
protected:
|
||||
// Returns the path to the root of the glsl, hlsl or slang shader directory.
|
||||
std::string getShadersPath() const;
|
||||
|
||||
// Frame counter to display fps
|
||||
uint32_t frameCounter = 0;
|
||||
uint32_t lastFPS = 0;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> lastTimestamp, tPrevEnd;
|
||||
// Vulkan instance, stores all per-application states
|
||||
VkInstance instance{ VK_NULL_HANDLE };
|
||||
std::vector<std::string> supportedInstanceExtensions;
|
||||
// Physical device (GPU) that Vulkan will use
|
||||
VkPhysicalDevice physicalDevice{ VK_NULL_HANDLE };
|
||||
// Stores physical device properties (for e.g. checking device limits)
|
||||
VkPhysicalDeviceProperties deviceProperties{};
|
||||
// Stores the features available on the selected physical device (for e.g. checking if a feature is available)
|
||||
VkPhysicalDeviceFeatures deviceFeatures{};
|
||||
// Stores all available memory (type) properties for the physical device
|
||||
VkPhysicalDeviceMemoryProperties deviceMemoryProperties{};
|
||||
/** @brief Set of physical device features to be enabled for this example (must be set in the derived constructor) */
|
||||
VkPhysicalDeviceFeatures enabledFeatures{};
|
||||
/** @brief Set of device extensions to be enabled for this example (must be set in the derived constructor) */
|
||||
std::vector<const char*> enabledDeviceExtensions;
|
||||
/** @brief Set of instance extensions to be enabled for this example (must be set in the derived constructor) */
|
||||
std::vector<const char*> enabledInstanceExtensions;
|
||||
/** @brief Set of layer settings to be enabled for this example (must be set in the derived constructor) */
|
||||
std::vector<VkLayerSettingEXT> enabledLayerSettings;
|
||||
/** @brief Optional pNext structure for passing extension structures to device creation */
|
||||
void* deviceCreatepNextChain = nullptr;
|
||||
/** @brief Logical device, application's view of the physical device (GPU) */
|
||||
VkDevice device{ VK_NULL_HANDLE };
|
||||
// Handle to the device graphics queue that command buffers are submitted to
|
||||
VkQueue queue{ VK_NULL_HANDLE };
|
||||
// Depth buffer format (selected during Vulkan initialization)
|
||||
VkFormat depthFormat{VK_FORMAT_UNDEFINED};
|
||||
// Command buffer pool
|
||||
VkCommandPool cmdPool{ VK_NULL_HANDLE };
|
||||
// Command buffers used for rendering
|
||||
std::array<VkCommandBuffer, maxConcurrentFrames> drawCmdBuffers;
|
||||
// Global render pass for frame buffer writes
|
||||
VkRenderPass renderPass{ VK_NULL_HANDLE };
|
||||
// List of available frame buffers (same as number of swap chain images)
|
||||
std::vector<VkFramebuffer>frameBuffers;
|
||||
// Descriptor set pool
|
||||
VkDescriptorPool descriptorPool{ VK_NULL_HANDLE };
|
||||
// List of shader modules created (stored for cleanup)
|
||||
std::vector<VkShaderModule> shaderModules;
|
||||
// Pipeline cache object
|
||||
VkPipelineCache pipelineCache{ VK_NULL_HANDLE };
|
||||
// Wraps the swap chain to present images (framebuffers) to the windowing system
|
||||
VulkanSwapChain swapChain;
|
||||
|
||||
// Synchronization related objects and variables
|
||||
// These are used to have multiple frame buffers "in flight" to get some CPU/GPU parallelism
|
||||
uint32_t currentImageIndex{ 0 };
|
||||
uint32_t currentBuffer{ 0 };
|
||||
std::array<VkSemaphore, maxConcurrentFrames> presentCompleteSemaphores{};
|
||||
std::vector<VkSemaphore> renderCompleteSemaphores{};
|
||||
std::array<VkFence, maxConcurrentFrames> waitFences;
|
||||
|
||||
bool requiresStencil{ false };
|
||||
public:
|
||||
bool prepared = false;
|
||||
bool resized = false;
|
||||
uint32_t width = 1280;
|
||||
uint32_t height = 720;
|
||||
|
||||
vks::UIOverlay ui;
|
||||
CommandLineParser commandLineParser;
|
||||
|
||||
/** @brief Last frame time measured using a high performance timer (if available) */
|
||||
float frameTimer = 1.0f;
|
||||
|
||||
vks::Benchmark benchmark;
|
||||
|
||||
/** @brief Encapsulated physical and logical vulkan device */
|
||||
vks::VulkanDevice *vulkanDevice{};
|
||||
|
||||
/** @brief Example settings that can be changed e.g. by command line arguments */
|
||||
struct Settings {
|
||||
/** @brief Activates validation layers (and message output) when set to true */
|
||||
bool validation = false;
|
||||
/** @brief Set to true if fullscreen mode has been requested via command line */
|
||||
bool fullscreen = false;
|
||||
/** @brief Set to true if v-sync will be forced for the swapchain */
|
||||
bool vsync = false;
|
||||
/** @brief Enable UI overlay */
|
||||
bool overlay = true;
|
||||
} settings;
|
||||
|
||||
/** @brief State of gamepad input (only used on Android) */
|
||||
struct {
|
||||
glm::vec2 axisLeft = glm::vec2(0.0f);
|
||||
glm::vec2 axisRight = glm::vec2(0.0f);
|
||||
} gamePadState;
|
||||
|
||||
/** @brief State of mouse/touch input */
|
||||
struct {
|
||||
struct {
|
||||
bool left = false;
|
||||
bool right = false;
|
||||
bool middle = false;
|
||||
} buttons;
|
||||
glm::vec2 position;
|
||||
} mouseState;
|
||||
|
||||
VkClearColorValue defaultClearColor = { { 0.025f, 0.025f, 0.025f, 1.0f } };
|
||||
|
||||
static std::vector<const char*> args;
|
||||
|
||||
// Defines a frame rate independent timer value clamped from -1.0...1.0
|
||||
// For use in animations, rotations, etc.
|
||||
float timer = 0.0f;
|
||||
// Multiplier for speeding up (or slowing down) the global timer
|
||||
float timerSpeed = 0.25f;
|
||||
bool paused = false;
|
||||
|
||||
Camera camera;
|
||||
|
||||
std::string title = "Vulkan Example";
|
||||
std::string name = "vulkanExample";
|
||||
uint32_t apiVersion = VK_API_VERSION_1_0;
|
||||
|
||||
/** @brief Default depth stencil attachment used by the default render pass */
|
||||
struct {
|
||||
VkImage image;
|
||||
VkDeviceMemory memory;
|
||||
VkImageView view;
|
||||
} depthStencil{};
|
||||
|
||||
/** @brief Can be used by samples that use the core version of dynamic rendering */
|
||||
bool useDynamicRendering{ false };
|
||||
VkPhysicalDeviceDynamicRenderingFeatures baseDynamicRenderingFeatures{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES, .dynamicRendering = VK_TRUE };
|
||||
|
||||
// OS specific
|
||||
#if defined(_WIN32)
|
||||
HWND window;
|
||||
HINSTANCE windowInstance;
|
||||
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||
// true if application has focused, false if moved to background
|
||||
bool focused = false;
|
||||
struct TouchPos {
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
} touchPos{};
|
||||
bool touchDown = false;
|
||||
double touchTimer = 0.0;
|
||||
int64_t lastTapTime = 0;
|
||||
#elif defined (__OHOS__)
|
||||
NativeResourceManager* s_nativeResourceManager;
|
||||
bool focused = false;
|
||||
struct TouchPos {
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
} touchPos;
|
||||
bool touchDown = false;
|
||||
double touchTimer = 0.0;
|
||||
int64_t lastTapTime = 0;
|
||||
int64_t touchDownTime = 0;
|
||||
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT))
|
||||
void* view;
|
||||
#if defined(VK_USE_PLATFORM_METAL_EXT)
|
||||
CAMetalLayer* metalLayer;
|
||||
#endif
|
||||
#if defined(VK_EXAMPLE_XCODE_GENERATED)
|
||||
bool quit = false;
|
||||
#endif
|
||||
#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
|
||||
bool quit = false;
|
||||
IDirectFB *dfb = nullptr;
|
||||
IDirectFBDisplayLayer *layer = nullptr;
|
||||
IDirectFBWindow *window = nullptr;
|
||||
IDirectFBSurface *surface = nullptr;
|
||||
IDirectFBEventBuffer *event_buffer = nullptr;
|
||||
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
wl_display *display = nullptr;
|
||||
wl_registry *registry = nullptr;
|
||||
wl_compositor *compositor = nullptr;
|
||||
struct xdg_wm_base *shell = nullptr;
|
||||
wl_seat *seat = nullptr;
|
||||
wl_pointer *pointer = nullptr;
|
||||
wl_keyboard *keyboard = nullptr;
|
||||
wl_surface *surface = nullptr;
|
||||
struct xdg_surface *xdg_surface;
|
||||
struct xdg_toplevel *xdg_toplevel;
|
||||
bool quit = false;
|
||||
bool configured = false;
|
||||
|
||||
#elif defined(_DIRECT2DISPLAY)
|
||||
bool quit = false;
|
||||
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
||||
bool quit = false;
|
||||
xcb_connection_t *connection;
|
||||
xcb_screen_t *screen;
|
||||
xcb_window_t window;
|
||||
xcb_intern_atom_reply_t *atom_wm_delete_window;
|
||||
#elif defined(VK_USE_PLATFORM_HEADLESS_EXT)
|
||||
bool quit = false;
|
||||
#elif defined(VK_USE_PLATFORM_SCREEN_QNX)
|
||||
screen_context_t screen_context = nullptr;
|
||||
screen_window_t screen_window = nullptr;
|
||||
screen_event_t screen_event = nullptr;
|
||||
bool quit = false;
|
||||
#endif
|
||||
|
||||
#if defined(VK_EXAMPLE_PS5)
|
||||
/** @brief PS5: how the launcher runs this sample (ps5/src/main.cpp); also in the host reference build (ps5/README.md) */
|
||||
struct {
|
||||
/** @brief Frames to draw before the sample ends itself; 0: until OPTIONS is pressed */
|
||||
uint32_t frameBudget = 0;
|
||||
/** @brief Where the last frame of the budget is saved (a PPM at half the display's size); empty: none */
|
||||
std::string screenshotPath;
|
||||
/** @brief Frames drawn so far */
|
||||
uint32_t framesDrawn = 0;
|
||||
/** @brief When half the budget had been drawn, and when the last frame began (steady_clock seconds):
|
||||
* the frame rate after start-up, without the last frame's screenshot */
|
||||
double halfwayTime = 0.0;
|
||||
double lastFrameTime = 0.0;
|
||||
/** @brief The launcher's own menu: ImGui windows of its own instead of the sample's overlay window */
|
||||
bool ownOverlay = false;
|
||||
/** @brief OPTIONS ends the sample, back to the menu; in a title with one program, it shows and hides the overlay */
|
||||
bool optionsEnds = true;
|
||||
/** @brief A test run's variant of the sample ("uikit.aurora" runs uikit with "aurora"); empty otherwise */
|
||||
std::string variant;
|
||||
/** @brief A recording's camera orbit in a test run, degrees a second (test-run.txt's "orbit") */
|
||||
float orbit = 0.0f;
|
||||
} ps5;
|
||||
/** @brief Reads the pad: the sticks move the camera, the buttons drive the UI overlay, OPTIONS ends the sample */
|
||||
void ps5HandleInput();
|
||||
#endif
|
||||
|
||||
/** @brief Default base class constructor */
|
||||
VulkanExampleBase();
|
||||
virtual ~VulkanExampleBase();
|
||||
/** @brief Setup the vulkan instance, enable required extensions and connect to the physical device (GPU) */
|
||||
bool initVulkan();
|
||||
|
||||
#if defined(_WIN32)
|
||||
void setupConsole(std::string title);
|
||||
void setupDPIAwareness();
|
||||
HWND setupWindow(HINSTANCE hinstance, WNDPROC wndproc);
|
||||
void handleMessages(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
||||
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||
static int32_t handleAppInput(struct android_app* app, AInputEvent* event);
|
||||
static void handleAppCommand(android_app* app, int32_t cmd);
|
||||
#elif defined(VK_USE_PLATFORM_OHOS)
|
||||
static void handleAppInput(OH_NativeXComponent *component, void *window, void* pThis);
|
||||
// Static callback for libuv async handle
|
||||
static void onMessageCallbackStatic(uv_async_t* handle);
|
||||
// Instance method called from static callback
|
||||
void onMessageCallback();
|
||||
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT))
|
||||
void* setupWindow(void* view);
|
||||
void displayLinkOutputCb();
|
||||
void mouseDragged(float x, float y);
|
||||
void windowWillResize(float x, float y);
|
||||
void windowDidResize();
|
||||
#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
|
||||
IDirectFBSurface *setupWindow();
|
||||
void handleEvent(const DFBWindowEvent *event);
|
||||
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
struct xdg_surface *setupWindow();
|
||||
void initWaylandConnection();
|
||||
void setSize(int width, int height);
|
||||
static void registryGlobalCb(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version);
|
||||
void registryGlobal(struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version);
|
||||
static void registryGlobalRemoveCb(void *data, struct wl_registry *registry, uint32_t name);
|
||||
static void seatCapabilitiesCb(void *data, wl_seat *seat, uint32_t caps);
|
||||
void seatCapabilities(wl_seat *seat, uint32_t caps);
|
||||
static void pointerEnterCb(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy);
|
||||
static void pointerLeaveCb(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface);
|
||||
static void pointerMotionCb(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy);
|
||||
void pointerMotion(struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy);
|
||||
static void pointerButtonCb(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state);
|
||||
void pointerButton(struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state);
|
||||
static void pointerAxisCb(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value);
|
||||
void pointerAxis(struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value);
|
||||
static void keyboardKeymapCb(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size);
|
||||
static void keyboardEnterCb(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys);
|
||||
static void keyboardLeaveCb(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface);
|
||||
static void keyboardKeyCb(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state);
|
||||
void keyboardKey(struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state);
|
||||
static void keyboardModifiersCb(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group);
|
||||
|
||||
#elif defined(_DIRECT2DISPLAY)
|
||||
//
|
||||
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
||||
xcb_window_t setupWindow();
|
||||
void initxcbConnection();
|
||||
void handleEvent(const xcb_generic_event_t *event);
|
||||
#elif defined(VK_USE_PLATFORM_SCREEN_QNX)
|
||||
void setupWindow();
|
||||
void handleEvent();
|
||||
#else
|
||||
void setupWindow();
|
||||
#endif
|
||||
/** @brief (Virtual) Creates the application wide Vulkan instance */
|
||||
virtual VkResult createInstance();
|
||||
/** @brief (Pure virtual) Render function to be implemented by the sample application */
|
||||
virtual void render() = 0;
|
||||
/** @brief (Virtual) Called after a key was pressed, can be used to do custom key handling */
|
||||
virtual void keyPressed(uint32_t);
|
||||
/** @brief (Virtual) Called after the mouse cursor moved and before internal events (like camera rotation) is handled */
|
||||
virtual void mouseMoved(double x, double y, bool &handled);
|
||||
/** @brief (Virtual) Called when the window has been resized, can be used by the sample application to recreate resources */
|
||||
virtual void windowResized();
|
||||
/** @brief (Virtual) Setup default depth and stencil views */
|
||||
virtual void setupDepthStencil();
|
||||
/** @brief (Virtual) Setup default framebuffers for all requested swapchain images */
|
||||
virtual void setupFrameBuffer();
|
||||
/** @brief (Virtual) Setup a default renderpass */
|
||||
virtual void setupRenderPass();
|
||||
/** @brief (Virtual) Called after the physical device features have been read, can be used to set features to enable on the device */
|
||||
virtual void getEnabledFeatures();
|
||||
/** @brief (Virtual) Called after the physical device extensions have been read, can be used to enable extensions based on the supported extension listing*/
|
||||
virtual void getEnabledExtensions();
|
||||
/** @brief Begin dynamic rendering with the default setup for the base class color and depth attachment */
|
||||
void beginDynamicRendering(VkCommandBuffer cmdBuffer);
|
||||
/** @brief End dynamic rendering with the default setup for the base class color and depth attachment */
|
||||
void endDynamicRendering(VkCommandBuffer cmdBuffer);
|
||||
|
||||
/** @brief Prepares all Vulkan resources and functions required to run the sample */
|
||||
virtual void prepare();
|
||||
|
||||
/** @brief Loads a SPIR-V shader file for the given shader stage */
|
||||
VkPipelineShaderStageCreateInfo loadShader(std::string fileName, VkShaderStageFlagBits stage);
|
||||
|
||||
void windowResize();
|
||||
|
||||
/** @brief Entry point for the main render loop */
|
||||
void renderLoop();
|
||||
|
||||
/** @brief Adds the drawing commands for the ImGui overlay to the given command buffer */
|
||||
void drawUI(const VkCommandBuffer commandBuffer);
|
||||
|
||||
/** Prepare the next frame for workload submission by acquiring the next swap chain image and waiting for the previous command buffer to finish */
|
||||
void prepareFrame(bool waitForFence = true);
|
||||
/** @brief Presents the current image to the swap chain */
|
||||
void submitFrame(bool skipQueueSubmit = false);
|
||||
|
||||
/** @brief (Virtual) Called when the UI overlay is updating, can be used to add custom elements to the overlay */
|
||||
virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay);
|
||||
|
||||
#if defined(_WIN32)
|
||||
virtual void OnHandleMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
||||
#endif
|
||||
};
|
||||
|
||||
#include "Entrypoints.h"
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,442 @@
|
||||
// RommPS: the credits card and the Codec call. See credits.hpp.
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#include "credits.hpp"
|
||||
|
||||
#include "avatar_png.h"
|
||||
#include "core/tween.hpp"
|
||||
#include "qrcodegen.h"
|
||||
#include "rommps_api.hpp"
|
||||
#include "stb_image.h" // implemented by base/VulkanglTFModel.cpp
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#if !defined(PS5_HOST_REFERENCE)
|
||||
extern "C" int sceSystemServiceLaunchWebBrowser(const char *uri, void *param);
|
||||
#endif
|
||||
|
||||
namespace rommps
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
namespace ui = hui::ui;
|
||||
namespace tween = hui::tween;
|
||||
using hui::Action;
|
||||
using hui::audio::Cue;
|
||||
using hui::gfx::Align;
|
||||
using hui::gfx::Rect;
|
||||
|
||||
// ------------------------------------------------------------------ the call
|
||||
|
||||
enum class Kind
|
||||
{
|
||||
say, // a line, typed out
|
||||
ask, // a yes-or-no question
|
||||
watch, // a video, in the console's browser
|
||||
end,
|
||||
};
|
||||
|
||||
struct Step
|
||||
{
|
||||
Kind kind;
|
||||
int speaker; // 0 the author (left), 1 the voice (right)
|
||||
const char *text; // the line, the question, or the video's title
|
||||
int next; // the step after (a question's yes)
|
||||
int no = -1; // a question's no
|
||||
const char *video = nullptr; // a YouTube id: its embed player in the browser, its page for a phone
|
||||
};
|
||||
|
||||
// Original lines, in the spirit of the famous call; the videos are links.
|
||||
constexpr Step kScript[] = {
|
||||
/* 0 */ {Kind::say, 1, "Don't be alarmed. We've been watching your saves for some time now.", 1},
|
||||
/* 1 */ {Kind::say, 0, "Who is this? How did you get this frequency?", 2},
|
||||
/* 2 */ {Kind::say, 1, "Every slot you overwrite. Every state you load. Each one a memory, thrown away the moment a "
|
||||
"better one comes along.", 3},
|
||||
/* 3 */ {Kind::say, 1, "Left alone, they drift apart. One console remembers the castle you conquered. Another, a "
|
||||
"door still locked.", 4},
|
||||
/* 4 */ {Kind::say, 0, "So you keep them in sync.", 5},
|
||||
/* 5 */ {Kind::say, 1, "We give them context. One truth, kept on a server of your choosing.", 6},
|
||||
/* 6 */ {Kind::say, 0, "And when two truths disagree?", 7},
|
||||
/* 7 */ {Kind::say, 1, "Then you decide. We don't choose what you remember.", 8},
|
||||
/* 8 */ {Kind::say, 1, "We only make sure you never lose it.", 9},
|
||||
/* 9 */ {Kind::say, 0, "...", 10},
|
||||
/* 10 */ {Kind::say, 1, "One more thing.", 11},
|
||||
/* 11 */ {Kind::ask, 1, "Do you want to know the truth about the Patriots AI?", 12, 18},
|
||||
/* 12 */ {Kind::say, 1, "Then listen carefully. And remember what you hear.", 13},
|
||||
/* 13 */ {Kind::watch, 1, "The truth about the Patriots AI", 14, -1, "C31XYgr8gp0"},
|
||||
/* 14 */ {Kind::say, 1, "Now you know what we are. Or what we were meant to be.", 15},
|
||||
/* 15 */ {Kind::ask, 1, "Do you want to learn more about AI altogether?", 16, 19},
|
||||
/* 16 */ {Kind::watch, 1, "Learn more about AI", 17, -1, "-gGLvg0n-uY"},
|
||||
/* 17 */ {Kind::say, 1, "Stay curious. That's what keeps a memory alive.", 20},
|
||||
/* 18 */ {Kind::say, 1, "Then forget we ever spoke. Your saves are waiting.", 20},
|
||||
/* 19 */ {Kind::say, 1, "Then go play. We'll keep your saves safe.", 20},
|
||||
/* 20 */ {Kind::end, 1, "", 20},
|
||||
};
|
||||
constexpr float kRing = 2.4f; // seconds the call rings
|
||||
constexpr float kOpen = 0.45f; // the screen opening and closing
|
||||
constexpr float kCharsPerSecond = 34.0f;
|
||||
|
||||
const Color kGreen = Color::rgb(0x8dffb0);
|
||||
const Color kDimGreen = Color::rgb(0x2f8f55);
|
||||
const Color kDark = Color::rgb(0x020805);
|
||||
|
||||
// Opens a link in the console's web browser; RommPS keeps running behind it.
|
||||
bool open_in_browser(const char *url)
|
||||
{
|
||||
#if defined(PS5_HOST_REFERENCE)
|
||||
(void)url;
|
||||
return false;
|
||||
#else
|
||||
return sceSystemServiceLaunchWebBrowser(url, nullptr) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
float hold_for(const char *text)
|
||||
{
|
||||
return 1.4f + static_cast<float>(std::strlen(text)) / 28.0f;
|
||||
}
|
||||
|
||||
// A cheap hash for the static.
|
||||
float noise(int x, int y, int frame)
|
||||
{
|
||||
std::uint32_t h = static_cast<std::uint32_t>(x * 374761393 + y * 668265263 + frame * 2147483647);
|
||||
h = (h ^ (h >> 13)) * 1274126177u;
|
||||
return static_cast<float>((h ^ (h >> 16)) & 0xffff) / 65535.0f;
|
||||
}
|
||||
|
||||
void draw_portrait_frame(hui::gfx::DrawList &list, const Rect &r, bool speaking, float clock)
|
||||
{
|
||||
const float glow = speaking ? 0.55f + 0.25f * std::sin(clock * 9.0f) : 0.25f;
|
||||
list.glow(r, 6, 26, kGreen.with_alpha(0.25f * glow));
|
||||
list.bordered_rect(r.inset(-8), 4, Color::rgb(0x000000, 0.0f), 3, kGreen.with_alpha(0.35f + 0.5f * glow));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ------------------------------------------------------------------ QR
|
||||
|
||||
QrCode QrCode::encode(const std::string &text)
|
||||
{
|
||||
QrCode out;
|
||||
std::vector<std::uint8_t> qr(qrcodegen_BUFFER_LEN_MAX), tmp(qrcodegen_BUFFER_LEN_MAX);
|
||||
if (!qrcodegen_encodeText(text.c_str(), tmp.data(), qr.data(), qrcodegen_Ecc_MEDIUM, qrcodegen_VERSION_MIN,
|
||||
qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true))
|
||||
return out;
|
||||
out.size = qrcodegen_getSize(qr.data());
|
||||
out.modules.resize(static_cast<std::size_t>(out.size * out.size));
|
||||
for (int y = 0; y < out.size; ++y)
|
||||
for (int x = 0; x < out.size; ++x)
|
||||
out.modules[static_cast<std::size_t>(y * out.size + x)] = qrcodegen_getModule(qr.data(), x, y);
|
||||
return out;
|
||||
}
|
||||
|
||||
void draw_qr(hui::gfx::DrawList &list, const QrCode &qr, const Rect &rect)
|
||||
{
|
||||
list.rounded_rect(rect, 10, Color::rgb(0xffffff));
|
||||
if (qr.size == 0)
|
||||
return;
|
||||
const float cell = std::floor(rect.w / static_cast<float>(qr.size + 4)); // two modules of quiet zone a side
|
||||
const float x0 = rect.x + std::floor((rect.w - cell * static_cast<float>(qr.size)) * 0.5f);
|
||||
const float y0 = rect.y + std::floor((rect.h - cell * static_cast<float>(qr.size)) * 0.5f);
|
||||
const Color ink = Color::rgb(0x000000);
|
||||
for (int y = 0; y < qr.size; ++y)
|
||||
{
|
||||
// Runs of dark modules as one rectangle each.
|
||||
int x = 0;
|
||||
while (x < qr.size)
|
||||
{
|
||||
if (!qr.dark(x, y))
|
||||
{
|
||||
++x;
|
||||
continue;
|
||||
}
|
||||
int end = x;
|
||||
while (end < qr.size && qr.dark(end, y))
|
||||
++end;
|
||||
list.rounded_rect({x0 + static_cast<float>(x) * cell, y0 + static_cast<float>(y) * cell,
|
||||
static_cast<float>(end - x) * cell, cell},
|
||||
0, ink);
|
||||
x = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ credits
|
||||
|
||||
std::uint32_t avatar_texture(hui::gfx::Renderer *renderer)
|
||||
{
|
||||
static std::uint32_t texture = 0;
|
||||
static bool tried = false;
|
||||
if (texture || tried || !renderer)
|
||||
return texture;
|
||||
tried = true;
|
||||
int w = 0, h = 0, channels = 0;
|
||||
unsigned char *rgba = stbi_load_from_memory(kAvatarPng, static_cast<int>(sizeof kAvatarPng), &w, &h, &channels, 4);
|
||||
if (rgba)
|
||||
{
|
||||
texture = renderer->create_texture(w, h, rgba);
|
||||
stbi_image_free(rgba);
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
void draw_credits(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, const Rect &rect,
|
||||
bool focused, float clock)
|
||||
{
|
||||
static const QrCode qr = QrCode::encode(kSourceUrl);
|
||||
const Rect avatar{rect.x, rect.y, 128, 128};
|
||||
if (focused)
|
||||
{
|
||||
list.glow(avatar, 64, 26, look.accent.with_alpha(0.45f + 0.2f * std::sin(clock * 4.0f)));
|
||||
list.bordered_rect(avatar.inset(-7), 70, Color::rgb(0x000000, 0.0f), 4, look.focus.with_alpha(1.0f));
|
||||
}
|
||||
if (const std::uint32_t texture = avatar_texture(nullptr))
|
||||
list.image(texture, avatar, hui::gfx::kFullUv, Color::rgb(0xffffff), 64);
|
||||
else
|
||||
list.circle(avatar.cx(), avatar.cy(), 64, look.panel_muted.with_alpha(0.3f));
|
||||
|
||||
const float tx = avatar.x + avatar.w + 24;
|
||||
ui::text(list, fonts.regular, "Made with love", tx, rect.y + 52, 22, look.panel_muted);
|
||||
ui::text(list, fonts.semibold, "by s0liton", tx, rect.y + 86, 26, look.panel_text);
|
||||
ui::text(list, fonts.regular, focused ? "Press Cross" : "", tx, rect.y + 120, 18, look.accent);
|
||||
|
||||
const float q = rect.h;
|
||||
draw_qr(list, qr, {rect.x + rect.w - q, rect.y, q, q});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ the call
|
||||
|
||||
void CodecCall::start()
|
||||
{
|
||||
active_ = true;
|
||||
age_ = 0.0f;
|
||||
step_ = -1;
|
||||
step_age_ = 0.0f;
|
||||
typed_ = 0;
|
||||
choice_ = 0;
|
||||
opened_ = false;
|
||||
closing_ = -1.0f;
|
||||
}
|
||||
|
||||
void CodecCall::go(int step, hui::ui::Feedback &feedback)
|
||||
{
|
||||
step_ = step;
|
||||
step_age_ = 0.0f;
|
||||
typed_ = 0;
|
||||
choice_ = 0;
|
||||
opened_ = false;
|
||||
const Step &s = kScript[step];
|
||||
if (s.kind == Kind::end)
|
||||
{
|
||||
feedback.play(Cue::modal_close);
|
||||
closing_ = 0.0f;
|
||||
}
|
||||
else if (s.kind == Kind::watch)
|
||||
{
|
||||
// The payload's page hosting YouTube's player, filling the browser
|
||||
// (an embed opened on its own fails with YouTube's error 153).
|
||||
const std::string page = "http://127.0.0.1:" + std::to_string(payload_port()) + "/video?v=" + s.video;
|
||||
opened_ = open_in_browser(page.c_str());
|
||||
feedback.play(Cue::launch);
|
||||
}
|
||||
}
|
||||
|
||||
void CodecCall::update(const hui::InputFrame &input, float dt, hui::ui::Feedback &feedback)
|
||||
{
|
||||
if (!active_)
|
||||
return;
|
||||
age_ += dt;
|
||||
if (closing_ >= 0.0f)
|
||||
{
|
||||
closing_ += dt;
|
||||
if (closing_ >= kOpen)
|
||||
active_ = false;
|
||||
return;
|
||||
}
|
||||
if (input.is_pressed(Action::back))
|
||||
{
|
||||
feedback.play(Cue::modal_close);
|
||||
closing_ = 0.0f;
|
||||
return;
|
||||
}
|
||||
if (step_ < 0)
|
||||
{
|
||||
// Ringing: the call's sound once (RommPS's notify cue), then the line opens.
|
||||
if (age_ - dt <= 0.0f)
|
||||
feedback.play(Cue::notify, 1.0f, 0.0f, 0.8f);
|
||||
if (age_ >= kRing || input.is_pressed(Action::confirm))
|
||||
{
|
||||
go(0, feedback);
|
||||
feedback.play(Cue::open, 1.3f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const Step &s = kScript[step_];
|
||||
step_age_ += dt;
|
||||
if (s.kind == Kind::watch)
|
||||
{
|
||||
// Back from the browser (or done with the phone): Cross carries on.
|
||||
if (input.is_pressed(Action::confirm) && step_age_ > 0.5f)
|
||||
{
|
||||
feedback.play(Cue::open, 1.3f);
|
||||
go(s.next, feedback);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const int length = static_cast<int>(std::strlen(s.text));
|
||||
const int typed = std::min(length, static_cast<int>(step_age_ * kCharsPerSecond));
|
||||
if (typed / 3 != typed_ / 3 && typed < length)
|
||||
feedback.play(Cue::tick, s.speaker ? 0.8f : 1.25f, s.speaker ? 0.4f : -0.4f, 0.35f);
|
||||
typed_ = typed;
|
||||
const float done_at = static_cast<float>(length) / kCharsPerSecond;
|
||||
const bool confirm = input.is_pressed(Action::confirm);
|
||||
if (typed_ < length)
|
||||
{
|
||||
if (confirm)
|
||||
{
|
||||
step_age_ = done_at; // show it all first
|
||||
typed_ = length;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (s.kind == Kind::ask)
|
||||
{
|
||||
if (input.nav == hui::Direction::left || input.nav == hui::Direction::right)
|
||||
{
|
||||
const int next = input.nav == hui::Direction::left ? 0 : 1;
|
||||
if (next != choice_)
|
||||
{
|
||||
choice_ = next;
|
||||
feedback.play(Cue::focus, next ? 0.9f : 1.1f);
|
||||
}
|
||||
}
|
||||
if (confirm)
|
||||
{
|
||||
feedback.play(Cue::select);
|
||||
go(choice_ == 0 ? s.next : s.no, feedback);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (confirm || step_age_ > done_at + hold_for(s.text))
|
||||
go(s.next, feedback);
|
||||
}
|
||||
|
||||
void CodecCall::draw(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, float clock) const
|
||||
{
|
||||
if (!active_)
|
||||
return;
|
||||
const float open = closing_ >= 0.0f ? 1.0f - tween::clamp01(closing_ / kOpen) : tween::clamp01(age_ / kOpen);
|
||||
const float w = hui::gfx::kVirtualWidth, h = hui::gfx::kVirtualHeight;
|
||||
list.rounded_rect({0, 0, w, h}, 0, Color::rgb(0x000000, std::min(1.0f, open * 3.0f)));
|
||||
// A CRT opening from its middle line.
|
||||
const float shown = h * tween::cubic_out(open);
|
||||
list.push_clip({0, (h - shown) * 0.5f, w, std::max(2.0f, shown)});
|
||||
list.rounded_rect({0, 0, w, h}, 0, kDark);
|
||||
|
||||
const bool ringing = step_ < 0;
|
||||
const Step *step = ringing ? nullptr : &kScript[step_];
|
||||
const int speaker = ringing ? -1 : step->speaker;
|
||||
const int frame = static_cast<int>(clock * 24.0f);
|
||||
|
||||
// Left: the author.
|
||||
const Rect left{230, 170, 330, 420};
|
||||
draw_portrait_frame(list, left, speaker == 0, clock);
|
||||
if (const std::uint32_t texture = avatar_texture(nullptr))
|
||||
list.image(texture, left, hui::gfx::kFullUv, Color::rgb(0xc8ffd8, speaker == 0 || ringing ? 1.0f : 0.75f), 0);
|
||||
ui::text(list, fonts.mono, "S0LITON", left.cx(), left.y + left.h + 50, 26, kGreen, Align::center, 4.0f);
|
||||
|
||||
// Right: no face, only static.
|
||||
const Rect right{1360, 170, 330, 420};
|
||||
draw_portrait_frame(list, right, speaker == 1, clock);
|
||||
if (!ringing)
|
||||
{
|
||||
constexpr int kCols = 22, kRows = 28;
|
||||
const float cw = right.w / kCols, ch = right.h / kRows;
|
||||
const float level = speaker == 1 ? 0.95f : 0.55f;
|
||||
for (int y = 0; y < kRows; ++y)
|
||||
for (int x = 0; x < kCols; ++x)
|
||||
{
|
||||
const float n = noise(x, y, frame);
|
||||
list.rounded_rect({right.x + static_cast<float>(x) * cw, right.y + static_cast<float>(y) * ch, cw, ch}, 0,
|
||||
Color{0.35f * n, 0.9f * n, 0.5f * n, level});
|
||||
}
|
||||
}
|
||||
ui::text(list, fonts.mono, "???", right.cx(), right.y + right.h + 50, 26, kGreen, Align::center, 4.0f);
|
||||
|
||||
// Middle: the frequency, and a meter for whoever speaks.
|
||||
const Rect panel{660, 230, 600, 300};
|
||||
list.bordered_rect(panel, 8, Color::rgb(0x041a0c), 2, kDimGreen);
|
||||
ui::text(list, fonts.mono, "PTT", panel.x + 30, panel.y + 48, 22, kDimGreen, Align::left, 3.0f);
|
||||
ui::text(list, fonts.mono, "MEMORY", panel.x + panel.w - 30, panel.y + 48, 22, kDimGreen, Align::right, 3.0f);
|
||||
const bool blink = ringing && std::fmod(clock, 0.8f) < 0.4f;
|
||||
ui::text(list, fonts.mono, "140.85", panel.cx(), panel.y + 190, 112, blink ? kGreen.with_alpha(0.35f) : kGreen,
|
||||
Align::center, 4.0f);
|
||||
if (ringing)
|
||||
ui::text(list, fonts.mono, "CALL", panel.cx(), panel.y + 262, 30, blink ? kGreen : kDimGreen, Align::center, 8.0f);
|
||||
else
|
||||
{
|
||||
// Bars on the speaker's side, moving while the line is typed.
|
||||
const bool talking = step->kind != Kind::watch && typed_ < static_cast<int>(std::strlen(step->text));
|
||||
for (int i = 0; i < 12; ++i)
|
||||
{
|
||||
const float t = talking ? 0.3f + 0.7f * noise(i, speaker, frame / 2) : 0.15f;
|
||||
const float bh = 10.0f + 60.0f * t;
|
||||
const float bx = speaker == 0 ? panel.x + 40 + static_cast<float>(i) * 18 : panel.x + panel.w - 40 - 12 -
|
||||
static_cast<float>(i) * 18;
|
||||
list.rounded_rect({bx, panel.y + panel.h - 24 - bh, 12, bh}, 2, kGreen.with_alpha(0.4f + 0.6f * t));
|
||||
}
|
||||
}
|
||||
|
||||
// The words.
|
||||
if (step && step->kind == Kind::watch)
|
||||
{
|
||||
// The video: in the browser, and on a phone through the QR code.
|
||||
static const char *qr_for = nullptr;
|
||||
static QrCode qr;
|
||||
if (qr_for != step->video)
|
||||
{
|
||||
qr_for = step->video;
|
||||
qr = QrCode::encode(std::string("https://www.youtube.com/watch?v=") + step->video);
|
||||
}
|
||||
ui::text(list, fonts.mono, "INCOMING TRANSMISSION", 260, 700, 24, kGreen, Align::left, 4.0f);
|
||||
ui::text(list, fonts.regular, step->text, 260, 770, 40, Color::rgb(0xe9fff0));
|
||||
ui::paragraph(list, fonts.regular,
|
||||
opened_ ? "Playing in the browser. Close it to come back here, then press Cross."
|
||||
: "The browser didn't open. Scan the code to watch on your phone, then press Cross.",
|
||||
260, 840, 28, 1050, 40, kGreen.with_alpha(0.85f), 3);
|
||||
draw_qr(list, qr, {1420, 650, 260, 260});
|
||||
ui::text(list, fonts.mono, "SCAN TO WATCH", 1550, 950, 18, kDimGreen, Align::center, 3.0f);
|
||||
}
|
||||
else if (step && step->kind != Kind::end)
|
||||
{
|
||||
ui::text(list, fonts.mono, step->speaker ? "???" : "S0LITON", 260, 760, 26, kGreen, Align::left, 3.0f);
|
||||
const std::string text(step->text, static_cast<std::size_t>(typed_));
|
||||
ui::paragraph(list, fonts.regular, text, 260, 820, 36, 1400, 50, Color::rgb(0xe9fff0), 3);
|
||||
if (step->kind == Kind::ask && typed_ >= static_cast<int>(std::strlen(step->text)))
|
||||
{
|
||||
const char *answers[] = {"YES", "NO"};
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
const Rect r{260.0f + static_cast<float>(i) * 260.0f, 880, 220, 70};
|
||||
const bool on = i == choice_;
|
||||
list.bordered_rect(r, 6, on ? kGreen : Color::rgb(0x041a0c), 3, on ? kGreen : kDimGreen);
|
||||
ui::text(list, fonts.mono, answers[i], r.cx(), r.cy() + 12, 32, on ? kDark : kGreen, Align::center, 6.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
const char *keys = step && step->kind == Kind::ask ? "LEFT RIGHT CHOOSE CROSS ANSWER CIRCLE HANG UP"
|
||||
: step && step->kind == Kind::watch ? "CROSS CONTINUE CIRCLE HANG UP"
|
||||
: "CIRCLE HANG UP CROSS NEXT";
|
||||
ui::text(list, fonts.mono, keys, w - 120, h - 60, 20, kDimGreen, Align::right, 2.0f);
|
||||
|
||||
// Scanlines over it all.
|
||||
for (float y = 0; y < h; y += 4.0f)
|
||||
list.rounded_rect({0, y, w, 1.5f}, 0, Color::rgb(0x000000, 0.28f));
|
||||
list.pop_clip();
|
||||
}
|
||||
|
||||
} // namespace rommps
|
||||
@@ -0,0 +1,67 @@
|
||||
// RommPS: the credits card in Settings (who made it, a QR code to its
|
||||
// source) and the Codec call behind the author's portrait.
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "core/input.hpp"
|
||||
#include "gfx/renderer.hpp"
|
||||
#include "rommps_look.hpp"
|
||||
#include "ui/feedback.hpp"
|
||||
#include "ui/fonts.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace rommps
|
||||
{
|
||||
|
||||
inline constexpr const char *kSourceUrl = "https://github.com/s0liton/RommPS";
|
||||
|
||||
// A QR code's modules, true for dark. Empty if the text didn't fit.
|
||||
struct QrCode
|
||||
{
|
||||
int size = 0;
|
||||
std::vector<bool> modules;
|
||||
static QrCode encode(const std::string &text);
|
||||
bool dark(int x, int y) const { return modules[static_cast<std::size_t>(y * size + x)]; }
|
||||
};
|
||||
|
||||
// Draws a QR code on a white plate (its quiet zone included) filling rect.
|
||||
void draw_qr(hui::gfx::DrawList &list, const QrCode &qr, const hui::gfx::Rect &rect);
|
||||
|
||||
// The author's portrait, as a texture once the renderer is there (0 before).
|
||||
std::uint32_t avatar_texture(hui::gfx::Renderer *renderer);
|
||||
|
||||
void draw_credits(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, const hui::gfx::Rect &rect,
|
||||
bool focused, float clock);
|
||||
|
||||
// A call on 140.85: the author's portrait, a voice with no face, and a
|
||||
// conversation about what a save file is, an original homage to a famous
|
||||
// Codec call. It plays by itself; Cross skips a line, Circle hangs up. At the
|
||||
// end it asks two questions, and a yes opens a video in the console's browser
|
||||
// (with its QR code on screen, for a phone); back from the browser, Cross
|
||||
// carries on.
|
||||
class CodecCall
|
||||
{
|
||||
public:
|
||||
void start();
|
||||
bool active() const { return active_; }
|
||||
void update(const hui::InputFrame &input, float dt, hui::ui::Feedback &feedback);
|
||||
void draw(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, float clock) const;
|
||||
|
||||
private:
|
||||
void go(int step, hui::ui::Feedback &feedback);
|
||||
|
||||
bool active_ = false;
|
||||
float age_ = 0.0f; // since the call started
|
||||
int step_ = -1; // -1 while it rings
|
||||
float step_age_ = 0.0f;
|
||||
int typed_ = 0; // characters of the line shown
|
||||
int choice_ = 0; // a question's answer: 0 yes, 1 no
|
||||
bool opened_ = false; // the browser took the video
|
||||
float closing_ = -1.0f;
|
||||
};
|
||||
|
||||
} // namespace rommps
|
||||
@@ -0,0 +1,984 @@
|
||||
// RommPS: the Library tab. See library_page.hpp.
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#include "library_page.hpp"
|
||||
|
||||
#include "ui/components/component.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
namespace rommps
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
namespace ui = hui::ui;
|
||||
namespace tween = hui::tween;
|
||||
using hui::Action;
|
||||
using hui::Direction;
|
||||
using hui::audio::Cue;
|
||||
using hui::gfx::Align;
|
||||
using hui::gfx::Rect;
|
||||
|
||||
constexpr float kMargin = 96.0f;
|
||||
|
||||
// "On this console"
|
||||
constexpr float kRecentW = 150.0f, kRecentH = 200.0f, kRecentGap = 22.0f;
|
||||
// Platform tiles
|
||||
constexpr int kTileCols = 4;
|
||||
constexpr float kTileW = 408.0f, kTileH = 220.0f, kTileGap = 32.0f, kTilePitch = kTileH + kTileGap;
|
||||
// A grid of games
|
||||
constexpr int kCols = 8;
|
||||
constexpr float kGridTop = 400.0f, kCardW = 176.0f, kCardH = 235.0f, kColPitch = 200.0f, kRowPitch = 260.0f;
|
||||
constexpr float kBarX = 1726.0f, kBarW = 98.0f, kBarTop = 392.0f, kBarH = 600.0f;
|
||||
// The A to Z bar's slots: # (digits and the rest) then A to Z.
|
||||
constexpr int kSlots = 27;
|
||||
constexpr float kSearchDelay = 0.35f; // seconds after the last key before searching
|
||||
|
||||
using Slots = std::array<int, kSlots>; // each slot's first game, -1 for none
|
||||
|
||||
int slot_of(const std::string &key)
|
||||
{
|
||||
const char c = key.empty() ? '#' : key[0];
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return 1 + (c - 'a');
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return 1 + (c - 'A');
|
||||
return 0;
|
||||
}
|
||||
|
||||
Slots slots_of(const GameList &l)
|
||||
{
|
||||
Slots s;
|
||||
s.fill(-1);
|
||||
for (const auto &[key, offset] : l.letters)
|
||||
{
|
||||
int &first = s[static_cast<std::size_t>(slot_of(key))];
|
||||
if (first < 0 || offset < first)
|
||||
first = offset;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
char slot_char(int slot)
|
||||
{
|
||||
return slot == 0 ? '#' : static_cast<char>('A' + slot - 1);
|
||||
}
|
||||
|
||||
void pop_utf8(std::string &s)
|
||||
{
|
||||
while (!s.empty() && (static_cast<unsigned char>(s.back()) & 0xC0) == 0x80)
|
||||
s.pop_back();
|
||||
if (!s.empty())
|
||||
s.pop_back();
|
||||
}
|
||||
|
||||
// The end of a text that fits a width.
|
||||
std::string tail(const hui::gfx::Font &font, const std::string &text, float size, float width)
|
||||
{
|
||||
std::size_t from = 0;
|
||||
while (from < text.size() && font.measure(std::string_view(text).substr(from), size) > width)
|
||||
++from;
|
||||
return from ? "\xE2\x80\xA6" + text.substr(from + 1) : text;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ------------------------------------------------------------------ shared
|
||||
|
||||
std::string human_size(double bytes)
|
||||
{
|
||||
char text[32];
|
||||
const char *units[] = {"B", "KB", "MB", "GB", "TB"};
|
||||
int unit = 0;
|
||||
while (bytes >= 1024.0 && unit < 4)
|
||||
{
|
||||
bytes /= 1024.0;
|
||||
++unit;
|
||||
}
|
||||
std::snprintf(text, sizeof text, unit ? "%.1f %s" : "%.0f %s", bytes, units[unit]);
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string details_line(const Details &d)
|
||||
{
|
||||
std::string out;
|
||||
auto add = [&](const std::string &part) {
|
||||
if (!part.empty())
|
||||
out += (out.empty() ? "" : " \xC2\xB7 ") + part;
|
||||
};
|
||||
if (d.year)
|
||||
add(std::to_string(d.year));
|
||||
add(d.publishers.empty() ? d.developers : d.publishers);
|
||||
add(d.genres);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string play_time(double ms)
|
||||
{
|
||||
const int minutes = static_cast<int>(ms / 60000.0 + 0.5);
|
||||
char text[32];
|
||||
if (minutes < 60)
|
||||
std::snprintf(text, sizeof text, "%d min", std::max(1, minutes));
|
||||
else if (minutes % 60 == 0)
|
||||
std::snprintf(text, sizeof text, "%d h", minutes / 60);
|
||||
else
|
||||
std::snprintf(text, sizeof text, "%d h %d min", minutes / 60, minutes % 60);
|
||||
return text;
|
||||
}
|
||||
|
||||
void draw_cover(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, App &app, const Game *g,
|
||||
const Rect &rect, float alpha, float radius, bool name)
|
||||
{
|
||||
const std::uint32_t texture = g ? app.cover(g->cover).texture : 0;
|
||||
if (texture)
|
||||
{
|
||||
list.image(texture, rect, hui::gfx::kFullUv, Color::rgb(0xffffff, alpha), radius);
|
||||
return;
|
||||
}
|
||||
// No cover (yet): a tinted card with the name.
|
||||
list.rounded_rect(rect, radius, look.covers ? Color::rgb(0x1c2140, 0.75f * alpha) : look.panel.with_alpha(look.panel.a * alpha));
|
||||
list.bordered_rect(rect, radius, Color::rgb(0x000000, 0.0f), 1.5f, look.outline.with_alpha(look.outline.a * alpha));
|
||||
if (g && name)
|
||||
ui::paragraph(list, fonts.semibold, g->name, rect.x + 16, rect.y + 44, rect.w > 160 ? 22.0f : 18.0f, rect.w - 32,
|
||||
rect.w > 160 ? 30.0f : 25.0f, look.panel_text.with_alpha(0.75f * alpha), 4);
|
||||
}
|
||||
|
||||
void draw_badge(hui::gfx::DrawList &list, const Look &look, App &app, const Game *g, const Rect &rect, Color accent)
|
||||
{
|
||||
if (!g)
|
||||
return;
|
||||
const Download *d = app.download_for(g->id);
|
||||
if (d && d->active())
|
||||
{
|
||||
const Rect bar{rect.x + 12, rect.y + rect.h - 20, rect.w - 24, 8};
|
||||
list.rounded_rect(bar, 4, Color::rgb(0x000000, 0.55f));
|
||||
list.rounded_rect({bar.x, bar.y, std::max(8.0f, bar.w * d->progress()), bar.h}, 4, accent);
|
||||
}
|
||||
else if (g->installed || (d && d->status == "done"))
|
||||
{
|
||||
const float cx = rect.x + rect.w - 24, cy = rect.y + 24;
|
||||
list.circle(cx, cy, 14, look.success);
|
||||
list.line(cx - 6, cy + 1, cx - 2, cy + 5, 3.2f, Color::rgb(0x0b0d16));
|
||||
list.line(cx - 2, cy + 5, cx + 6, cy - 5, 3.2f, Color::rgb(0x0b0d16));
|
||||
}
|
||||
}
|
||||
|
||||
void draw_version(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &, const Game *g,
|
||||
const Rect &rect)
|
||||
{
|
||||
if (!g || g->version.empty())
|
||||
return;
|
||||
const float size = rect.w > 160 ? 17.0f : 15.0f, pad = 10;
|
||||
const std::string text = fonts.semibold.font->fit(g->version, size, rect.w - 24 - 2 * pad);
|
||||
const float w = fonts.semibold.font->measure(text, size) + 2 * pad;
|
||||
const Rect tag{rect.x + 12, rect.y + rect.h - size - 30, w, size + 14};
|
||||
list.rounded_rect(tag, tag.h * 0.5f, Color::rgb(0x0b0d16, 0.78f));
|
||||
ui::text(list, fonts.semibold, text, tag.x + pad, tag.y + size + 3, size, Color::rgb(0xffffff));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ the page
|
||||
|
||||
LibraryPage::LibraryPage()
|
||||
{
|
||||
keys_.style.bindings = hui::ui::KeyboardBindings::standard();
|
||||
keys_.style.bindings.layout = Action::count;
|
||||
keys_.style.bindings.done = Action::jump_next; // R2 shows the results
|
||||
keys_.style.done_label = "Search";
|
||||
keys_.style.max_length = 60;
|
||||
keys_.on_text = [this](std::string_view text) {
|
||||
query_ += text;
|
||||
typed_age_ = 0.0f;
|
||||
};
|
||||
keys_.on_backspace = [this] {
|
||||
pop_utf8(query_);
|
||||
typed_age_ = 0.0f;
|
||||
};
|
||||
keys_.set_bounds({360, 610, 1200, 370});
|
||||
}
|
||||
|
||||
void LibraryPage::enter(App &app)
|
||||
{
|
||||
mode_age_ = 0.0f;
|
||||
app.refresh_installed();
|
||||
if (mode_ == kSearch)
|
||||
mode_ = before_search_;
|
||||
}
|
||||
|
||||
void LibraryPage::refuse(hui::ui::Feedback &feedback)
|
||||
{
|
||||
feedback.play(Cue::error, 1.0f, 0.0f, 0.6f);
|
||||
feedback.rumble(0.25f, 0.05f);
|
||||
nudge_.trigger();
|
||||
}
|
||||
|
||||
const Game *LibraryPage::focused(App &app) const
|
||||
{
|
||||
if (mode_ == kPlatforms)
|
||||
{
|
||||
const auto &recent = app.installed();
|
||||
if (section_ == 0 && recent_ >= 0 && recent_ < static_cast<int>(recent.size()))
|
||||
return &recent[static_cast<std::size_t>(recent_)];
|
||||
return nullptr;
|
||||
}
|
||||
if (mode_ == kGrid)
|
||||
return grid(app).at(index_);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string LibraryPage::backdrop_cover(App &app) const
|
||||
{
|
||||
if (const Game *g = focused(app))
|
||||
return g->cover;
|
||||
if (mode_ == kPlatforms && section_ == 1)
|
||||
{
|
||||
const auto &platforms = app.platforms();
|
||||
if (tile_ >= 0 && tile_ < static_cast<int>(platforms.size()))
|
||||
if (const Game *first = app.games(platforms[static_cast<std::size_t>(tile_)].id, "").at(0))
|
||||
return first->cover;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool LibraryPage::update(App &app, const hui::InputFrame &input, float dt, hui::ui::Feedback &feedback)
|
||||
{
|
||||
mode_age_ += dt;
|
||||
shown_age_ += dt;
|
||||
nudge_.update(dt, 9.0f);
|
||||
bool details = false;
|
||||
switch (mode_)
|
||||
{
|
||||
case kPlatforms:
|
||||
update_platforms(app, input, feedback, details);
|
||||
break;
|
||||
case kGrid:
|
||||
update_grid(app, input, feedback, details);
|
||||
break;
|
||||
case kSearch:
|
||||
update_search(app, input, dt, feedback);
|
||||
break;
|
||||
}
|
||||
// Scrolling.
|
||||
const int tile_rows = std::max(1, static_cast<int>((1000.0f - platforms_top(app) - 24.0f) / kTilePitch));
|
||||
const int tile_row = tile_ / kTileCols;
|
||||
if (tile_row < tiles_scroll_.target)
|
||||
tiles_scroll_.target = static_cast<float>(tile_row);
|
||||
if (tile_row > tiles_scroll_.target + static_cast<float>(tile_rows - 1))
|
||||
tiles_scroll_.target = static_cast<float>(tile_row - tile_rows + 1);
|
||||
tiles_scroll_.update(dt, 14.0f);
|
||||
recent_scroll_.reveal(static_cast<float>(recent_) * (kRecentW + kRecentGap),
|
||||
static_cast<float>(recent_) * (kRecentW + kRecentGap) + kRecentW, 1728.0f, 120.0f);
|
||||
recent_scroll_.update(dt, 14.0f);
|
||||
grid_scroll_.target = static_cast<float>(std::max(0, index_ / kCols - 1));
|
||||
grid_scroll_.update(dt, 14.0f);
|
||||
const Game *g = focused(app);
|
||||
const int id = g ? g->id : -1;
|
||||
if (id != shown_id_)
|
||||
{
|
||||
shown_id_ = id;
|
||||
shown_age_ = 0.0f;
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ platforms
|
||||
|
||||
float LibraryPage::platforms_top(App &app) const
|
||||
{
|
||||
return app.installed().empty() ? 196.0f : 486.0f;
|
||||
}
|
||||
|
||||
void LibraryPage::update_platforms(App &app, const hui::InputFrame &input, hui::ui::Feedback &feedback, bool &details)
|
||||
{
|
||||
const auto &recent = app.installed();
|
||||
const auto &platforms = app.platforms();
|
||||
const int nrecent = static_cast<int>(recent.size()), ntiles = static_cast<int>(platforms.size());
|
||||
if (nrecent == 0)
|
||||
section_ = 1;
|
||||
recent_ = std::clamp(recent_, 0, std::max(0, nrecent - 1));
|
||||
tile_ = std::clamp(tile_, 0, std::max(0, ntiles - 1));
|
||||
|
||||
// The covers of the tiles in view.
|
||||
const int first_row = static_cast<int>(tiles_scroll_.value);
|
||||
for (int i = first_row * kTileCols; i < std::min(ntiles, (first_row + 3) * kTileCols); ++i)
|
||||
app.ensure(app.games(platforms[static_cast<std::size_t>(i)].id, ""), 0, 0);
|
||||
|
||||
const bool repeat = input.nav_repeat;
|
||||
if (section_ == 0)
|
||||
{
|
||||
switch (input.nav)
|
||||
{
|
||||
case Direction::left:
|
||||
case Direction::right:
|
||||
{
|
||||
const int next = recent_ + (input.nav == Direction::right ? 1 : -1);
|
||||
if (next >= 0 && next < nrecent)
|
||||
{
|
||||
recent_ = next;
|
||||
feedback.play(Cue::focus);
|
||||
}
|
||||
else if (!repeat)
|
||||
refuse(feedback);
|
||||
break;
|
||||
}
|
||||
case Direction::down:
|
||||
if (ntiles > 0)
|
||||
{
|
||||
section_ = 1;
|
||||
feedback.play(Cue::tab);
|
||||
}
|
||||
else if (!repeat)
|
||||
refuse(feedback);
|
||||
break;
|
||||
case Direction::up:
|
||||
if (!repeat)
|
||||
refuse(feedback);
|
||||
break;
|
||||
case Direction::none:
|
||||
break;
|
||||
}
|
||||
if (input.is_pressed(Action::confirm) && nrecent > 0)
|
||||
{
|
||||
details = true;
|
||||
feedback.play(Cue::open);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (input.nav)
|
||||
{
|
||||
case Direction::left:
|
||||
case Direction::right:
|
||||
{
|
||||
const int col = tile_ % kTileCols;
|
||||
const int next = tile_ + (input.nav == Direction::right ? 1 : -1);
|
||||
if ((input.nav == Direction::right ? col < kTileCols - 1 : col > 0) && next >= 0 && next < ntiles)
|
||||
{
|
||||
tile_ = next;
|
||||
feedback.play(Cue::focus);
|
||||
}
|
||||
else if (!repeat)
|
||||
refuse(feedback);
|
||||
break;
|
||||
}
|
||||
case Direction::up:
|
||||
if (tile_ - kTileCols >= 0)
|
||||
{
|
||||
tile_ -= kTileCols;
|
||||
feedback.play(Cue::focus);
|
||||
}
|
||||
else if (nrecent > 0)
|
||||
{
|
||||
section_ = 0;
|
||||
feedback.play(Cue::tab);
|
||||
}
|
||||
else if (!repeat)
|
||||
refuse(feedback);
|
||||
break;
|
||||
case Direction::down:
|
||||
if (tile_ + kTileCols < ntiles)
|
||||
{
|
||||
tile_ += kTileCols;
|
||||
feedback.play(Cue::focus);
|
||||
}
|
||||
else if (tile_ / kTileCols < (ntiles - 1) / kTileCols)
|
||||
{
|
||||
tile_ = ntiles - 1;
|
||||
feedback.play(Cue::focus);
|
||||
}
|
||||
else if (!repeat)
|
||||
refuse(feedback);
|
||||
break;
|
||||
case Direction::none:
|
||||
break;
|
||||
}
|
||||
if (input.is_pressed(Action::confirm) && ntiles > 0)
|
||||
{
|
||||
feedback.play(Cue::open);
|
||||
open_grid(app, platforms[static_cast<std::size_t>(tile_)].id, "");
|
||||
}
|
||||
}
|
||||
if (input.is_pressed(Action::north))
|
||||
{
|
||||
feedback.play(Cue::modal_open);
|
||||
open_search(0);
|
||||
}
|
||||
}
|
||||
|
||||
void LibraryPage::draw_platforms(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, App &app,
|
||||
Color accent, float clock, float age) const
|
||||
{
|
||||
const auto &recent = app.installed();
|
||||
const auto &platforms = app.platforms();
|
||||
const float shake = ui::shake(nudge_.value, clock, 10.0f, 9.0f);
|
||||
|
||||
// On this console
|
||||
if (!recent.empty())
|
||||
{
|
||||
const float in = tween::stagger(age, 1, 0.08f, 0.6f);
|
||||
list.push_opacity(in);
|
||||
ui::text(list, fonts.semibold, "On this console", kMargin, 190, 24, section_ == 0 ? look.text : look.muted);
|
||||
list.push_clip({0, 150, 1920, 330});
|
||||
for (int i = 0; i < static_cast<int>(recent.size()); ++i)
|
||||
{
|
||||
const Game &g = recent[static_cast<std::size_t>(i)];
|
||||
Rect r{kMargin + static_cast<float>(i) * (kRecentW + kRecentGap) - recent_scroll_.offset(), 214, kRecentW, kRecentH};
|
||||
if (r.x > 1920 || r.x + r.w < 0)
|
||||
continue;
|
||||
const bool on = section_ == 0 && i == recent_;
|
||||
if (on)
|
||||
{
|
||||
r.x += shake;
|
||||
list.glow(r, 18, 22, accent.with_alpha(0.45f));
|
||||
}
|
||||
draw_cover(list, fonts, look, app, &g, r, on || section_ == 0 ? 1.0f : 0.8f, 16);
|
||||
if (on)
|
||||
list.bordered_rect(r.inset(-5), 20, Color::rgb(0x000000, 0.0f), 4, look.focus.with_alpha(1.0f));
|
||||
}
|
||||
list.pop_clip();
|
||||
if (section_ == 0)
|
||||
{
|
||||
const Game &g = recent[static_cast<std::size_t>(recent_)];
|
||||
const Platform *p = app.platform_by_id(g.platform_id);
|
||||
std::string line = g.name + (p ? " \xC2\xB7 " + p->name : "");
|
||||
ui::text(list, fonts.regular, fonts.regular.font->fit(line, 22, 1700), kMargin, 452, 22, look.muted);
|
||||
}
|
||||
list.pop_opacity();
|
||||
}
|
||||
|
||||
// Platforms
|
||||
const float top = platforms_top(app);
|
||||
const float in = tween::stagger(age, 2, 0.08f, 0.6f);
|
||||
list.push_opacity(in);
|
||||
char label[64];
|
||||
std::snprintf(label, sizeof label, "Platforms \xC2\xB7 %d", static_cast<int>(platforms.size()));
|
||||
ui::text(list, fonts.semibold, label, kMargin, top, 24, section_ == 1 ? look.text : look.muted);
|
||||
if (platforms.empty())
|
||||
ui::text(list, fonts.regular, "Loading your platforms", kMargin, top + 60, 26, look.muted);
|
||||
const float grid_top = top + 24;
|
||||
list.push_clip({0, grid_top - 20, 1920, 1080 - grid_top + 20});
|
||||
for (int i = 0; i < static_cast<int>(platforms.size()); ++i)
|
||||
{
|
||||
const Platform &p = platforms[static_cast<std::size_t>(i)];
|
||||
const int row = i / kTileCols, col = i % kTileCols;
|
||||
const float y = grid_top + (static_cast<float>(row) - tiles_scroll_.value) * kTilePitch;
|
||||
if (y > 1080 || y + kTileH < grid_top - 20)
|
||||
continue;
|
||||
const bool on = section_ == 1 && i == tile_;
|
||||
Rect r{kMargin + static_cast<float>(col) * (kTileW + kTileGap) + (on ? shake : 0.0f), y, kTileW, kTileH};
|
||||
if (on)
|
||||
list.glow(r, 26, 26, accent.with_alpha(0.4f));
|
||||
draw_panel(list, look, r, 26);
|
||||
// A few of its covers, fanned on the right.
|
||||
GameList &games = app.games(p.id, "");
|
||||
for (int k = 2; k >= 0; --k)
|
||||
{
|
||||
const Game *g = games.at(k);
|
||||
if (!g)
|
||||
continue;
|
||||
const Rect c{r.x + r.w - 128 - static_cast<float>(k) * 40, r.y + 28 + static_cast<float>(k) * 10, 100,
|
||||
133};
|
||||
list.shadow({c.x, c.y + 8, c.w, c.h}, 10, 16, Color::rgb(0x000000, 0.35f));
|
||||
draw_cover(list, fonts, look, app, g, c, 1.0f - 0.15f * static_cast<float>(k), 10, false);
|
||||
}
|
||||
ui::paragraph(list, fonts.semibold, p.name, r.x + 28, r.y + 56, 28, 165, 34, look.panel_text, 2);
|
||||
char count[48];
|
||||
std::snprintf(count, sizeof count, p.rom_count == 1 ? "%d game" : "%d games", p.rom_count);
|
||||
ui::text(list, fonts.regular, count, r.x + 28, r.y + 150, 22, look.panel_muted);
|
||||
ui::text(list, fonts.semibold, fonts.semibold.font->fit(p.profile.empty() ? "No emulator" : p.profile, 18, 200),
|
||||
r.x + 28, r.y + 190, 18, p.profile.empty() ? look.panel_muted.with_alpha(0.6f) : accent, Align::left,
|
||||
1.5f);
|
||||
if (on)
|
||||
list.bordered_rect(r.inset(-6), 30, Color::rgb(0x000000, 0.0f), 4, look.focus.with_alpha(1.0f));
|
||||
}
|
||||
list.pop_clip();
|
||||
list.pop_opacity();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ a grid
|
||||
|
||||
void LibraryPage::open_grid(App &app, int platform_id, const std::string &search)
|
||||
{
|
||||
grid_platform_ = platform_id;
|
||||
grid_search_ = search;
|
||||
mode_ = kGrid;
|
||||
mode_age_ = 0.0f;
|
||||
index_ = 0;
|
||||
on_letters_ = false;
|
||||
grid_scroll_.snap(0.0f);
|
||||
app.ensure(grid(app), 0, kCols * 4);
|
||||
}
|
||||
|
||||
int LibraryPage::letter_at(const GameList &l, int index) const
|
||||
{
|
||||
const Slots s = slots_of(l);
|
||||
int best = -1, best_offset = -1;
|
||||
for (int i = 0; i < kSlots; ++i)
|
||||
if (s[static_cast<std::size_t>(i)] >= 0 && s[static_cast<std::size_t>(i)] <= index &&
|
||||
s[static_cast<std::size_t>(i)] >= best_offset)
|
||||
{
|
||||
best = i;
|
||||
best_offset = s[static_cast<std::size_t>(i)];
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
void LibraryPage::jump_letter(GameList &l, int slot, hui::ui::Feedback &feedback)
|
||||
{
|
||||
const Slots s = slots_of(l);
|
||||
if (slot < 0 || slot >= kSlots || s[static_cast<std::size_t>(slot)] < 0)
|
||||
{
|
||||
refuse(feedback);
|
||||
return;
|
||||
}
|
||||
index_ = std::min(s[static_cast<std::size_t>(slot)], std::max(0, l.total - 1));
|
||||
feedback.play(Cue::focus);
|
||||
}
|
||||
|
||||
void LibraryPage::update_grid(App &app, const hui::InputFrame &input, hui::ui::Feedback &feedback, bool &details)
|
||||
{
|
||||
GameList &l = grid(app);
|
||||
if (&l == focus_list_ && l.hidden.size() != focus_hidden_)
|
||||
index_ = l.shown(focus_raw_);
|
||||
if (l.ready())
|
||||
index_ = std::min(index_, std::max(0, l.total - 1)); // the list can come out shorter than first thought
|
||||
move_in_grid(app, input, feedback, details);
|
||||
GameList &now = grid(app);
|
||||
focus_list_ = &now;
|
||||
focus_raw_ = now.ready() ? now.raw(index_) : index_;
|
||||
focus_hidden_ = now.hidden.size();
|
||||
}
|
||||
|
||||
void LibraryPage::move_in_grid(App &app, const hui::InputFrame &input, hui::ui::Feedback &feedback, bool &details)
|
||||
{
|
||||
GameList &l = grid(app);
|
||||
// The games around where the grid is going, not the rows it scrolls past
|
||||
// on the way (a jump to a letter animates through everything between).
|
||||
const int target_row = std::max(0, index_ / kCols - 2);
|
||||
app.ensure(l, target_row * kCols, (target_row + 7) * kCols);
|
||||
const int total = std::max(0, l.total);
|
||||
const Slots slots = slots_of(l);
|
||||
const bool repeat = input.nav_repeat;
|
||||
|
||||
if (input.is_pressed(Action::back))
|
||||
{
|
||||
feedback.play(Cue::back);
|
||||
if (on_letters_)
|
||||
on_letters_ = false;
|
||||
else
|
||||
{
|
||||
mode_ = kPlatforms;
|
||||
mode_age_ = 0.0f;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (input.is_pressed(Action::north))
|
||||
{
|
||||
feedback.play(Cue::modal_open);
|
||||
open_search(grid_platform_);
|
||||
return;
|
||||
}
|
||||
if (on_letters_)
|
||||
{
|
||||
if (input.nav == Direction::up || input.nav == Direction::down)
|
||||
{
|
||||
const int step = input.nav == Direction::down ? 1 : -1;
|
||||
int next = letter_ + step;
|
||||
while (next >= 0 && next < kSlots && slots[static_cast<std::size_t>(next)] < 0)
|
||||
next += step;
|
||||
if (next >= 0 && next < kSlots)
|
||||
{
|
||||
letter_ = next;
|
||||
feedback.play(Cue::focus, 1.0f, 0.0f, 0.6f);
|
||||
}
|
||||
else if (!repeat)
|
||||
refuse(feedback);
|
||||
}
|
||||
else if (input.nav == Direction::left || input.is_pressed(Action::confirm))
|
||||
{
|
||||
jump_letter(l, letter_, feedback);
|
||||
on_letters_ = false;
|
||||
}
|
||||
else if (input.nav == Direction::right && !repeat)
|
||||
refuse(feedback);
|
||||
return;
|
||||
}
|
||||
if (total == 0)
|
||||
return;
|
||||
switch (input.nav)
|
||||
{
|
||||
case Direction::left:
|
||||
if (index_ % kCols > 0)
|
||||
{
|
||||
--index_;
|
||||
feedback.play(Cue::focus);
|
||||
}
|
||||
else if (!repeat)
|
||||
refuse(feedback);
|
||||
break;
|
||||
case Direction::right:
|
||||
if (index_ % kCols < kCols - 1 && index_ + 1 < total)
|
||||
{
|
||||
++index_;
|
||||
feedback.play(Cue::focus);
|
||||
}
|
||||
else if (!l.letters.empty())
|
||||
{
|
||||
on_letters_ = true;
|
||||
letter_ = std::max(0, letter_at(l, index_));
|
||||
feedback.play(Cue::tab);
|
||||
}
|
||||
else if (!repeat)
|
||||
refuse(feedback);
|
||||
break;
|
||||
case Direction::up:
|
||||
if (index_ - kCols >= 0)
|
||||
{
|
||||
index_ -= kCols;
|
||||
feedback.play(Cue::focus);
|
||||
}
|
||||
else if (!repeat)
|
||||
refuse(feedback);
|
||||
break;
|
||||
case Direction::down:
|
||||
if (index_ + kCols < total)
|
||||
{
|
||||
index_ += kCols;
|
||||
feedback.play(Cue::focus);
|
||||
}
|
||||
else if (index_ / kCols < (total - 1) / kCols)
|
||||
{
|
||||
index_ = total - 1;
|
||||
feedback.play(Cue::focus);
|
||||
}
|
||||
else if (!repeat)
|
||||
refuse(feedback);
|
||||
break;
|
||||
case Direction::none:
|
||||
break;
|
||||
}
|
||||
if (input.is_pressed(Action::jump_prev) || input.is_pressed(Action::jump_next))
|
||||
{
|
||||
const int here = std::max(0, letter_at(l, index_));
|
||||
const int step = input.is_pressed(Action::jump_next) ? 1 : -1;
|
||||
int next = here + step;
|
||||
// Back: the start of this letter first, when the focus is past it.
|
||||
if (step < 0 && here >= 0 && slots[static_cast<std::size_t>(here)] >= 0 &&
|
||||
slots[static_cast<std::size_t>(here)] < index_)
|
||||
next = here;
|
||||
while (next >= 0 && next < kSlots && slots[static_cast<std::size_t>(next)] < 0)
|
||||
next += step;
|
||||
jump_letter(l, next, feedback);
|
||||
}
|
||||
if (input.is_pressed(Action::confirm))
|
||||
{
|
||||
if (l.at(index_))
|
||||
{
|
||||
details = true;
|
||||
feedback.play(Cue::open);
|
||||
}
|
||||
else
|
||||
refuse(feedback);
|
||||
}
|
||||
}
|
||||
|
||||
void LibraryPage::draw_grid(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, App &app,
|
||||
Color accent, float clock, float age) const
|
||||
{
|
||||
GameList &l = grid(app);
|
||||
const Game *g = l.at(index_);
|
||||
const Platform *own = app.platform_by_id(grid_platform_);
|
||||
const Platform *p = app.platform_by_id(g ? g->platform_id : grid_platform_);
|
||||
if (!p)
|
||||
p = own;
|
||||
|
||||
// The heading: where we are, then the focused game.
|
||||
const float in = tween::stagger(age, 1, 0.08f, 0.6f);
|
||||
list.push_opacity(in);
|
||||
std::string where = grid_search_.empty() ? (own ? own->name : "Library")
|
||||
: "\xE2\x80\x9C" + grid_search_ + "\xE2\x80\x9D \xC2\xB7 " +
|
||||
(own ? "in " + own->name : "every platform");
|
||||
char count[48] = "";
|
||||
if (l.ready())
|
||||
std::snprintf(count, sizeof count, l.total == 1 ? "1 game" : "%d games", l.total);
|
||||
ui::text(list, fonts.semibold, ui::upper(fonts.semibold.font->fit(where, 20, 1300)), kMargin, 196, 20, accent,
|
||||
Align::left, 4.0f);
|
||||
ui::text(list, fonts.regular, count, 1824, 196, 22, look.muted, Align::right);
|
||||
const float fade = tween::smoothstep(tween::clamp01(shown_age_ / 0.25f));
|
||||
if (g)
|
||||
{
|
||||
list.push_opacity(fade);
|
||||
ui::text(list, fonts.display, fonts.display.font->fit(g->name, 60, 1600), kMargin - 3 + 12.0f * (1.0f - fade), 268,
|
||||
60, look.text);
|
||||
std::string meta = human_size(g->size);
|
||||
if (g->versions > 1)
|
||||
meta = g->version + ", 1 of " + std::to_string(g->versions) + " versions \xC2\xB7 " + meta;
|
||||
if (!grid_search_.empty() || !own)
|
||||
meta += " \xC2\xB7 " + (p ? p->name : std::string());
|
||||
meta += " \xC2\xB7 " + (p && !p->profile.empty() ? p->profile : std::string("No emulator"));
|
||||
// RomM's details when it has some: year, publisher, genres first.
|
||||
const Details &info = app.details(g->id);
|
||||
const std::string about = details_line(info);
|
||||
if (!about.empty())
|
||||
{
|
||||
if (info.play_ms > 0)
|
||||
meta += " \xC2\xB7 played " + play_time(info.play_ms);
|
||||
ui::text(list, fonts.regular, fonts.regular.font->fit(about, 24, 1300), kMargin, 314, 24, look.muted);
|
||||
}
|
||||
else
|
||||
ui::text(list, fonts.regular, fonts.regular.font->fit(meta, 24, 1300), kMargin, 314, 24, look.muted);
|
||||
const Download *d = app.download_for(g->id);
|
||||
if (d && d->active())
|
||||
{
|
||||
const Rect bar{kMargin, 336, 420, 8};
|
||||
list.rounded_rect(bar, 4, look.muted.with_alpha(0.25f));
|
||||
list.rounded_rect({bar.x, bar.y, std::max(8.0f, bar.w * d->progress()), bar.h}, 4, accent);
|
||||
}
|
||||
else
|
||||
{
|
||||
const float w = ui::text(list, fonts.semibold, g->installed ? "Downloaded" : "In your RomM library", kMargin,
|
||||
352, 20, g->installed ? look.success : look.muted);
|
||||
// With the details on the line above, the file and emulator follow the state.
|
||||
if (!about.empty())
|
||||
ui::text(list, fonts.regular, fonts.regular.font->fit("\xC2\xB7 " + meta, 20, 1200), kMargin + w + 14,
|
||||
352, 20, look.muted);
|
||||
}
|
||||
list.pop_opacity();
|
||||
}
|
||||
else if (!l.ready())
|
||||
ui::text(list, fonts.display, l.error.empty() ? "Loading" : "Couldn't load these games", kMargin - 3, 268, 60,
|
||||
look.text);
|
||||
else if (l.total == 0)
|
||||
{
|
||||
ui::text(list, fonts.display, grid_search_.empty() ? "No games here" : "Nothing matches", kMargin - 3, 268, 60,
|
||||
look.text);
|
||||
ui::text(list, fonts.regular, grid_search_.empty() ? "" : "Try fewer letters, or search every platform",
|
||||
kMargin, 314, 24, look.muted);
|
||||
}
|
||||
if (!l.error.empty())
|
||||
ui::text(list, fonts.regular, fonts.regular.font->fit(l.error, 20, 1200), kMargin, 352, 20, look.danger);
|
||||
list.pop_opacity();
|
||||
|
||||
// The grid.
|
||||
const float appear = tween::stagger(age, 2, 0.08f, 0.6f);
|
||||
list.push_opacity(appear);
|
||||
const float shake = ui::shake(nudge_.value, clock, 10.0f, 9.0f);
|
||||
const int total = std::max(0, l.total);
|
||||
const int first_row = std::max(0, static_cast<int>(grid_scroll_.value) - 1);
|
||||
list.push_clip({0, kGridTop - 30, kBarX - 10, 1080 - kGridTop + 30});
|
||||
Rect focus_rect{};
|
||||
for (int row = first_row; row < first_row + 5; ++row)
|
||||
for (int col = 0; col < kCols; ++col)
|
||||
{
|
||||
const int i = row * kCols + col;
|
||||
if (i >= total)
|
||||
break;
|
||||
const float y = kGridTop + (static_cast<float>(row) - grid_scroll_.value) * kRowPitch + 24 * (1.0f - appear);
|
||||
if (y > 1080 || y + kCardH < kGridTop - 40)
|
||||
continue;
|
||||
const Rect r{kMargin + static_cast<float>(col) * kColPitch, y, kCardW, kCardH};
|
||||
if (i == index_)
|
||||
{
|
||||
focus_rect = r;
|
||||
continue; // drawn last, on top
|
||||
}
|
||||
const Game *game = l.at(i);
|
||||
const float dim = y < kGridTop - 4 ? 0.35f : 0.85f;
|
||||
if (game)
|
||||
{
|
||||
draw_cover(list, fonts, look, app, game, r, dim, 14);
|
||||
draw_badge(list, look, app, game, r, accent);
|
||||
draw_version(list, fonts, look, game, r);
|
||||
}
|
||||
else
|
||||
list.rounded_rect(r, 14, look.panel.with_alpha(look.panel.a * 0.6f));
|
||||
}
|
||||
if (focus_rect.w > 0)
|
||||
{
|
||||
const float grow = 1.08f;
|
||||
Rect r{focus_rect.x - focus_rect.w * (grow - 1) * 0.5f + (on_letters_ ? 0.0f : shake),
|
||||
focus_rect.y - focus_rect.h * (grow - 1) * 0.5f, focus_rect.w * grow, focus_rect.h * grow};
|
||||
list.shadow({r.x, r.y + 16, r.w, r.h}, 18, 30, Color::rgb(0x000000, 0.55f));
|
||||
if (!on_letters_)
|
||||
list.glow(r, 18, 24, accent.with_alpha(0.4f + 0.15f * ui::breathe(clock)));
|
||||
draw_cover(list, fonts, look, app, l.at(index_), r, 1.0f, 18);
|
||||
draw_badge(list, look, app, l.at(index_), r, accent);
|
||||
draw_version(list, fonts, look, l.at(index_), r);
|
||||
list.bordered_rect(r.inset(-5), 22, Color::rgb(0x000000, 0.0f), 4,
|
||||
look.focus.with_alpha(on_letters_ ? 0.35f : 1.0f));
|
||||
}
|
||||
list.pop_clip();
|
||||
// The covers of the two rows below are asked for too (after the ones on
|
||||
// screen), so scrolling down finds them ready.
|
||||
for (int i = (first_row + 5) * kCols; i < (first_row + 7) * kCols && i < total; ++i)
|
||||
if (const Game *game = l.at(i))
|
||||
app.cover(game->cover);
|
||||
// The row below fades out under the button hints.
|
||||
list.gradient_rect({0, 930, kBarX - 10, 150}, 0, look.page.with_alpha(0.0f), look.page.with_alpha(0.85f));
|
||||
|
||||
// The A to Z bar.
|
||||
if (!l.letters.empty())
|
||||
{
|
||||
const Slots slots = slots_of(l);
|
||||
const int here = letter_at(l, index_);
|
||||
list.rounded_rect({kBarX, kBarTop, kBarW, kBarH}, 24, look.panel);
|
||||
list.bordered_rect({kBarX, kBarTop, kBarW, kBarH}, 24, Color::rgb(0x000000, 0.0f), 1.5f, look.outline);
|
||||
const float pitch = (kBarH - 24) / kSlots;
|
||||
for (int s = 0; s < kSlots; ++s)
|
||||
{
|
||||
const float cy = kBarTop + 12 + pitch * (static_cast<float>(s) + 0.5f);
|
||||
const bool has = slots[static_cast<std::size_t>(s)] >= 0;
|
||||
const bool focused = on_letters_ && s == letter_;
|
||||
if (focused)
|
||||
list.rounded_rect({kBarX + 16, cy - pitch * 0.5f - 2, kBarW - 32, pitch + 4}, 8, look.highlight);
|
||||
else if (s == here)
|
||||
list.rounded_rect({kBarX + 22, cy - pitch * 0.5f, kBarW - 44, pitch}, 8, accent.with_alpha(0.25f));
|
||||
const char text[2] = {slot_char(s), 0};
|
||||
ui::text(list, fonts.semibold, text, kBarX + kBarW * 0.5f, cy + 7, 19,
|
||||
focused ? look.on_highlight
|
||||
: has ? (s == here ? look.panel_text : look.panel_muted)
|
||||
: look.panel_muted.with_alpha(0.25f),
|
||||
Align::center);
|
||||
}
|
||||
}
|
||||
list.pop_opacity();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ search
|
||||
|
||||
void LibraryPage::open_search(int platform_id)
|
||||
{
|
||||
before_search_ = mode_ == kSearch ? before_search_ : mode_;
|
||||
mode_ = kSearch;
|
||||
mode_age_ = 0.0f;
|
||||
search_platform_ = platform_id;
|
||||
query_.clear();
|
||||
applied_.clear();
|
||||
typed_age_ = 0.0f;
|
||||
keys_.set_length(0);
|
||||
keys_.enter();
|
||||
}
|
||||
|
||||
void LibraryPage::update_search(App &app, const hui::InputFrame &input, float dt, hui::ui::Feedback &feedback)
|
||||
{
|
||||
keys_.style.theme = *look(current_look()).theme;
|
||||
keys_.set_length(static_cast<int>(query_.size()));
|
||||
const hui::ui::Event e = keys_.handle(input, feedback);
|
||||
keys_.update(dt);
|
||||
typed_age_ += dt;
|
||||
if (query_ != applied_ && typed_age_ > kSearchDelay)
|
||||
applied_ = query_;
|
||||
if (!applied_.empty())
|
||||
app.ensure(app.games(search_platform_, applied_), 0, 9);
|
||||
if (e == hui::ui::Event::cancelled)
|
||||
{
|
||||
mode_ = before_search_;
|
||||
mode_age_ = 0.0f;
|
||||
}
|
||||
else if (e == hui::ui::Event::activated)
|
||||
{
|
||||
if (query_.empty())
|
||||
refuse(feedback);
|
||||
else
|
||||
open_grid(app, search_platform_, query_);
|
||||
}
|
||||
}
|
||||
|
||||
void LibraryPage::draw_search(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, App &app,
|
||||
Color accent, float clock) const
|
||||
{
|
||||
const Platform *p = app.platform_by_id(search_platform_);
|
||||
const float in = tween::stagger(mode_age_, 0, 0.06f, 0.5f);
|
||||
list.push_opacity(in);
|
||||
ui::text(list, fonts.semibold, ui::upper(p ? "Search " + p->name : std::string("Search every platform")), kMargin,
|
||||
196, 20, accent, Align::left, 4.0f);
|
||||
const Rect field{kMargin, 220, 1300, 84};
|
||||
draw_panel(list, look, field, 22);
|
||||
const float size = 36.0f;
|
||||
const std::string shown = tail(*fonts.regular.font, query_, size, field.w - 80);
|
||||
const float w = query_.empty() ? 0.0f
|
||||
: ui::text(list, fonts.regular, shown, field.x + 32, field.cy() + 13, size, look.panel_text);
|
||||
if (query_.empty())
|
||||
ui::text(list, fonts.regular, "A few letters of the name", field.x + 32, field.cy() + 13, size,
|
||||
look.panel_muted.with_alpha(0.6f));
|
||||
if (std::fmod(clock, 1.0f) < 0.6f)
|
||||
list.rounded_rect({field.x + 34 + w, field.y + 20, 3, field.h - 40}, 1.5f, accent);
|
||||
|
||||
// The first results, as you type.
|
||||
std::string status = "Type to search";
|
||||
const GameList *l = applied_.empty() ? nullptr : &app.games(search_platform_, applied_);
|
||||
if (l)
|
||||
{
|
||||
char text[64];
|
||||
if (!l->ready())
|
||||
std::snprintf(text, sizeof text, "Searching");
|
||||
else
|
||||
std::snprintf(text, sizeof text, l->total == 1 ? "1 game" : "%d games", l->total);
|
||||
status = l->error.empty() ? text : l->error;
|
||||
}
|
||||
ui::text(list, fonts.semibold, fonts.semibold.font->fit(status, 24, 440), 1824, field.cy() + 9, 24, look.muted,
|
||||
Align::right);
|
||||
if (l)
|
||||
{
|
||||
for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
const Game *g = l->at(i);
|
||||
if (!g)
|
||||
break;
|
||||
const Rect r{kMargin + static_cast<float>(i) * 192.0f, 340, 168, 224};
|
||||
draw_cover(list, fonts, look, app, g, r, 1.0f, 14);
|
||||
draw_badge(list, look, app, g, r, accent);
|
||||
draw_version(list, fonts, look, g, r);
|
||||
}
|
||||
}
|
||||
ui::Canvas canvas{list, fonts, 0, clock};
|
||||
keys_.draw(canvas);
|
||||
list.pop_opacity();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ drawing
|
||||
|
||||
void LibraryPage::draw(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, App &app, Color accent,
|
||||
float clock, float age) const
|
||||
{
|
||||
switch (mode_)
|
||||
{
|
||||
case kPlatforms:
|
||||
draw_platforms(list, fonts, look, app, accent, clock, std::min(age, mode_age_));
|
||||
break;
|
||||
case kGrid:
|
||||
draw_grid(list, fonts, look, app, accent, clock, std::min(age, mode_age_));
|
||||
break;
|
||||
case kSearch:
|
||||
draw_search(list, fonts, look, app, accent, clock);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<hui::ui::Hint> LibraryPage::hints() const
|
||||
{
|
||||
using hui::ui::Button;
|
||||
switch (mode_)
|
||||
{
|
||||
case kPlatforms:
|
||||
return {{Button::l1, "Pages", Button::r1},
|
||||
{Button::triangle, "Search"},
|
||||
{Button::cross, section_ == 0 ? "Details" : "Open"}};
|
||||
case kGrid:
|
||||
if (on_letters_)
|
||||
return {{Button::cross, "Jump"}, {Button::circle, "Back"}};
|
||||
return {{Button::l2, "Letters", Button::r2},
|
||||
{Button::triangle, "Search"},
|
||||
{Button::cross, "Details"},
|
||||
{Button::circle, "Back"}};
|
||||
case kSearch:
|
||||
return {{Button::square, "Delete"}, {Button::r2, "Show all"}, {Button::circle, "Back"}};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace rommps
|
||||
@@ -0,0 +1,130 @@
|
||||
// RommPS: the Library tab.
|
||||
//
|
||||
// - Platforms: "On this console" (the games here, most recently played
|
||||
// first) above a grid of RomM's platforms, each with a few of its covers.
|
||||
// Cross opens one, Triangle searches every platform.
|
||||
// - A platform (or a search's results): the focused game's details above a
|
||||
// grid of covers that loads as it scrolls, and an A to Z bar on the right
|
||||
// (Right from the last column to reach it, L2 / R2 to jump a letter).
|
||||
// Triangle searches the platform, Circle goes back.
|
||||
// - Search: the kit's keyboard, the first results updating as you type;
|
||||
// R2 (or Done) shows them all.
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "core/input.hpp"
|
||||
#include "core/tween.hpp"
|
||||
#include "rommps_app.hpp"
|
||||
#include "rommps_look.hpp"
|
||||
#include "ui/components/keyboard.hpp"
|
||||
#include "ui/feedback.hpp"
|
||||
#include "ui/fonts.hpp"
|
||||
#include "ui/glyphs.hpp"
|
||||
#include "ui/motion.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace rommps
|
||||
{
|
||||
|
||||
// A game's cover, or a card with its name while there's none.
|
||||
void draw_cover(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, App &app, const Game *g,
|
||||
const hui::gfx::Rect &rect, float alpha, float radius, bool name = true);
|
||||
// "Downloaded", or a running download's progress, on a card.
|
||||
void draw_badge(hui::gfx::DrawList &list, const Look &look, App &app, const Game *g, const hui::gfx::Rect &rect,
|
||||
Color accent);
|
||||
// When RomM has more than one version of the game, what sets this one apart
|
||||
// ("USA, Rev 1"), in a tag along the cover's bottom edge.
|
||||
void draw_version(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, const Game *g,
|
||||
const hui::gfx::Rect &rect);
|
||||
std::string human_size(double bytes);
|
||||
// "1994 · Nintendo · Platform, Adventure" ("" while there's nothing to say).
|
||||
std::string details_line(const Details &d);
|
||||
// "1 h 30 min" of play time.
|
||||
std::string play_time(double ms);
|
||||
|
||||
class LibraryPage
|
||||
{
|
||||
public:
|
||||
LibraryPage();
|
||||
LibraryPage(const LibraryPage &) = delete;
|
||||
LibraryPage &operator=(const LibraryPage &) = delete;
|
||||
|
||||
void enter(App &app);
|
||||
// True when Cross asks for the focused game's details.
|
||||
bool update(App &app, const hui::InputFrame &input, float dt, hui::ui::Feedback &feedback);
|
||||
void draw(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, App &app, Color accent,
|
||||
float clock, float age) const;
|
||||
// The game the focus is on (nullptr on a platform tile or a placeholder).
|
||||
const Game *focused(App &app) const;
|
||||
// A cover to colour the backdrop with ("" for none).
|
||||
std::string backdrop_cover(App &app) const;
|
||||
std::vector<hui::ui::Hint> hints() const;
|
||||
|
||||
private:
|
||||
enum Mode
|
||||
{
|
||||
kPlatforms,
|
||||
kGrid,
|
||||
kSearch
|
||||
};
|
||||
|
||||
// ---- platforms
|
||||
void update_platforms(App &app, const hui::InputFrame &input, hui::ui::Feedback &feedback, bool &details);
|
||||
void draw_platforms(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, App &app, Color accent,
|
||||
float clock, float age) const;
|
||||
float platforms_top(App &app) const;
|
||||
// ---- a grid of games
|
||||
void open_grid(App &app, int platform_id, const std::string &search);
|
||||
void update_grid(App &app, const hui::InputFrame &input, hui::ui::Feedback &feedback, bool &details);
|
||||
void move_in_grid(App &app, const hui::InputFrame &input, hui::ui::Feedback &feedback, bool &details);
|
||||
void draw_grid(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, App &app, Color accent,
|
||||
float clock, float age) const;
|
||||
GameList &grid(App &app) const { return app.games(grid_platform_, grid_search_); }
|
||||
int letter_at(const GameList &l, int index) const; // index into l.letters, -1 for none
|
||||
void jump_letter(GameList &l, int letter, hui::ui::Feedback &feedback);
|
||||
// ---- search
|
||||
void open_search(int platform_id);
|
||||
void update_search(App &app, const hui::InputFrame &input, float dt, hui::ui::Feedback &feedback);
|
||||
void draw_search(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, App &app, Color accent,
|
||||
float clock) const;
|
||||
|
||||
void refuse(hui::ui::Feedback &feedback);
|
||||
|
||||
Mode mode_ = kPlatforms;
|
||||
Mode before_search_ = kPlatforms;
|
||||
float mode_age_ = 0.0f;
|
||||
hui::ui::Pulse nudge_;
|
||||
|
||||
// platforms: section 0 is "On this console", 1 the platform grid
|
||||
int section_ = 1;
|
||||
int recent_ = 0, tile_ = 0;
|
||||
hui::ui::Scroller recent_scroll_;
|
||||
hui::tween::Spring tiles_scroll_; // in rows
|
||||
|
||||
// a grid
|
||||
int grid_platform_ = 0;
|
||||
std::string grid_search_;
|
||||
int index_ = 0;
|
||||
// The focused game by RomM's offset, so it stays put when a page turns out
|
||||
// to hold files that aren't games, which shifts the shown positions.
|
||||
const GameList *focus_list_ = nullptr;
|
||||
int focus_raw_ = 0;
|
||||
std::size_t focus_hidden_ = 0;
|
||||
bool on_letters_ = false;
|
||||
int letter_ = 0; // the A to Z bar's focus, an index into the list's letters
|
||||
hui::tween::Spring grid_scroll_; // in rows
|
||||
hui::tween::Spring focus_x_, focus_y_;
|
||||
int shown_id_ = -1;
|
||||
float shown_age_ = 0.0f;
|
||||
|
||||
// search
|
||||
int search_platform_ = 0;
|
||||
std::string query_, applied_;
|
||||
float typed_age_ = 0.0f;
|
||||
hui::ui::Keyboard keys_;
|
||||
};
|
||||
|
||||
} // namespace rommps
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,356 @@
|
||||
// RommPS: what the screen shows, kept up to date from the RomM Sync payload.
|
||||
//
|
||||
// The payload does the work (sync, downloads, RomM) and serves its state on
|
||||
// http://127.0.0.1:<port>/api (src/web.c in the repository). This keeps a
|
||||
// copy for the screen: status, platforms, the games of the platforms looked
|
||||
// at, downloads and covers, refreshed on timers through the API client
|
||||
// (platform/ps5/app/src/rommps_api.hpp) so a frame never waits for it.
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "gfx/draw_list.hpp"
|
||||
#include "gfx/renderer.hpp"
|
||||
#include "rommps_api.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace rommps
|
||||
{
|
||||
|
||||
using hui::gfx::Color;
|
||||
|
||||
// Colours taken from a cover, for the backdrop and highlights.
|
||||
struct Palette
|
||||
{
|
||||
Color dark = Color::rgb(0x14182e);
|
||||
Color mid = Color::rgb(0x2b3466);
|
||||
Color accent = Color::rgb(0x76d6ff);
|
||||
};
|
||||
|
||||
struct Cover
|
||||
{
|
||||
std::uint32_t texture = 0; // 0 until it's loaded (or if there is none)
|
||||
Palette palette;
|
||||
bool requested = false;
|
||||
bool failed = false;
|
||||
double used = 0; // when it was last drawn
|
||||
};
|
||||
|
||||
struct Game
|
||||
{
|
||||
int id = 0;
|
||||
int platform_id = 0;
|
||||
std::string name, fs_name, cover; // cover: the RomM asset path, "" if none
|
||||
double size = 0;
|
||||
bool installed = false;
|
||||
bool multi = false;
|
||||
bool not_game = false; // a file RomM scanned beside the games (metadata.txt...): hidden
|
||||
std::string version; // what sets this version apart, when RomM has others of the game
|
||||
int versions = 0; // how many versions RomM has, this one included; 0 if just one
|
||||
};
|
||||
|
||||
// More about a game (/api/roms/<id>/details): RomM's metadata, the user's own
|
||||
// data and the play time RomM has recorded. Empty fields weren't known.
|
||||
struct Details
|
||||
{
|
||||
bool loaded = false, failed = false;
|
||||
int year = 0;
|
||||
std::string publishers, developers, genres, players, summary, status, last_played;
|
||||
double rating = 0; // RomM's average, on its own scale (shown /10 or /100)
|
||||
double main_story_s = 0; // How Long to Beat, the main story
|
||||
double play_ms = 0; // every device's sessions RomM has
|
||||
int sessions = 0, completion = 0, my_rating = 0;
|
||||
};
|
||||
|
||||
struct Platform
|
||||
{
|
||||
int id = 0;
|
||||
std::string name, slug, profile; // profile: the emulator, "" if none plays it
|
||||
int rom_count = 0;
|
||||
};
|
||||
|
||||
// A list of games from RomM, A to Z: a platform's, a search's (in one
|
||||
// platform, or every platform when platform_id is 0). Loaded a page at a time
|
||||
// as it's looked at.
|
||||
struct GameList
|
||||
{
|
||||
static constexpr int kPage = 60;
|
||||
int platform_id = 0;
|
||||
std::string search;
|
||||
int total = -1; // games to show; unknown until the first page arrives
|
||||
std::vector<Game> games; // RomM's whole list, by RomM's offset; id 0 isn't loaded yet
|
||||
std::vector<unsigned char> pages; // per page: 0 not asked, 1 asked, 2 here
|
||||
std::vector<std::pair<std::string, int>> letters; // first letter -> the index of its first game
|
||||
std::vector<std::pair<std::string, int>> raw_letters; // the same, by RomM's offset
|
||||
std::vector<int> hidden; // RomM offsets of the files that aren't games, sorted
|
||||
std::string error;
|
||||
bool ready() const { return total >= 0; }
|
||||
// Positions in the list as shown, which skips what isn't a game, and
|
||||
// RomM's offsets. What isn't a game is known once its page is here.
|
||||
int raw(int index) const
|
||||
{
|
||||
for (int h : hidden)
|
||||
if (h <= index)
|
||||
++index;
|
||||
return index;
|
||||
}
|
||||
int shown(int raw_index) const
|
||||
{
|
||||
int before = 0;
|
||||
for (int h : hidden)
|
||||
before += h < raw_index;
|
||||
return raw_index - before;
|
||||
}
|
||||
const Game *at(int index) const
|
||||
{
|
||||
if (index < 0 || index >= total)
|
||||
return nullptr;
|
||||
const int r = raw(index);
|
||||
return r < static_cast<int>(games.size()) && games[static_cast<std::size_t>(r)].id
|
||||
? &games[static_cast<std::size_t>(r)]
|
||||
: nullptr;
|
||||
}
|
||||
void reindex() // after a page arrives
|
||||
{
|
||||
hidden.clear();
|
||||
for (std::size_t i = 0; i < games.size(); ++i)
|
||||
if (games[i].id && games[i].not_game)
|
||||
hidden.push_back(static_cast<int>(i));
|
||||
total = static_cast<int>(games.size() - hidden.size());
|
||||
letters.clear();
|
||||
for (const auto &[key, offset] : raw_letters)
|
||||
letters.push_back({key, shown(offset)});
|
||||
}
|
||||
};
|
||||
|
||||
struct Download
|
||||
{
|
||||
int id = 0, rom_id = 0;
|
||||
std::string name, platform, status, error;
|
||||
double done = 0, total = 0, speed = 0;
|
||||
float progress() const { return total > 0 ? static_cast<float>(done / total) : 0.0f; }
|
||||
// The payload's states: queued, downloading, cancelling, then done, error or cancelled.
|
||||
bool running() const { return status == "downloading"; }
|
||||
bool active() const { return status == "queued" || running() || status == "cancelling"; }
|
||||
bool finished() const { return !active(); }
|
||||
};
|
||||
|
||||
struct Status
|
||||
{
|
||||
bool known = false; // an answer arrived at least once
|
||||
bool reachable = false; // the payload answered the last time
|
||||
bool paired = false, setup_complete = false;
|
||||
std::string version, server, user, ip, error;
|
||||
int port = 8780;
|
||||
int tracked_saves = 0, conflicts = 0, sync_count = 0;
|
||||
bool syncing = false;
|
||||
std::string phase;
|
||||
double last_run = 0; // the payload's clock, seconds
|
||||
double uptime = 0;
|
||||
std::string last_result; // "OK", or the last sync's error
|
||||
int last_up = 0, last_down = 0;
|
||||
std::string server_version;
|
||||
// Every cover kept on the console (covers.h in the payload).
|
||||
std::string covers_state;
|
||||
int covers_done = 0, covers_total = 0;
|
||||
// Pairing in progress (/api/pair/start): "pending" while RomM waits for
|
||||
// the code to be approved, "error" when it failed, "" otherwise.
|
||||
std::string pairing, pairing_message, user_code, verification_url;
|
||||
};
|
||||
|
||||
// The payload's settings that RommPS shows (/api/config).
|
||||
struct Config
|
||||
{
|
||||
bool known = false;
|
||||
std::string server_url;
|
||||
bool tls_verify = true;
|
||||
int sync_interval_min = 0;
|
||||
bool sync_on_game_exit = true, sync_on_game_start = false, sync_on_change = false;
|
||||
bool notify = true, update_check = true;
|
||||
std::string states = "off"; // off, upload, sync
|
||||
std::string conflict_policy = "ask"; // ask, newest, local, server
|
||||
std::string ps2_cards = "per_game"; // per_game, backup
|
||||
bool cover_cache = true;
|
||||
int download_concurrency = 1;
|
||||
};
|
||||
|
||||
// The payload's own updates (/api/update).
|
||||
struct Update
|
||||
{
|
||||
bool known = false;
|
||||
std::string state, message, error, current, latest;
|
||||
bool available = false, autostart = false;
|
||||
// false: RomM Sync doesn't update itself; a new RommPS brings it (PS5).
|
||||
bool self_update = true;
|
||||
double done = 0, total = 0;
|
||||
bool busy() const { return state == "checking" || state == "downloading" || state == "installing" || state == "restarting"; }
|
||||
};
|
||||
|
||||
class App
|
||||
{
|
||||
public:
|
||||
App();
|
||||
|
||||
// Once per frame: delivers finished requests, starts the timed ones and
|
||||
// turns decoded covers into textures (renderer may be null in tests).
|
||||
void tick(float dt, hui::gfx::Renderer *renderer);
|
||||
|
||||
const Status &status() const { return status_; }
|
||||
// RommPS carries the RomM Sync payload of its own release
|
||||
// (assets/rommps/romm-sync.elf, version kMinPayload): it starts it when
|
||||
// none is running, puts it in place of an older one (never of a newer
|
||||
// one), and keeps the copies the console starts it from up to date.
|
||||
static constexpr const char *kMinPayload = "1.1.1";
|
||||
bool payload_too_old() const;
|
||||
void start_payload(); // sends the payload to the HEN's loader
|
||||
// Sending it, or RomM Sync is running and not answering yet (starting up,
|
||||
// or waking from rest mode): either way it'll answer in a moment.
|
||||
bool payload_starting() const { return start_state_ == 1 || payload_waiting_; }
|
||||
const std::string &payload_error() const { return start_error_; }
|
||||
// The payload into the HEN's autostart folder, so it starts with the console.
|
||||
void install_autostart(Api::Callback done);
|
||||
const std::vector<Platform> &platforms() const { return platforms_; }
|
||||
const std::vector<Download> &downloads() const { return downloads_; }
|
||||
const Download *download_for(int rom_id) const;
|
||||
|
||||
const Platform *platform_by_id(int id) const;
|
||||
|
||||
// A list of games (made the first time it's asked for), and the pages
|
||||
// holding [first, last] loaded. Lists stay for a while, a few at most.
|
||||
GameList &games(int platform_id, const std::string &search);
|
||||
void ensure(GameList &list, int first, int last);
|
||||
|
||||
// The games on this console, most recently played first.
|
||||
const std::vector<Game> &installed() const { return installed_; }
|
||||
// A game's details; asks for them the first time.
|
||||
const Details &details(int rom_id);
|
||||
void refresh_installed();
|
||||
// Asks for the platforms again: which emulator plays each, and its
|
||||
// folder, change when emulators are set up here or in the web UI.
|
||||
void reload_platforms()
|
||||
{
|
||||
if (!platforms_busy_)
|
||||
refresh_platforms();
|
||||
}
|
||||
// A cover's texture and palette; asks for it the first time.
|
||||
const Cover &cover(const std::string &path);
|
||||
|
||||
void download(int rom_id, const std::string &name);
|
||||
void cancel_download(int job_id);
|
||||
void clear_finished_downloads();
|
||||
void sync_now();
|
||||
|
||||
const Config &config() const { return config_; }
|
||||
void refresh_config();
|
||||
// Changes settings: fields is a JSON object's members ("\"notify\":true").
|
||||
// The copy here changes at once; a refusal puts the payload's back.
|
||||
void set_config(const std::string &fields, const std::function<void(Config &)> &apply);
|
||||
|
||||
const Update &update() const { return update_; }
|
||||
void refresh_update();
|
||||
void check_update();
|
||||
void install_update();
|
||||
|
||||
// Pairing: asks RomM for a code to approve (status().pairing follows it).
|
||||
void pair_start(const std::string &server_url, bool tls_verify);
|
||||
void pair_forget();
|
||||
// Background syncs wait until the setup is finished again.
|
||||
void setup_restart();
|
||||
// Asks for the status now rather than at the next tick.
|
||||
void refresh_now() { refresh_status(); }
|
||||
|
||||
// Any other call to the payload, for the setup's steps.
|
||||
void get(const std::string &path, Api::Callback done) { api_.get(path, std::move(done)); }
|
||||
void post(const std::string &path, const std::string &body, Api::Callback done)
|
||||
{
|
||||
api_.post(path, body, std::move(done));
|
||||
}
|
||||
void say(const std::string &message);
|
||||
|
||||
// A message for the screen to show for a few seconds ("" for none).
|
||||
const std::string &toast() const { return toast_; }
|
||||
float toast_age() const { return toast_age_; }
|
||||
|
||||
private:
|
||||
void refresh_status();
|
||||
// Whether a RomM Sync is running, from the lock it holds while it runs:
|
||||
// 1 yes, 0 no, -1 can't tell.
|
||||
static int payload_lock();
|
||||
// Brings the copies the console starts RomM Sync from up to the one
|
||||
// RommPS carries, leaving autostart on or off as it is; turns it on once
|
||||
// for anyone who never chose either way (1.1.0's setup left it off).
|
||||
void repair_autostart();
|
||||
void evict_covers(hui::gfx::Renderer *renderer);
|
||||
void send_page(const std::string &key, int page);
|
||||
void refresh_platforms();
|
||||
void refresh_downloads();
|
||||
|
||||
Api api_;
|
||||
// Covers on workers of their own, so lists never wait behind them; asked
|
||||
// for only while they're on screen (wanted_), newest first.
|
||||
static constexpr int kCoverWorkers = 3;
|
||||
static constexpr int kCoversInFlight = 6;
|
||||
Api covers_api_[kCoverWorkers]; // each to the payload on 127.0.0.1
|
||||
std::vector<std::string> wanted_; // drawn since the last tick, in draw order
|
||||
// Pages of games, on workers of their own too; asked for at the tick if
|
||||
// the screen still wants them.
|
||||
static constexpr int kPageWorkers = 2;
|
||||
Api pages_api_[kPageWorkers];
|
||||
std::vector<std::pair<std::string, int>> wanted_pages_; // since the last tick
|
||||
int pages_in_flight_ = 0, next_page_worker_ = 0;
|
||||
int covers_in_flight_ = 0, next_cover_worker_ = 0;
|
||||
Status status_;
|
||||
std::vector<Platform> platforms_;
|
||||
std::vector<Download> downloads_;
|
||||
std::vector<Game> installed_;
|
||||
std::unordered_map<int, Details> details_;
|
||||
struct CachedList
|
||||
{
|
||||
std::unique_ptr<GameList> list;
|
||||
double used = 0;
|
||||
};
|
||||
std::unordered_map<std::string, CachedList> lists_;
|
||||
double clock_ = 0;
|
||||
Config config_;
|
||||
Update update_;
|
||||
float update_timer_ = 0;
|
||||
bool update_busy_ = false;
|
||||
std::unordered_map<std::string, Cover> covers_;
|
||||
struct Pending
|
||||
{
|
||||
std::string path, bytes;
|
||||
};
|
||||
std::deque<Pending> to_decode_;
|
||||
float status_timer_ = 0, downloads_timer_ = 0;
|
||||
bool status_busy_ = false, downloads_busy_ = false, platforms_busy_ = false;
|
||||
bool was_paired_ = false;
|
||||
const std::string &bundled_payload();
|
||||
std::string bundled_;
|
||||
bool bundled_read_ = false;
|
||||
std::atomic<int> start_state_{0}; // 0 idle, 1 sending, 2 sent, 3 failed
|
||||
std::string start_error_; // written before start_state_ becomes 3
|
||||
bool tried_start_ = false, updated_payload_ = false, repaired_ = false;
|
||||
bool payload_waiting_ = false;
|
||||
int unanswered_ = 0; // status requests in a row RomM Sync didn't answer
|
||||
int active_downloads_ = 0;
|
||||
std::string toast_;
|
||||
float toast_age_ = 0;
|
||||
};
|
||||
|
||||
// The program's one App, created on first use.
|
||||
App &app();
|
||||
|
||||
} // namespace rommps
|
||||
|
||||
// The program hands the kit's renderer over (rommps.cpp is compiled in a
|
||||
// namespace of its own, so this is plain C).
|
||||
extern "C" void rommps_set_renderer(void *renderer);
|
||||
extern "C" void *rommps_renderer();
|
||||
@@ -0,0 +1,171 @@
|
||||
// RommPS: the screen's look. See rommps_look.hpp.
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#include "rommps_look.hpp"
|
||||
|
||||
#include "core/save_file.hpp"
|
||||
#include "ui/components/component.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include <vector>
|
||||
|
||||
// Set for a whole test run (ps5/src/main.cpp): its pictures never depend on
|
||||
// what was picked by hand.
|
||||
extern bool ps5TestRun;
|
||||
|
||||
#ifndef PS5_APP_ROOT
|
||||
#define PS5_APP_ROOT "/app0"
|
||||
#endif
|
||||
|
||||
namespace rommps
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
const char *const kFolder = PS5_APP_ROOT "/hui";
|
||||
const char *const kPath = PS5_APP_ROOT "/hui/rommps-look.txt";
|
||||
|
||||
const hui::ui::Theme *theme_named(const char *id)
|
||||
{
|
||||
for (const hui::ui::Theme &t : hui::ui::themes())
|
||||
if (std::string(t.id) == id)
|
||||
return &t;
|
||||
return &hui::ui::default_theme();
|
||||
}
|
||||
|
||||
Color or_else(Color a, Color b)
|
||||
{
|
||||
return a.a > 0.0f ? a : b;
|
||||
}
|
||||
|
||||
// The design RommPS was drawn for: white on the cover's aurora, frosted panels.
|
||||
Look cover_colours()
|
||||
{
|
||||
Look l;
|
||||
l.id = "covers";
|
||||
l.name = "Cover colours";
|
||||
l.family = "Follows the game you're looking at";
|
||||
l.covers = true;
|
||||
l.theme = theme_named("acrylic");
|
||||
l.backdrop.mode = hui::gfx::BackdropMode::aurora;
|
||||
l.page = Color::rgb(0x05060c);
|
||||
l.text = Color::rgb(0xffffff);
|
||||
l.muted = Color::rgb(0xffffff, 0.72f);
|
||||
l.panel = Color::rgb(0x0b1026, 0.5f);
|
||||
l.panel_text = l.text;
|
||||
l.panel_muted = l.muted;
|
||||
l.outline = Color::rgb(0xffffff, 0.2f);
|
||||
l.accent = Color::rgb(0x76d6ff);
|
||||
l.button = Color::rgb(0xffffff);
|
||||
l.on_button = Color::rgb(0x0b0d16);
|
||||
l.focus = Color::rgb(0xffffff);
|
||||
l.highlight = Color::rgb(0xffffff);
|
||||
l.on_highlight = Color::rgb(0x0b0d16);
|
||||
l.danger = Color::rgb(0xf87171);
|
||||
l.success = Color::rgb(0x4ade80);
|
||||
l.warning = Color::rgb(0xfbbf24);
|
||||
l.radius = 28.0f;
|
||||
l.dark = true;
|
||||
return l;
|
||||
}
|
||||
|
||||
Look from_theme(const hui::ui::Theme &t)
|
||||
{
|
||||
Look l;
|
||||
l.id = t.id;
|
||||
l.name = t.name;
|
||||
l.family = t.family;
|
||||
l.theme = &t;
|
||||
l.backdrop = t.backdrop;
|
||||
l.page = t.page;
|
||||
l.text = or_else(t.page_text, t.text);
|
||||
l.muted = or_else(t.page_text_muted, t.text_muted);
|
||||
l.panel = t.surface;
|
||||
l.panel_text = t.text;
|
||||
l.panel_muted = t.text_muted;
|
||||
l.outline = t.outline;
|
||||
l.accent = t.accent;
|
||||
l.button = t.primary;
|
||||
l.on_button = t.on_primary;
|
||||
l.focus = t.focus;
|
||||
l.highlight = t.primary;
|
||||
l.on_highlight = t.on_primary;
|
||||
l.danger = t.danger;
|
||||
l.success = t.success;
|
||||
l.warning = t.warning;
|
||||
l.radius = t.radius_card;
|
||||
l.dark = t.dark;
|
||||
return l;
|
||||
}
|
||||
|
||||
const std::vector<Look> &all()
|
||||
{
|
||||
static const std::vector<Look> looks = [] {
|
||||
std::vector<Look> v{cover_colours()};
|
||||
for (const hui::ui::Theme &t : hui::ui::themes())
|
||||
v.push_back(from_theme(t));
|
||||
return v;
|
||||
}();
|
||||
return looks;
|
||||
}
|
||||
|
||||
int g_current = -1; // -1: not read yet
|
||||
|
||||
} // namespace
|
||||
|
||||
int look_count()
|
||||
{
|
||||
return static_cast<int>(all().size());
|
||||
}
|
||||
|
||||
const Look &look(int index)
|
||||
{
|
||||
const std::vector<Look> &v = all();
|
||||
return v[index >= 0 && index < static_cast<int>(v.size()) ? static_cast<std::size_t>(index) : 0];
|
||||
}
|
||||
|
||||
int current_look()
|
||||
{
|
||||
if (ps5TestRun && g_current < 0)
|
||||
g_current = 0; // a test run starts from the default, and may change it
|
||||
if (g_current < 0)
|
||||
{
|
||||
g_current = 0;
|
||||
std::string id;
|
||||
if (hui::save::read_file(kPath, &id, 64))
|
||||
{
|
||||
while (!id.empty() && (id.back() == '\n' || id.back() == '\r' || id.back() == ' '))
|
||||
id.pop_back();
|
||||
for (int i = 0; i < look_count(); ++i)
|
||||
if (id == look(i).id)
|
||||
g_current = i;
|
||||
}
|
||||
}
|
||||
return g_current;
|
||||
}
|
||||
|
||||
void set_look(int index)
|
||||
{
|
||||
if (index < 0 || index >= look_count())
|
||||
return;
|
||||
g_current = index;
|
||||
if (ps5TestRun)
|
||||
return;
|
||||
hui::save::ensure_directory(kFolder);
|
||||
chmod(kFolder, 0777);
|
||||
if (hui::save::write_atomic(kPath, std::string(look(index).id) + "\n").empty())
|
||||
chmod(kPath, 0666);
|
||||
}
|
||||
|
||||
void draw_panel(hui::gfx::DrawList &list, const Look &look, const hui::gfx::Rect &r, float radius)
|
||||
{
|
||||
if (radius < 0.0f)
|
||||
radius = look.radius;
|
||||
list.shadow({r.x, r.y + 16, r.w, r.h}, radius, 34, Color::rgb(0x000000, look.dark ? 0.4f : 0.12f));
|
||||
list.rounded_rect(r, radius, look.panel);
|
||||
list.bordered_rect(r, radius, Color::rgb(0x000000, 0.0f), 1.5f, look.outline);
|
||||
}
|
||||
|
||||
} // namespace rommps
|
||||
@@ -0,0 +1,60 @@
|
||||
// RommPS: the screen's look. "Cover colours" (the backdrop and accents follow
|
||||
// the focused game's cover), or one of the UI kit's themes. Every colour the
|
||||
// screen draws with comes from here, so a light theme reads as well as a dark
|
||||
// one. The choice is kept in the title's folder (/app0/hui/rommps-look.txt).
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "gfx/backdrop_spec.hpp"
|
||||
#include "gfx/draw_list.hpp"
|
||||
#include "ui/glyphs.hpp"
|
||||
#include "ui/theme.hpp"
|
||||
|
||||
namespace rommps
|
||||
{
|
||||
|
||||
using hui::gfx::Color;
|
||||
|
||||
struct Look
|
||||
{
|
||||
const char *id = "";
|
||||
const char *name = "";
|
||||
const char *family = "";
|
||||
bool covers = false; // the backdrop and accent follow the focused cover
|
||||
const hui::ui::Theme *theme; // what the kit's components (the keyboard) are drawn in
|
||||
hui::gfx::BackdropSpec backdrop;
|
||||
Color page; // the page's own colour, for fades over it
|
||||
Color text; // text straight on the page
|
||||
Color muted; // secondary text on the page
|
||||
Color panel; // panels and cards
|
||||
Color panel_text; // text on a panel
|
||||
Color panel_muted;
|
||||
Color outline; // panel edges
|
||||
Color accent; // highlights, selections (Cover colours: the cover's)
|
||||
Color button; // the main action's body
|
||||
Color on_button; // ... and its text
|
||||
Color focus; // the focus ring
|
||||
Color highlight; // a focused row's plate
|
||||
Color on_highlight;
|
||||
Color danger, success, warning;
|
||||
float radius = 28.0f; // panels
|
||||
bool dark = true;
|
||||
|
||||
hui::ui::GlyphStyle glyphs() const
|
||||
{
|
||||
return dark ? hui::ui::GlyphStyle::dark() : hui::ui::GlyphStyle::light();
|
||||
}
|
||||
};
|
||||
|
||||
int look_count();
|
||||
const Look &look(int index);
|
||||
// The look in use: the saved one, Cover colours until one is picked.
|
||||
int current_look();
|
||||
// Uses the look from now on and saves the choice (silently, a test run excepted).
|
||||
void set_look(int index);
|
||||
|
||||
// A panel in the look: its shadow, body and edge.
|
||||
void draw_panel(hui::gfx::DrawList &list, const Look &look, const hui::gfx::Rect &r, float radius = -1.0f);
|
||||
|
||||
} // namespace rommps
|
||||
@@ -0,0 +1,209 @@
|
||||
// RommPS: a list of setting rows. See rows.hpp.
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#include "rows.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
namespace rommps
|
||||
{
|
||||
|
||||
using hui::Action;
|
||||
using hui::Direction;
|
||||
using hui::audio::Cue;
|
||||
using hui::gfx::Rect;
|
||||
|
||||
void RowList::set_focus(int row, bool snap)
|
||||
{
|
||||
focus_ = std::max(0, row);
|
||||
if (snap)
|
||||
position_.snap(static_cast<float>(focus_));
|
||||
}
|
||||
|
||||
void RowList::refuse(hui::ui::Feedback &feedback)
|
||||
{
|
||||
feedback.play(Cue::error, 1.0f, 0.0f, 0.6f);
|
||||
feedback.rumble(0.25f, 0.05f);
|
||||
nudge_.trigger();
|
||||
}
|
||||
|
||||
RowEvent RowList::handle(const std::vector<Row> &rows, const hui::InputFrame &input, hui::ui::Feedback &feedback)
|
||||
{
|
||||
RowEvent event;
|
||||
const int count = static_cast<int>(rows.size());
|
||||
if (count == 0)
|
||||
return event;
|
||||
focus_ = std::clamp(focus_, 0, count - 1);
|
||||
const Row &row = rows[static_cast<std::size_t>(focus_)];
|
||||
switch (input.nav)
|
||||
{
|
||||
case Direction::up:
|
||||
case Direction::down:
|
||||
{
|
||||
const int next = focus_ + (input.nav == Direction::down ? 1 : -1);
|
||||
if (next >= 0 && next < count)
|
||||
{
|
||||
focus_ = next;
|
||||
feedback.play(Cue::focus);
|
||||
}
|
||||
else if (!input.nav_repeat)
|
||||
refuse(feedback);
|
||||
break;
|
||||
}
|
||||
case Direction::left:
|
||||
case Direction::right:
|
||||
if (row.kind == RowKind::choice && row.enabled)
|
||||
{
|
||||
event.row = focus_;
|
||||
event.step = input.nav == Direction::right ? 1 : -1;
|
||||
feedback.play(Cue::slider, input.nav == Direction::right ? 1.06f : 0.94f);
|
||||
}
|
||||
else if (!input.nav_repeat)
|
||||
refuse(feedback);
|
||||
break;
|
||||
case Direction::none:
|
||||
break;
|
||||
}
|
||||
if (input.is_pressed(Action::confirm))
|
||||
{
|
||||
if (!row.enabled || row.kind == RowKind::info)
|
||||
refuse(feedback);
|
||||
else if (row.kind == RowKind::choice)
|
||||
{
|
||||
event.row = focus_;
|
||||
event.step = 1;
|
||||
feedback.play(Cue::slider, 1.06f);
|
||||
}
|
||||
else
|
||||
{
|
||||
event.row = focus_;
|
||||
event.activated = true;
|
||||
feedback.play(row.kind == RowKind::toggle ? Cue::toggle : Cue::select);
|
||||
}
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
void RowList::update(float dt, int count, float visible_height)
|
||||
{
|
||||
focus_ = std::clamp(focus_, 0, std::max(0, count - 1));
|
||||
position_.target = static_cast<float>(focus_);
|
||||
position_.update(dt, 22.0f);
|
||||
// Keep the focused row a row away from the edges.
|
||||
const float top = static_cast<float>(focus_ - 1) * kRowHeight;
|
||||
const float bottom = static_cast<float>(focus_ + 2) * kRowHeight;
|
||||
const float most = std::max(0.0f, static_cast<float>(count) * kRowHeight - visible_height);
|
||||
if (top < scroll_.target)
|
||||
scroll_.target = top;
|
||||
if (bottom > scroll_.target + visible_height)
|
||||
scroll_.target = bottom - visible_height;
|
||||
scroll_.target = std::clamp(scroll_.target, 0.0f, most);
|
||||
scroll_.update(dt, 14.0f);
|
||||
nudge_.update(dt, 9.0f);
|
||||
}
|
||||
|
||||
void RowList::draw(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, const std::vector<Row> &rows,
|
||||
const Rect &bounds, float clock) const
|
||||
{
|
||||
namespace ui = hui::ui;
|
||||
list.push_clip({bounds.x - 40, bounds.y, bounds.w + 80, bounds.h});
|
||||
const float shake = ui::shake(nudge_.value, clock, 10.0f, 9.0f);
|
||||
const float plate_y = bounds.y + position_.value * kRowHeight - scroll_.value;
|
||||
if (!rows.empty())
|
||||
list.rounded_rect({bounds.x + shake, plate_y + 6, bounds.w, kRowHeight - 12}, 22, look.highlight);
|
||||
for (int i = 0; i < static_cast<int>(rows.size()); ++i)
|
||||
{
|
||||
const Row &row = rows[static_cast<std::size_t>(i)];
|
||||
const float y = bounds.y + static_cast<float>(i) * kRowHeight - scroll_.value;
|
||||
if (y + kRowHeight < bounds.y || y > bounds.y + bounds.h)
|
||||
continue;
|
||||
const bool focused = i == focus_;
|
||||
const float dim = row.enabled ? 1.0f : 0.45f;
|
||||
const Color ink = focused ? look.on_highlight : row.danger ? look.danger : look.text;
|
||||
const Color soft = focused ? look.on_highlight.with_alpha(0.7f) : look.muted;
|
||||
const float x = bounds.x + 32 + (focused ? shake : 0.0f);
|
||||
const float right = bounds.x + bounds.w - 32 + (focused ? shake : 0.0f);
|
||||
const bool two_lines = !row.detail.empty();
|
||||
const float label_y = y + (two_lines ? 42.0f : 56.0f);
|
||||
// The right-hand part first, so the label knows how much room it has.
|
||||
float used = 0.0f;
|
||||
if (row.kind == RowKind::toggle)
|
||||
{
|
||||
const Rect track{right - 68, y + kRowHeight * 0.5f - 18, 68, 36};
|
||||
list.rounded_rect(track, 18, row.on ? (focused ? look.on_highlight : look.accent) : soft.with_alpha(0.3f * dim));
|
||||
const float knob = row.on ? track.x + track.w - 18 : track.x + 18;
|
||||
list.circle(knob, track.cy(), 13, row.on ? (focused ? look.highlight : look.page) : ink.with_alpha(dim));
|
||||
used = 90.0f;
|
||||
}
|
||||
else if (!row.value.empty())
|
||||
{
|
||||
const float size = 24.0f;
|
||||
const float chevrons = row.kind == RowKind::choice ? 30.0f : 0.0f;
|
||||
const std::string value = fonts.semibold.font->fit(row.value, size, bounds.w * 0.45f);
|
||||
const float w = ui::text(list, fonts.semibold, value, right - chevrons, y + kRowHeight * 0.5f + 9, size,
|
||||
(row.kind == RowKind::info ? soft : ink).with_alpha(dim), hui::gfx::Align::right);
|
||||
if (row.kind == RowKind::choice && row.enabled)
|
||||
{
|
||||
const float cy = y + kRowHeight * 0.5f;
|
||||
const float lx = right - chevrons - w - 22, rx = right - 8;
|
||||
const Color c = focused ? ink : soft;
|
||||
list.line(lx + 6, cy - 9, lx, cy, 3.0f, c);
|
||||
list.line(lx, cy, lx + 6, cy + 9, 3.0f, c);
|
||||
list.line(rx - 6, cy - 9, rx, cy, 3.0f, c);
|
||||
list.line(rx, cy, rx - 6, cy + 9, 3.0f, c);
|
||||
}
|
||||
used = w + chevrons + (row.kind == RowKind::choice ? 50.0f : 24.0f);
|
||||
}
|
||||
const float room = std::max(200.0f, bounds.w - 64 - used);
|
||||
ui::text(list, fonts.semibold, fonts.semibold.font->fit(row.label, 26, room), x, label_y, 26, ink.with_alpha(dim));
|
||||
if (two_lines)
|
||||
ui::text(list, fonts.regular, fonts.regular.font->fit(row.detail, 19, room), x, label_y + 30, 19,
|
||||
soft.with_alpha(dim));
|
||||
}
|
||||
list.pop_clip();
|
||||
}
|
||||
|
||||
const char *label_of(std::span<const Named> options, const std::string &id)
|
||||
{
|
||||
for (const Named &o : options)
|
||||
if (id == o.id)
|
||||
return o.label;
|
||||
return options.empty() ? "" : options.front().label;
|
||||
}
|
||||
|
||||
const char *step_option(std::span<const Named> options, const std::string &id, int step)
|
||||
{
|
||||
const int n = static_cast<int>(options.size());
|
||||
int at = 0;
|
||||
for (int i = 0; i < n; ++i)
|
||||
if (id == options[static_cast<std::size_t>(i)].id)
|
||||
at = i;
|
||||
return options[static_cast<std::size_t>(((at + step) % n + n) % n)].id;
|
||||
}
|
||||
|
||||
std::string interval_label(int minutes)
|
||||
{
|
||||
char text[32];
|
||||
if (minutes <= 0)
|
||||
return "Off";
|
||||
if (minutes < 60)
|
||||
std::snprintf(text, sizeof text, "%d min", minutes);
|
||||
else if (minutes % 60 == 0)
|
||||
std::snprintf(text, sizeof text, "%d h", minutes / 60);
|
||||
else
|
||||
std::snprintf(text, sizeof text, "%d h %d min", minutes / 60, minutes % 60);
|
||||
return text;
|
||||
}
|
||||
|
||||
int step_interval(int minutes, int step)
|
||||
{
|
||||
constexpr int n = static_cast<int>(std::size(kIntervals));
|
||||
int at = 0; // the nearest option at or below the current value
|
||||
for (int i = 0; i < n; ++i)
|
||||
if (kIntervals[i] <= minutes)
|
||||
at = i;
|
||||
return kIntervals[((at + step) % n + n) % n];
|
||||
}
|
||||
|
||||
} // namespace rommps
|
||||
@@ -0,0 +1,85 @@
|
||||
// RommPS: a list of setting rows for the pad: switches, choices and actions.
|
||||
// Settings and the setup's steps build their rows every frame from what the
|
||||
// payload says, and react to what handle() reports.
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "core/input.hpp"
|
||||
#include "core/tween.hpp"
|
||||
#include "rommps_look.hpp"
|
||||
#include "ui/feedback.hpp"
|
||||
#include "ui/fonts.hpp"
|
||||
#include "ui/motion.hpp"
|
||||
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace rommps
|
||||
{
|
||||
|
||||
enum class RowKind
|
||||
{
|
||||
toggle, // Cross switches it
|
||||
choice, // left and right step through its options
|
||||
action, // Cross does it
|
||||
info, // only shows something
|
||||
};
|
||||
|
||||
struct Row
|
||||
{
|
||||
RowKind kind = RowKind::action;
|
||||
std::string label;
|
||||
std::string value; // a choice's option, an action's or info's right-hand text
|
||||
std::string detail; // a second, smaller line
|
||||
bool on = false; // a toggle's state
|
||||
bool enabled = true;
|
||||
bool danger = false; // an action that can't be undone
|
||||
};
|
||||
|
||||
struct RowEvent
|
||||
{
|
||||
int row = -1;
|
||||
int step = 0; // a choice: -1 or +1
|
||||
bool activated = false; // a toggle or action: Cross
|
||||
};
|
||||
|
||||
class RowList
|
||||
{
|
||||
public:
|
||||
static constexpr float kRowHeight = 92.0f;
|
||||
|
||||
RowEvent handle(const std::vector<Row> &rows, const hui::InputFrame &input, hui::ui::Feedback &feedback);
|
||||
void update(float dt, int count, float visible_height);
|
||||
void draw(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, const std::vector<Row> &rows,
|
||||
const hui::gfx::Rect &bounds, float clock) const;
|
||||
|
||||
int focus() const { return focus_; }
|
||||
void set_focus(int row, bool snap = true);
|
||||
void refuse(hui::ui::Feedback &feedback);
|
||||
|
||||
private:
|
||||
int focus_ = 0;
|
||||
hui::tween::Spring position_, scroll_;
|
||||
hui::ui::Pulse nudge_;
|
||||
};
|
||||
|
||||
// The options the payload's settings take, as Settings and the setup show them.
|
||||
struct Named
|
||||
{
|
||||
const char *id;
|
||||
const char *label;
|
||||
};
|
||||
inline constexpr Named kPolicies[] = {
|
||||
{"ask", "Ask me"}, {"newest", "Keep the newest"}, {"local", "Keep this console's"}, {"server", "Keep RomM's"}};
|
||||
inline constexpr Named kStates[] = {{"off", "Off"}, {"upload", "Upload only"}, {"sync", "Upload and download"}};
|
||||
inline constexpr int kIntervals[] = {0, 15, 30, 60, 120, 360};
|
||||
|
||||
const char *label_of(std::span<const Named> options, const std::string &id);
|
||||
// The option before (-1) or after (+1) id, wrapping round.
|
||||
const char *step_option(std::span<const Named> options, const std::string &id, int step);
|
||||
std::string interval_label(int minutes);
|
||||
int step_interval(int minutes, int step);
|
||||
|
||||
} // namespace rommps
|
||||
@@ -0,0 +1,939 @@
|
||||
// RommPS - the screen: Home, Library, Downloads and Settings for the RomM Sync payload.
|
||||
// Copyright (C) 2026 BlackBearReloaded (the Aurora Shelf design it grew from)
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// Grown from PS5_VKHomebrewUI's "Aurora Shelf" (src/concepts/aurora.cpp),
|
||||
// copied in by PS5_VulkanTemplate's new-title.py. What the screen shows comes
|
||||
// from the payload through rommps_app.hpp; the screen only draws it and sends
|
||||
// what the pad asks for.
|
||||
//
|
||||
// - L1 / R1 move between Home, Library and Downloads.
|
||||
// - Home: the sync at a glance; Cross syncs now.
|
||||
// - Library (library_page.hpp): the games on this console and RomM's
|
||||
// platforms; a platform's games in a grid with an A to Z bar; search. The
|
||||
// backdrop eases toward the focused cover's colours; Cross opens a frosted
|
||||
// sheet to download the game.
|
||||
// - Downloads: what's queued and running; Cross cancels one.
|
||||
// - Settings: the look, the payload's settings, its updates, the setup.
|
||||
//
|
||||
// Until the payload answers the screen says so; until it's paired with RomM
|
||||
// and set up, the setup (setup_page.hpp) fills it. Every colour comes from
|
||||
// the look picked in Settings (rommps_look.hpp): the cover's colours, or one
|
||||
// of the kit's themes.
|
||||
|
||||
#include "concepts/concepts.hpp"
|
||||
|
||||
#include "core/tween.hpp"
|
||||
#include "rommps_app.hpp"
|
||||
#include "rommps_look.hpp"
|
||||
#include "library_page.hpp"
|
||||
#include "settings_page.hpp"
|
||||
#include "setup_page.hpp"
|
||||
#include "ui/glyphs.hpp"
|
||||
#include "ui/motion.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace hui::screen
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using gfx::Color;
|
||||
using gfx::Rect;
|
||||
using rommps::App;
|
||||
|
||||
const Color kWhite = Color::rgb(0xffffff);
|
||||
const Color kInk = Color::rgb(0x0b0d16);
|
||||
|
||||
constexpr float kMargin = 96.0f;
|
||||
|
||||
enum Tab
|
||||
{
|
||||
kHome,
|
||||
kLibrary,
|
||||
kDownloads,
|
||||
kSettings,
|
||||
kTabs
|
||||
};
|
||||
constexpr const char *kTabNames[kTabs] = {"Home", "Library", "Downloads", "Settings"};
|
||||
|
||||
constexpr const char *kTechniques[] = {
|
||||
"The RomM Sync payload's state, read through its local API on a worker thread",
|
||||
"Covers fetched once, decoded a few per frame, their colours driving the backdrop",
|
||||
"Spring-driven shelves, card growth and a gliding focus ring (from Aurora Shelf)",
|
||||
"Frosted details sheet over the blurred screen",
|
||||
"The setup and settings on the pad, with the kit's keyboard and its thirty themes",
|
||||
};
|
||||
|
||||
std::string ago(double seconds)
|
||||
{
|
||||
char text[32];
|
||||
if (seconds < 60)
|
||||
return "just now";
|
||||
if (seconds < 3600)
|
||||
std::snprintf(text, sizeof text, "%d min ago", static_cast<int>(seconds / 60));
|
||||
else if (seconds < 86400)
|
||||
std::snprintf(text, sizeof text, "%d h ago", static_cast<int>(seconds / 3600));
|
||||
else
|
||||
std::snprintf(text, sizeof text, seconds < 2 * 86400 ? "%d day ago" : "%d days ago",
|
||||
static_cast<int>(seconds / 86400));
|
||||
return text;
|
||||
}
|
||||
|
||||
class RommPS final : public app::Concept
|
||||
{
|
||||
public:
|
||||
explicit RommPS(app::Context &context) : context_(context), app_(rommps::app())
|
||||
{
|
||||
apply_palette(rommps::Palette{}, true);
|
||||
}
|
||||
|
||||
const app::ConceptInfo &info() const override
|
||||
{
|
||||
static const app::ConceptInfo kInfo{
|
||||
"rommps",
|
||||
"RommPS",
|
||||
"Your RomM library and save sync",
|
||||
"examples/rommps/kit/screen.cpp",
|
||||
audio::SoundSet::glass,
|
||||
Color::rgb(0x76d6ff),
|
||||
kTechniques,
|
||||
};
|
||||
return kInfo;
|
||||
}
|
||||
|
||||
void enter() override
|
||||
{
|
||||
age_ = 0.0f;
|
||||
sheet_open_ = false;
|
||||
}
|
||||
|
||||
void update(const InputFrame &input, float dt, app::Feedback &feedback) override
|
||||
{
|
||||
age_ += dt;
|
||||
clock_ += dt;
|
||||
app_.tick(dt, static_cast<gfx::Renderer *>(rommps_renderer()));
|
||||
cancel_seen_ = false;
|
||||
const bool ready = app_.status().paired && app_.status().setup_complete;
|
||||
|
||||
// The Codec call (Settings, behind the author's portrait) takes the pad.
|
||||
const bool calling = settings_.call().active();
|
||||
if (calling)
|
||||
settings_.call().update(input, dt, feedback);
|
||||
if (ready && !sheet_open_ && !calling && !setup_.editing())
|
||||
{
|
||||
if (input.is_pressed(Action::page_prev) || input.is_pressed(Action::page_next))
|
||||
{
|
||||
tab_ = (tab_ + (input.is_pressed(Action::page_next) ? 1 : kTabs - 1)) % kTabs;
|
||||
tab_age_ = 0.0f;
|
||||
feedback.play(audio::Cue::tab);
|
||||
if (tab_ == kSettings)
|
||||
settings_.enter(app_);
|
||||
if (tab_ == kLibrary)
|
||||
{
|
||||
library_.enter(app_);
|
||||
app_.reload_platforms(); // emulators may have changed meanwhile
|
||||
}
|
||||
}
|
||||
}
|
||||
// Settings' Emulators opens that setup step on its own.
|
||||
if (settings_.take_emulators_request())
|
||||
setup_.edit_emulators();
|
||||
const bool setting_up = (!ready && app_.status().known && app_.status().reachable) || setup_.editing();
|
||||
if (setting_up && !setting_up_ && !setup_.editing())
|
||||
setup_.enter();
|
||||
setting_up_ = setting_up;
|
||||
if (calling)
|
||||
;
|
||||
else if (setting_up)
|
||||
setup_.update(app_, input, dt, feedback);
|
||||
else if (!ready)
|
||||
{
|
||||
// RomM Sync not running and RommPS couldn't start it: Cross tries again.
|
||||
if (app_.status().known && !app_.status().reachable && !app_.payload_starting() &&
|
||||
input.is_pressed(Action::confirm))
|
||||
{
|
||||
app_.start_payload();
|
||||
feedback.play(audio::Cue::focus);
|
||||
}
|
||||
}
|
||||
else if (tab_ == kHome)
|
||||
update_home(input, feedback);
|
||||
else if (tab_ == kLibrary)
|
||||
{
|
||||
if (sheet_open_)
|
||||
update_sheet(input, dt, feedback);
|
||||
else if (library_.update(app_, input, dt, feedback))
|
||||
open_sheet();
|
||||
}
|
||||
else if (tab_ == kDownloads)
|
||||
update_downloads(input, dt, feedback);
|
||||
else
|
||||
settings_.update(app_, input, dt, feedback);
|
||||
|
||||
// ---- animation state ----
|
||||
tab_age_ += dt;
|
||||
tab_position_.target = static_cast<float>(tab_);
|
||||
tab_position_.update(dt, 16.0f);
|
||||
// The backdrop eases toward the focused game's cover (or platform's).
|
||||
if (tab_ == kLibrary)
|
||||
{
|
||||
const std::string cover = sheet_open_ ? sheet_game_.cover : library_.backdrop_cover(app_);
|
||||
if (!cover.empty())
|
||||
apply_palette(app_.cover(cover).palette, false);
|
||||
}
|
||||
for (ui::SpringColor &colour : palette_)
|
||||
colour.update(dt, 4.0f);
|
||||
nudge_.update(dt, 9.0f);
|
||||
sheet_.target = sheet_open_ ? 1.0f : 0.0f;
|
||||
sheet_.update(dt, context_.settings.reduced_motion ? 40.0f : 13.0f);
|
||||
action_position_.target = static_cast<float>(action_);
|
||||
action_position_.update(dt, 22.0f);
|
||||
download_row_ = std::clamp(download_row_, 0, std::max(0, static_cast<int>(app_.downloads().size()) - 1));
|
||||
if (!cancel_seen_) // focus left the download (another row, tab or button): the hold is gone
|
||||
{
|
||||
cancel_hold_ = 0;
|
||||
cancel_job_ = 0;
|
||||
}
|
||||
download_position_.target = static_cast<float>(download_row_);
|
||||
download_position_.update(dt, 20.0f);
|
||||
}
|
||||
|
||||
void draw(app::Frame &frame) const override
|
||||
{
|
||||
const rommps::Look &L = look();
|
||||
if (L.covers)
|
||||
{
|
||||
frame.backdrop.mode = gfx::BackdropMode::aurora;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
frame.backdrop.colors[i] = palette_[i].value();
|
||||
}
|
||||
else
|
||||
frame.backdrop = L.backdrop;
|
||||
frame.backdrop.time = clock_;
|
||||
|
||||
gfx::DrawList &list = frame.scene;
|
||||
const float back = sheet_.value;
|
||||
list.push_transform(1.0f - 0.035f * back, 960, 540, 0, 0);
|
||||
draw_top_bar(list);
|
||||
const bool ready = app_.status().paired && app_.status().setup_complete;
|
||||
if (setting_up_)
|
||||
setup_.draw(list, context_.fonts, L, app_, clock_);
|
||||
else if (!ready)
|
||||
draw_not_ready(list);
|
||||
else if (tab_ == kHome)
|
||||
draw_home(list);
|
||||
else if (tab_ == kLibrary)
|
||||
draw_library(list);
|
||||
else if (tab_ == kDownloads)
|
||||
draw_downloads(list);
|
||||
else
|
||||
settings_.draw(list, context_.fonts, L, app_, clock_, tab_age_);
|
||||
list.pop_transform();
|
||||
if (back > 0.01f)
|
||||
{
|
||||
list.rounded_rect({0, 0, gfx::kVirtualWidth, gfx::kVirtualHeight}, 0, L.page.with_alpha(0.45f * back));
|
||||
frame.glass = true;
|
||||
draw_sheet(frame.overlay, frame.glass_texture);
|
||||
}
|
||||
if (settings_.call().active())
|
||||
{
|
||||
settings_.call().draw(frame.overlay, context_.fonts, clock_);
|
||||
return;
|
||||
}
|
||||
draw_toast(back > 0.5f ? frame.overlay : frame.scene);
|
||||
draw_hints(back > 0.5f ? frame.overlay : frame.scene);
|
||||
}
|
||||
|
||||
private:
|
||||
// ---------------------------------------------------------------- look
|
||||
|
||||
static const rommps::Look &look()
|
||||
{
|
||||
return rommps::look(rommps::current_look());
|
||||
}
|
||||
|
||||
// Highlights: the focused cover's colour, or the theme's accent.
|
||||
Color accent() const
|
||||
{
|
||||
return look().covers ? palette_[3].value() : look().accent;
|
||||
}
|
||||
|
||||
Color cover_accent(const rommps::Palette &palette) const
|
||||
{
|
||||
return look().covers ? palette.accent : look().accent;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- data
|
||||
|
||||
const rommps::Game *focused_game() const
|
||||
{
|
||||
return tab_ == kLibrary && !sheet_open_ ? library_.focused(app_) : nullptr;
|
||||
}
|
||||
|
||||
// The backdrop's four colours, from the focused cover (or the default).
|
||||
void apply_palette(const rommps::Palette &p, bool snap)
|
||||
{
|
||||
const Color targets[4] = {
|
||||
gfx::mix(p.dark, Color::rgb(0x05060c), 0.35f),
|
||||
gfx::mix(p.dark, p.mid, 0.35f),
|
||||
p.mid,
|
||||
gfx::mix(p.mid, p.accent, 0.55f),
|
||||
};
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (snap)
|
||||
palette_[i].snap(targets[i]);
|
||||
else
|
||||
palette_[i].target(targets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- input
|
||||
|
||||
void refuse(app::Feedback &feedback, float direction)
|
||||
{
|
||||
feedback.play(audio::Cue::error, 1.0f, 0.0f, 0.6f);
|
||||
feedback.rumble(0.25f, 0.05f);
|
||||
nudge_direction_ = direction;
|
||||
nudge_.trigger();
|
||||
}
|
||||
|
||||
void update_home(const InputFrame &input, app::Feedback &feedback)
|
||||
{
|
||||
if (input.is_pressed(Action::confirm))
|
||||
{
|
||||
if (app_.status().syncing)
|
||||
{
|
||||
refuse(feedback, 0.0f);
|
||||
return;
|
||||
}
|
||||
app_.sync_now();
|
||||
feedback.play(audio::Cue::launch);
|
||||
feedback.rumble(0.5f, 0.12f);
|
||||
}
|
||||
}
|
||||
|
||||
void open_sheet()
|
||||
{
|
||||
const rommps::Game *g = library_.focused(app_);
|
||||
if (!g)
|
||||
return;
|
||||
sheet_game_ = *g;
|
||||
sheet_open_ = true;
|
||||
action_ = 0;
|
||||
action_position_.snap(0.0f);
|
||||
}
|
||||
|
||||
// The sheet's actions for the focused game, and whether each can be used.
|
||||
struct SheetAction
|
||||
{
|
||||
std::string label;
|
||||
bool enabled;
|
||||
int kind; // 0 download, 1 cancel, 2 close
|
||||
};
|
||||
|
||||
std::vector<SheetAction> sheet_actions() const
|
||||
{
|
||||
std::vector<SheetAction> actions;
|
||||
const rommps::Game *g = &sheet_game_;
|
||||
const rommps::Platform *p = app_.platform_by_id(sheet_game_.platform_id);
|
||||
const rommps::Download *d = g ? app_.download_for(g->id) : nullptr;
|
||||
if (d && d->active())
|
||||
actions.push_back({"Hold to cancel download", true, 1});
|
||||
else if (g && g->installed)
|
||||
actions.push_back({"Downloaded", false, 0});
|
||||
else if (p && p->profile.empty())
|
||||
actions.push_back({"No emulator for this platform", false, 0});
|
||||
else
|
||||
actions.push_back({"Download", true, 0});
|
||||
actions.push_back({"Close", true, 2});
|
||||
return actions;
|
||||
}
|
||||
|
||||
// Cancelling a download deletes what came so far, so it takes holding
|
||||
// Cross for a second, not a press: the row (or the sheet's button) fills
|
||||
// up meanwhile, and letting go before the end keeps the download.
|
||||
static constexpr float kCancelHold = 1.0f;
|
||||
|
||||
bool hold_to_cancel(const InputFrame &input, float dt, const rommps::Download *d, app::Feedback &feedback)
|
||||
{
|
||||
cancel_seen_ = true;
|
||||
if (!d || !d->active() || !input.is_held(Action::confirm))
|
||||
{
|
||||
cancel_hold_ = 0;
|
||||
cancel_job_ = 0;
|
||||
return false;
|
||||
}
|
||||
if (input.is_pressed(Action::confirm))
|
||||
{
|
||||
cancel_job_ = d->id; // every hold starts with a press, from nothing
|
||||
cancel_hold_ = 0;
|
||||
feedback.play(audio::Cue::tick, 1.0f, 0.0f, 0.5f);
|
||||
}
|
||||
else if (cancel_job_ != d->id)
|
||||
return false; // held from before, or from another row
|
||||
if (cancel_hold_ < 0)
|
||||
return false; // done: waits for the button to come up
|
||||
cancel_hold_ += dt;
|
||||
if (cancel_hold_ < kCancelHold)
|
||||
return false;
|
||||
app_.cancel_download(d->id);
|
||||
feedback.play(audio::Cue::back);
|
||||
feedback.rumble(0.8f, 0.2f);
|
||||
cancel_hold_ = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0..1 while Cross is held on this download.
|
||||
float cancel_progress(int job_id) const
|
||||
{
|
||||
return job_id && job_id == cancel_job_ && cancel_hold_ > 0 ? std::min(1.0f, cancel_hold_ / kCancelHold) : 0.0f;
|
||||
}
|
||||
|
||||
void update_sheet(const InputFrame &input, float dt, app::Feedback &feedback)
|
||||
{
|
||||
const std::vector<SheetAction> actions = sheet_actions();
|
||||
const int count = static_cast<int>(actions.size());
|
||||
if (input.nav == Direction::up || input.nav == Direction::down)
|
||||
{
|
||||
const int next = std::clamp(action_ + (input.nav == Direction::down ? 1 : -1), 0, count - 1);
|
||||
if (next != action_)
|
||||
{
|
||||
action_ = next;
|
||||
feedback.play(audio::Cue::focus, 1.0f, 0.35f);
|
||||
}
|
||||
}
|
||||
action_ = std::clamp(action_, 0, count - 1);
|
||||
if (actions[static_cast<std::size_t>(action_)].kind == 1)
|
||||
{
|
||||
if (hold_to_cancel(input, dt, app_.download_for(sheet_game_.id), feedback))
|
||||
sheet_open_ = false;
|
||||
}
|
||||
else if (input.is_pressed(Action::confirm))
|
||||
{
|
||||
const SheetAction &a = actions[static_cast<std::size_t>(action_)];
|
||||
const rommps::Game *g = &sheet_game_;
|
||||
if (!a.enabled)
|
||||
refuse(feedback, 0.0f);
|
||||
else if (a.kind == 0 && g)
|
||||
{
|
||||
app_.download(g->id, g->name);
|
||||
feedback.play(audio::Cue::launch);
|
||||
feedback.rumble(0.7f, 0.18f);
|
||||
sheet_open_ = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
feedback.play(audio::Cue::modal_close);
|
||||
sheet_open_ = false;
|
||||
}
|
||||
}
|
||||
if (input.is_pressed(Action::back))
|
||||
{
|
||||
feedback.play(audio::Cue::back);
|
||||
sheet_open_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void update_downloads(const InputFrame &input, float dt, app::Feedback &feedback)
|
||||
{
|
||||
const int count = static_cast<int>(app_.downloads().size());
|
||||
// The list can get shorter at any tick (finished ones cleared, the payload restarted).
|
||||
download_row_ = std::clamp(download_row_, 0, std::max(0, count - 1));
|
||||
if ((input.nav == Direction::up || input.nav == Direction::down) && count > 0)
|
||||
{
|
||||
const int next = std::clamp(download_row_ + (input.nav == Direction::down ? 1 : -1), 0, count - 1);
|
||||
if (next != download_row_)
|
||||
{
|
||||
download_row_ = next;
|
||||
feedback.play(audio::Cue::focus);
|
||||
}
|
||||
else if (!input.nav_repeat)
|
||||
refuse(feedback, 0.0f);
|
||||
}
|
||||
if (input.is_pressed(Action::west) && count > 0)
|
||||
{
|
||||
const auto &downloads = app_.downloads();
|
||||
if (std::any_of(downloads.begin(), downloads.end(), [](const rommps::Download &d) { return d.finished(); }))
|
||||
{
|
||||
app_.clear_finished_downloads();
|
||||
download_row_ = 0;
|
||||
feedback.play(audio::Cue::back);
|
||||
}
|
||||
else
|
||||
refuse(feedback, 0.0f);
|
||||
}
|
||||
if (count > 0 && download_row_ < static_cast<int>(app_.downloads().size()))
|
||||
{
|
||||
const rommps::Download &d = app_.downloads()[static_cast<std::size_t>(download_row_)];
|
||||
if (d.active())
|
||||
hold_to_cancel(input, dt, &d, feedback);
|
||||
else if (input.is_pressed(Action::confirm))
|
||||
refuse(feedback, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- drawing
|
||||
|
||||
void draw_top_bar(gfx::DrawList &list) const
|
||||
{
|
||||
const ui::Fonts &fonts = context_.fonts;
|
||||
const rommps::Look &L = look();
|
||||
const float in = tween::stagger(age_, 0, 0.05f, 0.5f);
|
||||
list.push_opacity(in);
|
||||
float x = kMargin;
|
||||
const bool ready = app_.status().paired && app_.status().setup_complete;
|
||||
ui::text(list, fonts.display, "RommPS", x, 96 - 10 * (1.0f - in), 34, L.text);
|
||||
x += 220;
|
||||
if (ready)
|
||||
{
|
||||
for (int i = 0; i < kTabs; ++i)
|
||||
{
|
||||
const bool active = i == tab_;
|
||||
const float w = ui::text(list, active ? fonts.semibold : fonts.regular, kTabNames[i], x,
|
||||
92 - 10 * (1.0f - in), 26, active ? L.text : L.muted);
|
||||
if (active)
|
||||
list.rounded_rect({x, 104, w, 4}, 2, accent());
|
||||
x += w + 44;
|
||||
}
|
||||
}
|
||||
// Right: the server, or what's wrong.
|
||||
const rommps::Status &s = app_.status();
|
||||
const bool old_payload = s.reachable && app_.payload_too_old();
|
||||
std::string where = !s.known ? "Connecting"
|
||||
: !s.reachable ? "RomM Sync isn't running"
|
||||
: old_payload ? "Updating RomM Sync " + s.version
|
||||
: s.paired ? s.user + " @ " + s.server
|
||||
: "Not paired";
|
||||
const float dot_x = 1824 - 12 - ui::text(list, fonts.regular, context_.fonts.regular.font->fit(where, 22, 640), 1824, 90,
|
||||
22, L.muted, gfx::Align::right);
|
||||
list.circle(dot_x - 12, 83, 7,
|
||||
s.reachable && s.paired && !old_payload ? L.success : s.reachable ? L.warning : L.danger);
|
||||
list.pop_opacity();
|
||||
}
|
||||
|
||||
void draw_panel(gfx::DrawList &list, const Rect &r, float radius = -1.0f) const
|
||||
{
|
||||
rommps::draw_panel(list, look(), r, radius);
|
||||
}
|
||||
|
||||
// The payload isn't answering (the setup takes over once it does).
|
||||
void draw_not_ready(gfx::DrawList &list) const
|
||||
{
|
||||
const ui::Fonts &fonts = context_.fonts;
|
||||
const rommps::Look &L = look();
|
||||
const rommps::Status &s = app_.status();
|
||||
const float in = tween::stagger(age_, 1, 0.08f, 0.6f);
|
||||
list.push_opacity(in);
|
||||
const Rect panel{360, 300, 1200, 440};
|
||||
draw_panel(list, panel, 36);
|
||||
const float x = panel.x + 72;
|
||||
const bool starting = app_.payload_starting() || (s.known && !s.reachable && app_.payload_error().empty());
|
||||
ui::text(list, fonts.display,
|
||||
!s.known ? "Connecting to RomM Sync" : starting ? "Starting RomM Sync" : "RomM Sync isn't running", x,
|
||||
panel.y + 120, 56, L.panel_text);
|
||||
ui::paragraph(list, fonts.regular,
|
||||
starting ? "RomM Sync does the syncing in the background, and RommPS starts it for you. "
|
||||
"This takes a few seconds."
|
||||
: "RommPS couldn't start RomM Sync. Press Cross to try again, or load romm-sync.elf "
|
||||
"with your HEN's payload loader.",
|
||||
x, panel.y + 190, 28, 1060, 42, L.panel_muted, 4);
|
||||
if (!starting && !app_.payload_error().empty())
|
||||
ui::text(list, fonts.regular, fonts.regular.font->fit(app_.payload_error(), 22, 1060), x, panel.y + 340, 22,
|
||||
L.panel_muted.with_alpha(0.8f));
|
||||
if (s.known && !s.error.empty())
|
||||
ui::text(list, fonts.regular, fonts.regular.font->fit(s.error, 22, 1060), x, panel.y + 380, 22,
|
||||
L.panel_muted.with_alpha(0.6f));
|
||||
list.pop_opacity();
|
||||
}
|
||||
|
||||
void draw_home(gfx::DrawList &list) const
|
||||
{
|
||||
const ui::Fonts &fonts = context_.fonts;
|
||||
const rommps::Look &L = look();
|
||||
const rommps::Status &s = app_.status();
|
||||
const float in = tween::stagger(tab_age_, 1, 0.08f, 0.6f);
|
||||
list.push_opacity(in);
|
||||
const float x = kMargin + 30.0f * (1.0f - in);
|
||||
ui::text(list, fonts.semibold, "SAVE SYNC", x, 212, 20, accent(), gfx::Align::left, 4.0f);
|
||||
// The headline only says synced when the last sync went through.
|
||||
std::string headline = s.syncing ? "Syncing"
|
||||
: !s.sync_count ? "Not synced yet"
|
||||
: s.last_result != "OK" ? "The last sync failed"
|
||||
: s.conflicts == 1 ? "1 save needs you"
|
||||
: s.conflicts > 1 ? std::to_string(s.conflicts) + " saves need you"
|
||||
: "Your saves are synced";
|
||||
ui::text(list, fonts.display, headline, x - 4, 304, 80, L.text);
|
||||
// Saves changed in both places are settled in the web UI, for now.
|
||||
char web[96];
|
||||
std::snprintf(web, sizeof web, "http://%s:%d", s.ip.c_str(), s.port);
|
||||
std::string line = s.syncing ? (s.phase.empty() ? "Working" : s.phase)
|
||||
: s.conflicts > 0 && !s.ip.empty() ? std::string("Choose which copy to keep at ") + web
|
||||
: s.sync_count ? "Last sync " + ago(std::difftime(std::time(nullptr), s.last_run)) +
|
||||
" \xC2\xB7 " + s.last_result
|
||||
: "No sync yet";
|
||||
ui::text(list, fonts.regular, context_.fonts.regular.font->fit(line, 28, 1500), x, 360, 28, L.muted);
|
||||
|
||||
// Sync now
|
||||
const Rect button{x, 410, 260, 68};
|
||||
list.glow(button, 34, 18, accent().with_alpha(0.35f));
|
||||
list.rounded_rect(button, 34, s.syncing ? L.button.with_alpha(0.4f) : L.button);
|
||||
ui::draw_button(list, fonts, L.covers ? ui::GlyphStyle::light() : ui::GlyphStyle::dark(), ui::Button::cross,
|
||||
button.x + 24, button.cy(), 34);
|
||||
ui::text(list, fonts.semibold, "Sync now", button.x + 76, button.cy() + 10, 28, L.on_button);
|
||||
|
||||
// Tiles
|
||||
char value[64];
|
||||
struct Tile
|
||||
{
|
||||
const char *label;
|
||||
std::string value;
|
||||
} tiles[4];
|
||||
std::snprintf(value, sizeof value, "%d", s.tracked_saves);
|
||||
tiles[0] = {"SAVES TRACKED", value};
|
||||
std::snprintf(value, sizeof value, "%d", s.conflicts);
|
||||
tiles[1] = {"CONFLICTS", value};
|
||||
std::snprintf(value, sizeof value, "%d up \xC2\xB7 %d down", s.last_up, s.last_down);
|
||||
tiles[2] = {"LAST SYNC", s.sync_count ? value : "-"};
|
||||
tiles[3] = {"ROMM SYNC", "v" + s.version};
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
const float appear = tween::stagger(tab_age_, 2 + i, 0.07f, 0.5f);
|
||||
const Rect tile{kMargin + static_cast<float>(i) * 432, 580 + 24 * (1.0f - appear), 408, 190};
|
||||
list.push_opacity(appear);
|
||||
draw_panel(list, tile, 26);
|
||||
ui::text(list, fonts.semibold, tiles[i].label, tile.x + 32, tile.y + 52, 17, L.panel_muted, gfx::Align::left,
|
||||
3.0f);
|
||||
ui::text(list, fonts.semibold, context_.fonts.semibold.font->fit(tiles[i].value, 46, 350), tile.x + 32, tile.y + 128,
|
||||
46, i == 1 && s.conflicts ? L.warning : L.panel_text);
|
||||
list.pop_opacity();
|
||||
}
|
||||
ui::text(list, fonts.regular, context_.fonts.regular.font->fit(s.server, 22, 1700), kMargin, 850, 22,
|
||||
L.muted.with_alpha(0.7f * L.muted.a));
|
||||
list.pop_opacity();
|
||||
}
|
||||
|
||||
void draw_library(gfx::DrawList &list) const
|
||||
{
|
||||
library_.draw(list, context_.fonts, look(), app_, accent(), clock_, tab_age_);
|
||||
}
|
||||
|
||||
void draw_downloads(gfx::DrawList &list) const
|
||||
{
|
||||
const ui::Fonts &fonts = context_.fonts;
|
||||
const rommps::Look &L = look();
|
||||
const auto &downloads = app_.downloads();
|
||||
const float in = tween::stagger(tab_age_, 1, 0.08f, 0.6f);
|
||||
list.push_opacity(in);
|
||||
ui::text(list, fonts.display, "Downloads", kMargin, 240, 64, L.text);
|
||||
// Top right: everything downloading at once, and what's waiting.
|
||||
double total_speed = 0;
|
||||
int running = 0, queued = 0;
|
||||
for (const rommps::Download &d : downloads)
|
||||
{
|
||||
if (d.running())
|
||||
{
|
||||
total_speed += d.speed;
|
||||
++running;
|
||||
}
|
||||
else if (d.status == "queued")
|
||||
++queued;
|
||||
}
|
||||
if (running)
|
||||
{
|
||||
ui::text(list, fonts.semibold, rommps::human_size(total_speed) + "/s", 1824, 228, 40, accent(), gfx::Align::right);
|
||||
char count[64];
|
||||
std::snprintf(count, sizeof count, queued ? "%d downloading \xC2\xB7 %d waiting" : "%d downloading", running,
|
||||
queued);
|
||||
ui::text(list, fonts.regular, count, 1824, 262, 22, L.muted, gfx::Align::right);
|
||||
}
|
||||
else if (queued)
|
||||
{
|
||||
char count[48];
|
||||
std::snprintf(count, sizeof count, "%d waiting", queued);
|
||||
ui::text(list, fonts.regular, count, 1824, 240, 24, L.muted, gfx::Align::right);
|
||||
}
|
||||
if (downloads.empty())
|
||||
{
|
||||
ui::text(list, fonts.regular, "Nothing downloading. Pick a game in Library to download it.", kMargin, 310, 28,
|
||||
L.muted);
|
||||
list.pop_opacity();
|
||||
return;
|
||||
}
|
||||
constexpr float kRowH = 110.0f;
|
||||
const float top = 300.0f;
|
||||
const int first = std::max(0, download_row_ - 5);
|
||||
list.rounded_rect({kMargin - 24, top + (download_position_.value - static_cast<float>(first)) * kRowH, 1776, kRowH - 14},
|
||||
26, L.text.with_alpha(0.1f));
|
||||
for (int i = first; i < static_cast<int>(downloads.size()) && i < first + 6; ++i)
|
||||
{
|
||||
const rommps::Download &d = downloads[static_cast<std::size_t>(i)];
|
||||
const float y = top + static_cast<float>(i - first) * kRowH;
|
||||
ui::text(list, fonts.semibold, context_.fonts.semibold.font->fit(d.name, 28, 1100), kMargin, y + 42, 28, L.text);
|
||||
std::string meta = d.platform + " \xC2\xB7 ";
|
||||
if (d.running())
|
||||
{
|
||||
char text[96];
|
||||
std::snprintf(text, sizeof text, "%s of %s", rommps::human_size(d.done).c_str(),
|
||||
rommps::human_size(d.total).c_str());
|
||||
meta += text;
|
||||
}
|
||||
else if (d.status == "error")
|
||||
meta += d.error;
|
||||
else
|
||||
{
|
||||
static const char *const kStates[][2] = {{"queued", "Waiting"},
|
||||
{"cancelling", "Cancelling"},
|
||||
{"cancelled", "Cancelled, nothing kept"},
|
||||
{"done", "Downloaded"}};
|
||||
for (const auto &st : kStates)
|
||||
if (d.status == st[0])
|
||||
meta += st[1];
|
||||
}
|
||||
const float hold = cancel_progress(d.id);
|
||||
if (hold > 0)
|
||||
meta = "Keep holding to cancel";
|
||||
ui::text(list, fonts.regular, context_.fonts.regular.font->fit(meta, 22, 1200), kMargin, y + 80, 22,
|
||||
d.status == "error" || hold > 0 ? L.danger : L.muted);
|
||||
const Rect bar{1360, y + 54, 420, 10};
|
||||
// Above the bar: the download's speed and how far it is.
|
||||
if (d.running())
|
||||
{
|
||||
ui::text(list, fonts.semibold, rommps::human_size(d.speed) + "/s", bar.x, y + 40, 24, L.text);
|
||||
char pct[16];
|
||||
std::snprintf(pct, sizeof pct, "%d%%", static_cast<int>(d.progress() * 100.0f));
|
||||
ui::text(list, fonts.regular, pct, bar.x + bar.w, y + 40, 22, L.muted, gfx::Align::right);
|
||||
}
|
||||
list.rounded_rect(bar, 5, L.muted.with_alpha(0.25f));
|
||||
const float progress = d.status == "done" ? 1.0f : d.progress();
|
||||
if (progress > 0)
|
||||
list.rounded_rect({bar.x, bar.y, std::max(10.0f, bar.w * progress), bar.h}, 5,
|
||||
d.status == "done" ? L.success : accent());
|
||||
if (hold > 0)
|
||||
list.rounded_rect({bar.x, bar.y, std::max(10.0f, bar.w * hold), bar.h}, 5, L.danger);
|
||||
}
|
||||
list.pop_opacity();
|
||||
}
|
||||
|
||||
void draw_sheet(gfx::DrawList &list, std::uint32_t glass) const
|
||||
{
|
||||
const ui::Fonts &fonts = context_.fonts;
|
||||
const rommps::Game *g = &sheet_game_;
|
||||
if (!g)
|
||||
return;
|
||||
const rommps::Look &L = look();
|
||||
const rommps::Palette palette = app_.cover(g->cover).palette;
|
||||
const Color acc = cover_accent(palette);
|
||||
const rommps::Platform *p = app_.platform_by_id(sheet_game_.platform_id);
|
||||
const float t = sheet_.value;
|
||||
constexpr float kHeight = 520.0f;
|
||||
const Rect sheet{120, gfx::kVirtualHeight - kHeight * t - 40.0f * t + 60.0f * (1.0f - t), 1680, kHeight};
|
||||
list.push_opacity(tween::clamp01(t * 1.4f));
|
||||
list.shadow({sheet.x, sheet.y + 20, sheet.w, sheet.h}, 44, 60, Color::rgb(0x000000, 0.5f));
|
||||
list.glass(glass, sheet, 44, kWhite);
|
||||
list.rounded_rect(sheet, 44, L.covers ? gfx::mix(palette.dark, kInk, 0.5f).with_alpha(0.62f) : L.panel);
|
||||
list.bordered_rect(sheet, 44, Color::rgb(0x000000, 0.0f), 1.5f, L.outline);
|
||||
|
||||
const Rect art{sheet.x + 56, sheet.y + 56, 300, 400};
|
||||
rommps::draw_cover(list, fonts, L, app_, g, art, 1.0f, 26);
|
||||
const float x = art.x + art.w + 56;
|
||||
ui::text(list, fonts.semibold, ui::upper(p ? p->name : ""), x, sheet.y + 92, 20, acc, gfx::Align::left, 4.0f);
|
||||
float y = ui::paragraph(list, fonts.display, g->name, x - 2, sheet.y + 156, 54, 640, 62, L.panel_text, 2) - 18;
|
||||
if (g->versions > 1)
|
||||
{
|
||||
const std::string v = g->version + " \xC2\xB7 1 of " + std::to_string(g->versions) + " versions";
|
||||
ui::text(list, fonts.semibold, fonts.semibold.font->fit(v, 22, 660), x, y, 22, L.panel_text.with_alpha(0.7f));
|
||||
y += 38;
|
||||
}
|
||||
// RomM's details: year, publisher, genres; the summary; then the numbers.
|
||||
const rommps::Details &info = app_.details(g->id);
|
||||
const std::string about = rommps::details_line(info);
|
||||
if (!about.empty())
|
||||
{
|
||||
ui::text(list, fonts.semibold, fonts.semibold.font->fit(about, 22, 660), x, y, 22, acc);
|
||||
y += 38;
|
||||
}
|
||||
if (!info.summary.empty())
|
||||
y = ui::paragraph(list, fonts.regular, info.summary, x, y, 20, 660, 28, L.panel_text.with_alpha(0.85f), 3) + 6;
|
||||
std::vector<std::string> facts;
|
||||
if (info.play_ms > 0)
|
||||
facts.push_back("Played " + rommps::play_time(info.play_ms));
|
||||
if (!info.last_played.empty())
|
||||
facts.push_back("Last played " + info.last_played.substr(0, 10));
|
||||
static const char *const kStatus[][2] = {{"incomplete", "Playing"}, {"finished", "Finished"},
|
||||
{"completed_100", "Completed 100%"}, {"retired", "Retired"},
|
||||
{"never_playing", "Not for me"}};
|
||||
for (const auto &st : kStatus)
|
||||
if (info.status == st[0])
|
||||
facts.push_back(st[1]);
|
||||
if (info.main_story_s > 0)
|
||||
facts.push_back("Main story ~" + std::to_string(static_cast<int>(info.main_story_s / 3600.0 + 0.5)) + " h");
|
||||
if (!info.players.empty())
|
||||
facts.push_back(info.players + (info.players == "1" ? " player" : " players"));
|
||||
if (info.rating > 0)
|
||||
{
|
||||
char r[24];
|
||||
std::snprintf(r, sizeof r, info.rating > 10 ? "%.0f/100" : "%.1f/10", info.rating);
|
||||
facts.push_back(r);
|
||||
}
|
||||
std::string line;
|
||||
for (const std::string &f : facts)
|
||||
line += (line.empty() ? "" : " \xC2\xB7 ") + f;
|
||||
// The file and emulator, last.
|
||||
std::string file = rommps::human_size(g->size) + (g->multi ? ", several files" : "");
|
||||
if (p && !p->profile.empty())
|
||||
file += " \xC2\xB7 plays with " + p->profile;
|
||||
y = std::max(y, sheet.y + 330.0f);
|
||||
if (!line.empty())
|
||||
{
|
||||
ui::paragraph(list, fonts.regular, line, x, y, 20, 660, 28, L.panel_muted, 2);
|
||||
y += 60;
|
||||
}
|
||||
ui::text(list, fonts.regular, context_.fonts.regular.font->fit(file, 20, 660), x, std::min(y, sheet.y + 470.0f), 20,
|
||||
L.panel_muted);
|
||||
|
||||
const std::vector<SheetAction> actions = sheet_actions();
|
||||
const float ax = sheet.x + sheet.w - 56 - 460;
|
||||
const float ay = sheet.y + 76;
|
||||
list.rounded_rect({ax, ay + action_position_.value * 84, 460, 72}, 36, L.highlight);
|
||||
for (int i = 0; i < static_cast<int>(actions.size()); ++i)
|
||||
{
|
||||
const bool focused = i == action_;
|
||||
const float y = ay + static_cast<float>(i) * 84;
|
||||
if (!focused)
|
||||
list.bordered_rect({ax, y, 460, 72}, 36, L.panel_text.with_alpha(0.06f), 1.5f, L.outline);
|
||||
const SheetAction &a = actions[static_cast<std::size_t>(i)];
|
||||
if (a.kind == 1 && focused)
|
||||
if (const rommps::Download *d = app_.download_for(g->id))
|
||||
if (const float hold = cancel_progress(d->id); hold > 0)
|
||||
list.rounded_rect({ax, y, std::max(72.0f, 460 * hold), 72}, 36, L.danger.with_alpha(0.85f));
|
||||
ui::text(list, fonts.semibold, context_.fonts.semibold.font->fit(a.label, 26, 400), ax + 36, y + 46, 26,
|
||||
(focused ? L.on_highlight : L.panel_text).with_alpha(a.enabled ? 1.0f : 0.5f));
|
||||
}
|
||||
list.pop_opacity();
|
||||
}
|
||||
|
||||
void draw_toast(gfx::DrawList &list) const
|
||||
{
|
||||
const std::string &message = app_.toast();
|
||||
if (message.empty())
|
||||
return;
|
||||
const ui::Fonts &fonts = context_.fonts;
|
||||
const float a = tween::clamp01(app_.toast_age() * 5.0f) * tween::clamp01((4.0f - app_.toast_age()) * 2.0f);
|
||||
list.push_opacity(a);
|
||||
const std::string fitted = fonts.regular.font->fit(message, 24, 1100);
|
||||
const rommps::Look &L = look();
|
||||
const Rect r{kMargin, 960, 1180, 64};
|
||||
list.rounded_rect(r, 32, L.covers ? Color::rgb(0x14182e, 0.92f) : L.page.with_alpha(0.95f));
|
||||
list.bordered_rect(r, 32, Color::rgb(0x000000, 0.0f), 1.5f, L.outline);
|
||||
ui::text(list, fonts.regular, fitted, r.x + 32, r.y + 41, 24, L.text);
|
||||
list.pop_opacity();
|
||||
}
|
||||
|
||||
void draw_hints(gfx::DrawList &list) const
|
||||
{
|
||||
const ui::Fonts &fonts = context_.fonts;
|
||||
const ui::GlyphStyle style = look().glyphs();
|
||||
if (setting_up_)
|
||||
{
|
||||
const std::vector<ui::Hint> hints = setup_.hints();
|
||||
ui::draw_hints(list, fonts, style, hints.data(), static_cast<int>(hints.size()), 1824, true);
|
||||
return;
|
||||
}
|
||||
const bool ready = app_.status().paired && app_.status().setup_complete;
|
||||
if (!ready)
|
||||
return;
|
||||
if (sheet_open_)
|
||||
{
|
||||
const ui::Hint hints[] = {{ui::Button::cross, "Choose"}, {ui::Button::circle, "Back"}};
|
||||
ui::draw_hints(list, fonts, style, hints, 2, 1824, true);
|
||||
return;
|
||||
}
|
||||
list.push_opacity(tween::stagger(age_, 8, 0.08f, 0.5f) * (1.0f - sheet_.value));
|
||||
const ui::Hint pages{ui::Button::l1, "Pages", ui::Button::r1};
|
||||
if (tab_ == kHome)
|
||||
{
|
||||
const ui::Hint hints[] = {pages, {ui::Button::cross, "Sync now"}};
|
||||
ui::draw_hints(list, fonts, style, hints, 2, 1824, true);
|
||||
}
|
||||
else if (tab_ == kLibrary)
|
||||
{
|
||||
const std::vector<ui::Hint> hints = library_.hints();
|
||||
ui::draw_hints(list, fonts, style, hints.data(), static_cast<int>(hints.size()), 1824, true);
|
||||
}
|
||||
else if (tab_ == kDownloads)
|
||||
{
|
||||
// Cross on a download that's still going stops it and deletes what
|
||||
// came so far; Square takes the finished ones off the list.
|
||||
const auto &downloads = app_.downloads();
|
||||
const bool on_active = download_row_ < static_cast<int>(downloads.size()) &&
|
||||
downloads[static_cast<std::size_t>(download_row_)].active();
|
||||
const bool any_finished = std::any_of(downloads.begin(), downloads.end(),
|
||||
[](const rommps::Download &d) { return d.finished(); });
|
||||
std::vector<ui::Hint> hints{pages};
|
||||
if (any_finished)
|
||||
hints.push_back({ui::Button::square, "Clear finished"});
|
||||
if (on_active)
|
||||
hints.push_back({ui::Button::cross, "Hold to cancel and delete"});
|
||||
ui::draw_hints(list, fonts, style, hints.data(), static_cast<int>(hints.size()), 1824, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
const ui::Hint hints[] = {pages, {ui::Button::cross, settings_.cross_hint(app_)}};
|
||||
ui::draw_hints(list, fonts, style, hints, 2, 1824, true);
|
||||
}
|
||||
list.pop_opacity();
|
||||
}
|
||||
|
||||
app::Context &context_;
|
||||
App &app_;
|
||||
int tab_ = kLibrary;
|
||||
float tab_age_ = 0.0f;
|
||||
tween::Spring tab_position_;
|
||||
float age_ = 0.0f;
|
||||
float clock_ = 0.0f;
|
||||
int shown_id_ = -1;
|
||||
ui::SpringColor palette_[4];
|
||||
ui::Pulse nudge_;
|
||||
float nudge_direction_ = 0.0f;
|
||||
bool sheet_open_ = false;
|
||||
tween::Spring sheet_;
|
||||
int action_ = 0;
|
||||
tween::Spring action_position_;
|
||||
int download_row_ = 0;
|
||||
float cancel_hold_ = 0; // seconds Cross has been held to cancel cancel_job_; -1 once done
|
||||
int cancel_job_ = 0;
|
||||
bool cancel_seen_ = false; // hold_to_cancel ran this frame
|
||||
tween::Spring download_position_;
|
||||
rommps::SettingsPage settings_;
|
||||
rommps::LibraryPage library_;
|
||||
rommps::Game sheet_game_; // the game the details sheet is for
|
||||
rommps::SetupPage setup_;
|
||||
bool setting_up_ = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<app::Concept> make_rommps(app::Context &context)
|
||||
{
|
||||
return std::make_unique<RommPS>(context);
|
||||
}
|
||||
|
||||
} // namespace hui::screen
|
||||
|
||||
// The title's screen, for its program (ps5/ui/kit.hpp declares it)
|
||||
namespace ps5ui
|
||||
{
|
||||
std::unique_ptr<hui::app::Concept> make_screen(hui::app::Context &context)
|
||||
{
|
||||
return hui::screen::make_rommps(context);
|
||||
}
|
||||
} // namespace ps5ui
|
||||
@@ -0,0 +1,307 @@
|
||||
// RommPS: the Settings tab. See settings_page.hpp.
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#include "settings_page.hpp"
|
||||
|
||||
#include "core/tween.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
namespace rommps
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
namespace ui = hui::ui;
|
||||
namespace tween = hui::tween;
|
||||
using hui::gfx::Rect;
|
||||
|
||||
constexpr float kMargin = 96.0f;
|
||||
constexpr float kArmedFor = 4.0f; // seconds a first press waits for the second
|
||||
const Rect kRows{kMargin - 32, 290, 1150, 640};
|
||||
const Rect kAbout{1300, 290, 524, 690};
|
||||
|
||||
std::string flag_json(const char *key, bool on)
|
||||
{
|
||||
return std::string("\"") + key + "\":" + (on ? "true" : "false");
|
||||
}
|
||||
|
||||
std::string int_json(const char *key, int value)
|
||||
{
|
||||
return std::string("\"") + key + "\":" + std::to_string(value);
|
||||
}
|
||||
|
||||
std::string str_json(const char *key, const char *value)
|
||||
{
|
||||
return std::string("\"") + key + "\":\"" + value + "\"";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SettingsPage::enter(App &app)
|
||||
{
|
||||
app.refresh_config();
|
||||
app.refresh_update();
|
||||
armed_ = -1;
|
||||
on_credits_ = false;
|
||||
}
|
||||
|
||||
std::vector<Row> SettingsPage::rows(const App &app) const
|
||||
{
|
||||
const Config &c = app.config();
|
||||
const Update &u = app.update();
|
||||
std::vector<Row> r(kCount);
|
||||
const Look &l = look(current_look());
|
||||
r[kLook] = {RowKind::choice, "Look", l.name, l.family};
|
||||
r[kInterval] = {RowKind::choice, "Sync every", interval_label(c.sync_interval_min), "Besides the syncs below"};
|
||||
r[kOnExit] = {RowKind::toggle, "Sync when a game closes", "", "Uploads what the game saved", c.sync_on_game_exit};
|
||||
r[kOnStart] = {RowKind::toggle, "Sync when a game starts", "", "Fetches newer saves before you play",
|
||||
c.sync_on_game_start};
|
||||
r[kOnChange] = {RowKind::toggle, "Upload saves as they change", "", "While you play", c.sync_on_change};
|
||||
r[kStates] = {RowKind::choice, "Save states", label_of(rommps::kStates, c.states), "Besides the saves themselves"};
|
||||
r[kPolicy] = {RowKind::choice, "A save changed in both places", label_of(kPolicies, c.conflict_policy),
|
||||
"On this console and on RomM since the last sync"};
|
||||
r[kConcurrency] = {RowKind::choice, "Downloads at once", std::to_string(c.download_concurrency), ""};
|
||||
r[kNotify] = {RowKind::toggle, "Notifications", "", "On the console when a sync ends or fails", c.notify};
|
||||
r[kUpdateCheck] = {RowKind::toggle, "Look for updates every day", "", "", c.update_check};
|
||||
{
|
||||
const Status &s = app.status();
|
||||
char detail[96];
|
||||
if (!c.cover_cache)
|
||||
std::snprintf(detail, sizeof detail, "Covers come from RomM as you browse");
|
||||
else if (s.covers_state == "paused")
|
||||
std::snprintf(detail, sizeof detail, "Paused while you play or download (%d of %d saved)", s.covers_done,
|
||||
s.covers_total);
|
||||
else if (s.covers_state == "full")
|
||||
std::snprintf(detail, sizeof detail, "The cover space is nearly full (%d of %d saved)", s.covers_done,
|
||||
s.covers_total);
|
||||
else if (s.covers_total > 0 && s.covers_done < s.covers_total)
|
||||
std::snprintf(detail, sizeof detail, "Saving covers: %d of %d", s.covers_done, s.covers_total);
|
||||
else if (s.covers_total > 0)
|
||||
std::snprintf(detail, sizeof detail, "All %d covers are on the console", s.covers_total);
|
||||
else
|
||||
std::snprintf(detail, sizeof detail, "Faster browsing: every game's cover, kept up to date");
|
||||
r[kCovers] = {RowKind::toggle, "Keep all covers on the console", "", detail, c.cover_cache};
|
||||
}
|
||||
|
||||
Row &update = r[kUpdate];
|
||||
update.kind = RowKind::action;
|
||||
update.label = "RomM Sync " + (u.current.empty() ? app.status().version : u.current);
|
||||
if (!u.self_update)
|
||||
{
|
||||
// RommPS brings RomM Sync with it: the store updates both.
|
||||
update.label = std::string("RommPS ") + App::kMinPayload;
|
||||
update.value = u.state == "checking" ? "Checking" : "Check for updates";
|
||||
update.enabled = u.state != "checking";
|
||||
update.detail = u.available ? "RommPS " + u.latest + " is out: update it in ProsperoStore"
|
||||
: u.state == "error" ? (u.error.empty() ? u.message : u.error)
|
||||
: u.latest.empty() ? "Updates come from ProsperoStore, with RomM Sync in them"
|
||||
: "Up to date";
|
||||
}
|
||||
else if (u.busy())
|
||||
{
|
||||
update.value = u.message.empty() ? "Working" : u.message;
|
||||
if (u.total > 0)
|
||||
{
|
||||
char text[32];
|
||||
std::snprintf(text, sizeof text, " %d%%", static_cast<int>(100.0 * u.done / u.total));
|
||||
update.value += text;
|
||||
}
|
||||
update.enabled = false;
|
||||
}
|
||||
else if (u.available)
|
||||
{
|
||||
update.value = "Install " + u.latest;
|
||||
update.detail = "RomM Sync restarts with the new version";
|
||||
}
|
||||
else
|
||||
{
|
||||
update.value = "Check for updates";
|
||||
update.detail = u.state == "error" ? (u.error.empty() ? u.message : u.error)
|
||||
: u.state == "pending" ? "Restart the console to finish installing"
|
||||
: u.latest.empty() ? ""
|
||||
: "Up to date";
|
||||
}
|
||||
|
||||
r[kEmulators] = {RowKind::action, "Emulators", "", "Which emulators RomM Sync uses, and for which systems"};
|
||||
r[kSetup] = {RowKind::action, "Set up again", "", "Pick your emulators again. Background syncs wait until it's done"};
|
||||
r[kForget] = {RowKind::action, "Unpair from RomM", "", "Files on this console are kept"};
|
||||
r[kForget].danger = true;
|
||||
if (armed_ == kSetup || armed_ == kForget)
|
||||
r[static_cast<std::size_t>(armed_)].value = "Press again to confirm";
|
||||
if (!c.known)
|
||||
for (int i = kInterval; i <= kCovers; ++i)
|
||||
r[static_cast<std::size_t>(i)].enabled = false;
|
||||
return r;
|
||||
}
|
||||
|
||||
const char *SettingsPage::cross_hint(const App &app) const
|
||||
{
|
||||
if (on_credits_)
|
||||
return "Answer";
|
||||
const std::vector<Row> r = rows(app);
|
||||
const Row &row = r[static_cast<std::size_t>(std::clamp(list_.focus(), 0, kCount - 1))];
|
||||
switch (row.kind)
|
||||
{
|
||||
case RowKind::toggle:
|
||||
return "Switch";
|
||||
case RowKind::choice:
|
||||
return "Change";
|
||||
default:
|
||||
return "Choose";
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsPage::update(App &app, const hui::InputFrame &input, float dt, hui::ui::Feedback &feedback)
|
||||
{
|
||||
avatar_texture(static_cast<hui::gfx::Renderer *>(rommps_renderer()));
|
||||
const std::vector<Row> r = rows(app);
|
||||
armed_age_ += dt;
|
||||
// Below the last row: the author's portrait, and the call behind it.
|
||||
if (on_credits_)
|
||||
{
|
||||
list_.update(dt, kCount, kRows.h);
|
||||
if (input.nav == hui::Direction::up || input.is_pressed(hui::Action::back))
|
||||
{
|
||||
on_credits_ = false;
|
||||
feedback.play(hui::audio::Cue::focus);
|
||||
}
|
||||
else if (input.is_pressed(hui::Action::confirm))
|
||||
{
|
||||
call_.start();
|
||||
feedback.play(hui::audio::Cue::modal_open);
|
||||
}
|
||||
else if (input.nav != hui::Direction::none && !input.nav_repeat)
|
||||
list_.refuse(feedback);
|
||||
return;
|
||||
}
|
||||
if (input.nav == hui::Direction::down && !input.nav_repeat && list_.focus() == kCount - 1)
|
||||
{
|
||||
on_credits_ = true;
|
||||
feedback.play(hui::audio::Cue::focus);
|
||||
return;
|
||||
}
|
||||
if (armed_ >= 0 && (armed_age_ > kArmedFor || list_.focus() != armed_))
|
||||
armed_ = -1;
|
||||
const RowEvent e = list_.handle(r, input, feedback);
|
||||
list_.update(dt, kCount, kRows.h);
|
||||
if (e.row < 0)
|
||||
return;
|
||||
const Config &c = app.config();
|
||||
switch (e.row)
|
||||
{
|
||||
case kLook:
|
||||
set_look((current_look() + e.step + look_count()) % look_count());
|
||||
break;
|
||||
case kInterval:
|
||||
{
|
||||
const int minutes = step_interval(c.sync_interval_min, e.step);
|
||||
app.set_config(int_json("sync_interval_min", minutes), [minutes](Config &k) { k.sync_interval_min = minutes; });
|
||||
break;
|
||||
}
|
||||
case kOnExit:
|
||||
app.set_config(flag_json("sync_on_game_exit", !c.sync_on_game_exit),
|
||||
[](Config &k) { k.sync_on_game_exit = !k.sync_on_game_exit; });
|
||||
break;
|
||||
case kOnStart:
|
||||
app.set_config(flag_json("sync_on_game_start", !c.sync_on_game_start),
|
||||
[](Config &k) { k.sync_on_game_start = !k.sync_on_game_start; });
|
||||
break;
|
||||
case kOnChange:
|
||||
app.set_config(flag_json("sync_on_change", !c.sync_on_change),
|
||||
[](Config &k) { k.sync_on_change = !k.sync_on_change; });
|
||||
break;
|
||||
case kStates:
|
||||
{
|
||||
const std::string id = step_option(rommps::kStates, c.states, e.step);
|
||||
app.set_config(str_json("states", id.c_str()), [id](Config &k) { k.states = id; });
|
||||
break;
|
||||
}
|
||||
case kPolicy:
|
||||
{
|
||||
const std::string id = step_option(kPolicies, c.conflict_policy, e.step);
|
||||
app.set_config(str_json("conflict_policy", id.c_str()), [id](Config &k) { k.conflict_policy = id; });
|
||||
break;
|
||||
}
|
||||
case kConcurrency:
|
||||
{
|
||||
const int n = (c.download_concurrency - 1 + e.step + 4) % 4 + 1;
|
||||
app.set_config(int_json("download_concurrency", n), [n](Config &k) { k.download_concurrency = n; });
|
||||
break;
|
||||
}
|
||||
case kNotify:
|
||||
app.set_config(flag_json("notify", !c.notify), [](Config &k) { k.notify = !k.notify; });
|
||||
break;
|
||||
case kUpdateCheck:
|
||||
app.set_config(flag_json("update_check", !c.update_check), [](Config &k) { k.update_check = !k.update_check; });
|
||||
break;
|
||||
case kCovers:
|
||||
app.set_config(flag_json("cover_cache", !c.cover_cache), [](Config &k) { k.cover_cache = !k.cover_cache; });
|
||||
break;
|
||||
case kUpdate:
|
||||
if (app.update().available && app.update().self_update)
|
||||
app.install_update();
|
||||
else
|
||||
app.check_update();
|
||||
break;
|
||||
case kEmulators:
|
||||
emulators_asked_ = true;
|
||||
break;
|
||||
case kSetup:
|
||||
case kForget:
|
||||
if (armed_ != e.row)
|
||||
{
|
||||
armed_ = e.row;
|
||||
armed_age_ = 0.0f;
|
||||
break;
|
||||
}
|
||||
armed_ = -1;
|
||||
if (e.row == kSetup)
|
||||
app.setup_restart();
|
||||
else
|
||||
app.pair_forget();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsPage::draw(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, const App &app,
|
||||
float clock, float age) const
|
||||
{
|
||||
const float in = tween::stagger(age, 1, 0.08f, 0.6f);
|
||||
list.push_opacity(in);
|
||||
ui::text(list, fonts.display, "Settings", kMargin, 240, 64, look.text);
|
||||
list_.draw(list, fonts, look, rows(app), {kRows.x, kRows.y + 24 * (1.0f - in), kRows.w, kRows.h}, clock);
|
||||
|
||||
// About
|
||||
const float appear = tween::stagger(age, 3, 0.08f, 0.6f);
|
||||
list.push_opacity(appear);
|
||||
const Rect panel{kAbout.x, kAbout.y + 24 * (1.0f - appear), kAbout.w, kAbout.h};
|
||||
draw_panel(list, look, panel);
|
||||
const Status &s = app.status();
|
||||
const float x = panel.x + 36;
|
||||
float y = panel.y + 64;
|
||||
auto item = [&](const char *label, const std::string &value) {
|
||||
ui::text(list, fonts.semibold, label, x, y, 16, look.panel_muted, hui::gfx::Align::left, 3.0f);
|
||||
ui::text(list, fonts.regular, fonts.regular.font->fit(value.empty() ? "-" : value, 24, panel.w - 72), x, y + 36, 24,
|
||||
look.panel_text);
|
||||
y += 80;
|
||||
};
|
||||
item("ROMM SERVER", s.server);
|
||||
item("SIGNED IN AS", s.user);
|
||||
item("ROMM VERSION", s.server_version);
|
||||
char web[96];
|
||||
std::snprintf(web, sizeof web, "http://%s:%d", s.ip.c_str(), s.port);
|
||||
item("ALSO ON YOUR PHONE OR COMPUTER", s.ip.empty() ? "" : web);
|
||||
const Update &u = app.update();
|
||||
item("STARTS WITH THE CONSOLE", !u.known ? "" : u.autostart ? "Yes" : "No: turn it on in Set up again");
|
||||
list.line(panel.x + 36, panel.y + panel.h - 196, panel.x + panel.w - 36, panel.y + panel.h - 196, 1.5f, look.outline);
|
||||
draw_credits(list, fonts, look, {x, panel.y + panel.h - 168, panel.w - 72, 136}, on_credits_, clock);
|
||||
list.pop_opacity();
|
||||
list.pop_opacity();
|
||||
}
|
||||
|
||||
} // namespace rommps
|
||||
@@ -0,0 +1,64 @@
|
||||
// RommPS: the Settings tab. The look, when and what RomM Sync syncs, its
|
||||
// updates, the setup again and unpairing; an About panel beside them.
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "credits.hpp"
|
||||
#include "rommps_app.hpp"
|
||||
#include "rows.hpp"
|
||||
|
||||
namespace rommps
|
||||
{
|
||||
|
||||
class SettingsPage
|
||||
{
|
||||
public:
|
||||
void enter(App &app);
|
||||
void update(App &app, const hui::InputFrame &input, float dt, hui::ui::Feedback &feedback);
|
||||
void draw(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, const App &app, float clock,
|
||||
float age) const;
|
||||
// What Cross does on the focused row, for the hints.
|
||||
const char *cross_hint(const App &app) const;
|
||||
// True once after Emulators was chosen: the screen opens that setup step.
|
||||
bool take_emulators_request()
|
||||
{
|
||||
const bool asked = emulators_asked_;
|
||||
emulators_asked_ = false;
|
||||
return asked;
|
||||
}
|
||||
// The Codec call behind the author's portrait; the screen draws it over everything.
|
||||
CodecCall &call() { return call_; }
|
||||
const CodecCall &call() const { return call_; }
|
||||
|
||||
private:
|
||||
enum Id
|
||||
{
|
||||
kLook,
|
||||
kInterval,
|
||||
kOnExit,
|
||||
kOnStart,
|
||||
kOnChange,
|
||||
kStates,
|
||||
kPolicy,
|
||||
kConcurrency,
|
||||
kNotify,
|
||||
kUpdateCheck,
|
||||
kCovers,
|
||||
kUpdate,
|
||||
kEmulators,
|
||||
kSetup,
|
||||
kForget,
|
||||
kCount
|
||||
};
|
||||
std::vector<Row> rows(const App &app) const;
|
||||
|
||||
RowList list_;
|
||||
bool on_credits_ = false;
|
||||
bool emulators_asked_ = false; // the focus is on the author's portrait, below the rows
|
||||
CodecCall call_;
|
||||
int armed_ = -1; // a row that does something lasting, waiting for its second press
|
||||
float armed_age_ = 0.0f;
|
||||
};
|
||||
|
||||
} // namespace rommps
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,189 @@
|
||||
// RommPS: the setup, on the console. The same steps as the web page's wizard:
|
||||
//
|
||||
// 1. the RomM server's address, typed on the kit's on-screen keyboard;
|
||||
// 2. the code to approve in RomM (its device pairing), until it's approved;
|
||||
// 3. the emulators found on the console, and which one plays a system
|
||||
// several of them can;
|
||||
// 4. what the first sync would do, and a word about backups;
|
||||
// 5. when to sync, then the first sync.
|
||||
//
|
||||
// The step follows the payload: unpaired is step 1 or 2, paired but not set
|
||||
// up is 3 onwards.
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "rommps_app.hpp"
|
||||
#include "rows.hpp"
|
||||
#include "ui/components/keyboard.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace rommps
|
||||
{
|
||||
|
||||
class SetupPage
|
||||
{
|
||||
public:
|
||||
SetupPage();
|
||||
SetupPage(const SetupPage &) = delete;
|
||||
SetupPage &operator=(const SetupPage &) = delete;
|
||||
// Starts over from where the payload is (each time the setup appears).
|
||||
void enter();
|
||||
// Settings' Emulators: just this step, from the emulators set up now;
|
||||
// Save or Circle goes back to Settings.
|
||||
void edit_emulators();
|
||||
bool editing() const { return editing_; }
|
||||
void update(App &app, const hui::InputFrame &input, float dt, hui::ui::Feedback &feedback);
|
||||
void draw(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, const App &app, float clock) const;
|
||||
// The hints for the step on screen.
|
||||
std::vector<hui::ui::Hint> hints() const;
|
||||
|
||||
private:
|
||||
enum Step
|
||||
{
|
||||
kServer,
|
||||
kApprove,
|
||||
kEmulators,
|
||||
kPreview,
|
||||
kFinish
|
||||
};
|
||||
struct System
|
||||
{
|
||||
std::string key, label; // label: the emulator, and its core when it has one
|
||||
std::vector<std::string> slugs; // every RomM slug it plays
|
||||
};
|
||||
// A system on the RomM server no emulator here plays: off, or the folders picked for it.
|
||||
struct Own
|
||||
{
|
||||
std::vector<std::string> slugs;
|
||||
std::string name;
|
||||
int games = -1; // the server's count, -1 for an emulator's system
|
||||
bool on = false;
|
||||
std::string folder[3]; // games, saves, states
|
||||
std::string id; // for an emulator with no preset: its profile, "<emulator>-<system>"
|
||||
};
|
||||
struct Emulator
|
||||
{
|
||||
std::string root, name, kind, id;
|
||||
std::vector<std::string> cores;
|
||||
std::vector<System> systems;
|
||||
bool on = true;
|
||||
bool ready = true; // false: found, no preset yet (set up by its folders, like a system with no emulator)
|
||||
bool fresh = false; // found since the last setup
|
||||
std::string note;
|
||||
};
|
||||
// What a row of the emulator step does.
|
||||
struct RowRef
|
||||
{
|
||||
enum Kind
|
||||
{
|
||||
kNone,
|
||||
kEmulator,
|
||||
kMain,
|
||||
kAlso,
|
||||
kPs2,
|
||||
kOwn,
|
||||
kOwnFolder,
|
||||
kContinue
|
||||
} kind = kNone;
|
||||
int emulator = -1; // or, for kOwnFolder, which folder (0 games, 1 saves, 2 states)
|
||||
std::string key, root;
|
||||
};
|
||||
struct Option
|
||||
{
|
||||
std::string root, label;
|
||||
bool retroarch = false;
|
||||
};
|
||||
struct Conflict
|
||||
{
|
||||
std::string key;
|
||||
std::vector<Option> options;
|
||||
};
|
||||
struct Preview
|
||||
{
|
||||
bool loaded = false, loading = false;
|
||||
std::string error;
|
||||
int games = 0, saves = 0, up = 0, down = 0, both = 0, in_sync = 0;
|
||||
};
|
||||
|
||||
void go(Step step);
|
||||
void follow_status(App &app);
|
||||
void update_server(App &app, const hui::InputFrame &input, float dt, hui::ui::Feedback &feedback);
|
||||
void update_emulators(App &app, const hui::InputFrame &input, float dt, hui::ui::Feedback &feedback);
|
||||
void update_preview(App &app, const hui::InputFrame &input, float dt, hui::ui::Feedback &feedback);
|
||||
void update_finish(App &app, const hui::InputFrame &input, float dt, hui::ui::Feedback &feedback);
|
||||
void load_emulators(App &app);
|
||||
void load_preview(App &app);
|
||||
void load_autostart(App &app);
|
||||
std::vector<Conflict> conflicts() const;
|
||||
// The root of the emulator that keeps a system: the one picked, else RetroArch, else the first.
|
||||
std::string chosen(const Conflict &conflict) const;
|
||||
// Rows above the emulators' (a message instead of them).
|
||||
int lead() const { return error_.empty() && !emulators_.empty() ? 0 : 1; }
|
||||
std::vector<Row> emulator_rows(const App &app, std::vector<RowRef> *refs = nullptr) const;
|
||||
std::vector<Row> preview_rows() const;
|
||||
std::vector<Row> finish_rows() const;
|
||||
bool has_ps2() const;
|
||||
// The server's systems no ticked emulator plays, in the server's order.
|
||||
std::vector<std::string> uncovered() const;
|
||||
// The folder picker (/api/fs), for one of a system's folders.
|
||||
void open_picker(App &app, const std::string &key, int which);
|
||||
void browse(App &app, const std::string &path);
|
||||
void update_picker(App &app, const hui::InputFrame &input, float dt, hui::ui::Feedback &feedback);
|
||||
void draw_picker(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, float clock) const;
|
||||
|
||||
void draw_steps(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look) const;
|
||||
void draw_server(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, const App &app,
|
||||
float clock) const;
|
||||
void draw_approve(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, const App &app,
|
||||
float clock) const;
|
||||
void draw_heading(hui::gfx::DrawList &list, const hui::ui::Fonts &fonts, const Look &look, const char *title,
|
||||
const std::string &text) const;
|
||||
|
||||
Step step_ = kServer;
|
||||
float step_age_ = 0.0f;
|
||||
RowList rows_;
|
||||
bool busy_ = false; // a step's request is out
|
||||
std::string error_;
|
||||
|
||||
// 1-2
|
||||
hui::ui::Keyboard keys_;
|
||||
std::string url_;
|
||||
bool url_known_ = false;
|
||||
bool tls_verify_ = true;
|
||||
|
||||
// 3
|
||||
std::vector<Emulator> emulators_;
|
||||
bool emulators_loaded_ = false;
|
||||
std::map<std::string, std::string> choices_; // system key -> the root of its main emulator
|
||||
std::map<std::string, std::set<std::string>> also_; // system key -> roots of its extra emulators
|
||||
bool editing_ = false;
|
||||
std::string ps2_cards_ = "per_game";
|
||||
std::map<std::string, Own> own_; // by system key (first slug)
|
||||
std::vector<std::string> own_order_; // the server's order
|
||||
std::vector<std::string> manual_; // own_ keys of emulators with no preset, in the order found
|
||||
bool picking_ = false;
|
||||
std::string pick_key_;
|
||||
int pick_which_ = 0;
|
||||
std::string fs_path_, fs_parent_;
|
||||
std::vector<std::string> fs_folders_;
|
||||
bool fs_loading_ = false;
|
||||
std::string fs_error_;
|
||||
RowList pick_rows_;
|
||||
|
||||
// 4
|
||||
Preview preview_;
|
||||
bool backed_up_ = false;
|
||||
|
||||
// 5
|
||||
Config finish_;
|
||||
bool finish_known_ = false;
|
||||
bool autostart_supported_ = false, autostart_installed_ = false, autostart_on_ = false;
|
||||
bool autostart_defaulted_ = false; // turned on for a first setup already
|
||||
};
|
||||
|
||||
} // namespace rommps
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* RommPS - its program, made from the PS5 Vulkan Template's UI starter.
|
||||
*
|
||||
* One complete design of PS5_VKHomebrewUI's kit (ps5/ui/kit.hpp) fills the
|
||||
* screen: its procedural backdrop, its scene, its frosted overlay and its post
|
||||
* overlay, its sounds on the console's audio output, its rumble and its light
|
||||
* bar colour. There is no switcher: every button is the design's, and holding
|
||||
* OPTIONS for a second leaves (back to the menu, or out of a title of one
|
||||
* program). The design grew from the kit's "aurora", in kit/screen.cpp.
|
||||
*
|
||||
* Copyright (C) 2026 Mihawk
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "kit.hpp"
|
||||
|
||||
// Covers become textures through the kit's renderer (kit/rommps_app.cpp). C
|
||||
// linkage, so it names the same function from inside this file's namespace.
|
||||
extern "C" void rommps_set_renderer(void *renderer);
|
||||
|
||||
// RommPS's own interface sounds: assets/rommps/sounds/<cue>_NN.wav in the
|
||||
// title (platform/ps5/app/assets/sounds in the repository; focus_01.wav,
|
||||
// select_01.wav, back_01.wav, ...: the kit's cue names, audio/cues.cpp), 48 kHz
|
||||
// 16- or 24-bit PCM. Each cue there replaces the kit's recordings of that cue;
|
||||
// the cues it leaves out keep them.
|
||||
std::string soundPackDir(const ps5ui::Kit &kit)
|
||||
{
|
||||
const size_t slash = kit.assets.rfind('/');
|
||||
return (slash == std::string::npos ? std::string(".") : kit.assets.substr(0, slash)) + "/rommps/sounds";
|
||||
}
|
||||
|
||||
bool cueOfFile(const std::string &file, hui::audio::Cue *cue)
|
||||
{
|
||||
if (file.size() <= 4 || file.compare(file.size() - 4, 4, ".wav") != 0) {
|
||||
return false;
|
||||
}
|
||||
std::string stem = file.substr(0, file.size() - 4);
|
||||
const size_t underscore = stem.rfind('_');
|
||||
if (underscore != std::string::npos && underscore + 1 < stem.size() &&
|
||||
stem.find_first_not_of("0123456789", underscore + 1) == std::string::npos) {
|
||||
stem.resize(underscore);
|
||||
}
|
||||
for (int i = 0; i < (int)hui::audio::Cue::count; i++) {
|
||||
if (stem == hui::audio::cue_name((hui::audio::Cue)i)) {
|
||||
*cue = (hui::audio::Cue)i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Rebuilds the kit's sound bank with the pack's cues in place of the built-in ones.
|
||||
void loadSoundPack(ps5ui::Kit &kit)
|
||||
{
|
||||
using namespace hui::audio;
|
||||
const std::string packDir = soundPackDir(kit);
|
||||
std::vector<std::string> files = hui::save::list_files(packDir);
|
||||
if (files.empty()) {
|
||||
return;
|
||||
}
|
||||
std::sort(files.begin(), files.end());
|
||||
SoundBank bank;
|
||||
bool replaced[(int)Cue::count] = {};
|
||||
int loaded = 0, rejected = 0;
|
||||
for (const std::string &name : files) {
|
||||
Cue cue;
|
||||
std::string data;
|
||||
if (!cueOfFile(name, &cue) || !hui::save::read_file(packDir + "/" + name, &data, 16u << 20)) {
|
||||
rejected++;
|
||||
continue;
|
||||
}
|
||||
DecodedWav wav = decode_wav(data);
|
||||
if (!wav.ok()) {
|
||||
say("sound pack: %s: %s", name.c_str(), wav.error.c_str());
|
||||
rejected++;
|
||||
continue;
|
||||
}
|
||||
// Both sets, so a cue sounds the same whichever set asks for it
|
||||
for (int set = 0; set < (int)SoundSet::count; set++) {
|
||||
bank.add((SoundSet)set, cue, wav.samples, wav.frames);
|
||||
}
|
||||
replaced[(int)cue] = true;
|
||||
loaded++;
|
||||
}
|
||||
if (loaded == 0) {
|
||||
say("sound pack: nothing usable in %s (%d rejected)", packDir.c_str(), rejected);
|
||||
return;
|
||||
}
|
||||
// The built-in recordings of the cues the pack leaves out
|
||||
for (int set = 0; set < (int)SoundSet::count; set++) {
|
||||
const std::string dir = kit.assets + "/audio/sfx/" + sound_set_name((SoundSet)set);
|
||||
for (const std::string &name : hui::save::list_files(dir)) {
|
||||
Cue cue;
|
||||
std::string data;
|
||||
if (!cueOfFile(name, &cue) || replaced[(int)cue] || !hui::save::read_file(dir + "/" + name, &data, 16u << 20)) {
|
||||
continue;
|
||||
}
|
||||
DecodedWav wav = decode_wav(data);
|
||||
if (wav.ok()) {
|
||||
bank.add((SoundSet)set, cue, std::move(wav.samples), wav.frames);
|
||||
}
|
||||
}
|
||||
}
|
||||
kit.sounds = std::move(bank);
|
||||
say("sound pack: %d sounds from %s (%d rejected)", loaded, packDir.c_str(), rejected);
|
||||
}
|
||||
|
||||
// The design on screen: this title's own, in kit/screen.cpp
|
||||
std::unique_ptr<hui::app::Concept> makeScreen(hui::app::Context &context)
|
||||
{
|
||||
return ps5ui::make_screen(context);
|
||||
}
|
||||
|
||||
class VulkanExample : public ps5ui::KitExample
|
||||
{
|
||||
public:
|
||||
// What a design receives: the fonts, the sample content, the live numbers
|
||||
// it may show and the settings it may change
|
||||
hui::Settings choices;
|
||||
hui::app::Telemetry telemetry;
|
||||
std::unique_ptr<hui::app::Context> context;
|
||||
std::unique_ptr<hui::app::Concept> screen;
|
||||
hui::app::Frame frame;
|
||||
hui::ui::Feedback feedback;
|
||||
ps5ui::HoldToLeave leaving;
|
||||
hui::gfx::DrawList leavingList;
|
||||
double fpsSeconds{ 0.0 };
|
||||
int fpsFrames{ 0 };
|
||||
|
||||
VulkanExample() : KitExample()
|
||||
{
|
||||
title = "RommPS";
|
||||
name = "rommps";
|
||||
// The kit draws the whole screen and owns the pad
|
||||
settings.overlay = false;
|
||||
ps5.ownOverlay = true;
|
||||
defaultClearColor = { { 0.0f, 0.0f, 0.0f, 1.0f } };
|
||||
}
|
||||
|
||||
void prepare() override
|
||||
{
|
||||
VulkanExampleBase::prepare();
|
||||
// The kit's sounds, without its music behind them
|
||||
prepareKit({ .music = false });
|
||||
// Before the first sound plays: the bank is replaced whole
|
||||
loadSoundPack(kit);
|
||||
rommps_set_renderer(static_cast<hui::gfx::Renderer *>(&kit.renderer));
|
||||
context = std::make_unique<hui::app::Context>(hui::app::Context{ kit.fonts, kit.catalog, telemetry, choices });
|
||||
screen = makeScreen(*context);
|
||||
screen->enter();
|
||||
kit.apply(choices);
|
||||
feedback.play(hui::audio::Cue::welcome);
|
||||
prepared = true;
|
||||
}
|
||||
|
||||
// Holding OPTIONS for a second leaves; a press is the design's
|
||||
void holdOptions(float dt)
|
||||
{
|
||||
leaving.label = ps5.optionsEnds ? "Back to the menu" : "Close";
|
||||
if (!ps5.frameBudget && leaving.update(dt, (ps5_pad().held & PAD_OPTIONS) != 0)) {
|
||||
quit = true;
|
||||
}
|
||||
}
|
||||
|
||||
void updateTelemetry()
|
||||
{
|
||||
if (benchmark.active) {
|
||||
// A test run's pictures must not depend on the console's timing
|
||||
telemetry.push(16.7f);
|
||||
telemetry.fps = 59.9f;
|
||||
telemetry.average_ms = 16.7f;
|
||||
} else {
|
||||
telemetry.push(frameTimer * 1000.0f);
|
||||
fpsSeconds += frameTimer;
|
||||
fpsFrames++;
|
||||
if (fpsSeconds >= 0.5) {
|
||||
telemetry.fps = (float)(fpsFrames / fpsSeconds);
|
||||
telemetry.average_ms = (float)(fpsSeconds * 1000.0 / fpsFrames);
|
||||
fpsSeconds = 0.0;
|
||||
fpsFrames = 0;
|
||||
}
|
||||
telemetry.voices = kit.mixer.active_voices();
|
||||
}
|
||||
telemetry.draw_calls = kit.renderer.last_draw_calls();
|
||||
telemetry.instances = kit.renderer.last_instances();
|
||||
}
|
||||
|
||||
void render() override
|
||||
{
|
||||
if (!prepared) {
|
||||
return;
|
||||
}
|
||||
prepareFrame();
|
||||
const float dt = std::min(frameTimer, 0.05f);
|
||||
// A test run ignores the pad: the design shows its entrance and rests,
|
||||
// or takes the presses its test-run.txt gives ("press rommps <frame> r1")
|
||||
hui::InputFrame input = kit.input();
|
||||
if (ps5.frameBudget) {
|
||||
input = kit.scripted("rommps", ps5.framesDrawn);
|
||||
}
|
||||
holdOptions(dt);
|
||||
updateTelemetry();
|
||||
// CIRCLE is the screen's (back, in the setup and the sheets): RommPS closes
|
||||
// with the PS button, or by holding OPTIONS
|
||||
screen->update(input, dt, feedback);
|
||||
if (context->settings_changed) {
|
||||
context->settings_changed = false;
|
||||
kit.apply(choices);
|
||||
}
|
||||
kit.play(feedback, screen->info().sounds, choices.haptics);
|
||||
feedback.clear();
|
||||
if (choices.light_bar) {
|
||||
kit.light_bar(screen->info().accent);
|
||||
}
|
||||
kit.tick(dt);
|
||||
|
||||
// backdrop, scene, [the glass copy], overlay, post: the order a design is drawn in
|
||||
frame.reset();
|
||||
frame.glass_texture = kit.renderer.glass_texture();
|
||||
screen->draw(frame);
|
||||
kit.renderer.begin();
|
||||
kit.renderer.backdrop(frame.backdrop);
|
||||
kit.renderer.draw(frame.scene);
|
||||
if (frame.glass) {
|
||||
kit.renderer.glass();
|
||||
}
|
||||
kit.renderer.draw(frame.overlay);
|
||||
kit.renderer.backdrop(frame.post);
|
||||
leavingList.clear();
|
||||
leaving.draw(leavingList, kit.fonts, ps5ui::active_theme());
|
||||
kit.renderer.draw(leavingList);
|
||||
buildKitCommandBuffer();
|
||||
submitFrame();
|
||||
}
|
||||
|
||||
~VulkanExample() override
|
||||
{
|
||||
if (device) {
|
||||
vkDeviceWaitIdle(device);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
VULKAN_EXAMPLE_MAIN()
|
||||
@@ -0,0 +1,34 @@
|
||||
clone_folder: c:\dev\glm-cmake
|
||||
|
||||
os:
|
||||
- Visual Studio 2013
|
||||
|
||||
platform:
|
||||
- x86
|
||||
- x86_64
|
||||
|
||||
build_script:
|
||||
- md build_pure_11
|
||||
- cd build_pure_11
|
||||
- cmake -DCMAKE_CXX_COMPILER=$COMPILER -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_LANG_EXTENSIONS=ON -DGLM_TEST_FORCE_PURE=ON ..
|
||||
- cmake --build . --config Debug
|
||||
- cmake --build . --config Release
|
||||
- cd ..
|
||||
- md build_simd_11
|
||||
- cd build_simd_11
|
||||
- cmake -DCMAKE_CXX_COMPILER=$COMPILER -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_LANG_EXTENSIONS=ON ..
|
||||
- cmake --build . --config Debug
|
||||
- cmake --build . --config Release
|
||||
- cd ..
|
||||
- md build_pure_98
|
||||
- cd build_pure_98
|
||||
- cmake -DCMAKE_CXX_COMPILER=$COMPILER -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_CXX_98=ON -DGLM_TEST_FORCE_PURE=ON ..
|
||||
- cmake --build . --config Debug
|
||||
- cmake --build . --config Release
|
||||
- cd ..
|
||||
- md build_simd_98
|
||||
- cd build_simd_98
|
||||
- cmake -DCMAKE_CXX_COMPILER=$COMPILER -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_CXX_98=ON ..
|
||||
- cmake --build . --config Debug
|
||||
- cmake --build . --config Release
|
||||
- cd ..
|
||||
@@ -0,0 +1,56 @@
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# CMake
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
*.cmake
|
||||
# ^ May need to add future .cmake files as exceptions
|
||||
|
||||
# Test logs
|
||||
Testing/*
|
||||
|
||||
# Test input
|
||||
test/gtc/*.dds
|
||||
|
||||
# Project Files
|
||||
Makefile
|
||||
*.cbp
|
||||
*.user
|
||||
|
||||
# Misc.
|
||||
*.log
|
||||
|
||||
# local build(s)
|
||||
build*
|
||||
|
||||
/.vs
|
||||
/CMakeSettings.json
|
||||
@@ -0,0 +1,84 @@
|
||||
language: cpp
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.9
|
||||
env: COMPILER=g++-4.9
|
||||
- compiler: clang
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-precise-3.8
|
||||
packages:
|
||||
- clang-3.8
|
||||
env: COMPILER=clang++-3.8
|
||||
|
||||
compiler:
|
||||
- clang
|
||||
|
||||
before_script:
|
||||
|
||||
script:
|
||||
- mkdir ./build_pure_11
|
||||
- cd ./build_pure_11
|
||||
- cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_BUILD_TYPE=Release -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_CXX_11=ON -DGLM_TEST_FORCE_PURE=ON ..
|
||||
- cmake --build .
|
||||
- ctest
|
||||
- cd ..
|
||||
- mkdir ./build_pure_98
|
||||
- cd ./build_pure_98
|
||||
- cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_BUILD_TYPE=Release -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_CXX_98=ON -DGLM_TEST_FORCE_PURE=ON ..
|
||||
- cmake --build .
|
||||
- ctest
|
||||
- cd ..
|
||||
- mkdir ./build_pure_11_debug
|
||||
- cd ./build_pure_11_debug
|
||||
- cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_BUILD_TYPE=Debug -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_CXX_11=ON -DGLM_TEST_FORCE_PURE=ON ..
|
||||
- cmake --build .
|
||||
- ctest
|
||||
- cd ..
|
||||
- mkdir ./build_pure_98_debug
|
||||
- cd ./build_pure_98_debug
|
||||
- cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_BUILD_TYPE=Debug -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_CXX_98=ON -DGLM_TEST_FORCE_PURE=ON ..
|
||||
- cmake --build .
|
||||
- ctest
|
||||
- cd ..
|
||||
- mkdir ./build_simd_11
|
||||
- cd ./build_simd_11
|
||||
- if [[ $TRAVIS_OS_NAME == 'osx' ]]; then cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_BUILD_TYPE=Release -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_CXX_11=ON -DGLM_TEST_ENABLE_SIMD_SSE3=ON ..; else cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_BUILD_TYPE=Release -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_CXX_11=ON -DGLM_TEST_ENABLE_SIMD_AVX=ON ..; fi
|
||||
- cmake --build .
|
||||
- ctest
|
||||
- cd ..
|
||||
- mkdir ./build_simd_98
|
||||
- cd ./build_simd_98
|
||||
- if [[ $TRAVIS_OS_NAME == 'osx' ]]; then cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_BUILD_TYPE=Release -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_CXX_98=ON -DGLM_TEST_ENABLE_SIMD_SSE3=ON ..; else cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_BUILD_TYPE=Release -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_CXX_98=ON -DGLM_TEST_ENABLE_SIMD_AVX=ON ..; fi
|
||||
- cmake --build .
|
||||
- ctest
|
||||
- cd ..
|
||||
- mkdir ./build_simd_11_debug
|
||||
- cd ./build_simd_11_debug
|
||||
- if [[ $TRAVIS_OS_NAME == 'osx' ]]; then cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_BUILD_TYPE=Debug -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_CXX_11=ON -DGLM_TEST_ENABLE_SIMD_SSE3=ON ..; else cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_BUILD_TYPE=Debug -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_CXX_11=ON -DGLM_TEST_ENABLE_SIMD_AVX=ON ..; fi
|
||||
- cmake --build .
|
||||
- ctest
|
||||
- cd ..
|
||||
- mkdir ./build_simd_98_debug
|
||||
- cd ./build_simd_98_debug
|
||||
- if [[ $TRAVIS_OS_NAME == 'osx' ]]; then cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_BUILD_TYPE=Debug -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_CXX_98=ON -DGLM_TEST_ENABLE_SIMD_SSE3=ON ..; else cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_BUILD_TYPE=Debug -DGLM_TEST_ENABLE=ON -DGLM_TEST_ENABLE_CXX_98=ON -DGLM_TEST_ENABLE_SIMD_AVX=ON ..; fi
|
||||
- cmake --build .
|
||||
- ctest
|
||||
- cd ..
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
file(GLOB ROOT_SOURCE *.cpp)
|
||||
file(GLOB ROOT_INLINE *.inl)
|
||||
file(GLOB ROOT_HEADER *.hpp)
|
||||
file(GLOB ROOT_TEXT ../*.txt)
|
||||
file(GLOB ROOT_MD ../*.md)
|
||||
file(GLOB ROOT_NAT ../util/glm.natvis)
|
||||
|
||||
file(GLOB_RECURSE CORE_SOURCE ./detail/*.cpp)
|
||||
file(GLOB_RECURSE CORE_INLINE ./detail/*.inl)
|
||||
file(GLOB_RECURSE CORE_HEADER ./detail/*.hpp)
|
||||
|
||||
file(GLOB_RECURSE GTC_SOURCE ./gtc/*.cpp)
|
||||
file(GLOB_RECURSE GTC_INLINE ./gtc/*.inl)
|
||||
file(GLOB_RECURSE GTC_HEADER ./gtc/*.hpp)
|
||||
|
||||
file(GLOB_RECURSE GTX_SOURCE ./gtx/*.cpp)
|
||||
file(GLOB_RECURSE GTX_INLINE ./gtx/*.inl)
|
||||
file(GLOB_RECURSE GTX_HEADER ./gtx/*.hpp)
|
||||
|
||||
file(GLOB_RECURSE SIMD_SOURCE ./simd/*.cpp)
|
||||
file(GLOB_RECURSE SIMD_INLINE ./simd/*.inl)
|
||||
file(GLOB_RECURSE SIMD_HEADER ./simd/*.h)
|
||||
|
||||
source_group("Text Files" FILES ${ROOT_TEXT} ${ROOT_MD})
|
||||
source_group("Core Files" FILES ${CORE_SOURCE})
|
||||
source_group("Core Files" FILES ${CORE_INLINE})
|
||||
source_group("Core Files" FILES ${CORE_HEADER})
|
||||
source_group("GTC Files" FILES ${GTC_SOURCE})
|
||||
source_group("GTC Files" FILES ${GTC_INLINE})
|
||||
source_group("GTC Files" FILES ${GTC_HEADER})
|
||||
source_group("GTX Files" FILES ${GTX_SOURCE})
|
||||
source_group("GTX Files" FILES ${GTX_INLINE})
|
||||
source_group("GTX Files" FILES ${GTX_HEADER})
|
||||
source_group("SIMD Files" FILES ${SIMD_SOURCE})
|
||||
source_group("SIMD Files" FILES ${SIMD_INLINE})
|
||||
source_group("SIMD Files" FILES ${SIMD_HEADER})
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
|
||||
|
||||
if(GLM_STATIC_LIBRARY_ENABLE OR GLM_DYNAMIC_LIBRARY_ENABLE)
|
||||
if(GLM_STATIC_LIBRARY_ENABLE)
|
||||
add_library(glm_static STATIC ${ROOT_TEXT} ${ROOT_MD} ${ROOT_NAT}
|
||||
${ROOT_SOURCE} ${ROOT_INLINE} ${ROOT_HEADER}
|
||||
${CORE_SOURCE} ${CORE_INLINE} ${CORE_HEADER}
|
||||
${GTC_SOURCE} ${GTC_INLINE} ${GTC_HEADER}
|
||||
${GTX_SOURCE} ${GTX_INLINE} ${GTX_HEADER}
|
||||
${SIMD_SOURCE} ${SIMD_INLINE} ${SIMD_HEADER})
|
||||
endif(GLM_STATIC_LIBRARY_ENABLE)
|
||||
|
||||
if(GLM_DYNAMIC_LIBRARY_ENABLE)
|
||||
add_library(glm_shared SHARED ${ROOT_TEXT} ${ROOT_MD} ${ROOT_NAT}
|
||||
${ROOT_SOURCE} ${ROOT_INLINE} ${ROOT_HEADER}
|
||||
${CORE_SOURCE} ${CORE_INLINE} ${CORE_HEADER}
|
||||
${GTC_SOURCE} ${GTC_INLINE} ${GTC_HEADER}
|
||||
${GTX_SOURCE} ${GTX_INLINE} ${GTX_HEADER}
|
||||
${SIMD_SOURCE} ${SIMD_INLINE} ${SIMD_HEADER})
|
||||
endif(GLM_DYNAMIC_LIBRARY_ENABLE)
|
||||
|
||||
else(GLM_STATIC_LIBRARY_ENABLE OR GLM_DYNAMIC_LIBRARY_ENABLE)
|
||||
add_executable(glm_dummy ${ROOT_TEXT} ${ROOT_MD} ${ROOT_NAT}
|
||||
${ROOT_SOURCE} ${ROOT_INLINE} ${ROOT_HEADER}
|
||||
${CORE_SOURCE} ${CORE_INLINE} ${CORE_HEADER}
|
||||
${GTC_SOURCE} ${GTC_INLINE} ${GTC_HEADER}
|
||||
${GTX_SOURCE} ${GTX_INLINE} ${GTX_HEADER}
|
||||
${SIMD_SOURCE} ${SIMD_INLINE} ${SIMD_HEADER})
|
||||
|
||||
endif(GLM_STATIC_LIBRARY_ENABLE OR GLM_DYNAMIC_LIBRARY_ENABLE)
|
||||
@@ -0,0 +1,8 @@
|
||||
/// @ref core
|
||||
/// @file glm/common.hpp
|
||||
|
||||
#include "detail/setup.hpp"
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "detail/func_common.hpp"
|
||||
@@ -0,0 +1,397 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/_features.hpp
|
||||
|
||||
#pragma once
|
||||
|
||||
// #define GLM_CXX98_EXCEPTIONS
|
||||
// #define GLM_CXX98_RTTI
|
||||
|
||||
// #define GLM_CXX11_RVALUE_REFERENCES
|
||||
// Rvalue references - GCC 4.3
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2118.html
|
||||
|
||||
// GLM_CXX11_TRAILING_RETURN
|
||||
// Rvalue references for *this - GCC not supported
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2439.htm
|
||||
|
||||
// GLM_CXX11_NONSTATIC_MEMBER_INIT
|
||||
// Initialization of class objects by rvalues - GCC any
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1610.html
|
||||
|
||||
// GLM_CXX11_NONSTATIC_MEMBER_INIT
|
||||
// Non-static data member initializers - GCC 4.7
|
||||
// http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2756.htm
|
||||
|
||||
// #define GLM_CXX11_VARIADIC_TEMPLATE
|
||||
// Variadic templates - GCC 4.3
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2242.pdf
|
||||
|
||||
//
|
||||
// Extending variadic template template parameters - GCC 4.4
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2555.pdf
|
||||
|
||||
// #define GLM_CXX11_GENERALIZED_INITIALIZERS
|
||||
// Initializer lists - GCC 4.4
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2672.htm
|
||||
|
||||
// #define GLM_CXX11_STATIC_ASSERT
|
||||
// Static assertions - GCC 4.3
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1720.html
|
||||
|
||||
// #define GLM_CXX11_AUTO_TYPE
|
||||
// auto-typed variables - GCC 4.4
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1984.pdf
|
||||
|
||||
// #define GLM_CXX11_AUTO_TYPE
|
||||
// Multi-declarator auto - GCC 4.4
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1737.pdf
|
||||
|
||||
// #define GLM_CXX11_AUTO_TYPE
|
||||
// Removal of auto as a storage-class specifier - GCC 4.4
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2546.htm
|
||||
|
||||
// #define GLM_CXX11_AUTO_TYPE
|
||||
// New function declarator syntax - GCC 4.4
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2541.htm
|
||||
|
||||
// #define GLM_CXX11_LAMBDAS
|
||||
// New wording for C++0x lambdas - GCC 4.5
|
||||
// http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2009/n2927.pdf
|
||||
|
||||
// #define GLM_CXX11_DECLTYPE
|
||||
// Declared type of an expression - GCC 4.3
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2343.pdf
|
||||
|
||||
//
|
||||
// Right angle brackets - GCC 4.3
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1757.html
|
||||
|
||||
//
|
||||
// Default template arguments for function templates DR226 GCC 4.3
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#226
|
||||
|
||||
//
|
||||
// Solving the SFINAE problem for expressions DR339 GCC 4.4
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2634.html
|
||||
|
||||
// #define GLM_CXX11_ALIAS_TEMPLATE
|
||||
// Template aliases N2258 GCC 4.7
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2258.pdf
|
||||
|
||||
//
|
||||
// Extern templates N1987 Yes
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1987.htm
|
||||
|
||||
// #define GLM_CXX11_NULLPTR
|
||||
// Null pointer constant N2431 GCC 4.6
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2431.pdf
|
||||
|
||||
// #define GLM_CXX11_STRONG_ENUMS
|
||||
// Strongly-typed enums N2347 GCC 4.4
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2347.pdf
|
||||
|
||||
//
|
||||
// Forward declarations for enums N2764 GCC 4.6
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf
|
||||
|
||||
//
|
||||
// Generalized attributes N2761 GCC 4.8
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2761.pdf
|
||||
|
||||
//
|
||||
// Generalized constant expressions N2235 GCC 4.6
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2235.pdf
|
||||
|
||||
//
|
||||
// Alignment support N2341 GCC 4.8
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2341.pdf
|
||||
|
||||
// #define GLM_CXX11_DELEGATING_CONSTRUCTORS
|
||||
// Delegating constructors N1986 GCC 4.7
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1986.pdf
|
||||
|
||||
//
|
||||
// Inheriting constructors N2540 GCC 4.8
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2540.htm
|
||||
|
||||
// #define GLM_CXX11_EXPLICIT_CONVERSIONS
|
||||
// Explicit conversion operators N2437 GCC 4.5
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2437.pdf
|
||||
|
||||
//
|
||||
// New character types N2249 GCC 4.4
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2249.html
|
||||
|
||||
//
|
||||
// Unicode string literals N2442 GCC 4.5
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2442.htm
|
||||
|
||||
//
|
||||
// Raw string literals N2442 GCC 4.5
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2442.htm
|
||||
|
||||
//
|
||||
// Universal character name literals N2170 GCC 4.5
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2170.html
|
||||
|
||||
// #define GLM_CXX11_USER_LITERALS
|
||||
// User-defined literals N2765 GCC 4.7
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2765.pdf
|
||||
|
||||
//
|
||||
// Standard Layout Types N2342 GCC 4.5
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2342.htm
|
||||
|
||||
// #define GLM_CXX11_DEFAULTED_FUNCTIONS
|
||||
// #define GLM_CXX11_DELETED_FUNCTIONS
|
||||
// Defaulted and deleted functions N2346 GCC 4.4
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2346.htm
|
||||
|
||||
//
|
||||
// Extended friend declarations N1791 GCC 4.7
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1791.pdf
|
||||
|
||||
//
|
||||
// Extending sizeof N2253 GCC 4.4
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2253.html
|
||||
|
||||
// #define GLM_CXX11_INLINE_NAMESPACES
|
||||
// Inline namespaces N2535 GCC 4.4
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2535.htm
|
||||
|
||||
// #define GLM_CXX11_UNRESTRICTED_UNIONS
|
||||
// Unrestricted unions N2544 GCC 4.6
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf
|
||||
|
||||
// #define GLM_CXX11_LOCAL_TYPE_TEMPLATE_ARGS
|
||||
// Local and unnamed types as template arguments N2657 GCC 4.5
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2657.htm
|
||||
|
||||
// #define GLM_CXX11_RANGE_FOR
|
||||
// Range-based for N2930 GCC 4.6
|
||||
// http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2009/n2930.html
|
||||
|
||||
// #define GLM_CXX11_OVERRIDE_CONTROL
|
||||
// Explicit virtual overrides N2928 N3206 N3272 GCC 4.7
|
||||
// http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2009/n2928.htm
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3206.htm
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3272.htm
|
||||
|
||||
//
|
||||
// Minimal support for garbage collection and reachability-based leak detection N2670 No
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2670.htm
|
||||
|
||||
// #define GLM_CXX11_NOEXCEPT
|
||||
// Allowing move constructors to throw [noexcept] N3050 GCC 4.6 (core language only)
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3050.html
|
||||
|
||||
//
|
||||
// Defining move special member functions N3053 GCC 4.6
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3053.html
|
||||
|
||||
//
|
||||
// Sequence points N2239 Yes
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2239.html
|
||||
|
||||
//
|
||||
// Atomic operations N2427 GCC 4.4
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2239.html
|
||||
|
||||
//
|
||||
// Strong Compare and Exchange N2748 GCC 4.5
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2427.html
|
||||
|
||||
//
|
||||
// Bidirectional Fences N2752 GCC 4.8
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2752.htm
|
||||
|
||||
//
|
||||
// Memory model N2429 GCC 4.8
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2429.htm
|
||||
|
||||
//
|
||||
// Data-dependency ordering: atomics and memory model N2664 GCC 4.4
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2664.htm
|
||||
|
||||
//
|
||||
// Propagating exceptions N2179 GCC 4.4
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2179.html
|
||||
|
||||
//
|
||||
// Abandoning a process and at_quick_exit N2440 GCC 4.8
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2440.htm
|
||||
|
||||
//
|
||||
// Allow atomics use in signal handlers N2547 Yes
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2547.htm
|
||||
|
||||
//
|
||||
// Thread-local storage N2659 GCC 4.8
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2659.htm
|
||||
|
||||
//
|
||||
// Dynamic initialization and destruction with concurrency N2660 GCC 4.3
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm
|
||||
|
||||
//
|
||||
// __func__ predefined identifier N2340 GCC 4.3
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2340.htm
|
||||
|
||||
//
|
||||
// C99 preprocessor N1653 GCC 4.3
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1653.htm
|
||||
|
||||
//
|
||||
// long long N1811 GCC 4.3
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1811.pdf
|
||||
|
||||
//
|
||||
// Extended integral types N1988 Yes
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1988.pdf
|
||||
|
||||
#if(GLM_COMPILER & GLM_COMPILER_GCC)
|
||||
|
||||
# define GLM_CXX11_STATIC_ASSERT
|
||||
|
||||
#elif(GLM_COMPILER & GLM_COMPILER_CLANG)
|
||||
# if(__has_feature(cxx_exceptions))
|
||||
# define GLM_CXX98_EXCEPTIONS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_rtti))
|
||||
# define GLM_CXX98_RTTI
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_access_control_sfinae))
|
||||
# define GLM_CXX11_ACCESS_CONTROL_SFINAE
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_alias_templates))
|
||||
# define GLM_CXX11_ALIAS_TEMPLATE
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_alignas))
|
||||
# define GLM_CXX11_ALIGNAS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_attributes))
|
||||
# define GLM_CXX11_ATTRIBUTES
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_constexpr))
|
||||
# define GLM_CXX11_CONSTEXPR
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_decltype))
|
||||
# define GLM_CXX11_DECLTYPE
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_default_function_template_args))
|
||||
# define GLM_CXX11_DEFAULT_FUNCTION_TEMPLATE_ARGS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_defaulted_functions))
|
||||
# define GLM_CXX11_DEFAULTED_FUNCTIONS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_delegating_constructors))
|
||||
# define GLM_CXX11_DELEGATING_CONSTRUCTORS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_deleted_functions))
|
||||
# define GLM_CXX11_DELETED_FUNCTIONS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_explicit_conversions))
|
||||
# define GLM_CXX11_EXPLICIT_CONVERSIONS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_generalized_initializers))
|
||||
# define GLM_CXX11_GENERALIZED_INITIALIZERS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_implicit_moves))
|
||||
# define GLM_CXX11_IMPLICIT_MOVES
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_inheriting_constructors))
|
||||
# define GLM_CXX11_INHERITING_CONSTRUCTORS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_inline_namespaces))
|
||||
# define GLM_CXX11_INLINE_NAMESPACES
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_lambdas))
|
||||
# define GLM_CXX11_LAMBDAS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_local_type_template_args))
|
||||
# define GLM_CXX11_LOCAL_TYPE_TEMPLATE_ARGS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_noexcept))
|
||||
# define GLM_CXX11_NOEXCEPT
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_nonstatic_member_init))
|
||||
# define GLM_CXX11_NONSTATIC_MEMBER_INIT
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_nullptr))
|
||||
# define GLM_CXX11_NULLPTR
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_override_control))
|
||||
# define GLM_CXX11_OVERRIDE_CONTROL
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_reference_qualified_functions))
|
||||
# define GLM_CXX11_REFERENCE_QUALIFIED_FUNCTIONS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_range_for))
|
||||
# define GLM_CXX11_RANGE_FOR
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_raw_string_literals))
|
||||
# define GLM_CXX11_RAW_STRING_LITERALS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_rvalue_references))
|
||||
# define GLM_CXX11_RVALUE_REFERENCES
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_static_assert))
|
||||
# define GLM_CXX11_STATIC_ASSERT
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_auto_type))
|
||||
# define GLM_CXX11_AUTO_TYPE
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_strong_enums))
|
||||
# define GLM_CXX11_STRONG_ENUMS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_trailing_return))
|
||||
# define GLM_CXX11_TRAILING_RETURN
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_unicode_literals))
|
||||
# define GLM_CXX11_UNICODE_LITERALS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_unrestricted_unions))
|
||||
# define GLM_CXX11_UNRESTRICTED_UNIONS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_user_literals))
|
||||
# define GLM_CXX11_USER_LITERALS
|
||||
# endif
|
||||
|
||||
# if(__has_feature(cxx_variadic_templates))
|
||||
# define GLM_CXX11_VARIADIC_TEMPLATES
|
||||
# endif
|
||||
|
||||
#endif//(GLM_COMPILER & GLM_COMPILER_CLANG)
|
||||
@@ -0,0 +1,30 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/_fixes.hpp
|
||||
|
||||
#include <cmath>
|
||||
|
||||
//! Workaround for compatibility with other libraries
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
//! Workaround for compatibility with other libraries
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
//! Workaround for Android
|
||||
#ifdef isnan
|
||||
#undef isnan
|
||||
#endif
|
||||
|
||||
//! Workaround for Android
|
||||
#ifdef isinf
|
||||
#undef isinf
|
||||
#endif
|
||||
|
||||
//! Workaround for Chrone Native Client
|
||||
#ifdef log2
|
||||
#undef log2
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/_noise.hpp
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../vec2.hpp"
|
||||
#include "../vec3.hpp"
|
||||
#include "../vec4.hpp"
|
||||
#include "../common.hpp"
|
||||
|
||||
namespace glm{
|
||||
namespace detail
|
||||
{
|
||||
template<typename T>
|
||||
GLM_FUNC_QUALIFIER T mod289(T const & x)
|
||||
{
|
||||
return x - floor(x * (static_cast<T>(1.0) / static_cast<T>(289.0))) * static_cast<T>(289.0);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
GLM_FUNC_QUALIFIER T permute(T const & x)
|
||||
{
|
||||
return mod289(((x * static_cast<T>(34)) + static_cast<T>(1)) * x);
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<2, T, P> permute(vec<2, T, P> const & x)
|
||||
{
|
||||
return mod289(((x * static_cast<T>(34)) + static_cast<T>(1)) * x);
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<3, T, P> permute(vec<3, T, P> const & x)
|
||||
{
|
||||
return mod289(((x * static_cast<T>(34)) + static_cast<T>(1)) * x);
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<4, T, P> permute(vec<4, T, P> const & x)
|
||||
{
|
||||
return mod289(((x * static_cast<T>(34)) + static_cast<T>(1)) * x);
|
||||
}
|
||||
/*
|
||||
template<typename T, precision P, template<typename> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> permute(vecType<L, T, P> const & x)
|
||||
{
|
||||
return mod289(((x * T(34)) + T(1)) * x);
|
||||
}
|
||||
*/
|
||||
template<typename T>
|
||||
GLM_FUNC_QUALIFIER T taylorInvSqrt(T const & r)
|
||||
{
|
||||
return T(1.79284291400159) - T(0.85373472095314) * r;
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<2, T, P> taylorInvSqrt(vec<2, T, P> const & r)
|
||||
{
|
||||
return T(1.79284291400159) - T(0.85373472095314) * r;
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<3, T, P> taylorInvSqrt(vec<3, T, P> const & r)
|
||||
{
|
||||
return T(1.79284291400159) - T(0.85373472095314) * r;
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<4, T, P> taylorInvSqrt(vec<4, T, P> const & r)
|
||||
{
|
||||
return T(1.79284291400159) - T(0.85373472095314) * r;
|
||||
}
|
||||
/*
|
||||
template<typename T, precision P, template<typename> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> taylorInvSqrt(vecType<L, T, P> const & r)
|
||||
{
|
||||
return T(1.79284291400159) - T(0.85373472095314) * r;
|
||||
}
|
||||
*/
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<2, T, P> fade(vec<2, T, P> const & t)
|
||||
{
|
||||
return (t * t * t) * (t * (t * T(6) - T(15)) + T(10));
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<3, T, P> fade(vec<3, T, P> const & t)
|
||||
{
|
||||
return (t * t * t) * (t * (t * T(6) - T(15)) + T(10));
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<4, T, P> fade(vec<4, T, P> const & t)
|
||||
{
|
||||
return (t * t * t) * (t * (t * T(6) - T(15)) + T(10));
|
||||
}
|
||||
/*
|
||||
template<typename T, precision P, template<typename> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> fade(vecType<L, T, P> const & t)
|
||||
{
|
||||
return (t * t * t) * (t * (t * T(6) - T(15)) + T(10));
|
||||
}
|
||||
*/
|
||||
}//namespace detail
|
||||
}//namespace glm
|
||||
|
||||
@@ -0,0 +1,797 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/_swizzle.hpp
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace glm{
|
||||
namespace detail
|
||||
{
|
||||
// Internal class for implementing swizzle operators
|
||||
template<typename T, int N>
|
||||
struct _swizzle_base0
|
||||
{
|
||||
protected:
|
||||
GLM_FUNC_QUALIFIER T& elem(size_t i){ return (reinterpret_cast<T*>(_buffer))[i]; }
|
||||
GLM_FUNC_QUALIFIER T const& elem(size_t i) const{ return (reinterpret_cast<const T*>(_buffer))[i]; }
|
||||
|
||||
// Use an opaque buffer to *ensure* the compiler doesn't call a constructor.
|
||||
// The size 1 buffer is assumed to aligned to the actual members so that the
|
||||
// elem()
|
||||
char _buffer[1];
|
||||
};
|
||||
|
||||
template<int N, typename T, precision P, int E0, int E1, int E2, int E3, bool Aligned>
|
||||
struct _swizzle_base1 : public _swizzle_base0<T, N>
|
||||
{
|
||||
};
|
||||
|
||||
template<typename T, precision P, int E0, int E1, bool Aligned>
|
||||
struct _swizzle_base1<2, T, P, E0,E1,-1,-2, Aligned> : public _swizzle_base0<T, 2>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER vec<2, T, P> operator ()() const { return vec<2, T, P>(this->elem(E0), this->elem(E1)); }
|
||||
};
|
||||
|
||||
template<typename T, precision P, int E0, int E1, int E2, bool Aligned>
|
||||
struct _swizzle_base1<3, T, P, E0,E1,E2,-1, Aligned> : public _swizzle_base0<T, 3>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER vec<3, T, P> operator ()() const { return vec<3, T, P>(this->elem(E0), this->elem(E1), this->elem(E2)); }
|
||||
};
|
||||
|
||||
template<typename T, precision P, int E0, int E1, int E2, int E3, bool Aligned>
|
||||
struct _swizzle_base1<4, T, P, E0,E1,E2,E3, Aligned> : public _swizzle_base0<T, 4>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER vec<4, T, P> operator ()() const { return vec<4, T, P>(this->elem(E0), this->elem(E1), this->elem(E2), this->elem(E3)); }
|
||||
};
|
||||
|
||||
// Internal class for implementing swizzle operators
|
||||
/*
|
||||
Template parameters:
|
||||
|
||||
ValueType = type of scalar values (e.g. float, double)
|
||||
VecType = class the swizzle is applies to (e.g. vec<3, float>)
|
||||
N = number of components in the vector (e.g. 3)
|
||||
E0...3 = what index the n-th element of this swizzle refers to in the unswizzled vec
|
||||
|
||||
DUPLICATE_ELEMENTS = 1 if there is a repeated element, 0 otherwise (used to specialize swizzles
|
||||
containing duplicate elements so that they cannot be used as r-values).
|
||||
*/
|
||||
template<int N, typename T, precision P, int E0, int E1, int E2, int E3, int DUPLICATE_ELEMENTS>
|
||||
struct _swizzle_base2 : public _swizzle_base1<N, T, P, E0,E1,E2,E3, detail::is_aligned<P>::value>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER _swizzle_base2& operator= (const T& t)
|
||||
{
|
||||
for (int i = 0; i < N; ++i)
|
||||
(*this)[i] = t;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER _swizzle_base2& operator= (vec<N, T, P> const& that)
|
||||
{
|
||||
struct op {
|
||||
GLM_FUNC_QUALIFIER void operator() (T& e, T& t) { e = t; }
|
||||
};
|
||||
_apply_op(that, op());
|
||||
return *this;
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER void operator -= (vec<N, T, P> const& that)
|
||||
{
|
||||
struct op {
|
||||
GLM_FUNC_QUALIFIER void operator() (T& e, T& t) { e -= t; }
|
||||
};
|
||||
_apply_op(that, op());
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER void operator += (vec<N, T, P> const& that)
|
||||
{
|
||||
struct op {
|
||||
GLM_FUNC_QUALIFIER void operator() (T& e, T& t) { e += t; }
|
||||
};
|
||||
_apply_op(that, op());
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER void operator *= (vec<N, T, P> const& that)
|
||||
{
|
||||
struct op {
|
||||
GLM_FUNC_QUALIFIER void operator() (T& e, T& t) { e *= t; }
|
||||
};
|
||||
_apply_op(that, op());
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER void operator /= (vec<N, T, P> const& that)
|
||||
{
|
||||
struct op {
|
||||
GLM_FUNC_QUALIFIER void operator() (T& e, T& t) { e /= t; }
|
||||
};
|
||||
_apply_op(that, op());
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER T& operator[](size_t i)
|
||||
{
|
||||
const int offset_dst[4] = { E0, E1, E2, E3 };
|
||||
return this->elem(offset_dst[i]);
|
||||
}
|
||||
GLM_FUNC_QUALIFIER T operator[](size_t i) const
|
||||
{
|
||||
const int offset_dst[4] = { E0, E1, E2, E3 };
|
||||
return this->elem(offset_dst[i]);
|
||||
}
|
||||
|
||||
protected:
|
||||
template<typename U>
|
||||
GLM_FUNC_QUALIFIER void _apply_op(vec<N, T, P> const& that, U op)
|
||||
{
|
||||
// Make a copy of the data in this == &that.
|
||||
// The copier should optimize out the copy in cases where the function is
|
||||
// properly inlined and the copy is not necessary.
|
||||
T t[N];
|
||||
for (int i = 0; i < N; ++i)
|
||||
t[i] = that[i];
|
||||
for (int i = 0; i < N; ++i)
|
||||
op( (*this)[i], t[i] );
|
||||
}
|
||||
};
|
||||
|
||||
// Specialization for swizzles containing duplicate elements. These cannot be modified.
|
||||
template<int N, typename T, precision P, int E0, int E1, int E2, int E3>
|
||||
struct _swizzle_base2<N, T, P, E0,E1,E2,E3, 1> : public _swizzle_base1<N, T, P, E0,E1,E2,E3, detail::is_aligned<P>::value>
|
||||
{
|
||||
struct Stub {};
|
||||
|
||||
GLM_FUNC_QUALIFIER _swizzle_base2& operator= (Stub const &) { return *this; }
|
||||
|
||||
GLM_FUNC_QUALIFIER T operator[] (size_t i) const
|
||||
{
|
||||
const int offset_dst[4] = { E0, E1, E2, E3 };
|
||||
return this->elem(offset_dst[i]);
|
||||
}
|
||||
};
|
||||
|
||||
template<int N, typename T, precision P, int E0, int E1, int E2, int E3>
|
||||
struct _swizzle : public _swizzle_base2<N, T, P, E0, E1, E2, E3, (E0 == E1 || E0 == E2 || E0 == E3 || E1 == E2 || E1 == E3 || E2 == E3)>
|
||||
{
|
||||
typedef _swizzle_base2<N, T, P, E0, E1, E2, E3, (E0 == E1 || E0 == E2 || E0 == E3 || E1 == E2 || E1 == E3 || E2 == E3)> base_type;
|
||||
|
||||
using base_type::operator=;
|
||||
|
||||
GLM_FUNC_QUALIFIER operator vec<N, T, P> () const { return (*this)(); }
|
||||
};
|
||||
|
||||
//
|
||||
// To prevent the C++ syntax from getting entirely overwhelming, define some alias macros
|
||||
//
|
||||
#define _GLM_SWIZZLE_TEMPLATE1 template<int N, typename T, precision P, int E0, int E1, int E2, int E3>
|
||||
#define _GLM_SWIZZLE_TEMPLATE2 template<int N, typename T, precision P, int E0, int E1, int E2, int E3, int F0, int F1, int F2, int F3>
|
||||
#define _GLM_SWIZZLE_TYPE1 _swizzle<N, T, P, E0, E1, E2, E3>
|
||||
#define _GLM_SWIZZLE_TYPE2 _swizzle<N, T, P, F0, F1, F2, F3>
|
||||
|
||||
//
|
||||
// Wrapper for a binary operator (e.g. u.yy + v.zy)
|
||||
//
|
||||
#define _GLM_SWIZZLE_VECTOR_BINARY_OPERATOR_IMPLEMENTATION(OPERAND) \
|
||||
_GLM_SWIZZLE_TEMPLATE2 \
|
||||
GLM_FUNC_QUALIFIER vec<N, T, P> operator OPERAND ( const _GLM_SWIZZLE_TYPE1& a, const _GLM_SWIZZLE_TYPE2& b) \
|
||||
{ \
|
||||
return a() OPERAND b(); \
|
||||
} \
|
||||
_GLM_SWIZZLE_TEMPLATE1 \
|
||||
GLM_FUNC_QUALIFIER vec<N, T, P> operator OPERAND ( const _GLM_SWIZZLE_TYPE1& a, const vec<N, T, P>& b) \
|
||||
{ \
|
||||
return a() OPERAND b; \
|
||||
} \
|
||||
_GLM_SWIZZLE_TEMPLATE1 \
|
||||
GLM_FUNC_QUALIFIER vec<N, T, P> operator OPERAND ( const vec<N, T, P>& a, const _GLM_SWIZZLE_TYPE1& b) \
|
||||
{ \
|
||||
return a OPERAND b(); \
|
||||
}
|
||||
|
||||
//
|
||||
// Wrapper for a operand between a swizzle and a binary (e.g. 1.0f - u.xyz)
|
||||
//
|
||||
#define _GLM_SWIZZLE_SCALAR_BINARY_OPERATOR_IMPLEMENTATION(OPERAND) \
|
||||
_GLM_SWIZZLE_TEMPLATE1 \
|
||||
GLM_FUNC_QUALIFIER vec<N, T, P> operator OPERAND ( const _GLM_SWIZZLE_TYPE1& a, const T& b) \
|
||||
{ \
|
||||
return a() OPERAND b; \
|
||||
} \
|
||||
_GLM_SWIZZLE_TEMPLATE1 \
|
||||
GLM_FUNC_QUALIFIER vec<N, T, P> operator OPERAND ( const T& a, const _GLM_SWIZZLE_TYPE1& b) \
|
||||
{ \
|
||||
return a OPERAND b(); \
|
||||
}
|
||||
|
||||
//
|
||||
// Macro for wrapping a function taking one argument (e.g. abs())
|
||||
//
|
||||
#define _GLM_SWIZZLE_FUNCTION_1_ARGS(RETURN_TYPE,FUNCTION) \
|
||||
_GLM_SWIZZLE_TEMPLATE1 \
|
||||
GLM_FUNC_QUALIFIER typename _GLM_SWIZZLE_TYPE1::RETURN_TYPE FUNCTION(const _GLM_SWIZZLE_TYPE1& a) \
|
||||
{ \
|
||||
return FUNCTION(a()); \
|
||||
}
|
||||
|
||||
//
|
||||
// Macro for wrapping a function taking two vector arguments (e.g. dot()).
|
||||
//
|
||||
#define _GLM_SWIZZLE_FUNCTION_2_ARGS(RETURN_TYPE,FUNCTION) \
|
||||
_GLM_SWIZZLE_TEMPLATE2 \
|
||||
GLM_FUNC_QUALIFIER typename _GLM_SWIZZLE_TYPE1::RETURN_TYPE FUNCTION(const _GLM_SWIZZLE_TYPE1& a, const _GLM_SWIZZLE_TYPE2& b) \
|
||||
{ \
|
||||
return FUNCTION(a(), b()); \
|
||||
} \
|
||||
_GLM_SWIZZLE_TEMPLATE1 \
|
||||
GLM_FUNC_QUALIFIER typename _GLM_SWIZZLE_TYPE1::RETURN_TYPE FUNCTION(const _GLM_SWIZZLE_TYPE1& a, const _GLM_SWIZZLE_TYPE1& b) \
|
||||
{ \
|
||||
return FUNCTION(a(), b()); \
|
||||
} \
|
||||
_GLM_SWIZZLE_TEMPLATE1 \
|
||||
GLM_FUNC_QUALIFIER typename _GLM_SWIZZLE_TYPE1::RETURN_TYPE FUNCTION(const _GLM_SWIZZLE_TYPE1& a, const typename V& b) \
|
||||
{ \
|
||||
return FUNCTION(a(), b); \
|
||||
} \
|
||||
_GLM_SWIZZLE_TEMPLATE1 \
|
||||
GLM_FUNC_QUALIFIER typename _GLM_SWIZZLE_TYPE1::RETURN_TYPE FUNCTION(const V& a, const _GLM_SWIZZLE_TYPE1& b) \
|
||||
{ \
|
||||
return FUNCTION(a, b()); \
|
||||
}
|
||||
|
||||
//
|
||||
// Macro for wrapping a function take 2 vec arguments followed by a scalar (e.g. mix()).
|
||||
//
|
||||
#define _GLM_SWIZZLE_FUNCTION_2_ARGS_SCALAR(RETURN_TYPE,FUNCTION) \
|
||||
_GLM_SWIZZLE_TEMPLATE2 \
|
||||
GLM_FUNC_QUALIFIER typename _GLM_SWIZZLE_TYPE1::RETURN_TYPE FUNCTION(const _GLM_SWIZZLE_TYPE1& a, const _GLM_SWIZZLE_TYPE2& b, const T& c) \
|
||||
{ \
|
||||
return FUNCTION(a(), b(), c); \
|
||||
} \
|
||||
_GLM_SWIZZLE_TEMPLATE1 \
|
||||
GLM_FUNC_QUALIFIER typename _GLM_SWIZZLE_TYPE1::RETURN_TYPE FUNCTION(const _GLM_SWIZZLE_TYPE1& a, const _GLM_SWIZZLE_TYPE1& b, const T& c) \
|
||||
{ \
|
||||
return FUNCTION(a(), b(), c); \
|
||||
} \
|
||||
_GLM_SWIZZLE_TEMPLATE1 \
|
||||
GLM_FUNC_QUALIFIER typename _GLM_SWIZZLE_TYPE1::RETURN_TYPE FUNCTION(const _GLM_SWIZZLE_TYPE1& a, const typename S0::vec_type& b, const T& c)\
|
||||
{ \
|
||||
return FUNCTION(a(), b, c); \
|
||||
} \
|
||||
_GLM_SWIZZLE_TEMPLATE1 \
|
||||
GLM_FUNC_QUALIFIER typename _GLM_SWIZZLE_TYPE1::RETURN_TYPE FUNCTION(const typename V& a, const _GLM_SWIZZLE_TYPE1& b, const T& c) \
|
||||
{ \
|
||||
return FUNCTION(a, b(), c); \
|
||||
}
|
||||
|
||||
}//namespace detail
|
||||
}//namespace glm
|
||||
|
||||
namespace glm
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
_GLM_SWIZZLE_SCALAR_BINARY_OPERATOR_IMPLEMENTATION(-)
|
||||
_GLM_SWIZZLE_SCALAR_BINARY_OPERATOR_IMPLEMENTATION(*)
|
||||
_GLM_SWIZZLE_VECTOR_BINARY_OPERATOR_IMPLEMENTATION(+)
|
||||
_GLM_SWIZZLE_VECTOR_BINARY_OPERATOR_IMPLEMENTATION(-)
|
||||
_GLM_SWIZZLE_VECTOR_BINARY_OPERATOR_IMPLEMENTATION(*)
|
||||
_GLM_SWIZZLE_VECTOR_BINARY_OPERATOR_IMPLEMENTATION(/)
|
||||
}
|
||||
|
||||
//
|
||||
// Swizzles are distinct types from the unswizzled type. The below macros will
|
||||
// provide template specializations for the swizzle types for the given functions
|
||||
// so that the compiler does not have any ambiguity to choosing how to handle
|
||||
// the function.
|
||||
//
|
||||
// The alternative is to use the operator()() when calling the function in order
|
||||
// to explicitly convert the swizzled type to the unswizzled type.
|
||||
//
|
||||
|
||||
//_GLM_SWIZZLE_FUNCTION_1_ARGS(vec_type, abs);
|
||||
//_GLM_SWIZZLE_FUNCTION_1_ARGS(vec_type, acos);
|
||||
//_GLM_SWIZZLE_FUNCTION_1_ARGS(vec_type, acosh);
|
||||
//_GLM_SWIZZLE_FUNCTION_1_ARGS(vec_type, all);
|
||||
//_GLM_SWIZZLE_FUNCTION_1_ARGS(vec_type, any);
|
||||
|
||||
//_GLM_SWIZZLE_FUNCTION_2_ARGS(value_type, dot);
|
||||
//_GLM_SWIZZLE_FUNCTION_2_ARGS(vec_type, cross);
|
||||
//_GLM_SWIZZLE_FUNCTION_2_ARGS(vec_type, step);
|
||||
//_GLM_SWIZZLE_FUNCTION_2_ARGS_SCALAR(vec_type, mix);
|
||||
}
|
||||
|
||||
#define _GLM_SWIZZLE2_2_MEMBERS(T, P, E0,E1) \
|
||||
struct { detail::_swizzle<2, T, P, 0,0,-1,-2> E0 ## E0; }; \
|
||||
struct { detail::_swizzle<2, T, P, 0,1,-1,-2> E0 ## E1; }; \
|
||||
struct { detail::_swizzle<2, T, P, 1,0,-1,-2> E1 ## E0; }; \
|
||||
struct { detail::_swizzle<2, T, P, 1,1,-1,-2> E1 ## E1; };
|
||||
|
||||
#define _GLM_SWIZZLE2_3_MEMBERS(T, P, E0,E1) \
|
||||
struct { detail::_swizzle<3,T, P, 0,0,0,-1> E0 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<3,T, P, 0,0,1,-1> E0 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<3,T, P, 0,1,0,-1> E0 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<3,T, P, 0,1,1,-1> E0 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<3,T, P, 1,0,0,-1> E1 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<3,T, P, 1,0,1,-1> E1 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<3,T, P, 1,1,0,-1> E1 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<3,T, P, 1,1,1,-1> E1 ## E1 ## E1; };
|
||||
|
||||
#define _GLM_SWIZZLE2_4_MEMBERS(T, P, E0,E1) \
|
||||
struct { detail::_swizzle<4,T, P, 0,0,0,0> E0 ## E0 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,0,0,1> E0 ## E0 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,0,1,0> E0 ## E0 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,0,1,1> E0 ## E0 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,1,0,0> E0 ## E1 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,1,0,1> E0 ## E1 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,1,1,0> E0 ## E1 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,1,1,1> E0 ## E1 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,0,0,0> E1 ## E0 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,0,0,1> E1 ## E0 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,0,1,0> E1 ## E0 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,0,1,1> E1 ## E0 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,1,0,0> E1 ## E1 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,1,0,1> E1 ## E1 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,1,1,0> E1 ## E1 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,1,1,1> E1 ## E1 ## E1 ## E1; };
|
||||
|
||||
#define _GLM_SWIZZLE3_2_MEMBERS(T, P, E0,E1,E2) \
|
||||
struct { detail::_swizzle<2,T, P, 0,0,-1,-2> E0 ## E0; }; \
|
||||
struct { detail::_swizzle<2,T, P, 0,1,-1,-2> E0 ## E1; }; \
|
||||
struct { detail::_swizzle<2,T, P, 0,2,-1,-2> E0 ## E2; }; \
|
||||
struct { detail::_swizzle<2,T, P, 1,0,-1,-2> E1 ## E0; }; \
|
||||
struct { detail::_swizzle<2,T, P, 1,1,-1,-2> E1 ## E1; }; \
|
||||
struct { detail::_swizzle<2,T, P, 1,2,-1,-2> E1 ## E2; }; \
|
||||
struct { detail::_swizzle<2,T, P, 2,0,-1,-2> E2 ## E0; }; \
|
||||
struct { detail::_swizzle<2,T, P, 2,1,-1,-2> E2 ## E1; }; \
|
||||
struct { detail::_swizzle<2,T, P, 2,2,-1,-2> E2 ## E2; };
|
||||
|
||||
#define _GLM_SWIZZLE3_3_MEMBERS(T, P ,E0,E1,E2) \
|
||||
struct { detail::_swizzle<3, T, P, 0,0,0,-1> E0 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,0,1,-1> E0 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,0,2,-1> E0 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,1,0,-1> E0 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,1,1,-1> E0 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,1,2,-1> E0 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,2,0,-1> E0 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,2,1,-1> E0 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,2,2,-1> E0 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,0,0,-1> E1 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,0,1,-1> E1 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,0,2,-1> E1 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,1,0,-1> E1 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,1,1,-1> E1 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,1,2,-1> E1 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,2,0,-1> E1 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,2,1,-1> E1 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,2,2,-1> E1 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,0,0,-1> E2 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,0,1,-1> E2 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,0,2,-1> E2 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,1,0,-1> E2 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,1,1,-1> E2 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,1,2,-1> E2 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,2,0,-1> E2 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,2,1,-1> E2 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,2,2,-1> E2 ## E2 ## E2; };
|
||||
|
||||
#define _GLM_SWIZZLE3_4_MEMBERS(T, P, E0,E1,E2) \
|
||||
struct { detail::_swizzle<4,T, P, 0,0,0,0> E0 ## E0 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,0,0,1> E0 ## E0 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,0,0,2> E0 ## E0 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,0,1,0> E0 ## E0 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,0,1,1> E0 ## E0 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,0,1,2> E0 ## E0 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,0,2,0> E0 ## E0 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,0,2,1> E0 ## E0 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,0,2,2> E0 ## E0 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,1,0,0> E0 ## E1 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,1,0,1> E0 ## E1 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,1,0,2> E0 ## E1 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,1,1,0> E0 ## E1 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,1,1,1> E0 ## E1 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,1,1,2> E0 ## E1 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,1,2,0> E0 ## E1 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,1,2,1> E0 ## E1 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,1,2,2> E0 ## E1 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,2,0,0> E0 ## E2 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,2,0,1> E0 ## E2 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,2,0,2> E0 ## E2 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,2,1,0> E0 ## E2 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,2,1,1> E0 ## E2 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,2,1,2> E0 ## E2 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,2,2,0> E0 ## E2 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,2,2,1> E0 ## E2 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 0,2,2,2> E0 ## E2 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,0,0,0> E1 ## E0 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,0,0,1> E1 ## E0 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,0,0,2> E1 ## E0 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,0,1,0> E1 ## E0 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,0,1,1> E1 ## E0 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,0,1,2> E1 ## E0 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,0,2,0> E1 ## E0 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,0,2,1> E1 ## E0 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,0,2,2> E1 ## E0 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,1,0,0> E1 ## E1 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,1,0,1> E1 ## E1 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,1,0,2> E1 ## E1 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,1,1,0> E1 ## E1 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,1,1,1> E1 ## E1 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,1,1,2> E1 ## E1 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,1,2,0> E1 ## E1 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,1,2,1> E1 ## E1 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,1,2,2> E1 ## E1 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,2,0,0> E1 ## E2 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,2,0,1> E1 ## E2 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,2,0,2> E1 ## E2 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,2,1,0> E1 ## E2 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,2,1,1> E1 ## E2 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,2,1,2> E1 ## E2 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,2,2,0> E1 ## E2 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,2,2,1> E1 ## E2 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 1,2,2,2> E1 ## E2 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,0,0,0> E2 ## E0 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,0,0,1> E2 ## E0 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,0,0,2> E2 ## E0 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,0,1,0> E2 ## E0 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,0,1,1> E2 ## E0 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,0,1,2> E2 ## E0 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,0,2,0> E2 ## E0 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,0,2,1> E2 ## E0 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,0,2,2> E2 ## E0 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,1,0,0> E2 ## E1 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,1,0,1> E2 ## E1 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,1,0,2> E2 ## E1 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,1,1,0> E2 ## E1 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,1,1,1> E2 ## E1 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,1,1,2> E2 ## E1 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,1,2,0> E2 ## E1 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,1,2,1> E2 ## E1 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,1,2,2> E2 ## E1 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,2,0,0> E2 ## E2 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,2,0,1> E2 ## E2 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,2,0,2> E2 ## E2 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,2,1,0> E2 ## E2 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,2,1,1> E2 ## E2 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,2,1,2> E2 ## E2 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,2,2,0> E2 ## E2 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,2,2,1> E2 ## E2 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4,T, P, 2,2,2,2> E2 ## E2 ## E2 ## E2; };
|
||||
|
||||
#define _GLM_SWIZZLE4_2_MEMBERS(T, P, E0,E1,E2,E3) \
|
||||
struct { detail::_swizzle<2,T, P, 0,0,-1,-2> E0 ## E0; }; \
|
||||
struct { detail::_swizzle<2,T, P, 0,1,-1,-2> E0 ## E1; }; \
|
||||
struct { detail::_swizzle<2,T, P, 0,2,-1,-2> E0 ## E2; }; \
|
||||
struct { detail::_swizzle<2,T, P, 0,3,-1,-2> E0 ## E3; }; \
|
||||
struct { detail::_swizzle<2,T, P, 1,0,-1,-2> E1 ## E0; }; \
|
||||
struct { detail::_swizzle<2,T, P, 1,1,-1,-2> E1 ## E1; }; \
|
||||
struct { detail::_swizzle<2,T, P, 1,2,-1,-2> E1 ## E2; }; \
|
||||
struct { detail::_swizzle<2,T, P, 1,3,-1,-2> E1 ## E3; }; \
|
||||
struct { detail::_swizzle<2,T, P, 2,0,-1,-2> E2 ## E0; }; \
|
||||
struct { detail::_swizzle<2,T, P, 2,1,-1,-2> E2 ## E1; }; \
|
||||
struct { detail::_swizzle<2,T, P, 2,2,-1,-2> E2 ## E2; }; \
|
||||
struct { detail::_swizzle<2,T, P, 2,3,-1,-2> E2 ## E3; }; \
|
||||
struct { detail::_swizzle<2,T, P, 3,0,-1,-2> E3 ## E0; }; \
|
||||
struct { detail::_swizzle<2,T, P, 3,1,-1,-2> E3 ## E1; }; \
|
||||
struct { detail::_swizzle<2,T, P, 3,2,-1,-2> E3 ## E2; }; \
|
||||
struct { detail::_swizzle<2,T, P, 3,3,-1,-2> E3 ## E3; };
|
||||
|
||||
#define _GLM_SWIZZLE4_3_MEMBERS(T, P, E0,E1,E2,E3) \
|
||||
struct { detail::_swizzle<3, T, P, 0,0,0,-1> E0 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,0,1,-1> E0 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,0,2,-1> E0 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,0,3,-1> E0 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,1,0,-1> E0 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,1,1,-1> E0 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,1,2,-1> E0 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,1,3,-1> E0 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,2,0,-1> E0 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,2,1,-1> E0 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,2,2,-1> E0 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,2,3,-1> E0 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,3,0,-1> E0 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,3,1,-1> E0 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,3,2,-1> E0 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 0,3,3,-1> E0 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,0,0,-1> E1 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,0,1,-1> E1 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,0,2,-1> E1 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,0,3,-1> E1 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,1,0,-1> E1 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,1,1,-1> E1 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,1,2,-1> E1 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,1,3,-1> E1 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,2,0,-1> E1 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,2,1,-1> E1 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,2,2,-1> E1 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,2,3,-1> E1 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,3,0,-1> E1 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,3,1,-1> E1 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,3,2,-1> E1 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 1,3,3,-1> E1 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,0,0,-1> E2 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,0,1,-1> E2 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,0,2,-1> E2 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,0,3,-1> E2 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,1,0,-1> E2 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,1,1,-1> E2 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,1,2,-1> E2 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,1,3,-1> E2 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,2,0,-1> E2 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,2,1,-1> E2 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,2,2,-1> E2 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,2,3,-1> E2 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,3,0,-1> E2 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,3,1,-1> E2 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,3,2,-1> E2 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 2,3,3,-1> E2 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,0,0,-1> E3 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,0,1,-1> E3 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,0,2,-1> E3 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,0,3,-1> E3 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,1,0,-1> E3 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,1,1,-1> E3 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,1,2,-1> E3 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,1,3,-1> E3 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,2,0,-1> E3 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,2,1,-1> E3 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,2,2,-1> E3 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,2,3,-1> E3 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,3,0,-1> E3 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,3,1,-1> E3 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,3,2,-1> E3 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<3, T, P, 3,3,3,-1> E3 ## E3 ## E3; };
|
||||
|
||||
#define _GLM_SWIZZLE4_4_MEMBERS(T, P, E0,E1,E2,E3) \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,0,0> E0 ## E0 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,0,1> E0 ## E0 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,0,2> E0 ## E0 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,0,3> E0 ## E0 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,1,0> E0 ## E0 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,1,1> E0 ## E0 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,1,2> E0 ## E0 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,1,3> E0 ## E0 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,2,0> E0 ## E0 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,2,1> E0 ## E0 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,2,2> E0 ## E0 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,2,3> E0 ## E0 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,3,0> E0 ## E0 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,3,1> E0 ## E0 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,3,2> E0 ## E0 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,0,3,3> E0 ## E0 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,0,0> E0 ## E1 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,0,1> E0 ## E1 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,0,2> E0 ## E1 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,0,3> E0 ## E1 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,1,0> E0 ## E1 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,1,1> E0 ## E1 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,1,2> E0 ## E1 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,1,3> E0 ## E1 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,2,0> E0 ## E1 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,2,1> E0 ## E1 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,2,2> E0 ## E1 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,2,3> E0 ## E1 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,3,0> E0 ## E1 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,3,1> E0 ## E1 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,3,2> E0 ## E1 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,1,3,3> E0 ## E1 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,0,0> E0 ## E2 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,0,1> E0 ## E2 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,0,2> E0 ## E2 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,0,3> E0 ## E2 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,1,0> E0 ## E2 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,1,1> E0 ## E2 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,1,2> E0 ## E2 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,1,3> E0 ## E2 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,2,0> E0 ## E2 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,2,1> E0 ## E2 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,2,2> E0 ## E2 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,2,3> E0 ## E2 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,3,0> E0 ## E2 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,3,1> E0 ## E2 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,3,2> E0 ## E2 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,2,3,3> E0 ## E2 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,0,0> E0 ## E3 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,0,1> E0 ## E3 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,0,2> E0 ## E3 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,0,3> E0 ## E3 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,1,0> E0 ## E3 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,1,1> E0 ## E3 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,1,2> E0 ## E3 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,1,3> E0 ## E3 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,2,0> E0 ## E3 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,2,1> E0 ## E3 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,2,2> E0 ## E3 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,2,3> E0 ## E3 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,3,0> E0 ## E3 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,3,1> E0 ## E3 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,3,2> E0 ## E3 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 0,3,3,3> E0 ## E3 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,0,0> E1 ## E0 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,0,1> E1 ## E0 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,0,2> E1 ## E0 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,0,3> E1 ## E0 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,1,0> E1 ## E0 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,1,1> E1 ## E0 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,1,2> E1 ## E0 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,1,3> E1 ## E0 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,2,0> E1 ## E0 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,2,1> E1 ## E0 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,2,2> E1 ## E0 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,2,3> E1 ## E0 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,3,0> E1 ## E0 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,3,1> E1 ## E0 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,3,2> E1 ## E0 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,0,3,3> E1 ## E0 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,0,0> E1 ## E1 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,0,1> E1 ## E1 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,0,2> E1 ## E1 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,0,3> E1 ## E1 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,1,0> E1 ## E1 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,1,1> E1 ## E1 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,1,2> E1 ## E1 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,1,3> E1 ## E1 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,2,0> E1 ## E1 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,2,1> E1 ## E1 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,2,2> E1 ## E1 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,2,3> E1 ## E1 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,3,0> E1 ## E1 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,3,1> E1 ## E1 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,3,2> E1 ## E1 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,1,3,3> E1 ## E1 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,0,0> E1 ## E2 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,0,1> E1 ## E2 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,0,2> E1 ## E2 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,0,3> E1 ## E2 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,1,0> E1 ## E2 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,1,1> E1 ## E2 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,1,2> E1 ## E2 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,1,3> E1 ## E2 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,2,0> E1 ## E2 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,2,1> E1 ## E2 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,2,2> E1 ## E2 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,2,3> E1 ## E2 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,3,0> E1 ## E2 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,3,1> E1 ## E2 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,3,2> E1 ## E2 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,2,3,3> E1 ## E2 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,0,0> E1 ## E3 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,0,1> E1 ## E3 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,0,2> E1 ## E3 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,0,3> E1 ## E3 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,1,0> E1 ## E3 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,1,1> E1 ## E3 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,1,2> E1 ## E3 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,1,3> E1 ## E3 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,2,0> E1 ## E3 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,2,1> E1 ## E3 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,2,2> E1 ## E3 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,2,3> E1 ## E3 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,3,0> E1 ## E3 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,3,1> E1 ## E3 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,3,2> E1 ## E3 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 1,3,3,3> E1 ## E3 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,0,0> E2 ## E0 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,0,1> E2 ## E0 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,0,2> E2 ## E0 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,0,3> E2 ## E0 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,1,0> E2 ## E0 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,1,1> E2 ## E0 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,1,2> E2 ## E0 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,1,3> E2 ## E0 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,2,0> E2 ## E0 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,2,1> E2 ## E0 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,2,2> E2 ## E0 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,2,3> E2 ## E0 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,3,0> E2 ## E0 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,3,1> E2 ## E0 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,3,2> E2 ## E0 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,0,3,3> E2 ## E0 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,0,0> E2 ## E1 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,0,1> E2 ## E1 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,0,2> E2 ## E1 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,0,3> E2 ## E1 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,1,0> E2 ## E1 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,1,1> E2 ## E1 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,1,2> E2 ## E1 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,1,3> E2 ## E1 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,2,0> E2 ## E1 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,2,1> E2 ## E1 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,2,2> E2 ## E1 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,2,3> E2 ## E1 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,3,0> E2 ## E1 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,3,1> E2 ## E1 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,3,2> E2 ## E1 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,1,3,3> E2 ## E1 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,0,0> E2 ## E2 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,0,1> E2 ## E2 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,0,2> E2 ## E2 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,0,3> E2 ## E2 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,1,0> E2 ## E2 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,1,1> E2 ## E2 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,1,2> E2 ## E2 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,1,3> E2 ## E2 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,2,0> E2 ## E2 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,2,1> E2 ## E2 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,2,2> E2 ## E2 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,2,3> E2 ## E2 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,3,0> E2 ## E2 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,3,1> E2 ## E2 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,3,2> E2 ## E2 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,2,3,3> E2 ## E2 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,0,0> E2 ## E3 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,0,1> E2 ## E3 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,0,2> E2 ## E3 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,0,3> E2 ## E3 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,1,0> E2 ## E3 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,1,1> E2 ## E3 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,1,2> E2 ## E3 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,1,3> E2 ## E3 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,2,0> E2 ## E3 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,2,1> E2 ## E3 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,2,2> E2 ## E3 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,2,3> E2 ## E3 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,3,0> E2 ## E3 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,3,1> E2 ## E3 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,3,2> E2 ## E3 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 2,3,3,3> E2 ## E3 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,0,0> E3 ## E0 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,0,1> E3 ## E0 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,0,2> E3 ## E0 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,0,3> E3 ## E0 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,1,0> E3 ## E0 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,1,1> E3 ## E0 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,1,2> E3 ## E0 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,1,3> E3 ## E0 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,2,0> E3 ## E0 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,2,1> E3 ## E0 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,2,2> E3 ## E0 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,2,3> E3 ## E0 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,3,0> E3 ## E0 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,3,1> E3 ## E0 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,3,2> E3 ## E0 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,0,3,3> E3 ## E0 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,0,0> E3 ## E1 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,0,1> E3 ## E1 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,0,2> E3 ## E1 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,0,3> E3 ## E1 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,1,0> E3 ## E1 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,1,1> E3 ## E1 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,1,2> E3 ## E1 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,1,3> E3 ## E1 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,2,0> E3 ## E1 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,2,1> E3 ## E1 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,2,2> E3 ## E1 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,2,3> E3 ## E1 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,3,0> E3 ## E1 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,3,1> E3 ## E1 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,3,2> E3 ## E1 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,1,3,3> E3 ## E1 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,0,0> E3 ## E2 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,0,1> E3 ## E2 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,0,2> E3 ## E2 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,0,3> E3 ## E2 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,1,0> E3 ## E2 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,1,1> E3 ## E2 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,1,2> E3 ## E2 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,1,3> E3 ## E2 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,2,0> E3 ## E2 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,2,1> E3 ## E2 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,2,2> E3 ## E2 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,2,3> E3 ## E2 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,3,0> E3 ## E2 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,3,1> E3 ## E2 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,3,2> E3 ## E2 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,2,3,3> E3 ## E2 ## E3 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,0,0> E3 ## E3 ## E0 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,0,1> E3 ## E3 ## E0 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,0,2> E3 ## E3 ## E0 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,0,3> E3 ## E3 ## E0 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,1,0> E3 ## E3 ## E1 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,1,1> E3 ## E3 ## E1 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,1,2> E3 ## E3 ## E1 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,1,3> E3 ## E3 ## E1 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,2,0> E3 ## E3 ## E2 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,2,1> E3 ## E3 ## E2 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,2,2> E3 ## E3 ## E2 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,2,3> E3 ## E3 ## E2 ## E3; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,3,0> E3 ## E3 ## E3 ## E0; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,3,1> E3 ## E3 ## E3 ## E1; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,3,2> E3 ## E3 ## E3 ## E2; }; \
|
||||
struct { detail::_swizzle<4, T, P, 3,3,3,3> E3 ## E3 ## E3 ## E3; };
|
||||
@@ -0,0 +1,685 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/_swizzle_func.hpp
|
||||
|
||||
#pragma once
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, CONST, A, B) \
|
||||
vec<2, T, P> A ## B() CONST \
|
||||
{ \
|
||||
return vec<2, T, P>(this->A, this->B); \
|
||||
}
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, CONST, A, B, C) \
|
||||
vec<3, T, P> A ## B ## C() CONST \
|
||||
{ \
|
||||
return vec<3, T, P>(this->A, this->B, this->C); \
|
||||
}
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, CONST, A, B, C, D) \
|
||||
vec<4, T, P> A ## B ## C ## D() CONST \
|
||||
{ \
|
||||
return vec<4, T, P>(this->A, this->B, this->C, this->D); \
|
||||
}
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC2_ENTRY_DEF(T, P, L, CONST, A, B) \
|
||||
template<typename T> \
|
||||
vec<L, T, P> vec<L, T, P>::A ## B() CONST \
|
||||
{ \
|
||||
return vec<2, T, P>(this->A, this->B); \
|
||||
}
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC3_ENTRY_DEF(T, P, L, CONST, A, B, C) \
|
||||
template<typename T> \
|
||||
vec<3, T, P> vec<L, T, P>::A ## B ## C() CONST \
|
||||
{ \
|
||||
return vec<3, T, P>(this->A, this->B, this->C); \
|
||||
}
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC4_ENTRY_DEF(T, P, L, CONST, A, B, C, D) \
|
||||
template<typename T> \
|
||||
vec<4, T, P> vec<L, T, P>::A ## B ## C ## D() CONST \
|
||||
{ \
|
||||
return vec<4, T, P>(this->A, this->B, this->C, this->D); \
|
||||
}
|
||||
|
||||
#define GLM_MUTABLE
|
||||
|
||||
#define GLM_SWIZZLE_GEN_REF2_FROM_VEC2_SWIZZLE(T, P, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, 2, GLM_MUTABLE, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, 2, GLM_MUTABLE, B, A)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_REF_FROM_VEC2(T, P) \
|
||||
GLM_SWIZZLE_GEN_REF2_FROM_VEC2_SWIZZLE(T, P, x, y) \
|
||||
GLM_SWIZZLE_GEN_REF2_FROM_VEC2_SWIZZLE(T, P, r, g) \
|
||||
GLM_SWIZZLE_GEN_REF2_FROM_VEC2_SWIZZLE(T, P, s, t)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_REF2_FROM_VEC3_SWIZZLE(T, P, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, C, B)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_REF3_FROM_VEC3_SWIZZLE(T, P, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, GLM_MUTABLE, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, GLM_MUTABLE, A, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, GLM_MUTABLE, B, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, GLM_MUTABLE, B, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, GLM_MUTABLE, C, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, GLM_MUTABLE, C, B, A)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_REF_FROM_VEC3_COMP(T, P, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_REF3_FROM_VEC3_SWIZZLE(T, P, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_REF2_FROM_VEC3_SWIZZLE(T, P, A, B, C)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_REF_FROM_VEC3(T, P) \
|
||||
GLM_SWIZZLE_GEN_REF_FROM_VEC3_COMP(T, P, x, y, z) \
|
||||
GLM_SWIZZLE_GEN_REF_FROM_VEC3_COMP(T, P, r, g, b) \
|
||||
GLM_SWIZZLE_GEN_REF_FROM_VEC3_COMP(T, P, s, t, p)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_REF2_FROM_VEC4_SWIZZLE(T, P, A, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, GLM_MUTABLE, D, C)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_REF3_FROM_VEC4_SWIZZLE(T, P, A, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , A, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , A, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , A, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , A, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , A, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , B, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , B, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , B, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , B, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , B, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , C, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , C, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , C, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , C, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , C, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , C, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , D, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , D, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , D, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , D, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , D, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, , D, C, B)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_REF4_FROM_VEC4_SWIZZLE(T, P, A, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , A, C, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , A, C, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , A, D, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , A, D, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , A, B, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , A, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , B, C, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , B, C, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , B, D, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , B, D, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , B, A, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , B, A, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , C, B, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , C, B, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , C, D, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , C, D, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , C, A, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , C, A, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , D, C, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , D, C, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , D, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , D, A, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , D, B, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, , D, B, C, A)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_REF_FROM_VEC4_COMP(T, P, A, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_REF2_FROM_VEC4_SWIZZLE(T, P, A, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_REF3_FROM_VEC4_SWIZZLE(T, P, A, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_REF4_FROM_VEC4_SWIZZLE(T, P, A, B, C, D)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_REF_FROM_VEC4(T, P) \
|
||||
GLM_SWIZZLE_GEN_REF_FROM_VEC4_COMP(T, P, x, y, z, w) \
|
||||
GLM_SWIZZLE_GEN_REF_FROM_VEC4_COMP(T, P, r, g, b, a) \
|
||||
GLM_SWIZZLE_GEN_REF_FROM_VEC4_COMP(T, P, s, t, p, q)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC2_FROM_VEC2_SWIZZLE(T, P, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, B, B)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC3_FROM_VEC2_SWIZZLE(T, P, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, B, B)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC4_FROM_VEC2_SWIZZLE(T, P, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, B, B)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC_FROM_VEC2_COMP(T, P, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_FROM_VEC2_SWIZZLE(T, P, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_FROM_VEC2_SWIZZLE(T, P, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_FROM_VEC2_SWIZZLE(T, P, A, B)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC_FROM_VEC2(T, P) \
|
||||
GLM_SWIZZLE_GEN_VEC_FROM_VEC2_COMP(T, P, x, y) \
|
||||
GLM_SWIZZLE_GEN_VEC_FROM_VEC2_COMP(T, P, r, g) \
|
||||
GLM_SWIZZLE_GEN_VEC_FROM_VEC2_COMP(T, P, s, t)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC2_FROM_VEC3_SWIZZLE(T, P, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, C, C)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC3_FROM_VEC3_SWIZZLE(T, P, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, C, C)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC4_FROM_VEC3_SWIZZLE(T, P, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, C, C)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC_FROM_VEC3_COMP(T, P, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC2_FROM_VEC3_SWIZZLE(T, P, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_FROM_VEC3_SWIZZLE(T, P, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_FROM_VEC3_SWIZZLE(T, P, A, B, C)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC_FROM_VEC3(T, P) \
|
||||
GLM_SWIZZLE_GEN_VEC_FROM_VEC3_COMP(T, P, x, y, z) \
|
||||
GLM_SWIZZLE_GEN_VEC_FROM_VEC3_COMP(T, P, r, g, b) \
|
||||
GLM_SWIZZLE_GEN_VEC_FROM_VEC3_COMP(T, P, s, t, p)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC2_FROM_VEC4_SWIZZLE(T, P, A, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC2_ENTRY(T, P, const, D, D)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC3_FROM_VEC4_SWIZZLE(T, P, A, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, A, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, B, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, C, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC3_ENTRY(T, P, const, D, D, D)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC4_FROM_VEC4_SWIZZLE(T, P, A, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, A, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, B, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, C, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, A, D, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, A, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, B, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, C, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, B, D, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, A, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, B, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, C, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, C, D, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, A, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, B, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, C, D, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, A, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, A, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, A, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, A, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, B, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, B, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, B, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, B, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, C, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, C, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, C, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, D, A) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, D, B) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, D, C) \
|
||||
GLM_SWIZZLE_GEN_VEC4_ENTRY(T, P, const, D, D, D, D)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC_FROM_VEC4_COMP(T, P, A, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC2_FROM_VEC4_SWIZZLE(T, P, A, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC3_FROM_VEC4_SWIZZLE(T, P, A, B, C, D) \
|
||||
GLM_SWIZZLE_GEN_VEC4_FROM_VEC4_SWIZZLE(T, P, A, B, C, D)
|
||||
|
||||
#define GLM_SWIZZLE_GEN_VEC_FROM_VEC4(T, P) \
|
||||
GLM_SWIZZLE_GEN_VEC_FROM_VEC4_COMP(T, P, x, y, z, w) \
|
||||
GLM_SWIZZLE_GEN_VEC_FROM_VEC4_COMP(T, P, r, g, b, a) \
|
||||
GLM_SWIZZLE_GEN_VEC_FROM_VEC4_COMP(T, P, s, t, p, q)
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/_vectorize.hpp
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "type_vec1.hpp"
|
||||
#include "type_vec2.hpp"
|
||||
#include "type_vec3.hpp"
|
||||
#include "type_vec4.hpp"
|
||||
|
||||
namespace glm{
|
||||
namespace detail
|
||||
{
|
||||
template<length_t L, typename R, typename T, precision P>
|
||||
struct functor1{};
|
||||
|
||||
template<typename R, typename T, precision P>
|
||||
struct functor1<1, R, T, P>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<1, R, P> call(R (*Func) (T x), vec<1, T, P> const & v)
|
||||
{
|
||||
return vec<1, R, P>(Func(v.x));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename R, typename T, precision P>
|
||||
struct functor1<2, R, T, P>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<2, R, P> call(R (*Func) (T x), vec<2, T, P> const & v)
|
||||
{
|
||||
return vec<2, R, P>(Func(v.x), Func(v.y));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename R, typename T, precision P>
|
||||
struct functor1<3, R, T, P>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<3, R, P> call(R (*Func) (T x), vec<3, T, P> const & v)
|
||||
{
|
||||
return vec<3, R, P>(Func(v.x), Func(v.y), Func(v.z));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename R, typename T, precision P>
|
||||
struct functor1<4, R, T, P>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, R, P> call(R (*Func) (T x), vec<4, T, P> const & v)
|
||||
{
|
||||
return vec<4, R, P>(Func(v.x), Func(v.y), Func(v.z), Func(v.w));
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P>
|
||||
struct functor2{};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct functor2<1, T, P>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<1, T, P> call(T (*Func) (T x, T y), vec<1, T, P> const & a, vec<1, T, P> const & b)
|
||||
{
|
||||
return vec<1, T, P>(Func(a.x, b.x));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct functor2<2, T, P>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<2, T, P> call(T (*Func) (T x, T y), vec<2, T, P> const & a, vec<2, T, P> const & b)
|
||||
{
|
||||
return vec<2, T, P>(Func(a.x, b.x), Func(a.y, b.y));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct functor2<3, T, P>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<3, T, P> call(T (*Func) (T x, T y), vec<3, T, P> const & a, vec<3, T, P> const & b)
|
||||
{
|
||||
return vec<3, T, P>(Func(a.x, b.x), Func(a.y, b.y), Func(a.z, b.z));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct functor2<4, T, P>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, T, P> call(T (*Func) (T x, T y), vec<4, T, P> const & a, vec<4, T, P> const & b)
|
||||
{
|
||||
return vec<4, T, P>(Func(a.x, b.x), Func(a.y, b.y), Func(a.z, b.z), Func(a.w, b.w));
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P>
|
||||
struct functor2_vec_sca{};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct functor2_vec_sca<1, T, P>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<1, T, P> call(T (*Func) (T x, T y), vec<1, T, P> const & a, T b)
|
||||
{
|
||||
return vec<1, T, P>(Func(a.x, b));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct functor2_vec_sca<2, T, P>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<2, T, P> call(T (*Func) (T x, T y), vec<2, T, P> const & a, T b)
|
||||
{
|
||||
return vec<2, T, P>(Func(a.x, b), Func(a.y, b));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct functor2_vec_sca<3, T, P>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<3, T, P> call(T (*Func) (T x, T y), vec<3, T, P> const & a, T b)
|
||||
{
|
||||
return vec<3, T, P>(Func(a.x, b), Func(a.y, b), Func(a.z, b));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct functor2_vec_sca<4, T, P>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, T, P> call(T (*Func) (T x, T y), vec<4, T, P> const & a, T b)
|
||||
{
|
||||
return vec<4, T, P>(Func(a.x, b), Func(a.y, b), Func(a.z, b), Func(a.w, b));
|
||||
}
|
||||
};
|
||||
}//namespace detail
|
||||
}//namespace glm
|
||||
@@ -0,0 +1,207 @@
|
||||
/// @ref core
|
||||
/// @file glm/core/dummy.cpp
|
||||
///
|
||||
/// GLM is a header only library. There is nothing to compile.
|
||||
/// dummy.cpp exist only a wordaround for CMake file.
|
||||
|
||||
/*
|
||||
#define GLM_MESSAGES
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/ext.hpp>
|
||||
#include <limits>
|
||||
|
||||
struct material
|
||||
{
|
||||
glm::vec4 emission; // Ecm
|
||||
glm::vec4 ambient; // Acm
|
||||
glm::vec4 diffuse; // Dcm
|
||||
glm::vec4 specular; // Scm
|
||||
float shininess; // Srm
|
||||
};
|
||||
|
||||
struct light
|
||||
{
|
||||
glm::vec4 ambient; // Acli
|
||||
glm::vec4 diffuse; // Dcli
|
||||
glm::vec4 specular; // Scli
|
||||
glm::vec4 position; // Ppli
|
||||
glm::vec4 halfVector; // Derived: Hi
|
||||
glm::vec3 spotDirection; // Sdli
|
||||
float spotExponent; // Srli
|
||||
float spotCutoff; // Crli
|
||||
// (range: [0.0,90.0], 180.0)
|
||||
float spotCosCutoff; // Derived: cos(Crli)
|
||||
// (range: [1.0,0.0],-1.0)
|
||||
float constantAttenuation; // K0
|
||||
float linearAttenuation; // K1
|
||||
float quadraticAttenuation;// K2
|
||||
};
|
||||
|
||||
|
||||
// Sample 1
|
||||
#include <glm/vec3.hpp>// glm::vec3
|
||||
#include <glm/geometric.hpp>// glm::cross, glm::normalize
|
||||
|
||||
glm::vec3 computeNormal
|
||||
(
|
||||
glm::vec3 const & a,
|
||||
glm::vec3 const & b,
|
||||
glm::vec3 const & c
|
||||
)
|
||||
{
|
||||
return glm::normalize(glm::cross(c - a, b - a));
|
||||
}
|
||||
|
||||
typedef unsigned int GLuint;
|
||||
#define GL_FALSE 0
|
||||
void glUniformMatrix4fv(GLuint, int, int, float*){}
|
||||
|
||||
// Sample 2
|
||||
#include <glm/vec3.hpp> // glm::vec3
|
||||
#include <glm/vec4.hpp> // glm::vec4, glm::ivec4
|
||||
#include <glm/mat4x4.hpp> // glm::mat4
|
||||
#include <glm/gtc/matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale, glm::perspective
|
||||
#include <glm/gtc/type_ptr.hpp> // glm::value_ptr
|
||||
void func(GLuint LocationMVP, float Translate, glm::vec2 const & Rotate)
|
||||
{
|
||||
glm::mat4 Projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.f);
|
||||
glm::mat4 ViewTranslate = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -Translate));
|
||||
glm::mat4 ViewRotateX = glm::rotate(ViewTranslate, Rotate.y, glm::vec3(-1.0f, 0.0f, 0.0f));
|
||||
glm::mat4 View = glm::rotate(ViewRotateX, Rotate.x, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
glm::mat4 Model = glm::scale(glm::mat4(1.0f), glm::vec3(0.5f));
|
||||
glm::mat4 MVP = Projection * View * Model;
|
||||
glUniformMatrix4fv(LocationMVP, 1, GL_FALSE, glm::value_ptr(MVP));
|
||||
}
|
||||
|
||||
// Sample 3
|
||||
#include <glm/vec2.hpp>// glm::vec2
|
||||
#include <glm/packing.hpp>// glm::packUnorm2x16
|
||||
#include <glm/integer.hpp>// glm::uint
|
||||
#include <glm/gtc/type_precision.hpp>// glm::i8vec2, glm::i32vec2
|
||||
std::size_t const VertexCount = 4;
|
||||
// Float quad geometry
|
||||
std::size_t const PositionSizeF32 = VertexCount * sizeof(glm::vec2);
|
||||
glm::vec2 const PositionDataF32[VertexCount] =
|
||||
{
|
||||
glm::vec2(-1.0f,-1.0f),
|
||||
glm::vec2( 1.0f,-1.0f),
|
||||
glm::vec2( 1.0f, 1.0f),
|
||||
glm::vec2(-1.0f, 1.0f)
|
||||
};
|
||||
// Half-float quad geometry
|
||||
std::size_t const PositionSizeF16 = VertexCount * sizeof(glm::uint);
|
||||
glm::uint const PositionDataF16[VertexCount] =
|
||||
{
|
||||
glm::uint(glm::packUnorm2x16(glm::vec2(-1.0f, -1.0f))),
|
||||
glm::uint(glm::packUnorm2x16(glm::vec2( 1.0f, -1.0f))),
|
||||
glm::uint(glm::packUnorm2x16(glm::vec2( 1.0f, 1.0f))),
|
||||
glm::uint(glm::packUnorm2x16(glm::vec2(-1.0f, 1.0f)))
|
||||
};
|
||||
// 8 bits signed integer quad geometry
|
||||
std::size_t const PositionSizeI8 = VertexCount * sizeof(glm::i8vec2);
|
||||
glm::i8vec2 const PositionDataI8[VertexCount] =
|
||||
{
|
||||
glm::i8vec2(-1,-1),
|
||||
glm::i8vec2( 1,-1),
|
||||
glm::i8vec2( 1, 1),
|
||||
glm::i8vec2(-1, 1)
|
||||
};
|
||||
// 32 bits signed integer quad geometry
|
||||
std::size_t const PositionSizeI32 = VertexCount * sizeof(glm::i32vec2);
|
||||
glm::i32vec2 const PositionDataI32[VertexCount] =
|
||||
{
|
||||
glm::i32vec2 (-1,-1),
|
||||
glm::i32vec2 ( 1,-1),
|
||||
glm::i32vec2 ( 1, 1),
|
||||
glm::i32vec2 (-1, 1)
|
||||
};
|
||||
|
||||
struct intersection
|
||||
{
|
||||
glm::vec4 position;
|
||||
glm::vec3 normal;
|
||||
};
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
// Sample 4
|
||||
#include <glm/vec3.hpp>// glm::vec3
|
||||
#include <glm/geometric.hpp>// glm::normalize, glm::dot, glm::reflect
|
||||
#include <glm/exponential.hpp>// glm::pow
|
||||
#include <glm/gtc/random.hpp>// glm::vecRand3
|
||||
glm::vec3 lighting
|
||||
(
|
||||
intersection const & Intersection,
|
||||
material const & Material,
|
||||
light const & Light,
|
||||
glm::vec3 const & View
|
||||
)
|
||||
{
|
||||
glm::vec3 Color(0.0f);
|
||||
glm::vec3 LightVertor(glm::normalize(
|
||||
Light.position - Intersection.position +
|
||||
glm::vecRand3(0.0f, Light.inaccuracy));
|
||||
|
||||
if(!shadow(Intersection.position, Light.position, LightVertor))
|
||||
{
|
||||
float Diffuse = glm::dot(Intersection.normal, LightVector);
|
||||
if(Diffuse <= 0.0f)
|
||||
return Color;
|
||||
if(Material.isDiffuse())
|
||||
Color += Light.color() * Material.diffuse * Diffuse;
|
||||
if(Material.isSpecular())
|
||||
{
|
||||
glm::vec3 Reflect(glm::reflect(
|
||||
glm::normalize(-LightVector),
|
||||
glm::normalize(Intersection.normal)));
|
||||
float Dot = glm::dot(Reflect, View);
|
||||
float Base = Dot > 0.0f ? Dot : 0.0f;
|
||||
float Specular = glm::pow(Base, Material.exponent);
|
||||
Color += Material.specular * Specular;
|
||||
}
|
||||
}
|
||||
return Color;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
template<typename T, glm::precision P, template<typename, glm::precision> class vecType>
|
||||
T normalizeDotA(vecType<L, T, P> const & x, vecType<L, T, P> const & y)
|
||||
{
|
||||
return glm::dot(x, y) * glm::inversesqrt(glm::dot(x, x) * glm::dot(y, y));
|
||||
}
|
||||
|
||||
#define GLM_TEMPLATE_GENTYPE typename T, glm::precision P, template<typename, glm::precision> class
|
||||
|
||||
template<GLM_TEMPLATE_GENTYPE vecType>
|
||||
T normalizeDotB(vecType<L, T, P> const & x, vecType<L, T, P> const & y)
|
||||
{
|
||||
return glm::dot(x, y) * glm::inversesqrt(glm::dot(x, x) * glm::dot(y, y));
|
||||
}
|
||||
|
||||
template<typename vecType>
|
||||
typename vecType::value_type normalizeDotC(vecType const & a, vecType const & b)
|
||||
{
|
||||
return glm::dot(a, b) * glm::inversesqrt(glm::dot(a, a) * glm::dot(b, b));
|
||||
}
|
||||
*/
|
||||
int main()
|
||||
{
|
||||
/*
|
||||
glm::vec1 o(1);
|
||||
glm::vec2 a(1);
|
||||
glm::vec3 b(1);
|
||||
glm::vec4 c(1);
|
||||
|
||||
glm::quat q;
|
||||
glm::dualquat p;
|
||||
|
||||
glm::mat4 m(1);
|
||||
|
||||
float a0 = normalizeDotA(a, a);
|
||||
float b0 = normalizeDotB(b, b);
|
||||
float c0 = normalizeDotC(c, c);
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_common.hpp
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
///
|
||||
/// @defgroup core_func_common Common functions
|
||||
/// @ingroup core
|
||||
///
|
||||
/// These all operate component-wise. The description is per component.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "setup.hpp"
|
||||
#include "precision.hpp"
|
||||
#include "type_int.hpp"
|
||||
#include "_fixes.hpp"
|
||||
|
||||
namespace glm
|
||||
{
|
||||
/// @addtogroup core_func_common
|
||||
/// @{
|
||||
|
||||
/// Returns x if x >= 0; otherwise, it returns -x.
|
||||
///
|
||||
/// @tparam genType floating-point or signed integer; scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/abs.xml">GLSL abs man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<typename genType>
|
||||
GLM_FUNC_DECL genType abs(genType x);
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> abs(vecType<L, T, P> const & x);
|
||||
|
||||
/// Returns 1.0 if x > 0, 0.0 if x == 0, or -1.0 if x < 0.
|
||||
///
|
||||
/// @tparam genType Floating-point or signed integer; scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/sign.xml">GLSL sign man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> sign(vecType<L, T, P> const & x);
|
||||
|
||||
/// Returns a value equal to the nearest integer that is less then or equal to x.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/floor.xml">GLSL floor man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> floor(vecType<L, T, P> const & x);
|
||||
|
||||
/// Returns a value equal to the nearest integer to x
|
||||
/// whose absolute value is not larger than the absolute value of x.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/trunc.xml">GLSL trunc man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> trunc(vecType<L, T, P> const & x);
|
||||
|
||||
/// Returns a value equal to the nearest integer to x.
|
||||
/// The fraction 0.5 will round in a direction chosen by the
|
||||
/// implementation, presumably the direction that is fastest.
|
||||
/// This includes the possibility that round(x) returns the
|
||||
/// same value as roundEven(x) for all values of x.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/round.xml">GLSL round man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> round(vecType<L, T, P> const & x);
|
||||
|
||||
/// Returns a value equal to the nearest integer to x.
|
||||
/// A fractional part of 0.5 will round toward the nearest even
|
||||
/// integer. (Both 3.5 and 4.5 for x will return 4.0.)
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/roundEven.xml">GLSL roundEven man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
/// @see <a href="http://developer.amd.com/documentation/articles/pages/New-Round-to-Even-Technique.aspx">New round to even technique</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> roundEven(vecType<L, T, P> const & x);
|
||||
|
||||
/// Returns a value equal to the nearest integer
|
||||
/// that is greater than or equal to x.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/ceil.xml">GLSL ceil man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> ceil(vecType<L, T, P> const & x);
|
||||
|
||||
/// Return x - floor(x).
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/fract.xml">GLSL fract man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<typename genType>
|
||||
GLM_FUNC_DECL genType fract(genType x);
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> fract(vecType<L, T, P> const & x);
|
||||
|
||||
/// Modulus. Returns x - y * floor(x / y)
|
||||
/// for each component in x using the floating point value y.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/mod.xml">GLSL mod man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<typename genType>
|
||||
GLM_FUNC_DECL genType mod(genType x, genType y);
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> mod(vecType<L, T, P> const & x, T y);
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> mod(vecType<L, T, P> const & x, vecType<L, T, P> const & y);
|
||||
|
||||
/// Returns the fractional part of x and sets i to the integer
|
||||
/// part (as a whole number floating point value). Both the
|
||||
/// return value and the output parameter will have the same
|
||||
/// sign as x.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/modf.xml">GLSL modf man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<typename genType>
|
||||
GLM_FUNC_DECL genType modf(genType x, genType & i);
|
||||
|
||||
/// Returns y if y < x; otherwise, it returns x.
|
||||
///
|
||||
/// @tparam genType Floating-point or integer; scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/min.xml">GLSL min man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<typename genType>
|
||||
GLM_FUNC_DECL genType min(genType x, genType y);
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> min(vecType<L, T, P> const & x, T y);
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> min(vecType<L, T, P> const & x, vecType<L, T, P> const & y);
|
||||
|
||||
/// Returns y if x < y; otherwise, it returns x.
|
||||
///
|
||||
/// @tparam genType Floating-point or integer; scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/max.xml">GLSL max man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<typename genType>
|
||||
GLM_FUNC_DECL genType max(genType x, genType y);
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> max(vecType<L, T, P> const & x, T y);
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> max(vecType<L, T, P> const & x, vecType<L, T, P> const & y);
|
||||
|
||||
/// Returns min(max(x, minVal), maxVal) for each component in x
|
||||
/// using the floating-point values minVal and maxVal.
|
||||
///
|
||||
/// @tparam genType Floating-point or integer; scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/clamp.xml">GLSL clamp man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<typename genType>
|
||||
GLM_FUNC_DECL genType clamp(genType x, genType minVal, genType maxVal);
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> clamp(vecType<L, T, P> const & x, T minVal, T maxVal);
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> clamp(vecType<L, T, P> const & x, vecType<L, T, P> const & minVal, vecType<L, T, P> const & maxVal);
|
||||
|
||||
/// If genTypeU is a floating scalar or vector:
|
||||
/// Returns x * (1.0 - a) + y * a, i.e., the linear blend of
|
||||
/// x and y using the floating-point value a.
|
||||
/// The value for a is not restricted to the range [0, 1].
|
||||
///
|
||||
/// If genTypeU is a boolean scalar or vector:
|
||||
/// Selects which vector each returned component comes
|
||||
/// from. For a component of <a> that is false, the
|
||||
/// corresponding component of x is returned. For a
|
||||
/// component of a that is true, the corresponding
|
||||
/// component of y is returned. Components of x and y that
|
||||
/// are not selected are allowed to be invalid floating point
|
||||
/// values and will have no effect on the results. Thus, this
|
||||
/// provides different functionality than
|
||||
/// genType mix(genType x, genType y, genType(a))
|
||||
/// where a is a Boolean vector.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/mix.xml">GLSL mix man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
///
|
||||
/// @param[in] x Value to interpolate.
|
||||
/// @param[in] y Value to interpolate.
|
||||
/// @param[in] a Interpolant.
|
||||
///
|
||||
/// @tparam genTypeT Floating point scalar or vector.
|
||||
/// @tparam genTypeU Floating point or boolean scalar or vector. It can't be a vector if it is the length of genTypeT.
|
||||
///
|
||||
/// @code
|
||||
/// #include <glm/glm.hpp>
|
||||
/// ...
|
||||
/// float a;
|
||||
/// bool b;
|
||||
/// glm::dvec3 e;
|
||||
/// glm::dvec3 f;
|
||||
/// glm::vec4 g;
|
||||
/// glm::vec4 h;
|
||||
/// ...
|
||||
/// glm::vec4 r = glm::mix(g, h, a); // Interpolate with a floating-point scalar two vectors.
|
||||
/// glm::vec4 s = glm::mix(g, h, b); // Returns g or h;
|
||||
/// glm::dvec3 t = glm::mix(e, f, a); // Types of the third parameter is not required to match with the first and the second.
|
||||
/// glm::vec4 u = glm::mix(g, h, r); // Interpolations can be perform per component with a vector for the last parameter.
|
||||
/// @endcode
|
||||
template<length_t L, typename T, typename U, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> mix(vecType<L, T, P> const & x, vecType<L, T, P> const & y, vecType<L, U, P> const & a);
|
||||
|
||||
template<length_t L, typename T, typename U, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> mix(vecType<L, T, P> const & x, vecType<L, T, P> const & y, U a);
|
||||
|
||||
template<typename genTypeT, typename genTypeU>
|
||||
GLM_FUNC_DECL genTypeT mix(genTypeT x, genTypeT y, genTypeU a);
|
||||
|
||||
/// Returns 0.0 if x < edge, otherwise it returns 1.0 for each component of a genType.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/step.xml">GLSL step man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<typename genType>
|
||||
GLM_FUNC_DECL genType step(genType edge, genType x);
|
||||
|
||||
/// Returns 0.0 if x < edge, otherwise it returns 1.0.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/step.xml">GLSL step man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<template<length_t, typename, precision> class vecType, length_t L, typename T, precision P>
|
||||
GLM_FUNC_DECL vecType<L, T, P> step(T edge, vecType<L, T, P> const & x);
|
||||
|
||||
/// Returns 0.0 if x < edge, otherwise it returns 1.0.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/step.xml">GLSL step man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<template<length_t, typename, precision> class vecType, length_t L, typename T, precision P>
|
||||
GLM_FUNC_DECL vecType<L, T, P> step(vecType<L, T, P> const & edge, vecType<L, T, P> const & x);
|
||||
|
||||
/// Returns 0.0 if x <= edge0 and 1.0 if x >= edge1 and
|
||||
/// performs smooth Hermite interpolation between 0 and 1
|
||||
/// when edge0 < x < edge1. This is useful in cases where
|
||||
/// you would want a threshold function with a smooth
|
||||
/// transition. This is equivalent to:
|
||||
/// genType t;
|
||||
/// t = clamp ((x - edge0) / (edge1 - edge0), 0, 1);
|
||||
/// return t * t * (3 - 2 * t);
|
||||
/// Results are undefined if edge0 >= edge1.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/smoothstep.xml">GLSL smoothstep man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<typename genType>
|
||||
GLM_FUNC_DECL genType smoothstep(genType edge0, genType edge1, genType x);
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> smoothstep(T edge0, T edge1, vecType<L, T, P> const & x);
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> smoothstep(vecType<L, T, P> const & edge0, vecType<L, T, P> const & edge1, vecType<L, T, P> const & x);
|
||||
|
||||
/// Returns true if x holds a NaN (not a number)
|
||||
/// representation in the underlying implementation's set of
|
||||
/// floating point representations. Returns false otherwise,
|
||||
/// including for implementations with no NaN
|
||||
/// representations.
|
||||
///
|
||||
/// /!\ When using compiler fast math, this function may fail.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/isnan.xml">GLSL isnan man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, bool, P> isnan(vecType<L, T, P> const & x);
|
||||
|
||||
/// Returns true if x holds a positive infinity or negative
|
||||
/// infinity representation in the underlying implementation's
|
||||
/// set of floating point representations. Returns false
|
||||
/// otherwise, including for implementations with no infinity
|
||||
/// representations.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/isinf.xml">GLSL isinf man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, bool, P> isinf(vecType<L, T, P> const & x);
|
||||
|
||||
/// Returns a signed integer value representing
|
||||
/// the encoding of a floating-point value. The floating-point
|
||||
/// value's bit-level representation is preserved.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/floatBitsToInt.xml">GLSL floatBitsToInt man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
GLM_FUNC_DECL int floatBitsToInt(float const & v);
|
||||
|
||||
/// Returns a signed integer value representing
|
||||
/// the encoding of a floating-point value. The floatingpoint
|
||||
/// value's bit-level representation is preserved.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/floatBitsToInt.xml">GLSL floatBitsToInt man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<template<length_t, typename, precision> class vecType, length_t L, precision P>
|
||||
GLM_FUNC_DECL vecType<L, int, P> floatBitsToInt(vecType<L, float, P> const & v);
|
||||
|
||||
/// Returns a unsigned integer value representing
|
||||
/// the encoding of a floating-point value. The floatingpoint
|
||||
/// value's bit-level representation is preserved.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/floatBitsToUint.xml">GLSL floatBitsToUint man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
GLM_FUNC_DECL uint floatBitsToUint(float const & v);
|
||||
|
||||
/// Returns a unsigned integer value representing
|
||||
/// the encoding of a floating-point value. The floatingpoint
|
||||
/// value's bit-level representation is preserved.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/floatBitsToUint.xml">GLSL floatBitsToUint man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<template<length_t, typename, precision> class vecType, length_t L, precision P>
|
||||
GLM_FUNC_DECL vecType<L, uint, P> floatBitsToUint(vecType<L, float, P> const & v);
|
||||
|
||||
/// Returns a floating-point value corresponding to a signed
|
||||
/// integer encoding of a floating-point value.
|
||||
/// If an inf or NaN is passed in, it will not signal, and the
|
||||
/// resulting floating point value is unspecified. Otherwise,
|
||||
/// the bit-level representation is preserved.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/intBitsToFloat.xml">GLSL intBitsToFloat man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
GLM_FUNC_DECL float intBitsToFloat(int const & v);
|
||||
|
||||
/// Returns a floating-point value corresponding to a signed
|
||||
/// integer encoding of a floating-point value.
|
||||
/// If an inf or NaN is passed in, it will not signal, and the
|
||||
/// resulting floating point value is unspecified. Otherwise,
|
||||
/// the bit-level representation is preserved.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/intBitsToFloat.xml">GLSL intBitsToFloat man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<template<length_t, typename, precision> class vecType, length_t L, precision P>
|
||||
GLM_FUNC_DECL vecType<L, float, P> intBitsToFloat(vecType<L, int, P> const & v);
|
||||
|
||||
/// Returns a floating-point value corresponding to a
|
||||
/// unsigned integer encoding of a floating-point value.
|
||||
/// If an inf or NaN is passed in, it will not signal, and the
|
||||
/// resulting floating point value is unspecified. Otherwise,
|
||||
/// the bit-level representation is preserved.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/uintBitsToFloat.xml">GLSL uintBitsToFloat man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
GLM_FUNC_DECL float uintBitsToFloat(uint const & v);
|
||||
|
||||
/// Returns a floating-point value corresponding to a
|
||||
/// unsigned integer encoding of a floating-point value.
|
||||
/// If an inf or NaN is passed in, it will not signal, and the
|
||||
/// resulting floating point value is unspecified. Otherwise,
|
||||
/// the bit-level representation is preserved.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/uintBitsToFloat.xml">GLSL uintBitsToFloat man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<template<length_t, typename, precision> class vecType, length_t L, precision P>
|
||||
GLM_FUNC_DECL vecType<L, float, P> uintBitsToFloat(vecType<L, uint, P> const & v);
|
||||
|
||||
/// Computes and returns a * b + c.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/fma.xml">GLSL fma man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<typename genType>
|
||||
GLM_FUNC_DECL genType fma(genType const & a, genType const & b, genType const & c);
|
||||
|
||||
/// Splits x into a floating-point significand in the range
|
||||
/// [0.5, 1.0) and an integral exponent of two, such that:
|
||||
/// x = significand * exp(2, exponent)
|
||||
///
|
||||
/// The significand is returned by the function and the
|
||||
/// exponent is returned in the parameter exp. For a
|
||||
/// floating-point value of zero, the significant and exponent
|
||||
/// are both zero. For a floating-point value that is an
|
||||
/// infinity or is not a number, the results are undefined.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/frexp.xml">GLSL frexp man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<typename genType, typename genIType>
|
||||
GLM_FUNC_DECL genType frexp(genType const & x, genIType & exp);
|
||||
|
||||
/// Builds a floating-point number from x and the
|
||||
/// corresponding integral exponent of two in exp, returning:
|
||||
/// significand * exp(2, exponent)
|
||||
///
|
||||
/// If this product is too large to be represented in the
|
||||
/// floating-point type, the result is undefined.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/ldexp.xml">GLSL ldexp man page</a>;
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.3 Common Functions</a>
|
||||
template<typename genType, typename genIType>
|
||||
GLM_FUNC_DECL genType ldexp(genType const & x, genIType const & exp);
|
||||
|
||||
/// @}
|
||||
}//namespace glm
|
||||
|
||||
#include "func_common.inl"
|
||||
|
||||
@@ -0,0 +1,881 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_common.inl
|
||||
|
||||
#include "func_vector_relational.hpp"
|
||||
#include "type_vec2.hpp"
|
||||
#include "type_vec3.hpp"
|
||||
#include "type_vec4.hpp"
|
||||
#include "_vectorize.hpp"
|
||||
#include <limits>
|
||||
|
||||
namespace glm
|
||||
{
|
||||
// min
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType min(genType x, genType y)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559 || std::numeric_limits<genType>::is_integer || GLM_UNRESTRICTED_GENTYPE, "'min' only accept floating-point or integer inputs");
|
||||
return x < y ? x : y;
|
||||
}
|
||||
|
||||
// max
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType max(genType x, genType y)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559 || std::numeric_limits<genType>::is_integer || GLM_UNRESTRICTED_GENTYPE, "'max' only accept floating-point or integer inputs");
|
||||
|
||||
return x > y ? x : y;
|
||||
}
|
||||
|
||||
// abs
|
||||
template<>
|
||||
GLM_FUNC_QUALIFIER int32 abs(int32 x)
|
||||
{
|
||||
int32 const y = x >> 31;
|
||||
return (x ^ y) - y;
|
||||
}
|
||||
|
||||
// round
|
||||
# if GLM_HAS_CXX11_STL
|
||||
using ::std::round;
|
||||
# else
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType round(genType x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'round' only accept floating-point inputs");
|
||||
|
||||
return x < static_cast<genType>(0) ? static_cast<genType>(int(x - static_cast<genType>(0.5))) : static_cast<genType>(int(x + static_cast<genType>(0.5)));
|
||||
}
|
||||
# endif
|
||||
|
||||
// trunc
|
||||
# if GLM_HAS_CXX11_STL
|
||||
using ::std::trunc;
|
||||
# else
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType trunc(genType x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'trunc' only accept floating-point inputs");
|
||||
|
||||
return x < static_cast<genType>(0) ? -std::floor(-x) : std::floor(x);
|
||||
}
|
||||
# endif
|
||||
|
||||
}//namespace glm
|
||||
|
||||
namespace glm{
|
||||
namespace detail
|
||||
{
|
||||
template<typename genFIType, bool /*signed*/>
|
||||
struct compute_abs
|
||||
{};
|
||||
|
||||
template<typename genFIType>
|
||||
struct compute_abs<genFIType, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static genFIType call(genFIType x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(
|
||||
std::numeric_limits<genFIType>::is_iec559 || std::numeric_limits<genFIType>::is_signed || GLM_UNRESTRICTED_GENTYPE,
|
||||
"'abs' only accept floating-point and integer scalar or vector inputs");
|
||||
|
||||
return x >= genFIType(0) ? x : -x;
|
||||
// TODO, perf comp with: *(((int *) &x) + 1) &= 0x7fffffff;
|
||||
}
|
||||
};
|
||||
|
||||
#if GLM_COMPILER & GLM_COMPILER_CUDA
|
||||
template<>
|
||||
struct compute_abs<float, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static float call(float x)
|
||||
{
|
||||
return fabsf(x);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
template<typename genFIType>
|
||||
struct compute_abs<genFIType, false>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static genFIType call(genFIType x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(
|
||||
(!std::numeric_limits<genFIType>::is_signed && std::numeric_limits<genFIType>::is_integer) || GLM_UNRESTRICTED_GENTYPE,
|
||||
"'abs' only accept floating-point and integer scalar or vector inputs");
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_abs_vector
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(abs, x);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, typename U, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_mix_vector
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x, vecType<L, T, P> const & y, vecType<L, U, P> const & a)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<U>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'mix' only accept floating-point inputs for the interpolator a");
|
||||
|
||||
return vecType<L, T, P>(vecType<L, U, P>(x) + a * vecType<L, U, P>(y - x));
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_mix_vector<L, T, bool, P, vecType, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x, vecType<L, T, P> const & y, vecType<L, bool, P> const & a)
|
||||
{
|
||||
vecType<L, T, P> Result(uninitialize);
|
||||
for(length_t i = 0; i < x.length(); ++i)
|
||||
Result[i] = a[i] ? y[i] : x[i];
|
||||
return Result;
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, typename U, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_mix_scalar
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x, vecType<L, T, P> const & y, U const & a)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<U>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'mix' only accept floating-point inputs for the interpolator a");
|
||||
|
||||
return vecType<L, T, P>(vecType<L, U, P>(x) + a * vecType<L, U, P>(y - x));
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_mix_scalar<L, T, bool, P, vecType, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x, vecType<L, T, P> const & y, bool const & a)
|
||||
{
|
||||
return a ? y : x;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename U>
|
||||
struct compute_mix
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static T call(T const & x, T const & y, U const & a)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<U>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'mix' only accept floating-point inputs for the interpolator a");
|
||||
|
||||
return static_cast<T>(static_cast<U>(x) + a * static_cast<U>(y - x));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct compute_mix<T, bool>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static T call(T const & x, T const & y, bool const & a)
|
||||
{
|
||||
return a ? y : x;
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool isFloat, bool Aligned>
|
||||
struct compute_sign
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x)
|
||||
{
|
||||
return vecType<L, T, P>(glm::lessThan(vecType<L, T, P>(0), x)) - vecType<L, T, P>(glm::lessThan(x, vecType<L, T, P>(0)));
|
||||
}
|
||||
};
|
||||
|
||||
# if GLM_ARCH == GLM_ARCH_X86
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_sign<L, T, P, vecType, false, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x)
|
||||
{
|
||||
T const Shift(static_cast<T>(sizeof(T) * 8 - 1));
|
||||
vecType<L, T, P> const y(vecType<L, typename make_unsigned<T>::type, P>(-x) >> typename make_unsigned<T>::type(Shift));
|
||||
|
||||
return (x >> Shift) | y;
|
||||
}
|
||||
};
|
||||
# endif
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_floor
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(std::floor, x);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_ceil
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(std::ceil, x);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_fract
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x)
|
||||
{
|
||||
return x - floor(x);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_trunc
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(trunc, x);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_round
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(round, x);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_mod
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & a, vecType<L, T, P> const & b)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'mod' only accept floating-point inputs. Include <glm/gtc/integer.hpp> for integer inputs.");
|
||||
return a - b * floor(a / b);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_min_vector
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x, vecType<L, T, P> const & y)
|
||||
{
|
||||
return detail::functor2<L, T, P>::call(min, x, y);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_max_vector
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x, vecType<L, T, P> const & y)
|
||||
{
|
||||
return detail::functor2<L, T, P>::call(max, x, y);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_clamp_vector
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x, vecType<L, T, P> const & minVal, vecType<L, T, P> const & maxVal)
|
||||
{
|
||||
return min(max(x, minVal), maxVal);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_step_vector
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & edge, vecType<L, T, P> const & x)
|
||||
{
|
||||
return mix(vecType<L, T, P>(1), vecType<L, T, P>(0), glm::lessThan(x, edge));
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_smoothstep_vector
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & edge0, vecType<L, T, P> const & edge1, vecType<L, T, P> const & x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'step' only accept floating-point inputs");
|
||||
vecType<L, T, P> const tmp(clamp((x - edge0) / (edge1 - edge0), static_cast<T>(0), static_cast<T>(1)));
|
||||
return tmp * tmp * (static_cast<T>(3) - static_cast<T>(2) * tmp);
|
||||
}
|
||||
};
|
||||
}//namespace detail
|
||||
|
||||
template<typename genFIType>
|
||||
GLM_FUNC_QUALIFIER genFIType abs(genFIType x)
|
||||
{
|
||||
return detail::compute_abs<genFIType, std::numeric_limits<genFIType>::is_signed>::call(x);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> abs(vecType<L, T, P> const & x)
|
||||
{
|
||||
return detail::compute_abs_vector<L, T, P, vecType, detail::is_aligned<P>::value>::call(x);
|
||||
}
|
||||
|
||||
// sign
|
||||
// fast and works for any type
|
||||
template<typename genFIType>
|
||||
GLM_FUNC_QUALIFIER genFIType sign(genFIType x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(
|
||||
std::numeric_limits<genFIType>::is_iec559 || (std::numeric_limits<genFIType>::is_signed && std::numeric_limits<genFIType>::is_integer),
|
||||
"'sign' only accept signed inputs");
|
||||
|
||||
return detail::compute_sign<1, genFIType, defaultp, vec, std::numeric_limits<genFIType>::is_iec559, highp>::call(vec<1, genFIType>(x)).x;
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> sign(vecType<L, T, P> const & x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(
|
||||
std::numeric_limits<T>::is_iec559 || (std::numeric_limits<T>::is_signed && std::numeric_limits<T>::is_integer),
|
||||
"'sign' only accept signed inputs");
|
||||
|
||||
return detail::compute_sign<L, T, P, vecType, std::numeric_limits<T>::is_iec559, detail::is_aligned<P>::value>::call(x);
|
||||
}
|
||||
|
||||
// floor
|
||||
using ::std::floor;
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> floor(vecType<L, T, P> const & x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'floor' only accept floating-point inputs.");
|
||||
return detail::compute_floor<L, T, P, vecType, detail::is_aligned<P>::value>::call(x);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> trunc(vecType<L, T, P> const & x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'trunc' only accept floating-point inputs");
|
||||
return detail::compute_trunc<L, T, P, vecType, detail::is_aligned<P>::value>::call(x);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> round(vecType<L, T, P> const & x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'round' only accept floating-point inputs");
|
||||
return detail::compute_round<L, T, P, vecType, detail::is_aligned<P>::value>::call(x);
|
||||
}
|
||||
|
||||
/*
|
||||
// roundEven
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType roundEven(genType const& x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'roundEven' only accept floating-point inputs");
|
||||
|
||||
return genType(int(x + genType(int(x) % 2)));
|
||||
}
|
||||
*/
|
||||
|
||||
// roundEven
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType roundEven(genType x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'roundEven' only accept floating-point inputs");
|
||||
|
||||
int Integer = static_cast<int>(x);
|
||||
genType IntegerPart = static_cast<genType>(Integer);
|
||||
genType FractionalPart = fract(x);
|
||||
|
||||
if(FractionalPart > static_cast<genType>(0.5) || FractionalPart < static_cast<genType>(0.5))
|
||||
{
|
||||
return round(x);
|
||||
}
|
||||
else if((Integer % 2) == 0)
|
||||
{
|
||||
return IntegerPart;
|
||||
}
|
||||
else if(x <= static_cast<genType>(0)) // Work around...
|
||||
{
|
||||
return IntegerPart - static_cast<genType>(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return IntegerPart + static_cast<genType>(1);
|
||||
}
|
||||
//else // Bug on MinGW 4.5.2
|
||||
//{
|
||||
// return mix(IntegerPart + genType(-1), IntegerPart + genType(1), x <= genType(0));
|
||||
//}
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> roundEven(vecType<L, T, P> const & x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'roundEven' only accept floating-point inputs");
|
||||
return detail::functor1<L, T, T, P>::call(roundEven, x);
|
||||
}
|
||||
|
||||
// ceil
|
||||
using ::std::ceil;
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> ceil(vecType<L, T, P> const & x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'ceil' only accept floating-point inputs");
|
||||
return detail::compute_ceil<L, T, P, vecType, detail::is_aligned<P>::value>::call(x);
|
||||
}
|
||||
|
||||
// fract
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType fract(genType x)
|
||||
{
|
||||
return fract(vec<1, genType>(x)).x;
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> fract(vecType<L, T, P> const & x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'fract' only accept floating-point inputs");
|
||||
return detail::compute_fract<L, T, P, vecType, detail::is_aligned<P>::value>::call(x);
|
||||
}
|
||||
|
||||
// mod
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType mod(genType x, genType y)
|
||||
{
|
||||
# if GLM_COMPILER & GLM_COMPILER_CUDA
|
||||
// Another Cuda compiler bug https://github.com/g-truc/glm/issues/530
|
||||
vec<1, genType, defaultp> Result(mod(vec<1, genType, defaultp>(x), y));
|
||||
return Result.x;
|
||||
# else
|
||||
return mod(vec<1, genType, defaultp>(x), y).x;
|
||||
# endif
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> mod(vecType<L, T, P> const & x, T y)
|
||||
{
|
||||
return detail::compute_mod<L, T, P, vecType, detail::is_aligned<P>::value>::call(x, vecType<L, T, P>(y));
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> mod(vecType<L, T, P> const & x, vecType<L, T, P> const & y)
|
||||
{
|
||||
return detail::compute_mod<L, T, P, vecType, detail::is_aligned<P>::value>::call(x, y);
|
||||
}
|
||||
|
||||
// modf
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType modf(genType x, genType & i)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'modf' only accept floating-point inputs");
|
||||
return std::modf(x, &i);
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<1, T, P> modf(vec<1, T, P> const & x, vec<1, T, P> & i)
|
||||
{
|
||||
return vec<1, T, P>(
|
||||
modf(x.x, i.x));
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<2, T, P> modf(vec<2, T, P> const & x, vec<2, T, P> & i)
|
||||
{
|
||||
return vec<2, T, P>(
|
||||
modf(x.x, i.x),
|
||||
modf(x.y, i.y));
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<3, T, P> modf(vec<3, T, P> const & x, vec<3, T, P> & i)
|
||||
{
|
||||
return vec<3, T, P>(
|
||||
modf(x.x, i.x),
|
||||
modf(x.y, i.y),
|
||||
modf(x.z, i.z));
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<4, T, P> modf(vec<4, T, P> const & x, vec<4, T, P> & i)
|
||||
{
|
||||
return vec<4, T, P>(
|
||||
modf(x.x, i.x),
|
||||
modf(x.y, i.y),
|
||||
modf(x.z, i.z),
|
||||
modf(x.w, i.w));
|
||||
}
|
||||
|
||||
//// Only valid if (INT_MIN <= x-y <= INT_MAX)
|
||||
//// min(x,y)
|
||||
//r = y + ((x - y) & ((x - y) >> (sizeof(int) *
|
||||
//CHAR_BIT - 1)));
|
||||
//// max(x,y)
|
||||
//r = x - ((x - y) & ((x - y) >> (sizeof(int) *
|
||||
//CHAR_BIT - 1)));
|
||||
|
||||
// min
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> min(vecType<L, T, P> const & a, T b)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || std::numeric_limits<T>::is_integer || GLM_UNRESTRICTED_GENTYPE, "'min' only accept floating-point or integer inputs");
|
||||
return detail::compute_min_vector<L, T, P, vecType, detail::is_aligned<P>::value>::call(a, vecType<L, T, P>(b));
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> min(vecType<L, T, P> const & a, vecType<L, T, P> const & b)
|
||||
{
|
||||
return detail::compute_min_vector<L, T, P, vecType, detail::is_aligned<P>::value>::call(a, b);
|
||||
}
|
||||
|
||||
// max
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> max(vecType<L, T, P> const & a, T b)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || std::numeric_limits<T>::is_integer || GLM_UNRESTRICTED_GENTYPE, "'max' only accept floating-point or integer inputs");
|
||||
return detail::compute_max_vector<L, T, P, vecType, detail::is_aligned<P>::value>::call(a, vecType<L, T, P>(b));
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> max(vecType<L, T, P> const & a, vecType<L, T, P> const & b)
|
||||
{
|
||||
return detail::compute_max_vector<L, T, P, vecType, detail::is_aligned<P>::value>::call(a, b);
|
||||
}
|
||||
|
||||
// clamp
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType clamp(genType x, genType minVal, genType maxVal)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559 || std::numeric_limits<genType>::is_integer || GLM_UNRESTRICTED_GENTYPE, "'clamp' only accept floating-point or integer inputs");
|
||||
return min(max(x, minVal), maxVal);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> clamp(vecType<L, T, P> const & x, T minVal, T maxVal)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || std::numeric_limits<T>::is_integer || GLM_UNRESTRICTED_GENTYPE, "'clamp' only accept floating-point or integer inputs");
|
||||
return detail::compute_clamp_vector<L, T, P, vecType, detail::is_aligned<P>::value>::call(x, vecType<L, T, P>(minVal), vecType<L, T, P>(maxVal));
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> clamp(vecType<L, T, P> const & x, vecType<L, T, P> const & minVal, vecType<L, T, P> const & maxVal)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || std::numeric_limits<T>::is_integer || GLM_UNRESTRICTED_GENTYPE, "'clamp' only accept floating-point or integer inputs");
|
||||
return detail::compute_clamp_vector<L, T, P, vecType, detail::is_aligned<P>::value>::call(x, minVal, maxVal);
|
||||
}
|
||||
|
||||
template<typename genTypeT, typename genTypeU>
|
||||
GLM_FUNC_QUALIFIER genTypeT mix(genTypeT x, genTypeT y, genTypeU a)
|
||||
{
|
||||
return detail::compute_mix<genTypeT, genTypeU>::call(x, y, a);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, typename U, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> mix(vecType<L, T, P> const & x, vecType<L, T, P> const & y, U a)
|
||||
{
|
||||
return detail::compute_mix_scalar<L, T, U, P, vecType, detail::is_aligned<P>::value>::call(x, y, a);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, typename U, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> mix(vecType<L, T, P> const & x, vecType<L, T, P> const & y, vecType<L, U, P> const & a)
|
||||
{
|
||||
return detail::compute_mix_vector<L, T, U, P, vecType, detail::is_aligned<P>::value>::call(x, y, a);
|
||||
}
|
||||
|
||||
// step
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType step(genType edge, genType x)
|
||||
{
|
||||
return mix(static_cast<genType>(1), static_cast<genType>(0), glm::lessThan(x, edge));
|
||||
}
|
||||
|
||||
template<template<length_t, typename, precision> class vecType, length_t L, typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> step(T edge, vecType<L, T, P> const & x)
|
||||
{
|
||||
return detail::compute_step_vector<L, T, P, vecType, detail::is_aligned<P>::value>::call(vecType<L, T, P>(edge), x);
|
||||
}
|
||||
|
||||
template<template<length_t, typename, precision> class vecType, length_t L, typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> step(vecType<L, T, P> const & edge, vecType<L, T, P> const & x)
|
||||
{
|
||||
return detail::compute_step_vector<L, T, P, vecType, detail::is_aligned<P>::value>::call(edge, x);
|
||||
}
|
||||
|
||||
// smoothstep
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType smoothstep(genType edge0, genType edge1, genType x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'smoothstep' only accept floating-point inputs");
|
||||
|
||||
genType const tmp(clamp((x - edge0) / (edge1 - edge0), genType(0), genType(1)));
|
||||
return tmp * tmp * (genType(3) - genType(2) * tmp);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> smoothstep(T edge0, T edge1, vecType<L, T, P> const & x)
|
||||
{
|
||||
return detail::compute_smoothstep_vector<L, T, P, vecType, detail::is_aligned<P>::value>::call(vecType<L, T, P>(edge0), vecType<L, T, P>(edge1), x);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> smoothstep(vecType<L, T, P> const & edge0, vecType<L, T, P> const & edge1, vecType<L, T, P> const & x)
|
||||
{
|
||||
return detail::compute_smoothstep_vector<L, T, P, vecType, detail::is_aligned<P>::value>::call(edge0, edge1, x);
|
||||
}
|
||||
|
||||
# if GLM_HAS_CXX11_STL
|
||||
using std::isnan;
|
||||
# else
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER bool isnan(genType x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'isnan' only accept floating-point inputs");
|
||||
|
||||
# if GLM_HAS_CXX11_STL
|
||||
return std::isnan(x);
|
||||
# elif GLM_COMPILER & GLM_COMPILER_VC
|
||||
return _isnan(x) != 0;
|
||||
# elif GLM_COMPILER & GLM_COMPILER_INTEL
|
||||
# if GLM_PLATFORM & GLM_PLATFORM_WINDOWS
|
||||
return _isnan(x) != 0;
|
||||
# else
|
||||
return ::isnan(x) != 0;
|
||||
# endif
|
||||
# elif (GLM_COMPILER & (GLM_COMPILER_GCC | GLM_COMPILER_CLANG)) && (GLM_PLATFORM & GLM_PLATFORM_ANDROID) && __cplusplus < 201103L
|
||||
return _isnan(x) != 0;
|
||||
# elif GLM_COMPILER & GLM_COMPILER_CUDA
|
||||
return isnan(x) != 0;
|
||||
# else
|
||||
return std::isnan(x);
|
||||
# endif
|
||||
}
|
||||
# endif
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, bool, P> isnan(vecType<L, T, P> const & x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'isnan' only accept floating-point inputs");
|
||||
|
||||
return detail::functor1<L, bool, T, P>::call(isnan, x);
|
||||
}
|
||||
|
||||
# if GLM_HAS_CXX11_STL
|
||||
using std::isinf;
|
||||
# else
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER bool isinf(genType x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'isinf' only accept floating-point inputs");
|
||||
|
||||
# if GLM_HAS_CXX11_STL
|
||||
return std::isinf(x);
|
||||
# elif GLM_COMPILER & (GLM_COMPILER_INTEL | GLM_COMPILER_VC)
|
||||
# if(GLM_PLATFORM & GLM_PLATFORM_WINDOWS)
|
||||
return _fpclass(x) == _FPCLASS_NINF || _fpclass(x) == _FPCLASS_PINF;
|
||||
# else
|
||||
return ::isinf(x);
|
||||
# endif
|
||||
# elif GLM_COMPILER & (GLM_COMPILER_GCC | GLM_COMPILER_CLANG)
|
||||
# if(GLM_PLATFORM & GLM_PLATFORM_ANDROID && __cplusplus < 201103L)
|
||||
return _isinf(x) != 0;
|
||||
# else
|
||||
return std::isinf(x);
|
||||
# endif
|
||||
# elif GLM_COMPILER & GLM_COMPILER_CUDA
|
||||
// http://developer.download.nvidia.com/compute/cuda/4_2/rel/toolkit/docs/online/group__CUDA__MATH__DOUBLE_g13431dd2b40b51f9139cbb7f50c18fab.html#g13431dd2b40b51f9139cbb7f50c18fab
|
||||
return isinf(double(x)) != 0;
|
||||
# else
|
||||
return std::isinf(x);
|
||||
# endif
|
||||
}
|
||||
# endif
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, bool, P> isinf(vecType<L, T, P> const & x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'isnan' only accept floating-point inputs");
|
||||
|
||||
return detail::functor1<L, bool, T, P>::call(isinf, x);
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER int floatBitsToInt(float const & v)
|
||||
{
|
||||
union
|
||||
{
|
||||
float in;
|
||||
int out;
|
||||
} u;
|
||||
|
||||
u.in = v;
|
||||
|
||||
return u.out;
|
||||
}
|
||||
|
||||
template<template<length_t, typename, precision> class vecType, length_t L, precision P>
|
||||
GLM_FUNC_QUALIFIER vecType<L, int, P> floatBitsToInt(vecType<L, float, P> const & v)
|
||||
{
|
||||
return reinterpret_cast<vecType<L, int, P>&>(const_cast<vecType<L, float, P>&>(v));
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER uint floatBitsToUint(float const & v)
|
||||
{
|
||||
union
|
||||
{
|
||||
float in;
|
||||
uint out;
|
||||
} u;
|
||||
|
||||
u.in = v;
|
||||
|
||||
return u.out;
|
||||
}
|
||||
|
||||
template<template<length_t, typename, precision> class vecType, length_t L, precision P>
|
||||
GLM_FUNC_QUALIFIER vecType<L, uint, P> floatBitsToUint(vecType<L, float, P> const & v)
|
||||
{
|
||||
return reinterpret_cast<vecType<L, uint, P>&>(const_cast<vecType<L, float, P>&>(v));
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER float intBitsToFloat(int const & v)
|
||||
{
|
||||
union
|
||||
{
|
||||
int in;
|
||||
float out;
|
||||
} u;
|
||||
|
||||
u.in = v;
|
||||
|
||||
return u.out;
|
||||
}
|
||||
|
||||
template<template<length_t, typename, precision> class vecType, length_t L, precision P>
|
||||
GLM_FUNC_QUALIFIER vecType<L, float, P> intBitsToFloat(vecType<L, int, P> const & v)
|
||||
{
|
||||
return reinterpret_cast<vecType<L, float, P>&>(const_cast<vecType<L, int, P>&>(v));
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER float uintBitsToFloat(uint const & v)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint in;
|
||||
float out;
|
||||
} u;
|
||||
|
||||
u.in = v;
|
||||
|
||||
return u.out;
|
||||
}
|
||||
|
||||
template<template<length_t, typename, precision> class vecType, length_t L, precision P>
|
||||
GLM_FUNC_QUALIFIER vecType<L, float, P> uintBitsToFloat(vecType<L, uint, P> const & v)
|
||||
{
|
||||
return reinterpret_cast<vecType<L, float, P>&>(const_cast<vecType<L, uint, P>&>(v));
|
||||
}
|
||||
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType fma(genType const & a, genType const & b, genType const & c)
|
||||
{
|
||||
return a * b + c;
|
||||
}
|
||||
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType frexp(genType x, int & exp)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'frexp' only accept floating-point inputs");
|
||||
|
||||
return std::frexp(x, &exp);
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<1, T, P> frexp(vec<1, T, P> const & x, vec<1, int, P> & exp)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'frexp' only accept floating-point inputs");
|
||||
|
||||
return vec<1, T, P>(std::frexp(x.x, &exp.x));
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<2, T, P> frexp(vec<2, T, P> const & x, vec<2, int, P> & exp)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'frexp' only accept floating-point inputs");
|
||||
|
||||
return vec<2, T, P>(
|
||||
frexp(x.x, exp.x),
|
||||
frexp(x.y, exp.y));
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<3, T, P> frexp(vec<3, T, P> const & x, vec<3, int, P> & exp)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'frexp' only accept floating-point inputs");
|
||||
|
||||
return vec<3, T, P>(
|
||||
frexp(x.x, exp.x),
|
||||
frexp(x.y, exp.y),
|
||||
frexp(x.z, exp.z));
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<4, T, P> frexp(vec<4, T, P> const & x, vec<4, int, P> & exp)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'frexp' only accept floating-point inputs");
|
||||
|
||||
return vec<4, T, P>(
|
||||
frexp(x.x, exp.x),
|
||||
frexp(x.y, exp.y),
|
||||
frexp(x.z, exp.z),
|
||||
frexp(x.w, exp.w));
|
||||
}
|
||||
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType ldexp(genType const & x, int const & exp)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'ldexp' only accept floating-point inputs");
|
||||
|
||||
return std::ldexp(x, exp);
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<1, T, P> ldexp(vec<1, T, P> const & x, vec<1, int, P> const & exp)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'ldexp' only accept floating-point inputs");
|
||||
|
||||
return vec<1, T, P>(
|
||||
ldexp(x.x, exp.x));
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<2, T, P> ldexp(vec<2, T, P> const & x, vec<2, int, P> const & exp)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'ldexp' only accept floating-point inputs");
|
||||
|
||||
return vec<2, T, P>(
|
||||
ldexp(x.x, exp.x),
|
||||
ldexp(x.y, exp.y));
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<3, T, P> ldexp(vec<3, T, P> const & x, vec<3, int, P> const & exp)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'ldexp' only accept floating-point inputs");
|
||||
|
||||
return vec<3, T, P>(
|
||||
ldexp(x.x, exp.x),
|
||||
ldexp(x.y, exp.y),
|
||||
ldexp(x.z, exp.z));
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<4, T, P> ldexp(vec<4, T, P> const & x, vec<4, int, P> const & exp)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'ldexp' only accept floating-point inputs");
|
||||
|
||||
return vec<4, T, P>(
|
||||
ldexp(x.x, exp.x),
|
||||
ldexp(x.y, exp.y),
|
||||
ldexp(x.z, exp.z),
|
||||
ldexp(x.w, exp.w));
|
||||
}
|
||||
}//namespace glm
|
||||
|
||||
#if GLM_ARCH != GLM_ARCH_PURE && GLM_HAS_UNRESTRICTED_UNIONS
|
||||
# include "func_common_simd.inl"
|
||||
#endif
|
||||
@@ -0,0 +1,231 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_common_simd.inl
|
||||
|
||||
#if GLM_ARCH & GLM_ARCH_SSE2_BIT
|
||||
|
||||
#include "../simd/common.h"
|
||||
|
||||
#include <immintrin.h>
|
||||
|
||||
namespace glm{
|
||||
namespace detail
|
||||
{
|
||||
template<precision P>
|
||||
struct compute_abs_vector<4, float, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const & v)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = glm_vec4_abs(v.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_abs_vector<4, int, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, int, P> call(vec<4, int, P> const & v)
|
||||
{
|
||||
vec<4, int, P> result(uninitialize);
|
||||
result.data = glm_ivec4_abs(v.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_floor<4, float, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const & v)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = glm_vec4_floor(v.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_ceil<4, float, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const & v)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = glm_vec4_ceil(v.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_fract<4, float, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const & v)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = glm_vec4_fract(v.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_round<4, float, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const & v)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = glm_vec4_round(v.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_mod<4, float, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const & x, vec<4, float, P> const & y)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = glm_vec4_mod(x.data, y.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_min_vector<4, float, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const & v1, vec<4, float, P> const & v2)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = _mm_min_ps(v1.data, v2.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_min_vector<4, int32, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, int32, P> call(vec<4, int32, P> const & v1, vec<4, int32, P> const & v2)
|
||||
{
|
||||
vec<4, int32, P> result(uninitialize);
|
||||
result.data = _mm_min_epi32(v1.data, v2.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_min_vector<4, uint32, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, int32, P> call(vec<4, uint32, P> const & v1, vec<4, uint32, P> const & v2)
|
||||
{
|
||||
vec<4, uint32, P> result(uninitialize);
|
||||
result.data = _mm_min_epu32(v1.data, v2.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_max_vector<4, float, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const & v1, vec<4, float, P> const & v2)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = _mm_max_ps(v1.data, v2.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_max_vector<4, int32, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, int32, P> call(vec<4, int32, P> const & v1, vec<4, int32, P> const & v2)
|
||||
{
|
||||
vec<4, int32, P> result(uninitialize);
|
||||
result.data = _mm_max_epi32(v1.data, v2.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_max_vector<4, uint32, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, uint32, P> call(vec<4, uint32, P> const & v1, vec<4, uint32, P> const & v2)
|
||||
{
|
||||
vec<4, uint32, P> result(uninitialize);
|
||||
result.data = _mm_max_epu32(v1.data, v2.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_clamp_vector<4, float, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const & x, vec<4, float, P> const & minVal, vec<4, float, P> const & maxVal)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = _mm_min_ps(_mm_max_ps(x.data, minVal.data), maxVal.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_clamp_vector<4, int32, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, int32, P> call(vec<4, int32, P> const & x, vec<4, int32, P> const & minVal, vec<4, int32, P> const & maxVal)
|
||||
{
|
||||
vec<4, int32, P> result(uninitialize);
|
||||
result.data = _mm_min_epi32(_mm_max_epi32(x.data, minVal.data), maxVal.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_clamp_vector<4, uint32, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, uint32, P> call(vec<4, uint32, P> const & x, vec<4, uint32, P> const & minVal, vec<4, uint32, P> const & maxVal)
|
||||
{
|
||||
vec<4, uint32, P> result(uninitialize);
|
||||
result.data = _mm_min_epu32(_mm_max_epu32(x.data, minVal.data), maxVal.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_mix_vector<4, float, bool, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const & x, vec<4, float, P> const & y, vec<4, bool, P> const & a)
|
||||
{
|
||||
__m128i const Load = _mm_set_epi32(-static_cast<int>(a.w), -static_cast<int>(a.z), -static_cast<int>(a.y), -static_cast<int>(a.x));
|
||||
__m128 const Mask = _mm_castsi128_ps(Load);
|
||||
|
||||
vec<4, float, P> Result(uninitialize);
|
||||
# if 0 && GLM_ARCH & GLM_ARCH_AVX
|
||||
Result.data = _mm_blendv_ps(x.data, y.data, Mask);
|
||||
# else
|
||||
Result.data = _mm_or_ps(_mm_and_ps(Mask, y.data), _mm_andnot_ps(Mask, x.data));
|
||||
# endif
|
||||
return Result;
|
||||
}
|
||||
};
|
||||
/* FIXME
|
||||
template<precision P>
|
||||
struct compute_step_vector<float, P, tvec4>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const& edge, vec<4, float, P> const& x)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = glm_vec4_step(edge.data, x.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
*/
|
||||
template<precision P>
|
||||
struct compute_smoothstep_vector<4, float, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const& edge0, vec<4, float, P> const& edge1, vec<4, float, P> const& x)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = glm_vec4_smoothstep(edge0.data, edge1.data, x.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}//namespace detail
|
||||
}//namespace glm
|
||||
|
||||
#endif//GLM_ARCH & GLM_ARCH_SSE2_BIT
|
||||
@@ -0,0 +1,103 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_exponential.hpp
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.2 Exponential Functions</a>
|
||||
///
|
||||
/// @defgroup core_func_exponential Exponential functions
|
||||
/// @ingroup core
|
||||
///
|
||||
/// These all operate component-wise. The description is per component.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "type_vec1.hpp"
|
||||
#include "type_vec2.hpp"
|
||||
#include "type_vec3.hpp"
|
||||
#include "type_vec4.hpp"
|
||||
#include <cmath>
|
||||
|
||||
namespace glm
|
||||
{
|
||||
/// @addtogroup core_func_exponential
|
||||
/// @{
|
||||
|
||||
/// Returns 'base' raised to the power 'exponent'.
|
||||
///
|
||||
/// @param base Floating point value. pow function is defined for input values of 'base' defined in the range (inf-, inf+) in the limit of the type precision.
|
||||
/// @param exponent Floating point value representing the 'exponent'.
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/pow.xml">GLSL pow man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.2 Exponential Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> pow(vecType<L, T, P> const & base, vecType<L, T, P> const & exponent);
|
||||
|
||||
/// Returns the natural exponentiation of x, i.e., e^x.
|
||||
///
|
||||
/// @param v exp function is defined for input values of v defined in the range (inf-, inf+) in the limit of the type precision.
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/exp.xml">GLSL exp man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.2 Exponential Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> exp(vecType<L, T, P> const & v);
|
||||
|
||||
/// Returns the natural logarithm of v, i.e.,
|
||||
/// returns the value y which satisfies the equation x = e^y.
|
||||
/// Results are undefined if v <= 0.
|
||||
///
|
||||
/// @param v log function is defined for input values of v defined in the range (0, inf+) in the limit of the type precision.
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/log.xml">GLSL log man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.2 Exponential Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> log(vecType<L, T, P> const & v);
|
||||
|
||||
/// Returns 2 raised to the v power.
|
||||
///
|
||||
/// @param v exp2 function is defined for input values of v defined in the range (inf-, inf+) in the limit of the type precision.
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/exp2.xml">GLSL exp2 man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.2 Exponential Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> exp2(vecType<L, T, P> const & v);
|
||||
|
||||
/// Returns the base 2 log of x, i.e., returns the value y,
|
||||
/// which satisfies the equation x = 2 ^ y.
|
||||
///
|
||||
/// @param v log2 function is defined for input values of v defined in the range (0, inf+) in the limit of the type precision.
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/log2.xml">GLSL log2 man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.2 Exponential Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> log2(vecType<L, T, P> const & v);
|
||||
|
||||
/// Returns the positive square root of v.
|
||||
///
|
||||
/// @param v sqrt function is defined for input values of v defined in the range [0, inf+) in the limit of the type precision.
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/sqrt.xml">GLSL sqrt man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.2 Exponential Functions</a>
|
||||
//template<typename genType>
|
||||
//GLM_FUNC_DECL genType sqrt(genType const & x);
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> sqrt(vecType<L, T, P> const & v);
|
||||
|
||||
/// Returns the reciprocal of the positive square root of v.
|
||||
///
|
||||
/// @param v inversesqrt function is defined for input values of v defined in the range [0, inf+) in the limit of the type precision.
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/inversesqrt.xml">GLSL inversesqrt man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.2 Exponential Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> inversesqrt(vecType<L, T, P> const & v);
|
||||
|
||||
/// @}
|
||||
}//namespace glm
|
||||
|
||||
#include "func_exponential.inl"
|
||||
@@ -0,0 +1,146 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_exponential.inl
|
||||
|
||||
#include "func_vector_relational.hpp"
|
||||
#include "_vectorize.hpp"
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
|
||||
namespace glm{
|
||||
namespace detail
|
||||
{
|
||||
# if GLM_HAS_CXX11_STL
|
||||
using std::log2;
|
||||
# else
|
||||
template<typename genType>
|
||||
genType log2(genType Value)
|
||||
{
|
||||
return std::log(Value) * static_cast<genType>(1.4426950408889634073599246810019);
|
||||
}
|
||||
# endif
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, class, precision> class vecType, bool isFloat, bool Aligned>
|
||||
struct compute_log2
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<L, T, P> call(vec<L, T, P> const& v)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(log2, v);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, bool Aligned>
|
||||
struct compute_sqrt
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<L, T, P> call(vec<L, T, P> const& x)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(std::sqrt, x);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, bool Aligned>
|
||||
struct compute_inversesqrt
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<L, T, P> call(vec<L, T, P> const & x)
|
||||
{
|
||||
return static_cast<T>(1) / sqrt(x);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, bool Aligned>
|
||||
struct compute_inversesqrt<L, float, lowp, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<L, float, lowp> call(vec<L, float, lowp> const & x)
|
||||
{
|
||||
vec<L, float, lowp> tmp(x);
|
||||
vec<L, float, lowp> xhalf(tmp * 0.5f);
|
||||
vec<L, uint, lowp>* p = reinterpret_cast<vec<L, uint, lowp>*>(const_cast<vec<L, float, lowp>*>(&x));
|
||||
vec<L, uint, lowp> i = vec<L, uint, lowp>(0x5f375a86) - (*p >> vec<L, uint, lowp>(1));
|
||||
vec<L, float, lowp>* ptmp = reinterpret_cast<vec<L, float, lowp>*>(&i);
|
||||
tmp = *ptmp;
|
||||
tmp = tmp * (1.5f - xhalf * tmp * tmp);
|
||||
return tmp;
|
||||
}
|
||||
};
|
||||
}//namespace detail
|
||||
|
||||
// pow
|
||||
using std::pow;
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> pow(vecType<L, T, P> const & base, vecType<L, T, P> const& exponent)
|
||||
{
|
||||
return detail::functor2<L, T, P>::call(pow, base, exponent);
|
||||
}
|
||||
|
||||
// exp
|
||||
using std::exp;
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> exp(vecType<L, T, P> const& x)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(exp, x);
|
||||
}
|
||||
|
||||
// log
|
||||
using std::log;
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> log(vecType<L, T, P> const& x)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(log, x);
|
||||
}
|
||||
|
||||
//exp2, ln2 = 0.69314718055994530941723212145818f
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType exp2(genType x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'exp2' only accept floating-point inputs");
|
||||
|
||||
return std::exp(static_cast<genType>(0.69314718055994530941723212145818) * x);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> exp2(vecType<L, T, P> const& x)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(exp2, x);
|
||||
}
|
||||
|
||||
// log2, ln2 = 0.69314718055994530941723212145818f
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType log2(genType x)
|
||||
{
|
||||
return log2(vec<1, genType>(x)).x;
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> log2(vecType<L, T, P> const& x)
|
||||
{
|
||||
return detail::compute_log2<L, T, P, vecType, std::numeric_limits<T>::is_iec559, detail::is_aligned<P>::value>::call(x);
|
||||
}
|
||||
|
||||
// sqrt
|
||||
using std::sqrt;
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> sqrt(vecType<L, T, P> const& x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'sqrt' only accept floating-point inputs");
|
||||
return detail::compute_sqrt<L, T, P, detail::is_aligned<P>::value>::call(x);
|
||||
}
|
||||
|
||||
// inversesqrt
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType inversesqrt(genType x)
|
||||
{
|
||||
return static_cast<genType>(1) / sqrt(x);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> inversesqrt(vecType<L, T, P> const& x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'inversesqrt' only accept floating-point inputs");
|
||||
return detail::compute_inversesqrt<L, T, P, detail::is_aligned<P>::value>::call(x);
|
||||
}
|
||||
}//namespace glm
|
||||
|
||||
#if GLM_ARCH != GLM_ARCH_PURE && GLM_HAS_UNRESTRICTED_UNIONS
|
||||
# include "func_exponential_simd.inl"
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_exponential_simd.inl
|
||||
|
||||
#include "../simd/exponential.h"
|
||||
|
||||
#if GLM_ARCH & GLM_ARCH_SSE2_BIT
|
||||
|
||||
namespace glm{
|
||||
namespace detail
|
||||
{
|
||||
template<precision P>
|
||||
struct compute_sqrt<4, float, P, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const & v)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = _mm_sqrt_ps(v.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct compute_sqrt<4, float, aligned_lowp, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, aligned_lowp> call(vec<4, float, aligned_lowp> const & v)
|
||||
{
|
||||
vec<4, float, aligned_lowp> result(uninitialize);
|
||||
result.data = glm_vec4_sqrt_lowp(v.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}//namespace detail
|
||||
}//namespace glm
|
||||
|
||||
#endif//GLM_ARCH & GLM_ARCH_SSE2_BIT
|
||||
@@ -0,0 +1,113 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_geometric.hpp
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.5 Geometric Functions</a>
|
||||
///
|
||||
/// @defgroup core_func_geometric Geometric functions
|
||||
/// @ingroup core
|
||||
///
|
||||
/// These operate on vectors as vectors, not component-wise.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "type_vec3.hpp"
|
||||
|
||||
namespace glm
|
||||
{
|
||||
/// @addtogroup core_func_geometric
|
||||
/// @{
|
||||
|
||||
/// Returns the length of x, i.e., sqrt(x * x).
|
||||
///
|
||||
/// @tparam genType Floating-point vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/length.xml">GLSL length man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.5 Geometric Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL T length(
|
||||
vecType<L, T, P> const& x);
|
||||
|
||||
/// Returns the distance betwwen p0 and p1, i.e., length(p0 - p1).
|
||||
///
|
||||
/// @tparam genType Floating-point vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/distance.xml">GLSL distance man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.5 Geometric Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL T distance(
|
||||
vecType<L, T, P> const& p0,
|
||||
vecType<L, T, P> const& p1);
|
||||
|
||||
/// Returns the dot product of x and y, i.e., result = x * y.
|
||||
///
|
||||
/// @tparam genType Floating-point vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/dot.xml">GLSL dot man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.5 Geometric Functions</a>
|
||||
template<length_t L, typename T, precision P>
|
||||
GLM_FUNC_DECL T dot(
|
||||
vec<L, T, P> const & x,
|
||||
vec<L, T, P> const & y);
|
||||
|
||||
/// Returns the cross product of x and y.
|
||||
///
|
||||
/// @tparam valType Floating-point scalar types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/cross.xml">GLSL cross man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.5 Geometric Functions</a>
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_DECL vec<3, T, P> cross(
|
||||
vec<3, T, P> const & x,
|
||||
vec<3, T, P> const & y);
|
||||
|
||||
/// Returns a vector in the same direction as x but with length of 1.
|
||||
/// According to issue 10 GLSL 1.10 specification, if length(x) == 0 then result is undefined and generate an error.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/normalize.xml">GLSL normalize man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.5 Geometric Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> normalize(
|
||||
vecType<L, T, P> const& x);
|
||||
|
||||
/// If dot(Nref, I) < 0.0, return N, otherwise, return -N.
|
||||
///
|
||||
/// @tparam genType Floating-point vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/faceforward.xml">GLSL faceforward man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.5 Geometric Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> faceforward(
|
||||
vecType<L, T, P> const& N,
|
||||
vecType<L, T, P> const& I,
|
||||
vecType<L, T, P> const& Nref);
|
||||
|
||||
/// For the incident vector I and surface orientation N,
|
||||
/// returns the reflection direction : result = I - 2.0 * dot(N, I) * N.
|
||||
///
|
||||
/// @tparam genType Floating-point vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/reflect.xml">GLSL reflect man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.5 Geometric Functions</a>
|
||||
template<typename genType>
|
||||
GLM_FUNC_DECL genType reflect(
|
||||
genType const & I,
|
||||
genType const & N);
|
||||
|
||||
/// For the incident vector I and surface normal N,
|
||||
/// and the ratio of indices of refraction eta,
|
||||
/// return the refraction vector.
|
||||
///
|
||||
/// @tparam genType Floating-point vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/refract.xml">GLSL refract man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.5 Geometric Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> refract(
|
||||
vecType<L, T, P> const& I,
|
||||
vecType<L, T, P> const& N,
|
||||
T eta);
|
||||
|
||||
/// @}
|
||||
}//namespace glm
|
||||
|
||||
#include "func_geometric.inl"
|
||||
@@ -0,0 +1,254 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_geometric.inl
|
||||
|
||||
#include "func_exponential.hpp"
|
||||
#include "func_common.hpp"
|
||||
#include "type_vec2.hpp"
|
||||
#include "type_vec4.hpp"
|
||||
#include "type_float.hpp"
|
||||
|
||||
namespace glm{
|
||||
namespace detail
|
||||
{
|
||||
template<template<length_t, typename, precision> class vecType, length_t L, typename T, precision P, bool Aligned>
|
||||
struct compute_length
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static T call(vecType<L, T, P> const & v)
|
||||
{
|
||||
return sqrt(dot(v, v));
|
||||
}
|
||||
};
|
||||
|
||||
template<template<length_t, typename, precision> class vecType, length_t L, typename T, precision P, bool Aligned>
|
||||
struct compute_distance
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static T call(vecType<L, T, P> const & p0, vecType<L, T, P> const & p1)
|
||||
{
|
||||
return length(p1 - p0);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename V, typename T, bool Aligned>
|
||||
struct compute_dot{};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_dot<vec<1, T, P>, T, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static T call(vec<1, T, P> const & a, vec<1, T, P> const & b)
|
||||
{
|
||||
return a.x * b.x;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_dot<vec<2, T, P>, T, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static T call(vec<2, T, P> const & a, vec<2, T, P> const & b)
|
||||
{
|
||||
vec<2, T, P> tmp(a * b);
|
||||
return tmp.x + tmp.y;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_dot<vec<3, T, P>, T, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static T call(vec<3, T, P> const & a, vec<3, T, P> const & b)
|
||||
{
|
||||
vec<3, T, P> tmp(a * b);
|
||||
return tmp.x + tmp.y + tmp.z;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_dot<vec<4, T, P>, T, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static T call(vec<4, T, P> const & a, vec<4, T, P> const & b)
|
||||
{
|
||||
vec<4, T, P> tmp(a * b);
|
||||
return (tmp.x + tmp.y) + (tmp.z + tmp.w);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_cross
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<3, T, P> call(vec<3, T, P> const & x, vec<3, T, P> const & y)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'cross' accepts only floating-point inputs");
|
||||
|
||||
return vec<3, T, P>(
|
||||
x.y * y.z - y.y * x.z,
|
||||
x.z * y.x - y.z * x.x,
|
||||
x.x * y.y - y.x * x.y);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_normalize
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & v)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'normalize' accepts only floating-point inputs");
|
||||
|
||||
return v * inversesqrt(dot(v, v));
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_faceforward
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & N, vecType<L, T, P> const & I, vecType<L, T, P> const & Nref)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'normalize' accepts only floating-point inputs");
|
||||
|
||||
return dot(Nref, I) < static_cast<T>(0) ? N : -N;
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_reflect
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & I, vecType<L, T, P> const & N)
|
||||
{
|
||||
return I - N * dot(N, I) * static_cast<T>(2);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_refract
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & I, vecType<L, T, P> const & N, T eta)
|
||||
{
|
||||
T const dotValue(dot(N, I));
|
||||
T const k(static_cast<T>(1) - eta * eta * (static_cast<T>(1) - dotValue * dotValue));
|
||||
return (eta * I - (eta * dotValue + std::sqrt(k)) * N) * static_cast<T>(k >= static_cast<T>(0));
|
||||
}
|
||||
};
|
||||
}//namespace detail
|
||||
|
||||
// length
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType length(genType x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'length' accepts only floating-point inputs");
|
||||
|
||||
return abs(x);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER T length(vecType<L, T, P> const & v)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'length' accepts only floating-point inputs");
|
||||
|
||||
return detail::compute_length<vecType, L, T, P, detail::is_aligned<P>::value>::call(v);
|
||||
}
|
||||
|
||||
// distance
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType distance(genType const & p0, genType const & p1)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'distance' accepts only floating-point inputs");
|
||||
|
||||
return length(p1 - p0);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER T distance(vecType<L, T, P> const & p0, vecType<L, T, P> const & p1)
|
||||
{
|
||||
return detail::compute_distance<vecType, L, T, P, detail::is_aligned<P>::value>::call(p0, p1);
|
||||
}
|
||||
|
||||
// dot
|
||||
template<typename T>
|
||||
GLM_FUNC_QUALIFIER T dot(T x, T y)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'dot' accepts only floating-point inputs");
|
||||
return x * y;
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER T dot(vec<L, T, P> const & x, vec<L, T, P> const & y)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'dot' accepts only floating-point inputs");
|
||||
return detail::compute_dot<vec<L, T, P>, T, detail::is_aligned<P>::value>::call(x, y);
|
||||
}
|
||||
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER T dot(tquat<T, P> const & x, tquat<T, P> const & y)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'dot' accepts only floating-point inputs");
|
||||
return detail::compute_dot<tquat<T, P>, T, detail::is_aligned<P>::value>::call(x, y);
|
||||
}
|
||||
|
||||
// cross
|
||||
template<typename T, precision P>
|
||||
GLM_FUNC_QUALIFIER vec<3, T, P> cross(vec<3, T, P> const & x, vec<3, T, P> const & y)
|
||||
{
|
||||
return detail::compute_cross<T, P, detail::is_aligned<P>::value>::call(x, y);
|
||||
}
|
||||
|
||||
// normalize
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType normalize(genType const & x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'normalize' accepts only floating-point inputs");
|
||||
|
||||
return x < genType(0) ? genType(-1) : genType(1);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> normalize(vecType<L, T, P> const & x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'normalize' accepts only floating-point inputs");
|
||||
|
||||
return detail::compute_normalize<L, T, P, vecType, detail::is_aligned<P>::value>::call(x);
|
||||
}
|
||||
|
||||
// faceforward
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType faceforward(genType const & N, genType const & I, genType const & Nref)
|
||||
{
|
||||
return dot(Nref, I) < static_cast<genType>(0) ? N : -N;
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> faceforward(vecType<L, T, P> const & N, vecType<L, T, P> const & I, vecType<L, T, P> const & Nref)
|
||||
{
|
||||
return detail::compute_faceforward<L, T, P, vecType, detail::is_aligned<P>::value>::call(N, I, Nref);
|
||||
}
|
||||
|
||||
// reflect
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType reflect(genType const & I, genType const & N)
|
||||
{
|
||||
return I - N * dot(N, I) * genType(2);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> reflect(vecType<L, T, P> const & I, vecType<L, T, P> const & N)
|
||||
{
|
||||
return detail::compute_reflect<L, T, P, vecType, detail::is_aligned<P>::value>::call(I, N);
|
||||
}
|
||||
|
||||
// refract
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType refract(genType const & I, genType const & N, genType eta)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'refract' accepts only floating-point inputs");
|
||||
genType const dotValue(dot(N, I));
|
||||
genType const k(static_cast<genType>(1) - eta * eta * (static_cast<genType>(1) - dotValue * dotValue));
|
||||
return (eta * I - (eta * dotValue + sqrt(k)) * N) * static_cast<genType>(k >= static_cast<genType>(0));
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> refract(vecType<L, T, P> const & I, vecType<L, T, P> const & N, T eta)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559, "'refract' accepts only floating-point inputs");
|
||||
return detail::compute_refract<L, T, P, vecType, detail::is_aligned<P>::value>::call(I, N, eta);
|
||||
}
|
||||
}//namespace glm
|
||||
|
||||
#if GLM_ARCH != GLM_ARCH_PURE && GLM_HAS_UNRESTRICTED_UNIONS
|
||||
# include "func_geometric_simd.inl"
|
||||
#endif
|
||||
@@ -0,0 +1,99 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_geometric_simd.inl
|
||||
|
||||
#include "../simd/geometric.h"
|
||||
|
||||
#if GLM_ARCH & GLM_ARCH_SSE2_BIT
|
||||
|
||||
namespace glm{
|
||||
namespace detail
|
||||
{
|
||||
template<precision P>
|
||||
struct compute_length<vec, 4, float, P, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static float call(vec<4, float, P> const & v)
|
||||
{
|
||||
return _mm_cvtss_f32(glm_vec4_length(v.data));
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_distance<vec, 4, float, P, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static float call(vec<4, float, P> const & p0, vec<4, float, P> const & p1)
|
||||
{
|
||||
return _mm_cvtss_f32(glm_vec4_distance(p0.data, p1.data));
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_dot<vec<4, float, P>, float, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static float call(vec<4, float, P> const& x, vec<4, float, P> const& y)
|
||||
{
|
||||
return _mm_cvtss_f32(glm_vec1_dot(x.data, y.data));
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_cross<float, P, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<3, float, P> call(vec<3, float, P> const & a, vec<3, float, P> const & b)
|
||||
{
|
||||
__m128 const set0 = _mm_set_ps(0.0f, a.z, a.y, a.x);
|
||||
__m128 const set1 = _mm_set_ps(0.0f, b.z, b.y, b.x);
|
||||
__m128 const xpd0 = glm_vec4_cross(set0, set1);
|
||||
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = xpd0;
|
||||
return vec<3, float, P>(result);
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_normalize<4, float, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const & v)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = glm_vec4_normalize(v.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_faceforward<4, float, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const& N, vec<4, float, P> const& I, vec<4, float, P> const& Nref)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = glm_vec4_faceforward(N.data, I.data, Nref.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_reflect<4, float, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const& I, vec<4, float, P> const& N)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = glm_vec4_reflect(I.data, N.data);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_refract<4, float, P, vec, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, float, P> call(vec<4, float, P> const& I, vec<4, float, P> const& N, float eta)
|
||||
{
|
||||
vec<4, float, P> result(uninitialize);
|
||||
result.data = glm_vec4_refract(I.data, N.data, _mm_set1_ps(eta));
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}//namespace detail
|
||||
}//namespace glm
|
||||
|
||||
#endif//GLM_ARCH & GLM_ARCH_SSE2_BIT
|
||||
@@ -0,0 +1,203 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_integer.hpp
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.8 Integer Functions</a>
|
||||
///
|
||||
/// @defgroup core_func_integer Integer functions
|
||||
/// @ingroup core
|
||||
///
|
||||
/// These all operate component-wise. The description is per component.
|
||||
/// The notation [a, b] means the set of bits from bit-number a through bit-number
|
||||
/// b, inclusive. The lowest-order bit is bit 0.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "setup.hpp"
|
||||
#include "precision.hpp"
|
||||
#include "func_common.hpp"
|
||||
#include "func_vector_relational.hpp"
|
||||
|
||||
namespace glm
|
||||
{
|
||||
/// @addtogroup core_func_integer
|
||||
/// @{
|
||||
|
||||
/// Adds 32-bit unsigned integer x and y, returning the sum
|
||||
/// modulo pow(2, 32). The value carry is set to 0 if the sum was
|
||||
/// less than pow(2, 32), or to 1 otherwise.
|
||||
///
|
||||
/// @tparam genUType Unsigned integer scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/uaddCarry.xml">GLSL uaddCarry man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.8 Integer Functions</a>
|
||||
template<length_t L, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, uint, P> uaddCarry(
|
||||
vecType<L, uint, P> const & x,
|
||||
vecType<L, uint, P> const & y,
|
||||
vecType<L, uint, P> & carry);
|
||||
|
||||
/// Subtracts the 32-bit unsigned integer y from x, returning
|
||||
/// the difference if non-negative, or pow(2, 32) plus the difference
|
||||
/// otherwise. The value borrow is set to 0 if x >= y, or to 1 otherwise.
|
||||
///
|
||||
/// @tparam genUType Unsigned integer scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/usubBorrow.xml">GLSL usubBorrow man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.8 Integer Functions</a>
|
||||
template<length_t L, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, uint, P> usubBorrow(
|
||||
vecType<L, uint, P> const & x,
|
||||
vecType<L, uint, P> const & y,
|
||||
vecType<L, uint, P> & borrow);
|
||||
|
||||
/// Multiplies 32-bit integers x and y, producing a 64-bit
|
||||
/// result. The 32 least-significant bits are returned in lsb.
|
||||
/// The 32 most-significant bits are returned in msb.
|
||||
///
|
||||
/// @tparam genUType Unsigned integer scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/umulExtended.xml">GLSL umulExtended man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.8 Integer Functions</a>
|
||||
template<length_t L, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL void umulExtended(
|
||||
vecType<L, uint, P> const & x,
|
||||
vecType<L, uint, P> const & y,
|
||||
vecType<L, uint, P> & msb,
|
||||
vecType<L, uint, P> & lsb);
|
||||
|
||||
/// Multiplies 32-bit integers x and y, producing a 64-bit
|
||||
/// result. The 32 least-significant bits are returned in lsb.
|
||||
/// The 32 most-significant bits are returned in msb.
|
||||
///
|
||||
/// @tparam genIType Signed integer scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/imulExtended.xml">GLSL imulExtended man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.8 Integer Functions</a>
|
||||
template<length_t L, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL void imulExtended(
|
||||
vecType<L, int, P> const & x,
|
||||
vecType<L, int, P> const & y,
|
||||
vecType<L, int, P> & msb,
|
||||
vecType<L, int, P> & lsb);
|
||||
|
||||
/// Extracts bits [offset, offset + bits - 1] from value,
|
||||
/// returning them in the least significant bits of the result.
|
||||
/// For unsigned data types, the most significant bits of the
|
||||
/// result will be set to zero. For signed data types, the
|
||||
/// most significant bits will be set to the value of bit offset + base - 1.
|
||||
///
|
||||
/// If bits is zero, the result will be zero. The result will be
|
||||
/// undefined if offset or bits is negative, or if the sum of
|
||||
/// offset and bits is greater than the number of bits used
|
||||
/// to store the operand.
|
||||
///
|
||||
/// @tparam T Signed or unsigned integer scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/bitfieldExtract.xml">GLSL bitfieldExtract man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.8 Integer Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> bitfieldExtract(
|
||||
vecType<L, T, P> const& Value,
|
||||
int Offset,
|
||||
int Bits);
|
||||
|
||||
/// Returns the insertion the bits least-significant bits of insert into base.
|
||||
///
|
||||
/// The result will have bits [offset, offset + bits - 1] taken
|
||||
/// from bits [0, bits - 1] of insert, and all other bits taken
|
||||
/// directly from the corresponding bits of base. If bits is
|
||||
/// zero, the result will simply be base. The result will be
|
||||
/// undefined if offset or bits is negative, or if the sum of
|
||||
/// offset and bits is greater than the number of bits used to
|
||||
/// store the operand.
|
||||
///
|
||||
/// @tparam T Signed or unsigned integer scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/bitfieldInsert.xml">GLSL bitfieldInsert man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.8 Integer Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> bitfieldInsert(
|
||||
vecType<L, T, P> const& Base,
|
||||
vecType<L, T, P> const& Insert,
|
||||
int Offset,
|
||||
int Bits);
|
||||
|
||||
/// Returns the reversal of the bits of value.
|
||||
/// The bit numbered n of the result will be taken from bit (bits - 1) - n of value,
|
||||
/// where bits is the total number of bits used to represent value.
|
||||
///
|
||||
/// @tparam T Signed or unsigned integer scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/bitfieldReverse.xml">GLSL bitfieldReverse man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.8 Integer Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> bitfieldReverse(vecType<L, T, P> const & v);
|
||||
|
||||
/// Returns the number of bits set to 1 in the binary representation of value.
|
||||
///
|
||||
/// @tparam T Signed or unsigned integer scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/bitCount.xml">GLSL bitCount man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.8 Integer Functions</a>
|
||||
template<typename genType>
|
||||
GLM_FUNC_DECL int bitCount(genType v);
|
||||
|
||||
/// Returns the number of bits set to 1 in the binary representation of value.
|
||||
///
|
||||
/// @tparam T Signed or unsigned integer scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/bitCount.xml">GLSL bitCount man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.8 Integer Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, int, P> bitCount(vecType<L, T, P> const & v);
|
||||
|
||||
/// Returns the bit number of the least significant bit set to
|
||||
/// 1 in the binary representation of value.
|
||||
/// If value is zero, -1 will be returned.
|
||||
///
|
||||
/// @tparam T Signed or unsigned integer scalar types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/findLSB.xml">GLSL findLSB man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.8 Integer Functions</a>
|
||||
template<typename genIUType>
|
||||
GLM_FUNC_DECL int findLSB(genIUType x);
|
||||
|
||||
/// Returns the bit number of the least significant bit set to
|
||||
/// 1 in the binary representation of value.
|
||||
/// If value is zero, -1 will be returned.
|
||||
///
|
||||
/// @tparam T Signed or unsigned integer scalar types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/findLSB.xml">GLSL findLSB man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.8 Integer Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, int, P> findLSB(vecType<L, T, P> const & v);
|
||||
|
||||
/// Returns the bit number of the most significant bit in the binary representation of value.
|
||||
/// For positive integers, the result will be the bit number of the most significant bit set to 1.
|
||||
/// For negative integers, the result will be the bit number of the most significant
|
||||
/// bit set to 0. For a value of zero or negative one, -1 will be returned.
|
||||
///
|
||||
/// @tparam T Signed or unsigned integer scalar types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/findMSB.xml">GLSL findMSB man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.8 Integer Functions</a>
|
||||
template<typename genIUType>
|
||||
GLM_FUNC_DECL int findMSB(genIUType x);
|
||||
|
||||
/// Returns the bit number of the most significant bit in the binary representation of value.
|
||||
/// For positive integers, the result will be the bit number of the most significant bit set to 1.
|
||||
/// For negative integers, the result will be the bit number of the most significant
|
||||
/// bit set to 0. For a value of zero or negative one, -1 will be returned.
|
||||
///
|
||||
/// @tparam T Signed or unsigned integer scalar types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/findMSB.xml">GLSL findMSB man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.8 Integer Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, int, P> findMSB(vecType<L, T, P> const & v);
|
||||
|
||||
/// @}
|
||||
}//namespace glm
|
||||
|
||||
#include "func_integer.inl"
|
||||
@@ -0,0 +1,375 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_integer.inl
|
||||
|
||||
#include "type_vec2.hpp"
|
||||
#include "type_vec3.hpp"
|
||||
#include "type_vec4.hpp"
|
||||
#include "type_int.hpp"
|
||||
#include "_vectorize.hpp"
|
||||
#if(GLM_ARCH & GLM_ARCH_X86 && GLM_COMPILER & GLM_COMPILER_VC)
|
||||
# include <intrin.h>
|
||||
# pragma intrinsic(_BitScanReverse)
|
||||
#endif//(GLM_ARCH & GLM_ARCH_X86 && GLM_COMPILER & GLM_COMPILER_VC)
|
||||
#include <limits>
|
||||
|
||||
#if !GLM_HAS_EXTENDED_INTEGER_TYPE
|
||||
# if GLM_COMPILER & GLM_COMPILER_GCC
|
||||
# pragma GCC diagnostic ignored "-Wlong-long"
|
||||
# endif
|
||||
# if (GLM_COMPILER & GLM_COMPILER_CLANG)
|
||||
# pragma clang diagnostic ignored "-Wc++11-long-long"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace glm{
|
||||
namespace detail
|
||||
{
|
||||
template<typename T>
|
||||
GLM_FUNC_QUALIFIER T mask(T Bits)
|
||||
{
|
||||
return Bits >= sizeof(T) * 8 ? ~static_cast<T>(0) : (static_cast<T>(1) << Bits) - static_cast<T>(1);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, glm::precision P, template<length_t, typename, precision> class vecType, bool Aligned, bool EXEC>
|
||||
struct compute_bitfieldReverseStep
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const& v, T, T)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, glm::precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_bitfieldReverseStep<L, T, P, vecType, Aligned, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const& v, T Mask, T Shift)
|
||||
{
|
||||
return (v & Mask) << Shift | (v & (~Mask)) >> Shift;
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, glm::precision P, template<length_t, typename, precision> class vecType, bool Aligned, bool EXEC>
|
||||
struct compute_bitfieldBitCountStep
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const& v, T, T)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, glm::precision P, template<length_t, typename, precision> class vecType, bool Aligned>
|
||||
struct compute_bitfieldBitCountStep<L, T, P, vecType, Aligned, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const& v, T Mask, T Shift)
|
||||
{
|
||||
return (v & Mask) + ((v >> Shift) & Mask);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename genIUType, size_t Bits>
|
||||
struct compute_findLSB
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static int call(genIUType Value)
|
||||
{
|
||||
if(Value == 0)
|
||||
return -1;
|
||||
|
||||
return glm::bitCount(~Value & (Value - static_cast<genIUType>(1)));
|
||||
}
|
||||
};
|
||||
|
||||
# if GLM_HAS_BITSCAN_WINDOWS
|
||||
template<typename genIUType>
|
||||
struct compute_findLSB<genIUType, 32>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static int call(genIUType Value)
|
||||
{
|
||||
unsigned long Result(0);
|
||||
unsigned char IsNotNull = _BitScanForward(&Result, *reinterpret_cast<unsigned long*>(&Value));
|
||||
return IsNotNull ? int(Result) : -1;
|
||||
}
|
||||
};
|
||||
|
||||
# if !((GLM_COMPILER & GLM_COMPILER_VC) && (GLM_MODEL == GLM_MODEL_32))
|
||||
template<typename genIUType>
|
||||
struct compute_findLSB<genIUType, 64>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static int call(genIUType Value)
|
||||
{
|
||||
unsigned long Result(0);
|
||||
unsigned char IsNotNull = _BitScanForward64(&Result, *reinterpret_cast<unsigned __int64*>(&Value));
|
||||
return IsNotNull ? int(Result) : -1;
|
||||
}
|
||||
};
|
||||
# endif
|
||||
# endif//GLM_HAS_BITSCAN_WINDOWS
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, bool EXEC = true>
|
||||
struct compute_findMSB_step_vec
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const & x, T Shift)
|
||||
{
|
||||
return x | (x >> Shift);
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
struct compute_findMSB_step_vec<L, T, P, vecType, false>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, T, P> call(vecType<L, T, P> const& x, T)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType, int>
|
||||
struct compute_findMSB_vec
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, int, P> call(vecType<L, T, P> const& v)
|
||||
{
|
||||
vecType<L, T, P> x(v);
|
||||
x = compute_findMSB_step_vec<L, T, P, vecType, sizeof(T) * 8 >= 8>::call(x, static_cast<T>( 1));
|
||||
x = compute_findMSB_step_vec<L, T, P, vecType, sizeof(T) * 8 >= 8>::call(x, static_cast<T>( 2));
|
||||
x = compute_findMSB_step_vec<L, T, P, vecType, sizeof(T) * 8 >= 8>::call(x, static_cast<T>( 4));
|
||||
x = compute_findMSB_step_vec<L, T, P, vecType, sizeof(T) * 8 >= 16>::call(x, static_cast<T>( 8));
|
||||
x = compute_findMSB_step_vec<L, T, P, vecType, sizeof(T) * 8 >= 32>::call(x, static_cast<T>(16));
|
||||
x = compute_findMSB_step_vec<L, T, P, vecType, sizeof(T) * 8 >= 64>::call(x, static_cast<T>(32));
|
||||
return vecType<L, int, P>(sizeof(T) * 8 - 1) - glm::bitCount(~x);
|
||||
}
|
||||
};
|
||||
|
||||
# if GLM_HAS_BITSCAN_WINDOWS
|
||||
template<typename genIUType>
|
||||
GLM_FUNC_QUALIFIER int compute_findMSB_32(genIUType Value)
|
||||
{
|
||||
unsigned long Result(0);
|
||||
unsigned char IsNotNull = _BitScanReverse(&Result, *reinterpret_cast<unsigned long*>(&Value));
|
||||
return IsNotNull ? int(Result) : -1;
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
struct compute_findMSB_vec<L, T, P, vecType, 32>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, int, P> call(vecType<L, T, P> const& x)
|
||||
{
|
||||
return detail::functor1<L, int, T, P>::call(compute_findMSB_32, x);
|
||||
}
|
||||
};
|
||||
|
||||
# if !((GLM_COMPILER & GLM_COMPILER_VC) && (GLM_MODEL == GLM_MODEL_32))
|
||||
template<typename genIUType>
|
||||
GLM_FUNC_QUALIFIER int compute_findMSB_64(genIUType Value)
|
||||
{
|
||||
unsigned long Result(0);
|
||||
unsigned char IsNotNull = _BitScanReverse64(&Result, *reinterpret_cast<unsigned __int64*>(&Value));
|
||||
return IsNotNull ? int(Result) : -1;
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
struct compute_findMSB_vec<L, T, P, vecType, 64>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vecType<L, int, P> call(vecType<L, T, P> const& x)
|
||||
{
|
||||
return detail::functor1<L, int, T, P>::call(compute_findMSB_64, x);
|
||||
}
|
||||
};
|
||||
# endif
|
||||
# endif//GLM_HAS_BITSCAN_WINDOWS
|
||||
}//namespace detail
|
||||
|
||||
// uaddCarry
|
||||
GLM_FUNC_QUALIFIER uint uaddCarry(uint const & x, uint const & y, uint & Carry)
|
||||
{
|
||||
uint64 const Value64(static_cast<uint64>(x) + static_cast<uint64>(y));
|
||||
uint64 const Max32((static_cast<uint64>(1) << static_cast<uint64>(32)) - static_cast<uint64>(1));
|
||||
Carry = Value64 > Max32 ? 1u : 0u;
|
||||
return static_cast<uint32>(Value64 % (Max32 + static_cast<uint64>(1)));
|
||||
}
|
||||
|
||||
template<length_t L, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, uint, P> uaddCarry(vecType<L, uint, P> const& x, vecType<L, uint, P> const& y, vecType<L, uint, P>& Carry)
|
||||
{
|
||||
vecType<L, uint64, P> Value64(vecType<L, uint64, P>(x) + vecType<L, uint64, P>(y));
|
||||
vecType<L, uint64, P> Max32((static_cast<uint64>(1) << static_cast<uint64>(32)) - static_cast<uint64>(1));
|
||||
Carry = mix(vecType<L, uint32, P>(0), vecType<L, uint32, P>(1), greaterThan(Value64, Max32));
|
||||
return vecType<L, uint32,P>(Value64 % (Max32 + static_cast<uint64>(1)));
|
||||
}
|
||||
|
||||
// usubBorrow
|
||||
GLM_FUNC_QUALIFIER uint usubBorrow(uint const & x, uint const & y, uint & Borrow)
|
||||
{
|
||||
GLM_STATIC_ASSERT(sizeof(uint) == sizeof(uint32), "uint and uint32 size mismatch");
|
||||
|
||||
Borrow = x >= y ? static_cast<uint32>(0) : static_cast<uint32>(1);
|
||||
if(y >= x)
|
||||
return y - x;
|
||||
else
|
||||
return static_cast<uint32>((static_cast<int64>(1) << static_cast<int64>(32)) + (static_cast<int64>(y) - static_cast<int64>(x)));
|
||||
}
|
||||
|
||||
template<length_t L, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, uint, P> usubBorrow(vecType<L, uint, P> const& x, vecType<L, uint, P> const& y, vecType<L, uint, P>& Borrow)
|
||||
{
|
||||
Borrow = mix(vecType<L, uint, P>(1), vecType<L, uint, P>(0), greaterThanEqual(x, y));
|
||||
vecType<L, uint, P> const YgeX(y - x);
|
||||
vecType<L, uint, P> const XgeY(vecType<L, uint32, P>((static_cast<int64>(1) << static_cast<int64>(32)) + (vecType<L, int64, P>(y) - vecType<L, int64, P>(x))));
|
||||
return mix(XgeY, YgeX, greaterThanEqual(y, x));
|
||||
}
|
||||
|
||||
// umulExtended
|
||||
GLM_FUNC_QUALIFIER void umulExtended(uint const & x, uint const & y, uint & msb, uint & lsb)
|
||||
{
|
||||
GLM_STATIC_ASSERT(sizeof(uint) == sizeof(uint32), "uint and uint32 size mismatch");
|
||||
|
||||
uint64 Value64 = static_cast<uint64>(x) * static_cast<uint64>(y);
|
||||
msb = static_cast<uint>(Value64 >> static_cast<uint64>(32));
|
||||
lsb = static_cast<uint>(Value64);
|
||||
}
|
||||
|
||||
template<length_t L, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER void umulExtended(vecType<L, uint, P> const& x, vecType<L, uint, P> const& y, vecType<L, uint, P>& msb, vecType<L, uint, P>& lsb)
|
||||
{
|
||||
GLM_STATIC_ASSERT(sizeof(uint) == sizeof(uint32), "uint and uint32 size mismatch");
|
||||
|
||||
vecType<L, uint64, P> Value64(vecType<L, uint64, P>(x) * vecType<L, uint64, P>(y));
|
||||
msb = vecType<L, uint32, P>(Value64 >> static_cast<uint64>(32));
|
||||
lsb = vecType<L, uint32, P>(Value64);
|
||||
}
|
||||
|
||||
// imulExtended
|
||||
GLM_FUNC_QUALIFIER void imulExtended(int x, int y, int& msb, int& lsb)
|
||||
{
|
||||
GLM_STATIC_ASSERT(sizeof(int) == sizeof(int32), "int and int32 size mismatch");
|
||||
|
||||
int64 Value64 = static_cast<int64>(x) * static_cast<int64>(y);
|
||||
msb = static_cast<int>(Value64 >> static_cast<int64>(32));
|
||||
lsb = static_cast<int>(Value64);
|
||||
}
|
||||
|
||||
template<length_t L, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER void imulExtended(vecType<L, int, P> const& x, vecType<L, int, P> const& y, vecType<L, int, P>& msb, vecType<L, int, P>& lsb)
|
||||
{
|
||||
GLM_STATIC_ASSERT(sizeof(int) == sizeof(int32), "int and int32 size mismatch");
|
||||
|
||||
vecType<L, int64, P> Value64(vecType<L, int64, P>(x) * vecType<L, int64, P>(y));
|
||||
lsb = vecType<L, int32, P>(Value64 & static_cast<int64>(0xFFFFFFFF));
|
||||
msb = vecType<L, int32, P>((Value64 >> static_cast<int64>(32)) & static_cast<int64>(0xFFFFFFFF));
|
||||
}
|
||||
|
||||
// bitfieldExtract
|
||||
template<typename genIUType>
|
||||
GLM_FUNC_QUALIFIER genIUType bitfieldExtract(genIUType Value, int Offset, int Bits)
|
||||
{
|
||||
return bitfieldExtract(vec<1, genIUType>(Value), Offset, Bits).x;
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> bitfieldExtract(vecType<L, T, P> const& Value, int Offset, int Bits)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_integer, "'bitfieldExtract' only accept integer inputs");
|
||||
|
||||
return (Value >> static_cast<T>(Offset)) & static_cast<T>(detail::mask(Bits));
|
||||
}
|
||||
|
||||
// bitfieldInsert
|
||||
template<typename genIUType>
|
||||
GLM_FUNC_QUALIFIER genIUType bitfieldInsert(genIUType const & Base, genIUType const & Insert, int Offset, int Bits)
|
||||
{
|
||||
return bitfieldInsert(vec<1, genIUType>(Base), vec<1, genIUType>(Insert), Offset, Bits).x;
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> bitfieldInsert(vecType<L, T, P> const& Base, vecType<L, T, P> const& Insert, int Offset, int Bits)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_integer, "'bitfieldInsert' only accept integer values");
|
||||
|
||||
T const Mask = static_cast<T>(detail::mask(Bits) << Offset);
|
||||
return (Base & ~Mask) | (Insert & Mask);
|
||||
}
|
||||
|
||||
// bitfieldReverse
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType bitfieldReverse(genType x)
|
||||
{
|
||||
return bitfieldReverse(glm::vec<1, genType, glm::defaultp>(x)).x;
|
||||
}
|
||||
|
||||
template<length_t L, typename T, glm::precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> bitfieldReverse(vecType<L, T, P> const& v)
|
||||
{
|
||||
vecType<L, T, P> x(v);
|
||||
x = detail::compute_bitfieldReverseStep<L, T, P, vecType, detail::is_aligned<P>::value, sizeof(T) * 8>= 2>::call(x, static_cast<T>(0x5555555555555555ull), static_cast<T>( 1));
|
||||
x = detail::compute_bitfieldReverseStep<L, T, P, vecType, detail::is_aligned<P>::value, sizeof(T) * 8>= 4>::call(x, static_cast<T>(0x3333333333333333ull), static_cast<T>( 2));
|
||||
x = detail::compute_bitfieldReverseStep<L, T, P, vecType, detail::is_aligned<P>::value, sizeof(T) * 8>= 8>::call(x, static_cast<T>(0x0F0F0F0F0F0F0F0Full), static_cast<T>( 4));
|
||||
x = detail::compute_bitfieldReverseStep<L, T, P, vecType, detail::is_aligned<P>::value, sizeof(T) * 8>= 16>::call(x, static_cast<T>(0x00FF00FF00FF00FFull), static_cast<T>( 8));
|
||||
x = detail::compute_bitfieldReverseStep<L, T, P, vecType, detail::is_aligned<P>::value, sizeof(T) * 8>= 32>::call(x, static_cast<T>(0x0000FFFF0000FFFFull), static_cast<T>(16));
|
||||
x = detail::compute_bitfieldReverseStep<L, T, P, vecType, detail::is_aligned<P>::value, sizeof(T) * 8>= 64>::call(x, static_cast<T>(0x00000000FFFFFFFFull), static_cast<T>(32));
|
||||
return x;
|
||||
}
|
||||
|
||||
// bitCount
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER int bitCount(genType x)
|
||||
{
|
||||
return bitCount(glm::vec<1, genType, glm::defaultp>(x)).x;
|
||||
}
|
||||
|
||||
template<length_t L, typename T, glm::precision P, template<length_t, typename, glm::precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, int, P> bitCount(vecType<L, T, P> const& v)
|
||||
{
|
||||
#if GLM_COMPILER & GLM_COMPILER_VC
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4310) //cast truncates constant value
|
||||
#endif
|
||||
vecType<L, typename detail::make_unsigned<T>::type, P> x(*reinterpret_cast<vecType<L, typename detail::make_unsigned<T>::type, P> const *>(&v));
|
||||
x = detail::compute_bitfieldBitCountStep<L, typename detail::make_unsigned<T>::type, P, vecType, detail::is_aligned<P>::value, sizeof(T) * 8>= 2>::call(x, typename detail::make_unsigned<T>::type(0x5555555555555555ull), typename detail::make_unsigned<T>::type( 1));
|
||||
x = detail::compute_bitfieldBitCountStep<L, typename detail::make_unsigned<T>::type, P, vecType, detail::is_aligned<P>::value, sizeof(T) * 8>= 4>::call(x, typename detail::make_unsigned<T>::type(0x3333333333333333ull), typename detail::make_unsigned<T>::type( 2));
|
||||
x = detail::compute_bitfieldBitCountStep<L, typename detail::make_unsigned<T>::type, P, vecType, detail::is_aligned<P>::value, sizeof(T) * 8>= 8>::call(x, typename detail::make_unsigned<T>::type(0x0F0F0F0F0F0F0F0Full), typename detail::make_unsigned<T>::type( 4));
|
||||
x = detail::compute_bitfieldBitCountStep<L, typename detail::make_unsigned<T>::type, P, vecType, detail::is_aligned<P>::value, sizeof(T) * 8>= 16>::call(x, typename detail::make_unsigned<T>::type(0x00FF00FF00FF00FFull), typename detail::make_unsigned<T>::type( 8));
|
||||
x = detail::compute_bitfieldBitCountStep<L, typename detail::make_unsigned<T>::type, P, vecType, detail::is_aligned<P>::value, sizeof(T) * 8>= 32>::call(x, typename detail::make_unsigned<T>::type(0x0000FFFF0000FFFFull), typename detail::make_unsigned<T>::type(16));
|
||||
x = detail::compute_bitfieldBitCountStep<L, typename detail::make_unsigned<T>::type, P, vecType, detail::is_aligned<P>::value, sizeof(T) * 8>= 64>::call(x, typename detail::make_unsigned<T>::type(0x00000000FFFFFFFFull), typename detail::make_unsigned<T>::type(32));
|
||||
return vecType<L, int, P>(x);
|
||||
#if GLM_COMPILER & GLM_COMPILER_VC
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
}
|
||||
|
||||
// findLSB
|
||||
template<typename genIUType>
|
||||
GLM_FUNC_QUALIFIER int findLSB(genIUType Value)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genIUType>::is_integer, "'findLSB' only accept integer values");
|
||||
|
||||
return detail::compute_findLSB<genIUType, sizeof(genIUType) * 8>::call(Value);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, int, P> findLSB(vecType<L, T, P> const& x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_integer, "'findLSB' only accept integer values");
|
||||
|
||||
return detail::functor1<L, int, T, P>::call(findLSB, x);
|
||||
}
|
||||
|
||||
// findMSB
|
||||
template<typename genIUType>
|
||||
GLM_FUNC_QUALIFIER int findMSB(genIUType v)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genIUType>::is_integer, "'findMSB' only accept integer values");
|
||||
|
||||
return findMSB(vec<1, genIUType>(v)).x;
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, int, P> findMSB(vecType<L, T, P> const& v)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_integer, "'findMSB' only accept integer values");
|
||||
|
||||
return detail::compute_findMSB_vec<L, T, P, vecType, sizeof(T) * 8>::call(v);
|
||||
}
|
||||
}//namespace glm
|
||||
|
||||
#if GLM_ARCH != GLM_ARCH_PURE && GLM_HAS_UNRESTRICTED_UNIONS
|
||||
# include "func_integer_simd.inl"
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_integer_simd.inl
|
||||
|
||||
#include "../simd/integer.h"
|
||||
|
||||
#if GLM_ARCH & GLM_ARCH_SSE2_BIT
|
||||
|
||||
namespace glm{
|
||||
namespace detail
|
||||
{
|
||||
template<glm::precision P>
|
||||
struct compute_bitfieldReverseStep<4, uint32, P, vec, true, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, uint32, P> call(vec<4, uint32, P> const& v, uint32 Mask, uint32 Shift)
|
||||
{
|
||||
__m128i const set0 = v.data;
|
||||
|
||||
__m128i const set1 = _mm_set1_epi32(static_cast<int>(Mask));
|
||||
__m128i const and1 = _mm_and_si128(set0, set1);
|
||||
__m128i const sft1 = _mm_slli_epi32(and1, Shift);
|
||||
|
||||
__m128i const set2 = _mm_andnot_si128(set0, _mm_set1_epi32(-1));
|
||||
__m128i const and2 = _mm_and_si128(set0, set2);
|
||||
__m128i const sft2 = _mm_srai_epi32(and2, Shift);
|
||||
|
||||
__m128i const or0 = _mm_or_si128(sft1, sft2);
|
||||
|
||||
return or0;
|
||||
}
|
||||
};
|
||||
|
||||
template<glm::precision P>
|
||||
struct compute_bitfieldBitCountStep<4, uint32, P, vec, true, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static vec<4, uint32, P> call(vec<4, uint32, P> const& v, uint32 Mask, uint32 Shift)
|
||||
{
|
||||
__m128i const set0 = v.data;
|
||||
|
||||
__m128i const set1 = _mm_set1_epi32(static_cast<int>(Mask));
|
||||
__m128i const and0 = _mm_and_si128(set0, set1);
|
||||
__m128i const sft0 = _mm_slli_epi32(set0, Shift);
|
||||
__m128i const and1 = _mm_and_si128(sft0, set1);
|
||||
__m128i const add0 = _mm_add_epi32(and0, and1);
|
||||
|
||||
return add0;
|
||||
}
|
||||
};
|
||||
}//namespace detail
|
||||
|
||||
# if GLM_ARCH & GLM_ARCH_AVX_BIT
|
||||
template<>
|
||||
GLM_FUNC_QUALIFIER int bitCount(uint32 x)
|
||||
{
|
||||
return _mm_popcnt_u32(x);
|
||||
}
|
||||
|
||||
# if(GLM_MODEL == GLM_MODEL_64)
|
||||
template<>
|
||||
GLM_FUNC_QUALIFIER int bitCount(uint64 x)
|
||||
{
|
||||
return static_cast<int>(_mm_popcnt_u64(x));
|
||||
}
|
||||
# endif//GLM_MODEL
|
||||
# endif//GLM_ARCH
|
||||
|
||||
}//namespace glm
|
||||
|
||||
#endif//GLM_ARCH & GLM_ARCH_SSE2_BIT
|
||||
@@ -0,0 +1,147 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_matrix.hpp
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.6 Matrix Functions</a>
|
||||
///
|
||||
/// @defgroup core_func_matrix Matrix functions
|
||||
/// @ingroup core
|
||||
///
|
||||
/// For each of the following built-in matrix functions, there is both a
|
||||
/// single-precision floating point version, where all arguments and return values
|
||||
/// are single precision, and a double-precision floating version, where all
|
||||
/// arguments and return values are double precision. Only the single-precision
|
||||
/// floating point version is shown.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Dependencies
|
||||
#include "../detail/precision.hpp"
|
||||
#include "../detail/setup.hpp"
|
||||
#include "../detail/type_mat.hpp"
|
||||
#include "../vec2.hpp"
|
||||
#include "../vec3.hpp"
|
||||
#include "../vec4.hpp"
|
||||
#include "../mat2x2.hpp"
|
||||
#include "../mat2x3.hpp"
|
||||
#include "../mat2x4.hpp"
|
||||
#include "../mat3x2.hpp"
|
||||
#include "../mat3x3.hpp"
|
||||
#include "../mat3x4.hpp"
|
||||
#include "../mat4x2.hpp"
|
||||
#include "../mat4x3.hpp"
|
||||
#include "../mat4x4.hpp"
|
||||
|
||||
namespace glm{
|
||||
namespace detail
|
||||
{
|
||||
template<typename T, precision P>
|
||||
struct outerProduct_trait<2, 2, T, P, vec, vec>
|
||||
{
|
||||
typedef mat<2, 2, T, P> type;
|
||||
};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct outerProduct_trait<2, 3, T, P, vec, vec>
|
||||
{
|
||||
typedef mat<3, 2, T, P> type;
|
||||
};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct outerProduct_trait<2, 4, T, P, vec, vec>
|
||||
{
|
||||
typedef mat<4, 2, T, P> type;
|
||||
};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct outerProduct_trait<3, 2, T, P, vec, vec>
|
||||
{
|
||||
typedef mat<2, 3, T, P> type;
|
||||
};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct outerProduct_trait<3, 3, T, P, vec, vec>
|
||||
{
|
||||
typedef mat<3, 3, T, P> type;
|
||||
};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct outerProduct_trait<3, 4, T, P, vec, vec>
|
||||
{
|
||||
typedef mat<4, 3, T, P> type;
|
||||
};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct outerProduct_trait<4, 2, T, P, vec, vec>
|
||||
{
|
||||
typedef mat<2, 4, T, P> type;
|
||||
};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct outerProduct_trait<4, 3, T, P, vec, vec>
|
||||
{
|
||||
typedef mat<3, 4, T, P> type;
|
||||
};
|
||||
|
||||
template<typename T, precision P>
|
||||
struct outerProduct_trait<4, 4, T, P, vec, vec>
|
||||
{
|
||||
typedef mat<4, 4, T, P> type;
|
||||
};
|
||||
|
||||
}//namespace detail
|
||||
|
||||
/// @addtogroup core_func_matrix
|
||||
/// @{
|
||||
|
||||
/// Multiply matrix x by matrix y component-wise, i.e.,
|
||||
/// result[i][j] is the scalar product of x[i][j] and y[i][j].
|
||||
///
|
||||
/// @tparam matType Floating-point matrix types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/matrixCompMult.xml">GLSL matrixCompMult man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.6 Matrix Functions</a>
|
||||
template<typename T, precision P, template<typename, precision> class matType>
|
||||
GLM_FUNC_DECL matType<T, P> matrixCompMult(matType<T, P> const & x, matType<T, P> const & y);
|
||||
|
||||
/// Treats the first parameter c as a column vector
|
||||
/// and the second parameter r as a row vector
|
||||
/// and does a linear algebraic matrix multiply c * r.
|
||||
///
|
||||
/// @tparam matType Floating-point matrix types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/outerProduct.xml">GLSL outerProduct man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.6 Matrix Functions</a>
|
||||
template<length_t DA, length_t DB, typename T, precision P, template<length_t, typename, precision> class vecTypeA, template<length_t, typename, precision> class vecTypeB>
|
||||
GLM_FUNC_DECL typename detail::outerProduct_trait<DA, DB, T, P, vecTypeA, vecTypeB>::type outerProduct(vecTypeA<DA, T, P> const & c, vecTypeB<DB, T, P> const & r);
|
||||
|
||||
/// Returns the transposed matrix of x
|
||||
///
|
||||
/// @tparam matType Floating-point matrix types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/transpose.xml">GLSL transpose man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.6 Matrix Functions</a>
|
||||
template<typename T, precision P, template<typename, precision> class matType>
|
||||
GLM_FUNC_DECL typename matType<T, P>::transpose_type transpose(matType<T, P> const & x);
|
||||
|
||||
/// Return the determinant of a squared matrix.
|
||||
///
|
||||
/// @tparam valType Floating-point scalar types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/determinant.xml">GLSL determinant man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.6 Matrix Functions</a>
|
||||
template<typename T, precision P, template<typename, precision> class matType>
|
||||
GLM_FUNC_DECL T determinant(matType<T, P> const & m);
|
||||
|
||||
/// Return the inverse of a squared matrix.
|
||||
///
|
||||
/// @tparam valType Floating-point scalar types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/inverse.xml">GLSL inverse man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.6 Matrix Functions</a>
|
||||
template<typename T, precision P, template<typename, precision> class matType>
|
||||
GLM_FUNC_DECL matType<T, P> inverse(matType<T, P> const & m);
|
||||
|
||||
/// @}
|
||||
}//namespace glm
|
||||
|
||||
#include "func_matrix.inl"
|
||||
@@ -0,0 +1,401 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_matrix.inl
|
||||
|
||||
#include "../geometric.hpp"
|
||||
#include <limits>
|
||||
|
||||
namespace glm{
|
||||
namespace detail
|
||||
{
|
||||
template<template<length_t, length_t, typename, precision> class matType, length_t C, length_t R, typename T, precision P, bool Aligned>
|
||||
struct compute_matrixCompMult
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static matType<C, R, T, P> call(matType<C, R, T, P> const& x, matType<C, R, T, P> const& y)
|
||||
{
|
||||
matType<C, R, T, P> result(uninitialize);
|
||||
for(length_t i = 0; i < result.length(); ++i)
|
||||
result[i] = x[i] * y[i];
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<template<length_t, length_t, typename, precision> class matType, length_t C, length_t R, typename T, precision P, bool Aligned>
|
||||
struct compute_transpose{};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_transpose<mat, 2, 2, T, P, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static mat<2, 2, T, P> call(mat<2, 2, T, P> const& m)
|
||||
{
|
||||
mat<2, 2, T, P> result(uninitialize);
|
||||
result[0][0] = m[0][0];
|
||||
result[0][1] = m[1][0];
|
||||
result[1][0] = m[0][1];
|
||||
result[1][1] = m[1][1];
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_transpose<mat, 2, 3, T, P, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static mat<3, 2, T, P> call(mat<2, 3, T, P> const& m)
|
||||
{
|
||||
mat<3,2, T, P> result(uninitialize);
|
||||
result[0][0] = m[0][0];
|
||||
result[0][1] = m[1][0];
|
||||
result[1][0] = m[0][1];
|
||||
result[1][1] = m[1][1];
|
||||
result[2][0] = m[0][2];
|
||||
result[2][1] = m[1][2];
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_transpose<mat, 2, 4, T, P, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static mat<4, 2, T, P> call(mat<2, 4, T, P> const& m)
|
||||
{
|
||||
mat<4, 2, T, P> result(uninitialize);
|
||||
result[0][0] = m[0][0];
|
||||
result[0][1] = m[1][0];
|
||||
result[1][0] = m[0][1];
|
||||
result[1][1] = m[1][1];
|
||||
result[2][0] = m[0][2];
|
||||
result[2][1] = m[1][2];
|
||||
result[3][0] = m[0][3];
|
||||
result[3][1] = m[1][3];
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_transpose<mat, 3, 2, T, P, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static mat<2, 3, T, P> call(mat<3, 2, T, P> const& m)
|
||||
{
|
||||
mat<2, 3, T, P> result(uninitialize);
|
||||
result[0][0] = m[0][0];
|
||||
result[0][1] = m[1][0];
|
||||
result[0][2] = m[2][0];
|
||||
result[1][0] = m[0][1];
|
||||
result[1][1] = m[1][1];
|
||||
result[1][2] = m[2][1];
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_transpose<mat, 3, 3, T, P, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static mat<3, 3, T, P> call(mat<3, 3, T, P> const& m)
|
||||
{
|
||||
mat<3, 3, T, P> result(uninitialize);
|
||||
result[0][0] = m[0][0];
|
||||
result[0][1] = m[1][0];
|
||||
result[0][2] = m[2][0];
|
||||
|
||||
result[1][0] = m[0][1];
|
||||
result[1][1] = m[1][1];
|
||||
result[1][2] = m[2][1];
|
||||
|
||||
result[2][0] = m[0][2];
|
||||
result[2][1] = m[1][2];
|
||||
result[2][2] = m[2][2];
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_transpose<mat, 3, 4, T, P, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static mat<4, 3, T, P> call(mat<3, 4, T, P> const& m)
|
||||
{
|
||||
mat<4, 3, T, P> result(uninitialize);
|
||||
result[0][0] = m[0][0];
|
||||
result[0][1] = m[1][0];
|
||||
result[0][2] = m[2][0];
|
||||
result[1][0] = m[0][1];
|
||||
result[1][1] = m[1][1];
|
||||
result[1][2] = m[2][1];
|
||||
result[2][0] = m[0][2];
|
||||
result[2][1] = m[1][2];
|
||||
result[2][2] = m[2][2];
|
||||
result[3][0] = m[0][3];
|
||||
result[3][1] = m[1][3];
|
||||
result[3][2] = m[2][3];
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_transpose<mat, 4, 2, T, P, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static mat<2, 4, T, P> call(mat<4, 2, T, P> const& m)
|
||||
{
|
||||
mat<2, 4, T, P> result(uninitialize);
|
||||
result[0][0] = m[0][0];
|
||||
result[0][1] = m[1][0];
|
||||
result[0][2] = m[2][0];
|
||||
result[0][3] = m[3][0];
|
||||
result[1][0] = m[0][1];
|
||||
result[1][1] = m[1][1];
|
||||
result[1][2] = m[2][1];
|
||||
result[1][3] = m[3][1];
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_transpose<mat, 4, 3, T, P, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static mat<3, 4, T, P> call(mat<4, 3, T, P> const& m)
|
||||
{
|
||||
mat<3, 4, T, P> result(uninitialize);
|
||||
result[0][0] = m[0][0];
|
||||
result[0][1] = m[1][0];
|
||||
result[0][2] = m[2][0];
|
||||
result[0][3] = m[3][0];
|
||||
result[1][0] = m[0][1];
|
||||
result[1][1] = m[1][1];
|
||||
result[1][2] = m[2][1];
|
||||
result[1][3] = m[3][1];
|
||||
result[2][0] = m[0][2];
|
||||
result[2][1] = m[1][2];
|
||||
result[2][2] = m[2][2];
|
||||
result[2][3] = m[3][2];
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_transpose<mat, 4, 4, T, P, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static mat<4, 4, T, P> call(mat<4, 4, T, P> const& m)
|
||||
{
|
||||
mat<4, 4, T, P> result(uninitialize);
|
||||
result[0][0] = m[0][0];
|
||||
result[0][1] = m[1][0];
|
||||
result[0][2] = m[2][0];
|
||||
result[0][3] = m[3][0];
|
||||
|
||||
result[1][0] = m[0][1];
|
||||
result[1][1] = m[1][1];
|
||||
result[1][2] = m[2][1];
|
||||
result[1][3] = m[3][1];
|
||||
|
||||
result[2][0] = m[0][2];
|
||||
result[2][1] = m[1][2];
|
||||
result[2][2] = m[2][2];
|
||||
result[2][3] = m[3][2];
|
||||
|
||||
result[3][0] = m[0][3];
|
||||
result[3][1] = m[1][3];
|
||||
result[3][2] = m[2][3];
|
||||
result[3][3] = m[3][3];
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<template<length_t, length_t, typename, precision> class matType, length_t C, length_t R, typename T, precision P, bool Aligned>
|
||||
struct compute_determinant{};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_determinant<mat, 2, 2, T, P, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static T call(mat<2, 2, T, P> const& m)
|
||||
{
|
||||
return m[0][0] * m[1][1] - m[1][0] * m[0][1];
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_determinant<mat, 3, 3, T, P, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static T call(mat<3, 3, T, P> const& m)
|
||||
{
|
||||
return
|
||||
+ m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2])
|
||||
- m[1][0] * (m[0][1] * m[2][2] - m[2][1] * m[0][2])
|
||||
+ m[2][0] * (m[0][1] * m[1][2] - m[1][1] * m[0][2]);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_determinant<mat, 4, 4, T, P, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static T call(mat<4, 4, T, P> const& m)
|
||||
{
|
||||
T SubFactor00 = m[2][2] * m[3][3] - m[3][2] * m[2][3];
|
||||
T SubFactor01 = m[2][1] * m[3][3] - m[3][1] * m[2][3];
|
||||
T SubFactor02 = m[2][1] * m[3][2] - m[3][1] * m[2][2];
|
||||
T SubFactor03 = m[2][0] * m[3][3] - m[3][0] * m[2][3];
|
||||
T SubFactor04 = m[2][0] * m[3][2] - m[3][0] * m[2][2];
|
||||
T SubFactor05 = m[2][0] * m[3][1] - m[3][0] * m[2][1];
|
||||
|
||||
vec<4, T, P> DetCof(
|
||||
+ (m[1][1] * SubFactor00 - m[1][2] * SubFactor01 + m[1][3] * SubFactor02),
|
||||
- (m[1][0] * SubFactor00 - m[1][2] * SubFactor03 + m[1][3] * SubFactor04),
|
||||
+ (m[1][0] * SubFactor01 - m[1][1] * SubFactor03 + m[1][3] * SubFactor05),
|
||||
- (m[1][0] * SubFactor02 - m[1][1] * SubFactor04 + m[1][2] * SubFactor05));
|
||||
|
||||
return
|
||||
m[0][0] * DetCof[0] + m[0][1] * DetCof[1] +
|
||||
m[0][2] * DetCof[2] + m[0][3] * DetCof[3];
|
||||
}
|
||||
};
|
||||
|
||||
template<template<length_t, length_t, typename, precision> class matType, length_t C, length_t R, typename T, precision P, bool Aligned>
|
||||
struct compute_inverse{};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_inverse<mat, 2, 2, T, P, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static mat<2, 2, T, P> call(mat<2, 2, T, P> const& m)
|
||||
{
|
||||
T OneOverDeterminant = static_cast<T>(1) / (
|
||||
+ m[0][0] * m[1][1]
|
||||
- m[1][0] * m[0][1]);
|
||||
|
||||
mat<2, 2, T, P> Inverse(
|
||||
+ m[1][1] * OneOverDeterminant,
|
||||
- m[0][1] * OneOverDeterminant,
|
||||
- m[1][0] * OneOverDeterminant,
|
||||
+ m[0][0] * OneOverDeterminant);
|
||||
|
||||
return Inverse;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_inverse<mat, 3, 3, T, P, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static mat<3, 3, T, P> call(mat<3, 3, T, P> const& m)
|
||||
{
|
||||
T OneOverDeterminant = static_cast<T>(1) / (
|
||||
+ m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2])
|
||||
- m[1][0] * (m[0][1] * m[2][2] - m[2][1] * m[0][2])
|
||||
+ m[2][0] * (m[0][1] * m[1][2] - m[1][1] * m[0][2]));
|
||||
|
||||
mat<3, 3, T, P> Inverse(uninitialize);
|
||||
Inverse[0][0] = + (m[1][1] * m[2][2] - m[2][1] * m[1][2]) * OneOverDeterminant;
|
||||
Inverse[1][0] = - (m[1][0] * m[2][2] - m[2][0] * m[1][2]) * OneOverDeterminant;
|
||||
Inverse[2][0] = + (m[1][0] * m[2][1] - m[2][0] * m[1][1]) * OneOverDeterminant;
|
||||
Inverse[0][1] = - (m[0][1] * m[2][2] - m[2][1] * m[0][2]) * OneOverDeterminant;
|
||||
Inverse[1][1] = + (m[0][0] * m[2][2] - m[2][0] * m[0][2]) * OneOverDeterminant;
|
||||
Inverse[2][1] = - (m[0][0] * m[2][1] - m[2][0] * m[0][1]) * OneOverDeterminant;
|
||||
Inverse[0][2] = + (m[0][1] * m[1][2] - m[1][1] * m[0][2]) * OneOverDeterminant;
|
||||
Inverse[1][2] = - (m[0][0] * m[1][2] - m[1][0] * m[0][2]) * OneOverDeterminant;
|
||||
Inverse[2][2] = + (m[0][0] * m[1][1] - m[1][0] * m[0][1]) * OneOverDeterminant;
|
||||
|
||||
return Inverse;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, precision P, bool Aligned>
|
||||
struct compute_inverse<mat, 4, 4, T, P, Aligned>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static mat<4, 4, T, P> call(mat<4, 4, T, P> const& m)
|
||||
{
|
||||
T Coef00 = m[2][2] * m[3][3] - m[3][2] * m[2][3];
|
||||
T Coef02 = m[1][2] * m[3][3] - m[3][2] * m[1][3];
|
||||
T Coef03 = m[1][2] * m[2][3] - m[2][2] * m[1][3];
|
||||
|
||||
T Coef04 = m[2][1] * m[3][3] - m[3][1] * m[2][3];
|
||||
T Coef06 = m[1][1] * m[3][3] - m[3][1] * m[1][3];
|
||||
T Coef07 = m[1][1] * m[2][3] - m[2][1] * m[1][3];
|
||||
|
||||
T Coef08 = m[2][1] * m[3][2] - m[3][1] * m[2][2];
|
||||
T Coef10 = m[1][1] * m[3][2] - m[3][1] * m[1][2];
|
||||
T Coef11 = m[1][1] * m[2][2] - m[2][1] * m[1][2];
|
||||
|
||||
T Coef12 = m[2][0] * m[3][3] - m[3][0] * m[2][3];
|
||||
T Coef14 = m[1][0] * m[3][3] - m[3][0] * m[1][3];
|
||||
T Coef15 = m[1][0] * m[2][3] - m[2][0] * m[1][3];
|
||||
|
||||
T Coef16 = m[2][0] * m[3][2] - m[3][0] * m[2][2];
|
||||
T Coef18 = m[1][0] * m[3][2] - m[3][0] * m[1][2];
|
||||
T Coef19 = m[1][0] * m[2][2] - m[2][0] * m[1][2];
|
||||
|
||||
T Coef20 = m[2][0] * m[3][1] - m[3][0] * m[2][1];
|
||||
T Coef22 = m[1][0] * m[3][1] - m[3][0] * m[1][1];
|
||||
T Coef23 = m[1][0] * m[2][1] - m[2][0] * m[1][1];
|
||||
|
||||
vec<4, T, P> Fac0(Coef00, Coef00, Coef02, Coef03);
|
||||
vec<4, T, P> Fac1(Coef04, Coef04, Coef06, Coef07);
|
||||
vec<4, T, P> Fac2(Coef08, Coef08, Coef10, Coef11);
|
||||
vec<4, T, P> Fac3(Coef12, Coef12, Coef14, Coef15);
|
||||
vec<4, T, P> Fac4(Coef16, Coef16, Coef18, Coef19);
|
||||
vec<4, T, P> Fac5(Coef20, Coef20, Coef22, Coef23);
|
||||
|
||||
vec<4, T, P> Vec0(m[1][0], m[0][0], m[0][0], m[0][0]);
|
||||
vec<4, T, P> Vec1(m[1][1], m[0][1], m[0][1], m[0][1]);
|
||||
vec<4, T, P> Vec2(m[1][2], m[0][2], m[0][2], m[0][2]);
|
||||
vec<4, T, P> Vec3(m[1][3], m[0][3], m[0][3], m[0][3]);
|
||||
|
||||
vec<4, T, P> Inv0(Vec1 * Fac0 - Vec2 * Fac1 + Vec3 * Fac2);
|
||||
vec<4, T, P> Inv1(Vec0 * Fac0 - Vec2 * Fac3 + Vec3 * Fac4);
|
||||
vec<4, T, P> Inv2(Vec0 * Fac1 - Vec1 * Fac3 + Vec3 * Fac5);
|
||||
vec<4, T, P> Inv3(Vec0 * Fac2 - Vec1 * Fac4 + Vec2 * Fac5);
|
||||
|
||||
vec<4, T, P> SignA(+1, -1, +1, -1);
|
||||
vec<4, T, P> SignB(-1, +1, -1, +1);
|
||||
mat<4, 4, T, P> Inverse(Inv0 * SignA, Inv1 * SignB, Inv2 * SignA, Inv3 * SignB);
|
||||
|
||||
vec<4, T, P> Row0(Inverse[0][0], Inverse[1][0], Inverse[2][0], Inverse[3][0]);
|
||||
|
||||
vec<4, T, P> Dot0(m[0] * Row0);
|
||||
T Dot1 = (Dot0.x + Dot0.y) + (Dot0.z + Dot0.w);
|
||||
|
||||
T OneOverDeterminant = static_cast<T>(1) / Dot1;
|
||||
|
||||
return Inverse * OneOverDeterminant;
|
||||
}
|
||||
};
|
||||
}//namespace detail
|
||||
|
||||
template<length_t C, length_t R, typename T, precision P, template<length_t, length_t, typename, precision> class matType>
|
||||
GLM_FUNC_QUALIFIER matType<C, R, T, P> matrixCompMult(matType<C, R, T, P> const & x, matType<C, R, T, P> const & y)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'matrixCompMult' only accept floating-point inputs");
|
||||
return detail::compute_matrixCompMult<matType, C, R, T, P, detail::is_aligned<P>::value>::call(x, y);
|
||||
}
|
||||
|
||||
template<length_t DA, length_t DB, typename T, precision P, template<length_t, typename, precision> class vecTypeA, template<length_t, typename, precision> class vecTypeB>
|
||||
GLM_FUNC_QUALIFIER typename detail::outerProduct_trait<DA, DB, T, P, vecTypeA, vecTypeB>::type outerProduct(vecTypeA<DA, T, P> const & c, vecTypeB<DB, T, P> const & r)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'outerProduct' only accept floating-point inputs");
|
||||
|
||||
typename detail::outerProduct_trait<DA, DB, T, P, vecTypeA, vecTypeB>::type m(uninitialize);
|
||||
for(length_t i = 0; i < m.length(); ++i)
|
||||
m[i] = c * r[i];
|
||||
return m;
|
||||
}
|
||||
|
||||
template<length_t C, length_t R, typename T, precision P, template<length_t, length_t, typename, precision> class matType>
|
||||
GLM_FUNC_QUALIFIER typename matType<C, R, T, P>::transpose_type transpose(matType<C, R, T, P> const & m)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'transpose' only accept floating-point inputs");
|
||||
return detail::compute_transpose<matType, C, R, T, P, detail::is_aligned<P>::value>::call(m);
|
||||
}
|
||||
|
||||
template<length_t C, length_t R, typename T, precision P, template<length_t, length_t, typename, precision> class matType>
|
||||
GLM_FUNC_QUALIFIER T determinant(matType<C, R, T, P> const & m)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'determinant' only accept floating-point inputs");
|
||||
return detail::compute_determinant<matType, C, R, T, P, detail::is_aligned<P>::value>::call(m);
|
||||
}
|
||||
|
||||
template<length_t C, length_t R, typename T, precision P, template<length_t, length_t, typename, precision> class matType>
|
||||
GLM_FUNC_QUALIFIER matType<C, R, T, P> inverse(matType<C, R, T, P> const & m)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<T>::is_iec559 || GLM_UNRESTRICTED_GENTYPE, "'inverse' only accept floating-point inputs");
|
||||
return detail::compute_inverse<matType, C, R, T, P, detail::is_aligned<P>::value>::call(m);
|
||||
}
|
||||
}//namespace glm
|
||||
|
||||
#if GLM_ARCH != GLM_ARCH_PURE && GLM_HAS_UNRESTRICTED_UNIONS
|
||||
# include "func_matrix_simd.inl"
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_matrix_simd.inl
|
||||
|
||||
#if GLM_ARCH & GLM_ARCH_SSE2_BIT
|
||||
|
||||
#include "type_mat4x4.hpp"
|
||||
#include "func_geometric.hpp"
|
||||
#include "../simd/matrix.h"
|
||||
|
||||
namespace glm{
|
||||
namespace detail
|
||||
{
|
||||
template<precision P>
|
||||
struct compute_matrixCompMult<mat, 4, 4, float, P, true>
|
||||
{
|
||||
GLM_STATIC_ASSERT(detail::is_aligned<P>::value, "Specialization requires aligned");
|
||||
|
||||
GLM_FUNC_QUALIFIER static mat<4, 4, float, P> call(mat<4, 4, float, P> const & x, mat<4, 4, float, P> const & y)
|
||||
{
|
||||
mat<4, 4, float, P> result(uninitialize);
|
||||
glm_mat4_matrixCompMult(
|
||||
*static_cast<glm_vec4 const (*)[4]>(&x[0].data),
|
||||
*static_cast<glm_vec4 const (*)[4]>(&y[0].data),
|
||||
*static_cast<glm_vec4(*)[4]>(&result[0].data));
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_transpose<mat, 4, 4, float, P, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static mat<4, 4, float, P> call(mat<4, 4, float, P> const & m)
|
||||
{
|
||||
mat<4, 4, float, P> result(uninitialize);
|
||||
glm_mat4_transpose(
|
||||
*static_cast<glm_vec4 const (*)[4]>(&m[0].data),
|
||||
*static_cast<glm_vec4(*)[4]>(&result[0].data));
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_determinant<mat, 4, 4, float, P, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static float call(mat<4, 4, float, P> const& m)
|
||||
{
|
||||
return _mm_cvtss_f32(glm_mat4_determinant(*reinterpret_cast<__m128 const(*)[4]>(&m[0].data)));
|
||||
}
|
||||
};
|
||||
|
||||
template<precision P>
|
||||
struct compute_inverse<mat, 4, 4, float, P, true>
|
||||
{
|
||||
GLM_FUNC_QUALIFIER static mat<4, 4, float, P> call(mat<4, 4, float, P> const& m)
|
||||
{
|
||||
mat<4, 4, float, P> Result(uninitialize);
|
||||
glm_mat4_inverse(*reinterpret_cast<__m128 const(*)[4]>(&m[0].data), *reinterpret_cast<__m128(*)[4]>(&Result[0].data));
|
||||
return Result;
|
||||
}
|
||||
};
|
||||
}//namespace detail
|
||||
|
||||
template<>
|
||||
GLM_FUNC_QUALIFIER mat<4, 4, float, aligned_lowp> outerProduct<4, 4, float, aligned_lowp, vec, vec>(vec<4, float, aligned_lowp> const & c, vec<4, float, aligned_lowp> const & r)
|
||||
{
|
||||
mat<4, 4, float, aligned_lowp> m(uninitialize);
|
||||
glm_mat4_outerProduct(c.data, r.data, *reinterpret_cast<__m128(*)[4]>(&m[0].data));
|
||||
return m;
|
||||
}
|
||||
|
||||
template<>
|
||||
GLM_FUNC_QUALIFIER mat<4, 4, float, aligned_mediump> outerProduct<4, 4, float, aligned_mediump, vec, vec>(vec<4, float, aligned_mediump> const & c, vec<4, float, aligned_mediump> const & r)
|
||||
{
|
||||
mat<4, 4, float, aligned_mediump> m(uninitialize);
|
||||
glm_mat4_outerProduct(c.data, r.data, *reinterpret_cast<__m128(*)[4]>(&m[0].data));
|
||||
return m;
|
||||
}
|
||||
|
||||
template<>
|
||||
GLM_FUNC_QUALIFIER mat<4, 4, float, aligned_highp> outerProduct<4, 4, float, aligned_highp, vec, vec>(vec<4, float, aligned_highp> const & c, vec<4, float, aligned_highp> const & r)
|
||||
{
|
||||
mat<4, 4, float, aligned_highp> m(uninitialize);
|
||||
glm_mat4_outerProduct(c.data, r.data, *reinterpret_cast<__m128(*)[4]>(&m[0].data));
|
||||
return m;
|
||||
}
|
||||
}//namespace glm
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,168 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_packing.hpp
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.4 Floating-Point Pack and Unpack Functions</a>
|
||||
/// @see gtc_packing
|
||||
///
|
||||
/// @defgroup core_func_packing Floating-Point Pack and Unpack Functions
|
||||
/// @ingroup core
|
||||
///
|
||||
/// These functions do not operate component-wise, rather as described in each case.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "type_vec2.hpp"
|
||||
#include "type_vec4.hpp"
|
||||
|
||||
namespace glm
|
||||
{
|
||||
/// @addtogroup core_func_packing
|
||||
/// @{
|
||||
|
||||
/// First, converts each component of the normalized floating-point value v into 8- or 16-bit integer values.
|
||||
/// Then, the results are packed into the returned 32-bit unsigned integer.
|
||||
///
|
||||
/// The conversion for component c of v to fixed point is done as follows:
|
||||
/// packUnorm2x16: round(clamp(c, 0, +1) * 65535.0)
|
||||
///
|
||||
/// The first component of the vector will be written to the least significant bits of the output;
|
||||
/// the last component will be written to the most significant bits.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/packUnorm2x16.xml">GLSL packUnorm2x16 man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.4 Floating-Point Pack and Unpack Functions</a>
|
||||
GLM_FUNC_DECL uint packUnorm2x16(vec2 const & v);
|
||||
|
||||
/// First, converts each component of the normalized floating-point value v into 8- or 16-bit integer values.
|
||||
/// Then, the results are packed into the returned 32-bit unsigned integer.
|
||||
///
|
||||
/// The conversion for component c of v to fixed point is done as follows:
|
||||
/// packSnorm2x16: round(clamp(v, -1, +1) * 32767.0)
|
||||
///
|
||||
/// The first component of the vector will be written to the least significant bits of the output;
|
||||
/// the last component will be written to the most significant bits.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/packSnorm2x16.xml">GLSL packSnorm2x16 man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.4 Floating-Point Pack and Unpack Functions</a>
|
||||
GLM_FUNC_DECL uint packSnorm2x16(vec2 const & v);
|
||||
|
||||
/// First, converts each component of the normalized floating-point value v into 8- or 16-bit integer values.
|
||||
/// Then, the results are packed into the returned 32-bit unsigned integer.
|
||||
///
|
||||
/// The conversion for component c of v to fixed point is done as follows:
|
||||
/// packUnorm4x8: round(clamp(c, 0, +1) * 255.0)
|
||||
///
|
||||
/// The first component of the vector will be written to the least significant bits of the output;
|
||||
/// the last component will be written to the most significant bits.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/packUnorm4x8.xml">GLSL packUnorm4x8 man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.4 Floating-Point Pack and Unpack Functions</a>
|
||||
GLM_FUNC_DECL uint packUnorm4x8(vec4 const & v);
|
||||
|
||||
/// First, converts each component of the normalized floating-point value v into 8- or 16-bit integer values.
|
||||
/// Then, the results are packed into the returned 32-bit unsigned integer.
|
||||
///
|
||||
/// The conversion for component c of v to fixed point is done as follows:
|
||||
/// packSnorm4x8: round(clamp(c, -1, +1) * 127.0)
|
||||
///
|
||||
/// The first component of the vector will be written to the least significant bits of the output;
|
||||
/// the last component will be written to the most significant bits.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/packSnorm4x8.xml">GLSL packSnorm4x8 man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.4 Floating-Point Pack and Unpack Functions</a>
|
||||
GLM_FUNC_DECL uint packSnorm4x8(vec4 const & v);
|
||||
|
||||
/// First, unpacks a single 32-bit unsigned integer p into a pair of 16-bit unsigned integers, four 8-bit unsigned integers, or four 8-bit signed integers.
|
||||
/// Then, each component is converted to a normalized floating-point value to generate the returned two- or four-component vector.
|
||||
///
|
||||
/// The conversion for unpacked fixed-point value f to floating point is done as follows:
|
||||
/// unpackUnorm2x16: f / 65535.0
|
||||
///
|
||||
/// The first component of the returned vector will be extracted from the least significant bits of the input;
|
||||
/// the last component will be extracted from the most significant bits.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/unpackUnorm2x16.xml">GLSL unpackUnorm2x16 man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.4 Floating-Point Pack and Unpack Functions</a>
|
||||
GLM_FUNC_DECL vec2 unpackUnorm2x16(uint p);
|
||||
|
||||
/// First, unpacks a single 32-bit unsigned integer p into a pair of 16-bit unsigned integers, four 8-bit unsigned integers, or four 8-bit signed integers.
|
||||
/// Then, each component is converted to a normalized floating-point value to generate the returned two- or four-component vector.
|
||||
///
|
||||
/// The conversion for unpacked fixed-point value f to floating point is done as follows:
|
||||
/// unpackSnorm2x16: clamp(f / 32767.0, -1, +1)
|
||||
///
|
||||
/// The first component of the returned vector will be extracted from the least significant bits of the input;
|
||||
/// the last component will be extracted from the most significant bits.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/unpackSnorm2x16.xml">GLSL unpackSnorm2x16 man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.4 Floating-Point Pack and Unpack Functions</a>
|
||||
GLM_FUNC_DECL vec2 unpackSnorm2x16(uint p);
|
||||
|
||||
/// First, unpacks a single 32-bit unsigned integer p into a pair of 16-bit unsigned integers, four 8-bit unsigned integers, or four 8-bit signed integers.
|
||||
/// Then, each component is converted to a normalized floating-point value to generate the returned two- or four-component vector.
|
||||
///
|
||||
/// The conversion for unpacked fixed-point value f to floating point is done as follows:
|
||||
/// unpackUnorm4x8: f / 255.0
|
||||
///
|
||||
/// The first component of the returned vector will be extracted from the least significant bits of the input;
|
||||
/// the last component will be extracted from the most significant bits.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/unpackUnorm4x8.xml">GLSL unpackUnorm4x8 man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.4 Floating-Point Pack and Unpack Functions</a>
|
||||
GLM_FUNC_DECL vec4 unpackUnorm4x8(uint p);
|
||||
|
||||
/// First, unpacks a single 32-bit unsigned integer p into a pair of 16-bit unsigned integers, four 8-bit unsigned integers, or four 8-bit signed integers.
|
||||
/// Then, each component is converted to a normalized floating-point value to generate the returned two- or four-component vector.
|
||||
///
|
||||
/// The conversion for unpacked fixed-point value f to floating point is done as follows:
|
||||
/// unpackSnorm4x8: clamp(f / 127.0, -1, +1)
|
||||
///
|
||||
/// The first component of the returned vector will be extracted from the least significant bits of the input;
|
||||
/// the last component will be extracted from the most significant bits.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/unpackSnorm4x8.xml">GLSL unpackSnorm4x8 man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.4 Floating-Point Pack and Unpack Functions</a>
|
||||
GLM_FUNC_DECL vec4 unpackSnorm4x8(uint p);
|
||||
|
||||
/// Returns a double-precision value obtained by packing the components of v into a 64-bit value.
|
||||
/// If an IEEE 754 Inf or NaN is created, it will not signal, and the resulting floating point value is unspecified.
|
||||
/// Otherwise, the bit- level representation of v is preserved.
|
||||
/// The first vector component specifies the 32 least significant bits;
|
||||
/// the second component specifies the 32 most significant bits.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/packDouble2x32.xml">GLSL packDouble2x32 man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.4 Floating-Point Pack and Unpack Functions</a>
|
||||
GLM_FUNC_DECL double packDouble2x32(uvec2 const & v);
|
||||
|
||||
/// Returns a two-component unsigned integer vector representation of v.
|
||||
/// The bit-level representation of v is preserved.
|
||||
/// The first component of the vector contains the 32 least significant bits of the double;
|
||||
/// the second component consists the 32 most significant bits.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/unpackDouble2x32.xml">GLSL unpackDouble2x32 man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.4 Floating-Point Pack and Unpack Functions</a>
|
||||
GLM_FUNC_DECL uvec2 unpackDouble2x32(double v);
|
||||
|
||||
/// Returns an unsigned integer obtained by converting the components of a two-component floating-point vector
|
||||
/// to the 16-bit floating-point representation found in the OpenGL Specification,
|
||||
/// and then packing these two 16- bit integers into a 32-bit unsigned integer.
|
||||
/// The first vector component specifies the 16 least-significant bits of the result;
|
||||
/// the second component specifies the 16 most-significant bits.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/packHalf2x16.xml">GLSL packHalf2x16 man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.4 Floating-Point Pack and Unpack Functions</a>
|
||||
GLM_FUNC_DECL uint packHalf2x16(vec2 const & v);
|
||||
|
||||
/// Returns a two-component floating-point vector with components obtained by unpacking a 32-bit unsigned integer into a pair of 16-bit values,
|
||||
/// interpreting those values as 16-bit floating-point numbers according to the OpenGL Specification,
|
||||
/// and converting them to 32-bit floating-point values.
|
||||
/// The first component of the vector is obtained from the 16 least-significant bits of v;
|
||||
/// the second component is obtained from the 16 most-significant bits of v.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/unpackHalf2x16.xml">GLSL unpackHalf2x16 man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.4 Floating-Point Pack and Unpack Functions</a>
|
||||
GLM_FUNC_DECL vec2 unpackHalf2x16(uint v);
|
||||
|
||||
/// @}
|
||||
}//namespace glm
|
||||
|
||||
#include "func_packing.inl"
|
||||
@@ -0,0 +1,190 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_packing.inl
|
||||
|
||||
#include "func_common.hpp"
|
||||
#include "type_half.hpp"
|
||||
#include "../fwd.hpp"
|
||||
|
||||
namespace glm
|
||||
{
|
||||
GLM_FUNC_QUALIFIER uint packUnorm2x16(vec2 const & v)
|
||||
{
|
||||
union
|
||||
{
|
||||
u16 in[2];
|
||||
uint out;
|
||||
} u;
|
||||
|
||||
u16vec2 result(round(clamp(v, 0.0f, 1.0f) * 65535.0f));
|
||||
|
||||
u.in[0] = result[0];
|
||||
u.in[1] = result[1];
|
||||
|
||||
return u.out;
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER vec2 unpackUnorm2x16(uint p)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint in;
|
||||
u16 out[2];
|
||||
} u;
|
||||
|
||||
u.in = p;
|
||||
|
||||
return vec2(u.out[0], u.out[1]) * 1.5259021896696421759365224689097e-5f;
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER uint packSnorm2x16(vec2 const & v)
|
||||
{
|
||||
union
|
||||
{
|
||||
i16 in[2];
|
||||
uint out;
|
||||
} u;
|
||||
|
||||
i16vec2 result(round(clamp(v, -1.0f, 1.0f) * 32767.0f));
|
||||
|
||||
u.in[0] = result[0];
|
||||
u.in[1] = result[1];
|
||||
|
||||
return u.out;
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER vec2 unpackSnorm2x16(uint p)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint in;
|
||||
i16 out[2];
|
||||
} u;
|
||||
|
||||
u.in = p;
|
||||
|
||||
return clamp(vec2(u.out[0], u.out[1]) * 3.0518509475997192297128208258309e-5f, -1.0f, 1.0f);
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER uint packUnorm4x8(vec4 const & v)
|
||||
{
|
||||
union
|
||||
{
|
||||
u8 in[4];
|
||||
uint out;
|
||||
} u;
|
||||
|
||||
u8vec4 result(round(clamp(v, 0.0f, 1.0f) * 255.0f));
|
||||
|
||||
u.in[0] = result[0];
|
||||
u.in[1] = result[1];
|
||||
u.in[2] = result[2];
|
||||
u.in[3] = result[3];
|
||||
|
||||
return u.out;
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER vec4 unpackUnorm4x8(uint p)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint in;
|
||||
u8 out[4];
|
||||
} u;
|
||||
|
||||
u.in = p;
|
||||
|
||||
return vec4(u.out[0], u.out[1], u.out[2], u.out[3]) * 0.0039215686274509803921568627451f;
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER uint packSnorm4x8(vec4 const & v)
|
||||
{
|
||||
union
|
||||
{
|
||||
i8 in[4];
|
||||
uint out;
|
||||
} u;
|
||||
|
||||
i8vec4 result(round(clamp(v, -1.0f, 1.0f) * 127.0f));
|
||||
|
||||
u.in[0] = result[0];
|
||||
u.in[1] = result[1];
|
||||
u.in[2] = result[2];
|
||||
u.in[3] = result[3];
|
||||
|
||||
return u.out;
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER glm::vec4 unpackSnorm4x8(uint p)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint in;
|
||||
i8 out[4];
|
||||
} u;
|
||||
|
||||
u.in = p;
|
||||
|
||||
return clamp(vec4(u.out[0], u.out[1], u.out[2], u.out[3]) * 0.0078740157480315f, -1.0f, 1.0f);
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER double packDouble2x32(uvec2 const & v)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint in[2];
|
||||
double out;
|
||||
} u;
|
||||
|
||||
u.in[0] = v[0];
|
||||
u.in[1] = v[1];
|
||||
|
||||
return u.out;
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER uvec2 unpackDouble2x32(double v)
|
||||
{
|
||||
union
|
||||
{
|
||||
double in;
|
||||
uint out[2];
|
||||
} u;
|
||||
|
||||
u.in = v;
|
||||
|
||||
return uvec2(u.out[0], u.out[1]);
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER uint packHalf2x16(vec2 const & v)
|
||||
{
|
||||
union
|
||||
{
|
||||
i16 in[2];
|
||||
uint out;
|
||||
} u;
|
||||
|
||||
u.in[0] = detail::toFloat16(v.x);
|
||||
u.in[1] = detail::toFloat16(v.y);
|
||||
|
||||
return u.out;
|
||||
}
|
||||
|
||||
GLM_FUNC_QUALIFIER vec2 unpackHalf2x16(uint v)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint in;
|
||||
i16 out[2];
|
||||
} u;
|
||||
|
||||
u.in = v;
|
||||
|
||||
return vec2(
|
||||
detail::toFloat32(u.out[0]),
|
||||
detail::toFloat32(u.out[1]));
|
||||
}
|
||||
}//namespace glm
|
||||
|
||||
#if GLM_ARCH != GLM_ARCH_PURE && GLM_HAS_UNRESTRICTED_UNIONS
|
||||
# include "func_packing_simd.inl"
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_packing_simd.inl
|
||||
|
||||
namespace glm{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
}//namespace detail
|
||||
}//namespace glm
|
||||
@@ -0,0 +1,176 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_trigonometric.hpp
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
///
|
||||
/// @defgroup core_func_trigonometric Angle and Trigonometry Functions
|
||||
/// @ingroup core
|
||||
///
|
||||
/// Function parameters specified as angle are assumed to be in units of radians.
|
||||
/// In no case will any of these functions result in a divide by zero error. If
|
||||
/// the divisor of a ratio is 0, then results will be undefined.
|
||||
///
|
||||
/// These all operate component-wise. The description is per component.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "setup.hpp"
|
||||
#include "precision.hpp"
|
||||
|
||||
namespace glm
|
||||
{
|
||||
/// @addtogroup core_func_trigonometric
|
||||
/// @{
|
||||
|
||||
/// Converts degrees to radians and returns the result.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/radians.xml">GLSL radians man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL GLM_CONSTEXPR vecType<L, T, P> radians(vecType<L, T, P> const & degrees);
|
||||
|
||||
/// Converts radians to degrees and returns the result.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/degrees.xml">GLSL degrees man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL GLM_CONSTEXPR vecType<L, T, P> degrees(vecType<L, T, P> const & radians);
|
||||
|
||||
/// The standard trigonometric sine function.
|
||||
/// The values returned by this function will range from [-1, 1].
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/sin.xml">GLSL sin man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> sin(vecType<L, T, P> const & angle);
|
||||
|
||||
/// The standard trigonometric cosine function.
|
||||
/// The values returned by this function will range from [-1, 1].
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/cos.xml">GLSL cos man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> cos(vecType<L, T, P> const & angle);
|
||||
|
||||
/// The standard trigonometric tangent function.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/tan.xml">GLSL tan man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> tan(vecType<L, T, P> const & angle);
|
||||
|
||||
/// Arc sine. Returns an angle whose sine is x.
|
||||
/// The range of values returned by this function is [-PI/2, PI/2].
|
||||
/// Results are undefined if |x| > 1.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/asin.xml">GLSL asin man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> asin(vecType<L, T, P> const & x);
|
||||
|
||||
/// Arc cosine. Returns an angle whose sine is x.
|
||||
/// The range of values returned by this function is [0, PI].
|
||||
/// Results are undefined if |x| > 1.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/acos.xml">GLSL acos man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> acos(vecType<L, T, P> const & x);
|
||||
|
||||
/// Arc tangent. Returns an angle whose tangent is y/x.
|
||||
/// The signs of x and y are used to determine what
|
||||
/// quadrant the angle is in. The range of values returned
|
||||
/// by this function is [-PI, PI]. Results are undefined
|
||||
/// if x and y are both 0.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/atan.xml">GLSL atan man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> atan(vecType<L, T, P> const & y, vecType<L, T, P> const & x);
|
||||
|
||||
/// Arc tangent. Returns an angle whose tangent is y_over_x.
|
||||
/// The range of values returned by this function is [-PI/2, PI/2].
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/atan.xml">GLSL atan man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> atan(vecType<L, T, P> const & y_over_x);
|
||||
|
||||
/// Returns the hyperbolic sine function, (exp(x) - exp(-x)) / 2
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/sinh.xml">GLSL sinh man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> sinh(vecType<L, T, P> const & angle);
|
||||
|
||||
/// Returns the hyperbolic cosine function, (exp(x) + exp(-x)) / 2
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/cosh.xml">GLSL cosh man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> cosh(vecType<L, T, P> const & angle);
|
||||
|
||||
/// Returns the hyperbolic tangent function, sinh(angle) / cosh(angle)
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/tanh.xml">GLSL tanh man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> tanh(vecType<L, T, P> const & angle);
|
||||
|
||||
/// Arc hyperbolic sine; returns the inverse of sinh.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/asinh.xml">GLSL asinh man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> asinh(vecType<L, T, P> const & x);
|
||||
|
||||
/// Arc hyperbolic cosine; returns the non-negative inverse
|
||||
/// of cosh. Results are undefined if x < 1.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/acosh.xml">GLSL acosh man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> acosh(vecType<L, T, P> const & x);
|
||||
|
||||
/// Arc hyperbolic tangent; returns the inverse of tanh.
|
||||
/// Results are undefined if abs(x) >= 1.
|
||||
///
|
||||
/// @tparam genType Floating-point scalar or vector types.
|
||||
///
|
||||
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/atanh.xml">GLSL atanh man page</a>
|
||||
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.1 Angle and Trigonometry Functions</a>
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_DECL vecType<L, T, P> atanh(vecType<L, T, P> const & x);
|
||||
|
||||
/// @}
|
||||
}//namespace glm
|
||||
|
||||
#include "func_trigonometric.inl"
|
||||
@@ -0,0 +1,200 @@
|
||||
/// @ref core
|
||||
/// @file glm/detail/func_trigonometric.inl
|
||||
|
||||
#include "_vectorize.hpp"
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace glm
|
||||
{
|
||||
// radians
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER GLM_CONSTEXPR genType radians(genType degrees)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'radians' only accept floating-point input");
|
||||
|
||||
return degrees * static_cast<genType>(0.01745329251994329576923690768489);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER GLM_CONSTEXPR vecType<L, T, P> radians(vecType<L, T, P> const & v)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(radians, v);
|
||||
}
|
||||
|
||||
// degrees
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER GLM_CONSTEXPR genType degrees(genType radians)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'degrees' only accept floating-point input");
|
||||
|
||||
return radians * static_cast<genType>(57.295779513082320876798154814105);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER GLM_CONSTEXPR vecType<L, T, P> degrees(vecType<L, T, P> const & v)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(degrees, v);
|
||||
}
|
||||
|
||||
// sin
|
||||
using ::std::sin;
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> sin(vecType<L, T, P> const & v)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(sin, v);
|
||||
}
|
||||
|
||||
// cos
|
||||
using std::cos;
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> cos(vecType<L, T, P> const & v)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(cos, v);
|
||||
}
|
||||
|
||||
// tan
|
||||
using std::tan;
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> tan(vecType<L, T, P> const & v)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(tan, v);
|
||||
}
|
||||
|
||||
// asin
|
||||
using std::asin;
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> asin(vecType<L, T, P> const & v)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(asin, v);
|
||||
}
|
||||
|
||||
// acos
|
||||
using std::acos;
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> acos(vecType<L, T, P> const & v)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(acos, v);
|
||||
}
|
||||
|
||||
// atan
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType atan(genType y, genType x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'atan' only accept floating-point input");
|
||||
|
||||
return ::std::atan2(y, x);
|
||||
}
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> atan(vecType<L, T, P> const & a, vecType<L, T, P> const & b)
|
||||
{
|
||||
return detail::functor2<L, T, P>::call(::std::atan2, a, b);
|
||||
}
|
||||
|
||||
using std::atan;
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> atan(vecType<L, T, P> const & v)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(atan, v);
|
||||
}
|
||||
|
||||
// sinh
|
||||
using std::sinh;
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> sinh(vecType<L, T, P> const & v)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(sinh, v);
|
||||
}
|
||||
|
||||
// cosh
|
||||
using std::cosh;
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> cosh(vecType<L, T, P> const & v)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(cosh, v);
|
||||
}
|
||||
|
||||
// tanh
|
||||
using std::tanh;
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> tanh(vecType<L, T, P> const & v)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(tanh, v);
|
||||
}
|
||||
|
||||
// asinh
|
||||
# if GLM_HAS_CXX11_STL
|
||||
using std::asinh;
|
||||
# else
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType asinh(genType x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'asinh' only accept floating-point input");
|
||||
|
||||
return (x < static_cast<genType>(0) ? static_cast<genType>(-1) : (x > static_cast<genType>(0) ? static_cast<genType>(1) : static_cast<genType>(0))) * log(std::abs(x) + sqrt(static_cast<genType>(1) + x * x));
|
||||
}
|
||||
# endif
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> asinh(vecType<L, T, P> const & v)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(asinh, v);
|
||||
}
|
||||
|
||||
// acosh
|
||||
# if GLM_HAS_CXX11_STL
|
||||
using std::acosh;
|
||||
# else
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType acosh(genType x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'acosh' only accept floating-point input");
|
||||
|
||||
if(x < static_cast<genType>(1))
|
||||
return static_cast<genType>(0);
|
||||
return log(x + sqrt(x * x - static_cast<genType>(1)));
|
||||
}
|
||||
# endif
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> acosh(vecType<L, T, P> const & v)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(acosh, v);
|
||||
}
|
||||
|
||||
// atanh
|
||||
# if GLM_HAS_CXX11_STL
|
||||
using std::atanh;
|
||||
# else
|
||||
template<typename genType>
|
||||
GLM_FUNC_QUALIFIER genType atanh(genType x)
|
||||
{
|
||||
GLM_STATIC_ASSERT(std::numeric_limits<genType>::is_iec559, "'atanh' only accept floating-point input");
|
||||
|
||||
if(std::abs(x) >= static_cast<genType>(1))
|
||||
return 0;
|
||||
return static_cast<genType>(0.5) * log((static_cast<genType>(1) + x) / (static_cast<genType>(1) - x));
|
||||
}
|
||||
# endif
|
||||
|
||||
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
|
||||
GLM_FUNC_QUALIFIER vecType<L, T, P> atanh(vecType<L, T, P> const & v)
|
||||
{
|
||||
return detail::functor1<L, T, T, P>::call(atanh, v);
|
||||
}
|
||||
}//namespace glm
|
||||
|
||||
#if GLM_ARCH != GLM_ARCH_PURE && GLM_HAS_UNRESTRICTED_UNIONS
|
||||
# include "func_trigonometric_simd.inl"
|
||||
#endif
|
||||
|
||||
Loaded 100 of 668 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user