19 Commits
Author SHA1 Message Date
Pedro Santos d309c16cb2 Release v1.1.0 (RommPS carries RomM Sync) 2026-10-09 02:32:32 -04:00
Soliton c632774407 RommPS carries RomM Sync and starts it: install from the store and go
RommPS bundles the payload from its own release (assets/rommps/romm-sync.elf)
and sends it to the HEN's loader when RomM Sync isn't running, or when the
running one is older. Setup's autostart step installs it into the HEN's
folder when it isn't there yet. tools/release-app.sh bundles the release's
own romm-sync.elf, checked against its SHA256SUMS.
2026-10-09 02:30:36 -04:00
Pedro Santos 754d4d60f2 Release v1.1.0
RomM Sync 1.1.0 and RommPS.
2026-10-09 01:54:11 -04:00
Soliton afd9406603 Shorter wording on the emulators step 2026-10-09 01:52:26 -04:00
Soliton f2a3871b4b Point the update check and the source link at s0liton/RommPS 2026-10-09 01:49:24 -04:00
Soliton 0894ec8df9 README picture: RommPS's library 2026-10-09 01:43:06 -04:00
Soliton 5b01d95ef5 Check the hard link portably in the e2e test (GNU stat -f means something else) 2026-10-09 01:42:33 -04:00
Soliton 87476147a0 Merge main: README title and cleanup 2026-10-09 01:37:03 -04:00
Soliton 694119b10f Cover caching, faster library, versions, and release prep for 1.1.0
- Every game's cover is kept on the console and refreshed at most once a
  day; RommPS asks for covers on screen first and caps the ones it keeps.
- Library pages are compressed and fetched ahead, list requests leave out
  RomM's id index and filter values, and RommPS only asks for the pages it
  lands on.
- Files RomM scans that aren't games are hidden and never downloaded, and
  manuals, soundtracks and screenshots inside a game's folder are skipped.
- Games with several versions say what sets each apart.
- Download speeds, hold Cross to cancel and delete, clear finished ones.
- Turning on an extra emulator links the games already there, with a
  notification; emulators with no preset (PS5NES, Porpoise) can be set up
  with folders the user picks.
- Fixes from the pre-release review: Mega Drive .md ROMs, cache pruning,
  the covers pass after a failed listing, unshared libcurl connections,
  the Downloads row bounds, the Home headline, hold-to-cancel state, and
  more.
- Release: hand-written notes in docs/releases, a contentVersion check,
  tools/release-app.sh for PPSA76677.zip, and the LLVM runtime licence.
2026-10-09 01:34:58 -04:00
Soliton a590964998 Add RommPS, the native PS5 app, and support for standalone PS5 emulators
RommPS (PPSA76677) is a native PS5 app for RomM Sync: library browsing by
platform with search and an A to Z bar, game details from RomM, downloads,
settings, the setup on the console, and themes.

The payload now handles standalone emulators on the PS5 (PSXS5 and
SwanStationPS5, PS5SX2, PS5N64), PS1 serials read from disc images (libchdr
for .chd), saves named by serial or file hash, several emulators per system
with one shared save where the format matches, shared memory card backups,
folders picked for systems without an emulator, and forgetting a previous
server's ids after pairing with another.

The PS4 build is marked experimental.
2026-10-08 22:05:23 -04:00
Pedro Santos 0ccf61ebd7 Change project name to RomM Server Client for PS4 and PS5
Updated project title and description to reflect support for PS4.
2026-10-08 14:34:04 -04:00
Pedro Santos 220f606256 Cleaned up README a bit....I gotta start being more concise.
Removed sections about release process and signing keys for clarity and conciseness.
2026-10-07 00:04:35 -04:00
Soliton 842e32cc73 Working prototype of PS4 build 2026-10-06 01:09:22 -04:00
Pedro Santos 44472365fc Merge pull request #4 from s0liton/fix/release-cache
Don't fail the release when the Docker build cache can't be saved
2026-10-03 12:30:40 -04:00
Pedro Santos 82d10ee0f3 Don't fail the release when the Docker build cache can't be saved 2026-10-03 12:18:37 -04:00
Pedro Santos fe8e3ba197 Added rebellion against DRM and greed. 2026-10-03 12:17:13 -04:00
Pedro Santos e84ddf7f5d Merge pull request #3 from s0liton/release/v1.0.2
Added: Check for updates feature with auto-reload
Added: Release signing and checking during update
Added: Alphabet ribbon for platform libraries large than 60 games (easier navigation)
Added: Additional TLS settings for Romm server connection
2026-10-03 12:10:05 -04:00
Soliton 200b3eecb0 Added update support, library alphabet ribbon, release signing, extra settings around server connection, general fixes 2026-10-03 11:58:51 -04:00
Pedro Santos 5c9e5f866d Update README.md
Clarifications.
2026-10-02 22:48:44 -04:00
664 changed files with 274833 additions and 891 deletions

No files matched your search

+1 -1
View File
@@ -12,5 +12,5 @@ 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
+110 -12
View File
@@ -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"
+7
View File
@@ -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/
+46 -39
View File
@@ -1,33 +1,55 @@
# 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
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 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
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
# 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/font_css.py $< $@ Inter
build/gen/icon_png.h: assets/icon0.png tools/embed.py
@mkdir -p $(dir $@)
python3 tools/embed.py $< $@ ICON_PNG
@@ -38,37 +60,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
+92 -15
View File
@@ -1,19 +1,52 @@
# RomM Sync for PS5
# RomM Server Client for PS4 and PS5
![Project Screenshot](assets/library.png)
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.
- **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 RommPS, the native app (or some other device), 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.
- **Easter Egg**
- Check in the Settings, scroll the settings list, see if you find the X.
## 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, this RomM client will work with all emulators, but I have included profiles for the emulators that exist for the console as of Oct 2026.
### Supported out of the box
Setup finds these on its own. Anything else can still sync by picking its folders during setup.
**PS5**
| Emulator | Systems | Saves |
| --- | --- | --- |
| RetroArch (mihawk-99's port) | 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) | Saves and states |
| PSXS5 / SwanStationPS5 | PS1 | One memory card per game, and states |
| PS5SX2 | PS2 | Its two shared memory cards, backed up to RomM (5.3 or newer) |
| PS5N64 | N64 | Saves and states |
PS5NES and Porpoise don't have presets yet, because neither works properly on my console right now:
- **PS5NES** v7 looks for its games in PS5SX2's folder instead of its own.
- **Porpoise** crashes the moment it leaves the app sandbox, with both OnionHEN and Lapy freeing it, so it fails to launch. Issue reported to the dev. In the meantime, you can sync files into the sandbox, but you won't have access to /data.
**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 +54,51 @@ 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 and open it. That's it: RommPS carries the RomM Sync payload and starts it for you, then walks you through setup. Its last step makes RomM Sync start with every jailbreak, so you don't have to open the app for syncing to work.
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.
To start RomM Sync with the console, let your HEN start it: the last setup step does this for you. Both HENs start `payloads/romm-sync.elf` when `romm-sync.elf.auto_start` sits next to it, in `/data/etaHEN/payloads` or `/data/OnionHEN/payloads` (onionHEN's Toolbox has a toggle for it under Payloads). RomM Sync works out which HEN is running. If you build it yourself, `make install PS5_HOST=<ps5-ip>` copies it there over FTP (add `PS5_FTP=ftp://<ps5-ip>:1337 PS5_HEN_DIR=/data/OnionHEN` for onionHEN's FTP plugin).
If your RomM server uses HTTPS, copy `cacert.pem` from the release to `/data/romm-sync/cacert.pem`.
### 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 experimental 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
There are two pieces to keep updated, and they update separately:
- **RomM Sync (the payload)** checks GitHub once a day (you can turn that off) and tells you when there's a new version. Settings can install it for you, but nothing installs on its own. Each payload release is signed, and RomM Sync only installs a file whose signature matches the key built into it, so nobody can slip a modified payload in through the updater.
- **RommPS (the app)** updates through ProsperoStore, like any other homebrew in its catalog. If the payload ever falls behind what the app needs, RommPS tells you to update it.
You can always update either one by hand from the releases page.
## Setup
@@ -59,8 +121,12 @@ LRPS2 keeps every game on one shared memory card by default. RomM stores saves p
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
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 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 +144,27 @@ 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
```
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

+31
View File
@@ -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.
+205
View File
@@ -0,0 +1,205 @@
/* 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 <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 <unistd.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;
}
void console_single_instance(void) {
pid_t pid;
int tries = 0;
/* Name the main thread so find_pid() of the next instance sees us. */
syscall(SYS_thr_set_name, -1, CONSOLE_PROC_NAME);
while ((pid = find_pid(CONSOLE_PROC_NAME)) > 0) {
if (kill(pid, SIGKILL) && errno != ESRCH) break;
sleep(1);
if (++tries >= 10) break;
}
}
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);
}
+35
View File
@@ -0,0 +1,35 @@
/* 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"
#define CONSOLE_ICON CONSOLE_DATA_DIR "/icon0.png"
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);
/* Names the main thread CONSOLE_PROC_NAME and kills any older copy. */
void console_single_instance(void);
/* 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
@@ -1,6 +1,7 @@
/* 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. */
* 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>
@@ -9,12 +10,35 @@
#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 const 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,
};
const plat_info_t *plat_info(void) { return &INFO; }
int plat_init(void) {
signal(SIGPIPE, SIG_IGN);
const char *d = getenv("ROMM_SYNC_DATA");
@@ -55,7 +79,19 @@ int plat_install_tile(int port) {
return -1;
}
const char *plat_name(void) { return "host"; }
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;
+355
View File
@@ -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(&param, 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)&param.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(&param) < 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;
}
+71
View File
@@ -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
+52
View File
@@ -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"}]
}
}
]
+163
View File
@@ -0,0 +1,163 @@
/* 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_DATA_DIR "/romm-sync.lock"
/* 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,
/* PS4 tiles need an installed package; there's no URL shortcut. */
.has_tile = 0,
/* GoldHEN runs the payload on a thread of its own process. */
.detach = 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);
}
int plat_install_tile(int port) {
(void)port;
return -1;
}
/* 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;
}
+89
View File
@@ -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 $@
+16
View File
@@ -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
+9
View File
@@ -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
+21
View File
@@ -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.
+21
View File
@@ -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();
}
};
+289
View File
@@ -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(&timestamp);
std::ofstream logfile;
logfile.open(logFileName, std::ios_base::app);
logfile << strtok(ctime(&timestamp), "\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);
}
}
}
+158
View File
@@ -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);
};
}
+108
View File
@@ -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
}
}
};
}
+252
View File
@@ -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;
}
};
+72
View File
@@ -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;
}
};
}
+184
View File
@@ -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
+121
View File
@@ -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,441 @@
// 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 "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 = std::string("http://127.0.0.1:8780/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
@@ -0,0 +1,926 @@
// RommPS: what the screen shows, kept up to date from the RomM Sync payload.
// See rommps_app.hpp.
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "rommps_app.hpp"
#include "cJSON.h"
#include "stb_image.h" // implemented by base/VulkanglTFModel.cpp
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <algorithm>
#include <cerrno>
#include <cstring>
#include <fstream>
#include <iterator>
#include <thread>
#include <cctype>
#include <cstdio>
#include <memory>
namespace rommps
{
namespace
{
constexpr float kStatusEvery = 3.0f;
constexpr float kDownloadsEvery = 1.0f; // while one is running
constexpr float kDownloadsIdleEvery = 5.0f; // otherwise
constexpr int kDecodesPerFrame = 2; // covers turned into textures each frame
constexpr std::size_t kCoverTextures = 360; // covers kept as textures (about 130 MB); the
// least recently drawn go, and come back from
// the payload's cache when drawn again
constexpr std::size_t kListsKept = 24; // game lists kept at once: every tile on the platforms page, and more
void *g_renderer = nullptr;
std::string url_escape(const std::string &s)
{
static const char hex[] = "0123456789ABCDEF";
std::string out;
for (unsigned char c : s)
{
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' ||
c == '.' || c == '~' || c == '/')
out += static_cast<char>(c);
else
{
out += '%';
out += hex[c >> 4];
out += hex[c & 15];
}
}
return out;
}
const char *str(const cJSON *o, const char *key, const char *fallback = "")
{
const cJSON *v = cJSON_GetObjectItemCaseSensitive(o, key);
return cJSON_IsString(v) ? v->valuestring : fallback;
}
double num(const cJSON *o, const char *key, double fallback = 0)
{
const cJSON *v = cJSON_GetObjectItemCaseSensitive(o, key);
return cJSON_IsNumber(v) ? v->valuedouble : fallback;
}
// The kit's fonts are baked for ASCII: accented letters fold to their plain
// ones ("Pok\xC3\xA9mon" reads "Pokemon"), typographic quotes and dashes to
// their ASCII forms, and anything else to "?".
std::string plain(const char *text)
{
static const char *const kLatin1 = // U+00C0 to U+00FF
"AAAAAAACEEEEIIII" "DNOOOOOxOUUUUYTs" "aaaaaaaceeeeiiii" "dnooooo/ouuuuyty";
std::string out;
const auto *p = reinterpret_cast<const unsigned char *>(text);
while (*p)
{
unsigned cp = *p++;
int more = cp >= 0xF0 ? 3 : cp >= 0xE0 ? 2 : cp >= 0xC0 ? 1 : 0;
if (more)
cp &= 0x3F >> more;
while (more-- > 0 && (*p & 0xC0) == 0x80)
cp = (cp << 6) | (*p++ & 0x3F);
if (cp < 0x80)
out += static_cast<char>(cp);
else if (cp >= 0xC0 && cp <= 0xFF)
out += kLatin1[cp - 0xC0];
else if (cp == 0x2018 || cp == 0x2019)
out += '\'';
else if (cp == 0x201C || cp == 0x201D)
out += '"';
else if (cp == 0x2013 || cp == 0x2014)
out += '-';
else if (cp == 0x2026)
out += "...";
else if (cp == 0xA0)
out += ' ';
else if (cp == 0xB7)
out += "\xC2\xB7"; // the middle dot is in the fonts: the app uses it too
else
out += '?';
}
return out;
}
bool flag(const cJSON *o, const char *key)
{
return cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(o, key));
}
// A cover's colours: its average as the body, a darker tone, and its most
// saturated region's colour as the accent, lifted so it reads on dark glass.
Palette palette_of(const unsigned char *rgba, int w, int h)
{
double sum[3] = {0, 0, 0}, sat_sum[3] = {0, 0, 0}, sat_weight = 0;
const int step = std::max(1, (w * h) / 4096);
int n = 0;
for (int i = 0; i < w * h; i += step)
{
const unsigned char *p = rgba + static_cast<std::size_t>(i) * 4;
const int mx = std::max({p[0], p[1], p[2]}), mn = std::min({p[0], p[1], p[2]});
const double sat = mx > 0 ? static_cast<double>(mx - mn) / mx : 0.0;
for (int c = 0; c < 3; ++c)
{
sum[c] += p[c];
sat_sum[c] += p[c] * sat * sat;
}
sat_weight += sat * sat;
++n;
}
if (n == 0)
return {};
auto rgb = [](double r, double g, double b) {
auto c = [](double v) { return static_cast<std::uint32_t>(std::clamp(v, 0.0, 255.0)); };
return Color::rgb((c(r) << 16) | (c(g) << 8) | c(b));
};
Palette out;
out.mid = rgb(sum[0] / n, sum[1] / n, sum[2] / n);
out.dark = rgb(sum[0] / n * 0.35, sum[1] / n * 0.35, sum[2] / n * 0.35);
if (sat_weight > 0.5)
{
double a[3] = {sat_sum[0] / sat_weight, sat_sum[1] / sat_weight, sat_sum[2] / sat_weight};
const double peak = std::max({a[0], a[1], a[2], 1.0});
const double lift = 230.0 / peak; // as bright as it gets, same hue
out.accent = rgb(a[0] * lift, a[1] * lift, a[2] * lift);
}
return out;
}
} // namespace
App &app()
{
static App instance;
return instance;
}
App::App() : api_("127.0.0.1", 8780)
{
refresh_status();
}
void App::say(const std::string &message)
{
toast_ = message;
toast_age_ = 0;
}
void App::tick(float dt, hui::gfx::Renderer *renderer)
{
clock_ += dt;
api_.poll();
for (Api &a : covers_api_)
a.poll();
for (Api &a : pages_api_)
a.poll();
// The pages the screen wanted last frame, in the order it asked. One it
// stopped wanting before a worker was free is never sent.
for (const auto &[key, page] : wanted_pages_)
if (pages_in_flight_ < kPageWorkers)
send_page(key, page);
wanted_pages_.clear();
// The covers drawn last frame and not asked for yet, in the order they
// were drawn; covers scrolled away from are never asked for.
for (const std::string &path : wanted_)
{
if (covers_in_flight_ >= kCoversInFlight)
break;
Cover &c = covers_[path];
if (c.requested)
continue;
c.requested = true;
++covers_in_flight_;
Api &worker = covers_api_[next_cover_worker_++ % kCoverWorkers];
worker.get("/cover?p=" + url_escape(path), [this, path](const Response &r) {
--covers_in_flight_;
if (r.ok() && !r.body.empty())
to_decode_.push_back({path, r.body});
else
covers_[path].failed = true;
});
}
wanted_.clear();
toast_age_ += dt;
if (!toast_.empty() && toast_age_ > 4.0f)
toast_.clear();
status_timer_ += dt;
if (status_timer_ >= kStatusEvery && !status_busy_)
refresh_status();
update_timer_ += dt;
if (update_.busy() && !update_busy_ && update_timer_ >= 1.0f)
refresh_update();
downloads_timer_ += dt;
if (status_.paired && !downloads_busy_ &&
downloads_timer_ >= (active_downloads_ ? kDownloadsEvery : kDownloadsIdleEvery))
refresh_downloads();
if (renderer)
evict_covers(renderer);
// The payload sent: its API answers within a few seconds.
if (start_state_ == 2)
{
start_state_ = 0;
status_timer_ = kStatusEvery;
}
else if (start_state_ == 3)
{
start_state_ = 0;
say("Couldn't start RomM Sync: " + start_error_);
}
// A few covers a frame become textures; the rest wait their turn, so a
// shelf full of new covers never costs a frame.
for (int i = 0; i < kDecodesPerFrame && !to_decode_.empty(); ++i)
{
Pending job = std::move(to_decode_.front());
to_decode_.pop_front();
Cover &cover = covers_[job.path];
int w = 0, h = 0, channels = 0;
unsigned char *rgba = stbi_load_from_memory(reinterpret_cast<const unsigned char *>(job.bytes.data()),
static_cast<int>(job.bytes.size()), &w, &h, &channels, 4);
if (!rgba)
{
cover.failed = true;
continue;
}
cover.palette = palette_of(rgba, w, h);
if (renderer)
cover.texture = renderer->create_texture(w, h, rgba);
stbi_image_free(rgba);
}
}
void App::evict_covers(hui::gfx::Renderer *renderer)
{
std::size_t loaded = 0;
for (const auto &[path, c] : covers_)
loaded += c.texture != 0;
if (loaded <= kCoverTextures)
return;
// Only covers not drawn for a few seconds, so no list being drawn still has one.
std::vector<std::pair<double, const std::string *>> old;
for (const auto &[path, c] : covers_)
if (c.texture && c.used < clock_ - 3.0)
old.push_back({c.used, &path});
std::sort(old.begin(), old.end());
std::vector<std::string> gone;
for (std::size_t i = 0; i < old.size() && loaded - gone.size() > kCoverTextures * 3 / 4; ++i)
gone.push_back(*old[i].second);
for (const std::string &path : gone)
{
renderer->destroy_texture(covers_[path].texture);
covers_.erase(path);
}
}
const std::string &App::bundled_payload()
{
if (!bundled_read_)
{
bundled_read_ = true;
std::ifstream f("/app0/assets/rommps/romm-sync.elf", std::ios::binary);
bundled_.assign(std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>());
}
return bundled_;
}
// Both HENs load ELF payloads sent to port 9021; RommPS reaches it on the
// console itself, the way it reaches the payload's API.
void App::start_payload()
{
if (start_state_ == 1)
return;
const std::string &elf = bundled_payload();
if (elf.size() < 4 || elf.compare(0, 4, "\x7f" "ELF") != 0)
{
start_error_ = "this RommPS doesn't carry the RomM Sync payload";
start_state_ = 3;
return;
}
start_error_.clear();
start_state_ = 1;
std::thread([this, elf] {
std::string err;
const int fd = ::socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in sa{};
sa.sin_family = AF_INET;
sa.sin_port = htons(9021);
::inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr);
if (fd < 0 || ::connect(fd, reinterpret_cast<sockaddr *>(&sa), sizeof sa) != 0)
err = std::string("the HEN's payload loader (port 9021) didn't answer: ") + std::strerror(errno);
for (std::size_t sent = 0; err.empty() && sent < elf.size();)
{
const ssize_t n = ::send(fd, elf.data() + sent, elf.size() - sent, 0);
if (n <= 0)
err = std::string("sending the payload failed: ") + std::strerror(errno);
else
sent += static_cast<std::size_t>(n);
}
if (fd >= 0)
::close(fd);
start_error_ = err;
start_state_ = err.empty() ? 2 : 3;
}).detach();
}
void App::install_autostart(Api::Callback done)
{
api_.post("/api/autostart/upload", bundled_payload(), [this, done](const Response &r) {
if (!r.ok())
say("Couldn't set up the autostart: " + r.message());
if (done)
done(r);
});
}
// "1.0.2" < "1.1.0", number by number. A build without a version ("dev")
// counts as new enough.
bool App::payload_too_old() const
{
const std::string &v = status_.version;
if (v.empty() || !std::isdigit(static_cast<unsigned char>(v[0])))
return false;
int have[3] = {}, want[3] = {};
std::sscanf(v.c_str(), "%d.%d.%d", &have[0], &have[1], &have[2]);
std::sscanf(kMinPayload, "%d.%d.%d", &want[0], &want[1], &want[2]);
for (int i = 0; i < 3; ++i)
if (have[i] != want[i])
return have[i] < want[i];
return false;
}
void App::refresh_status()
{
status_timer_ = 0;
status_busy_ = true;
api_.get("/api/status", [this](const Response &r) {
status_busy_ = false;
status_.known = true;
status_.reachable = r.status != 0;
// Not running: RommPS starts the payload it carries, once by itself
// (Cross on the screen tries again).
if (!status_.reachable && !tried_start_)
{
tried_start_ = true;
start_payload();
}
if (!r.ok())
{
status_.error = r.message();
return;
}
std::unique_ptr<cJSON, void (*)(cJSON *)> j(r.json(), cJSON_Delete);
if (!j)
return;
status_.error.clear();
status_.version = str(j.get(), "version");
// An older payload is replaced by the one RommPS carries, once it's
// idle: its autostart copy first, if it has one, then the running one.
if (payload_too_old() && !updated_payload_ && !flag(cJSON_GetObjectItemCaseSensitive(j.get(), "sync"), "running") &&
active_downloads_ == 0)
{
updated_payload_ = true;
say("Updating RomM Sync to " + std::string(kMinPayload));
api_.get("/api/autostart", [this](const Response &a) {
std::unique_ptr<cJSON, void (*)(cJSON *)> as(a.json(), cJSON_Delete);
if (a.ok() && flag(as.get(), "installed"))
install_autostart([this](const Response &) { start_payload(); });
else
start_payload();
});
}
status_.ip = str(j.get(), "ip");
status_.port = static_cast<int>(num(j.get(), "web_port", 8780));
status_.paired = flag(j.get(), "paired");
status_.setup_complete = flag(j.get(), "setup_complete");
status_.server = str(j.get(), "server_url");
status_.user = str(j.get(), "username");
status_.uptime = num(j.get(), "uptime");
const cJSON *sync = cJSON_GetObjectItemCaseSensitive(j.get(), "sync");
status_.syncing = flag(sync, "running");
status_.phase = str(sync, "phase");
status_.last_run = num(sync, "last_run");
status_.sync_count = static_cast<int>(num(sync, "sync_count"));
status_.tracked_saves = static_cast<int>(num(sync, "tracked_saves"));
status_.conflicts = cJSON_GetArraySize(cJSON_GetObjectItemCaseSensitive(sync, "conflicts"));
// The payload adds each sync at the end of its history: the last one is the newest.
const cJSON *history = cJSON_GetObjectItemCaseSensitive(sync, "history");
const cJSON *last = cJSON_GetArrayItem(history, cJSON_GetArraySize(history) - 1);
status_.last_result = last ? (str(last, "error")[0] ? str(last, "error") : "OK") : "";
status_.last_up = static_cast<int>(num(last, "uploaded"));
status_.last_down = static_cast<int>(num(last, "downloaded"));
status_.server_version = str(j.get(), "server_version");
const cJSON *covers = cJSON_GetObjectItemCaseSensitive(j.get(), "covers");
status_.covers_state = str(covers, "state");
status_.covers_done = static_cast<int>(num(covers, "done"));
status_.covers_total = static_cast<int>(num(covers, "total"));
const cJSON *pairing = cJSON_GetObjectItemCaseSensitive(j.get(), "pairing");
if (status_.pairing == "starting")
pairing = nullptr; // the answer to /api/pair/start says how it went
else
status_.pairing = str(pairing, "status");
if (pairing)
{
status_.pairing_message = str(pairing, "message");
status_.user_code = str(pairing, "user_code");
status_.verification_url = str(pairing, "verification_url");
}
if (!config_.known)
refresh_config();
const bool ready = status_.paired && status_.setup_complete;
if (ready && (!was_paired_ || platforms_.empty()) && !platforms_busy_)
{
refresh_platforms();
refresh_installed();
}
was_paired_ = ready;
});
}
void App::refresh_platforms()
{
platforms_busy_ = true;
api_.get("/api/platforms", [this](const Response &r) {
platforms_busy_ = false;
if (!r.ok())
{
say("Couldn't load your platforms: " + r.message());
return;
}
std::unique_ptr<cJSON, void (*)(cJSON *)> j(r.json(), cJSON_Delete);
std::vector<Platform> list;
const cJSON *p;
cJSON_ArrayForEach(p, j.get())
{
Platform plat;
plat.id = static_cast<int>(num(p, "id"));
plat.name = plain(str(p, "name"));
plat.slug = str(p, "slug");
plat.profile = str(p, "profile");
plat.rom_count = static_cast<int>(num(p, "rom_count"));
if (plat.rom_count > 0)
list.push_back(std::move(plat));
}
// Platforms with an emulator first, then by name.
std::stable_sort(list.begin(), list.end(), [](const Platform &a, const Platform &b) {
if (a.profile.empty() != b.profile.empty())
return !a.profile.empty();
return a.name < b.name;
});
platforms_ = std::move(list);
});
}
const Platform *App::platform_by_id(int id) const
{
for (const Platform &p : platforms_)
if (p.id == id)
return &p;
return nullptr;
}
GameList &App::games(int platform_id, const std::string &search)
{
const std::string key = std::to_string(platform_id) + "|" + search;
CachedList &entry = lists_[key];
if (!entry.list)
{
entry.list = std::make_unique<GameList>();
entry.list->platform_id = platform_id;
entry.list->search = search;
// Keep a few lists; the oldest goes when there are more.
if (lists_.size() > kListsKept)
{
auto oldest = lists_.end();
for (auto it = lists_.begin(); it != lists_.end(); ++it)
if (it->first != key && (oldest == lists_.end() || it->second.used < oldest->second.used))
oldest = it;
if (oldest != lists_.end())
lists_.erase(oldest);
}
}
CachedList &kept = lists_[key];
kept.used = clock_;
return *kept.list;
}
void App::ensure(GameList &list, int first, int last)
{
if (!list.ready())
last = 0; // the first page says how many there are
else
last = list.raw(std::min(last, list.total - 1));
first = list.ready() ? list.raw(std::max(first, 0)) : 0;
for (int page = first / GameList::kPage; page <= last / GameList::kPage && last >= 0; ++page)
{
if (static_cast<int>(list.pages.size()) <= page)
list.pages.resize(static_cast<std::size_t>(page) + 1, 0);
if (list.pages[static_cast<std::size_t>(page)])
continue;
const std::string key = std::to_string(list.platform_id) + "|" + list.search;
const std::pair<std::string, int> want{key, page};
if (std::find(wanted_pages_.begin(), wanted_pages_.end(), want) == wanted_pages_.end())
wanted_pages_.push_back(want);
}
}
void App::send_page(const std::string &key, int page)
{
auto found = lists_.find(key);
if (found == lists_.end() || !found->second.list)
return;
GameList &list = *found->second.list;
if (static_cast<int>(list.pages.size()) <= page || list.pages[static_cast<std::size_t>(page)])
return;
list.pages[static_cast<std::size_t>(page)] = 1;
++pages_in_flight_;
{
char path[512];
std::snprintf(path, sizeof path, "/api/roms?platform_id=%d&offset=%d&limit=%d&search=%s", list.platform_id,
page * GameList::kPage, GameList::kPage, url_escape(list.search).c_str());
pages_api_[next_page_worker_++ % kPageWorkers].get(path, [this, key, page](const Response &r) {
--pages_in_flight_;
auto it = lists_.find(key);
if (it == lists_.end() || !it->second.list)
return; // let go of meanwhile
GameList &l = *it->second.list;
if (!r.ok())
{
l.error = r.message();
if (static_cast<int>(l.pages.size()) > page)
l.pages[static_cast<std::size_t>(page)] = 0; // asked again when looked at
return;
}
std::unique_ptr<cJSON, void (*)(cJSON *)> j(r.json(), cJSON_Delete);
l.error.clear();
const int total = static_cast<int>(num(j.get(), "total"));
if (!l.ready() || total != static_cast<int>(l.games.size()))
{
l.games.resize(static_cast<std::size_t>(total));
l.total = total; // until reindex() below takes out what isn't a game
l.pages.resize(static_cast<std::size_t>((total + GameList::kPage - 1) / GameList::kPage), 0);
if (page < static_cast<int>(l.pages.size()))
l.pages[static_cast<std::size_t>(page)] = 1;
}
if (l.raw_letters.empty())
{
const cJSON *ci = cJSON_GetObjectItemCaseSensitive(j.get(), "char_index"), *c;
cJSON_ArrayForEach(c, ci) if (cJSON_IsNumber(c)) l.raw_letters.push_back({c->string, c->valueint});
std::sort(l.raw_letters.begin(), l.raw_letters.end(),
[](const auto &a, const auto &b) { return a.second < b.second; });
}
int index = page * GameList::kPage;
const cJSON *g;
cJSON_ArrayForEach(g, cJSON_GetObjectItemCaseSensitive(j.get(), "items"))
{
if (index >= total)
break;
Game &game = l.games[static_cast<std::size_t>(index++)];
game.id = static_cast<int>(num(g, "id"));
game.platform_id = static_cast<int>(num(g, "platform_id", l.platform_id));
game.name = plain(str(g, "name"));
game.fs_name = str(g, "fs_name");
game.cover = str(g, "cover");
game.size = num(g, "size");
game.installed = flag(g, "installed");
game.multi = flag(g, "multi");
game.not_game = flag(g, "not_game");
game.version = plain(str(g, "version"));
game.versions = static_cast<int>(num(g, "versions"));
}
l.reindex();
if (page < static_cast<int>(l.pages.size()))
l.pages[static_cast<std::size_t>(page)] = 2;
});
}
}
const Details &App::details(int rom_id)
{
auto found = details_.find(rom_id);
if (found != details_.end())
return found->second;
Details &d = details_[rom_id];
char path[64];
std::snprintf(path, sizeof path, "/api/roms/%d/details", rom_id);
api_.get(path, [this, rom_id](const Response &r) {
Details &out = details_[rom_id];
out.loaded = true;
if (!r.ok())
{
out.failed = true;
return;
}
std::unique_ptr<cJSON, void (*)(cJSON *)> j(r.json(), cJSON_Delete);
out.year = static_cast<int>(num(j.get(), "year"));
out.publishers = plain(str(j.get(), "publishers"));
out.developers = plain(str(j.get(), "developers"));
out.genres = plain(str(j.get(), "genres"));
out.players = plain(str(j.get(), "players"));
out.summary = plain(str(j.get(), "summary"));
out.status = str(j.get(), "status");
out.last_played = str(j.get(), "last_played");
out.rating = num(j.get(), "rating");
out.main_story_s = num(j.get(), "main_story_s");
out.play_ms = num(j.get(), "play_ms");
out.sessions = static_cast<int>(num(j.get(), "sessions"));
out.completion = static_cast<int>(num(j.get(), "completion"));
out.my_rating = static_cast<int>(num(j.get(), "my_rating"));
});
return d;
}
void App::refresh_installed()
{
api_.get("/api/installed?limit=30", [this](const Response &r) {
if (!r.ok())
return;
std::unique_ptr<cJSON, void (*)(cJSON *)> j(r.json(), cJSON_Delete);
std::vector<Game> list;
const cJSON *g;
cJSON_ArrayForEach(g, j.get())
{
Game game;
game.id = static_cast<int>(num(g, "id"));
game.platform_id = static_cast<int>(num(g, "platform_id"));
game.name = plain(str(g, "name"));
game.fs_name = str(g, "fs_name");
game.cover = str(g, "cover");
game.installed = true;
list.push_back(std::move(game));
}
installed_ = std::move(list);
});
}
const Cover &App::cover(const std::string &path)
{
static const Cover kNone;
if (path.empty())
return kNone;
Cover &c = covers_[path];
c.used = clock_;
// Asked for at the next tick, if it's still on screen then.
if (!c.requested && wanted_.size() < 64)
wanted_.push_back(path);
return c;
}
void App::refresh_downloads()
{
downloads_timer_ = 0;
downloads_busy_ = true;
api_.get("/api/downloads", [this](const Response &r) {
downloads_busy_ = false;
if (!r.ok())
return;
std::unique_ptr<cJSON, void (*)(cJSON *)> j(r.json(), cJSON_Delete);
std::vector<Download> list;
int active = 0;
bool finished_one = false;
const cJSON *d;
cJSON_ArrayForEach(d, j.get())
{
Download dl;
dl.id = static_cast<int>(num(d, "id"));
dl.rom_id = static_cast<int>(num(d, "rom_id"));
dl.name = plain(str(d, "name"));
dl.platform = plain(str(d, "platform"));
dl.status = str(d, "status");
dl.error = str(d, "error");
dl.done = num(d, "done");
dl.total = num(d, "total");
dl.speed = num(d, "speed");
active += dl.active();
// A game that just finished shows as downloaded on its shelf.
for (const Download &old : downloads_)
if (old.id == dl.id && old.active() && dl.status == "done")
finished_one = true;
list.push_back(std::move(dl));
}
downloads_ = std::move(list);
active_downloads_ = active;
// A game that finished shows as downloaded: the lists load again.
if (finished_one)
{
lists_.clear();
refresh_installed();
}
});
}
const Download *App::download_for(int rom_id) const
{
const Download *found = nullptr;
for (const Download &d : downloads_)
if (d.rom_id == rom_id)
found = &d; // the newest one wins
return found;
}
void App::download(int rom_id, const std::string &name)
{
// The name goes along, so the download shows it from the start.
std::unique_ptr<cJSON, void (*)(cJSON *)> j(cJSON_CreateObject(), cJSON_Delete);
cJSON_AddNumberToObject(j.get(), "rom_id", rom_id);
cJSON_AddStringToObject(j.get(), "name", name.c_str());
char *text = cJSON_PrintUnformatted(j.get());
const std::string body = text ? text : "{}";
cJSON_free(text);
api_.post("/api/download", body, [this](const Response &r) {
if (!r.ok())
say("Couldn't start the download: " + r.message());
refresh_downloads();
});
}
void App::clear_finished_downloads()
{
api_.post("/api/downloads/clear", "{}", [this](const Response &r) {
if (!r.ok())
say("Couldn't clear the list: " + r.message());
refresh_downloads();
});
}
void App::cancel_download(int job_id)
{
char body[64];
std::snprintf(body, sizeof body, "{\"id\":%d}", job_id);
api_.post("/api/downloads/cancel", body, [this](const Response &r) {
if (!r.ok())
say("Couldn't cancel: " + r.message());
refresh_downloads();
});
}
void App::sync_now()
{
api_.post("/api/sync", "{}", [this](const Response &r) {
say(r.ok() ? "Syncing" : "Couldn't start a sync: " + r.message());
refresh_status();
});
}
void App::refresh_config()
{
api_.get("/api/config", [this](const Response &r) {
if (!r.ok())
return;
std::unique_ptr<cJSON, void (*)(cJSON *)> j(r.json(), cJSON_Delete);
if (!j)
return;
Config c;
c.known = true;
c.server_url = str(j.get(), "server_url");
c.tls_verify = flag(j.get(), "tls_verify");
c.sync_interval_min = static_cast<int>(num(j.get(), "sync_interval_min"));
c.sync_on_game_exit = flag(j.get(), "sync_on_game_exit");
c.sync_on_game_start = flag(j.get(), "sync_on_game_start");
c.sync_on_change = flag(j.get(), "sync_on_change");
c.notify = flag(j.get(), "notify");
c.update_check = flag(j.get(), "update_check");
c.states = str(j.get(), "states", "off");
c.conflict_policy = str(j.get(), "conflict_policy", "ask");
c.ps2_cards = str(j.get(), "ps2_cards", "per_game");
c.cover_cache = flag(j.get(), "cover_cache");
c.download_concurrency = static_cast<int>(num(j.get(), "download_concurrency", 1));
config_ = c;
});
}
void App::set_config(const std::string &fields, const std::function<void(Config &)> &apply)
{
if (apply)
apply(config_);
api_.post("/api/config", "{" + fields + "}", [this](const Response &r) {
if (!r.ok())
{
say("Couldn't save that: " + r.message());
refresh_config();
}
});
}
void App::refresh_update()
{
update_timer_ = 0;
update_busy_ = true;
api_.get("/api/update", [this](const Response &r) {
update_busy_ = false;
if (!r.ok())
return;
std::unique_ptr<cJSON, void (*)(cJSON *)> j(r.json(), cJSON_Delete);
if (!j)
return;
const bool was_busy = update_.busy();
Update u;
u.known = true;
u.state = str(j.get(), "state");
u.message = str(j.get(), "message");
u.error = str(j.get(), "error");
u.current = str(j.get(), "current");
u.latest = str(j.get(), "latest");
u.available = flag(j.get(), "available");
u.autostart = flag(j.get(), "autostart");
u.done = num(j.get(), "done");
u.total = num(j.get(), "total");
update_ = u;
if (was_busy && !u.busy())
{
if (u.state == "error")
say("Update: " + (u.error.empty() ? u.message : u.error));
else if (u.available)
say("RomM Sync " + u.latest + " is available");
else if (u.state == "idle")
say("RomM Sync is up to date");
else if (!u.message.empty())
say(u.message);
}
});
}
void App::check_update()
{
api_.post("/api/update/check", "{}", [this](const Response &r) {
if (!r.ok())
say("Couldn't look for updates: " + r.message());
update_.state = "checking";
refresh_update();
});
}
void App::install_update()
{
api_.post("/api/update/install", "{}", [this](const Response &r) {
if (!r.ok())
say("Couldn't install the update: " + r.message());
refresh_update();
});
}
void App::pair_start(const std::string &server_url, bool tls_verify)
{
std::unique_ptr<cJSON, void (*)(cJSON *)> body(cJSON_CreateObject(), cJSON_Delete);
cJSON_AddStringToObject(body.get(), "server_url", server_url.c_str());
char *text = cJSON_PrintUnformatted(body.get());
const std::string json = text ? text : "{}";
cJSON_free(text);
status_.pairing = "starting";
status_.pairing_message.clear();
// The certificate setting has to be in place before the server is first contacted.
set_config(tls_verify ? "\"tls_verify\":true" : "\"tls_verify\":false",
[tls_verify](Config &c) { c.tls_verify = tls_verify; });
api_.post("/api/pair/start", json, [this](const Response &r) {
if (!r.ok())
{
status_.pairing = "error";
status_.pairing_message = r.message();
return;
}
std::unique_ptr<cJSON, void (*)(cJSON *)> j(r.json(), cJSON_Delete);
status_.pairing = str(j.get(), "status");
status_.pairing_message = str(j.get(), "message");
status_.user_code = str(j.get(), "user_code");
status_.verification_url = str(j.get(), "verification_url");
config_.known = false; // the server's address changed
});
}
void App::pair_forget()
{
api_.post("/api/pair/forget", "{}", [this](const Response &) {
platforms_.clear();
config_.known = false;
refresh_status();
});
}
void App::setup_restart()
{
api_.post("/api/setup/restart", "{}", [this](const Response &r) {
if (!r.ok())
say("Couldn't restart the setup: " + r.message());
refresh_status();
});
}
} // namespace rommps
extern "C" void rommps_set_renderer(void *renderer)
{
rommps::g_renderer = renderer;
}
extern "C" void *rommps_renderer()
{
return rommps::g_renderer;
}
@@ -0,0 +1,335 @@
// 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;
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
// it isn't running, and puts it in place of an older one.
static constexpr const char *kMinPayload = "1.1.0";
bool payload_too_old() const;
void start_payload(); // sends the payload to the HEN's loader
bool payload_starting() const { return start_state_ == 1; }
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();
// 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();
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:8780
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;
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,930 @@
// 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 <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_);
}
}
// 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 ? "RomM Sync " + s.version + " is too old: update it in Settings"
: 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);
std::string line = s.syncing ? (s.phase.empty() ? "Working" : s.phase)
: s.sync_count ? "Last sync " + ago(s.uptime - 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,296 @@
// 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.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.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, load romm-sync.elf after each boot");
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,188 @@
// 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;
};
} // 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()
+34
View File
@@ -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
+84
View File
@@ -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 ..
+67
View File
@@ -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)
+8
View File
@@ -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
Whitespace-only changes.
@@ -0,0 +1,111 @@
/// @ref core
/// @file glm/detail/func_vector_relational.hpp
///
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.7 Vector Relational Functions</a>
///
/// @defgroup core_func_vector_relational Vector Relational Functions
/// @ingroup core
///
/// Relational and equality operators (<, <=, >, >=, ==, !=) are defined to
/// operate on scalars and produce scalar Boolean results. For vector results,
/// use the following built-in functions.
///
/// In all cases, the sizes of all the input and return vectors for any particular
/// call must match.
#pragma once
#include "precision.hpp"
#include "setup.hpp"
namespace glm
{
/// @addtogroup core_func_vector_relational
/// @{
/// Returns the component-wise comparison result of x < y.
///
/// @tparam vecType Floating-point or integer vector types.
///
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/lessThan.xml">GLSL lessThan man page</a>
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.7 Vector Relational Functions</a>
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
GLM_FUNC_DECL vecType<L, bool, P> lessThan(vecType<L, T, P> const & x, vecType<L, T, P> const & y);
/// Returns the component-wise comparison of result x <= y.
///
/// @tparam vecType Floating-point or integer vector types.
///
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/lessThanEqual.xml">GLSL lessThanEqual man page</a>
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.7 Vector Relational Functions</a>
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
GLM_FUNC_DECL vecType<L, bool, P> lessThanEqual(vecType<L, T, P> const & x, vecType<L, T, P> const & y);
/// Returns the component-wise comparison of result x > y.
///
/// @tparam vecType Floating-point or integer vector types.
///
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/greaterThan.xml">GLSL greaterThan man page</a>
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.7 Vector Relational Functions</a>
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
GLM_FUNC_DECL vecType<L, bool, P> greaterThan(vecType<L, T, P> const & x, vecType<L, T, P> const & y);
/// Returns the component-wise comparison of result x >= y.
///
/// @tparam vecType Floating-point or integer vector types.
///
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/greaterThanEqual.xml">GLSL greaterThanEqual man page</a>
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.7 Vector Relational Functions</a>
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
GLM_FUNC_DECL vecType<L, bool, P> greaterThanEqual(vecType<L, T, P> const & x, vecType<L, T, P> const & y);
/// Returns the component-wise comparison of result x == y.
///
/// @tparam vecType Floating-point, integer or boolean vector types.
///
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/equal.xml">GLSL equal man page</a>
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.7 Vector Relational Functions</a>
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
GLM_FUNC_DECL vecType<L, bool, P> equal(vecType<L, T, P> const & x, vecType<L, T, P> const & y);
/// Returns the component-wise comparison of result x != y.
///
/// @tparam vecType Floating-point, integer or boolean vector types.
///
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/notEqual.xml">GLSL notEqual man page</a>
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.7 Vector Relational Functions</a>
template<length_t L, typename T, precision P, template<length_t, typename, precision> class vecType>
GLM_FUNC_DECL vecType<L, bool, P> notEqual(vecType<L, T, P> const & x, vecType<L, T, P> const & y);
/// Returns true if any component of x is true.
///
/// @tparam vecType Boolean vector types.
///
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/any.xml">GLSL any man page</a>
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.7 Vector Relational Functions</a>
template<length_t L, precision P, template<length_t, typename, precision> class vecType>
GLM_FUNC_DECL bool any(vecType<L, bool, P> const & v);
/// Returns true if all components of x are true.
///
/// @tparam vecType Boolean vector types.
///
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/all.xml">GLSL all man page</a>
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.7 Vector Relational Functions</a>
template<length_t L, precision P, template<length_t, typename, precision> class vecType>
GLM_FUNC_DECL bool all(vecType<L, bool, P> const & v);
/// Returns the component-wise logical complement of x.
/// /!\ Because of language incompatibilities between C++ and GLSL, GLM defines the function not but not_ instead.
///
/// @tparam vecType Boolean vector types.
///
/// @see <a href="http://www.opengl.org/sdk/docs/manglsl/xhtml/not.xml">GLSL not man page</a>
/// @see <a href="http://www.opengl.org/registry/doc/GLSLangSpec.4.20.8.pdf">GLSL 4.20.8 specification, section 8.7 Vector Relational Functions</a>
template<length_t L, precision P, template<length_t, typename, precision> class vecType>
GLM_FUNC_DECL vecType<L, bool, P> not_(vecType<L, bool, P> const & v);
/// @}
}//namespace glm
#include "func_vector_relational.inl"
Loaded 100 of 664 files, more files were not shown because too many files have changed in this diff. Show more