Compare commits
37 commits
3bb56c5bb0
...
4f95f3a928
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f95f3a928 | ||
|
|
3f545cc74d | ||
|
|
c731f79f81 | ||
|
|
c73457b913 | ||
|
|
6c18ddfa16 | ||
|
|
8db2d754f8 | ||
|
|
2446c75d87 | ||
|
|
e4c0479757 | ||
|
|
7503a73a57 | ||
|
|
cf7f5152bc | ||
|
|
44ff4c4839 | ||
|
|
cf3244197e | ||
|
|
3c59cf022a | ||
|
|
60760ba67f | ||
|
|
ac7e8889db | ||
|
|
26b48b6cfa | ||
|
|
b00b1b3151 | ||
|
|
297b79d347 | ||
|
|
82d37247d8 | ||
|
|
2f05c97bda | ||
|
|
b0f6c7a1b3 | ||
|
|
d91ba8c1b7 | ||
|
|
55d22e3164 | ||
|
|
f8907ca6ed | ||
|
|
ea1779a19e | ||
|
|
4998b7e017 | ||
|
|
4d2dcafb7a | ||
|
|
05bf7dd61f | ||
|
|
20f743d74b | ||
|
|
41e4a2ed9c | ||
|
|
c1c69d5e54 | ||
|
|
fd1855dc73 | ||
|
|
760366a0e4 | ||
|
|
fd4e92c69e | ||
|
|
cdb18da722 | ||
|
|
d5f9f7f0f0 | ||
|
|
8a7bfed29e |
|
|
@ -1,3 +0,0 @@
|
||||||
[target.x86_64-unknown-linux-gnu]
|
|
||||||
linker = "clang"
|
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold"]
|
|
||||||
59
Cargo.toml
59
Cargo.toml
|
|
@ -1,11 +1,24 @@
|
||||||
|
[package]
|
||||||
|
name = "vidya"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
members = [
|
members = [
|
||||||
"crates/renderer",
|
"crates/renderer",
|
||||||
"crates/window",
|
"crates/window",
|
||||||
"crates/game"
|
"crates/game",
|
||||||
]
|
"crates/text",
|
||||||
|
"crates/bevy_vulkan_render",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bevy_vulkan_render = { path = "crates/bevy_vulkan_render" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
bevy = { workspace = true }
|
||||||
|
|
||||||
[profile.debug-release]
|
[profile.debug-release]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
|
|
@ -15,23 +28,32 @@ debug = true
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
anyhow = "1.0.89"
|
anyhow = "1.0.89"
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
|
derive_more = { version = "1.0.0", features = ["deref", "debug", "deref_mut"] }
|
||||||
|
|
||||||
tracing = "0.1.40"
|
tracing = "0.1"
|
||||||
tracing-subscriber = {version ="0.3.18", features = ["env-filter"]}
|
tracing-subscriber = {version ="0.3", features = ["env-filter"]}
|
||||||
|
|
||||||
glam = {version = "0.29.0", features = ["bytemuck"]}
|
glam = {version = "0.29.0", features = ["bytemuck"]}
|
||||||
rand = "0.8.5"
|
rand = "0.9"
|
||||||
bitflags = "2.6"
|
bitflags = "2.6"
|
||||||
|
thread_local = "1.1.8"
|
||||||
|
|
||||||
ash = "0.38.0"
|
ash = "0.38.0"
|
||||||
ash-window = "0.13.0"
|
ash-window = "0.13.0"
|
||||||
vk-mem = "0.4.0"
|
vk-mem = "0.5.0"
|
||||||
|
gpu-allocator = { git = "https://github.com/janis-bhm/gpu-allocator", branch = "main" }
|
||||||
|
rectangle-pack = "0.4.2"
|
||||||
vk-sync = "0.1.6"
|
vk-sync = "0.1.6"
|
||||||
|
|
||||||
|
arrayvec = "0.7.6"
|
||||||
tinyvec = "1.8"
|
tinyvec = "1.8"
|
||||||
indexmap = "2"
|
indexmap = "2"
|
||||||
petgraph = "0.7"
|
petgraph = "0.7"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
|
ahash = "0.8"
|
||||||
|
|
||||||
|
# for non-cryptographic hashing of resources like pipelines, e.g. for caching
|
||||||
|
md-5 = "0.11.0"
|
||||||
|
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
|
|
||||||
|
|
@ -43,5 +65,26 @@ rayon = "1.10"
|
||||||
winit = {version = "0.30.5", features = ["rwh_06"]}
|
winit = {version = "0.30.5", features = ["rwh_06"]}
|
||||||
raw-window-handle = "0.6"
|
raw-window-handle = "0.6"
|
||||||
|
|
||||||
egui = "0.30"
|
egui = "0.32"
|
||||||
egui_winit_platform = "0.25"
|
egui_winit_platform = "0.27"
|
||||||
|
|
||||||
|
bevy = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy" }
|
||||||
|
bevy_app = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy" }
|
||||||
|
bevy_asset = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy" }
|
||||||
|
bevy_derive = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy" }
|
||||||
|
bevy_ecs = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy", features = ["multi_threaded", "trace"]}
|
||||||
|
bevy_hierarchy = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy" }
|
||||||
|
bevy_log = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy" }
|
||||||
|
bevy_math = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy" }
|
||||||
|
bevy_platform = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy" }
|
||||||
|
bevy_reflect = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy" }
|
||||||
|
bevy_tasks = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy" }
|
||||||
|
bevy_time = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy" }
|
||||||
|
bevy_transform = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy" }
|
||||||
|
bevy_utils = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy" }
|
||||||
|
bevy_window = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy" }
|
||||||
|
bevy_winit = { version = "0.19.0-dev", git = "https://github.com/bevyengine/bevy" }
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "bevy_integration"
|
||||||
|
path = "examples/bevy_integration.rs"
|
||||||
1
assets/fonts/.gitignore
vendored
Normal file
1
assets/fonts/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
*.ttf
|
||||||
28
assets/fonts/fetch.sh
Executable file
28
assets/fonts/fetch.sh
Executable file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
FORCE=0
|
||||||
|
if [[ "${1:-}" == "--force" ]]; then
|
||||||
|
FORCE=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BASE_URL="https://github.com/google/fonts/raw/refs/heads/main/"
|
||||||
|
|
||||||
|
declare -A FONTS=(
|
||||||
|
["Roboto.ttf"]="ofl/roboto/Roboto%5Bwdth,wght%5D.ttf"
|
||||||
|
["NotoSans.ttf"]="ofl/notosans/NotoSans%5Bwdth,wght%5D.ttf"
|
||||||
|
["NotoSansSC.ttf"]="ofl/notosanssc/NotoSansSC%5Bwdth,wght%5D.ttf"
|
||||||
|
)
|
||||||
|
|
||||||
|
for filename in "${!FONTS[@]}"; do
|
||||||
|
font_path="${FONTS[$filename]}"
|
||||||
|
url="${BASE_URL}${font_path}"
|
||||||
|
|
||||||
|
if [[ -f "$filename" && $FORCE -eq 0 ]]; then
|
||||||
|
echo "Skipping $filename (already exists)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Fetching $filename from $url"
|
||||||
|
curl -L "$url" -o "$filename"
|
||||||
|
done
|
||||||
266
assets/testing/hello.txt
Normal file
266
assets/testing/hello.txt
Normal file
|
|
@ -0,0 +1,266 @@
|
||||||
|
https://en.wiktionary.org/wiki/hello#Translations
|
||||||
|
Abkhaz: бзиа збаша (bzia zbaša), мыш бзи (məš bzi), (to a man) бзиара убааит (bziara ubaaiṭ), (to a woman) бзиара ббааит (bziara bbaaiṭ), (to more than one person) бзиара жәбааит pl (bziara ž°baaiṭ)
|
||||||
|
Afrikaans: hallo (af), goeiedag
|
||||||
|
Ainu: イランカラㇷ゚テ (irankarapte)
|
||||||
|
Albanian: tungjatjeta (sq), tung (informal), ç'kemi m or f
|
||||||
|
|
||||||
|
Arbëreshë Albanian: falem (sq)
|
||||||
|
|
||||||
|
Alemannic German: sälü, hoi, hello
|
||||||
|
Aleut: aang, draas
|
||||||
|
Ambonese Malay: wai
|
||||||
|
American Sign Language: B@Sfhead-PalmForward B@FromSfhead-PalmForward
|
||||||
|
Amharic: ሰላም (sälam)
|
||||||
|
Apache:
|
||||||
|
|
||||||
|
Jicarilla: dá nzhǫ́
|
||||||
|
Western Apache: dagotʼee, daʼanzho, yaʼateh
|
||||||
|
|
||||||
|
Arabic: السَّلَامُ عَلَيْكُمْ (ar) (as-salāmu ʿalaykum), سَلَام (ar) (salām), مَرْحَبًا (ar) (marḥaban), أَهْلًا (ar) (ʾahlan)
|
||||||
|
|
||||||
|
Egyptian Arabic: اهلاً (ahlan)
|
||||||
|
Iraqi Arabic: هلو (helaww)
|
||||||
|
|
||||||
|
Archi: салам алейкум (salam alejkum), варчӀами (warčʼami)
|
||||||
|
Armenian: բարև (hy) (barew), ողջույն (hy) (ołǰuyn)
|
||||||
|
Assamese: নমস্কাৰ (nomoskar) (very formal), আচ্চেলামো আলাইকোম (asselamü alaiküm) (formal, used among Muslims), হেল’ (helö)
|
||||||
|
Assyrian Neo-Aramaic: ܫܠܵܡܵܐ (šlama), (to a man) ܫܠܵܡܵܐ ܥܲܠܘܼܟ݂ (šlama ʿāloḳ), (to a woman) ܫܠܵܡܵܐ ܥܲܠܵܟ݂ܝ (šlama ʿālaḳ), (to more than one person) ܫܠܵܡܵܐ ܥܲܠܵܘܟ݂ܘܿܢ (šlama ʿāloḳon)
|
||||||
|
Asturian: hola (ast)
|
||||||
|
Azerbaijani: salam (az), səlam (South Azerbaijani), hər vaxtınız xeyir olsun, hər vaxtınız xeyir
|
||||||
|
Bashkir: сәләм (säläm)
|
||||||
|
Basque: kaixo (eu)
|
||||||
|
Bats: please add this translation if you can
|
||||||
|
Bavarian: servus, grias di, pfiati (Timau)
|
||||||
|
Belarusian: віта́ю (vitáju), здаро́ў (zdaróŭ) (colloquial), до́бры дзень (dóbry dzjenʹ) (good day)
|
||||||
|
Bengali: নমস্কার (bn) (nômôśkar), আসসালামুআলাইকুম (aśśalamualaikum), সালাম (śalam), হ্যালো (hjalo)
|
||||||
|
Bhojpuri: प्रणाम (praṇām)
|
||||||
|
Bouyei: mengz ndil
|
||||||
|
Bulgarian: здра́сти (bg) (zdrásti) (familiar), здраве́й (bg) sg (zdravéj) (familiar), здраве́йте (bg) pl (zdravéjte) (formal)
|
||||||
|
Burmese: မင်္ဂလာပါ (my) (mangga.lapa) , ဟဲလို (my) (hai:lui) (colloquial)
|
||||||
|
Catalan: hola (ca)
|
||||||
|
Cayuga: sgę́:nǫʔ
|
||||||
|
Central Atlas Tamazight: ⴰⵣⵓⵍ (azul)
|
||||||
|
Chamorro: håfa adai
|
||||||
|
Chechen: маршалла ду хьоьга (maršalla du ḥöga) (to one person), маршалла ду шуьга (maršalla du šüga) (to a group of people), ассаламу ӏалайкум (assalamu ʿalajkum)
|
||||||
|
Cherokee: ᎣᏏᏲ (chr) (osiyo)
|
||||||
|
Chichewa: moni
|
||||||
|
Chickasaw: chokma
|
||||||
|
Chinese:
|
||||||
|
|
||||||
|
Cantonese: 你好 (nei5 hou2), 哈佬 (haa1 lou2)
|
||||||
|
Dungan: ни хо (ni ho), сэляму (seli͡amu), хома (homa)
|
||||||
|
Hakka: 你好 (ngì-hó)
|
||||||
|
Mandarin: 你好 (zh) (nǐ hǎo), 您好 (zh) (nín hǎo) (polite), 你們好, 你们好 (nǐmen hǎo) (to a group of people), 好 (zh) (hǎo) (following an address form or name), 嗨 (zh) (hāi), 哈囉 (zh), 哈啰 (zh) (hāluó)
|
||||||
|
Min Dong: 汝好 (nṳ̄ hō̤)
|
||||||
|
Min Nan: 汝好 (lí hó)
|
||||||
|
Xiang: please add this translation if you can
|
||||||
|
Wu: 儂好, 侬好 (non hau)
|
||||||
|
|
||||||
|
Choctaw: halito
|
||||||
|
Chukchi: еттык (ettyk) (formal), ети (eti) (informal), етти (etti) (informal)
|
||||||
|
Coptic: ⲛⲟϥⲣⲓ (nofri)
|
||||||
|
Cornish: dydh da
|
||||||
|
Corsican: bonghjornu
|
||||||
|
Cree:
|
||||||
|
|
||||||
|
Plains Cree: tânisi
|
||||||
|
|
||||||
|
Czech: ahoj (cs), nazdar (cs) (informal), servus (cs) (informal), dobrý den (cs) (formal)
|
||||||
|
Danish: hej (da), dav (da), god dag (formal), hallo (da)
|
||||||
|
Dhivehi: އައްސަލާމު ޢަލައިކުމް (assalāmu ʿalaikum̊)
|
||||||
|
Dutch: hallo (nl), hoi (nl), hai (nl), hé (nl), dag (nl) (informal), goeiedag (nl), goededag (nl), goedendag (nl), goeiendag (nl) (formal)
|
||||||
|
Esperanto: saluton (eo)
|
||||||
|
Estonian: tere (et), hei (et)
|
||||||
|
Faroese: hey, halló
|
||||||
|
Fijian: bula (fj)
|
||||||
|
Finnish: terve (fi), moi (fi), hei (fi), moikka (fi)
|
||||||
|
French: bonjour (fr), salut (fr) (informal), coucou (fr)(informal), cocorico (fr)
|
||||||
|
Friulian: mandi
|
||||||
|
Galician: ola (gl), oula, ouga
|
||||||
|
Georgian: გამარჯობა (ka) (gamarǯoba), ჰეი (hei)
|
||||||
|
German: hallo (de), guten Tag (de), servus (de), moin (de), grüß Gott (de) (Southern German, Austria)
|
||||||
|
|
||||||
|
Alemannic German: grüezi
|
||||||
|
|
||||||
|
Gilbertese: mauri
|
||||||
|
Gothic: 𐌷𐌰𐌹𐌻𐍃 (hails), 𐌷𐌰𐌹𐌻𐌰 (haila)
|
||||||
|
Greek: γεια (el) (geia), γεια σου sg (geia sou), γεια σας pl (geia sas), χαίρετε (el) (chaírete)
|
||||||
|
|
||||||
|
Ancient: χαῖρε sg (khaîre), χαίρετε pl (khaírete), χαῖρε καί ὑγίαινε sg (khaîre kaí hugíaine)
|
||||||
|
|
||||||
|
Greenlandic: aluu (kl)
|
||||||
|
Guaraní: maitei (gn)
|
||||||
|
Gujarati: નમસ્તે (namaste), નમસ્કાર (namaskār)
|
||||||
|
Haitian Creole: bonjou
|
||||||
|
Hausa: sannu
|
||||||
|
Hawaiian: aloha
|
||||||
|
Hebrew: שָׁלוֹם (he) (shalóm), שָׁלוֹם עָלֵיכֶם (he) (shalóm 'aleikhém)
|
||||||
|
Hindi: नमस्ते (hi) (namaste), नमस्कार (hi) (namaskār), सलाम (hi) (salām) (used by Muslims), सत श्री अकाल (sat śrī akāl) (Sikh, hello/goodbye), हेलो (hi) (helo), हलो (halo), सत्य (hi) (satya), आदाब (hi) (ādāb)
|
||||||
|
Hmong:
|
||||||
|
Green Hmong: nyob zoo
|
||||||
|
White Hmong: nyob zoo
|
||||||
|
Hungarian: szia (hu), sziasztok (hu) pl (informal), szervusz (hu), szervusztok pl (somewhat formal), heló (hu), helló (hu) (informal), jó napot (hu), jó napot kívánok (hu) (formal), üdvözlöm
|
||||||
|
Icelandic: halló (is), hæ (is), góðan dag (is), góðan daginn (is)
|
||||||
|
Ido: hola (io)
|
||||||
|
Igbo: kèdu
|
||||||
|
Indonesian: hai (id), salam (id)
|
||||||
|
Interlingua: bon die, salute (ia)
|
||||||
|
Irish: Dia dhuit (formal, singular), Dia dhaoibh (formal, plural), Dia's Muire dhuit (formal, singular, response), Dia's Muire dhaoibh (formal, plural, response)
|
||||||
|
Isan: please add this translation if you can
|
||||||
|
Italian: ciao (it), salve (it), buongiorno (it), saluti (it) m pl
|
||||||
|
Iu Mien: yiem longx nyei
|
||||||
|
Jamaican Creole: ello, wah gwaan
|
||||||
|
Japanese: おはよう (ja) (ohayō) (morning), こんにちは (ja) (konnichi wa) (daytime), こんばんは (ja) (konban wa) (evening)
|
||||||
|
Javanese: halo
|
||||||
|
Jeju: 반갑수다 (ban-gapsuda), 펜안ᄒᆞ우꽈 (pen-anhawukkwa), 펜안 (pen-an)
|
||||||
|
Judeo-Tat: шолум (şolum)
|
||||||
|
Kabardian: уузыншэм (wwzənšăm)
|
||||||
|
Kabyle: azul
|
||||||
|
Kalmyk: мендвт (mendvt), менд (mend) (informal)
|
||||||
|
Kannada: ತುಳಿಲು (kn) (tuḷilu), ನಮಸ್ಕಾರ (kn) (namaskāra)
|
||||||
|
Karachay-Balkar: кюнюгюз ашхы болсун (künügüz aşxı bolsun), ассаламу алейкум (assalamu aleykum)
|
||||||
|
Karelian: terveh, hei
|
||||||
|
Kazakh: сәлем (kk) (sälem) (informal), сәлеметсіздер (sälemetsızder) (formal)
|
||||||
|
Khmer: ជំរាបសួរ (cumriəp suə), សួស្តី (suəsdəy)
|
||||||
|
Khün: please add this translation if you can
|
||||||
|
Kinyarwanda: muraho
|
||||||
|
Korean: 안녕하십니까 (annyeonghasimnikka) (formal), 안녕하세요 (ko) (annyeonghaseyo) (neutrally formal), 안녕(安寧) (ko) (annyeong) (informal)
|
||||||
|
Krio: kushɛ
|
||||||
|
Kurdish:
|
||||||
|
|
||||||
|
Northern Kurdish: merheba (ku), silav (ku), selam (ku)
|
||||||
|
|
||||||
|
Kyrgyz: саламатсыздарбы (salamatsızdarbı), салам (ky) (salam)
|
||||||
|
Ladino: shalom, bonjur, buenos diyas
|
||||||
|
Lak: салам (salam)
|
||||||
|
Lakota: háu
|
||||||
|
Lao: ສະບາຍດີ (sa bāi dī)
|
||||||
|
Latin: salvē (la) sg, salvēte (la) pl; avē (la) sg, avēte pl
|
||||||
|
Latvian: sveiki (informal to more than one person or people of indeterminate gender), sveiks (to a man), sveika (to a woman), čau (informal)
|
||||||
|
Laz: გეგაჯგინას (gegaǯginas)
|
||||||
|
Lezgi: салам (salam)
|
||||||
|
Lithuanian: labas (lt), sveikas (lt) (informal), sveiki (lt) (formal)
|
||||||
|
Livonian: tēriņtš
|
||||||
|
Luo: msawa
|
||||||
|
Lü: ᦍᦲᧃᦡᦲ (yiinḋii)
|
||||||
|
Luxembourgish: hallo
|
||||||
|
Macedonian: здраво (zdravo)
|
||||||
|
Malagasy: manao ahoana? (mg), salama (mg) (Tsimihety)
|
||||||
|
Malay: helo (ms), apa khabar (ms), salam (ms)
|
||||||
|
Malayalam: ഹലോ (halō), നമസ്തേ (ml) (namastē), നമസ്കാരം (ml) (namaskāraṃ)
|
||||||
|
Maltese: bonġu (mt) (before noon), bonswa (after noon), nsellimlek (formal one to one person), nsellmilkom (formal one to more than one person), nsellmulek (formal more than one person to one person), nsellmulkom (formal more than one person to more than one persons)
|
||||||
|
Manchu: ᠰᠠᡳᠶᡡᠨ (saiyūn)
|
||||||
|
Maori: kia ora (mi) (informal), tēnā koe (formal to one person), tēnā kōrua (formal to two people), tēnā koutou (formal to three or more people)
|
||||||
|
Mapudungun: mari mari
|
||||||
|
Maranungku: yo
|
||||||
|
Marathi: नमस्कार (mr) (namaskār)
|
||||||
|
Michif: tánishi, boñjour
|
||||||
|
Mingrelian: გომორძგუა (gomorʒgua)
|
||||||
|
Mohawk: sekoh
|
||||||
|
Mongolian:
|
||||||
|
|
||||||
|
Cyrillic: сайн уу? (mn) (sayn uu?) (informal), сайн байна уу? (mn) (sayn bayna uu?)
|
||||||
|
|
||||||
|
Mopan Maya: dʼyoos
|
||||||
|
Nahuatl: niltze (nah), panoltih
|
||||||
|
Navajo: yáʼátʼééh
|
||||||
|
Neapolitan: uè
|
||||||
|
Nepali: नमस्ते (ne) (namaste), नमस्कार (ne) (namaskār)
|
||||||
|
Norman: baon-n-jour (Guernsey), banjour (Guernsey), boujouo (continental Normandy), bouônjour (Jersey), bwõju (Sark)
|
||||||
|
Northern Thai: สบายดีก่อ
|
||||||
|
Norwegian:
|
||||||
|
|
||||||
|
Bokmål: hallo (no), hei (no), god dag (no) (formal), halla (no) (informal), heisann
|
||||||
|
|
||||||
|
Ojibwe: boozhoo
|
||||||
|
Okinawan: はいさい m (haisai), はいたい f (haitai), はい n (hai)
|
||||||
|
Old English: wes hāl
|
||||||
|
Oriya: ନମସ୍କାର (or) (nômôskarô)
|
||||||
|
Ossetian: салам (salam), байрай (bajraj), арфӕ (arfæ)
|
||||||
|
Palauan: alii
|
||||||
|
Pashto: سلام (ps) (salām), سلام الېک (slāmālék), السلام عليکم (as-salám alaykúm)
|
||||||
|
Persian: سلام (fa) (salâm), سلام علیکم (salâmo alaykom) (religious), درود (fa) (dorud) (literary)
|
||||||
|
Pitcairn-Norfolk: watawieh
|
||||||
|
Polish: cześć (pl) (informal), witaj (pl), witajcie, witam (more formal), dzień dobry (pl) (formal), siema (pl) (informal), halo (pl) (on phone), serwus (pl) (colloquial), cześka (colloquial), siemanero (colloquial)
|
||||||
|
Portuguese: oi (pt), olá (pt), (slang) e aí? (pt)
|
||||||
|
Punjabi: ਸਤਿ ਸ਼੍ਰੀ ਅਕਾਲ (sati śrī akāl)
|
||||||
|
Rapa Nui: 'iorana
|
||||||
|
Romani: te aves baxtalo (to a male), te aves baxtali (to a female), te aven baxtale (to two or more people)
|
||||||
|
Romanian: salut (ro), bună (ro), noroc (ro) (informal), bună ziua (formal), servus (ro)
|
||||||
|
Russian: приве́т (ru) (privét) (informal), здоро́во (ru) (zdoróvo) (colloquial), здра́вствуйте (ru) (zdrávstvujte) (formal, first "в" is silent), до́брый день (ru) (dóbryj denʹ), здра́вствуй (ru) (zdrávstvuj) (informal, first "в" is silent), салю́т (ru) (saljút)
|
||||||
|
Rusyn: наздар (nazdar)
|
||||||
|
Sami:
|
||||||
|
|
||||||
|
Inari Sami: tiervâ
|
||||||
|
Northern: dearvva, būres
|
||||||
|
Skolt: tiõrv
|
||||||
|
Southern: buaregh
|
||||||
|
|
||||||
|
Samoan: talofa
|
||||||
|
Sanskrit: नमस्कार (sa) (namaskāra), नमस्ते (namaste), नमो नमः (namo namaḥ) (formal)
|
||||||
|
Scots: hullo
|
||||||
|
Scottish Gaelic: halò (informal), latha math (formal), (informal) hòigh
|
||||||
|
Serbo-Croatian:
|
||||||
|
|
||||||
|
Cyrillic: здра̏во, ћа̑о, ме̏рха̄ба, селам, бог, бок
|
||||||
|
Roman: zdrȁvo (sh), ćȃo (sh), mȅrhāba, selam (sh), bog, bok (sh)
|
||||||
|
|
||||||
|
Sesotho: lumela
|
||||||
|
Shan: please add this translation if you can
|
||||||
|
Shona: mhoro
|
||||||
|
Sichuan Yi: please add this translation if you can
|
||||||
|
Sicilian: ciao, salutamu
|
||||||
|
Sindhi: هيلو
|
||||||
|
Sinhalese: හලෝ (halō), ආයුබෝවන් (si) (āyubōwan)
|
||||||
|
Situ: please add this translation if you can
|
||||||
|
Slovak: ahoj (sk), nazdar (informal), servus (sk) (informal), dobrý deň (formal)
|
||||||
|
Slovene: žívjo, zdrávo (informal), dóber dán, pozdravljeni (formal)
|
||||||
|
Somali: ma nabad baa, waa nabad
|
||||||
|
Sorbian:
|
||||||
|
|
||||||
|
Lower Sorbian: dobry źeń
|
||||||
|
|
||||||
|
Sotho: dumela (st)
|
||||||
|
Spanish: hola (es), buenos días (es), qué tal, buenas tardes (es)
|
||||||
|
Sundanese: halo
|
||||||
|
Svan: ხოჩა ლადა̈ღ (xoča ladäɣ)
|
||||||
|
Swahili: jambo (sw), salaam
|
||||||
|
Swedish: hallå (sv), hej (sv), god dag (sv) (formal), tjena (sv), hejsan (sv) (informal), tja (sv)
|
||||||
|
Tagalog: kamusta (tl)/kumusta (tl), musta (tl) (slang), hoy (tl), huy, oy/oi (informal), uy/ui (informal)
|
||||||
|
Tajik: салом (salom)
|
||||||
|
Tamil: வணக்கம் (ta) (vaṇakkam)
|
||||||
|
Tangsa: äshazhoix
|
||||||
|
Tatar: сәлам (tt) (sälam)
|
||||||
|
Telugu: నమసకారం (namasakāraṁ), బాగున్నారా (bāgunnārā)
|
||||||
|
Tetum: please add this translation if you can
|
||||||
|
Thai: สวัสดี (th) (sà-wàt-dii), สวัสดีครับ (male speaker), สวัสดีค่ะ (female speaker), หวัดดี (wàt-dii)
|
||||||
|
Tibetan: བཀྲ་ཤིས་བདེ་ལེགས (bkra shis bde legs)
|
||||||
|
Tigrinya: ሰላም (sälam)
|
||||||
|
Tongan: mālō e lelei
|
||||||
|
Tswana: dumela (tn) (singular, as in dumela, rra, "hello sir"), dumelang (tn) (plural, as in dumelang, borra, "hello gentlemen")
|
||||||
|
Turkish: merhaba (tr), selam (tr)
|
||||||
|
Turkmen: salam
|
||||||
|
Tuvan: экии (ekii)
|
||||||
|
Udmurt: ӟеч (dźeć), чырткем (ćyrtkem), умой (umoj)
|
||||||
|
Ukrainian: приві́т (uk) (pryvít) (informal), здоро́в був (uk) (zdoróv buv) (informal), добри́день (uk) (dobrýdenʹ) (neutral or formal), чоло́м (čolóm)
|
||||||
|
Urdu: سلام علیکم (salām-o-alaikum), اسلام علیکم (literally “Peace be upon you”), اسلام علیکم ورحمۃاللہ وبرکاتہ (literally “Peace be upon you & May Allah bless”), آداب (ur) (ādāb)
|
||||||
|
Uyghur: سالام (salam)
|
||||||
|
Uzbek: salom (uz)
|
||||||
|
Venetian: ciao (vec)
|
||||||
|
Vietnamese: xin chào (vi), chào (vi)
|
||||||
|
Volapük: glidis
|
||||||
|
Walloon: bondjoû (wa), a (wa), diewåde (wa) (old)
|
||||||
|
Welsh: helo (cy), bore da (good morning), dydd da (good day), hylo
|
||||||
|
West Frisian: hallo, hoi
|
||||||
|
Winnebago: haho (male speaker), hą (female speaker), hinįkaraginʼ
|
||||||
|
Xhosa: molo sg, molweni pl
|
||||||
|
Xibe: ᠪᠠᡳᡨᠠᡴᡡ
|
||||||
|
ᠨᠠ (baitakū na)
|
||||||
|
Yakut: эҕэрдэ (eğerde), дорообо (doroobo) (informal)
|
||||||
|
Yiddish: שלום־עליכם (sholem-aleykhem), אַ גוטן (yi) (a gutn), גוט־מאָרגן (yi) (gut-morgn)
|
||||||
|
Yoruba: Pẹlẹ o
|
||||||
|
Yup'ik: waqaa, cama-i
|
||||||
|
Zapotec: padiull
|
||||||
|
Zazaki: sılam, namaste
|
||||||
|
Zhuang: mwngz ndei
|
||||||
|
Zulu: sawubona (zu) (familiar), sanibonani (plural, respectful)
|
||||||
24
crates/bevy_vulkan_render/Cargo.toml
Normal file
24
crates/bevy_vulkan_render/Cargo.toml
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
[package]
|
||||||
|
name = "bevy_vulkan_render"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
trace = ["tracing"]
|
||||||
|
default = ["trace"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tracing = { workspace = true, optional = true }
|
||||||
|
bevy_app = { workspace = true }
|
||||||
|
bevy_asset = { workspace = true }
|
||||||
|
bevy_derive = { workspace = true }
|
||||||
|
bevy_ecs = { workspace = true }
|
||||||
|
bevy_log = { workspace = true }
|
||||||
|
bevy_platform = { workspace = true }
|
||||||
|
bevy_reflect = { workspace = true }
|
||||||
|
bevy_tasks = { workspace = true }
|
||||||
|
bevy_time = { workspace = true }
|
||||||
|
bevy_utils = { workspace = true }
|
||||||
|
bevy_window = { workspace = true }
|
||||||
|
renderer = { path = "../renderer" }
|
||||||
|
async-channel = "2"
|
||||||
153
crates/bevy_vulkan_render/src/extract_param.rs
Normal file
153
crates/bevy_vulkan_render/src/extract_param.rs
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
use crate::extract_plugin::MainWorld;
|
||||||
|
use bevy_ecs::{
|
||||||
|
change_detection::Tick,
|
||||||
|
prelude::*,
|
||||||
|
query::FilteredAccessSet,
|
||||||
|
system::{
|
||||||
|
ReadOnlySystemParam, SystemMeta, SystemParam, SystemParamItem, SystemParamValidationError,
|
||||||
|
SystemState,
|
||||||
|
},
|
||||||
|
world::unsafe_world_cell::UnsafeWorldCell,
|
||||||
|
};
|
||||||
|
use core::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
/// A helper for accessing [`MainWorld`] content using a system parameter.
|
||||||
|
///
|
||||||
|
/// A [`SystemParam`] adapter which applies the contained `SystemParam` to the [`World`]
|
||||||
|
/// contained in [`MainWorld`]. This parameter only works for systems run
|
||||||
|
/// during the [`ExtractSchedule`](crate::ExtractSchedule).
|
||||||
|
///
|
||||||
|
/// This requires that the contained [`SystemParam`] does not mutate the world, as it
|
||||||
|
/// uses a read-only reference to [`MainWorld`] internally.
|
||||||
|
///
|
||||||
|
/// ## Context
|
||||||
|
///
|
||||||
|
/// [`ExtractSchedule`] is used to extract (move) data from the simulation world ([`MainWorld`]) to the
|
||||||
|
/// render world. The render world drives rendering each frame (generally to a `Window`).
|
||||||
|
/// This design is used to allow performing calculations related to rendering a prior frame at the same
|
||||||
|
/// time as the next frame is simulated, which increases throughput (FPS).
|
||||||
|
///
|
||||||
|
/// [`Extract`] is used to get data from the main world during [`ExtractSchedule`].
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use bevy_ecs::prelude::*;
|
||||||
|
/// use bevy_render::Extract;
|
||||||
|
/// use bevy_render::sync_world::RenderEntity;
|
||||||
|
/// # #[derive(Component)]
|
||||||
|
/// // Do make sure to sync the cloud entities before extracting them.
|
||||||
|
/// # struct Cloud;
|
||||||
|
/// fn extract_clouds(mut commands: Commands, clouds: Extract<Query<RenderEntity, With<Cloud>>>) {
|
||||||
|
/// for cloud in &clouds {
|
||||||
|
/// commands.entity(cloud).insert(Cloud);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`ExtractSchedule`]: crate::ExtractSchedule
|
||||||
|
/// [Window]: bevy_window::Window
|
||||||
|
pub struct Extract<'w, 's, P>
|
||||||
|
where
|
||||||
|
P: ReadOnlySystemParam + 'static,
|
||||||
|
{
|
||||||
|
item: SystemParamItem<'w, 's, P>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct ExtractState<P: SystemParam + 'static> {
|
||||||
|
state: SystemState<P>,
|
||||||
|
main_world_state: <Res<'static, MainWorld> as SystemParam>::State,
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: The only `World` access (`Res<MainWorld>`) is read-only.
|
||||||
|
unsafe impl<P> ReadOnlySystemParam for Extract<'_, '_, P> where P: ReadOnlySystemParam {}
|
||||||
|
|
||||||
|
// SAFETY: The only `World` access is properly registered by `Res<MainWorld>::init_state`.
|
||||||
|
// This call will also ensure that there are no conflicts with prior params.
|
||||||
|
unsafe impl<P> SystemParam for Extract<'_, '_, P>
|
||||||
|
where
|
||||||
|
P: ReadOnlySystemParam,
|
||||||
|
{
|
||||||
|
type State = ExtractState<P>;
|
||||||
|
type Item<'w, 's> = Extract<'w, 's, P>;
|
||||||
|
|
||||||
|
fn init_state(world: &mut World) -> Self::State {
|
||||||
|
let mut main_world = world.resource_mut::<MainWorld>();
|
||||||
|
ExtractState {
|
||||||
|
state: SystemState::new(&mut main_world),
|
||||||
|
main_world_state: Res::<MainWorld>::init_state(world),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_access(
|
||||||
|
state: &Self::State,
|
||||||
|
system_meta: &mut SystemMeta,
|
||||||
|
component_access_set: &mut FilteredAccessSet,
|
||||||
|
world: &mut World,
|
||||||
|
) {
|
||||||
|
Res::<MainWorld>::init_access(
|
||||||
|
&state.main_world_state,
|
||||||
|
system_meta,
|
||||||
|
component_access_set,
|
||||||
|
world,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn get_param<'w, 's>(
|
||||||
|
state: &'s mut Self::State,
|
||||||
|
system_meta: &SystemMeta,
|
||||||
|
world: UnsafeWorldCell<'w>,
|
||||||
|
change_tick: Tick,
|
||||||
|
) -> Result<Self::Item<'w, 's>, SystemParamValidationError> {
|
||||||
|
// SAFETY:
|
||||||
|
// - The caller ensures that `world` is the same one that `init_state` was called with.
|
||||||
|
// - The caller ensures that no other `SystemParam`s will conflict with the accesses we have registered.
|
||||||
|
let main_world = unsafe {
|
||||||
|
Res::<MainWorld>::get_param(
|
||||||
|
&mut state.main_world_state,
|
||||||
|
system_meta,
|
||||||
|
world,
|
||||||
|
change_tick,
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
let item = state.state.get(main_world.into_inner())?;
|
||||||
|
Ok(Extract { item })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w, 's, P> Deref for Extract<'w, 's, P>
|
||||||
|
where
|
||||||
|
P: ReadOnlySystemParam,
|
||||||
|
{
|
||||||
|
type Target = SystemParamItem<'w, 's, P>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w, 's, P> DerefMut for Extract<'w, 's, P>
|
||||||
|
where
|
||||||
|
P: ReadOnlySystemParam,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'w, 's, P> IntoIterator for &'a Extract<'w, 's, P>
|
||||||
|
where
|
||||||
|
P: ReadOnlySystemParam,
|
||||||
|
&'a SystemParamItem<'w, 's, P>: IntoIterator,
|
||||||
|
{
|
||||||
|
type Item = <&'a SystemParamItem<'w, 's, P> as IntoIterator>::Item;
|
||||||
|
type IntoIter = <&'a SystemParamItem<'w, 's, P> as IntoIterator>::IntoIter;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
(&self.item).into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
139
crates/bevy_vulkan_render/src/extract_plugin.rs
Normal file
139
crates/bevy_vulkan_render/src/extract_plugin.rs
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use bevy_app::{App, Plugin, SubApp};
|
||||||
|
use bevy_ecs::{
|
||||||
|
resource::Resource,
|
||||||
|
schedule::{IntoScheduleConfigs, Schedule, ScheduleBuildSettings, ScheduleLabel, Schedules},
|
||||||
|
world::{Mut, World},
|
||||||
|
};
|
||||||
|
use bevy_utils::default;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Render, RenderApp, RenderSystems, ScratchMainWorld, SyncWorldPlugin,
|
||||||
|
sync_world::{despawn_temporary_render_entities, entity_sync_system},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The simulation [`World`] of the application, stored as a resource.
|
||||||
|
///
|
||||||
|
/// This resource is only available during [`ExtractSchedule`] and not
|
||||||
|
/// during command application of that schedule.
|
||||||
|
/// See [`Extract`] for more details.
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
pub struct MainWorld(World);
|
||||||
|
|
||||||
|
impl Deref for MainWorld {
|
||||||
|
type Target = World;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for MainWorld {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Schedule which extract data from the main world and inserts it into the render world.
|
||||||
|
///
|
||||||
|
/// This step should be kept as short as possible to increase the "pipelining potential" for
|
||||||
|
/// running the next frame while rendering the current frame.
|
||||||
|
///
|
||||||
|
/// This schedule is run on the main world, but its buffers are not applied
|
||||||
|
/// until it is returned to the render world.
|
||||||
|
#[derive(ScheduleLabel, PartialEq, Eq, Debug, Clone, Hash, Default)]
|
||||||
|
pub struct ExtractSchedule;
|
||||||
|
|
||||||
|
/// Executes the [`ExtractSchedule`] step of the renderer.
|
||||||
|
/// This updates the render world with the extracted ECS data of the current frame.
|
||||||
|
fn extract(main_world: &mut World, render_world: &mut World) {
|
||||||
|
// temporarily add the app world to the render world as a resource
|
||||||
|
let scratch_world = main_world.remove_resource::<ScratchMainWorld>().unwrap();
|
||||||
|
let inserted_world = core::mem::replace(main_world, scratch_world.0);
|
||||||
|
render_world.insert_resource(MainWorld(inserted_world));
|
||||||
|
render_world.run_schedule(ExtractSchedule);
|
||||||
|
|
||||||
|
// move the app world back, as if nothing happened.
|
||||||
|
let inserted_world = render_world.remove_resource::<MainWorld>().unwrap();
|
||||||
|
let scratch_world = core::mem::replace(main_world, inserted_world.0);
|
||||||
|
main_world.insert_resource(ScratchMainWorld(scratch_world));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies the commands from the extract schedule. This happens during
|
||||||
|
/// the render schedule rather than during extraction to allow the commands to run in parallel with the
|
||||||
|
/// main app when pipelined rendering is enabled.
|
||||||
|
fn apply_extract_commands(render_world: &mut World) {
|
||||||
|
render_world.resource_scope(|render_world, mut schedules: Mut<Schedules>| {
|
||||||
|
schedules
|
||||||
|
.get_mut(ExtractSchedule)
|
||||||
|
.unwrap()
|
||||||
|
.apply_deferred(render_world);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Plugin that sets up the [`RenderApp`] and handles extracting data from the
|
||||||
|
/// main world to the render world.
|
||||||
|
pub struct ExtractPlugin {
|
||||||
|
/// Function that gets run at the beginning of each extraction.
|
||||||
|
///
|
||||||
|
/// Gets the main world and render world as arguments (in that order).
|
||||||
|
pub pre_extract: fn(&mut World, &mut World),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ExtractPlugin {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
pre_extract: |_, _| {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for ExtractPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_plugins(SyncWorldPlugin);
|
||||||
|
app.init_resource::<ScratchMainWorld>();
|
||||||
|
|
||||||
|
let mut render_app = SubApp::new();
|
||||||
|
|
||||||
|
let mut extract_schedule = Schedule::new(ExtractSchedule);
|
||||||
|
// We skip applying any commands during the ExtractSchedule
|
||||||
|
// so commands can be applied on the render thread.
|
||||||
|
extract_schedule.set_build_settings(ScheduleBuildSettings {
|
||||||
|
auto_insert_apply_deferred: false,
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
extract_schedule.set_apply_final_deferred(false);
|
||||||
|
|
||||||
|
render_app.add_schedule(Render::base_schedule());
|
||||||
|
render_app.add_schedule(extract_schedule);
|
||||||
|
render_app.add_systems(
|
||||||
|
Render,
|
||||||
|
(
|
||||||
|
// This set applies the commands from the extract schedule while the render schedule
|
||||||
|
// is running in parallel with the main app.
|
||||||
|
apply_extract_commands.in_set(RenderSystems::ExtractCommands),
|
||||||
|
despawn_temporary_render_entities.in_set(RenderSystems::PostCleanup),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let pre_extract = self.pre_extract;
|
||||||
|
render_app.set_extract(move |main_world, render_world| {
|
||||||
|
pre_extract(main_world, render_world);
|
||||||
|
|
||||||
|
{
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
let _stage_span = bevy_log::info_span!("entity_sync").entered();
|
||||||
|
entity_sync_system(main_world, render_world);
|
||||||
|
}
|
||||||
|
|
||||||
|
// run extract schedule
|
||||||
|
extract(main_world, render_world);
|
||||||
|
});
|
||||||
|
|
||||||
|
let (sender, receiver) = bevy_time::create_time_channels();
|
||||||
|
render_app.insert_resource(sender);
|
||||||
|
app.insert_resource(receiver);
|
||||||
|
app.insert_sub_app(RenderApp, render_app);
|
||||||
|
}
|
||||||
|
}
|
||||||
149
crates/bevy_vulkan_render/src/lib.rs
Normal file
149
crates/bevy_vulkan_render/src/lib.rs
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use bevy_app::prelude::*;
|
||||||
|
use bevy_asset::AssetServer;
|
||||||
|
use bevy_ecs::{prelude::*, schedule::ScheduleBuildSettings};
|
||||||
|
|
||||||
|
use bevy_app::AppLabel;
|
||||||
|
use bevy_ecs::schedule::ScheduleLabel;
|
||||||
|
use bevy_utils::default;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
extract_plugin::{ExtractPlugin, ExtractSchedule},
|
||||||
|
sync_world::despawn_temporary_render_entities,
|
||||||
|
window::{
|
||||||
|
ExtractedWindows, Surfaces, configure_surfaces, extract_windows, need_surface_configuration,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use crate::window::{SurfaceData, Surfaces};
|
||||||
|
}
|
||||||
|
|
||||||
|
// The main plugin for the Vulkan renderer.
|
||||||
|
// This plugin is responsible for setting up the render app, and spawning the render thread.
|
||||||
|
// The render thread waits for the main app to send it render app, and then updates the render up before sending it back to the main app.
|
||||||
|
// Before the main app sends the render app, it runs the sync and extract schedules to update the entity mappings as well as extract any required entities and components from the main world to the render world.
|
||||||
|
// Then, the main world sends the render app to the render thread and starts to execute its schedules until the next frame, at which point it will wait for the render app to be sent back before running the sync and extract schedules again.
|
||||||
|
// The render thread will apply the extract commands in order to allow the main thread to get back to work sooner.
|
||||||
|
|
||||||
|
/// The main render schedule.
|
||||||
|
#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
|
||||||
|
pub struct Render;
|
||||||
|
|
||||||
|
impl Render {
|
||||||
|
/// Sets up the base structure of the rendering [`Schedule`].
|
||||||
|
///
|
||||||
|
/// The sets defined in this enum are configured to run in order.
|
||||||
|
pub fn base_schedule() -> Schedule {
|
||||||
|
use RenderSystems::*;
|
||||||
|
|
||||||
|
let mut schedule = Schedule::new(Self);
|
||||||
|
|
||||||
|
schedule.configure_sets((ExtractCommands, Render, Cleanup, PostCleanup).chain());
|
||||||
|
|
||||||
|
schedule.configure_sets((ExtractCommands, Prepare).chain());
|
||||||
|
|
||||||
|
schedule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The startup schedule of the [`RenderApp`]
|
||||||
|
#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
|
||||||
|
pub struct RenderStartup;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct RenderPlugin;
|
||||||
|
|
||||||
|
/// A label for the rendering sub-app.
|
||||||
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
|
||||||
|
pub struct RenderApp;
|
||||||
|
|
||||||
|
/// The systems sets of the default [`App`] rendering schedule.
|
||||||
|
///
|
||||||
|
/// These can be useful for ordering, but you almost never want to add your systems to these sets.
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||||
|
pub enum RenderSystems {
|
||||||
|
/// This is used for applying the commands from the [`ExtractSchedule`]
|
||||||
|
ExtractCommands,
|
||||||
|
/// Prepare render resources from extracted data for the GPU based on their sorted order.
|
||||||
|
/// Create [`BindGroups`](render_resource::BindGroup) that depend on those data.
|
||||||
|
Prepare,
|
||||||
|
/// Actual rendering happens here.
|
||||||
|
/// In most cases, only the render backend should insert resources here.
|
||||||
|
Render,
|
||||||
|
/// Cleanup render resources here.
|
||||||
|
Cleanup,
|
||||||
|
/// Final cleanup occurs: all entities will be despawned.
|
||||||
|
///
|
||||||
|
/// Runs after [`Cleanup`](RenderSystems::Cleanup).
|
||||||
|
PostCleanup,
|
||||||
|
}
|
||||||
|
|
||||||
|
mod extract_plugin;
|
||||||
|
|
||||||
|
fn mock_render_system() {
|
||||||
|
eprintln!("This is a mock render system. It doesn't do anything.");
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for RenderPlugin {
|
||||||
|
fn build(&self, app: &mut bevy_app::App) {
|
||||||
|
app.add_plugins(ExtractPlugin::default());
|
||||||
|
|
||||||
|
let asset_server = app.world().resource::<AssetServer>().clone();
|
||||||
|
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||||
|
render_app.insert_resource(asset_server);
|
||||||
|
render_app.init_schedule(RenderStartup);
|
||||||
|
render_app.update_schedule = Some(Render.intern());
|
||||||
|
render_app.add_systems(Render, mock_render_system.in_set(RenderSystems::Render));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_vulkan(app: &mut App) -> Option<()> {
|
||||||
|
let device = renderer::device::Device::new_from_default_desc(None, &[]).ok()?;
|
||||||
|
|
||||||
|
let device = RenderDevice { device };
|
||||||
|
app.insert_resource(device.clone());
|
||||||
|
|
||||||
|
let render_app = app.sub_app_mut(RenderApp);
|
||||||
|
render_app.insert_resource(device.clone());
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A "scratch" world used to avoid allocating new worlds every frame when
|
||||||
|
/// swapping out the [`MainWorld`] for [`ExtractSchedule`].
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
struct ScratchMainWorld(World);
|
||||||
|
|
||||||
|
fn render_start() {
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
tracing::event!(tracing::Level::DEBUG, "render_start");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_startup() {
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
tracing::event!(tracing::Level::DEBUG, "render_startup");
|
||||||
|
}
|
||||||
|
|
||||||
|
mod extract_param;
|
||||||
|
mod rendering;
|
||||||
|
mod sync_world;
|
||||||
|
mod window;
|
||||||
|
|
||||||
|
pub use rendering::RenderingPlugin;
|
||||||
|
pub use sync_world::{MainEntity, MainEntityHashMap, MainEntityHashSet, SyncWorldPlugin};
|
||||||
|
|
||||||
|
#[derive(Resource, Clone)]
|
||||||
|
pub struct RenderDevice {
|
||||||
|
device: renderer::device::Device,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for RenderDevice {
|
||||||
|
type Target = renderer::device::Device;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.device
|
||||||
|
}
|
||||||
|
}
|
||||||
205
crates/bevy_vulkan_render/src/rendering.rs
Normal file
205
crates/bevy_vulkan_render/src/rendering.rs
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
use async_channel::{Receiver, Sender};
|
||||||
|
|
||||||
|
use bevy_app::{App, AppExit, AppLabel, Plugin, SubApp};
|
||||||
|
use bevy_ecs::{
|
||||||
|
resource::Resource,
|
||||||
|
schedule::MainThreadExecutor,
|
||||||
|
world::{Mut, World},
|
||||||
|
};
|
||||||
|
use bevy_tasks::ComputeTaskPool;
|
||||||
|
|
||||||
|
use crate::RenderApp;
|
||||||
|
|
||||||
|
/// A Label for the sub app that runs the parts of pipelined rendering that need to run on the main thread.
|
||||||
|
///
|
||||||
|
/// The Main schedule of this app can be used to run logic after the render schedule starts, but
|
||||||
|
/// before I/O processing. This can be useful for something like frame pacing.
|
||||||
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
|
||||||
|
pub struct RenderExtractApp;
|
||||||
|
|
||||||
|
/// Channels used by the main app to send and receive the render app.
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct RenderAppChannels {
|
||||||
|
app_to_render_sender: Sender<SubApp>,
|
||||||
|
render_to_app_receiver: Receiver<SubApp>,
|
||||||
|
render_app_in_render_thread: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderAppChannels {
|
||||||
|
/// Create a `RenderAppChannels` from a [`async_channel::Receiver`] and [`async_channel::Sender`]
|
||||||
|
pub fn new(
|
||||||
|
app_to_render_sender: Sender<SubApp>,
|
||||||
|
render_to_app_receiver: Receiver<SubApp>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
app_to_render_sender,
|
||||||
|
render_to_app_receiver,
|
||||||
|
render_app_in_render_thread: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send the `render_app` to the rendering thread.
|
||||||
|
pub fn send_blocking(&mut self, render_app: SubApp) {
|
||||||
|
self.app_to_render_sender.send_blocking(render_app).unwrap();
|
||||||
|
self.render_app_in_render_thread = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive the `render_app` from the rendering thread.
|
||||||
|
/// Return `None` if the render thread has panicked.
|
||||||
|
pub async fn recv(&mut self) -> Option<SubApp> {
|
||||||
|
let render_app = self.render_to_app_receiver.recv().await.ok()?;
|
||||||
|
self.render_app_in_render_thread = false;
|
||||||
|
Some(render_app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for RenderAppChannels {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.render_app_in_render_thread {
|
||||||
|
// Any non-send data in the render world was initialized on the main thread.
|
||||||
|
// So on dropping the main world and ending the app, we block and wait for
|
||||||
|
// the render world to return to drop it. Which allows the non-send data
|
||||||
|
// drop methods to run on the correct thread.
|
||||||
|
self.render_to_app_receiver.recv_blocking().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`PipelinedRenderingPlugin`] can be added to your application to enable pipelined rendering.
|
||||||
|
///
|
||||||
|
/// This moves rendering into a different thread, so that the Nth frame's rendering can
|
||||||
|
/// be run at the same time as the N + 1 frame's simulation.
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// |--------------------|--------------------|--------------------|--------------------|
|
||||||
|
/// | simulation thread | frame 1 simulation | frame 2 simulation | frame 3 simulation |
|
||||||
|
/// |--------------------|--------------------|--------------------|--------------------|
|
||||||
|
/// | rendering thread | | frame 1 rendering | frame 2 rendering |
|
||||||
|
/// |--------------------|--------------------|--------------------|--------------------|
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The plugin is dependent on the [`RenderApp`] added by [`crate::RenderPlugin`] and so must
|
||||||
|
/// be added after that plugin. If it is not added after, the plugin will do nothing.
|
||||||
|
///
|
||||||
|
/// A single frame of execution looks something like below
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// |---------------------------------------------------------------------------|
|
||||||
|
/// | | | RenderExtractApp schedule | winit events | main schedule |
|
||||||
|
/// | sync | extract |----------------------------------------------------------|
|
||||||
|
/// | | | extract commands | rendering schedule |
|
||||||
|
/// |---------------------------------------------------------------------------|
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - `sync` is the step where the entity-entity mapping between the main and render world is updated.
|
||||||
|
/// This is run on the main app's thread. For more information checkout [`SyncWorldPlugin`].
|
||||||
|
/// - `extract` is the step where data is copied from the main world to the render world.
|
||||||
|
/// This is run on the main app's thread.
|
||||||
|
/// - On the render thread, we first apply the `extract commands`. This is not run during extract, so the
|
||||||
|
/// main schedule can start sooner.
|
||||||
|
/// - Then the `rendering schedule` is run. See [`RenderSystems`](crate::RenderSystems) for the standard steps in this process.
|
||||||
|
/// - In parallel to the rendering thread the [`RenderExtractApp`] schedule runs. By
|
||||||
|
/// default, this schedule is empty. But it is useful if you need something to run before I/O processing.
|
||||||
|
/// - Next all the `winit events` are processed.
|
||||||
|
/// - And finally the `main app schedule` is run.
|
||||||
|
/// - Once both the `main app schedule` and the `render schedule` are finished running, `extract` is run again.
|
||||||
|
///
|
||||||
|
/// [`SyncWorldPlugin`]: crate::sync_world::SyncWorldPlugin
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct RenderingPlugin;
|
||||||
|
|
||||||
|
impl Plugin for RenderingPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
// Don't add RenderExtractApp if RenderApp isn't initialized.
|
||||||
|
if app.get_sub_app(RenderApp).is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
app.insert_resource(MainThreadExecutor::new());
|
||||||
|
|
||||||
|
let mut sub_app = SubApp::new();
|
||||||
|
sub_app.set_extract(renderer_extract);
|
||||||
|
app.insert_sub_app(RenderExtractApp, sub_app);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets up the render thread and inserts resources into the main app used for controlling the render thread.
|
||||||
|
fn cleanup(&self, app: &mut App) {
|
||||||
|
// skip setting up when headless
|
||||||
|
if app.get_sub_app(RenderExtractApp).is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (app_to_render_sender, app_to_render_receiver) = async_channel::bounded::<SubApp>(1);
|
||||||
|
let (render_to_app_sender, render_to_app_receiver) = async_channel::bounded::<SubApp>(1);
|
||||||
|
|
||||||
|
let mut render_app = app
|
||||||
|
.remove_sub_app(RenderApp)
|
||||||
|
.expect("Unable to get RenderApp. Another plugin may have removed the RenderApp before RenderingPlugin");
|
||||||
|
|
||||||
|
// clone main thread executor to render world
|
||||||
|
let executor = app.world().get_resource::<MainThreadExecutor>().unwrap();
|
||||||
|
render_app.world_mut().insert_resource(executor.clone());
|
||||||
|
|
||||||
|
render_to_app_sender.send_blocking(render_app).unwrap();
|
||||||
|
|
||||||
|
app.insert_resource(RenderAppChannels::new(
|
||||||
|
app_to_render_sender,
|
||||||
|
render_to_app_receiver,
|
||||||
|
));
|
||||||
|
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
let _span = tracing::info_span!("render thread").entered();
|
||||||
|
|
||||||
|
let compute_task_pool = ComputeTaskPool::get();
|
||||||
|
loop {
|
||||||
|
// run a scope here to allow main world to use this thread while it's waiting for the render app
|
||||||
|
let sent_app = compute_task_pool
|
||||||
|
.scope(|s| {
|
||||||
|
s.spawn(async { app_to_render_receiver.recv().await });
|
||||||
|
})
|
||||||
|
.pop();
|
||||||
|
let Some(Ok(mut render_app)) = sent_app else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
let _sub_app_span = tracing::info_span!("sub app", name = ?RenderApp).entered();
|
||||||
|
render_app.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
if render_to_app_sender.send_blocking(render_app).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::debug!("exiting pipelined rendering thread");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function waits for the rendering world to be received,
|
||||||
|
// runs extract, and then sends the rendering world back to the render thread.
|
||||||
|
fn renderer_extract(app_world: &mut World, _world: &mut World) {
|
||||||
|
app_world.resource_scope(|world, main_thread_executor: Mut<MainThreadExecutor>| {
|
||||||
|
world.resource_scope(|world, mut render_channels: Mut<RenderAppChannels>| {
|
||||||
|
// we use a scope here to run any main thread tasks that the render world still needs to run
|
||||||
|
// while we wait for the render world to be received.
|
||||||
|
if let Some(mut render_app) = ComputeTaskPool::get()
|
||||||
|
.scope_with_executor(true, Some(&*main_thread_executor.0), |s| {
|
||||||
|
s.spawn(async { render_channels.recv().await });
|
||||||
|
})
|
||||||
|
.pop()
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
render_app.extract(world);
|
||||||
|
|
||||||
|
render_channels.send_blocking(render_app);
|
||||||
|
} else {
|
||||||
|
// Renderer thread panicked
|
||||||
|
world.write_message(AppExit::error());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
613
crates/bevy_vulkan_render/src/sync_world.rs
Normal file
613
crates/bevy_vulkan_render/src/sync_world.rs
Normal file
|
|
@ -0,0 +1,613 @@
|
||||||
|
use bevy_app::Plugin;
|
||||||
|
use bevy_derive::{Deref, DerefMut};
|
||||||
|
use bevy_ecs::{
|
||||||
|
component::Component,
|
||||||
|
entity::{ContainsEntity, Entity, EntityEquivalent, EntityHash},
|
||||||
|
lifecycle::{Add, Remove},
|
||||||
|
observer::On,
|
||||||
|
query::With,
|
||||||
|
reflect::ReflectComponent,
|
||||||
|
resource::Resource,
|
||||||
|
system::{Local, Query, ResMut, SystemState},
|
||||||
|
world::{Mut, World},
|
||||||
|
};
|
||||||
|
use bevy_platform::collections::{HashMap, HashSet};
|
||||||
|
use bevy_reflect::{Reflect, std_traits::ReflectDefault};
|
||||||
|
|
||||||
|
/// A plugin that synchronizes entities with [`SyncToRenderWorld`] between the main world and the render world.
|
||||||
|
///
|
||||||
|
/// All entities with the [`SyncToRenderWorld`] component are kept in sync. It
|
||||||
|
/// is automatically added as a required component by [`ExtractComponentPlugin`]
|
||||||
|
/// and [`SyncComponentPlugin`], so it doesn't need to be added manually when
|
||||||
|
/// spawning or as a required component when either of these plugins are used.
|
||||||
|
///
|
||||||
|
/// # Implementation
|
||||||
|
///
|
||||||
|
/// Bevy's renderer is architected independently from the main app.
|
||||||
|
/// It operates in its own separate ECS [`World`], so the renderer logic can run in parallel with the main world logic.
|
||||||
|
/// This is called "Pipelined Rendering", see [`PipelinedRenderingPlugin`] for more information.
|
||||||
|
///
|
||||||
|
/// [`SyncWorldPlugin`] is the first thing that runs every frame and it maintains an entity-to-entity mapping
|
||||||
|
/// between the main world and the render world.
|
||||||
|
/// It does so by spawning and despawning entities in the render world, to match spawned and despawned entities in the main world.
|
||||||
|
/// The link between synced entities is maintained by the [`RenderEntity`] and [`MainEntity`] components.
|
||||||
|
///
|
||||||
|
/// The [`RenderEntity`] contains the corresponding render world entity of a main world entity, while [`MainEntity`] contains
|
||||||
|
/// the corresponding main world entity of a render world entity.
|
||||||
|
/// For convenience, [`QueryData`](bevy_ecs::query::QueryData) implementations are provided for both components:
|
||||||
|
/// adding [`MainEntity`] to a query (without a `&`) will return the corresponding main world [`Entity`],
|
||||||
|
/// and adding [`RenderEntity`] will return the corresponding render world [`Entity`].
|
||||||
|
/// If you have access to the component itself, the underlying entities can be accessed by calling `.id()`.
|
||||||
|
///
|
||||||
|
/// Synchronization is necessary preparation for extraction ([`ExtractSchedule`](crate::ExtractSchedule)), which copies over component data from the main
|
||||||
|
/// to the render world for these entities.
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// |--------------------------------------------------------------------|
|
||||||
|
/// | | | Main world update |
|
||||||
|
/// | sync | extract |---------------------------------------------------|
|
||||||
|
/// | | | Render world update |
|
||||||
|
/// |--------------------------------------------------------------------|
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// An example for synchronized main entities 1v1 and 18v1
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// |---------------------------Main World------------------------------|
|
||||||
|
/// | Entity | Component |
|
||||||
|
/// |-------------------------------------------------------------------|
|
||||||
|
/// | ID: 1v1 | PointLight | RenderEntity(ID: 3V1) | SyncToRenderWorld |
|
||||||
|
/// | ID: 18v1 | PointLight | RenderEntity(ID: 5V1) | SyncToRenderWorld |
|
||||||
|
/// |-------------------------------------------------------------------|
|
||||||
|
///
|
||||||
|
/// |----------Render World-----------|
|
||||||
|
/// | Entity | Component |
|
||||||
|
/// |---------------------------------|
|
||||||
|
/// | ID: 3v1 | MainEntity(ID: 1V1) |
|
||||||
|
/// | ID: 5v1 | MainEntity(ID: 18V1) |
|
||||||
|
/// |---------------------------------|
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Note that this effectively establishes a link between the main world entity and the render world entity.
|
||||||
|
/// Not every entity needs to be synchronized, however; only entities with the [`SyncToRenderWorld`] component are synced.
|
||||||
|
/// Adding [`SyncToRenderWorld`] to a main world component will establish such a link.
|
||||||
|
/// Once a synchronized main entity is despawned, its corresponding render entity will be automatically
|
||||||
|
/// despawned in the next `sync`.
|
||||||
|
///
|
||||||
|
/// The sync step does not copy any of component data between worlds, since its often not necessary to transfer over all
|
||||||
|
/// the components of a main world entity.
|
||||||
|
/// The render world probably cares about a `Position` component, but not a `Velocity` component.
|
||||||
|
/// The extraction happens in its own step, independently from, and after synchronization.
|
||||||
|
///
|
||||||
|
/// Moreover, [`SyncWorldPlugin`] only synchronizes *entities*. [`RenderAsset`](crate::render_asset::RenderAsset)s like meshes and textures are handled
|
||||||
|
/// differently.
|
||||||
|
///
|
||||||
|
/// [`PipelinedRenderingPlugin`]: crate::pipelined_rendering::PipelinedRenderingPlugin
|
||||||
|
/// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin
|
||||||
|
/// [`SyncComponentPlugin`]: crate::sync_component::SyncComponentPlugin
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct SyncWorldPlugin;
|
||||||
|
|
||||||
|
impl Plugin for SyncWorldPlugin {
|
||||||
|
fn build(&self, app: &mut bevy_app::App) {
|
||||||
|
app.init_resource::<PendingSyncEntity>();
|
||||||
|
app.add_observer(
|
||||||
|
|add: On<Add, SyncToRenderWorld>, mut pending: ResMut<PendingSyncEntity>| {
|
||||||
|
pending.push(EntityRecord::Added(add.entity));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
app.add_observer(
|
||||||
|
|remove: On<Remove, SyncToRenderWorld>,
|
||||||
|
mut pending: ResMut<PendingSyncEntity>,
|
||||||
|
query: Query<&RenderEntity>| {
|
||||||
|
if let Ok(e) = query.get(remove.entity) {
|
||||||
|
pending.push(EntityRecord::Removed(*e));
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Marker component that indicates that its entity needs to be synchronized to the render world.
|
||||||
|
///
|
||||||
|
/// This component is automatically added as a required component by [`ExtractComponentPlugin`] and [`SyncComponentPlugin`].
|
||||||
|
/// For more information see [`SyncWorldPlugin`].
|
||||||
|
///
|
||||||
|
/// NOTE: This component should persist throughout the entity's entire lifecycle.
|
||||||
|
/// If this component is removed from its entity, the entity will be despawned.
|
||||||
|
///
|
||||||
|
/// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin
|
||||||
|
/// [`SyncComponentPlugin`]: crate::sync_component::SyncComponentPlugin
|
||||||
|
#[derive(Component, Copy, Clone, Debug, Default, Reflect)]
|
||||||
|
#[reflect(Component, Default, Clone)]
|
||||||
|
#[component(storage = "SparseSet")]
|
||||||
|
pub struct SyncToRenderWorld;
|
||||||
|
|
||||||
|
/// Component added on the main world entities that are synced to the Render World in order to keep track of the corresponding render world entity.
|
||||||
|
///
|
||||||
|
/// Can also be used as a newtype wrapper for render world entities.
|
||||||
|
#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, Reflect)]
|
||||||
|
#[component(clone_behavior = Ignore)]
|
||||||
|
#[reflect(Component, Clone)]
|
||||||
|
pub struct RenderEntity(Entity);
|
||||||
|
impl RenderEntity {
|
||||||
|
#[inline]
|
||||||
|
pub fn id(&self) -> Entity {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Entity> for RenderEntity {
|
||||||
|
fn from(entity: Entity) -> Self {
|
||||||
|
RenderEntity(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContainsEntity for RenderEntity {
|
||||||
|
fn entity(&self) -> Entity {
|
||||||
|
self.id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: RenderEntity is a newtype around Entity that derives its comparison traits.
|
||||||
|
unsafe impl EntityEquivalent for RenderEntity {}
|
||||||
|
|
||||||
|
/// Component added on the render world entities to keep track of the corresponding main world entity.
|
||||||
|
///
|
||||||
|
/// Can also be used as a newtype wrapper for main world entities.
|
||||||
|
#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Reflect)]
|
||||||
|
#[reflect(Component, Clone)]
|
||||||
|
pub struct MainEntity(Entity);
|
||||||
|
|
||||||
|
impl MainEntity {
|
||||||
|
#[inline]
|
||||||
|
pub fn id(&self) -> Entity {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Entity> for MainEntity {
|
||||||
|
fn from(entity: Entity) -> Self {
|
||||||
|
MainEntity(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContainsEntity for MainEntity {
|
||||||
|
fn entity(&self) -> Entity {
|
||||||
|
self.id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: RenderEntity is a newtype around Entity that derives its comparison traits.
|
||||||
|
unsafe impl EntityEquivalent for MainEntity {}
|
||||||
|
|
||||||
|
/// A [`HashMap`] pre-configured to use [`EntityHash`] hashing with a [`MainEntity`].
|
||||||
|
pub type MainEntityHashMap<V> = HashMap<MainEntity, V, EntityHash>;
|
||||||
|
|
||||||
|
/// A [`HashSet`] pre-configured to use [`EntityHash`] hashing with a [`MainEntity`]..
|
||||||
|
pub type MainEntityHashSet = HashSet<MainEntity, EntityHash>;
|
||||||
|
|
||||||
|
/// Marker component that indicates that its entity needs to be despawned at the end of the frame.
|
||||||
|
#[derive(Component, Copy, Clone, Debug, Default, Reflect)]
|
||||||
|
#[reflect(Component, Default, Clone)]
|
||||||
|
pub struct TemporaryRenderEntity;
|
||||||
|
|
||||||
|
/// A record enum to what entities with [`SyncToRenderWorld`] have been added or removed.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum EntityRecord {
|
||||||
|
/// When an entity is spawned on the main world, notify the render world so that it can spawn a corresponding
|
||||||
|
/// entity. This contains the main world entity.
|
||||||
|
Added(Entity),
|
||||||
|
/// When an entity is despawned on the main world, notify the render world so that the corresponding entity can be
|
||||||
|
/// despawned. This contains the render world entity.
|
||||||
|
Removed(RenderEntity),
|
||||||
|
/// When a component is removed from an entity, notify the render world so that the corresponding component can be
|
||||||
|
/// removed. This contains the main world entity.
|
||||||
|
ComponentRemoved(Entity),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entity Record in MainWorld pending to Sync
|
||||||
|
#[derive(Resource, Default, Deref, DerefMut)]
|
||||||
|
pub(crate) struct PendingSyncEntity {
|
||||||
|
records: Vec<EntityRecord>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn entity_sync_system(main_world: &mut World, render_world: &mut World) {
|
||||||
|
main_world.resource_scope(|world, mut pending: Mut<PendingSyncEntity>| {
|
||||||
|
// TODO : batching record
|
||||||
|
for record in pending.drain(..) {
|
||||||
|
match record {
|
||||||
|
EntityRecord::Added(e) => {
|
||||||
|
if let Ok(mut main_entity) = world.get_entity_mut(e) {
|
||||||
|
match main_entity.entry::<RenderEntity>() {
|
||||||
|
bevy_ecs::world::ComponentEntry::Occupied(_) => {
|
||||||
|
panic!("Attempting to synchronize an entity that has already been synchronized!");
|
||||||
|
}
|
||||||
|
bevy_ecs::world::ComponentEntry::Vacant(entry) => {
|
||||||
|
let id = render_world.spawn(MainEntity(e)).id();
|
||||||
|
|
||||||
|
entry.insert(RenderEntity(id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EntityRecord::Removed(render_entity) => {
|
||||||
|
if let Ok(ec) = render_world.get_entity_mut(render_entity.id()) {
|
||||||
|
ec.despawn();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
EntityRecord::ComponentRemoved(main_entity) => {
|
||||||
|
let Some(mut render_entity) = world.get_mut::<RenderEntity>(main_entity) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Ok(render_world_entity) = render_world.get_entity_mut(render_entity.id()) {
|
||||||
|
// In order to handle components that extract to derived components, we clear the entity
|
||||||
|
// and let the extraction system re-add the components.
|
||||||
|
render_world_entity.despawn();
|
||||||
|
|
||||||
|
let id = render_world.spawn(MainEntity(main_entity)).id();
|
||||||
|
render_entity.0 = id;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn despawn_temporary_render_entities(
|
||||||
|
world: &mut World,
|
||||||
|
state: &mut SystemState<Query<Entity, With<TemporaryRenderEntity>>>,
|
||||||
|
mut local: Local<Vec<Entity>>,
|
||||||
|
) {
|
||||||
|
let query = state.get(world).unwrap();
|
||||||
|
|
||||||
|
local.extend(query.iter());
|
||||||
|
|
||||||
|
// Ensure next frame allocation keeps order
|
||||||
|
local.sort_unstable_by_key(|e| e.index());
|
||||||
|
for e in local.drain(..).rev() {
|
||||||
|
world.despawn(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This module exists to keep the complex unsafe code out of the main module.
|
||||||
|
///
|
||||||
|
/// The implementations for both [`MainEntity`] and [`RenderEntity`] should stay in sync,
|
||||||
|
/// and are based off of the `&T` implementation in `bevy_ecs`.
|
||||||
|
mod render_entities_world_query_impls {
|
||||||
|
use super::{MainEntity, RenderEntity};
|
||||||
|
|
||||||
|
use bevy_ecs::{
|
||||||
|
archetype::Archetype,
|
||||||
|
change_detection::Tick,
|
||||||
|
component::{ComponentId, Components},
|
||||||
|
entity::Entity,
|
||||||
|
query::{
|
||||||
|
ArchetypeQueryData, FilteredAccess, IterQueryData, QueryData, ReadOnlyQueryData,
|
||||||
|
ReleaseStateQueryData, SingleEntityQueryData, WorldQuery,
|
||||||
|
},
|
||||||
|
storage::{Table, TableRow},
|
||||||
|
world::{World, unsafe_world_cell::UnsafeWorldCell},
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY: defers completely to `&RenderEntity` implementation,
|
||||||
|
// and then only modifies the output safely.
|
||||||
|
unsafe impl WorldQuery for RenderEntity {
|
||||||
|
type Fetch<'w> = <&'static RenderEntity as WorldQuery>::Fetch<'w>;
|
||||||
|
type State = <&'static RenderEntity as WorldQuery>::State;
|
||||||
|
|
||||||
|
fn shrink_fetch<'wlong: 'wshort, 'wshort>(
|
||||||
|
fetch: Self::Fetch<'wlong>,
|
||||||
|
) -> Self::Fetch<'wshort> {
|
||||||
|
fetch
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn init_fetch<'w, 's>(
|
||||||
|
world: UnsafeWorldCell<'w>,
|
||||||
|
component_id: &'s ComponentId,
|
||||||
|
last_run: Tick,
|
||||||
|
this_run: Tick,
|
||||||
|
) -> Self::Fetch<'w> {
|
||||||
|
// SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`.
|
||||||
|
unsafe {
|
||||||
|
<&RenderEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const IS_DENSE: bool = <&'static RenderEntity as WorldQuery>::IS_DENSE;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn set_archetype<'w, 's>(
|
||||||
|
fetch: &mut Self::Fetch<'w>,
|
||||||
|
component_id: &'s ComponentId,
|
||||||
|
archetype: &'w Archetype,
|
||||||
|
table: &'w Table,
|
||||||
|
) {
|
||||||
|
// SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`.
|
||||||
|
unsafe {
|
||||||
|
<&RenderEntity as WorldQuery>::set_archetype(fetch, component_id, archetype, table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn set_table<'w, 's>(
|
||||||
|
fetch: &mut Self::Fetch<'w>,
|
||||||
|
&component_id: &'s ComponentId,
|
||||||
|
table: &'w Table,
|
||||||
|
) {
|
||||||
|
// SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`.
|
||||||
|
unsafe { <&RenderEntity as WorldQuery>::set_table(fetch, &component_id, table) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) {
|
||||||
|
<&RenderEntity as WorldQuery>::update_component_access(&component_id, access);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_state(world: &mut World) -> ComponentId {
|
||||||
|
<&RenderEntity as WorldQuery>::init_state(world)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_state(components: &Components) -> Option<Self::State> {
|
||||||
|
<&RenderEntity as WorldQuery>::get_state(components)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_component_set(
|
||||||
|
&state: &ComponentId,
|
||||||
|
set_contains_id: &impl Fn(ComponentId) -> bool,
|
||||||
|
) -> bool {
|
||||||
|
<&RenderEntity as WorldQuery>::matches_component_set(&state, set_contains_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: Component access of Self::ReadOnly is a subset of Self.
|
||||||
|
// Self::ReadOnly matches exactly the same archetypes/tables as Self.
|
||||||
|
unsafe impl QueryData for RenderEntity {
|
||||||
|
const IS_READ_ONLY: bool = true;
|
||||||
|
const IS_ARCHETYPAL: bool = <&MainEntity as QueryData>::IS_ARCHETYPAL;
|
||||||
|
type ReadOnly = RenderEntity;
|
||||||
|
type Item<'w, 's> = Entity;
|
||||||
|
|
||||||
|
fn shrink<'wlong: 'wshort, 'wshort, 's>(
|
||||||
|
item: Self::Item<'wlong, 's>,
|
||||||
|
) -> Self::Item<'wshort, 's> {
|
||||||
|
item
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
unsafe fn fetch<'w, 's>(
|
||||||
|
state: &'s Self::State,
|
||||||
|
fetch: &mut Self::Fetch<'w>,
|
||||||
|
entity: Entity,
|
||||||
|
table_row: TableRow,
|
||||||
|
) -> Option<Self::Item<'w, 's>> {
|
||||||
|
// SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`.
|
||||||
|
let component =
|
||||||
|
unsafe { <&RenderEntity as QueryData>::fetch(state, fetch, entity, table_row) };
|
||||||
|
component.map(RenderEntity::id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_access(
|
||||||
|
state: &Self::State,
|
||||||
|
) -> impl Iterator<Item = bevy_ecs::query::EcsAccessType<'_>> {
|
||||||
|
<&RenderEntity as QueryData>::iter_access(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: access is read only and only on the current entity
|
||||||
|
unsafe impl IterQueryData for RenderEntity {}
|
||||||
|
|
||||||
|
// SAFETY: the underlying `Entity` is copied, and no mutable access is provided.
|
||||||
|
unsafe impl ReadOnlyQueryData for RenderEntity {}
|
||||||
|
|
||||||
|
unsafe impl SingleEntityQueryData for RenderEntity {}
|
||||||
|
|
||||||
|
impl ArchetypeQueryData for RenderEntity {}
|
||||||
|
|
||||||
|
impl ReleaseStateQueryData for RenderEntity {
|
||||||
|
fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> {
|
||||||
|
item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: defers completely to `&RenderEntity` implementation,
|
||||||
|
// and then only modifies the output safely.
|
||||||
|
unsafe impl WorldQuery for MainEntity {
|
||||||
|
type Fetch<'w> = <&'static MainEntity as WorldQuery>::Fetch<'w>;
|
||||||
|
type State = <&'static MainEntity as WorldQuery>::State;
|
||||||
|
|
||||||
|
fn shrink_fetch<'wlong: 'wshort, 'wshort>(
|
||||||
|
fetch: Self::Fetch<'wlong>,
|
||||||
|
) -> Self::Fetch<'wshort> {
|
||||||
|
fetch
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn init_fetch<'w, 's>(
|
||||||
|
world: UnsafeWorldCell<'w>,
|
||||||
|
component_id: &'s ComponentId,
|
||||||
|
last_run: Tick,
|
||||||
|
this_run: Tick,
|
||||||
|
) -> Self::Fetch<'w> {
|
||||||
|
// SAFETY: defers to the `&T` implementation, with T set to `MainEntity`.
|
||||||
|
unsafe {
|
||||||
|
<&MainEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const IS_DENSE: bool = <&'static MainEntity as WorldQuery>::IS_DENSE;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn set_archetype<'w, 's>(
|
||||||
|
fetch: &mut Self::Fetch<'w>,
|
||||||
|
component_id: &ComponentId,
|
||||||
|
archetype: &'w Archetype,
|
||||||
|
table: &'w Table,
|
||||||
|
) {
|
||||||
|
// SAFETY: defers to the `&T` implementation, with T set to `MainEntity`.
|
||||||
|
unsafe {
|
||||||
|
<&MainEntity as WorldQuery>::set_archetype(fetch, component_id, archetype, table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn set_table<'w, 's>(
|
||||||
|
fetch: &mut Self::Fetch<'w>,
|
||||||
|
&component_id: &'s ComponentId,
|
||||||
|
table: &'w Table,
|
||||||
|
) {
|
||||||
|
// SAFETY: defers to the `&T` implementation, with T set to `MainEntity`.
|
||||||
|
unsafe { <&MainEntity as WorldQuery>::set_table(fetch, &component_id, table) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) {
|
||||||
|
<&MainEntity as WorldQuery>::update_component_access(&component_id, access);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_state(world: &mut World) -> ComponentId {
|
||||||
|
<&MainEntity as WorldQuery>::init_state(world)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_state(components: &Components) -> Option<Self::State> {
|
||||||
|
<&MainEntity as WorldQuery>::get_state(components)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_component_set(
|
||||||
|
&state: &ComponentId,
|
||||||
|
set_contains_id: &impl Fn(ComponentId) -> bool,
|
||||||
|
) -> bool {
|
||||||
|
<&MainEntity as WorldQuery>::matches_component_set(&state, set_contains_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: Component access of Self::ReadOnly is a subset of Self.
|
||||||
|
// Self::ReadOnly matches exactly the same archetypes/tables as Self.
|
||||||
|
unsafe impl QueryData for MainEntity {
|
||||||
|
const IS_READ_ONLY: bool = true;
|
||||||
|
const IS_ARCHETYPAL: bool = <&MainEntity as QueryData>::IS_ARCHETYPAL;
|
||||||
|
type ReadOnly = MainEntity;
|
||||||
|
type Item<'w, 's> = Entity;
|
||||||
|
|
||||||
|
fn shrink<'wlong: 'wshort, 'wshort, 's>(
|
||||||
|
item: Self::Item<'wlong, 's>,
|
||||||
|
) -> Self::Item<'wshort, 's> {
|
||||||
|
item
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
unsafe fn fetch<'w, 's>(
|
||||||
|
state: &'s Self::State,
|
||||||
|
fetch: &mut Self::Fetch<'w>,
|
||||||
|
entity: Entity,
|
||||||
|
table_row: TableRow,
|
||||||
|
) -> Option<Self::Item<'w, 's>> {
|
||||||
|
// SAFETY: defers to the `&T` implementation, with T set to `MainEntity`.
|
||||||
|
let component =
|
||||||
|
unsafe { <&MainEntity as QueryData>::fetch(state, fetch, entity, table_row) };
|
||||||
|
component.map(MainEntity::id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_access(
|
||||||
|
state: &Self::State,
|
||||||
|
) -> impl Iterator<Item = bevy_ecs::query::EcsAccessType<'_>> {
|
||||||
|
<&MainEntity as QueryData>::iter_access(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: access is read only and only on the current entity
|
||||||
|
unsafe impl IterQueryData for MainEntity {}
|
||||||
|
|
||||||
|
// SAFETY: the underlying `Entity` is copied, and no mutable access is provided.
|
||||||
|
unsafe impl ReadOnlyQueryData for MainEntity {}
|
||||||
|
|
||||||
|
unsafe impl SingleEntityQueryData for MainEntity {}
|
||||||
|
|
||||||
|
impl ArchetypeQueryData for MainEntity {}
|
||||||
|
|
||||||
|
impl ReleaseStateQueryData for MainEntity {
|
||||||
|
fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> {
|
||||||
|
item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use bevy_ecs::{
|
||||||
|
component::Component,
|
||||||
|
entity::Entity,
|
||||||
|
lifecycle::{Add, Remove},
|
||||||
|
observer::On,
|
||||||
|
query::With,
|
||||||
|
system::{Query, ResMut},
|
||||||
|
world::World,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
EntityRecord, MainEntity, PendingSyncEntity, RenderEntity, SyncToRenderWorld,
|
||||||
|
entity_sync_system,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct RenderDataComponent;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sync_world() {
|
||||||
|
let mut main_world = World::new();
|
||||||
|
let mut render_world = World::new();
|
||||||
|
main_world.init_resource::<PendingSyncEntity>();
|
||||||
|
|
||||||
|
main_world.add_observer(
|
||||||
|
|add: On<Add, SyncToRenderWorld>, mut pending: ResMut<PendingSyncEntity>| {
|
||||||
|
pending.push(EntityRecord::Added(add.entity));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
main_world.add_observer(
|
||||||
|
|remove: On<Remove, SyncToRenderWorld>,
|
||||||
|
mut pending: ResMut<PendingSyncEntity>,
|
||||||
|
query: Query<&RenderEntity>| {
|
||||||
|
if let Ok(e) = query.get(remove.entity) {
|
||||||
|
pending.push(EntityRecord::Removed(*e));
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// spawn some empty entities for test
|
||||||
|
for _ in 0..99 {
|
||||||
|
main_world.spawn_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// spawn
|
||||||
|
let main_entity = main_world
|
||||||
|
.spawn(RenderDataComponent)
|
||||||
|
// indicates that its entity needs to be synchronized to the render world
|
||||||
|
.insert(SyncToRenderWorld)
|
||||||
|
.id();
|
||||||
|
|
||||||
|
entity_sync_system(&mut main_world, &mut render_world);
|
||||||
|
|
||||||
|
let mut q = render_world.query_filtered::<Entity, With<MainEntity>>();
|
||||||
|
|
||||||
|
// Only one synchronized entity
|
||||||
|
assert!(q.iter(&render_world).count() == 1);
|
||||||
|
|
||||||
|
let render_entity = q.single(&render_world).unwrap();
|
||||||
|
let render_entity_component = main_world.get::<RenderEntity>(main_entity).unwrap();
|
||||||
|
|
||||||
|
assert!(render_entity_component.id() == render_entity);
|
||||||
|
|
||||||
|
let main_entity_component = render_world
|
||||||
|
.get::<MainEntity>(render_entity_component.id())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(main_entity_component.id() == main_entity);
|
||||||
|
|
||||||
|
// despawn
|
||||||
|
main_world.despawn(main_entity);
|
||||||
|
|
||||||
|
entity_sync_system(&mut main_world, &mut render_world);
|
||||||
|
|
||||||
|
// Only one synchronized entity
|
||||||
|
assert!(q.iter(&render_world).count() == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
164
crates/bevy_vulkan_render/src/window/mod.rs
Normal file
164
crates/bevy_vulkan_render/src/window/mod.rs
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
};
|
||||||
|
|
||||||
|
use bevy_ecs::{entity::EntityHashMap, prelude::*};
|
||||||
|
use bevy_window::{PrimaryWindow, RawHandleWrapper, Window, WindowClosing};
|
||||||
|
use renderer::{ash::vk, swapchain::WindowSurface};
|
||||||
|
|
||||||
|
use crate::{RenderDevice, extract_param::Extract};
|
||||||
|
|
||||||
|
pub struct ExtractedWindow {
|
||||||
|
pub entity: Entity,
|
||||||
|
pub handle: RawHandleWrapper,
|
||||||
|
pub physical_width: u32,
|
||||||
|
pub physical_height: u32,
|
||||||
|
pub size_changed: bool,
|
||||||
|
/// On wayland, windows will not show unless they are presented at least once.
|
||||||
|
pub needs_initial_present: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Resource)]
|
||||||
|
pub struct ExtractedWindows {
|
||||||
|
pub primary: Option<Entity>,
|
||||||
|
pub windows: EntityHashMap<ExtractedWindow>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for ExtractedWindows {
|
||||||
|
type Target = EntityHashMap<ExtractedWindow>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.windows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for ExtractedWindows {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.windows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Resource)]
|
||||||
|
pub struct Surfaces {
|
||||||
|
surfaces: EntityHashMap<SurfaceData>,
|
||||||
|
configured: HashSet<Entity>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Surfaces {
|
||||||
|
fn remove(&mut self, entity: &Entity) {
|
||||||
|
self.surfaces.remove(entity);
|
||||||
|
self.configured.remove(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (&Entity, &SurfaceData)> {
|
||||||
|
self.surfaces.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SurfaceData {
|
||||||
|
pub surface: WindowSurface,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for SurfaceData {
|
||||||
|
type Target = WindowSurface;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.surface
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub(crate) fn extract_windows(
|
||||||
|
mut extracted_windows: ResMut<ExtractedWindows>,
|
||||||
|
mut closing: Extract<MessageReader<WindowClosing>>,
|
||||||
|
windows: Extract<Query<(Entity, &RawHandleWrapper, &Window, Option<&PrimaryWindow>)>>,
|
||||||
|
mut removed: Extract<RemovedComponents<RawHandleWrapper>>,
|
||||||
|
mut surfaces: ResMut<Surfaces>,
|
||||||
|
) {
|
||||||
|
for (entity, handle, window, primary) in windows.iter() {
|
||||||
|
if primary.is_some() {
|
||||||
|
extracted_windows.primary = Some(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (new_width, new_height) = (
|
||||||
|
window.resolution.physical_width().max(1),
|
||||||
|
window.resolution.physical_height().max(1),
|
||||||
|
);
|
||||||
|
|
||||||
|
let extracted_window = extracted_windows.entry(entity).or_insert(ExtractedWindow {
|
||||||
|
entity,
|
||||||
|
handle: handle.clone(),
|
||||||
|
physical_width: new_width,
|
||||||
|
physical_height: new_height,
|
||||||
|
size_changed: false,
|
||||||
|
needs_initial_present: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if extracted_window.physical_width != new_width
|
||||||
|
|| extracted_window.physical_height != new_height
|
||||||
|
{
|
||||||
|
extracted_window.physical_width = new_width;
|
||||||
|
extracted_window.physical_height = new_height;
|
||||||
|
extracted_window.size_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for closing in closing.read() {
|
||||||
|
extracted_windows.remove(&closing.window);
|
||||||
|
surfaces.remove(&closing.window);
|
||||||
|
}
|
||||||
|
for removed in removed.read() {
|
||||||
|
extracted_windows.remove(&removed);
|
||||||
|
surfaces.remove(&removed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn need_surface_configuration(
|
||||||
|
windows: Res<ExtractedWindows>,
|
||||||
|
surfaces: Res<Surfaces>,
|
||||||
|
) -> bool {
|
||||||
|
windows
|
||||||
|
.windows
|
||||||
|
.values()
|
||||||
|
.any(|window| !surfaces.configured.contains(&window.entity) || window.size_changed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn configure_surfaces(
|
||||||
|
windows: Res<ExtractedWindows>,
|
||||||
|
mut surfaces: ResMut<Surfaces>,
|
||||||
|
device: Res<RenderDevice>,
|
||||||
|
) {
|
||||||
|
for window in windows.windows.values() {
|
||||||
|
let surface = surfaces.surfaces.entry(window.entity).or_insert_with(|| {
|
||||||
|
let (display_handle, window_handle) = (
|
||||||
|
window.handle.get_display_handle(),
|
||||||
|
window.handle.get_window_handle(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let surface = WindowSurface::new(
|
||||||
|
(**device).clone(),
|
||||||
|
vk::Extent2D {
|
||||||
|
width: window.physical_width,
|
||||||
|
height: window.physical_height,
|
||||||
|
},
|
||||||
|
window_handle,
|
||||||
|
display_handle,
|
||||||
|
)
|
||||||
|
.expect("Failed to create window surface");
|
||||||
|
|
||||||
|
SurfaceData { surface }
|
||||||
|
});
|
||||||
|
|
||||||
|
if window.size_changed {
|
||||||
|
surface
|
||||||
|
.surface
|
||||||
|
.recreate_with(Some(vk::Extent2D {
|
||||||
|
width: window.physical_width,
|
||||||
|
height: window.physical_height,
|
||||||
|
}))
|
||||||
|
.expect("Failed to recreate surface");
|
||||||
|
}
|
||||||
|
surfaces.configured.insert(window.entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "game"
|
name = "game"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
winit = { workspace = true }
|
winit = { workspace = true }
|
||||||
|
|
@ -15,4 +15,4 @@ renderer = { path = "../renderer" }
|
||||||
|
|
||||||
egui = { workspace = true }
|
egui = { workspace = true }
|
||||||
egui_winit_platform = { workspace = true }
|
egui_winit_platform = { workspace = true }
|
||||||
egui_demo_lib = "0.30.0"
|
egui_demo_lib = "0.32"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
#![feature(result_flattening)]
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
use renderer::{render_graph, rendering::Font, swapchain::WindowSurface, Renderer2};
|
use renderer::{
|
||||||
|
Renderer2, render_graph,
|
||||||
|
swapchain::{Surface, SwapchainConfiguration},
|
||||||
|
};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use winit::{
|
use winit::{
|
||||||
|
|
@ -19,7 +21,7 @@ struct WindowState {
|
||||||
egui_platform: egui_winit_platform::Platform,
|
egui_platform: egui_winit_platform::Platform,
|
||||||
demo_app: egui_demo_lib::DemoWindows,
|
demo_app: egui_demo_lib::DemoWindows,
|
||||||
scale_factor: f64,
|
scale_factor: f64,
|
||||||
surface: WindowSurface,
|
surface: Surface,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WinitState {
|
struct WinitState {
|
||||||
|
|
@ -54,13 +56,24 @@ impl WinitState {
|
||||||
fn handle_final_resize(&mut self, window_id: WindowId, new_size: PhysicalSize<u32>) {
|
fn handle_final_resize(&mut self, window_id: WindowId, new_size: PhysicalSize<u32>) {
|
||||||
_ = (window_id, new_size);
|
_ = (window_id, new_size);
|
||||||
info!("TODO: implement resize events");
|
info!("TODO: implement resize events");
|
||||||
if let Some(window) = self.windows.get(&window_id) {
|
if let Some(WindowState { surface, .. }) = self.windows.get(&window_id) {
|
||||||
window
|
let config = surface
|
||||||
.surface
|
.swapchain()
|
||||||
.recreate_with(Some(renderer::Extent2D {
|
.as_ref()
|
||||||
width: new_size.width,
|
.map(|swapchain| swapchain.config().clone())
|
||||||
height: new_size.height,
|
.unwrap_or(SwapchainConfiguration::default());
|
||||||
}))
|
|
||||||
|
surface
|
||||||
|
.configure(
|
||||||
|
self.renderer.device(),
|
||||||
|
SwapchainConfiguration {
|
||||||
|
extent: renderer::Extent2D {
|
||||||
|
width: new_size.width,
|
||||||
|
height: new_size.height,
|
||||||
|
},
|
||||||
|
..config
|
||||||
|
},
|
||||||
|
)
|
||||||
.expect("swapchain recreation");
|
.expect("swapchain recreation");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -92,10 +105,8 @@ impl WinitState {
|
||||||
let dev = renderer.device().clone();
|
let dev = renderer.device().clone();
|
||||||
|
|
||||||
use renderer::ash::vk::Handle;
|
use renderer::ash::vk::Handle;
|
||||||
use renderer::device::DeviceOwned;
|
|
||||||
let [r, g, b]: [f32; 3] =
|
let [r, g, b]: [f32; 3] =
|
||||||
rand::prelude::StdRng::seed_from_u64(window.surface.surface.handle().as_raw())
|
rand::prelude::StdRng::seed_from_u64(window.surface.raw().as_raw()).random();
|
||||||
.gen();
|
|
||||||
render_graph::clear_pass(rg, renderer::util::Rgba([r, g, b, 1.0]), framebuffer);
|
render_graph::clear_pass(rg, renderer::util::Rgba([r, g, b, 1.0]), framebuffer);
|
||||||
egui_pre_pass(
|
egui_pre_pass(
|
||||||
&dev,
|
&dev,
|
||||||
|
|
@ -291,7 +302,7 @@ impl ApplicationHandler for WinitState {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _ = tracing_subscriber::fmt()
|
_ = tracing_subscriber::fmt()
|
||||||
.with_env_filter(EnvFilter::from_default_env())
|
.with_env_filter(EnvFilter::from_default_env())
|
||||||
.init();
|
.init();
|
||||||
let ev = EventLoop::new().unwrap();
|
let ev = EventLoop::new().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
[package]
|
[package]
|
||||||
name = "renderer"
|
name = "renderer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tinyvec = { workspace = true }
|
tinyvec = { workspace = true }
|
||||||
|
arrayvec = { workspace = true }
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
petgraph = { workspace = true }
|
petgraph = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
|
ahash = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
parking_lot = { workspace = true }
|
parking_lot = { workspace = true }
|
||||||
glam = { workspace = true }
|
glam = { workspace = true }
|
||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
|
thread_local = {workspace = true}
|
||||||
|
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
|
@ -20,6 +23,10 @@ tracing = { workspace = true }
|
||||||
ash = { workspace = true }
|
ash = { workspace = true }
|
||||||
ash-window = { workspace = true }
|
ash-window = { workspace = true }
|
||||||
vk-mem = { workspace = true }
|
vk-mem = { workspace = true }
|
||||||
|
gpu-allocator = { workspace = true }
|
||||||
|
rectangle-pack = { workspace = true }
|
||||||
|
|
||||||
|
md-5 = { workspace = true }
|
||||||
|
|
||||||
raw-window-handle = { workspace = true }
|
raw-window-handle = { workspace = true }
|
||||||
egui = { workspace = true , features = ["bytemuck"]}
|
egui = { workspace = true , features = ["bytemuck"]}
|
||||||
|
|
@ -29,11 +36,19 @@ futures = { workspace = true }
|
||||||
smol = { workspace = true }
|
smol = { workspace = true }
|
||||||
# tokio = {workspace = true, features = ["rt", "sync"]}
|
# tokio = {workspace = true, features = ["rt", "sync"]}
|
||||||
|
|
||||||
|
bevy_tasks = { workspace = true }
|
||||||
|
|
||||||
|
derive_more = { workspace = true , features = ["debug"] }
|
||||||
|
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
dyn-clone = "1"
|
dyn-clone = "1"
|
||||||
crossbeam = "0.8.4"
|
crossbeam = "0.8.4"
|
||||||
bytemuck = { version = "1.21.0", features = ["derive"] }
|
bytemuck = { version = "1.21.0", features = ["derive"] }
|
||||||
|
|
||||||
cosmic-text = "0.12.1"
|
cosmic-text = "0.12.1"
|
||||||
|
sys-locale = "0.3.2"
|
||||||
|
guillotiere = "0.6.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tracing-test = "0.2.5"
|
tracing-test = "0.2.5"
|
||||||
|
image = "0.25.5"
|
||||||
|
|
|
||||||
BIN
crates/renderer/rendered.png
Normal file
BIN
crates/renderer/rendered.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
|
|
@ -1,8 +1,13 @@
|
||||||
#!/bin/bash
|
#! /usr/bin/env nix-shell
|
||||||
|
#! nix-shell -i bash -p shader-slang
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
SLANGC="/opt/shader-slang-bin/bin/slangc"
|
SLANGC="slangc"
|
||||||
|
|
||||||
$SLANGC egui.slang -profile glsl_450 -target spirv -o egui_vert.spv -entry vertex
|
$SLANGC egui.slang -profile glsl_450 -target spirv -o egui_vert.spv -entry vertex
|
||||||
$SLANGC egui.slang -profile glsl_450 -target spirv -o egui_frag.spv -entry fragment
|
$SLANGC egui.slang -profile glsl_450 -target spirv -o egui_frag.spv -entry fragment
|
||||||
|
$SLANGC egui.slang -profile glsl_450 -target spirv -entry vertex -entry fragment -o egui.spv
|
||||||
|
$SLANGC wireframe.slang -profile glsl_450 -target spirv -entry vertex -entry fragment -o wireframe.spv
|
||||||
|
$SLANGC font.slang -profile glsl_450 -target spirv -entry vertex -entry fragment -o font.spv
|
||||||
|
# $SLANGC font.slang -profile glsl_450 -target spirv -entry mesh -entry task -entry fragment_barycentric -o font_mesh.spv
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@ struct Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VertexIn {
|
struct VertexIn {
|
||||||
[[vk::layout(0)]] float2 pos;
|
[[vk::location(0)]] float2 pos;
|
||||||
[[vk::layout(1)]] float2 uv;
|
[[vk::location(1)]] float2 uv;
|
||||||
[[vk::layout(2)]] float4 color;
|
[[vk::location(2)]] float4 color;
|
||||||
}
|
}
|
||||||
struct VertexOut {
|
struct VertexOut {
|
||||||
[[vk::layout(0)]] float4 color;
|
[[vk::location(0)]] float4 color;
|
||||||
[[vk::layout(1)]] float2 uv;
|
[[vk::location(1)]] float2 uv;
|
||||||
[[vk::layout(2), flat]] uint draw_id;
|
nointerpolation [[vk::location(2)]] uint draw_id;
|
||||||
float4 position : SV_Position;
|
float4 position : SV_Position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
BIN
crates/renderer/shaders/egui.spv
Normal file
BIN
crates/renderer/shaders/egui.spv
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
134
crates/renderer/shaders/font.slang
Normal file
134
crates/renderer/shaders/font.slang
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
|
||||||
|
struct VertexIn {
|
||||||
|
[[vk::location(0)]] float2 pos;
|
||||||
|
}
|
||||||
|
struct VertexOut {
|
||||||
|
[[vk::location(0)]] float4 color;
|
||||||
|
float4 position : SV_Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PushConstant {
|
||||||
|
float2 screen_size;
|
||||||
|
float2 position;
|
||||||
|
float size;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[vk::push_constant]]
|
||||||
|
ConstantBuffer<PushConstant> push_constant;
|
||||||
|
|
||||||
|
[shader("vertex")]
|
||||||
|
VertexOut vertex(VertexIn vertex) {
|
||||||
|
VertexOut output;
|
||||||
|
|
||||||
|
output.position = float4(
|
||||||
|
2.0 * (push_constant.position.x + vertex.pos.x * push_constant.size) /
|
||||||
|
push_constant.screen_size.x - 1.0,
|
||||||
|
2.0 * (push_constant.position.y + vertex.pos.y * push_constant.size) /
|
||||||
|
push_constant.screen_size.y - 1.0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
output.color = float4(1.0, 1.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FragmentOut {
|
||||||
|
float4 color : SV_Target;
|
||||||
|
}
|
||||||
|
|
||||||
|
[shader("fragment")]
|
||||||
|
FragmentOut fragment(VertexOut input, float3 bary: SV_BARYCENTRICS) {
|
||||||
|
FragmentOut output;
|
||||||
|
|
||||||
|
output.color = input.color;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct GlyphDesc {
|
||||||
|
uint vertex_offset;
|
||||||
|
uint index_offset;
|
||||||
|
uint vertex_count;
|
||||||
|
uint index_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const uint SOLID = 0;
|
||||||
|
static const uint CONVEX = 0;
|
||||||
|
static const uint CONCAVE = 0;
|
||||||
|
|
||||||
|
struct MeshOut {
|
||||||
|
float4 pos : SV_Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MeshInvocation {
|
||||||
|
uint glyph_id;
|
||||||
|
uint vertex_triangle_offsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MeshIn {
|
||||||
|
MeshInvocation glyph_id[32];
|
||||||
|
}
|
||||||
|
struct PrimOut {
|
||||||
|
uint kind : BLENDINDICES0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[vk::binding(0)]]
|
||||||
|
StructuredBuffer<GlyphDesc> glyphs;
|
||||||
|
|
||||||
|
[[vk::binding(1)]]
|
||||||
|
StructuredBuffer<uint> glyphs_ids;
|
||||||
|
|
||||||
|
[shader("mesh")]
|
||||||
|
[numthreads(32, 1, 1)]
|
||||||
|
[outputtopology("triangle")]
|
||||||
|
void mesh(uint3 gid: SV_GroupID, // dispatched group id
|
||||||
|
uint3 tid: SV_GroupThreadID, // global thread id
|
||||||
|
uint ti: SV_GroupIndex, // local group thread index
|
||||||
|
out indices uint3 triangles[126],
|
||||||
|
out vertices MeshOut vertices[64],
|
||||||
|
out primitives PrimOut attr[126],
|
||||||
|
in MeshIn mesh,
|
||||||
|
) {
|
||||||
|
|
||||||
|
SetMeshOutputCounts(0, 0);
|
||||||
|
// we have a limited number of verts/triangles we can output
|
||||||
|
// if a glyph exceeds the 126 triangles/64 verts limit, then we need more than
|
||||||
|
// one thread building geometry.
|
||||||
|
// all threads sharing one glyph should be in the same subgroup/wave.
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FragmentIn2 {
|
||||||
|
float4 pos : SV_Position;
|
||||||
|
sample float3 bary : SV_BARYCENTRICS;
|
||||||
|
uint kind : BLENDINDICES0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float2 computeUV(const float3 bary)
|
||||||
|
{
|
||||||
|
const float u = bary.x * 0 + bary.y * 0.5f + bary.z * 1;
|
||||||
|
const float v = bary.x * 0 + bary.y * 0.0f + bary.z * 1;
|
||||||
|
return float2(u, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
float computeQuadraticBezierFunction(const float2 uv)
|
||||||
|
{
|
||||||
|
return uv.x * uv.x - uv.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
[shader("fragment")]
|
||||||
|
FragmentOut fragment_barycentric(FragmentIn2 input) {
|
||||||
|
const uint kind = input.kind;
|
||||||
|
const float2 uv = computeUV(input.bary);
|
||||||
|
const float sign = computeQuadraticBezierFunction(uv);
|
||||||
|
|
||||||
|
if ((kind == CONVEX) && sign > 0.0f || (kind == CONCAVE) && sign < 0.0f) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
FragmentOut output;
|
||||||
|
output.color = float4(1.0, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
BIN
crates/renderer/shaders/font.spv
Normal file
BIN
crates/renderer/shaders/font.spv
Normal file
Binary file not shown.
BIN
crates/renderer/shaders/font_mesh.spv
Normal file
BIN
crates/renderer/shaders/font_mesh.spv
Normal file
Binary file not shown.
37
crates/renderer/shaders/wireframe.slang
Normal file
37
crates/renderer/shaders/wireframe.slang
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
struct VertexIn {
|
||||||
|
[[vk::location(0)]] float2 pos;
|
||||||
|
[[vk::location(1)]] float4 color;
|
||||||
|
}
|
||||||
|
struct VertexOut {
|
||||||
|
[[vk::location(0)]] float4 color;
|
||||||
|
float4 position : SV_Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PushConstant {
|
||||||
|
float4x4 mvp;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[vk::push_constant]]
|
||||||
|
ConstantBuffer<PushConstant> push_constant;
|
||||||
|
|
||||||
|
[shader("vertex")]
|
||||||
|
VertexOut vertex(VertexIn vertex) {
|
||||||
|
VertexOut output;
|
||||||
|
|
||||||
|
output.position = mul(push_constant.mvp, float4(vertex.pos, 1.0));
|
||||||
|
output.color = vertex.color;
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FragmentOut {
|
||||||
|
float4 color : SV_Target;
|
||||||
|
}
|
||||||
|
|
||||||
|
[shader("fragment")]
|
||||||
|
FragmentOut fragment(VertexOut input) {
|
||||||
|
FragmentOut output;
|
||||||
|
|
||||||
|
output.color = input.color;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
BIN
crates/renderer/shaders/wireframe.spv
Normal file
BIN
crates/renderer/shaders/wireframe.spv
Normal file
Binary file not shown.
|
|
@ -1,17 +1,11 @@
|
||||||
use std::{
|
use std::borrow::Cow;
|
||||||
borrow::Cow,
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ash::{prelude::VkResult, vk};
|
use ash::vk;
|
||||||
use itertools::Itertools;
|
use gpu_allocator::vulkan::AllocationScheme;
|
||||||
use vk_mem::Alloc;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
define_device_owned_handle,
|
|
||||||
device::{DeviceOwned, QueueFlags},
|
|
||||||
Device,
|
Device,
|
||||||
|
device::{Allocation, AllocationStrategy, DeviceObject, QueueFlags},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
@ -22,8 +16,8 @@ pub struct BufferDesc {
|
||||||
pub usage: vk::BufferUsageFlags,
|
pub usage: vk::BufferUsageFlags,
|
||||||
pub queue_families: QueueFlags,
|
pub queue_families: QueueFlags,
|
||||||
|
|
||||||
pub mem_usage: vk_mem::MemoryUsage,
|
pub mem_location: gpu_allocator::MemoryLocation,
|
||||||
pub alloc_flags: vk_mem::AllocationCreateFlags,
|
pub alloc_scheme: AllocationStrategy,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::hash::Hash for BufferDesc {
|
impl std::hash::Hash for BufferDesc {
|
||||||
|
|
@ -32,8 +26,7 @@ impl std::hash::Hash for BufferDesc {
|
||||||
self.size.hash(state);
|
self.size.hash(state);
|
||||||
self.usage.hash(state);
|
self.usage.hash(state);
|
||||||
self.queue_families.hash(state);
|
self.queue_families.hash(state);
|
||||||
self.mem_usage.hash(state);
|
self.mem_location.hash(state);
|
||||||
self.alloc_flags.bits().hash(state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,17 +38,8 @@ impl std::fmt::Debug for BufferDesc {
|
||||||
.field("size", &self.size)
|
.field("size", &self.size)
|
||||||
.field("usage", &self.usage)
|
.field("usage", &self.usage)
|
||||||
.field("queue_families", &self.queue_families)
|
.field("queue_families", &self.queue_families)
|
||||||
.field("mem_usage", &self.mem_usage)
|
.field("mem_location", &self.mem_location)
|
||||||
.field_with("alloc_flags", |f| {
|
.field("alloc_scheme", &self.alloc_scheme)
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
self.alloc_flags
|
|
||||||
.iter_names()
|
|
||||||
.map(|(name, _)| name)
|
|
||||||
.format(" | ")
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,8 +53,7 @@ impl PartialEq for BufferDesc {
|
||||||
&& self.size == other.size
|
&& self.size == other.size
|
||||||
&& self.usage == other.usage
|
&& self.usage == other.usage
|
||||||
&& self.queue_families == other.queue_families
|
&& self.queue_families == other.queue_families
|
||||||
&& self.mem_usage == other.mem_usage
|
&& self.mem_location == other.mem_location
|
||||||
&& self.alloc_flags.bits() == other.alloc_flags.bits()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,109 +65,107 @@ impl Default for BufferDesc {
|
||||||
size: Default::default(),
|
size: Default::default(),
|
||||||
usage: Default::default(),
|
usage: Default::default(),
|
||||||
queue_families: QueueFlags::empty(),
|
queue_families: QueueFlags::empty(),
|
||||||
alloc_flags: vk_mem::AllocationCreateFlags::empty(),
|
mem_location: gpu_allocator::MemoryLocation::Unknown,
|
||||||
mem_usage: vk_mem::MemoryUsage::Auto,
|
alloc_scheme: AllocationStrategy::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
define_device_owned_handle! {
|
#[derive(Debug)]
|
||||||
#[derive(Debug)]
|
pub struct Buffer {
|
||||||
pub Buffer(vk::Buffer) {
|
buffer: DeviceObject<vk::Buffer>,
|
||||||
alloc: vk_mem::Allocation,
|
pub desc: BufferDesc,
|
||||||
size: u64,
|
alloc: Allocation,
|
||||||
} => |this| unsafe {
|
|
||||||
this.device().clone().alloc().destroy_buffer(this.handle(), &mut this.alloc);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for Buffer {}
|
impl Eq for Buffer {}
|
||||||
impl PartialEq for Buffer {
|
impl PartialEq for Buffer {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.inner == other.inner
|
*self.buffer == *other.buffer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Buffer {
|
impl Buffer {
|
||||||
pub fn new(device: Device, desc: BufferDesc) -> VkResult<Self> {
|
pub fn new(device: Device, desc: BufferDesc) -> crate::Result<Self> {
|
||||||
let queue_families = device.queue_families().family_indices(desc.queue_families);
|
let (buffer, requirements) = Self::new_raw(device.clone(), &desc)?;
|
||||||
|
|
||||||
|
let alloc =
|
||||||
|
device
|
||||||
|
.alloc2
|
||||||
|
.lock()
|
||||||
|
.allocate(&gpu_allocator::vulkan::AllocationCreateDesc {
|
||||||
|
name: desc.name.as_deref().unwrap_or_default(),
|
||||||
|
requirements,
|
||||||
|
location: desc.mem_location,
|
||||||
|
linear: true,
|
||||||
|
allocation_scheme: match desc.alloc_scheme {
|
||||||
|
AllocationStrategy::AllocatorManaged => {
|
||||||
|
AllocationScheme::GpuAllocatorManaged
|
||||||
|
}
|
||||||
|
AllocationStrategy::Dedicated => AllocationScheme::DedicatedBuffer(buffer),
|
||||||
|
},
|
||||||
|
})?;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
device
|
||||||
|
.raw
|
||||||
|
.bind_buffer_memory(buffer, alloc.memory(), alloc.offset())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
buffer: DeviceObject::new_debug_named(device.clone(), buffer, desc.name.clone()),
|
||||||
|
desc,
|
||||||
|
alloc: Allocation::Owned(DeviceObject::new(device, alloc)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_raw(
|
||||||
|
device: Device,
|
||||||
|
desc: &BufferDesc,
|
||||||
|
) -> crate::Result<(vk::Buffer, vk::MemoryRequirements)> {
|
||||||
|
let queue_families = device.queues.family_indices(desc.queue_families);
|
||||||
|
|
||||||
let sharing_mode = if queue_families.len() > 1 {
|
let sharing_mode = if queue_families.len() > 1 {
|
||||||
vk::SharingMode::CONCURRENT
|
vk::SharingMode::CONCURRENT
|
||||||
} else {
|
} else {
|
||||||
vk::SharingMode::EXCLUSIVE
|
vk::SharingMode::EXCLUSIVE
|
||||||
};
|
};
|
||||||
|
let create_info = vk::BufferCreateInfo::default()
|
||||||
|
.size(desc.size)
|
||||||
|
.usage(desc.usage)
|
||||||
|
.queue_family_indices(&queue_families)
|
||||||
|
.sharing_mode(sharing_mode);
|
||||||
|
|
||||||
let (buffer, allocation) = unsafe {
|
let buffer = unsafe { device.dev().create_buffer(&create_info, None)? };
|
||||||
device.alloc().create_buffer(
|
let mem_reqs = unsafe { device.dev().get_buffer_memory_requirements(buffer) };
|
||||||
&vk::BufferCreateInfo::default()
|
|
||||||
.size(desc.size)
|
|
||||||
.usage(desc.usage)
|
|
||||||
.queue_family_indices(&queue_families)
|
|
||||||
.sharing_mode(sharing_mode),
|
|
||||||
&vk_mem::AllocationCreateInfo {
|
|
||||||
flags: desc.alloc_flags,
|
|
||||||
usage: desc.mem_usage,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self::construct(
|
Ok((buffer, mem_reqs))
|
||||||
device, buffer, desc.name, allocation, desc.size,
|
|
||||||
)?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
pub fn map(&mut self) -> Option<&[u8]> {
|
||||||
pub fn map_arc(self: &mut Arc<Self>) -> VkResult<MappedBuffer<'_>> {
|
if let Some(alloc) = self.alloc.allocation() {
|
||||||
Arc::get_mut(self).map(Self::map).unwrap()
|
alloc
|
||||||
}
|
.mapped_slice()
|
||||||
|
.map(|slice| &slice[..self.desc.size as usize])
|
||||||
pub fn map(&mut self) -> VkResult<MappedBuffer<'_>> {
|
} else {
|
||||||
let bytes = unsafe {
|
None
|
||||||
let data = self.inner.dev().alloc().map_memory(&mut self.alloc)?;
|
|
||||||
let slice = core::slice::from_raw_parts_mut(data, self.size as usize);
|
|
||||||
|
|
||||||
slice
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(MappedBuffer { inner: self, bytes })
|
|
||||||
}
|
|
||||||
pub fn buffer(&self) -> vk::Buffer {
|
|
||||||
self.handle()
|
|
||||||
}
|
|
||||||
pub fn len(&self) -> u64 {
|
|
||||||
self.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MappedBuffer<'a> {
|
|
||||||
bytes: &'a mut [u8],
|
|
||||||
inner: &'a mut Buffer,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for MappedBuffer<'_> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
self.inner
|
|
||||||
.inner
|
|
||||||
.dev()
|
|
||||||
.alloc()
|
|
||||||
.unmap_memory(&mut self.inner.alloc);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for MappedBuffer<'_> {
|
pub fn map_mut(&mut self) -> Option<&mut [u8]> {
|
||||||
type Target = [u8];
|
if let Some(alloc) = self.alloc.allocation_mut() {
|
||||||
|
alloc
|
||||||
|
.mapped_slice_mut()
|
||||||
|
.map(|slice| &mut slice[..self.desc.size as usize])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
pub fn raw(&self) -> vk::Buffer {
|
||||||
self.bytes
|
*self.buffer
|
||||||
}
|
}
|
||||||
}
|
pub fn len(&self) -> u64 {
|
||||||
|
self.desc.size
|
||||||
impl DerefMut for MappedBuffer<'_> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
self.bytes
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +1,14 @@
|
||||||
use ash::vk;
|
use ash::vk;
|
||||||
use tracing::{event, Level};
|
use tracing::{Level, event};
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// `str` must be a valid null-terminated C string or null.
|
||||||
unsafe fn str_from_raw_parts<'a>(str: *const i8) -> std::borrow::Cow<'a, str> {
|
unsafe fn str_from_raw_parts<'a>(str: *const i8) -> std::borrow::Cow<'a, str> {
|
||||||
use std::{borrow::Cow, ffi};
|
use std::{borrow::Cow, ffi};
|
||||||
if str.is_null() {
|
if str.is_null() {
|
||||||
Cow::from("")
|
Cow::from("")
|
||||||
} else {
|
} else {
|
||||||
ffi::CStr::from_ptr(str).to_string_lossy()
|
unsafe { ffi::CStr::from_ptr(str).to_string_lossy() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -17,11 +19,19 @@ pub(super) unsafe extern "system" fn debug_callback(
|
||||||
user_data: *mut core::ffi::c_void,
|
user_data: *mut core::ffi::c_void,
|
||||||
) -> vk::Bool32 {
|
) -> vk::Bool32 {
|
||||||
_ = user_data;
|
_ = user_data;
|
||||||
let callback_data = *callback_data;
|
// SAFETY:
|
||||||
let message_id_number = callback_data.message_id_number;
|
// - `callback_data` is a valid pointer to `DebugUtilsMessengerCallbackDataEXT`.
|
||||||
|
// - `p_message_id_name` is a valid null-terminated C string or NULL.
|
||||||
|
// - `p_message` is a valid null-terminated C string or NULL.
|
||||||
|
let (message_id_number, message_id_name, message) = unsafe {
|
||||||
|
let callback_data = &*callback_data;
|
||||||
|
|
||||||
let message_id_name = str_from_raw_parts(callback_data.p_message_id_name);
|
let message_id_number = callback_data.message_id_number;
|
||||||
let message = str_from_raw_parts(callback_data.p_message);
|
|
||||||
|
let message_id_name = str_from_raw_parts(callback_data.p_message_id_name);
|
||||||
|
let message = str_from_raw_parts(callback_data.p_message);
|
||||||
|
(message_id_number, message_id_name, message)
|
||||||
|
};
|
||||||
|
|
||||||
match message_severity {
|
match message_severity {
|
||||||
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => {
|
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,19 +1,23 @@
|
||||||
use std::{collections::BTreeMap, sync::Arc};
|
use std::{
|
||||||
|
collections::{BTreeMap, HashMap},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use ash::{prelude::VkResult, vk};
|
use ash::vk;
|
||||||
|
use gpu_allocator::MemoryLocation;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
EguiState,
|
||||||
buffers::{Buffer, BufferDesc},
|
buffers::{Buffer, BufferDesc},
|
||||||
device::{self, DeviceOwned},
|
commands::traits::CommandBufferExt,
|
||||||
|
device::{self, AllocationStrategy},
|
||||||
images::{Image, ImageDesc, ImageViewDesc},
|
images::{Image, ImageDesc, ImageViewDesc},
|
||||||
render_graph::{
|
render_graph::{
|
||||||
buffer_barrier, image_barrier, Access, Barrier, GraphResourceDesc, GraphResourceId,
|
Access, Barrier, GraphResourceDesc, GraphResourceId, PassDesc, RecordFn, RenderContext,
|
||||||
PassDesc, RecordFn, RenderContext, RenderGraph,
|
RenderGraph, buffer_barrier, image_barrier,
|
||||||
},
|
},
|
||||||
texture,
|
texture::{self, TextureId},
|
||||||
util::Rect2D,
|
|
||||||
EguiState,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn egui_pre_pass(
|
pub fn egui_pre_pass(
|
||||||
|
|
@ -22,7 +26,7 @@ pub fn egui_pre_pass(
|
||||||
textures: &mut crate::texture::TextureManager,
|
textures: &mut crate::texture::TextureManager,
|
||||||
egui_state: &mut EguiState,
|
egui_state: &mut EguiState,
|
||||||
output: &egui::FullOutput,
|
output: &egui::FullOutput,
|
||||||
) -> VkResult<()> {
|
) -> crate::Result<()> {
|
||||||
// allocate resource ids for textures in tessellated list (imported from texture manager)
|
// allocate resource ids for textures in tessellated list (imported from texture manager)
|
||||||
// define accesses for resource ids
|
// define accesses for resource ids
|
||||||
|
|
||||||
|
|
@ -30,206 +34,300 @@ pub fn egui_pre_pass(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use rectangle_pack::{
|
||||||
|
GroupedRectsToPlace, RectToInsert, TargetBin, contains_smallest_box, pack_rects,
|
||||||
|
volume_heuristic,
|
||||||
|
};
|
||||||
|
let mut rects_to_place = GroupedRectsToPlace::<TextureId, ()>::new();
|
||||||
|
|
||||||
|
let mut max_width = 0;
|
||||||
|
let mut max_height = 0;
|
||||||
|
|
||||||
// create textures for new egui textures
|
// create textures for new egui textures
|
||||||
for (egui_id, delta) in output
|
// these are real images that will be sampled from in the shader.
|
||||||
|
let updates = output
|
||||||
.textures_delta
|
.textures_delta
|
||||||
.set
|
.set
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, image)| image.is_whole())
|
.map(|(egui_id, delta)| {
|
||||||
{
|
max_width = max_width.max(delta.image.width() as u32);
|
||||||
tracing::trace!("creating texture image for egui image {egui_id:?}");
|
max_height = max_height.max(delta.image.height() as u32);
|
||||||
let image = Image::new(
|
|
||||||
dev.clone(),
|
|
||||||
ImageDesc {
|
|
||||||
name: Some(format!("egui-texture-{egui_id:?}").into()),
|
|
||||||
format: vk::Format::R8G8B8A8_UNORM,
|
|
||||||
extent: vk::Extent3D {
|
|
||||||
width: delta.image.width() as u32,
|
|
||||||
height: delta.image.height() as u32,
|
|
||||||
depth: 1,
|
|
||||||
},
|
|
||||||
usage: vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST,
|
|
||||||
mem_usage: vk_mem::MemoryUsage::AutoPreferDevice,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let tid = textures.insert_image(Arc::new(image));
|
use std::collections::hash_map::Entry;
|
||||||
if let Some(old) = egui_state.textures.insert(
|
let id = match egui_state.textures.entry(*egui_id) {
|
||||||
*egui_id,
|
Entry::Occupied(entry) => entry.get().id,
|
||||||
crate::EguiTextureInfo {
|
Entry::Vacant(entry) => {
|
||||||
id: tid,
|
tracing::trace!("creating texture image for egui image {egui_id:?}");
|
||||||
options: delta.options,
|
|
||||||
},
|
let Ok(image) = Image::new(
|
||||||
|
dev.clone(),
|
||||||
|
ImageDesc {
|
||||||
|
name: Some(format!("egui-texture-{egui_id:?}").into()),
|
||||||
|
format: vk::Format::R8G8B8A8_UNORM,
|
||||||
|
extent: vk::Extent3D {
|
||||||
|
width: delta.image.width() as u32,
|
||||||
|
height: delta.image.height() as u32,
|
||||||
|
depth: 1,
|
||||||
|
},
|
||||||
|
usage: vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST,
|
||||||
|
mem_location: MemoryLocation::GpuOnly,
|
||||||
|
alloc_scheme: AllocationStrategy::Dedicated,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
) else {
|
||||||
|
return Err(crate::Error::Todo(
|
||||||
|
"handle image creation failure in egui pre-pass",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let id = textures.insert_image(Arc::new(image));
|
||||||
|
entry.insert(crate::EguiTextureInfo {
|
||||||
|
id,
|
||||||
|
options: delta.options,
|
||||||
|
});
|
||||||
|
id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
rects_to_place.push_rect(
|
||||||
|
id,
|
||||||
|
None,
|
||||||
|
RectToInsert::new(delta.image.width() as u32, delta.image.height() as u32, 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok((id, (delta.pos, &delta.image)))
|
||||||
|
})
|
||||||
|
.collect::<crate::Result<HashMap<_, _>>>()?;
|
||||||
|
|
||||||
|
let rect_placements = loop {
|
||||||
|
let mut target_bins = BTreeMap::new();
|
||||||
|
target_bins.insert(0, TargetBin::new(max_width, max_height, 1));
|
||||||
|
|
||||||
|
match pack_rects(
|
||||||
|
&rects_to_place,
|
||||||
|
&mut target_bins,
|
||||||
|
&volume_heuristic,
|
||||||
|
&contains_smallest_box,
|
||||||
) {
|
) {
|
||||||
textures.remove_texture(old.id);
|
Ok(placements) => break placements,
|
||||||
|
Err(_) => {
|
||||||
|
max_width *= 2;
|
||||||
|
max_height *= 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// calculate size for staging buffer.
|
let extent = rect_placements
|
||||||
// calculate size for staging image.
|
.packed_locations()
|
||||||
let (staging_size, image_size) = output.textures_delta.set.iter().fold(
|
.iter()
|
||||||
(0usize, glam::UVec2::ZERO),
|
.fold((0u32, 0u32), |(w, h), (_id, (_bin, loc))| {
|
||||||
|(mut buffer, mut image), (_id, delta)| {
|
(w.max(loc.x() + loc.width()), h.max(loc.y() + loc.height()))
|
||||||
let bytes = delta.image.height() * delta.image.width() * delta.image.bytes_per_pixel();
|
});
|
||||||
image = image.max(glam::uvec2(
|
|
||||||
delta.image.width() as u32,
|
|
||||||
delta.image.height() as u32,
|
|
||||||
));
|
|
||||||
buffer = buffer + bytes;
|
|
||||||
|
|
||||||
(buffer, image)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
staging_size,
|
"creating staging buffer {}x{} for uploading egui textures",
|
||||||
"creating staging buffer for uploading egui textures"
|
extent.0,
|
||||||
|
extent.1,
|
||||||
);
|
);
|
||||||
let mut staging_buffer = Buffer::new(
|
let mut staging_buffer = Buffer::new(
|
||||||
dev.clone(),
|
dev.clone(),
|
||||||
BufferDesc {
|
BufferDesc {
|
||||||
name: Some("egui-prepass-staging-buffer".into()),
|
name: Some("egui-prepass-staging-buffer".into()),
|
||||||
size: staging_size as u64,
|
size: (extent.0 * extent.1 * 4) as u64, // RGBA8
|
||||||
usage: vk::BufferUsageFlags::TRANSFER_SRC,
|
usage: vk::BufferUsageFlags::TRANSFER_SRC,
|
||||||
queue_families: device::QueueFlags::empty(),
|
queue_families: device::QueueFlags::empty(),
|
||||||
mem_usage: vk_mem::MemoryUsage::AutoPreferHost,
|
mem_location: MemoryLocation::CpuToGpu,
|
||||||
alloc_flags: vk_mem::AllocationCreateFlags::MAPPED
|
|
||||||
| vk_mem::AllocationCreateFlags::HOST_ACCESS_SEQUENTIAL_WRITE
|
|
||||||
| vk_mem::AllocationCreateFlags::STRATEGY_FIRST_FIT,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
tracing::trace!("creating staging image for uploading egui textures with dims={image_size:?}");
|
let staging_image = Image::new(
|
||||||
let staging_image = Arc::new(Image::new(
|
|
||||||
dev.clone(),
|
dev.clone(),
|
||||||
ImageDesc {
|
ImageDesc {
|
||||||
name: Some("egui-prepass-staging-buffer".into()),
|
name: Some("egui-prepass-staging-buffer".into()),
|
||||||
format: vk::Format::R8G8B8A8_UNORM,
|
format: vk::Format::R8G8B8A8_UNORM,
|
||||||
extent: vk::Extent3D {
|
extent: vk::Extent3D {
|
||||||
width: image_size.x,
|
width: extent.0,
|
||||||
height: image_size.y,
|
height: extent.1,
|
||||||
depth: 1,
|
depth: 1,
|
||||||
},
|
},
|
||||||
usage: vk::ImageUsageFlags::TRANSFER_SRC | vk::ImageUsageFlags::TRANSFER_DST,
|
usage: vk::ImageUsageFlags::TRANSFER_SRC | vk::ImageUsageFlags::TRANSFER_DST,
|
||||||
queue_families: device::QueueFlags::empty(),
|
queue_families: device::QueueFlags::empty(),
|
||||||
mem_usage: vk_mem::MemoryUsage::AutoPreferDevice,
|
mem_location: MemoryLocation::GpuOnly,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)?);
|
)?;
|
||||||
|
|
||||||
let aliased_images = {
|
let staging_map = staging_buffer.map_mut().unwrap();
|
||||||
tracing::trace!("mmap-ing staging buffer");
|
|
||||||
let mut staging_map = staging_buffer.map()?;
|
|
||||||
let mut offset = 0;
|
|
||||||
|
|
||||||
let aliased_images = output
|
struct ImageUpdate {
|
||||||
.textures_delta
|
#[allow(dead_code)]
|
||||||
.set
|
id: TextureId,
|
||||||
.iter()
|
graph_id: GraphResourceId,
|
||||||
.map(|(id, delta)| {
|
dest_offset: (u32, u32),
|
||||||
let bytes =
|
src_offset: (u32, u32),
|
||||||
delta.image.height() * delta.image.width() * delta.image.bytes_per_pixel();
|
size: (u32, u32),
|
||||||
|
}
|
||||||
|
|
||||||
let mem = &mut staging_map[offset..offset + bytes];
|
let updates = updates
|
||||||
match &delta.image {
|
.into_iter()
|
||||||
egui::ImageData::Color(arc) => {
|
.filter_map(|(id, (pos, image))| {
|
||||||
let slice = unsafe {
|
let (_, loc) = rect_placements.packed_locations().get(&id)?;
|
||||||
core::slice::from_raw_parts(
|
|
||||||
arc.pixels.as_ptr().cast::<u8>(),
|
|
||||||
arc.pixels.len() * size_of::<egui::Color32>(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
mem[..slice.len()].copy_from_slice(slice);
|
|
||||||
}
|
|
||||||
egui::ImageData::Font(font_image) => {
|
|
||||||
for (i, c) in font_image.srgba_pixels(None).enumerate() {
|
|
||||||
let bytes = c.to_array();
|
|
||||||
mem[i * 4..(i + 1) * 4].copy_from_slice(&bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let old_offset = offset;
|
// copy scanlines from image data into staging buffer
|
||||||
offset += bytes;
|
let rect_width = loc.width() as usize;
|
||||||
|
let rect_height = loc.height() as usize;
|
||||||
|
let rect_x = loc.x() as usize;
|
||||||
|
let rect_y = loc.y() as usize;
|
||||||
|
|
||||||
let pos = delta.pos.unwrap_or_default();
|
let pixel_size = 4usize; // RGBA8
|
||||||
let rect = Rect2D::new_from_size(
|
let atlas_width = extent.0 as usize;
|
||||||
glam::ivec2(pos[0] as i32, pos[1] as i32),
|
|
||||||
glam::ivec2(delta.image.width() as i32, delta.image.height() as i32),
|
|
||||||
);
|
|
||||||
(*id, (old_offset, bytes, rect))
|
|
||||||
})
|
|
||||||
.collect::<BTreeMap<_, _>>();
|
|
||||||
|
|
||||||
// let tessellated = egui.tessellate(output.shapes, output.pixels_per_point);
|
let image_data = match image {
|
||||||
|
egui::ImageData::Color(color_image) => color_image.as_raw(),
|
||||||
|
};
|
||||||
|
|
||||||
aliased_images
|
for (atlas_y, image_y) in (rect_y..rect_y + rect_height).enumerate() {
|
||||||
};
|
let atlas_start = (image_y * atlas_width + rect_x) * pixel_size;
|
||||||
|
let atlas_end = atlas_start + rect_width * pixel_size;
|
||||||
|
|
||||||
let textures = output
|
let image_start = (atlas_y * rect_width) * pixel_size;
|
||||||
.textures_delta
|
let image_end = image_start + rect_width * pixel_size;
|
||||||
.set
|
staging_map[atlas_start..atlas_end]
|
||||||
.iter()
|
.copy_from_slice(&image_data[image_start..image_end]);
|
||||||
.filter_map(|(egui_id, _)| {
|
}
|
||||||
egui_state
|
|
||||||
.lookup_texture(*egui_id)
|
let graph_id = rg.import_image(
|
||||||
.and_then(|tid| textures.get_texture(tid))
|
textures.get_texture(id)?,
|
||||||
.map(|img| (*egui_id, img))
|
Access {
|
||||||
})
|
layout: vk::ImageLayout::GENERAL,
|
||||||
.map(|(id, img)| {
|
..Access::undefined()
|
||||||
(
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(ImageUpdate {
|
||||||
id,
|
id,
|
||||||
rg.import_image(
|
graph_id,
|
||||||
img,
|
dest_offset: pos.map(|p| (p[0] as u32, p[1] as u32)).unwrap_or_default(),
|
||||||
Access {
|
src_offset: (rect_x as u32, rect_y as u32),
|
||||||
layout: vk::ImageLayout::GENERAL,
|
size: (rect_width as u32, rect_height as u32),
|
||||||
..Access::undefined()
|
})
|
||||||
},
|
})
|
||||||
),
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let writes = updates
|
||||||
|
.iter()
|
||||||
|
.map(|update| {
|
||||||
|
(
|
||||||
|
update.graph_id,
|
||||||
|
Access {
|
||||||
|
layout: vk::ImageLayout::GENERAL,
|
||||||
|
..Access::undefined()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<BTreeMap<_, _>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let staging_buffer = rg.import_buffer(Arc::new(staging_buffer), Access::undefined());
|
let staging_buffer = rg.import_buffer(Arc::new(staging_buffer), Access::undefined());
|
||||||
let staging_image = rg.import_image(staging_image, Access::undefined());
|
let staging_image = rg.import_image(Arc::new(staging_image), Access::undefined());
|
||||||
|
|
||||||
let record = Box::new({
|
let record = Box::new({
|
||||||
let textures = textures.clone();
|
// let textures = textures.clone();
|
||||||
move |ctx: &RenderContext| -> crate::Result<()> {
|
move |ctx: &RenderContext| -> crate::Result<()> {
|
||||||
let staging_image = ctx.get_image(staging_image).unwrap().clone();
|
let staging_image = ctx.get_image(staging_image).unwrap().clone();
|
||||||
let staging_buffer = ctx.get_buffer(staging_buffer).unwrap();
|
let staging_buffer = ctx.get_buffer(staging_buffer).unwrap();
|
||||||
|
|
||||||
for (id, (offset, _, rect)) in aliased_images {
|
let image: Barrier = image_barrier(
|
||||||
tracing::trace!(
|
staging_image.raw(),
|
||||||
"record-prepass: fetching alias of prepass staging image id={id:?}"
|
staging_image.format(),
|
||||||
);
|
Access {
|
||||||
let alias = unsafe {
|
stage: vk::PipelineStageFlags2::NONE,
|
||||||
staging_image.get_alias(ImageDesc {
|
mask: vk::AccessFlags2::empty(),
|
||||||
name: Some(format!("egui-prepass-staging-aliased-{id:?}v").into()),
|
layout: vk::ImageLayout::UNDEFINED,
|
||||||
format: vk::Format::R8G8B8A8_UNORM,
|
},
|
||||||
extent: vk::Extent3D {
|
Access {
|
||||||
width: rect.width() as u32,
|
stage: vk::PipelineStageFlags2::TRANSFER,
|
||||||
height: rect.height() as u32,
|
mask: vk::AccessFlags2::TRANSFER_WRITE,
|
||||||
depth: 1,
|
layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||||
},
|
},
|
||||||
usage: vk::ImageUsageFlags::TRANSFER_SRC
|
None,
|
||||||
| vk::ImageUsageFlags::TRANSFER_DST,
|
)
|
||||||
queue_families: device::QueueFlags::empty(),
|
.into();
|
||||||
..Default::default()
|
|
||||||
})?
|
|
||||||
};
|
|
||||||
|
|
||||||
let texture = textures.get(&id).and_then(|id| ctx.get_image(*id)).unwrap();
|
unsafe {
|
||||||
|
ctx.device
|
||||||
|
.dev()
|
||||||
|
.cmd_pipeline_barrier2(ctx.cmd.buffer(), &((&image).into()));
|
||||||
|
}
|
||||||
|
|
||||||
let image: Barrier = image_barrier(
|
ctx.cmd.copy_buffer_to_image(
|
||||||
alias.handle(),
|
staging_buffer.raw(),
|
||||||
alias.format(),
|
staging_image.raw(),
|
||||||
|
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||||
|
&[vk::BufferImageCopy {
|
||||||
|
buffer_offset: 0,
|
||||||
|
buffer_row_length: staging_image.width(),
|
||||||
|
buffer_image_height: staging_image.height(),
|
||||||
|
image_subresource: vk::ImageSubresourceLayers::default()
|
||||||
|
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
||||||
|
.base_array_layer(0)
|
||||||
|
.mip_level(0)
|
||||||
|
.layer_count(1),
|
||||||
|
image_offset: vk::Offset3D { x: 0, y: 0, z: 0 },
|
||||||
|
image_extent: staging_image.size(),
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
|
||||||
|
let from_barrier: Barrier = image_barrier(
|
||||||
|
staging_image.raw(),
|
||||||
|
staging_image.format(),
|
||||||
|
Access {
|
||||||
|
stage: vk::PipelineStageFlags2::TRANSFER,
|
||||||
|
mask: vk::AccessFlags2::TRANSFER_WRITE,
|
||||||
|
layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||||
|
},
|
||||||
|
Access {
|
||||||
|
stage: vk::PipelineStageFlags2::TRANSFER,
|
||||||
|
mask: vk::AccessFlags2::TRANSFER_READ,
|
||||||
|
layout: vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
ctx.device
|
||||||
|
.dev()
|
||||||
|
.cmd_pipeline_barrier2(ctx.cmd.buffer(), &((&from_barrier).into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for ImageUpdate {
|
||||||
|
graph_id,
|
||||||
|
dest_offset,
|
||||||
|
src_offset,
|
||||||
|
size,
|
||||||
|
..
|
||||||
|
} in &updates
|
||||||
|
{
|
||||||
|
let texture = ctx.get_image(*graph_id).unwrap();
|
||||||
|
|
||||||
|
let to_barrier: Barrier = image_barrier(
|
||||||
|
texture.raw(),
|
||||||
|
texture.format(),
|
||||||
Access {
|
Access {
|
||||||
stage: vk::PipelineStageFlags2::NONE,
|
stage: vk::PipelineStageFlags2::NONE,
|
||||||
mask: vk::AccessFlags2::empty(),
|
mask: vk::AccessFlags2::empty(),
|
||||||
layout: vk::ImageLayout::UNDEFINED,
|
// TODO: this is somewhat sub-optimal, but I think
|
||||||
|
// perfectly legal to not-care about the layout of a
|
||||||
|
// texture? In this case, the entire texture is
|
||||||
|
// overwritten, so the layout doesn't matter, and else
|
||||||
|
// the layout will be `GENERAL` because the texture was
|
||||||
|
// previously written to.
|
||||||
|
layout: if staging_image.size() == texture.size() {
|
||||||
|
vk::ImageLayout::UNDEFINED
|
||||||
|
} else {
|
||||||
|
vk::ImageLayout::GENERAL
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Access {
|
Access {
|
||||||
stage: vk::PipelineStageFlags2::TRANSFER,
|
stage: vk::PipelineStageFlags2::TRANSFER,
|
||||||
|
|
@ -243,80 +341,13 @@ pub fn egui_pre_pass(
|
||||||
unsafe {
|
unsafe {
|
||||||
ctx.device
|
ctx.device
|
||||||
.dev()
|
.dev()
|
||||||
.cmd_pipeline_barrier2(ctx.cmd.buffer(), &((&image).into()));
|
.cmd_pipeline_barrier2(ctx.cmd.buffer(), &((&to_barrier).into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.cmd.copy_buffer_to_image(
|
|
||||||
staging_buffer.handle(),
|
|
||||||
alias.handle(),
|
|
||||||
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
||||||
&[vk::BufferImageCopy {
|
|
||||||
buffer_offset: offset as u64,
|
|
||||||
buffer_row_length: alias.width(),
|
|
||||||
buffer_image_height: alias.height(),
|
|
||||||
image_subresource: vk::ImageSubresourceLayers::default()
|
|
||||||
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
|
||||||
.base_array_layer(0)
|
|
||||||
.mip_level(0)
|
|
||||||
.layer_count(1),
|
|
||||||
image_offset: vk::Offset3D { x: 0, y: 0, z: 0 },
|
|
||||||
image_extent: alias.size(),
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
|
|
||||||
let from_barrier = image_barrier(
|
|
||||||
alias.handle(),
|
|
||||||
alias.format(),
|
|
||||||
Access {
|
|
||||||
stage: vk::PipelineStageFlags2::TRANSFER,
|
|
||||||
mask: vk::AccessFlags2::TRANSFER_WRITE,
|
|
||||||
layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
||||||
},
|
|
||||||
Access {
|
|
||||||
stage: vk::PipelineStageFlags2::TRANSFER,
|
|
||||||
mask: vk::AccessFlags2::TRANSFER_READ,
|
|
||||||
layout: vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
let to_barrier = image_barrier(
|
|
||||||
texture.handle(),
|
|
||||||
texture.format(),
|
|
||||||
Access {
|
|
||||||
stage: vk::PipelineStageFlags2::NONE,
|
|
||||||
mask: vk::AccessFlags2::empty(),
|
|
||||||
// TODO: this is somewhat sub-optimal, but I think
|
|
||||||
// perfectly legal to not-care about the layout of a
|
|
||||||
// texture? In this case, the entire texture is
|
|
||||||
// overwritten, so the layout doesn't matter, and else
|
|
||||||
// the layout will be `GENERAL` because the texture was
|
|
||||||
// previously written to.
|
|
||||||
layout: if alias.size() == texture.size() {
|
|
||||||
vk::ImageLayout::UNDEFINED
|
|
||||||
} else {
|
|
||||||
vk::ImageLayout::GENERAL
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Access {
|
|
||||||
stage: vk::PipelineStageFlags2::TRANSFER,
|
|
||||||
mask: vk::AccessFlags2::TRANSFER_WRITE,
|
|
||||||
layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
ctx.device.dev().cmd_pipeline_barrier2(
|
|
||||||
ctx.cmd.buffer(),
|
|
||||||
&vk::DependencyInfo::default()
|
|
||||||
.image_memory_barriers(&[from_barrier, to_barrier]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ctx.cmd.copy_images(
|
ctx.cmd.copy_images(
|
||||||
alias.handle(),
|
staging_image.raw(),
|
||||||
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
|
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||||
texture.handle(),
|
texture.raw(),
|
||||||
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||||
&[vk::ImageCopy {
|
&[vk::ImageCopy {
|
||||||
src_subresource: vk::ImageSubresourceLayers::default()
|
src_subresource: vk::ImageSubresourceLayers::default()
|
||||||
|
|
@ -324,23 +355,31 @@ pub fn egui_pre_pass(
|
||||||
.base_array_layer(0)
|
.base_array_layer(0)
|
||||||
.mip_level(0)
|
.mip_level(0)
|
||||||
.layer_count(1),
|
.layer_count(1),
|
||||||
src_offset: vk::Offset3D { x: 0, y: 0, z: 0 },
|
src_offset: vk::Offset3D {
|
||||||
|
x: src_offset.0 as i32,
|
||||||
|
y: src_offset.1 as i32,
|
||||||
|
z: 0,
|
||||||
|
},
|
||||||
dst_subresource: vk::ImageSubresourceLayers::default()
|
dst_subresource: vk::ImageSubresourceLayers::default()
|
||||||
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
||||||
.base_array_layer(0)
|
.base_array_layer(0)
|
||||||
.mip_level(0)
|
.mip_level(0)
|
||||||
.layer_count(1),
|
.layer_count(1),
|
||||||
dst_offset: vk::Offset3D {
|
dst_offset: vk::Offset3D {
|
||||||
x: rect.top_left().x,
|
x: dest_offset.0 as i32,
|
||||||
y: rect.top_left().y,
|
y: dest_offset.1 as i32,
|
||||||
z: 0,
|
z: 0,
|
||||||
},
|
},
|
||||||
extent: alias.size(),
|
extent: vk::Extent3D {
|
||||||
|
width: size.0,
|
||||||
|
height: size.1,
|
||||||
|
depth: 1,
|
||||||
|
},
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
|
|
||||||
let image: Barrier = image_barrier(
|
let image: Barrier = image_barrier(
|
||||||
texture.handle(),
|
texture.raw(),
|
||||||
texture.format(),
|
texture.format(),
|
||||||
Access {
|
Access {
|
||||||
stage: vk::PipelineStageFlags2::TRANSFER,
|
stage: vk::PipelineStageFlags2::TRANSFER,
|
||||||
|
|
@ -379,24 +418,14 @@ pub fn egui_pre_pass(
|
||||||
(staging_image, Access::undefined()),
|
(staging_image, Access::undefined()),
|
||||||
]
|
]
|
||||||
.to_vec(),
|
.to_vec(),
|
||||||
writes: textures
|
writes,
|
||||||
.iter()
|
|
||||||
.map(|(_, id)| {
|
|
||||||
(
|
|
||||||
*id,
|
|
||||||
Access {
|
|
||||||
layout: vk::ImageLayout::GENERAL,
|
|
||||||
..Access::undefined()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
record: Some(record),
|
record: Some(record),
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn egui_pass()
|
// fn egui_pass()
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn egui_pass(
|
pub fn egui_pass(
|
||||||
dev: &device::Device,
|
dev: &device::Device,
|
||||||
rg: &mut RenderGraph,
|
rg: &mut RenderGraph,
|
||||||
|
|
@ -406,7 +435,7 @@ pub fn egui_pass(
|
||||||
egui: &egui::Context,
|
egui: &egui::Context,
|
||||||
output: egui::FullOutput,
|
output: egui::FullOutput,
|
||||||
target: GraphResourceId,
|
target: GraphResourceId,
|
||||||
) -> VkResult<Vec<texture::TextureId>> {
|
) -> crate::Result<Vec<texture::TextureId>> {
|
||||||
let draw_data = egui.tessellate(output.shapes, output.pixels_per_point);
|
let draw_data = egui.tessellate(output.shapes, output.pixels_per_point);
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
|
@ -470,19 +499,17 @@ pub fn egui_pass(
|
||||||
name: Some("egui-draw-staging".into()),
|
name: Some("egui-draw-staging".into()),
|
||||||
size: staging_size as u64,
|
size: staging_size as u64,
|
||||||
usage: vk::BufferUsageFlags::TRANSFER_SRC,
|
usage: vk::BufferUsageFlags::TRANSFER_SRC,
|
||||||
mem_usage: vk_mem::MemoryUsage::AutoPreferHost,
|
mem_location: MemoryLocation::CpuToGpu,
|
||||||
alloc_flags: vk_mem::AllocationCreateFlags::MAPPED
|
|
||||||
| vk_mem::AllocationCreateFlags::HOST_ACCESS_SEQUENTIAL_WRITE
|
|
||||||
| vk_mem::AllocationCreateFlags::STRATEGY_FIRST_FIT,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut map = staging.map()?;
|
let map = staging.map_mut().unwrap();
|
||||||
|
|
||||||
let (st_vertices, rest) = map.split_at_mut(vertices_size);
|
let (st_vertices, rest) = map.split_at_mut(vertices_size);
|
||||||
let (st_indices, st_drawcalls) = rest.split_at_mut(indices_size);
|
let (st_indices, rest) = rest.split_at_mut(indices_size);
|
||||||
|
let (st_drawcalls, _rest) = rest.split_at_mut(draw_calls_size);
|
||||||
st_vertices.copy_from_slice(bytemuck::cast_slice(&vertices));
|
st_vertices.copy_from_slice(bytemuck::cast_slice(&vertices));
|
||||||
st_indices.copy_from_slice(bytemuck::cast_slice(&indices));
|
st_indices.copy_from_slice(bytemuck::cast_slice(&indices));
|
||||||
st_drawcalls.copy_from_slice(bytemuck::cast_slice(&draw_calls));
|
st_drawcalls.copy_from_slice(bytemuck::cast_slice(&draw_calls));
|
||||||
|
|
@ -493,21 +520,21 @@ pub fn egui_pass(
|
||||||
name: Some("egui-draw-vertices".into()),
|
name: Some("egui-draw-vertices".into()),
|
||||||
size: vertices_size as u64,
|
size: vertices_size as u64,
|
||||||
usage: vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::VERTEX_BUFFER,
|
usage: vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::VERTEX_BUFFER,
|
||||||
mem_usage: vk_mem::MemoryUsage::AutoPreferDevice,
|
mem_location: MemoryLocation::GpuOnly,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}));
|
}));
|
||||||
let indices = rg.add_resource(GraphResourceDesc::Buffer(BufferDesc {
|
let indices = rg.add_resource(GraphResourceDesc::Buffer(BufferDesc {
|
||||||
name: Some("egui-draw-indices".into()),
|
name: Some("egui-draw-indices".into()),
|
||||||
size: indices_size as u64,
|
size: indices_size as u64,
|
||||||
usage: vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::INDEX_BUFFER,
|
usage: vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::INDEX_BUFFER,
|
||||||
mem_usage: vk_mem::MemoryUsage::AutoPreferDevice,
|
mem_location: MemoryLocation::GpuOnly,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}));
|
}));
|
||||||
let draw_calls = rg.add_resource(GraphResourceDesc::Buffer(BufferDesc {
|
let draw_calls = rg.add_resource(GraphResourceDesc::Buffer(BufferDesc {
|
||||||
name: Some("egui-draw-draw_calls".into()),
|
name: Some("egui-draw-draw_calls".into()),
|
||||||
size: draw_calls_size as u64,
|
size: draw_calls_size as u64,
|
||||||
usage: vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::INDIRECT_BUFFER,
|
usage: vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::INDIRECT_BUFFER,
|
||||||
mem_usage: vk_mem::MemoryUsage::AutoPreferDevice,
|
mem_location: MemoryLocation::GpuOnly,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -517,13 +544,14 @@ pub fn egui_pass(
|
||||||
name: Some("egui-draw-texture_ids".into()),
|
name: Some("egui-draw-texture_ids".into()),
|
||||||
size: (textures_indices.len() * size_of::<u32>()) as u64,
|
size: (textures_indices.len() * size_of::<u32>()) as u64,
|
||||||
usage: vk::BufferUsageFlags::STORAGE_BUFFER,
|
usage: vk::BufferUsageFlags::STORAGE_BUFFER,
|
||||||
mem_usage: vk_mem::MemoryUsage::AutoPreferDevice,
|
mem_location: MemoryLocation::CpuToGpu,
|
||||||
alloc_flags: vk_mem::AllocationCreateFlags::HOST_ACCESS_SEQUENTIAL_WRITE,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
{
|
{
|
||||||
let mut map = texture_ids.map()?;
|
let map = texture_ids
|
||||||
|
.map_mut()
|
||||||
|
.expect("texture id buffer should be device visible");
|
||||||
map.copy_from_slice(bytemuck::cast_slice(&textures_indices));
|
map.copy_from_slice(bytemuck::cast_slice(&textures_indices));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -534,27 +562,23 @@ pub fn egui_pass(
|
||||||
.values()
|
.values()
|
||||||
.map(|entry| {
|
.map(|entry| {
|
||||||
let texture = texture_handler.get_texture(entry.id).unwrap();
|
let texture = texture_handler.get_texture(entry.id).unwrap();
|
||||||
let info = vk::DescriptorImageInfo {
|
vk::DescriptorImageInfo {
|
||||||
sampler: samplers.get_sampler(entry.into_sampler_desc()).unwrap(),
|
sampler: samplers.get_sampler(entry.as_sampler_desc()).unwrap(),
|
||||||
image_view: texture
|
image_view: texture
|
||||||
.get_view(ImageViewDesc {
|
.create_view(
|
||||||
kind: vk::ImageViewType::TYPE_2D,
|
ImageViewDesc::color_2d()
|
||||||
format: texture.format(),
|
.with_mip_range(0..1)
|
||||||
aspect: vk::ImageAspectFlags::COLOR,
|
.with_layer_range(0..1),
|
||||||
mip_range: (0..1).into(),
|
)
|
||||||
layer_range: (0..1).into(),
|
.unwrap()
|
||||||
..Default::default()
|
.raw(),
|
||||||
})
|
|
||||||
.unwrap(),
|
|
||||||
image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||||
};
|
}
|
||||||
|
|
||||||
info
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let uniform_info = vk::DescriptorBufferInfo {
|
let uniform_info = vk::DescriptorBufferInfo {
|
||||||
buffer: texture_ids.buffer(),
|
buffer: texture_ids.raw(),
|
||||||
offset: 0,
|
offset: 0,
|
||||||
range: texture_ids.len(),
|
range: texture_ids.len(),
|
||||||
};
|
};
|
||||||
|
|
@ -620,8 +644,8 @@ pub fn egui_pass(
|
||||||
let target = ctx.get_image(target).unwrap();
|
let target = ctx.get_image(target).unwrap();
|
||||||
|
|
||||||
cmd.copy_buffers(
|
cmd.copy_buffers(
|
||||||
staging.buffer(),
|
staging.raw(),
|
||||||
vertices.buffer(),
|
vertices.raw(),
|
||||||
&[vk::BufferCopy {
|
&[vk::BufferCopy {
|
||||||
src_offset: 0,
|
src_offset: 0,
|
||||||
dst_offset: 0,
|
dst_offset: 0,
|
||||||
|
|
@ -629,8 +653,8 @@ pub fn egui_pass(
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
cmd.copy_buffers(
|
cmd.copy_buffers(
|
||||||
staging.buffer(),
|
staging.raw(),
|
||||||
indices.buffer(),
|
indices.raw(),
|
||||||
&[vk::BufferCopy {
|
&[vk::BufferCopy {
|
||||||
src_offset: vertices_size as u64,
|
src_offset: vertices_size as u64,
|
||||||
dst_offset: 0,
|
dst_offset: 0,
|
||||||
|
|
@ -638,8 +662,8 @@ pub fn egui_pass(
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
cmd.copy_buffers(
|
cmd.copy_buffers(
|
||||||
staging.buffer(),
|
staging.raw(),
|
||||||
draw_calls.buffer(),
|
draw_calls.raw(),
|
||||||
&[vk::BufferCopy {
|
&[vk::BufferCopy {
|
||||||
src_offset: (vertices_size + indices_size) as u64,
|
src_offset: (vertices_size + indices_size) as u64,
|
||||||
dst_offset: 0,
|
dst_offset: 0,
|
||||||
|
|
@ -649,7 +673,7 @@ pub fn egui_pass(
|
||||||
|
|
||||||
let barriers = [
|
let barriers = [
|
||||||
buffer_barrier(
|
buffer_barrier(
|
||||||
vertices.handle(),
|
vertices.raw(),
|
||||||
0,
|
0,
|
||||||
vertices.len(),
|
vertices.len(),
|
||||||
Access::transfer_write(),
|
Access::transfer_write(),
|
||||||
|
|
@ -657,7 +681,7 @@ pub fn egui_pass(
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
buffer_barrier(
|
buffer_barrier(
|
||||||
indices.handle(),
|
indices.raw(),
|
||||||
0,
|
0,
|
||||||
indices.len(),
|
indices.len(),
|
||||||
Access::transfer_write(),
|
Access::transfer_write(),
|
||||||
|
|
@ -665,7 +689,7 @@ pub fn egui_pass(
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
buffer_barrier(
|
buffer_barrier(
|
||||||
draw_calls.handle(),
|
draw_calls.raw(),
|
||||||
0,
|
0,
|
||||||
draw_calls.len(),
|
draw_calls.len(),
|
||||||
Access::transfer_write(),
|
Access::transfer_write(),
|
||||||
|
|
@ -682,12 +706,7 @@ pub fn egui_pass(
|
||||||
|
|
||||||
let color_attachment = &vk::RenderingAttachmentInfo::default()
|
let color_attachment = &vk::RenderingAttachmentInfo::default()
|
||||||
.image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
.image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||||
.image_view(target.get_view(ImageViewDesc {
|
.image_view(target.create_view(ImageViewDesc::color_2d())?.raw())
|
||||||
kind: vk::ImageViewType::TYPE_2D,
|
|
||||||
format: target.format(),
|
|
||||||
aspect: vk::ImageAspectFlags::COLOR,
|
|
||||||
..Default::default()
|
|
||||||
})?)
|
|
||||||
.load_op(vk::AttachmentLoadOp::LOAD)
|
.load_op(vk::AttachmentLoadOp::LOAD)
|
||||||
.store_op(vk::AttachmentStoreOp::STORE);
|
.store_op(vk::AttachmentStoreOp::STORE);
|
||||||
|
|
||||||
|
|
@ -711,8 +730,8 @@ pub fn egui_pass(
|
||||||
.height(target.height() as f32)]);
|
.height(target.height() as f32)]);
|
||||||
|
|
||||||
cmd.bind_pipeline(&pipeline);
|
cmd.bind_pipeline(&pipeline);
|
||||||
cmd.bind_indices(indices.buffer(), 0, vk::IndexType::UINT32);
|
cmd.bind_indices(indices.raw(), 0, vk::IndexType::UINT32);
|
||||||
cmd.bind_vertex_buffers(&[vertices.buffer()], &[0]);
|
cmd.bind_vertex_buffers(&[vertices.raw()], &[0]);
|
||||||
cmd.push_constants(
|
cmd.push_constants(
|
||||||
&pipeline_layout,
|
&pipeline_layout,
|
||||||
vk::ShaderStageFlags::VERTEX,
|
vk::ShaderStageFlags::VERTEX,
|
||||||
|
|
@ -727,7 +746,7 @@ pub fn egui_pass(
|
||||||
&[descriptor_set],
|
&[descriptor_set],
|
||||||
);
|
);
|
||||||
cmd.draw_indexed_indirect(
|
cmd.draw_indexed_indirect(
|
||||||
draw_calls.buffer(),
|
draw_calls.raw(),
|
||||||
0,
|
0,
|
||||||
num_draw_calls as u32,
|
num_draw_calls as u32,
|
||||||
size_of::<vk::DrawIndexedIndirectCommand>() as u32,
|
size_of::<vk::DrawIndexedIndirectCommand>() as u32,
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
512
crates/renderer/src/instance.rs
Normal file
512
crates/renderer/src/instance.rs
Normal file
|
|
@ -0,0 +1,512 @@
|
||||||
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
ffi::{CStr, CString},
|
||||||
|
mem::MaybeUninit,
|
||||||
|
ops::Deref,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use ash::{Entry, ext, khr, vk};
|
||||||
|
use raw_window_handle::RawDisplayHandle;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Error, PhysicalDeviceFeatures, PhysicalDeviceInfo, SurfaceCapabilities,
|
||||||
|
device::{Extension, get_extensions, get_layers},
|
||||||
|
get_physical_device_features, get_physical_device_properties, make_extension,
|
||||||
|
swapchain::Surface,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct DebugUtilsCreateInfo {
|
||||||
|
pub severity: vk::DebugUtilsMessageSeverityFlagsEXT,
|
||||||
|
pub message_type: vk::DebugUtilsMessageTypeFlagsEXT,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct InstanceExtensions {
|
||||||
|
pub(crate) get_surface_capabilities2: Option<khr::get_surface_capabilities2::Instance>,
|
||||||
|
pub(crate) surface_maintenance1: Option<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InstanceInner {
|
||||||
|
pub raw: ash::Instance,
|
||||||
|
pub(crate) extensions: InstanceExtensions,
|
||||||
|
pub entry: Entry,
|
||||||
|
pub(crate) _debug_utils: DebugUtils,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Instance {
|
||||||
|
pub(crate) inner: Arc<InstanceInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Instance {
|
||||||
|
type Target = InstanceInner;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Debug for Instance {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Instance")
|
||||||
|
.field("raw", &self.inner.raw.handle())
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InstanceDesc<'a> {
|
||||||
|
pub app_name: Option<&'a str>,
|
||||||
|
pub app_version: u32,
|
||||||
|
pub instance_extensions: &'a [Extension<'a>],
|
||||||
|
pub layer_settings: &'a [vk::LayerSettingEXT<'a>],
|
||||||
|
pub layers: &'a [&'a CStr],
|
||||||
|
pub display_handle: Option<RawDisplayHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const VALIDATION_LAYER: &CStr = c"VK_LAYER_KHRONOS_validation";
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
const DEBUG_LAYER_SETTINGS: &[vk::LayerSettingEXT] = &[];
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
const DEBUG_LAYER_SETTINGS: &[vk::LayerSettingEXT] = &[
|
||||||
|
vk::LayerSettingEXT {
|
||||||
|
p_layer_name: VALIDATION_LAYER.as_ptr(),
|
||||||
|
p_setting_name: c"validate_best_practices".as_ptr(),
|
||||||
|
value_count: 1,
|
||||||
|
p_values: &[1u8; 1] as *const u8 as _,
|
||||||
|
ty: vk::LayerSettingTypeEXT::BOOL32,
|
||||||
|
_marker: core::marker::PhantomData,
|
||||||
|
},
|
||||||
|
vk::LayerSettingEXT {
|
||||||
|
p_layer_name: VALIDATION_LAYER.as_ptr(),
|
||||||
|
p_setting_name: c"validate_best_practices_amd".as_ptr(),
|
||||||
|
value_count: 1,
|
||||||
|
p_values: &[1u8; 1] as *const u8 as _,
|
||||||
|
ty: vk::LayerSettingTypeEXT::BOOL32,
|
||||||
|
_marker: core::marker::PhantomData,
|
||||||
|
},
|
||||||
|
vk::LayerSettingEXT {
|
||||||
|
p_layer_name: VALIDATION_LAYER.as_ptr(),
|
||||||
|
p_setting_name: c"validate_sync".as_ptr(),
|
||||||
|
value_count: 1,
|
||||||
|
p_values: &[1u8; 1] as *const u8 as _,
|
||||||
|
ty: vk::LayerSettingTypeEXT::BOOL32,
|
||||||
|
_marker: core::marker::PhantomData,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
impl Default for InstanceDesc<'_> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
app_name: None,
|
||||||
|
app_version: vk::make_api_version(0, 1, 0, 0),
|
||||||
|
instance_extensions: &[make_extension!(khr::surface)],
|
||||||
|
layer_settings: DEBUG_LAYER_SETTINGS,
|
||||||
|
layers: &[VALIDATION_LAYER],
|
||||||
|
display_handle: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance {
|
||||||
|
pub fn new<'a>(desc: &InstanceDesc<'a>) -> crate::Result<Self> {
|
||||||
|
let entry = unsafe { ash::Entry::load()? };
|
||||||
|
|
||||||
|
let app_name = desc
|
||||||
|
.app_name
|
||||||
|
.and_then(|name| CString::new(name).ok())
|
||||||
|
.unwrap_or(c"ShooterGame".to_owned());
|
||||||
|
|
||||||
|
let version =
|
||||||
|
unsafe { entry.try_enumerate_instance_version()? }.unwrap_or(vk::API_VERSION_1_0);
|
||||||
|
|
||||||
|
let app_info = vk::ApplicationInfo::default()
|
||||||
|
.api_version(if version < vk::API_VERSION_1_1 {
|
||||||
|
vk::API_VERSION_1_0
|
||||||
|
} else {
|
||||||
|
// ash doesn't support 1.4 yet
|
||||||
|
vk::API_VERSION_1_3
|
||||||
|
})
|
||||||
|
.application_name(&app_name)
|
||||||
|
.application_version(desc.app_version)
|
||||||
|
.engine_name(c"Bevy Engine")
|
||||||
|
.engine_version(vk::make_api_version(0, 0, 1, 0));
|
||||||
|
|
||||||
|
let mut validation_info =
|
||||||
|
vk::LayerSettingsCreateInfoEXT::default().settings(desc.layer_settings);
|
||||||
|
|
||||||
|
let layers = match get_layers(&entry, desc.layers.to_vec()) {
|
||||||
|
Ok(layers) => layers,
|
||||||
|
Err((supported, unsupported)) => {
|
||||||
|
tracing::error!(
|
||||||
|
"Some requested layers were not supported by instance:\nSupported: {:?}\nUnsupported: {:?}",
|
||||||
|
supported,
|
||||||
|
unsupported
|
||||||
|
);
|
||||||
|
supported
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut requested_extensions = desc.instance_extensions.to_vec();
|
||||||
|
|
||||||
|
requested_extensions.push(make_extension!(ext::debug_utils as 1));
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
requested_extensions.push(make_extension!(ext::layer_settings));
|
||||||
|
|
||||||
|
// These are wanted for surface capabilities querying, but not strictly
|
||||||
|
// required, and are only supported on newer devices, especially on
|
||||||
|
// NVIDIA cards.
|
||||||
|
requested_extensions.push(make_extension!(khr::get_surface_capabilities2));
|
||||||
|
requested_extensions.push(make_extension!(ext::surface_maintenance1));
|
||||||
|
|
||||||
|
let (extensions, unsupported_extensions) =
|
||||||
|
get_extensions(&entry, &layers, requested_extensions, desc.display_handle)?;
|
||||||
|
|
||||||
|
if !unsupported_extensions.is_empty() {
|
||||||
|
tracing::error!(
|
||||||
|
"extensions were requested but not supported by instance: {:?}",
|
||||||
|
unsupported_extensions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let layers = layers
|
||||||
|
.iter()
|
||||||
|
.map(|layer| layer.as_ptr())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let extension_names = extensions
|
||||||
|
.iter()
|
||||||
|
.map(|ext| ext.name.as_ptr())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let create_info = vk::InstanceCreateInfo::default()
|
||||||
|
.application_info(&app_info)
|
||||||
|
.enabled_extension_names(&extension_names)
|
||||||
|
.enabled_layer_names(&layers)
|
||||||
|
.push_next(&mut validation_info);
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
"Creating instance:\napp_info: {app_info:#?}\ncreate_info: {create_info:#?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let instance = unsafe { entry.create_instance(&create_info, None)? };
|
||||||
|
|
||||||
|
let debug_utils = {
|
||||||
|
let debug_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
|
||||||
|
.message_severity(
|
||||||
|
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR
|
||||||
|
| vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
|
||||||
|
| vk::DebugUtilsMessageSeverityFlagsEXT::INFO,
|
||||||
|
)
|
||||||
|
.message_type(
|
||||||
|
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
|
||||||
|
| vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
|
||||||
|
| vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
|
||||||
|
)
|
||||||
|
.pfn_user_callback(Some(crate::debug::debug_callback));
|
||||||
|
|
||||||
|
let instance = ext::debug_utils::Instance::new(&entry, &instance);
|
||||||
|
let messenger = unsafe { instance.create_debug_utils_messenger(&debug_info, None)? };
|
||||||
|
|
||||||
|
crate::DebugUtils {
|
||||||
|
instance,
|
||||||
|
messenger,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let extensions = InstanceExtensions {
|
||||||
|
get_surface_capabilities2: extensions
|
||||||
|
.contains(&make_extension!(khr::get_surface_capabilities2))
|
||||||
|
.then(|| khr::get_surface_capabilities2::Instance::new(&entry, &instance)),
|
||||||
|
surface_maintenance1: extensions
|
||||||
|
.contains(&make_extension!(ext::surface_maintenance1))
|
||||||
|
.then_some(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let instance = Arc::new(InstanceInner {
|
||||||
|
raw: instance,
|
||||||
|
extensions,
|
||||||
|
_debug_utils: debug_utils,
|
||||||
|
entry,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Self { inner: instance })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn choose_adapter(
|
||||||
|
&self,
|
||||||
|
mut discriminator: impl FnMut(&PhysicalDeviceInfo, &PhysicalDeviceInfo) -> Ordering,
|
||||||
|
) -> crate::Result<PhysicalDeviceInfo> {
|
||||||
|
let pdevs = unsafe { self.inner.raw.enumerate_physical_devices()? };
|
||||||
|
let mut pdevs = pdevs
|
||||||
|
.into_iter()
|
||||||
|
.map(|pdev| -> crate::Result<PhysicalDeviceInfo> { self.expose_adapter(pdev) })
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
pdevs.sort_unstable_by(&mut discriminator);
|
||||||
|
pdevs.pop().ok_or(Error::NoAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Queries the surface capabilities of the given physical device and
|
||||||
|
/// surface, including supported formats and present modes.
|
||||||
|
/// If a present mode is provided, and the
|
||||||
|
/// `VK_KHR_get_surface_capabilities2` and `VK_EXT_surface_maintenance1`
|
||||||
|
/// extensions are available, it will also query present mode compatibility
|
||||||
|
/// information to filter the present modes based on the provided initial
|
||||||
|
/// mode.
|
||||||
|
pub(crate) fn get_adapter_surface_capabilities(
|
||||||
|
&self,
|
||||||
|
pdev: vk::PhysicalDevice,
|
||||||
|
surface: &Surface,
|
||||||
|
present_mode: Option<vk::PresentModeKHR>,
|
||||||
|
) -> crate::Result<SurfaceCapabilities> {
|
||||||
|
let formats = unsafe {
|
||||||
|
surface
|
||||||
|
.functor
|
||||||
|
.get_physical_device_surface_formats(pdev, surface.raw)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let present_modes = unsafe {
|
||||||
|
surface
|
||||||
|
.functor
|
||||||
|
.get_physical_device_surface_present_modes(pdev, surface.raw)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let generic_caps = unsafe {
|
||||||
|
surface
|
||||||
|
.functor
|
||||||
|
.get_physical_device_surface_capabilities(pdev, surface.raw)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let (capabilities, present_modes) = if let Some(ref functor) =
|
||||||
|
self.extensions.get_surface_capabilities2
|
||||||
|
&& self.extensions.surface_maintenance1.is_some()
|
||||||
|
{
|
||||||
|
let mut capabilities = vk::SurfaceCapabilities2KHR::default();
|
||||||
|
let mut surface_info =
|
||||||
|
vk::PhysicalDeviceSurfaceInfo2KHR::default().surface(surface.raw());
|
||||||
|
|
||||||
|
let present_mode = present_mode.unwrap_or(present_modes[0]);
|
||||||
|
let mut surface_present_mode =
|
||||||
|
vk::SurfacePresentModeEXT::default().present_mode(present_mode);
|
||||||
|
surface_info = surface_info.push_next(&mut surface_present_mode);
|
||||||
|
|
||||||
|
let mut present_modes = MaybeUninit::<[vk::PresentModeKHR; 8]>::uninit();
|
||||||
|
let mut present_mode_info = vk::SurfacePresentModeCompatibilityEXT::default()
|
||||||
|
.present_modes(unsafe { present_modes.assume_init_mut() });
|
||||||
|
capabilities = capabilities.push_next(&mut present_mode_info);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
functor.get_physical_device_surface_capabilities2(
|
||||||
|
pdev,
|
||||||
|
&surface_info,
|
||||||
|
&mut capabilities,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
(capabilities.surface_capabilities, unsafe {
|
||||||
|
let num_present_modes = present_mode_info.present_mode_count as usize;
|
||||||
|
present_modes.assume_init()[..num_present_modes].to_vec()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
(generic_caps, present_modes)
|
||||||
|
};
|
||||||
|
|
||||||
|
// surface.functor
|
||||||
|
// .get_physical_device_surface_capabilities2(pdev, surface.raw, present_mode)?;
|
||||||
|
|
||||||
|
Ok(SurfaceCapabilities {
|
||||||
|
capabilities,
|
||||||
|
min_image_count: generic_caps.min_image_count,
|
||||||
|
formats,
|
||||||
|
present_modes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn expose_adapter(
|
||||||
|
&self,
|
||||||
|
pdev: vk::PhysicalDevice,
|
||||||
|
) -> crate::Result<PhysicalDeviceInfo> {
|
||||||
|
let properties = get_physical_device_properties(&self.inner, pdev)?;
|
||||||
|
let features = get_physical_device_features(&self.inner, pdev, &properties);
|
||||||
|
|
||||||
|
Ok(PhysicalDeviceInfo {
|
||||||
|
pdev,
|
||||||
|
properties,
|
||||||
|
features,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn choose_adapter_default(
|
||||||
|
&self,
|
||||||
|
surface: Option<&Surface>,
|
||||||
|
required_extensions: &[Extension],
|
||||||
|
required_features: Option<&PhysicalDeviceFeatures>,
|
||||||
|
) -> crate::Result<PhysicalDeviceInfo> {
|
||||||
|
self.choose_adapter(|a, b| {
|
||||||
|
// Extensions: we definitely need swapchain.
|
||||||
|
match a
|
||||||
|
.properties
|
||||||
|
.supports_extension(make_extension!(khr::swapchain))
|
||||||
|
.cmp(
|
||||||
|
&b.properties
|
||||||
|
.supports_extension(make_extension!(khr::swapchain)),
|
||||||
|
) {
|
||||||
|
Ordering::Equal => {}
|
||||||
|
other => return other,
|
||||||
|
}
|
||||||
|
|
||||||
|
for ext in required_extensions {
|
||||||
|
match a.properties.supports_extension(*ext) {
|
||||||
|
true => {}
|
||||||
|
false => return Ordering::Less,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check surface compatibility
|
||||||
|
// TODO
|
||||||
|
_ = surface;
|
||||||
|
|
||||||
|
if let Some(ref required_features) = required_features {
|
||||||
|
if b.features.superset_of(&required_features)
|
||||||
|
&& !a.features.superset_of(&required_features)
|
||||||
|
{
|
||||||
|
return Ordering::Less;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check specific features that we need
|
||||||
|
match a
|
||||||
|
.features
|
||||||
|
.core13
|
||||||
|
.synchronization2
|
||||||
|
.cmp(&b.features.core13.synchronization2)
|
||||||
|
{
|
||||||
|
Ordering::Equal => {}
|
||||||
|
other => return other,
|
||||||
|
}
|
||||||
|
match a
|
||||||
|
.features
|
||||||
|
.core13
|
||||||
|
.dynamic_rendering
|
||||||
|
.cmp(&b.features.core13.dynamic_rendering)
|
||||||
|
{
|
||||||
|
Ordering::Equal => {}
|
||||||
|
other => return other,
|
||||||
|
}
|
||||||
|
match a
|
||||||
|
.features
|
||||||
|
.core13
|
||||||
|
.maintenance4
|
||||||
|
.cmp(&b.features.core13.maintenance4)
|
||||||
|
{
|
||||||
|
Ordering::Equal => {}
|
||||||
|
other => return other,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer discrete GPUs
|
||||||
|
let a_discrete = a.properties.core.device_type == vk::PhysicalDeviceType::DISCRETE_GPU;
|
||||||
|
let b_discrete = b.properties.core.device_type == vk::PhysicalDeviceType::DISCRETE_GPU;
|
||||||
|
|
||||||
|
match (a_discrete, b_discrete) {
|
||||||
|
(true, false) => return Ordering::Greater,
|
||||||
|
(false, true) => return Ordering::Less,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then prefer newer APIs
|
||||||
|
match a
|
||||||
|
.properties
|
||||||
|
.core
|
||||||
|
.api_version
|
||||||
|
.cmp(&b.properties.core.api_version)
|
||||||
|
{
|
||||||
|
Ordering::Equal => {}
|
||||||
|
other => return other,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer larger memory
|
||||||
|
match a
|
||||||
|
.properties
|
||||||
|
.get_device_local_memory_count()
|
||||||
|
.cmp(&b.properties.get_device_local_memory_count())
|
||||||
|
{
|
||||||
|
Ordering::Equal => {}
|
||||||
|
other => return other,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer larger texture size
|
||||||
|
match a
|
||||||
|
.properties
|
||||||
|
.core
|
||||||
|
.limits
|
||||||
|
.max_image_dimension2_d
|
||||||
|
.cmp(&b.properties.core.limits.max_image_dimension2_d)
|
||||||
|
{
|
||||||
|
Ordering::Equal => {}
|
||||||
|
other => return other,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ordering::Equal
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// So... basically this probably doesn't matter because the graphics queue will always be a present queue as well...
|
||||||
|
pub(crate) fn query_presentation_support(
|
||||||
|
&self,
|
||||||
|
pdev: vk::PhysicalDevice,
|
||||||
|
queue_family: u32,
|
||||||
|
display_handle: RawDisplayHandle,
|
||||||
|
) -> bool {
|
||||||
|
unsafe {
|
||||||
|
match display_handle {
|
||||||
|
RawDisplayHandle::Xlib(display) => {
|
||||||
|
let surface =
|
||||||
|
ash::khr::xlib_surface::Instance::new(&self.inner.entry, &self.inner.raw);
|
||||||
|
surface.get_physical_device_xlib_presentation_support(
|
||||||
|
pdev,
|
||||||
|
queue_family,
|
||||||
|
display.display.unwrap().as_ptr() as _,
|
||||||
|
display.screen as _,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RawDisplayHandle::Xcb(_xcb_display_handle) => todo!("xcb"),
|
||||||
|
RawDisplayHandle::Wayland(wayland_display_handle) => {
|
||||||
|
let surface = ash::khr::wayland_surface::Instance::new(
|
||||||
|
&self.inner.entry,
|
||||||
|
&self.inner.raw,
|
||||||
|
);
|
||||||
|
surface.get_physical_device_wayland_presentation_support(
|
||||||
|
pdev,
|
||||||
|
queue_family,
|
||||||
|
wayland_display_handle.display.cast().as_mut(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RawDisplayHandle::Drm(_) => {
|
||||||
|
// idk ?
|
||||||
|
true
|
||||||
|
}
|
||||||
|
RawDisplayHandle::Windows(_) => {
|
||||||
|
ash::khr::win32_surface::Instance::new(&self.inner.entry, &self.inner.raw)
|
||||||
|
.get_physical_device_win32_presentation_support(pdev, queue_family)
|
||||||
|
}
|
||||||
|
_ => panic!("unsupported platform"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct DebugUtils {
|
||||||
|
pub instance: ext::debug_utils::Instance,
|
||||||
|
pub messenger: vk::DebugUtilsMessengerEXT,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for DebugUtils {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
self.instance
|
||||||
|
.destroy_debug_utils_messenger(self.messenger, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
1
crates/renderer/src/memory.rs
Normal file
1
crates/renderer/src/memory.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
use std::{borrow::Cow, path::Path, sync::Arc};
|
use std::{borrow::Cow, path::Path, sync::Arc};
|
||||||
|
|
||||||
use ash::{prelude::*, vk};
|
use ash::{ext, prelude::*, vk};
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
define_device_owned_handle,
|
define_device_owned_handle,
|
||||||
device::{Device, DeviceOwnedDebugObject},
|
device::{
|
||||||
|
Device, DeviceHandle, DeviceInner, DeviceObject, asdf::traits::ExternallyManagedObject,
|
||||||
|
},
|
||||||
|
make_extension,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -39,12 +43,6 @@ pub struct PipelineLayoutDesc<'a> {
|
||||||
pub name: Option<Cow<'static, str>>,
|
pub name: Option<Cow<'static, str>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum PipelineDesc<'a> {
|
|
||||||
Compute(ComputePipelineDesc<'a>),
|
|
||||||
Graphics(GraphicsPipelineDesc<'a>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ComputePipelineDesc<'a> {
|
pub struct ComputePipelineDesc<'a> {
|
||||||
pub flags: vk::PipelineCreateFlags,
|
pub flags: vk::PipelineCreateFlags,
|
||||||
|
|
@ -222,15 +220,28 @@ pub struct DescriptorSetAllocDesc<'a> {
|
||||||
pub layout: &'a DescriptorSetLayout,
|
pub layout: &'a DescriptorSetLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
define_device_owned_handle! {
|
impl DeviceHandle for vk::DescriptorPool {
|
||||||
#[derive(Debug)]
|
unsafe fn destroy(&mut self, device: &Device) {
|
||||||
pub DescriptorPool(vk::DescriptorPool) {} => |this| unsafe {
|
unsafe { device.dev().destroy_descriptor_pool(*self, None) };
|
||||||
this.device().dev().destroy_descriptor_pool(this.handle(), None);
|
}
|
||||||
|
}
|
||||||
|
impl<T: AsRef<DeviceInner>> ExternallyManagedObject<T> for vk::DescriptorPool {
|
||||||
|
unsafe fn destroy(self, device: &T) {
|
||||||
|
// SAFETY: We have exclusive ownership of the descriptor pool, so it's safe to destroy it.
|
||||||
|
unsafe {
|
||||||
|
device.as_ref().raw.destroy_descriptor_pool(self, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DescriptorPool {
|
||||||
|
pool: DeviceObject<vk::DescriptorPool>,
|
||||||
|
lock: Mutex<()>,
|
||||||
|
}
|
||||||
|
|
||||||
impl DescriptorPool {
|
impl DescriptorPool {
|
||||||
pub fn new(device: Device, desc: DescriptorPoolDesc) -> VkResult<Self> {
|
pub fn new(device: Device, desc: DescriptorPoolDesc) -> crate::Result<Self> {
|
||||||
let info = &vk::DescriptorPoolCreateInfo::default()
|
let info = &vk::DescriptorPoolCreateInfo::default()
|
||||||
.flags(desc.flags)
|
.flags(desc.flags)
|
||||||
.max_sets(desc.max_sets)
|
.max_sets(desc.max_sets)
|
||||||
|
|
@ -238,49 +249,66 @@ impl DescriptorPool {
|
||||||
|
|
||||||
let handle = unsafe { device.dev().create_descriptor_pool(info, None)? };
|
let handle = unsafe { device.dev().create_descriptor_pool(info, None)? };
|
||||||
|
|
||||||
Self::construct(device, handle, desc.name)
|
Ok(Self {
|
||||||
|
pool: DeviceObject::new_debug_named(device, handle, desc.name),
|
||||||
|
lock: Mutex::new(()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allocate(&self, descs: &[DescriptorSetAllocDesc]) -> VkResult<Vec<vk::DescriptorSet>> {
|
pub fn allocate(&self, descs: &[DescriptorSetAllocDesc]) -> VkResult<Vec<vk::DescriptorSet>> {
|
||||||
let layouts = descs
|
let layouts = descs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|desc| desc.layout.handle())
|
.map(|desc| desc.layout.raw())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let info = &vk::DescriptorSetAllocateInfo::default()
|
let info = &vk::DescriptorSetAllocateInfo::default()
|
||||||
.descriptor_pool(self.handle())
|
.descriptor_pool(*self.pool)
|
||||||
.set_layouts(&layouts);
|
.set_layouts(&layouts);
|
||||||
let sets = unsafe { self.device().dev().allocate_descriptor_sets(&info)? };
|
|
||||||
|
let sets = unsafe {
|
||||||
|
let _lock = self.lock.lock();
|
||||||
|
self.pool.device().raw.allocate_descriptor_sets(info)?
|
||||||
|
};
|
||||||
|
|
||||||
for (&set, desc) in sets.iter().zip(descs) {
|
for (&set, desc) in sets.iter().zip(descs) {
|
||||||
if let Some(name) = desc.name.as_ref() {
|
if let Some(name) = desc.name.as_ref() {
|
||||||
self.device().debug_name_object(set, &name)?;
|
unsafe { self.pool.device().debug_name_object(set, name) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(sets)
|
Ok(sets)
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn free(&self) {}
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn reset(&self) -> VkResult<()> {
|
pub fn reset(&self) -> VkResult<()> {
|
||||||
|
let _lock = self.lock.lock();
|
||||||
unsafe {
|
unsafe {
|
||||||
self.device()
|
self.pool
|
||||||
.dev()
|
.device()
|
||||||
.reset_descriptor_pool(self.handle(), vk::DescriptorPoolResetFlags::empty())
|
.raw
|
||||||
|
.reset_descriptor_pool(*self.pool, vk::DescriptorPoolResetFlags::empty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
define_device_owned_handle! {
|
impl<T: AsRef<DeviceInner>> ExternallyManagedObject<T> for vk::DescriptorSetLayout {
|
||||||
#[derive(Debug)]
|
unsafe fn destroy(self, device: &T) {
|
||||||
pub DescriptorSetLayout(vk::DescriptorSetLayout) {} => |this| unsafe {
|
unsafe {
|
||||||
this.device().dev().destroy_descriptor_set_layout(this.handle(), None);
|
device
|
||||||
|
.as_ref()
|
||||||
|
.raw
|
||||||
|
.destroy_descriptor_set_layout(self, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DescriptorSetLayout {
|
||||||
|
layout: DeviceObject<vk::DescriptorSetLayout>,
|
||||||
|
}
|
||||||
|
|
||||||
impl DescriptorSetLayout {
|
impl DescriptorSetLayout {
|
||||||
pub fn new(device: Device, desc: DescriptorSetLayoutDesc) -> VkResult<Self> {
|
pub fn new(device: Device, desc: DescriptorSetLayoutDesc) -> crate::Result<Self> {
|
||||||
let (flags, bindings): (Vec<_>, Vec<_>) = desc
|
let (flags, bindings): (Vec<_>, Vec<_>) = desc
|
||||||
.bindings
|
.bindings
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -303,45 +331,60 @@ impl DescriptorSetLayout {
|
||||||
.bindings(&bindings)
|
.bindings(&bindings)
|
||||||
.flags(desc.flags);
|
.flags(desc.flags);
|
||||||
|
|
||||||
if device.features().version >= vk::API_VERSION_1_2
|
if device.properties().device_api_version >= vk::API_VERSION_1_2
|
||||||
|| device
|
|| device
|
||||||
.features()
|
.properties()
|
||||||
.supports_extension(&crate::make_extention_properties(
|
.supports_extension(make_extension!(ext::descriptor_indexing))
|
||||||
ash::ext::descriptor_indexing::NAME,
|
|
||||||
ash::ext::descriptor_indexing::SPEC_VERSION,
|
|
||||||
))
|
|
||||||
{
|
{
|
||||||
info = info.push_next(flags);
|
info = info.push_next(flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
let layout = unsafe { device.dev().create_descriptor_set_layout(&info, None)? };
|
let layout = unsafe { device.raw.create_descriptor_set_layout(&info, None)? };
|
||||||
|
|
||||||
Self::construct(device, layout, desc.name)
|
Ok(Self {
|
||||||
|
layout: DeviceObject::new_debug_named(device, layout, desc.name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw(&self) -> vk::DescriptorSetLayout {
|
||||||
|
*self.layout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::device::DeviceOwned;
|
use crate::device::DeviceOwned;
|
||||||
|
|
||||||
define_device_owned_handle! {
|
impl<T: AsRef<DeviceInner>> ExternallyManagedObject<T> for vk::PipelineLayout {
|
||||||
#[derive(Debug)]
|
unsafe fn destroy(self, device: &T) {
|
||||||
pub PipelineLayout(vk::PipelineLayout) {} => |this| unsafe {
|
unsafe {
|
||||||
this.device().dev().destroy_pipeline_layout(this.handle(), None);
|
device.as_ref().raw.destroy_pipeline_layout(self, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PipelineLayout {
|
||||||
|
layout: DeviceObject<vk::PipelineLayout>,
|
||||||
|
}
|
||||||
|
|
||||||
impl PipelineLayout {
|
impl PipelineLayout {
|
||||||
pub fn new(device: Device, desc: PipelineLayoutDesc) -> VkResult<Self> {
|
pub fn new(device: Device, desc: PipelineLayoutDesc) -> crate::Result<Self> {
|
||||||
let set_layouts = desc
|
let set_layouts = desc
|
||||||
.descriptor_set_layouts
|
.descriptor_set_layouts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|desc| desc.handle())
|
.map(|desc| desc.raw())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let info = &vk::PipelineLayoutCreateInfo::default()
|
let info = &vk::PipelineLayoutCreateInfo::default()
|
||||||
.set_layouts(&set_layouts)
|
.set_layouts(&set_layouts)
|
||||||
.push_constant_ranges(desc.push_constant_ranges);
|
.push_constant_ranges(desc.push_constant_ranges);
|
||||||
let layout = unsafe { device.dev().create_pipeline_layout(info, None)? };
|
let layout = unsafe { device.raw.create_pipeline_layout(info, None)? };
|
||||||
|
|
||||||
Self::construct(device, layout, desc.name)
|
Ok(Self {
|
||||||
|
layout: DeviceObject::new_debug_named(device, layout, desc.name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw(&self) -> vk::PipelineLayout {
|
||||||
|
*self.layout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -440,60 +483,69 @@ impl Sampler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
define_device_owned_handle! {
|
impl<T: AsRef<DeviceInner>> ExternallyManagedObject<T> for vk::ShaderModule {
|
||||||
#[derive(Debug)]
|
unsafe fn destroy(self, device: &T) {
|
||||||
pub ShaderModule(vk::ShaderModule) {} => |this| unsafe {
|
unsafe {
|
||||||
this.device().dev().destroy_shader_module(this.handle(), None);
|
device.as_ref().raw.destroy_shader_module(self, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ShaderModule {
|
||||||
|
module: DeviceObject<vk::ShaderModule>,
|
||||||
|
}
|
||||||
|
|
||||||
impl ShaderModule {
|
impl ShaderModule {
|
||||||
|
pub fn raw(&self) -> vk::ShaderModule {
|
||||||
|
*self.module
|
||||||
|
}
|
||||||
pub fn new_from_path<P: AsRef<Path>>(device: Device, path: P) -> crate::Result<Self> {
|
pub fn new_from_path<P: AsRef<Path>>(device: Device, path: P) -> crate::Result<Self> {
|
||||||
use std::io::{BufReader, Read, Seek};
|
use std::io::{BufReader, Read, Seek};
|
||||||
|
|
||||||
let mut file = std::fs::File::open(path)?;
|
let path = path.as_ref();
|
||||||
|
let mut file =
|
||||||
|
std::fs::File::open(path).map_err(|_| crate::Error::AssetMissing(path.to_owned()))?;
|
||||||
let size = file.seek(std::io::SeekFrom::End(0))? / 4;
|
let size = file.seek(std::io::SeekFrom::End(0))? / 4;
|
||||||
file.seek(std::io::SeekFrom::Start(0))?;
|
file.seek(std::io::SeekFrom::Start(0))?;
|
||||||
let mut reader = BufReader::new(file);
|
let mut reader = BufReader::new(file);
|
||||||
|
|
||||||
let mut buffer = Vec::<u32>::with_capacity(size as usize);
|
let mut buffer = vec![0; size as usize];
|
||||||
buffer.resize(size as usize, 0);
|
|
||||||
let size = reader.read(bytemuck::cast_slice_mut(buffer.as_mut_slice()))?;
|
let size = reader.read(bytemuck::cast_slice_mut(buffer.as_mut_slice()))?;
|
||||||
buffer.resize(size / 4, 0);
|
buffer.resize(size / 4, 0);
|
||||||
|
|
||||||
Ok(Self::new_from_memory(device, &buffer)?)
|
Self::new_from_memory(device, &buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_from_memory(device: Device, buffer: &[u32]) -> VkResult<Self> {
|
pub fn new_from_memory(device: Device, buffer: &[u32]) -> crate::Result<Self> {
|
||||||
let info = &vk::ShaderModuleCreateInfo::default().code(buffer);
|
let info = &vk::ShaderModuleCreateInfo::default().code(buffer);
|
||||||
|
|
||||||
let module = unsafe { device.dev().create_shader_module(info, None)? };
|
let module = unsafe { device.dev().create_shader_module(info, None)? };
|
||||||
|
|
||||||
Self::construct(device, module, None)
|
Ok(Self {
|
||||||
|
module: DeviceObject::new(device, module),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Pipeline {
|
pub struct Pipeline {
|
||||||
pipeline: DeviceOwnedDebugObject<vk::Pipeline>,
|
pipeline: DeviceObject<vk::Pipeline>,
|
||||||
bind_point: vk::PipelineBindPoint,
|
bind_point: vk::PipelineBindPoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Pipeline {
|
impl<T: AsRef<DeviceInner>> ExternallyManagedObject<T> for vk::Pipeline {
|
||||||
fn drop(&mut self) {
|
unsafe fn destroy(self, device: &T) {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.pipeline
|
device.as_ref().raw.destroy_pipeline(self, None);
|
||||||
.dev()
|
|
||||||
.dev()
|
|
||||||
.destroy_pipeline(self.pipeline.handle(), None);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShaderStageDesc<'_> {
|
impl ShaderStageDesc<'_> {
|
||||||
fn into_create_info(&self) -> vk::PipelineShaderStageCreateInfo {
|
fn as_create_info(&'_ self) -> vk::PipelineShaderStageCreateInfo<'_> {
|
||||||
vk::PipelineShaderStageCreateInfo::default()
|
vk::PipelineShaderStageCreateInfo::default()
|
||||||
.module(self.module.handle())
|
.module(self.module.raw())
|
||||||
.flags(self.flags)
|
.flags(self.flags)
|
||||||
.stage(self.stage)
|
.stage(self.stage)
|
||||||
.name(&self.entry)
|
.name(&self.entry)
|
||||||
|
|
@ -501,224 +553,328 @@ impl ShaderStageDesc<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pipeline {
|
impl Pipeline {
|
||||||
pub fn new(device: Device, desc: PipelineDesc) -> VkResult<Self> {
|
pub fn new_compute(device: Device, desc: ComputePipelineDesc) -> crate::Result<Self> {
|
||||||
let name: Option<Cow<'static, str>>;
|
let info = &vk::ComputePipelineCreateInfo::default()
|
||||||
let bind_point: vk::PipelineBindPoint;
|
.flags(desc.flags)
|
||||||
let result = match desc {
|
.layout(desc.layout.raw())
|
||||||
PipelineDesc::Compute(desc) => {
|
.base_pipeline_handle(
|
||||||
name = desc.name;
|
desc.base_pipeline
|
||||||
bind_point = vk::PipelineBindPoint::COMPUTE;
|
.map(|p| p.raw())
|
||||||
let info = &vk::ComputePipelineCreateInfo::default()
|
.unwrap_or(vk::Pipeline::null()),
|
||||||
.flags(desc.flags)
|
)
|
||||||
.layout(desc.layout.handle())
|
.stage(desc.shader_stage.as_create_info());
|
||||||
.base_pipeline_handle(
|
|
||||||
desc.base_pipeline
|
|
||||||
.map(|p| p.handle())
|
|
||||||
.unwrap_or(vk::Pipeline::null()),
|
|
||||||
)
|
|
||||||
.stage(desc.shader_stage.into_create_info());
|
|
||||||
|
|
||||||
unsafe {
|
let pipeline = unsafe {
|
||||||
device.dev().create_compute_pipelines(
|
device
|
||||||
vk::PipelineCache::null(),
|
.dev()
|
||||||
core::slice::from_ref(info),
|
.create_compute_pipelines(
|
||||||
None,
|
device.pools.pipeline_cache.raw,
|
||||||
)
|
core::slice::from_ref(info),
|
||||||
}
|
None,
|
||||||
}
|
)
|
||||||
PipelineDesc::Graphics(desc) => {
|
// It's cool to just take the first one and ignore any
|
||||||
name = desc.name;
|
// potentially created pipelines since we know there wont be any
|
||||||
bind_point = vk::PipelineBindPoint::GRAPHICS;
|
// others.
|
||||||
|
.map_err(|(_, err)| err)?[0]
|
||||||
let stages = desc
|
|
||||||
.shader_stages
|
|
||||||
.iter()
|
|
||||||
.map(|stage| stage.into_create_info())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let vertex_input = desc.vertex_input.map(|vertex| {
|
|
||||||
vk::PipelineVertexInputStateCreateInfo::default()
|
|
||||||
.vertex_attribute_descriptions(vertex.attributes)
|
|
||||||
.vertex_binding_descriptions(vertex.bindings)
|
|
||||||
});
|
|
||||||
let input_assembly = desc.input_assembly.map(|state| {
|
|
||||||
vk::PipelineInputAssemblyStateCreateInfo::default()
|
|
||||||
.primitive_restart_enable(state.primitive_restart)
|
|
||||||
.topology(state.topology)
|
|
||||||
});
|
|
||||||
let tessellation = desc.tessellation.map(|state| {
|
|
||||||
vk::PipelineTessellationStateCreateInfo::default()
|
|
||||||
.flags(state.flags)
|
|
||||||
.patch_control_points(state.patch_control_points)
|
|
||||||
});
|
|
||||||
let viewport = desc.viewport.map(|state| {
|
|
||||||
let mut info = vk::PipelineViewportStateCreateInfo::default()
|
|
||||||
.scissor_count(state.num_scissors)
|
|
||||||
.viewport_count(state.num_viewports);
|
|
||||||
if let Some(viewports) = state.viewports {
|
|
||||||
info = info.viewports(viewports);
|
|
||||||
}
|
|
||||||
if let Some(scissors) = state.scissors {
|
|
||||||
info = info.scissors(scissors);
|
|
||||||
}
|
|
||||||
|
|
||||||
info
|
|
||||||
});
|
|
||||||
|
|
||||||
let rasterization = desc.rasterization.map(|state| {
|
|
||||||
let mut info = vk::PipelineRasterizationStateCreateInfo::default()
|
|
||||||
.line_width(state.line_width)
|
|
||||||
.cull_mode(state.cull_mode)
|
|
||||||
.polygon_mode(state.polygon_mode)
|
|
||||||
.rasterizer_discard_enable(state.discard_enable)
|
|
||||||
.depth_clamp_enable(state.depth_clamp_enable);
|
|
||||||
|
|
||||||
if let Some(depth_bias) = state.depth_bias {
|
|
||||||
info = info
|
|
||||||
.depth_bias_enable(true)
|
|
||||||
.depth_bias_clamp(depth_bias.clamp)
|
|
||||||
.depth_bias_constant_factor(depth_bias.constant_factor)
|
|
||||||
.depth_bias_slope_factor(depth_bias.slope_factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
info
|
|
||||||
});
|
|
||||||
|
|
||||||
let multisample = desc.multisample.map(|state| {
|
|
||||||
let info = vk::PipelineMultisampleStateCreateInfo::default()
|
|
||||||
.flags(state.flags)
|
|
||||||
.min_sample_shading(state.min_sample_shading)
|
|
||||||
.rasterization_samples(state.rasterization_samples)
|
|
||||||
.sample_mask(state.sample_mask)
|
|
||||||
.sample_shading_enable(state.sample_shading_enable)
|
|
||||||
.alpha_to_coverage_enable(state.alpha_to_coverage_enable)
|
|
||||||
.alpha_to_one_enable(state.alpha_to_one_enable);
|
|
||||||
|
|
||||||
info
|
|
||||||
});
|
|
||||||
|
|
||||||
let color_blend = desc.color_blend.map(|state| {
|
|
||||||
let info = vk::PipelineColorBlendStateCreateInfo::default()
|
|
||||||
.flags(state.flags)
|
|
||||||
.attachments(state.attachments)
|
|
||||||
.blend_constants(state.blend_constants)
|
|
||||||
.logic_op(state.logic_op.unwrap_or(Default::default()))
|
|
||||||
.logic_op_enable(state.logic_op.is_some());
|
|
||||||
|
|
||||||
info
|
|
||||||
});
|
|
||||||
|
|
||||||
let depth_stencil = desc.depth_stencil.map(|state| {
|
|
||||||
let mut info =
|
|
||||||
vk::PipelineDepthStencilStateCreateInfo::default().flags(state.flags);
|
|
||||||
|
|
||||||
if let Some(depth) = state.depth {
|
|
||||||
info = info
|
|
||||||
.depth_compare_op(depth.compare_op.unwrap_or(vk::CompareOp::default()))
|
|
||||||
.depth_test_enable(depth.compare_op.is_some())
|
|
||||||
.depth_write_enable(depth.write_enable)
|
|
||||||
.depth_bounds_test_enable(depth.bounds.is_some());
|
|
||||||
if let Some(bounds) = depth.bounds {
|
|
||||||
info = info
|
|
||||||
.max_depth_bounds(bounds.max)
|
|
||||||
.min_depth_bounds(bounds.min);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(stencil) = state.stencil {
|
|
||||||
info = info
|
|
||||||
.stencil_test_enable(true)
|
|
||||||
.front(stencil.front)
|
|
||||||
.back(stencil.back);
|
|
||||||
}
|
|
||||||
|
|
||||||
info
|
|
||||||
});
|
|
||||||
|
|
||||||
let dynamic = desc.dynamic.map(|state| {
|
|
||||||
let info = vk::PipelineDynamicStateCreateInfo::default()
|
|
||||||
.flags(state.flags)
|
|
||||||
.dynamic_states(state.dynamic_states);
|
|
||||||
|
|
||||||
info
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut rendering = desc.rendering.map(|state| {
|
|
||||||
let info = vk::PipelineRenderingCreateInfo::default()
|
|
||||||
.color_attachment_formats(state.color_formats)
|
|
||||||
.depth_attachment_format(state.depth_format.unwrap_or_default())
|
|
||||||
.stencil_attachment_format(state.stencil_format.unwrap_or_default());
|
|
||||||
|
|
||||||
info
|
|
||||||
});
|
|
||||||
|
|
||||||
fn option_to_ptr<T>(option: &Option<T>) -> *const T {
|
|
||||||
option
|
|
||||||
.as_ref()
|
|
||||||
.map(|t| t as *const T)
|
|
||||||
.unwrap_or(core::ptr::null())
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut info = vk::GraphicsPipelineCreateInfo {
|
|
||||||
flags: desc.flags,
|
|
||||||
stage_count: stages.len() as u32,
|
|
||||||
p_stages: stages.as_ptr(),
|
|
||||||
p_vertex_input_state: option_to_ptr(&vertex_input),
|
|
||||||
p_input_assembly_state: option_to_ptr(&input_assembly),
|
|
||||||
p_tessellation_state: option_to_ptr(&tessellation),
|
|
||||||
p_viewport_state: option_to_ptr(&viewport),
|
|
||||||
p_rasterization_state: option_to_ptr(&rasterization),
|
|
||||||
p_multisample_state: option_to_ptr(&multisample),
|
|
||||||
p_depth_stencil_state: option_to_ptr(&depth_stencil),
|
|
||||||
p_color_blend_state: option_to_ptr(&color_blend),
|
|
||||||
p_dynamic_state: option_to_ptr(&dynamic),
|
|
||||||
layout: desc.layout.handle(),
|
|
||||||
render_pass: desc.render_pass.unwrap_or(vk::RenderPass::null()),
|
|
||||||
subpass: desc.subpass.unwrap_or(0),
|
|
||||||
base_pipeline_handle: desc
|
|
||||||
.base_pipeline
|
|
||||||
.map(|piepline| piepline.pipeline.handle())
|
|
||||||
.unwrap_or(vk::Pipeline::null()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(rendering) = rendering.as_mut() {
|
|
||||||
info = info.push_next(rendering)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
device.dev().create_graphics_pipelines(
|
|
||||||
vk::PipelineCache::null(),
|
|
||||||
core::slice::from_ref(&info),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let pipeline = match result {
|
|
||||||
Ok(pipelines) => pipelines[0],
|
|
||||||
Err((pipelines, error)) => {
|
|
||||||
tracing::error!("failed to create pipelines with :{error}");
|
|
||||||
for pipeline in pipelines {
|
|
||||||
unsafe {
|
|
||||||
device.dev().destroy_pipeline(pipeline, None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Err(error.into());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
pipeline: DeviceOwnedDebugObject::new(device, pipeline, name)?,
|
pipeline: DeviceObject::new_debug_named(device, pipeline, desc.name),
|
||||||
bind_point,
|
bind_point: vk::PipelineBindPoint::COMPUTE,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle(&self) -> vk::Pipeline {
|
pub fn new_graphics(device: Device, desc: GraphicsPipelineDesc) -> crate::Result<Self> {
|
||||||
self.pipeline.handle()
|
let stages = desc
|
||||||
|
.shader_stages
|
||||||
|
.iter()
|
||||||
|
.map(|stage| stage.as_create_info())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let vertex_input = desc.vertex_input.map(|vertex| {
|
||||||
|
vk::PipelineVertexInputStateCreateInfo::default()
|
||||||
|
.vertex_attribute_descriptions(vertex.attributes)
|
||||||
|
.vertex_binding_descriptions(vertex.bindings)
|
||||||
|
});
|
||||||
|
let input_assembly = desc.input_assembly.map(|state| {
|
||||||
|
vk::PipelineInputAssemblyStateCreateInfo::default()
|
||||||
|
.primitive_restart_enable(state.primitive_restart)
|
||||||
|
.topology(state.topology)
|
||||||
|
});
|
||||||
|
let tessellation = desc.tessellation.map(|state| {
|
||||||
|
vk::PipelineTessellationStateCreateInfo::default()
|
||||||
|
.flags(state.flags)
|
||||||
|
.patch_control_points(state.patch_control_points)
|
||||||
|
});
|
||||||
|
let viewport = desc.viewport.map(|state| {
|
||||||
|
let mut info = vk::PipelineViewportStateCreateInfo::default()
|
||||||
|
.scissor_count(state.num_scissors)
|
||||||
|
.viewport_count(state.num_viewports);
|
||||||
|
if let Some(viewports) = state.viewports {
|
||||||
|
info = info.viewports(viewports);
|
||||||
|
}
|
||||||
|
if let Some(scissors) = state.scissors {
|
||||||
|
info = info.scissors(scissors);
|
||||||
|
}
|
||||||
|
|
||||||
|
info
|
||||||
|
});
|
||||||
|
|
||||||
|
let rasterization = desc.rasterization.map(|state| {
|
||||||
|
let mut info = vk::PipelineRasterizationStateCreateInfo::default()
|
||||||
|
.line_width(state.line_width)
|
||||||
|
.cull_mode(state.cull_mode)
|
||||||
|
.polygon_mode(state.polygon_mode)
|
||||||
|
.rasterizer_discard_enable(state.discard_enable)
|
||||||
|
.depth_clamp_enable(state.depth_clamp_enable);
|
||||||
|
|
||||||
|
if let Some(depth_bias) = state.depth_bias {
|
||||||
|
info = info
|
||||||
|
.depth_bias_enable(true)
|
||||||
|
.depth_bias_clamp(depth_bias.clamp)
|
||||||
|
.depth_bias_constant_factor(depth_bias.constant_factor)
|
||||||
|
.depth_bias_slope_factor(depth_bias.slope_factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
info
|
||||||
|
});
|
||||||
|
|
||||||
|
let multisample = desc.multisample.map(|state| {
|
||||||
|
vk::PipelineMultisampleStateCreateInfo::default()
|
||||||
|
.flags(state.flags)
|
||||||
|
.min_sample_shading(state.min_sample_shading)
|
||||||
|
.rasterization_samples(state.rasterization_samples)
|
||||||
|
.sample_mask(state.sample_mask)
|
||||||
|
.sample_shading_enable(state.sample_shading_enable)
|
||||||
|
.alpha_to_coverage_enable(state.alpha_to_coverage_enable)
|
||||||
|
.alpha_to_one_enable(state.alpha_to_one_enable)
|
||||||
|
});
|
||||||
|
|
||||||
|
let color_blend = desc.color_blend.map(|state| {
|
||||||
|
vk::PipelineColorBlendStateCreateInfo::default()
|
||||||
|
.flags(state.flags)
|
||||||
|
.attachments(state.attachments)
|
||||||
|
.blend_constants(state.blend_constants)
|
||||||
|
.logic_op(state.logic_op.unwrap_or(Default::default()))
|
||||||
|
.logic_op_enable(state.logic_op.is_some())
|
||||||
|
});
|
||||||
|
|
||||||
|
let depth_stencil = desc.depth_stencil.map(|state| {
|
||||||
|
let mut info = vk::PipelineDepthStencilStateCreateInfo::default().flags(state.flags);
|
||||||
|
|
||||||
|
if let Some(depth) = state.depth {
|
||||||
|
info = info
|
||||||
|
.depth_compare_op(depth.compare_op.unwrap_or(vk::CompareOp::default()))
|
||||||
|
.depth_test_enable(depth.compare_op.is_some())
|
||||||
|
.depth_write_enable(depth.write_enable)
|
||||||
|
.depth_bounds_test_enable(depth.bounds.is_some());
|
||||||
|
if let Some(bounds) = depth.bounds {
|
||||||
|
info = info
|
||||||
|
.max_depth_bounds(bounds.max)
|
||||||
|
.min_depth_bounds(bounds.min);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(stencil) = state.stencil {
|
||||||
|
info = info
|
||||||
|
.stencil_test_enable(true)
|
||||||
|
.front(stencil.front)
|
||||||
|
.back(stencil.back);
|
||||||
|
}
|
||||||
|
|
||||||
|
info
|
||||||
|
});
|
||||||
|
|
||||||
|
let dynamic = desc.dynamic.map(|state| {
|
||||||
|
vk::PipelineDynamicStateCreateInfo::default()
|
||||||
|
.flags(state.flags)
|
||||||
|
.dynamic_states(state.dynamic_states)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut rendering = desc.rendering.map(|state| {
|
||||||
|
vk::PipelineRenderingCreateInfo::default()
|
||||||
|
.color_attachment_formats(state.color_formats)
|
||||||
|
.depth_attachment_format(state.depth_format.unwrap_or_default())
|
||||||
|
.stencil_attachment_format(state.stencil_format.unwrap_or_default())
|
||||||
|
});
|
||||||
|
|
||||||
|
fn option_to_ptr<T>(option: &Option<T>) -> *const T {
|
||||||
|
option
|
||||||
|
.as_ref()
|
||||||
|
.map(|t| t as *const T)
|
||||||
|
.unwrap_or(core::ptr::null())
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut info = vk::GraphicsPipelineCreateInfo {
|
||||||
|
flags: desc.flags,
|
||||||
|
stage_count: stages.len() as u32,
|
||||||
|
p_stages: stages.as_ptr(),
|
||||||
|
p_vertex_input_state: option_to_ptr(&vertex_input),
|
||||||
|
p_input_assembly_state: option_to_ptr(&input_assembly),
|
||||||
|
p_tessellation_state: option_to_ptr(&tessellation),
|
||||||
|
p_viewport_state: option_to_ptr(&viewport),
|
||||||
|
p_rasterization_state: option_to_ptr(&rasterization),
|
||||||
|
p_multisample_state: option_to_ptr(&multisample),
|
||||||
|
p_depth_stencil_state: option_to_ptr(&depth_stencil),
|
||||||
|
p_color_blend_state: option_to_ptr(&color_blend),
|
||||||
|
p_dynamic_state: option_to_ptr(&dynamic),
|
||||||
|
layout: desc.layout.raw(),
|
||||||
|
render_pass: desc.render_pass.unwrap_or(vk::RenderPass::null()),
|
||||||
|
subpass: desc.subpass.unwrap_or(0),
|
||||||
|
base_pipeline_handle: desc
|
||||||
|
.base_pipeline
|
||||||
|
.map(|piepline| *piepline.pipeline)
|
||||||
|
.unwrap_or(vk::Pipeline::null()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(rendering) = rendering.as_mut() {
|
||||||
|
info = info.push_next(rendering)
|
||||||
|
}
|
||||||
|
|
||||||
|
let pipeline = unsafe {
|
||||||
|
device
|
||||||
|
.dev()
|
||||||
|
.create_graphics_pipelines(
|
||||||
|
device.pools.pipeline_cache.raw,
|
||||||
|
core::slice::from_ref(&info),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
// It's cool to just take the first one and ignore any
|
||||||
|
// potentially created pipelines since we know there wont be any
|
||||||
|
// others.
|
||||||
|
.map_err(|(_, err)| err)?[0]
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
pipeline: DeviceObject::new_debug_named(device, pipeline, desc.name),
|
||||||
|
bind_point: vk::PipelineBindPoint::GRAPHICS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw(&self) -> vk::Pipeline {
|
||||||
|
*self.pipeline
|
||||||
}
|
}
|
||||||
pub fn bind_point(&self) -> vk::PipelineBindPoint {
|
pub fn bind_point(&self) -> vk::PipelineBindPoint {
|
||||||
self.bind_point
|
self.bind_point
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) mod pipeline_cache {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use ash::vk;
|
||||||
|
|
||||||
|
use ash::Device;
|
||||||
|
|
||||||
|
use crate::PhysicalDeviceInfo;
|
||||||
|
use crate::device::DeviceInner;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PipelineCache {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
key: u128,
|
||||||
|
pub(crate) raw: vk::PipelineCache,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::device::asdf::traits::ExternallyManagedObject<Arc<DeviceInner>> for PipelineCache {
|
||||||
|
unsafe fn destroy(self, owner: &Arc<DeviceInner>) {
|
||||||
|
tracing::info!("destroying pipeline cache with key {:x}", self.key);
|
||||||
|
if let Ok(data) = self.export(&owner.raw) {
|
||||||
|
tracing::info!(
|
||||||
|
"exported pipeline cache with key {:x} and size {} bytes",
|
||||||
|
self.key,
|
||||||
|
data.len()
|
||||||
|
);
|
||||||
|
_ = Self::write_to_disk(self.key, &data).inspect_err(|err| {
|
||||||
|
tracing::error!("failed to write pipeline cache to disk: {err}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
unsafe { owner.raw.destroy_pipeline_cache(self.raw, None) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PipelineCache {
|
||||||
|
const MAGIC: [u8; 4] = *b"VYPC";
|
||||||
|
const KEY_VERSION: u32 = 1;
|
||||||
|
const PATH: &'static str = "pipeline_cache.bin";
|
||||||
|
fn calculate_key(adapter: &PhysicalDeviceInfo) -> u128 {
|
||||||
|
use md5::Digest;
|
||||||
|
let mut hasher = md5::Md5::new();
|
||||||
|
let props = &adapter.properties;
|
||||||
|
hasher.update(bytemuck::bytes_of(&[
|
||||||
|
props.core.vendor_id,
|
||||||
|
props.core.api_version,
|
||||||
|
props.core.device_id,
|
||||||
|
props.core.driver_version,
|
||||||
|
]));
|
||||||
|
u128::from_le_bytes(hasher.finalize().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_from_disk(key: u128) -> Option<(u128, Vec<u8>)> {
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
let file = std::fs::File::open(Self::PATH).ok()?;
|
||||||
|
let mut reader = std::io::BufReader::new(file);
|
||||||
|
let mut magic = [0; 4];
|
||||||
|
reader.read_exact(&mut magic).ok()?;
|
||||||
|
if magic != Self::MAGIC {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut version = 0;
|
||||||
|
reader
|
||||||
|
.read_exact(bytemuck::bytes_of_mut(&mut version))
|
||||||
|
.ok()?;
|
||||||
|
if version != Self::KEY_VERSION {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut disk_key = 0;
|
||||||
|
reader
|
||||||
|
.read_exact(bytemuck::bytes_of_mut(&mut disk_key))
|
||||||
|
.ok()?;
|
||||||
|
if disk_key != key {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut data = Vec::new();
|
||||||
|
reader.read_to_end(&mut data).ok()?;
|
||||||
|
|
||||||
|
Some((key, data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to_disk(key: u128, data: &[u8]) -> std::io::Result<()> {
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
let file = std::fs::File::create(Self::PATH)?;
|
||||||
|
let mut writer = std::io::BufWriter::new(file);
|
||||||
|
writer.write_all(&Self::MAGIC)?;
|
||||||
|
writer.write_all(bytemuck::bytes_of(&Self::KEY_VERSION))?;
|
||||||
|
writer.write_all(bytemuck::bytes_of(&key))?;
|
||||||
|
writer.write_all(data)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(device: &Device, adapter: &PhysicalDeviceInfo) -> crate::Result<Self> {
|
||||||
|
let key = Self::calculate_key(adapter);
|
||||||
|
let data = Self::load_from_disk(key).map(|(key, data)| {
|
||||||
|
tracing::info!("loaded pipeline cache from disk with key {key:x}");
|
||||||
|
data
|
||||||
|
});
|
||||||
|
|
||||||
|
let info = vk::PipelineCacheCreateInfo::default()
|
||||||
|
// .flags(vk::PipelineCacheCreateFlags::EXTERNALLY_SYNCHRONIZED)
|
||||||
|
.initial_data(data.as_deref().unwrap_or_default());
|
||||||
|
|
||||||
|
let cache = unsafe { device.create_pipeline_cache(&info, None)? };
|
||||||
|
|
||||||
|
Ok(Self { key, raw: cache })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn export(&self, device: &ash::Device) -> crate::Result<Vec<u8>> {
|
||||||
|
let data = unsafe { device.get_pipeline_cache_data(self.raw)? };
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
355
crates/renderer/src/queue.rs
Normal file
355
crates/renderer/src/queue.rs
Normal file
|
|
@ -0,0 +1,355 @@
|
||||||
|
use bitflags::bitflags;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use raw_window_handle::RawDisplayHandle;
|
||||||
|
use std::{collections::HashMap, ops::Deref, sync::Arc};
|
||||||
|
use tinyvec::{ArrayVec, array_vec};
|
||||||
|
|
||||||
|
use ash::vk;
|
||||||
|
|
||||||
|
use crate::{Instance, PhysicalDeviceInfo, Result};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Queue {
|
||||||
|
inner: Arc<QueueInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Queue {
|
||||||
|
type Target = QueueInner;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Queue {
|
||||||
|
pub fn from_vk_queue(raw: vk::Queue, family: QueueFamily) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(QueueInner {
|
||||||
|
raw,
|
||||||
|
family,
|
||||||
|
lock: Mutex::new(()),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn with_locked<F, R>(&self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&Queue) -> R,
|
||||||
|
{
|
||||||
|
let _lock = self.inner.lock.lock();
|
||||||
|
f(&self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct QueueInner {
|
||||||
|
pub(crate) raw: vk::Queue,
|
||||||
|
pub(crate) family: QueueFamily,
|
||||||
|
pub(crate) lock: Mutex<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueueInner {
|
||||||
|
pub fn raw(&self) -> vk::Queue {
|
||||||
|
self.raw
|
||||||
|
}
|
||||||
|
pub fn family_index(&self) -> u32 {
|
||||||
|
self.family.index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct QueueFamily {
|
||||||
|
pub index: u32,
|
||||||
|
pub count: u32,
|
||||||
|
pub flags: QueueFlags,
|
||||||
|
pub granularity: TransferGranuality,
|
||||||
|
pub timestamp_valid_bits: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct QueueFlags: u16 {
|
||||||
|
const GRAPHICS = 0b1;
|
||||||
|
const COMPUTE = 0b10;
|
||||||
|
const TRANSFER = 0b100;
|
||||||
|
const PRESENT = 0b1000;
|
||||||
|
const ENCODE = 0b1_0000;
|
||||||
|
const DECODE = 0b10_0000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueueFlags {
|
||||||
|
pub fn from_vk_flags(vk_flags: vk::QueueFlags) -> Self {
|
||||||
|
let mut flags = QueueFlags::empty();
|
||||||
|
if vk_flags.contains(vk::QueueFlags::GRAPHICS) {
|
||||||
|
flags |= QueueFlags::GRAPHICS;
|
||||||
|
}
|
||||||
|
if vk_flags.contains(vk::QueueFlags::COMPUTE) {
|
||||||
|
flags |= QueueFlags::COMPUTE;
|
||||||
|
}
|
||||||
|
if vk_flags.contains(vk::QueueFlags::TRANSFER) {
|
||||||
|
flags |= QueueFlags::TRANSFER;
|
||||||
|
}
|
||||||
|
if vk_flags.contains(vk::QueueFlags::VIDEO_ENCODE_KHR) {
|
||||||
|
flags |= QueueFlags::ENCODE;
|
||||||
|
}
|
||||||
|
if vk_flags.contains(vk::QueueFlags::VIDEO_DECODE_KHR) {
|
||||||
|
flags |= QueueFlags::DECODE;
|
||||||
|
}
|
||||||
|
flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum TransferGranuality {
|
||||||
|
FullImage,
|
||||||
|
Unrestricted,
|
||||||
|
Tiled(u32, u32, u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransferGranuality {
|
||||||
|
pub fn from_vk_granularity(vk_granularity: vk::Extent3D) -> Self {
|
||||||
|
if vk_granularity.width == 0 || vk_granularity.height == 0 || vk_granularity.depth == 0 {
|
||||||
|
TransferGranuality::FullImage
|
||||||
|
} else if vk_granularity.width == 1
|
||||||
|
&& vk_granularity.height == 1
|
||||||
|
&& vk_granularity.depth == 1
|
||||||
|
{
|
||||||
|
TransferGranuality::Unrestricted
|
||||||
|
} else {
|
||||||
|
TransferGranuality::Tiled(
|
||||||
|
vk_granularity.width,
|
||||||
|
vk_granularity.height,
|
||||||
|
vk_granularity.depth,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DeviceQueues {
|
||||||
|
graphics: Queue,
|
||||||
|
compute: Queue,
|
||||||
|
transfer: Queue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceQueues {
|
||||||
|
pub fn graphics(&self) -> &Queue {
|
||||||
|
&self.graphics
|
||||||
|
}
|
||||||
|
pub fn compute(&self) -> &Queue {
|
||||||
|
&self.compute
|
||||||
|
}
|
||||||
|
pub fn transfer(&self) -> &Queue {
|
||||||
|
&self.transfer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn family_indices(&self, flags: crate::device::QueueFlags) -> ArrayVec<[u32; 4]> {
|
||||||
|
let mut indices = array_vec!([u32; 4]);
|
||||||
|
use crate::device::QueueFlags as QF;
|
||||||
|
if flags.intersects(QF::GRAPHICS | QF::PRESENT) {
|
||||||
|
indices.push(self.graphics.family.index);
|
||||||
|
}
|
||||||
|
if flags.contains(QF::ASYNC_COMPUTE) {
|
||||||
|
indices.push(self.compute.family.index);
|
||||||
|
}
|
||||||
|
if flags.contains(QF::TRANSFER) {
|
||||||
|
indices.push(self.transfer.family.index);
|
||||||
|
}
|
||||||
|
let len = indices.partition_dedup().0.len();
|
||||||
|
_ = indices.drain(len..);
|
||||||
|
indices
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn swapchain_family_indices(&self) -> &[u32] {
|
||||||
|
core::slice::from_ref(&self.graphics.family.index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller must ensure that the queues aren't already locked when calling this function.
|
||||||
|
pub unsafe fn lock(&self) {
|
||||||
|
core::mem::forget((
|
||||||
|
self.graphics.inner.lock.lock(),
|
||||||
|
self.compute.inner.lock.lock(),
|
||||||
|
self.transfer.inner.lock.lock(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller must have acquired and have logical ownership of the lock on the queues.
|
||||||
|
pub unsafe fn unlock(&self) {
|
||||||
|
unsafe {
|
||||||
|
self.graphics.inner.lock.force_unlock();
|
||||||
|
self.compute.inner.lock.force_unlock();
|
||||||
|
self.transfer.inner.lock.force_unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DeviceQueueInfos {
|
||||||
|
graphics: QueueFamily,
|
||||||
|
compute: Option<QueueFamily>,
|
||||||
|
transfer: Option<QueueFamily>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceQueueInfos {
|
||||||
|
const PRIORITIES: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
|
||||||
|
pub fn into_create_infos(&self) -> Vec<vk::DeviceQueueCreateInfo<'_>> {
|
||||||
|
let families = self.queue_family_indices();
|
||||||
|
|
||||||
|
families
|
||||||
|
.into_iter()
|
||||||
|
.map(|(index, count)| {
|
||||||
|
vk::DeviceQueueCreateInfo::default()
|
||||||
|
.queue_family_index(index)
|
||||||
|
.queue_priorities(&Self::PRIORITIES[..count as usize])
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queue_family_indices(&self) -> HashMap<u32, u32> {
|
||||||
|
let mut families = HashMap::new();
|
||||||
|
|
||||||
|
families.insert(self.graphics.index, 1);
|
||||||
|
if let Some(compute) = self.compute {
|
||||||
|
*families.entry(compute.index).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
if let Some(transfer) = self.transfer {
|
||||||
|
*families.entry(transfer.index).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
families
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn retrieve_queues(self, dev: &ash::Device) -> DeviceQueues {
|
||||||
|
let families = self.queue_family_indices();
|
||||||
|
|
||||||
|
let mut queues = families
|
||||||
|
.into_iter()
|
||||||
|
.map(|(queue_family_index, count)| {
|
||||||
|
let queues = (0..count)
|
||||||
|
.map(|queue_index| unsafe {
|
||||||
|
dev.get_device_queue(queue_family_index, queue_index)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
(queue_family_index, queues)
|
||||||
|
})
|
||||||
|
.collect::<HashMap<u32, Vec<vk::Queue>>>();
|
||||||
|
|
||||||
|
let graphics = Queue::from_vk_queue(
|
||||||
|
queues.get_mut(&self.graphics.index).unwrap().pop().unwrap(),
|
||||||
|
self.graphics,
|
||||||
|
);
|
||||||
|
let compute = self
|
||||||
|
.compute
|
||||||
|
.map(|compute| {
|
||||||
|
Queue::from_vk_queue(
|
||||||
|
queues.get_mut(&compute.index).unwrap().pop().unwrap(),
|
||||||
|
compute,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| graphics.clone());
|
||||||
|
let transfer = self
|
||||||
|
.transfer
|
||||||
|
.map(|transfer| {
|
||||||
|
Queue::from_vk_queue(
|
||||||
|
queues.get_mut(&transfer.index).unwrap().pop().unwrap(),
|
||||||
|
transfer,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| graphics.clone());
|
||||||
|
|
||||||
|
DeviceQueues {
|
||||||
|
graphics,
|
||||||
|
compute,
|
||||||
|
transfer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_queue_families(
|
||||||
|
instance: &Instance,
|
||||||
|
pdev: &PhysicalDeviceInfo,
|
||||||
|
display_handle: Option<RawDisplayHandle>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let queue_families = unsafe {
|
||||||
|
instance
|
||||||
|
.inner
|
||||||
|
.raw
|
||||||
|
.get_physical_device_queue_family_properties(pdev.pdev)
|
||||||
|
};
|
||||||
|
|
||||||
|
let queue_families = queue_families
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, props)| {
|
||||||
|
let mut flags = QueueFlags::from_vk_flags(props.queue_flags);
|
||||||
|
if let Some(display_handle) = display_handle {
|
||||||
|
if instance.query_presentation_support(pdev.pdev, index as u32, display_handle)
|
||||||
|
{
|
||||||
|
flags |= QueueFlags::PRESENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QueueFamily {
|
||||||
|
index: index as u32,
|
||||||
|
count: props.queue_count,
|
||||||
|
flags,
|
||||||
|
granularity: TransferGranuality::from_vk_granularity(
|
||||||
|
props.min_image_transfer_granularity,
|
||||||
|
),
|
||||||
|
timestamp_valid_bits: props.timestamp_valid_bits,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut queue_counts = queue_families
|
||||||
|
.iter()
|
||||||
|
.map(|family| family.count)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let graphics = queue_families
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, family)| {
|
||||||
|
family
|
||||||
|
.flags
|
||||||
|
.contains(QueueFlags::GRAPHICS | QueueFlags::COMPUTE)
|
||||||
|
})
|
||||||
|
.map(|(i, family)| {
|
||||||
|
queue_counts[i] -= 1;
|
||||||
|
*family
|
||||||
|
})
|
||||||
|
.expect("No graphics queue family found");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
graphics.flags.contains(QueueFlags::PRESENT),
|
||||||
|
"Selected graphics queue family does not support presentation"
|
||||||
|
);
|
||||||
|
|
||||||
|
let compute = queue_families
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(i, family)| queue_counts[*i] > 0 && family.flags.contains(QueueFlags::COMPUTE))
|
||||||
|
.map(|(i, family)| {
|
||||||
|
queue_counts[i] -= 1;
|
||||||
|
*family
|
||||||
|
});
|
||||||
|
|
||||||
|
let transfer = queue_families
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(i, family)| queue_counts[*i] > 0 && family.flags.contains(QueueFlags::TRANSFER))
|
||||||
|
.map(|(i, family)| {
|
||||||
|
queue_counts[i] -= 1;
|
||||||
|
*family
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
graphics,
|
||||||
|
compute,
|
||||||
|
transfer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,8 +8,9 @@ use std::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
buffers::{Buffer, BufferDesc},
|
buffers::{Buffer, BufferDesc},
|
||||||
commands, def_monotonic_id,
|
commands::{self, traits::CommandBufferExt},
|
||||||
device::{self, DeviceOwned},
|
def_monotonic_id,
|
||||||
|
device::{self},
|
||||||
images::{self, Image, ImageDesc},
|
images::{self, Image, ImageDesc},
|
||||||
util::{self, Rgba, WithLifetime},
|
util::{self, Rgba, WithLifetime},
|
||||||
};
|
};
|
||||||
|
|
@ -61,13 +62,11 @@ impl GraphResource {
|
||||||
discr.hash(&mut state);
|
discr.hash(&mut state);
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
GraphResource::Framebuffer(swapchain_frame) => {
|
GraphResource::Framebuffer(swapchain_frame) => (swapchain_frame.raw()).hash(&mut state),
|
||||||
(swapchain_frame.handle()).hash(&mut state)
|
GraphResource::ImportedImage(image) => image.raw().hash(&mut state),
|
||||||
}
|
GraphResource::ImportedBuffer(buffer) => buffer.raw().hash(&mut state),
|
||||||
GraphResource::ImportedImage(image) => image.handle().hash(&mut state),
|
GraphResource::Image(image) => image.raw().hash(&mut state),
|
||||||
GraphResource::ImportedBuffer(buffer) => buffer.handle().hash(&mut state),
|
GraphResource::Buffer(buffer) => buffer.raw().hash(&mut state),
|
||||||
GraphResource::Image(image) => image.handle().hash(&mut state),
|
|
||||||
GraphResource::Buffer(buffer) => buffer.handle().hash(&mut state),
|
|
||||||
GraphResource::ImageDesc(image_desc) => image_desc.hash(&mut state),
|
GraphResource::ImageDesc(image_desc) => image_desc.hash(&mut state),
|
||||||
GraphResource::BufferDesc(buffer_desc) => buffer_desc.hash(&mut state),
|
GraphResource::BufferDesc(buffer_desc) => buffer_desc.hash(&mut state),
|
||||||
GraphResource::Default => {}
|
GraphResource::Default => {}
|
||||||
|
|
@ -460,8 +459,8 @@ impl RenderGraph {
|
||||||
refmap.allocate_ref_ranges(&self.pass_descs);
|
refmap.allocate_ref_ranges(&self.pass_descs);
|
||||||
refmap.ref_passes(&self.pass_descs);
|
refmap.ref_passes(&self.pass_descs);
|
||||||
let dag = refmap.build_dag();
|
let dag = refmap.build_dag();
|
||||||
let topo = refmap.toposort_dag(dag);
|
|
||||||
topo
|
refmap.toposort_dag(dag)
|
||||||
});
|
});
|
||||||
|
|
||||||
// create internal resources:
|
// create internal resources:
|
||||||
|
|
@ -485,15 +484,15 @@ impl RenderGraph {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ash::prelude::VkResult::Ok(())
|
crate::Result::Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let pool =
|
let pool =
|
||||||
commands::SingleUseCommandPool::new(device.clone(), device.graphics_queue().clone())?;
|
commands::SingleUseCommandPool::new(device.clone(), device.main_queue().clone())?;
|
||||||
|
|
||||||
let resources = &self.resources;
|
let resources = &self.resources;
|
||||||
let cmds = topo
|
let cmds = topo
|
||||||
.iter()
|
.into_iter()
|
||||||
.rev()
|
.rev()
|
||||||
.map(|(passes, accesses)| {
|
.map(|(passes, accesses)| {
|
||||||
let passes = passes
|
let passes = passes
|
||||||
|
|
@ -507,10 +506,11 @@ impl RenderGraph {
|
||||||
let cmd = pool.alloc()?;
|
let cmd = pool.alloc()?;
|
||||||
// transitions
|
// transitions
|
||||||
for (&id, &(from, to)) in accesses.iter() {
|
for (&id, &(from, to)) in accesses.iter() {
|
||||||
|
let buffer = unsafe { cmd.buffer() };
|
||||||
Self::transition_resource(
|
Self::transition_resource(
|
||||||
&resources[id.0 as usize],
|
&resources[id.0 as usize],
|
||||||
device.dev(),
|
device.dev(),
|
||||||
unsafe { &cmd.buffer() },
|
&buffer,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
);
|
);
|
||||||
|
|
@ -541,13 +541,10 @@ impl RenderGraph {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_outputs(&mut self) -> BTreeMap<GraphResourceId, GraphResource> {
|
pub fn get_outputs(&mut self) -> BTreeMap<GraphResourceId, GraphResource> {
|
||||||
let outputs = self
|
self.outputs
|
||||||
.outputs
|
.keys()
|
||||||
.iter()
|
.map(|id| (*id, core::mem::take(&mut self.resources[id.0 as usize])))
|
||||||
.map(|(id, _)| (*id, core::mem::take(&mut self.resources[id.0 as usize])))
|
.collect::<BTreeMap<_, _>>()
|
||||||
.collect::<BTreeMap<_, _>>();
|
|
||||||
|
|
||||||
outputs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transition_resource(
|
pub fn transition_resource(
|
||||||
|
|
@ -559,19 +556,19 @@ impl RenderGraph {
|
||||||
) {
|
) {
|
||||||
let barrier: Barrier = match res {
|
let barrier: Barrier = match res {
|
||||||
GraphResource::Framebuffer(arc) => {
|
GraphResource::Framebuffer(arc) => {
|
||||||
image_barrier(arc.handle(), arc.format(), from, to, None).into()
|
image_barrier(arc.raw(), arc.format(), from, to, None).into()
|
||||||
}
|
}
|
||||||
GraphResource::ImportedImage(arc) => {
|
GraphResource::ImportedImage(arc) => {
|
||||||
image_barrier(arc.handle(), arc.format(), from, to, None).into()
|
image_barrier(arc.raw(), arc.format(), from, to, None).into()
|
||||||
}
|
}
|
||||||
GraphResource::ImportedBuffer(arc) => {
|
GraphResource::ImportedBuffer(arc) => {
|
||||||
buffer_barrier(arc.handle(), 0, arc.len(), from, to, None).into()
|
buffer_barrier(arc.raw(), 0, arc.len(), from, to, None).into()
|
||||||
}
|
}
|
||||||
GraphResource::Image(image) => {
|
GraphResource::Image(image) => {
|
||||||
image_barrier(image.handle(), image.format(), from, to, None).into()
|
image_barrier(image.raw(), image.format(), from, to, None).into()
|
||||||
}
|
}
|
||||||
GraphResource::Buffer(buffer) => {
|
GraphResource::Buffer(buffer) => {
|
||||||
buffer_barrier(buffer.handle(), 0, buffer.len(), from, to, None).into()
|
buffer_barrier(buffer.raw(), 0, buffer.len(), from, to, None).into()
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
|
|
@ -682,7 +679,7 @@ mod graph_resolver {
|
||||||
Self {
|
Self {
|
||||||
num_resources,
|
num_resources,
|
||||||
num_passes,
|
num_passes,
|
||||||
references: vec![0; ((num_passes) * num_resources).div_ceil(64) as usize],
|
references: vec![0; (num_passes * num_resources).div_ceil(64)],
|
||||||
ref_ranges: Vec::new(),
|
ref_ranges: Vec::new(),
|
||||||
ref_accesses: Vec::new(),
|
ref_accesses: Vec::new(),
|
||||||
ref_access_passid: Vec::new(),
|
ref_access_passid: Vec::new(),
|
||||||
|
|
@ -1034,31 +1031,31 @@ mod graph_resolver {
|
||||||
dag.remove_node(sink);
|
dag.remove_node(sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(debug_assertions, test))]
|
// #[cfg(any(debug_assertions, test))]
|
||||||
std::fs::write(
|
// std::fs::write(
|
||||||
"render_graph2.dot",
|
// "render_graph2.dot",
|
||||||
&format!(
|
// &format!(
|
||||||
"{:?}",
|
// "{:?}",
|
||||||
petgraph::dot::Dot::with_attr_getters(
|
// petgraph::dot::Dot::with_attr_getters(
|
||||||
&dag,
|
// &dag,
|
||||||
&[],
|
// &[],
|
||||||
&|_graph, edgeref| {
|
// &|_graph, edgeref| {
|
||||||
format!(
|
// format!(
|
||||||
"label = \"{},{:#?}\"",
|
// "label = \"{},{:#?}\"",
|
||||||
edgeref.weight().0,
|
// edgeref.weight().0,
|
||||||
edgeref.weight().1,
|
// edgeref.weight().1,
|
||||||
)
|
// )
|
||||||
},
|
// },
|
||||||
&|_graph, noderef| {
|
// &|_graph, noderef| {
|
||||||
format!(
|
// format!(
|
||||||
"label = \"Pass({:?})\"",
|
// "label = \"Pass({:?})\"",
|
||||||
petgraph::visit::NodeRef::weight(&noderef)
|
// petgraph::visit::NodeRef::weight(&noderef)
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
)
|
// )
|
||||||
),
|
// ),
|
||||||
)
|
// )
|
||||||
.expect("writing render_graph repr");
|
// .expect("writing render_graph repr");
|
||||||
|
|
||||||
dag
|
dag
|
||||||
}
|
}
|
||||||
|
|
@ -1295,7 +1292,7 @@ pub fn clear_pass(rg: &mut RenderGraph, color: Rgba, target: GraphResourceId) {
|
||||||
let cmd = &ctx.cmd;
|
let cmd = &ctx.cmd;
|
||||||
|
|
||||||
cmd.clear_color_image(
|
cmd.clear_color_image(
|
||||||
target.handle(),
|
target.raw(),
|
||||||
target.format(),
|
target.format(),
|
||||||
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||||
color,
|
color,
|
||||||
|
|
|
||||||
|
|
@ -1 +1,383 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use ash::vk;
|
||||||
|
use glam::{f32::Mat4, vec3};
|
||||||
|
use gpu_allocator::MemoryLocation;
|
||||||
|
|
||||||
|
pub use crate::egui_pass::{egui_pass, egui_pre_pass};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Result,
|
||||||
|
buffers::{Buffer, BufferDesc},
|
||||||
|
commands::{self, traits::CommandBufferExt},
|
||||||
|
device::Device,
|
||||||
|
images::ImageViewDesc,
|
||||||
|
pipeline,
|
||||||
|
render_graph::{
|
||||||
|
Access, GraphResourceId, PassDesc, RecordFn, RenderContext, RenderGraph, buffer_barrier,
|
||||||
|
},
|
||||||
|
sync,
|
||||||
|
util::Rgba8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Wireframe {
|
||||||
|
positions: Arc<Buffer>,
|
||||||
|
indices: Arc<Buffer>,
|
||||||
|
num_indices: u32,
|
||||||
|
colors: Arc<Buffer>,
|
||||||
|
pipeline: Arc<pipeline::Pipeline>,
|
||||||
|
layout: Arc<pipeline::PipelineLayout>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wireframe {
|
||||||
|
pub async fn from_triangles_indexed(
|
||||||
|
dev: Device,
|
||||||
|
positions: Vec<glam::Vec3>,
|
||||||
|
indices: Vec<u32>,
|
||||||
|
colors: Vec<Rgba8>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let positions_size = positions.len() * size_of::<glam::Vec3>();
|
||||||
|
let num_indices = indices.len() as u32;
|
||||||
|
let indices_size = indices.len() * size_of::<u32>();
|
||||||
|
let indices_offset = positions_size;
|
||||||
|
let colors_size = colors.len() * size_of::<Rgba8>();
|
||||||
|
let colors_offset = indices_offset + indices_size;
|
||||||
|
|
||||||
|
let staging_size = positions_size + indices_size + colors_size;
|
||||||
|
|
||||||
|
let mut staging = Buffer::new(
|
||||||
|
dev.clone(),
|
||||||
|
BufferDesc {
|
||||||
|
name: Some("wireframe-staging".into()),
|
||||||
|
size: staging_size as u64,
|
||||||
|
usage: vk::BufferUsageFlags::TRANSFER_SRC,
|
||||||
|
mem_location: MemoryLocation::CpuToGpu,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let map = staging
|
||||||
|
.map_mut()
|
||||||
|
.expect("staging buffer should be host visible");
|
||||||
|
|
||||||
|
map[..positions_size].copy_from_slice(bytemuck::cast_slice(&positions));
|
||||||
|
map[indices_offset..][..indices_size].copy_from_slice(bytemuck::cast_slice(&indices));
|
||||||
|
map[colors_offset..][..colors_size].copy_from_slice(bytemuck::cast_slice(&colors));
|
||||||
|
}
|
||||||
|
|
||||||
|
let positions = Buffer::new(
|
||||||
|
dev.clone(),
|
||||||
|
BufferDesc {
|
||||||
|
name: Some("wireframe-positions".into()),
|
||||||
|
size: positions_size as u64,
|
||||||
|
usage: vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::VERTEX_BUFFER,
|
||||||
|
mem_location: MemoryLocation::GpuOnly,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
let indices = Buffer::new(
|
||||||
|
dev.clone(),
|
||||||
|
BufferDesc {
|
||||||
|
name: Some("wireframe-indices".into()),
|
||||||
|
size: indices_size as u64,
|
||||||
|
usage: vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::INDEX_BUFFER,
|
||||||
|
mem_location: MemoryLocation::GpuOnly,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
let colors = Buffer::new(
|
||||||
|
dev.clone(),
|
||||||
|
BufferDesc {
|
||||||
|
name: Some("wireframe-colors".into()),
|
||||||
|
size: colors_size as u64,
|
||||||
|
usage: vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::VERTEX_BUFFER,
|
||||||
|
mem_location: MemoryLocation::GpuOnly,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let pool = commands::SingleUseCommandPool::new(dev.clone(), dev.main_queue().clone())?;
|
||||||
|
|
||||||
|
let cmd = pool.alloc()?;
|
||||||
|
|
||||||
|
cmd.copy_buffers(
|
||||||
|
staging.raw(),
|
||||||
|
positions.raw(),
|
||||||
|
&[vk::BufferCopy {
|
||||||
|
src_offset: 0,
|
||||||
|
dst_offset: 0,
|
||||||
|
size: positions_size as u64,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
cmd.copy_buffers(
|
||||||
|
staging.raw(),
|
||||||
|
indices.raw(),
|
||||||
|
&[vk::BufferCopy {
|
||||||
|
src_offset: indices_offset as u64,
|
||||||
|
dst_offset: 0,
|
||||||
|
size: indices_size as u64,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
cmd.copy_buffers(
|
||||||
|
staging.raw(),
|
||||||
|
colors.raw(),
|
||||||
|
&[vk::BufferCopy {
|
||||||
|
src_offset: colors_offset as u64,
|
||||||
|
dst_offset: 0,
|
||||||
|
size: colors_size as u64,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
|
||||||
|
let barriers = [
|
||||||
|
buffer_barrier(
|
||||||
|
positions.raw(),
|
||||||
|
0,
|
||||||
|
positions.len(),
|
||||||
|
Access::transfer_write(),
|
||||||
|
Access::vertex_read(),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
buffer_barrier(
|
||||||
|
indices.raw(),
|
||||||
|
0,
|
||||||
|
indices.len(),
|
||||||
|
Access::transfer_write(),
|
||||||
|
Access::index_read(),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
buffer_barrier(
|
||||||
|
colors.raw(),
|
||||||
|
0,
|
||||||
|
colors.len(),
|
||||||
|
Access::transfer_write(),
|
||||||
|
Access::vertex_read(),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
unsafe {
|
||||||
|
dev.dev().cmd_pipeline_barrier2(
|
||||||
|
cmd.buffer(),
|
||||||
|
&vk::DependencyInfo::default().buffer_memory_barriers(&barriers),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let future = cmd.submit_async(
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Arc::new(sync::Fence::from_pool(&dev.pools.fences, None)?),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let (pipeline, layout) = Self::create_pipeline(dev.clone())?;
|
||||||
|
|
||||||
|
future.await;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
positions: Arc::new(positions),
|
||||||
|
indices: Arc::new(indices),
|
||||||
|
colors: Arc::new(colors),
|
||||||
|
pipeline: Arc::new(pipeline),
|
||||||
|
layout: Arc::new(layout),
|
||||||
|
num_indices,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_pipeline(device: Device) -> Result<(pipeline::Pipeline, pipeline::PipelineLayout)> {
|
||||||
|
let pipeline_layout = pipeline::PipelineLayout::new(
|
||||||
|
device.clone(),
|
||||||
|
pipeline::PipelineLayoutDesc {
|
||||||
|
descriptor_set_layouts: &[],
|
||||||
|
push_constant_ranges: &[vk::PushConstantRange {
|
||||||
|
offset: 0,
|
||||||
|
size: 128,
|
||||||
|
stage_flags: vk::ShaderStageFlags::VERTEX,
|
||||||
|
}],
|
||||||
|
name: Some("wireframe-pipeline-layout".into()),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let shader = pipeline::ShaderModule::new_from_path(
|
||||||
|
device.clone(),
|
||||||
|
"crates/renderer/shaders/wireframe.spv",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let pipeline = pipeline::Pipeline::new_graphics(
|
||||||
|
device.clone(),
|
||||||
|
pipeline::GraphicsPipelineDesc {
|
||||||
|
flags: Default::default(),
|
||||||
|
name: Some("wireframe-pipeline".into()),
|
||||||
|
shader_stages: &[
|
||||||
|
pipeline::ShaderStageDesc {
|
||||||
|
flags: vk::PipelineShaderStageCreateFlags::empty(),
|
||||||
|
module: &shader,
|
||||||
|
stage: vk::ShaderStageFlags::FRAGMENT,
|
||||||
|
entry: c"main".into(),
|
||||||
|
},
|
||||||
|
pipeline::ShaderStageDesc {
|
||||||
|
flags: vk::PipelineShaderStageCreateFlags::empty(),
|
||||||
|
module: &shader,
|
||||||
|
stage: vk::ShaderStageFlags::VERTEX,
|
||||||
|
entry: c"main".into(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
render_pass: None,
|
||||||
|
layout: &pipeline_layout,
|
||||||
|
subpass: None,
|
||||||
|
base_pipeline: None,
|
||||||
|
vertex_input: Some(pipeline::VertexInputState {
|
||||||
|
bindings: &[
|
||||||
|
vk::VertexInputBindingDescription {
|
||||||
|
binding: 0,
|
||||||
|
stride: size_of::<glam::Vec3>() as u32,
|
||||||
|
input_rate: vk::VertexInputRate::VERTEX,
|
||||||
|
},
|
||||||
|
vk::VertexInputBindingDescription {
|
||||||
|
binding: 1,
|
||||||
|
stride: size_of::<Rgba8>() as u32,
|
||||||
|
input_rate: vk::VertexInputRate::VERTEX,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
attributes: &[
|
||||||
|
vk::VertexInputAttributeDescription {
|
||||||
|
location: 0,
|
||||||
|
binding: 0,
|
||||||
|
format: vk::Format::R32G32B32_SFLOAT,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
vk::VertexInputAttributeDescription {
|
||||||
|
location: 1,
|
||||||
|
binding: 1,
|
||||||
|
format: vk::Format::R8G8B8A8_UNORM,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
input_assembly: Some(pipeline::InputAssemblyState {
|
||||||
|
topology: vk::PrimitiveTopology::TRIANGLE_LIST,
|
||||||
|
primitive_restart: false,
|
||||||
|
}),
|
||||||
|
tessellation: None,
|
||||||
|
viewport: Some(pipeline::ViewportState {
|
||||||
|
num_viewports: 1,
|
||||||
|
num_scissors: 1,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
rasterization: Some(pipeline::RasterizationState {
|
||||||
|
cull_mode: vk::CullModeFlags::NONE,
|
||||||
|
polygon_mode: vk::PolygonMode::LINE,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
multisample: Some(pipeline::MultisampleState {
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
depth_stencil: Some(pipeline::DepthStencilState {
|
||||||
|
depth: Some(pipeline::DepthState {
|
||||||
|
write_enable: false,
|
||||||
|
compare_op: Some(vk::CompareOp::LESS),
|
||||||
|
bounds: Some(pipeline::DepthBounds { min: 0.0, max: 1.0 }),
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
color_blend: Some(pipeline::ColorBlendState {
|
||||||
|
attachments: &[vk::PipelineColorBlendAttachmentState::default()
|
||||||
|
.color_write_mask(vk::ColorComponentFlags::RGBA)
|
||||||
|
.blend_enable(true)
|
||||||
|
.color_blend_op(vk::BlendOp::ADD)
|
||||||
|
.src_color_blend_factor(vk::BlendFactor::ONE)
|
||||||
|
.dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
|
||||||
|
.alpha_blend_op(vk::BlendOp::ADD)
|
||||||
|
.src_alpha_blend_factor(vk::BlendFactor::ONE)
|
||||||
|
.dst_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)],
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
rendering: Some(pipeline::RenderingState {
|
||||||
|
color_formats: &[vk::Format::R8G8B8A8_UNORM],
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
dynamic: Some(pipeline::DynamicState {
|
||||||
|
dynamic_states: &[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR],
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok((pipeline, pipeline_layout))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pass(&self, rg: &mut RenderGraph, target: GraphResourceId) -> Result<()> {
|
||||||
|
let reads = [(target, Access::color_attachment_read_write())].to_vec();
|
||||||
|
let writes = [(target, Access::color_attachment_write_only())].to_vec();
|
||||||
|
|
||||||
|
let record: Box<RecordFn> = Box::new({
|
||||||
|
let positions = self.positions.clone();
|
||||||
|
let indices = self.indices.clone();
|
||||||
|
let colors = self.colors.clone();
|
||||||
|
let num_indices = self.num_indices;
|
||||||
|
|
||||||
|
let pipeline = self.pipeline.clone();
|
||||||
|
let layout = self.layout.clone();
|
||||||
|
|
||||||
|
move |ctx: &RenderContext| -> Result<()> {
|
||||||
|
let target = ctx.get_image(target).unwrap();
|
||||||
|
let cmd = &ctx.cmd;
|
||||||
|
|
||||||
|
//let model = Mat4::from_scale(vec3(0.01, 0.01, 0.01));
|
||||||
|
let proj = Mat4::perspective_lh(
|
||||||
|
core::f32::consts::FRAC_PI_4,
|
||||||
|
target.width() as f32 / target.height() as f32,
|
||||||
|
0.1,
|
||||||
|
100.0,
|
||||||
|
);
|
||||||
|
let pos = vec3(0.0, 0.0, -5.0);
|
||||||
|
let view = Mat4::look_at_rh(pos, pos + vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0));
|
||||||
|
let to_clip = proj * view; // * model;
|
||||||
|
|
||||||
|
let color_attachment = &vk::RenderingAttachmentInfo::default()
|
||||||
|
.image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||||
|
.image_view(target.create_view(ImageViewDesc::color_2d())?.raw())
|
||||||
|
.load_op(vk::AttachmentLoadOp::LOAD)
|
||||||
|
.store_op(vk::AttachmentStoreOp::STORE);
|
||||||
|
|
||||||
|
cmd.begin_rendering(
|
||||||
|
vk::RenderingInfo::default()
|
||||||
|
.color_attachments(core::slice::from_ref(color_attachment))
|
||||||
|
.layer_count(1)
|
||||||
|
.render_area(vk::Rect2D::default().extent(target.extent_2d())),
|
||||||
|
);
|
||||||
|
|
||||||
|
cmd.set_scissors(&[vk::Rect2D::default()
|
||||||
|
.offset(vk::Offset2D::default())
|
||||||
|
.extent(target.extent_2d())]);
|
||||||
|
|
||||||
|
cmd.set_viewport(&[vk::Viewport::default()
|
||||||
|
.x(0.0)
|
||||||
|
.y(0.0)
|
||||||
|
.min_depth(0.0)
|
||||||
|
.max_depth(1.0)
|
||||||
|
.width(target.width() as f32)
|
||||||
|
.height(target.height() as f32)]);
|
||||||
|
|
||||||
|
cmd.bind_pipeline(&pipeline);
|
||||||
|
cmd.bind_indices(indices.raw(), 0, vk::IndexType::UINT32);
|
||||||
|
cmd.bind_vertex_buffers(&[positions.raw(), colors.raw()], &[0, 0]);
|
||||||
|
cmd.push_constants(
|
||||||
|
&layout,
|
||||||
|
vk::ShaderStageFlags::VERTEX,
|
||||||
|
0,
|
||||||
|
bytemuck::cast_slice(&[to_clip]),
|
||||||
|
);
|
||||||
|
cmd.draw_indexed(num_indices, 1, 0, 0, 0);
|
||||||
|
cmd.end_rendering();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rg.add_pass(PassDesc {
|
||||||
|
reads,
|
||||||
|
writes,
|
||||||
|
record: Some(record),
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +1,21 @@
|
||||||
use std::{
|
use std::{
|
||||||
future::Future,
|
future::Future,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
sync::{atomic::AtomicU32, Arc},
|
sync::{Arc, atomic::AtomicU32},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::device::{
|
||||||
|
DevicePools, Pool, PoolObject, Pooled,
|
||||||
|
asdf::{
|
||||||
|
DeviceObject, InnerDeviceObject,
|
||||||
|
traits::ExternallyManagedObject as ExternallyManagedObjectTrait,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use crate::{Result, device::DeviceInner};
|
||||||
|
|
||||||
use super::Device;
|
use super::Device;
|
||||||
use ash::{prelude::*, vk};
|
use ash::{prelude::VkResult, vk};
|
||||||
use crossbeam::channel::{Receiver, Sender};
|
use crossbeam::channel::{Receiver, Sender};
|
||||||
|
|
||||||
type Message = (SyncPrimitive, std::task::Waker);
|
type Message = (SyncPrimitive, std::task::Waker);
|
||||||
|
|
@ -19,6 +28,12 @@ pub struct SyncThreadpool {
|
||||||
num_threads: Arc<AtomicU32>,
|
num_threads: Arc<AtomicU32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for SyncThreadpool {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum SyncPrimitive {
|
enum SyncPrimitive {
|
||||||
Fence(Arc<Fence>),
|
Fence(Arc<Fence>),
|
||||||
|
|
@ -143,111 +158,309 @@ impl SyncThreadpool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Semaphore {
|
pub enum Fence {
|
||||||
device: Device,
|
Dedicated {
|
||||||
inner: vk::Semaphore,
|
fence: InnerDeviceObject<vk::Fence>,
|
||||||
|
},
|
||||||
|
Pooled {
|
||||||
|
fence: PoolObject<vk::Fence, Arc<Pool<vk::Fence>>>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Fence {
|
impl Pooled for vk::Fence {
|
||||||
dev: Device,
|
fn create_from_pool(pool: &Pool<Self>) -> Result<Self> {
|
||||||
fence: vk::Fence,
|
let fence = unsafe {
|
||||||
|
pool.device
|
||||||
|
.raw
|
||||||
|
.create_fence(&vk::FenceCreateInfo::default(), None)?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(fence)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExternallyManagedObjectTrait<Arc<Pool<vk::Fence>>> for vk::Fence {
|
||||||
|
unsafe fn destroy(self, pool: &Arc<Pool<vk::Fence>>) {
|
||||||
|
pool.push(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<DeviceInner>> ExternallyManagedObjectTrait<T> for vk::Fence {
|
||||||
|
unsafe fn destroy(self, device: &T) {
|
||||||
|
unsafe {
|
||||||
|
device.as_ref().raw.destroy_fence(self, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Fence {
|
impl std::fmt::Debug for Fence {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Fence").field("fence", &self.fence).finish()
|
f.debug_struct("Fence").field("fence", &self.raw()).finish()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Fence {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
self.dev.dev().destroy_fence(self.fence, None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fence {
|
impl Fence {
|
||||||
unsafe fn new(dev: Device, fence: vk::Fence) -> Fence {
|
pub fn new_dedicated(device: Device, name: Option<&'static str>) -> Result<Fence> {
|
||||||
Self { dev, fence }
|
let fence = unsafe {
|
||||||
|
device
|
||||||
|
.raw
|
||||||
|
.create_fence(&vk::FenceCreateInfo::default(), None)?
|
||||||
|
};
|
||||||
|
Ok(Self::Dedicated {
|
||||||
|
fence: DeviceObject::new_debug_named(device.shared.clone(), fence, name),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
pub fn create(dev: Device) -> VkResult<Fence> {
|
pub fn from_pool(pool: &Arc<Pool<vk::Fence>>, name: Option<&'static str>) -> Result<Fence> {
|
||||||
unsafe {
|
let fence = pool.get_debug_named(name)?;
|
||||||
Ok(Self::new(
|
|
||||||
dev.clone(),
|
Ok(Self::Pooled {
|
||||||
dev.dev()
|
fence: PoolObject::new(fence, pool.clone()),
|
||||||
.create_fence(&vk::FenceCreateInfo::default(), None)?,
|
})
|
||||||
))
|
}
|
||||||
|
|
||||||
|
pub fn raw(&self) -> vk::Fence {
|
||||||
|
match self {
|
||||||
|
Fence::Dedicated { fence } => **fence,
|
||||||
|
Fence::Pooled { fence } => **fence,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn create_signaled(dev: Device) -> VkResult<Fence> {
|
fn device(&self) -> &Arc<DeviceInner> {
|
||||||
unsafe {
|
match self {
|
||||||
Ok(Self::new(
|
Fence::Dedicated { fence } => &fence.device(),
|
||||||
dev.clone(),
|
Fence::Pooled { fence } => &fence.owner().device,
|
||||||
dev.dev().create_fence(
|
|
||||||
&vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED),
|
|
||||||
None,
|
|
||||||
)?,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn wait_on(&self, timeout: Option<u64>) -> Result<(), vk::Result> {
|
|
||||||
use core::slice::from_ref;
|
pub fn wait_on(&self, timeout: Option<u64>) -> VkResult<()> {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.dev
|
self.device().raw.wait_for_fences(
|
||||||
.dev()
|
core::slice::from_ref(&self.raw()),
|
||||||
.wait_for_fences(from_ref(&self.fence), true, timeout.unwrap_or(u64::MAX))
|
true,
|
||||||
|
timeout.unwrap_or(u64::MAX),
|
||||||
|
)?
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn fence(&self) -> vk::Fence {
|
|
||||||
self.fence
|
|
||||||
}
|
|
||||||
pub fn is_signaled(&self) -> bool {
|
pub fn is_signaled(&self) -> bool {
|
||||||
unsafe { self.dev.dev().get_fence_status(self.fence).unwrap_or(false) }
|
|
||||||
}
|
|
||||||
pub fn reset(&self) -> Result<(), vk::Result> {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
self.dev
|
self.device()
|
||||||
.dev()
|
.raw
|
||||||
.reset_fences(core::slice::from_ref(&self.fence))
|
.get_fence_status(self.raw())
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl AsRef<vk::Fence> for Fence {
|
pub fn reset(&self) -> Result<()> {
|
||||||
fn as_ref(&self) -> &vk::Fence {
|
unsafe {
|
||||||
todo!()
|
self.device()
|
||||||
|
.raw
|
||||||
|
.reset_fences(core::slice::from_ref(&self.raw()))?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_future<'a>(self) -> FenceFuture<'a> {
|
||||||
|
FenceFuture::new(Arc::new(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
impl Semaphore {
|
pub enum SemaphoreType {
|
||||||
pub fn new(device: Device) -> VkResult<Self> {
|
Binary,
|
||||||
|
Timeline(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum SemaphoreInner {
|
||||||
|
Binary(vk::Semaphore),
|
||||||
|
Timeline(vk::Semaphore),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExternallyManagedObjectTrait<Arc<DevicePools>> for SemaphoreInner {
|
||||||
|
unsafe fn destroy(self, owner: &Arc<DevicePools>) {
|
||||||
|
match self {
|
||||||
|
SemaphoreInner::Binary(semaphore) => {
|
||||||
|
owner.binary_semaphores.push(BinarySemaphore(semaphore))
|
||||||
|
}
|
||||||
|
SemaphoreInner::Timeline(semaphore) => {
|
||||||
|
owner.timeline_semaphores.push(TimelineSemaphore(semaphore))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExternallyManagedObjectTrait<Arc<DeviceInner>> for SemaphoreInner {
|
||||||
|
unsafe fn destroy(self, owner: &Arc<DeviceInner>) {
|
||||||
|
match self {
|
||||||
|
SemaphoreInner::Binary(semaphore) | SemaphoreInner::Timeline(semaphore) => {
|
||||||
|
unsafe { owner.raw.destroy_semaphore(semaphore, None) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::device::asdf::traits::DebugNameable for SemaphoreInner {
|
||||||
|
fn debug_name(&self, device: &DeviceInner, name: &str) {
|
||||||
|
unsafe {
|
||||||
|
device.debug_name_object(self.raw(), name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SemaphoreInner {
|
||||||
|
pub fn raw(&self) -> vk::Semaphore {
|
||||||
|
match self {
|
||||||
|
SemaphoreInner::Binary(semaphore) | SemaphoreInner::Timeline(semaphore) => *semaphore,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn semaphore_type(&self) -> SemaphoreType {
|
||||||
|
match self {
|
||||||
|
SemaphoreInner::Binary(_) => SemaphoreType::Binary,
|
||||||
|
SemaphoreInner::Timeline(_) => SemaphoreType::Timeline(!0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BinarySemaphore> for SemaphoreInner {
|
||||||
|
fn from(value: BinarySemaphore) -> Self {
|
||||||
|
SemaphoreInner::Binary(value.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<TimelineSemaphore> for SemaphoreInner {
|
||||||
|
fn from(value: TimelineSemaphore) -> Self {
|
||||||
|
SemaphoreInner::Timeline(value.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Semaphore {
|
||||||
|
Dedicated {
|
||||||
|
semaphore: InnerDeviceObject<SemaphoreInner>,
|
||||||
|
},
|
||||||
|
Pooled {
|
||||||
|
#[allow(private_interfaces)]
|
||||||
|
semaphore: PoolObject<SemaphoreInner, Arc<DevicePools>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub(crate) struct BinarySemaphore(pub(crate) vk::Semaphore);
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub(crate) struct TimelineSemaphore(pub(crate) vk::Semaphore);
|
||||||
|
|
||||||
|
// This is just so that ash can name these semaphore newtypes
|
||||||
|
impl vk::Handle for BinarySemaphore {
|
||||||
|
const TYPE: vk::ObjectType = <vk::Semaphore as vk::Handle>::TYPE;
|
||||||
|
|
||||||
|
fn as_raw(self) -> u64 {
|
||||||
|
self.0.as_raw()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_raw(_: u64) -> Self {
|
||||||
|
unimplemented!("BinarySemaphore cannot be created from raw handle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl vk::Handle for TimelineSemaphore {
|
||||||
|
const TYPE: vk::ObjectType = <vk::Semaphore as vk::Handle>::TYPE;
|
||||||
|
|
||||||
|
fn as_raw(self) -> u64 {
|
||||||
|
self.0.as_raw()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_raw(_: u64) -> Self {
|
||||||
|
unimplemented!("TimelineSemaphore cannot be created from raw handle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pooled for BinarySemaphore {
|
||||||
|
fn create_from_pool(pool: &Pool<Self>) -> Result<Self> {
|
||||||
let mut type_info =
|
let mut type_info =
|
||||||
vk::SemaphoreTypeCreateInfo::default().semaphore_type(vk::SemaphoreType::BINARY);
|
vk::SemaphoreTypeCreateInfo::default().semaphore_type(vk::SemaphoreType::BINARY);
|
||||||
let create_info = vk::SemaphoreCreateInfo::default().push_next(&mut type_info);
|
let create_info = vk::SemaphoreCreateInfo::default().push_next(&mut type_info);
|
||||||
let inner = unsafe { device.dev().create_semaphore(&create_info, None)? };
|
let inner = unsafe { pool.device.raw.create_semaphore(&create_info, None)? };
|
||||||
|
Ok(Self(inner))
|
||||||
Ok(Self { device, inner })
|
|
||||||
}
|
|
||||||
pub fn new_timeline(device: Device, value: u64) -> VkResult<Self> {
|
|
||||||
let mut type_info = vk::SemaphoreTypeCreateInfo::default()
|
|
||||||
.semaphore_type(vk::SemaphoreType::TIMELINE)
|
|
||||||
.initial_value(value);
|
|
||||||
let create_info = vk::SemaphoreCreateInfo::default().push_next(&mut type_info);
|
|
||||||
let inner = unsafe { device.dev().create_semaphore(&create_info, None)? };
|
|
||||||
|
|
||||||
Ok(Self { device, inner })
|
|
||||||
}
|
|
||||||
pub fn semaphore(&self) -> vk::Semaphore {
|
|
||||||
self.inner
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Semaphore {
|
impl Pooled for TimelineSemaphore {
|
||||||
fn drop(&mut self) {
|
fn create_from_pool(pool: &Pool<Self>) -> Result<Self> {
|
||||||
unsafe {
|
let mut type_info = vk::SemaphoreTypeCreateInfo::default()
|
||||||
self.device.dev().destroy_semaphore(self.inner, None);
|
.semaphore_type(vk::SemaphoreType::TIMELINE)
|
||||||
|
.initial_value(0);
|
||||||
|
let create_info = vk::SemaphoreCreateInfo::default().push_next(&mut type_info);
|
||||||
|
let inner = unsafe { pool.device.raw.create_semaphore(&create_info, None)? };
|
||||||
|
Ok(Self(inner))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Semaphore {
|
||||||
|
pub fn new_dedicated(
|
||||||
|
device: Device,
|
||||||
|
semaphore_type: SemaphoreType,
|
||||||
|
name: Option<&'static str>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let mut type_info = vk::SemaphoreTypeCreateInfo::default();
|
||||||
|
match semaphore_type {
|
||||||
|
SemaphoreType::Binary => {
|
||||||
|
type_info = type_info.semaphore_type(vk::SemaphoreType::BINARY);
|
||||||
|
}
|
||||||
|
SemaphoreType::Timeline(value) => {
|
||||||
|
type_info = type_info
|
||||||
|
.semaphore_type(vk::SemaphoreType::TIMELINE)
|
||||||
|
.initial_value(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let create_info = vk::SemaphoreCreateInfo::default().push_next(&mut type_info);
|
||||||
|
let inner = unsafe { device.dev().create_semaphore(&create_info, None)? };
|
||||||
|
|
||||||
|
let inner = match semaphore_type {
|
||||||
|
SemaphoreType::Binary => SemaphoreInner::Binary(inner),
|
||||||
|
SemaphoreType::Timeline(_) => SemaphoreInner::Timeline(inner),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self::Dedicated {
|
||||||
|
semaphore: DeviceObject::new_debug_named(device.shared.clone(), inner, name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_pool(
|
||||||
|
device: Device,
|
||||||
|
semaphore_type: SemaphoreType,
|
||||||
|
name: Option<&'static str>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let semaphore = match semaphore_type {
|
||||||
|
SemaphoreType::Binary => {
|
||||||
|
let semaphore: SemaphoreInner =
|
||||||
|
device.pools.binary_semaphores.get_debug_named(name)?.into();
|
||||||
|
PoolObject::new(semaphore, device.pools.clone())
|
||||||
|
}
|
||||||
|
SemaphoreType::Timeline(value) => {
|
||||||
|
let semaphore: SemaphoreInner = device
|
||||||
|
.pools
|
||||||
|
.timeline_semaphores
|
||||||
|
.get_debug_named(name)?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let info = vk::SemaphoreSignalInfo::default()
|
||||||
|
.semaphore(semaphore.raw())
|
||||||
|
.value(value);
|
||||||
|
unsafe {
|
||||||
|
device.raw.signal_semaphore(&info)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
PoolObject::new(semaphore, device.pools.clone())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self::Pooled { semaphore })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn semaphore(&self) -> vk::Semaphore {
|
||||||
|
match self {
|
||||||
|
Semaphore::Dedicated { semaphore, .. } => semaphore.raw(),
|
||||||
|
Semaphore::Pooled { semaphore, .. } => semaphore.raw(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -259,23 +472,16 @@ pub struct FenceFuture<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FenceFuture<'_> {
|
impl FenceFuture<'_> {
|
||||||
/// Unsafe because `fence` must not be destroyed while this future is live.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub unsafe fn from_fence(device: Device, fence: vk::Fence) -> Self {
|
|
||||||
Self {
|
|
||||||
fence: Arc::new(Fence::new(device, fence)),
|
|
||||||
_pd: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn new(fence: Arc<Fence>) -> Self {
|
pub fn new(fence: Arc<Fence>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
fence,
|
fence,
|
||||||
_pd: PhantomData,
|
_pd: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn block(&self) -> VkResult<()> {
|
pub fn block(&self) -> crate::Result<()> {
|
||||||
self.fence.wait_on(None)?;
|
self.fence.wait_on(None)?;
|
||||||
self.fence.reset()
|
self.fence.reset()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -292,7 +498,7 @@ impl Future for FenceFuture<'_> {
|
||||||
std::task::Poll::Ready(())
|
std::task::Poll::Ready(())
|
||||||
} else {
|
} else {
|
||||||
self.fence
|
self.fence
|
||||||
.dev
|
.device()
|
||||||
.sync_threadpool()
|
.sync_threadpool()
|
||||||
.spawn_waiter(self.fence.clone(), cx.waker().clone());
|
.spawn_waiter(self.fence.clone(), cx.waker().clone());
|
||||||
std::task::Poll::Pending
|
std::task::Poll::Pending
|
||||||
|
|
|
||||||
427
crates/renderer/src/text.rs
Normal file
427
crates/renderer/src/text.rs
Normal file
|
|
@ -0,0 +1,427 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
sync::{Arc, Weak},
|
||||||
|
};
|
||||||
|
|
||||||
|
use ash::vk::Extent2D;
|
||||||
|
use cosmic_text::{CacheKey, FontSystem, PhysicalGlyph, SwashCache};
|
||||||
|
use glam::IVec2;
|
||||||
|
use guillotiere::size2;
|
||||||
|
#[cfg(test)]
|
||||||
|
use image::{GenericImage, GenericImageView};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
def_monotonic_id,
|
||||||
|
util::{self, F32, Rect2D},
|
||||||
|
};
|
||||||
|
|
||||||
|
const ROBOTO_BYTES: &[u8] = include_bytes!("../../../assets/fonts/Roboto.ttf");
|
||||||
|
const NOTO_SANS_HAN_BYTES: &[u8] = include_bytes!("../../../assets/fonts/NotoSansSC.ttf");
|
||||||
|
|
||||||
|
def_monotonic_id!(pub FontId);
|
||||||
|
|
||||||
|
type FontData = Arc<dyn AsRef<[u8]> + Send + Sync>;
|
||||||
|
struct FontStore {
|
||||||
|
fonts: HashMap<FontId, FontData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontStore {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
fonts: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_font_bytes(&mut self, bytes: FontData) -> FontId {
|
||||||
|
let id = FontId::new();
|
||||||
|
self.fonts.insert(id, bytes);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_source(&self, id: FontId) -> Option<cosmic_text::fontdb::Source> {
|
||||||
|
self.fonts
|
||||||
|
.get(&id)
|
||||||
|
.map(|bytes| cosmic_text::fontdb::Source::Binary(Arc::clone(bytes)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct FontAtlasSets {
|
||||||
|
sets: HashMap<FontId, FontAtlasSet>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontAtlasSets {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
sets: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get(&self, key: &FontId) -> Option<&FontAtlasSet> {
|
||||||
|
self.sets.get(key)
|
||||||
|
}
|
||||||
|
fn get_mut(&mut self, key: &FontId) -> Option<&mut FontAtlasSet> {
|
||||||
|
self.sets.get_mut(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Per-Font Font Atlas Set
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct FontAtlasSet {
|
||||||
|
// TODO: add proper plural of atlas to english language.
|
||||||
|
atlantes: HashMap<util::F32, Vec<FontAtlas>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontAtlasSet {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
atlantes: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_glyph_info(&self, key: CacheKey) -> Option<AtlasGlyphInfo> {
|
||||||
|
self.atlantes
|
||||||
|
.get(&F32::from_bits(key.font_size_bits))?
|
||||||
|
.iter()
|
||||||
|
.find_map(|atlas| {
|
||||||
|
atlas
|
||||||
|
.glyphs
|
||||||
|
.get(&key)
|
||||||
|
.map(|&(rect, offset)| (Arc::downgrade(&atlas.image), rect, offset))
|
||||||
|
})
|
||||||
|
.map(|(image, rect, offset)| AtlasGlyphInfo {
|
||||||
|
image,
|
||||||
|
rect,
|
||||||
|
offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn add_glyph(
|
||||||
|
&mut self,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
swash_cache: &mut SwashCache,
|
||||||
|
physical: PhysicalGlyph,
|
||||||
|
) -> Result<AtlasGlyphInfo, Error> {
|
||||||
|
let key = physical.cache_key;
|
||||||
|
|
||||||
|
let atlantes = self
|
||||||
|
.atlantes
|
||||||
|
.entry(F32::from_bits(physical.cache_key.font_size_bits))
|
||||||
|
.or_insert_with(|| vec![FontAtlas::new(512)]);
|
||||||
|
|
||||||
|
let (data, size, offset) = get_outlined_glyph_texture(font_system, swash_cache, physical)?;
|
||||||
|
|
||||||
|
if !atlantes
|
||||||
|
.iter_mut()
|
||||||
|
.any(|atlas| atlas.add_glyph(key, &data, size, offset).is_some())
|
||||||
|
{
|
||||||
|
let max_size = size.height.max(size.height);
|
||||||
|
let x2_or_512 = (1u32 << (32 - max_size.leading_zeros())).max(512);
|
||||||
|
atlantes.push(FontAtlas::new(x2_or_512));
|
||||||
|
atlantes
|
||||||
|
.last_mut()
|
||||||
|
.unwrap()
|
||||||
|
.add_glyph(key, &data, size, offset)
|
||||||
|
.ok_or(Error::FailedToRasterizeGlyph(key))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.get_glyph_info(key).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Image(parking_lot::RwLock<ImageInner>);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct ImageInner {
|
||||||
|
image: Vec<u8>,
|
||||||
|
image_size: Extent2D,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageInner {
|
||||||
|
fn bytes(&self) -> &[u8] {
|
||||||
|
self.image.as_slice()
|
||||||
|
}
|
||||||
|
fn bytes_mut(&mut self) -> &mut [u8] {
|
||||||
|
self.image.as_mut_slice()
|
||||||
|
}
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
self.image_size.width
|
||||||
|
}
|
||||||
|
fn height(&self) -> u32 {
|
||||||
|
self.image_size.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct AtlasGlyphInfo {
|
||||||
|
image: Weak<Image>,
|
||||||
|
rect: Rect2D,
|
||||||
|
offset: IVec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FontAtlas {
|
||||||
|
// TODO: this image will be host-coherent and host-visible, so that it can
|
||||||
|
// be updated with new glyphs.
|
||||||
|
// that begs the question:
|
||||||
|
// - should it be staged to a device-local image when it's used?
|
||||||
|
// - does it make sense to use VK_EXT_external_memory_host here? It's
|
||||||
|
// supported on virtually all Windows and Linux drivers.
|
||||||
|
// - how to sync this? I need to make sure that when this image is written
|
||||||
|
// to, it isn't also being read from. Usually, these images will be
|
||||||
|
// write-only, except when rendering. Since currently my rendering may
|
||||||
|
// happen on any thread at any time, that's problematic.
|
||||||
|
//
|
||||||
|
// In fact, this is an awful type to use here because of the unique access
|
||||||
|
// requirement for mapping.
|
||||||
|
image: Arc<Image>,
|
||||||
|
// stores sub-rect of image and placement offset of glyph
|
||||||
|
glyphs: HashMap<CacheKey, (Rect2D, IVec2)>,
|
||||||
|
allocator: guillotiere::AtlasAllocator,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for FontAtlas {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("FontAtlas")
|
||||||
|
.field("image", &self.image)
|
||||||
|
.field("glyphs", &self.glyphs)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontAtlas {
|
||||||
|
fn new(size: u32) -> Self {
|
||||||
|
let num_bytes = size * size * 4;
|
||||||
|
Self {
|
||||||
|
image: Arc::new(Image(parking_lot::RwLock::new(ImageInner {
|
||||||
|
image: vec![0; num_bytes as usize],
|
||||||
|
image_size: Extent2D {
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
},
|
||||||
|
}))),
|
||||||
|
glyphs: HashMap::new(),
|
||||||
|
allocator: guillotiere::AtlasAllocator::new(size2(
|
||||||
|
size.try_into().unwrap(),
|
||||||
|
size.try_into().unwrap(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn has_glyph(&self, key: CacheKey) -> bool {
|
||||||
|
self.glyphs.contains_key(&key)
|
||||||
|
}
|
||||||
|
fn add_glyph(
|
||||||
|
&mut self,
|
||||||
|
key: CacheKey,
|
||||||
|
data: &[u8],
|
||||||
|
size: Extent2D,
|
||||||
|
offset: IVec2,
|
||||||
|
) -> Option<AtlasGlyphInfo> {
|
||||||
|
let allocation = self.allocator.allocate(guillotiere::size2(
|
||||||
|
(size.width + 1).try_into().unwrap(),
|
||||||
|
(size.height + 1).try_into().unwrap(),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let rect = allocation.rectangle;
|
||||||
|
let x = rect.min.x;
|
||||||
|
let y = rect.min.y;
|
||||||
|
let width = rect.width() - 1;
|
||||||
|
let height = rect.height() - 1;
|
||||||
|
let rect = Rect2D::new_from_size(IVec2::new(x, y), IVec2::new(width, height));
|
||||||
|
self.glyphs.insert(key, (rect, offset));
|
||||||
|
|
||||||
|
// put data into image array
|
||||||
|
let mut image = self.image.0.write();
|
||||||
|
for line_y in 0..height {
|
||||||
|
let y = y + line_y;
|
||||||
|
let image_offset = 4 * y as u32 * image.width() + 4 * x as u32;
|
||||||
|
let glyph_offset = 4 * line_y * width;
|
||||||
|
let len = 4 * width as usize;
|
||||||
|
let dst = &mut image.bytes_mut()[image_offset as usize..][..len];
|
||||||
|
let src = &data[glyph_offset as usize..][..len];
|
||||||
|
dst.copy_from_slice(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(AtlasGlyphInfo {
|
||||||
|
image: Arc::downgrade(&self.image),
|
||||||
|
rect,
|
||||||
|
offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Failed to rasterize glyph {0:?}.")]
|
||||||
|
FailedToRasterizeGlyph(CacheKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_outlined_glyph_texture(
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
swash_cache: &mut SwashCache,
|
||||||
|
glyph: PhysicalGlyph,
|
||||||
|
) -> Result<(Vec<u8>, Extent2D, IVec2), Error> {
|
||||||
|
let image = swash_cache
|
||||||
|
.get_image_uncached(font_system, glyph.cache_key)
|
||||||
|
.ok_or(Error::FailedToRasterizeGlyph(glyph.cache_key))?;
|
||||||
|
|
||||||
|
let cosmic_text::Placement {
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
} = image.placement;
|
||||||
|
|
||||||
|
let data = match image.content {
|
||||||
|
cosmic_text::SwashContent::Mask => image
|
||||||
|
.data
|
||||||
|
.iter()
|
||||||
|
.flat_map(|a| [255, 255, 255, *a])
|
||||||
|
.collect(),
|
||||||
|
cosmic_text::SwashContent::Color => image.data,
|
||||||
|
cosmic_text::SwashContent::SubpixelMask => {
|
||||||
|
// TODO: implement
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let extent = Extent2D { width, height };
|
||||||
|
|
||||||
|
Ok((data, extent, IVec2::new(left, top)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use cosmic_text::{Attrs, Buffer, Family, Metrics};
|
||||||
|
use glam::Vec2;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let mut font_store = FontStore::new();
|
||||||
|
let mut db = cosmic_text::fontdb::Database::new();
|
||||||
|
let mut font_id_map = HashMap::<FontId, cosmic_text::fontdb::ID>::new();
|
||||||
|
let mut reverse_font_id_map = HashMap::<cosmic_text::fontdb::ID, FontId>::new();
|
||||||
|
|
||||||
|
let roboto = font_store.add_font_bytes(Arc::new(ROBOTO_BYTES));
|
||||||
|
let noto_han = font_store.add_font_bytes(Arc::new(NOTO_SANS_HAN_BYTES));
|
||||||
|
let id = *db
|
||||||
|
.load_font_source(font_store.as_source(roboto).unwrap())
|
||||||
|
.last()
|
||||||
|
.unwrap();
|
||||||
|
font_id_map.insert(roboto, id);
|
||||||
|
reverse_font_id_map.insert(id, roboto);
|
||||||
|
|
||||||
|
let id = *db
|
||||||
|
.load_font_source(font_store.as_source(noto_han).unwrap())
|
||||||
|
.last()
|
||||||
|
.unwrap();
|
||||||
|
font_id_map.insert(noto_han, id);
|
||||||
|
reverse_font_id_map.insert(id, noto_han);
|
||||||
|
|
||||||
|
db.set_sans_serif_family("Roboto");
|
||||||
|
// db.load_system_fonts();
|
||||||
|
|
||||||
|
let locale = sys_locale::get_locale().unwrap_or("en-DK".to_string());
|
||||||
|
let mut font_system = FontSystem::new_with_locale_and_db(locale, db);
|
||||||
|
let mut swash = SwashCache::new();
|
||||||
|
|
||||||
|
let mut buffer = Buffer::new_empty(Metrics::new(14.0, 20.0));
|
||||||
|
|
||||||
|
let mut buf = buffer.borrow_with(&mut font_system);
|
||||||
|
|
||||||
|
let path = format!("../../assets/testing/hello.txt");
|
||||||
|
let attrs = Attrs::new()
|
||||||
|
.family(Family::SansSerif)
|
||||||
|
.metrics(Metrics::new(48.0, 56.0));
|
||||||
|
let _text = std::fs::read_to_string(path).expect("hello.txt");
|
||||||
|
|
||||||
|
buf.set_text(
|
||||||
|
"Hello, World! 你好! 안녕하세요",
|
||||||
|
attrs,
|
||||||
|
cosmic_text::Shaping::Advanced,
|
||||||
|
);
|
||||||
|
//buf.set_size(Some(400.0), Some(400.0));
|
||||||
|
buf.set_wrap(cosmic_text::Wrap::Word);
|
||||||
|
|
||||||
|
for line in buf.lines.iter_mut() {
|
||||||
|
line.set_align(Some(cosmic_text::Align::Left));
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.shape_until_scroll(false);
|
||||||
|
|
||||||
|
let mut font_atlantes = FontAtlasSets::new();
|
||||||
|
|
||||||
|
let mut glyphs = Vec::new();
|
||||||
|
let mut size = Vec2::new(0.0, 0.0);
|
||||||
|
|
||||||
|
_ = buffer
|
||||||
|
.layout_runs()
|
||||||
|
.flat_map(|run| {
|
||||||
|
size.x = size.x.max(run.line_w);
|
||||||
|
size.y = size.y + run.line_height;
|
||||||
|
run.glyphs.iter().map(move |glyph| (glyph, run.line_y))
|
||||||
|
})
|
||||||
|
.try_for_each(|(glyph, line_y)| -> Result<(), Error> {
|
||||||
|
let font_id = *reverse_font_id_map.get(&glyph.font_id).unwrap();
|
||||||
|
let set = font_atlantes.sets.entry(font_id).or_default();
|
||||||
|
|
||||||
|
let physical = glyph.physical((0.0, 0.0), 1.0);
|
||||||
|
|
||||||
|
let glyph_info = set
|
||||||
|
.get_glyph_info(physical.cache_key)
|
||||||
|
.map(Ok)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
set.add_glyph(&mut font_system, &mut swash, physical.clone())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let pos = {
|
||||||
|
let x = glyph_info.offset.x as f32 + physical.x as f32;
|
||||||
|
let y = line_y.round() + physical.y as f32 - glyph_info.offset.y as f32;
|
||||||
|
|
||||||
|
Vec2::new(x, y)
|
||||||
|
};
|
||||||
|
|
||||||
|
let size = glyph_info.rect.size();
|
||||||
|
glyphs.push((glyph_info, pos, size));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
let (width, height) = {
|
||||||
|
let tmp = size.ceil().as_uvec2();
|
||||||
|
(tmp.x, tmp.y)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut image = image::RgbaImage::from_pixel(width, height, image::Rgba([0, 0, 0, 255]));
|
||||||
|
|
||||||
|
eprintln!("glyphs: {glyphs:#?}");
|
||||||
|
eprintln!("image: {width}x{height}");
|
||||||
|
|
||||||
|
for (info, pos, _size) in glyphs {
|
||||||
|
let atlas_image = info.image.upgrade().unwrap();
|
||||||
|
let glyph = atlas_image.0.read();
|
||||||
|
|
||||||
|
let atlas_image = image::ImageBuffer::<image::Rgba<u8>, _>::from_raw(
|
||||||
|
glyph.width(),
|
||||||
|
glyph.height(),
|
||||||
|
glyph.bytes(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let glyph = atlas_image.view(
|
||||||
|
info.rect.top_left().x as u32,
|
||||||
|
info.rect.top_left().y as u32,
|
||||||
|
info.rect.width() as u32,
|
||||||
|
info.rect.height() as u32,
|
||||||
|
);
|
||||||
|
|
||||||
|
eprintln!("rect: {:?}", info.rect);
|
||||||
|
eprintln!("image_size: {:?}", image.dimensions());
|
||||||
|
|
||||||
|
image
|
||||||
|
.copy_from(&*glyph, pos.x as u32, pos.y as u32)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
image.save("rendered.png").unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,169 @@
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
mem::ManuallyDrop,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
};
|
||||||
|
|
||||||
use ash::vk;
|
use ash::vk;
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
|
pub(crate) mod weak_vec {
|
||||||
|
#![allow(dead_code)]
|
||||||
|
//! Module containing the [`WeakVec`] API.
|
||||||
|
|
||||||
|
use std::{sync::Weak, vec::Vec};
|
||||||
|
|
||||||
|
/// An optimized container for `Weak` references of `T` that minimizes reallocations by
|
||||||
|
/// dropping older elements that no longer have strong references to them.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct WeakVec<T> {
|
||||||
|
inner: Vec<Weak<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for WeakVec<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> WeakVec<T> {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self { inner: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes a new element to this collection.
|
||||||
|
///
|
||||||
|
/// If the inner Vec needs to be reallocated, we will first drop older elements that
|
||||||
|
/// no longer have strong references to them.
|
||||||
|
pub(crate) fn push(&mut self, value: Weak<T>) {
|
||||||
|
if self.inner.len() == self.inner.capacity() {
|
||||||
|
// Iterating backwards has the advantage that we don't do more work than we have to.
|
||||||
|
for i in (0..self.inner.len()).rev() {
|
||||||
|
if self.inner[i].strong_count() == 0 {
|
||||||
|
self.inner.swap_remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure our capacity is twice the number of live elements.
|
||||||
|
// Leaving some spare capacity ensures that we won't re-scan immediately.
|
||||||
|
self.inner.reserve_exact(self.inner.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct WeakVecIter<T> {
|
||||||
|
inner: std::vec::IntoIter<Weak<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Iterator for WeakVecIter<T> {
|
||||||
|
type Item = Weak<T>;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.inner.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoIterator for WeakVec<T> {
|
||||||
|
type Item = Weak<T>;
|
||||||
|
type IntoIter = WeakVecIter<T>;
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
WeakVecIter {
|
||||||
|
inner: self.inner.into_iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(derive_more::Debug)]
|
||||||
|
pub(crate) struct DropGuard(#[debug(skip)] ManuallyDrop<Box<dyn FnOnce()>>);
|
||||||
|
|
||||||
|
impl DropGuard {
|
||||||
|
pub(crate) fn new(f: impl FnOnce() + 'static) -> Self {
|
||||||
|
Self(ManuallyDrop::new(Box::new(f)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for DropGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
ManuallyDrop::take(&mut self.0)();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct DebugName {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
name: Cow<'static, str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Display for DebugName {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", &**self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Debug for DebugName {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", &**self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::ops::Deref for DebugName {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
return &self.name;
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
return "FEATURE_DISABLED";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Cow<'static, str>> for DebugName {
|
||||||
|
fn from(_value: Cow<'static, str>) -> Self {
|
||||||
|
Self {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
name: _value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for DebugName {
|
||||||
|
fn from(_value: &'static str) -> Self {
|
||||||
|
Self {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
name: Cow::Borrowed(_value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for DebugName {
|
||||||
|
fn from(_value: String) -> Self {
|
||||||
|
Self {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
name: Cow::Owned(_value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! def_monotonic_id {
|
macro_rules! def_monotonic_id {
|
||||||
($vis:vis $ty:ident) => {
|
($vis:vis $ty:ident) => {
|
||||||
#[derive(Copy, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
|
#[derive(Copy, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
|
||||||
$vis struct $ty(::core::num::NonZero<u32>);
|
$vis struct $ty(::core::num::NonZero<u32>);
|
||||||
|
|
||||||
|
impl ::core::default::Default for $ty {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl $ty {
|
impl $ty {
|
||||||
|
#[allow(dead_code, reason = "This method is macro-generated")]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
use ::core::sync::atomic::{AtomicU32, Ordering};
|
use ::core::sync::atomic::{AtomicU32, Ordering};
|
||||||
static COUNTER: AtomicU32 = AtomicU32::new(1);
|
static COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||||
|
|
@ -21,6 +175,7 @@ macro_rules! def_monotonic_id {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code, reason = "This method is macro-generated")]
|
||||||
pub fn as_u32(&self) -> u32 {
|
pub fn as_u32(&self) -> u32 {
|
||||||
self.0.get()
|
self.0.get()
|
||||||
}
|
}
|
||||||
|
|
@ -329,6 +484,17 @@ pub fn eq_f32(lhs: f32, rhs: f32) -> bool {
|
||||||
_ => lhs == rhs,
|
_ => lhs == rhs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn ord_f32(lhs: f32, rhs: f32) -> core::cmp::Ordering {
|
||||||
|
use core::cmp::Ordering;
|
||||||
|
use core::num::FpCategory::*;
|
||||||
|
match (lhs.classify(), rhs.classify()) {
|
||||||
|
(Zero, Zero) | (Nan, Nan) => Ordering::Equal,
|
||||||
|
(Infinite, Infinite) => {
|
||||||
|
core::cmp::PartialOrd::partial_cmp(&lhs.signum(), &rhs.signum()).unwrap()
|
||||||
|
}
|
||||||
|
_ => core::cmp::PartialOrd::partial_cmp(&lhs, &rhs).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn hash_f32<H: std::hash::Hasher>(state: &mut H, f: f32) {
|
pub fn hash_f32<H: std::hash::Hasher>(state: &mut H, f: f32) {
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
@ -342,6 +508,72 @@ pub fn hash_f32<H: std::hash::Hasher>(state: &mut H, f: f32) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct F32(f32);
|
||||||
|
|
||||||
|
impl F32 {
|
||||||
|
#[inline]
|
||||||
|
pub fn from_bits(bits: u32) -> Self {
|
||||||
|
Self(f32::from_bits(bits))
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn into_bits(self) -> u32 {
|
||||||
|
self.0.to_bits()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<f32> for F32 {
|
||||||
|
fn from(value: f32) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<F32> for f32 {
|
||||||
|
fn from(value: F32) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for F32 {
|
||||||
|
type Target = f32;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for F32 {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for F32 {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
eq_f32(self.0, other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for F32 {}
|
||||||
|
|
||||||
|
impl PartialOrd for F32 {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(ord_f32(self.0, other.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for F32 {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
ord_f32(self.0, other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::hash::Hash for F32 {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
hash_f32(state, self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn timed<T, F: FnOnce() -> T>(label: &str, f: F) -> T {
|
pub fn timed<T, F: FnOnce() -> T>(label: &str, f: F) -> T {
|
||||||
let now = std::time::Instant::now();
|
let now = std::time::Instant::now();
|
||||||
let out = f();
|
let out = f();
|
||||||
|
|
@ -360,7 +592,7 @@ pub struct Rgba8(pub [u8; 4]);
|
||||||
|
|
||||||
impl std::hash::Hash for Rgba {
|
impl std::hash::Hash for Rgba {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.0.map(|f| hash_f32(state, f));
|
self.0.iter().for_each(|&f| hash_f32(state, f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -551,7 +783,7 @@ pub struct CStringList {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CStringList {
|
impl CStringList {
|
||||||
pub fn from_iter<I, S>(i: I) -> CStringList
|
pub fn from_strings<I, S>(i: I) -> CStringList
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = S>,
|
I: IntoIterator<Item = S>,
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
|
|
@ -569,7 +801,7 @@ impl CStringList {
|
||||||
Self {
|
Self {
|
||||||
strings: strings
|
strings: strings
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|offset| unsafe { bytes.as_ptr().offset(offset as isize) as *const i8 })
|
.map(|offset| unsafe { bytes.as_ptr().add(offset) as *const i8 })
|
||||||
.collect(),
|
.collect(),
|
||||||
bytes,
|
bytes,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
crates/text/Cargo.toml
Normal file
29
crates/text/Cargo.toml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
[package]
|
||||||
|
name = "text"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
glam = { workspace = true }
|
||||||
|
derive_more = { workspace = true }
|
||||||
|
ahash = { workspace = true }
|
||||||
|
parking_lot = { workspace = true }
|
||||||
|
|
||||||
|
|
||||||
|
bevy_ecs = { workspace = true }
|
||||||
|
bevy_asset = { workspace = true }
|
||||||
|
bevy_reflect = { workspace = true }
|
||||||
|
bevy_utils = { workspace = true }
|
||||||
|
|
||||||
|
cosmic-text = "0.12.1"
|
||||||
|
sys-locale = "0.3.2"
|
||||||
|
guillotiere = "0.6.2"
|
||||||
|
|
||||||
|
renderer = {path = "../renderer"}
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tracing-test = "0.2.5"
|
||||||
|
image = "0.25.5"
|
||||||
484
crates/text/src/lib.rs
Normal file
484
crates/text/src/lib.rs
Normal file
|
|
@ -0,0 +1,484 @@
|
||||||
|
#![feature(debug_closure_helpers)]
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use bevy_asset::{Asset, AssetLoader};
|
||||||
|
use bevy_ecs::prelude::*;
|
||||||
|
use bevy_reflect::TypePath;
|
||||||
|
use glam::IVec2;
|
||||||
|
use guillotiere::size2;
|
||||||
|
use renderer::{
|
||||||
|
Extent2D, def_monotonic_id,
|
||||||
|
util::{F32, Rect2D},
|
||||||
|
};
|
||||||
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
|
use ahash::{HashMap, HashMapExt};
|
||||||
|
use cosmic_text::{CacheKey, FontSystem, PhysicalGlyph, SwashCache};
|
||||||
|
|
||||||
|
mod components {
|
||||||
|
use bevy_ecs::component::Component;
|
||||||
|
use bevy_reflect::Reflect;
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Component, Reflect, Deref, DerefMut)]
|
||||||
|
pub struct Text(pub String);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ROBOTO_BYTES: &[u8] =
|
||||||
|
include_bytes!("../../../assets/fonts/Roboto/Roboto-VariableFont_wdth,wght.ttf");
|
||||||
|
const NOTO_SANS_HAN_BYTES: &[u8] =
|
||||||
|
include_bytes!("../../../assets/fonts/Noto_Sans_SC/NotoSansSC-VariableFont_wght.ttf");
|
||||||
|
|
||||||
|
def_monotonic_id!(pub FontId);
|
||||||
|
|
||||||
|
#[derive(Clone, TypePath, Asset)]
|
||||||
|
pub struct FontData(Arc<dyn AsRef<[u8]> + Send + Sync>);
|
||||||
|
|
||||||
|
impl std::fmt::Debug for FontData {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_tuple("FontData")
|
||||||
|
.field_with(|f| write!(f, "{:?}", <dyn AsRef::<[u8]>>::as_ref(self.0.as_ref())))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct FontLoader;
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
enum FontLoaderError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Parser(#[from] cosmic_text::ttf_parser::FaceParsingError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssetLoader for FontLoader {
|
||||||
|
type Asset = FontData;
|
||||||
|
|
||||||
|
type Settings = ();
|
||||||
|
|
||||||
|
type Error = FontLoaderError;
|
||||||
|
|
||||||
|
async fn load(
|
||||||
|
&self,
|
||||||
|
reader: &mut dyn bevy_asset::io::Reader,
|
||||||
|
_settings: &Self::Settings,
|
||||||
|
_load_context: &mut bevy_asset::LoadContext<'_>,
|
||||||
|
) -> Result<Self::Asset, Self::Error> {
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
reader.read_to_end(&mut bytes).await?;
|
||||||
|
let font = FontData(Arc::new(bytes.into_boxed_slice()));
|
||||||
|
|
||||||
|
Ok(font)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FontStore {
|
||||||
|
fonts: HashMap<FontId, FontData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontStore {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
fonts: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_font_bytes(&mut self, bytes: FontData) -> FontId {
|
||||||
|
let id = FontId::new();
|
||||||
|
self.fonts.insert(id, bytes);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_source(&self, id: FontId) -> Option<cosmic_text::fontdb::Source> {
|
||||||
|
self.fonts
|
||||||
|
.get(&id)
|
||||||
|
.map(|bytes| cosmic_text::fontdb::Source::Binary(bytes.0.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Resource)]
|
||||||
|
struct FontAtlasSets {
|
||||||
|
sets: HashMap<FontId, FontAtlasSet>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontAtlasSets {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
sets: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get(&self, key: &FontId) -> Option<&FontAtlasSet> {
|
||||||
|
self.sets.get(key)
|
||||||
|
}
|
||||||
|
fn get_mut(&mut self, key: &FontId) -> Option<&mut FontAtlasSet> {
|
||||||
|
self.sets.get_mut(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Per-Font Font Atlas Set
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct FontAtlasSet {
|
||||||
|
// TODO: add proper plural of atlas to english language.
|
||||||
|
atlantes: HashMap<F32, Vec<FontAtlas>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontAtlasSet {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
atlantes: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_glyph_info(&self, key: CacheKey) -> Option<AtlasGlyphInfo> {
|
||||||
|
self.atlantes
|
||||||
|
.get(&F32::from_bits(key.font_size_bits))?
|
||||||
|
.iter()
|
||||||
|
.find_map(|atlas| {
|
||||||
|
atlas
|
||||||
|
.glyphs
|
||||||
|
.get(&key)
|
||||||
|
.map(|&(rect, offset)| (Arc::downgrade(&atlas.image), rect, offset))
|
||||||
|
})
|
||||||
|
.map(|(image, rect, offset)| AtlasGlyphInfo {
|
||||||
|
image,
|
||||||
|
rect,
|
||||||
|
offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn add_glyph(
|
||||||
|
&mut self,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
swash_cache: &mut SwashCache,
|
||||||
|
physical: PhysicalGlyph,
|
||||||
|
) -> Result<AtlasGlyphInfo, Error> {
|
||||||
|
let key = physical.cache_key;
|
||||||
|
|
||||||
|
let atlantes = self
|
||||||
|
.atlantes
|
||||||
|
.entry(F32::from_bits(physical.cache_key.font_size_bits))
|
||||||
|
.or_insert_with(|| vec![FontAtlas::new(512)]);
|
||||||
|
|
||||||
|
let (data, size, offset) = get_outlined_glyph_texture(font_system, swash_cache, physical)?;
|
||||||
|
|
||||||
|
if !atlantes
|
||||||
|
.iter_mut()
|
||||||
|
.any(|atlas| atlas.add_glyph(key, &data, size, offset).is_some())
|
||||||
|
{
|
||||||
|
let max_size = size.height.max(size.height);
|
||||||
|
let x2_or_512 = (1u32 << (32 - max_size.leading_zeros())).max(512);
|
||||||
|
atlantes.push(FontAtlas::new(x2_or_512));
|
||||||
|
atlantes
|
||||||
|
.last_mut()
|
||||||
|
.unwrap()
|
||||||
|
.add_glyph(key, &data, size, offset)
|
||||||
|
.ok_or(Error::FailedToRasterizeGlyph(key))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.get_glyph_info(key).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Image(parking_lot::RwLock<ImageInner>);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct ImageInner {
|
||||||
|
image: Vec<u8>,
|
||||||
|
image_size: Extent2D,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageInner {
|
||||||
|
fn bytes(&self) -> &[u8] {
|
||||||
|
self.image.as_slice()
|
||||||
|
}
|
||||||
|
fn bytes_mut(&mut self) -> &mut [u8] {
|
||||||
|
self.image.as_mut_slice()
|
||||||
|
}
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
self.image_size.width
|
||||||
|
}
|
||||||
|
fn height(&self) -> u32 {
|
||||||
|
self.image_size.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct AtlasGlyphInfo {
|
||||||
|
image: Weak<Image>,
|
||||||
|
rect: Rect2D,
|
||||||
|
offset: IVec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FontAtlas {
|
||||||
|
// TODO: this image will be host-coherent and host-visible, so that it can
|
||||||
|
// be updated with new glyphs.
|
||||||
|
// that begs the question:
|
||||||
|
// - should it be staged to a device-local image when it's used?
|
||||||
|
// - does it make sense to use VK_EXT_external_memory_host here? It's
|
||||||
|
// supported on virtually all Windows and Linux drivers.
|
||||||
|
// - how to sync this? I need to make sure that when this image is written
|
||||||
|
// to, it isn't also being read from. Usually, these images will be
|
||||||
|
// write-only, except when rendering. Since currently my rendering may
|
||||||
|
// happen on any thread at any time, that's problematic.
|
||||||
|
//
|
||||||
|
// In fact, this is an awful type to use here because of the unique access
|
||||||
|
// requirement for mapping.
|
||||||
|
image: Arc<Image>,
|
||||||
|
// stores sub-rect of image and placement offset of glyph
|
||||||
|
glyphs: HashMap<CacheKey, (Rect2D, IVec2)>,
|
||||||
|
allocator: guillotiere::AtlasAllocator,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for FontAtlas {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("FontAtlas")
|
||||||
|
.field("image", &self.image)
|
||||||
|
.field("glyphs", &self.glyphs)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontAtlas {
|
||||||
|
fn new(size: u32) -> Self {
|
||||||
|
let num_bytes = size * size * 4;
|
||||||
|
Self {
|
||||||
|
image: Arc::new(Image(parking_lot::RwLock::new(ImageInner {
|
||||||
|
image: vec![0; num_bytes as usize],
|
||||||
|
image_size: Extent2D {
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
},
|
||||||
|
}))),
|
||||||
|
glyphs: HashMap::new(),
|
||||||
|
allocator: guillotiere::AtlasAllocator::new(size2(
|
||||||
|
size.try_into().unwrap(),
|
||||||
|
size.try_into().unwrap(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn has_glyph(&self, key: CacheKey) -> bool {
|
||||||
|
self.glyphs.contains_key(&key)
|
||||||
|
}
|
||||||
|
fn add_glyph(
|
||||||
|
&mut self,
|
||||||
|
key: CacheKey,
|
||||||
|
data: &Vec<u8>,
|
||||||
|
size: Extent2D,
|
||||||
|
offset: IVec2,
|
||||||
|
) -> Option<AtlasGlyphInfo> {
|
||||||
|
let allocation = self.allocator.allocate(guillotiere::size2(
|
||||||
|
(size.width + 1).try_into().unwrap(),
|
||||||
|
(size.height + 1).try_into().unwrap(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let Some(allocation) = allocation else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let rect = allocation.rectangle;
|
||||||
|
let x = rect.min.x;
|
||||||
|
let y = rect.min.y;
|
||||||
|
let width = rect.width() - 1;
|
||||||
|
let height = rect.height() - 1;
|
||||||
|
let rect = Rect2D::new_from_size(IVec2::new(x, y), IVec2::new(width, height));
|
||||||
|
self.glyphs.insert(key, (rect, offset));
|
||||||
|
|
||||||
|
// put data into image array
|
||||||
|
let mut image = self.image.0.write();
|
||||||
|
for line_y in 0..height {
|
||||||
|
let y = y + line_y;
|
||||||
|
let image_offset = 4 * y as u32 * image.width() + 4 * x as u32;
|
||||||
|
let glyph_offset = 4 * line_y * width;
|
||||||
|
let len = 4 * width as usize;
|
||||||
|
let dst = &mut image.bytes_mut()[image_offset as usize..][..len];
|
||||||
|
let src = &data[glyph_offset as usize..][..len];
|
||||||
|
dst.copy_from_slice(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(AtlasGlyphInfo {
|
||||||
|
image: Arc::downgrade(&self.image),
|
||||||
|
rect,
|
||||||
|
offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Failed to rasterize glyph {0:?}.")]
|
||||||
|
FailedToRasterizeGlyph(CacheKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_outlined_glyph_texture(
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
swash_cache: &mut SwashCache,
|
||||||
|
glyph: PhysicalGlyph,
|
||||||
|
) -> Result<(Vec<u8>, Extent2D, IVec2), Error> {
|
||||||
|
let image = swash_cache
|
||||||
|
.get_image_uncached(font_system, glyph.cache_key)
|
||||||
|
.ok_or(Error::FailedToRasterizeGlyph(glyph.cache_key))?;
|
||||||
|
|
||||||
|
let cosmic_text::Placement {
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
} = image.placement;
|
||||||
|
|
||||||
|
let data = match image.content {
|
||||||
|
cosmic_text::SwashContent::Mask => image
|
||||||
|
.data
|
||||||
|
.iter()
|
||||||
|
.flat_map(|a| [255, 255, 255, *a])
|
||||||
|
.collect(),
|
||||||
|
cosmic_text::SwashContent::Color => image.data,
|
||||||
|
cosmic_text::SwashContent::SubpixelMask => {
|
||||||
|
// TODO: implement
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let extent = Extent2D { width, height };
|
||||||
|
|
||||||
|
Ok((data, extent, IVec2::new(left, top)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use cosmic_text::{Attrs, Buffer, Family, Metrics};
|
||||||
|
use glam::Vec2;
|
||||||
|
use image::{GenericImage, GenericImageView};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let mut font_store = FontStore::new();
|
||||||
|
let mut db = cosmic_text::fontdb::Database::new();
|
||||||
|
let mut font_id_map = HashMap::<FontId, cosmic_text::fontdb::ID>::new();
|
||||||
|
let mut reverse_font_id_map = HashMap::<cosmic_text::fontdb::ID, FontId>::new();
|
||||||
|
|
||||||
|
let roboto = font_store.add_font_bytes(FontData(Arc::new(ROBOTO_BYTES)));
|
||||||
|
let noto_han = font_store.add_font_bytes(FontData(Arc::new(NOTO_SANS_HAN_BYTES)));
|
||||||
|
let id = *db
|
||||||
|
.load_font_source(font_store.as_source(roboto).unwrap())
|
||||||
|
.last()
|
||||||
|
.unwrap();
|
||||||
|
font_id_map.insert(roboto, id);
|
||||||
|
reverse_font_id_map.insert(id, roboto);
|
||||||
|
|
||||||
|
let id = *db
|
||||||
|
.load_font_source(font_store.as_source(noto_han).unwrap())
|
||||||
|
.last()
|
||||||
|
.unwrap();
|
||||||
|
font_id_map.insert(noto_han, id);
|
||||||
|
reverse_font_id_map.insert(id, noto_han);
|
||||||
|
|
||||||
|
db.set_sans_serif_family("Roboto");
|
||||||
|
// db.load_system_fonts();
|
||||||
|
|
||||||
|
let locale = sys_locale::get_locale().unwrap_or("en-DK".to_string());
|
||||||
|
let mut font_system = FontSystem::new_with_locale_and_db(locale, db);
|
||||||
|
let mut swash = SwashCache::new();
|
||||||
|
|
||||||
|
let mut buffer = Buffer::new_empty(Metrics::new(14.0, 20.0));
|
||||||
|
|
||||||
|
let mut buf = buffer.borrow_with(&mut font_system);
|
||||||
|
|
||||||
|
let path = format!("../../assets/testing/hello.txt");
|
||||||
|
let attrs = Attrs::new()
|
||||||
|
.family(Family::SansSerif)
|
||||||
|
.metrics(Metrics::new(48.0, 56.0));
|
||||||
|
let text = std::fs::read_to_string(path).expect("hello.txt");
|
||||||
|
|
||||||
|
buf.set_text(
|
||||||
|
"Hello, World! 你好! 안녕하세요",
|
||||||
|
attrs,
|
||||||
|
cosmic_text::Shaping::Advanced,
|
||||||
|
);
|
||||||
|
//buf.set_size(Some(400.0), Some(400.0));
|
||||||
|
buf.set_wrap(cosmic_text::Wrap::Word);
|
||||||
|
|
||||||
|
for line in buf.lines.iter_mut() {
|
||||||
|
line.set_align(Some(cosmic_text::Align::Left));
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.shape_until_scroll(false);
|
||||||
|
|
||||||
|
let mut font_atlantes = FontAtlasSets::new();
|
||||||
|
|
||||||
|
let mut glyphs = Vec::new();
|
||||||
|
let mut size = Vec2::new(0.0, 0.0);
|
||||||
|
|
||||||
|
let result = buffer
|
||||||
|
.layout_runs()
|
||||||
|
.flat_map(|run| {
|
||||||
|
size.x = size.x.max(run.line_w);
|
||||||
|
size.y = size.y + run.line_height;
|
||||||
|
run.glyphs.iter().map(move |glyph| (glyph, run.line_y))
|
||||||
|
})
|
||||||
|
.try_for_each(|(glyph, line_y)| -> Result<(), Error> {
|
||||||
|
let font_id = *reverse_font_id_map.get(&glyph.font_id).unwrap();
|
||||||
|
let set = font_atlantes.sets.entry(font_id).or_default();
|
||||||
|
|
||||||
|
let physical = glyph.physical((0.0, 0.0), 1.0);
|
||||||
|
|
||||||
|
let glyph_info = set
|
||||||
|
.get_glyph_info(physical.cache_key)
|
||||||
|
.map(Ok)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
set.add_glyph(&mut font_system, &mut swash, physical.clone())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let pos = {
|
||||||
|
let x = glyph_info.offset.x as f32 + physical.x as f32;
|
||||||
|
let y = line_y.round() + physical.y as f32 - glyph_info.offset.y as f32;
|
||||||
|
|
||||||
|
Vec2::new(x, y)
|
||||||
|
};
|
||||||
|
|
||||||
|
let size = glyph_info.rect.size();
|
||||||
|
glyphs.push((glyph_info, pos, size));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
let (width, height) = {
|
||||||
|
let tmp = size.ceil().as_uvec2();
|
||||||
|
(tmp.x, tmp.y)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut image = image::RgbaImage::from_pixel(width, height, image::Rgba([0, 0, 0, 255]));
|
||||||
|
|
||||||
|
eprintln!("glyphs: {glyphs:#?}");
|
||||||
|
eprintln!("image: {width}x{height}");
|
||||||
|
|
||||||
|
for (info, pos, size) in glyphs {
|
||||||
|
let atlas_image = info.image.upgrade().unwrap();
|
||||||
|
let glyph = atlas_image.0.read();
|
||||||
|
|
||||||
|
let atlas_image = image::ImageBuffer::<image::Rgba<u8>, _>::from_raw(
|
||||||
|
glyph.width(),
|
||||||
|
glyph.height(),
|
||||||
|
glyph.bytes(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let glyph = atlas_image.view(
|
||||||
|
info.rect.top_left().x as u32,
|
||||||
|
info.rect.top_left().y as u32,
|
||||||
|
info.rect.width() as u32,
|
||||||
|
info.rect.height() as u32,
|
||||||
|
);
|
||||||
|
|
||||||
|
eprintln!("rect: {:?}", info.rect);
|
||||||
|
eprintln!("image_size: {:?}", image.dimensions());
|
||||||
|
|
||||||
|
image
|
||||||
|
.copy_from(&*glyph, pos.x as u32, pos.y as u32)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
image.save("rendered.png").unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "window"
|
name = "window"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
winit = { workspace = true }
|
winit = { workspace = true }
|
||||||
42
examples/bevy_integration.rs
Normal file
42
examples/bevy_integration.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
use bevy::a11y::AccessibilityPlugin;
|
||||||
|
use bevy::app::TerminalCtrlCHandlerPlugin;
|
||||||
|
use bevy::camera::CameraPlugin;
|
||||||
|
use bevy::diagnostic::DiagnosticsPlugin;
|
||||||
|
use bevy::input::InputPlugin;
|
||||||
|
use bevy::log::LogPlugin;
|
||||||
|
use bevy::mesh::MeshPlugin;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::state::app::StatesPlugin;
|
||||||
|
use bevy::text::TextPlugin;
|
||||||
|
use bevy::window::WindowPlugin;
|
||||||
|
use bevy::winit::WinitPlugin;
|
||||||
|
|
||||||
|
use bevy_vulkan_render::{RenderApp, RenderPlugin as VulkanRenderPlugin};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
eprintln!("Hello, world!");
|
||||||
|
let mut app = App::new();
|
||||||
|
|
||||||
|
app.add_plugins((
|
||||||
|
MinimalPlugins,
|
||||||
|
LogPlugin::default(),
|
||||||
|
TransformPlugin,
|
||||||
|
DiagnosticsPlugin,
|
||||||
|
AssetPlugin::default(),
|
||||||
|
ImagePlugin::default_nearest(),
|
||||||
|
WindowPlugin::default(),
|
||||||
|
AccessibilityPlugin,
|
||||||
|
TerminalCtrlCHandlerPlugin,
|
||||||
|
WinitPlugin::default(),
|
||||||
|
InputPlugin,
|
||||||
|
(
|
||||||
|
MeshPlugin,
|
||||||
|
CameraPlugin,
|
||||||
|
TextPlugin,
|
||||||
|
StatesPlugin,
|
||||||
|
VulkanRenderPlugin,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
app.run();
|
||||||
|
}
|
||||||
3
src/lib.rs
Normal file
3
src/lib.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
//! Empty crate
|
||||||
|
|
||||||
|
pub use bevy_vulkan_render as bevy_render;
|
||||||
Loading…
Reference in a new issue