Compare commits
17 commits
7640cf053b
...
2b09a2c4f8
Author | SHA1 | Date | |
---|---|---|---|
|
2b09a2c4f8 | ||
|
4cbf1f053b | ||
|
be9f00f63f | ||
|
44076fb5f0 | ||
|
40ea757543 | ||
|
b06c76f1e1 | ||
|
e03efa2803 | ||
|
40885eb9ce | ||
|
b2730fecbd | ||
|
0db6b7790d | ||
|
75e75ec706 | ||
|
5593abc3b5 | ||
|
b404b50b62 | ||
|
81c6b6f4ee | ||
|
f93513524e | ||
|
c70c1591db | ||
|
9802bec8b0 |
22
Cargo.toml
22
Cargo.toml
|
@ -14,21 +14,29 @@ debug = true
|
|||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.89"
|
||||
ash = "0.38.0"
|
||||
ash-window = "0.13.0"
|
||||
glam = {version = "0.29.0", features = ["bytemuck"]}
|
||||
thiserror = "2.0"
|
||||
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = {version ="0.3.18", features = ["env-filter"]}
|
||||
vk-mem = "0.4.0"
|
||||
vk-sync = "0.1.6"
|
||||
tinyvec = "1.8"
|
||||
|
||||
glam = {version = "0.29.0", features = ["bytemuck"]}
|
||||
rand = "0.8.5"
|
||||
bitflags = "2.6"
|
||||
petgraph = "0.7"
|
||||
thread_local = "1.1.8"
|
||||
|
||||
ash = "0.38.0"
|
||||
ash-window = "0.13.0"
|
||||
vk-mem = "0.4.0"
|
||||
vk-sync = "0.1.6"
|
||||
|
||||
arrayvec = "0.7.6"
|
||||
tinyvec = "1.8"
|
||||
indexmap = "2"
|
||||
petgraph = "0.7"
|
||||
itertools = "0.14.0"
|
||||
ahash = "0.8"
|
||||
|
||||
parking_lot = "0.12.3"
|
||||
|
||||
tokio = "1.42"
|
||||
futures = "0.3"
|
||||
|
|
Binary file not shown.
BIN
assets/fonts/Noto_Sans/NotoSans-VariableFont_wdth,wght.ttf
Normal file
BIN
assets/fonts/Noto_Sans/NotoSans-VariableFont_wdth,wght.ttf
Normal file
Binary file not shown.
93
assets/fonts/Noto_Sans/OFL.txt
Normal file
93
assets/fonts/Noto_Sans/OFL.txt
Normal file
|
@ -0,0 +1,93 @@
|
|||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
assets/fonts/Noto_Sans_SC/NotoSansSC-VariableFont_wght.ttf
Normal file
BIN
assets/fonts/Noto_Sans_SC/NotoSansSC-VariableFont_wght.ttf
Normal file
Binary file not shown.
93
assets/fonts/Noto_Sans_SC/OFL.txt
Normal file
93
assets/fonts/Noto_Sans_SC/OFL.txt
Normal file
|
@ -0,0 +1,93 @@
|
|||
Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
93
assets/fonts/Roboto/OFL.txt
Normal file
93
assets/fonts/Roboto/OFL.txt
Normal file
|
@ -0,0 +1,93 @@
|
|||
Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
assets/fonts/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf
Normal file
BIN
assets/fonts/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/Roboto/Roboto-VariableFont_wdth,wght.ttf
Normal file
BIN
assets/fonts/Roboto/Roboto-VariableFont_wdth,wght.ttf
Normal file
Binary file not shown.
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)
|
|
@ -6,7 +6,11 @@ edition = "2021"
|
|||
[dependencies]
|
||||
winit = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
smol = { workspace = true }
|
||||
glam = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
|
||||
renderer = { path = "../renderer" }
|
||||
|
||||
egui = { workspace = true }
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#![feature(result_flattening)]
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use renderer::Renderer;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use renderer::{render_graph, swapchain::WindowSurface, Renderer2};
|
||||
use tracing::info;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use winit::{
|
||||
|
@ -17,29 +19,34 @@ struct WindowState {
|
|||
egui_platform: egui_winit_platform::Platform,
|
||||
demo_app: egui_demo_lib::DemoWindows,
|
||||
scale_factor: f64,
|
||||
surface: WindowSurface,
|
||||
}
|
||||
|
||||
struct WinitState {
|
||||
last_resize_events: BTreeMap<WindowId, PhysicalSize<u32>>,
|
||||
window_attrs: WindowAttributes,
|
||||
windows2: BTreeMap<WindowId, WindowState>,
|
||||
renderer: Renderer<WindowId>,
|
||||
windows: BTreeMap<WindowId, WindowState>,
|
||||
renderer: Renderer2,
|
||||
last_redraw: std::time::Instant,
|
||||
egui_state: renderer::EguiState,
|
||||
}
|
||||
|
||||
impl WinitState {
|
||||
const BASE_WIDTH: u32 = 800;
|
||||
const BASE_HEIGHT: u32 = 600;
|
||||
fn new(window_title: String, display: DisplayHandle) -> WinitState {
|
||||
let renderer = Renderer2::new(display.as_raw()).expect("renderer");
|
||||
|
||||
Self {
|
||||
windows2: BTreeMap::new(),
|
||||
windows: BTreeMap::new(),
|
||||
last_resize_events: BTreeMap::new(),
|
||||
window_attrs: WindowAttributes::default()
|
||||
.with_title(window_title)
|
||||
.with_resizable(true)
|
||||
.with_inner_size(LogicalSize::new(Self::BASE_WIDTH, Self::BASE_HEIGHT)),
|
||||
// TODO: pass down this error and add some kind of error handling UI or dump
|
||||
renderer: Renderer::new(display.as_raw()).expect("renderer"),
|
||||
egui_state: renderer::EguiState::new(renderer.device().clone()).unwrap(),
|
||||
renderer,
|
||||
last_redraw: std::time::Instant::now(),
|
||||
}
|
||||
}
|
||||
|
@ -47,8 +54,10 @@ impl WinitState {
|
|||
fn handle_final_resize(&mut self, window_id: WindowId, new_size: PhysicalSize<u32>) {
|
||||
_ = (window_id, new_size);
|
||||
info!("TODO: implement resize events");
|
||||
if let Some(ctx) = self.renderer.window_contexts.get_mut(&window_id) {
|
||||
ctx.recreate_with(Some(renderer::Extent2D {
|
||||
if let Some(window) = self.windows.get(&window_id) {
|
||||
window
|
||||
.surface
|
||||
.recreate_with(Some(renderer::Extent2D {
|
||||
width: new_size.width,
|
||||
height: new_size.height,
|
||||
}))
|
||||
|
@ -64,26 +73,63 @@ impl WinitState {
|
|||
self.last_redraw.elapsed().as_millis()
|
||||
);
|
||||
self.last_redraw = std::time::Instant::now();
|
||||
if let Some(window) = self.windows2.get_mut(&window_id) {
|
||||
if let Some(window) = self.windows.get_mut(&window_id) {
|
||||
// egui
|
||||
|
||||
window.egui_platform.begin_pass();
|
||||
window.demo_app.ui(&window.egui_platform.context());
|
||||
let output = window.egui_platform.end_pass(Some(&window.window));
|
||||
|
||||
// self.renderer
|
||||
// .draw_egui(&window.egui_platform.context(), output)
|
||||
// .unwrap();
|
||||
|
||||
let textures_to_remove = output
|
||||
.textures_delta
|
||||
.free
|
||||
.iter()
|
||||
.filter_map(|id| self.egui_state.lookup_texture(*id))
|
||||
.collect::<Vec<_>>();
|
||||
// rendering
|
||||
self.renderer
|
||||
.debug_draw_egui(&window_id, &window.egui_platform.context(), output, || {
|
||||
window.window.pre_present_notify();
|
||||
})
|
||||
.inspect_err(|err| {
|
||||
tracing::error!("error encountered while drawing: {err}");
|
||||
})
|
||||
.expect("drawing");
|
||||
let render = self.renderer.draw_graph(&window.surface, |renderer, rg| {
|
||||
use renderer::rendering::{egui_pass, egui_pre_pass};
|
||||
let framebuffer = rg.get_framebuffer().unwrap();
|
||||
let dev = renderer.device().clone();
|
||||
|
||||
use renderer::ash::vk::Handle;
|
||||
use renderer::device::DeviceOwned;
|
||||
let [r, g, b]: [f32; 3] =
|
||||
rand::prelude::StdRng::seed_from_u64(window.surface.surface.handle().as_raw())
|
||||
.gen();
|
||||
render_graph::clear_pass(rg, renderer::util::Rgba([r, g, b, 1.0]), framebuffer);
|
||||
egui_pre_pass(
|
||||
&dev,
|
||||
rg,
|
||||
&mut renderer.texture_manager,
|
||||
&mut self.egui_state,
|
||||
&output,
|
||||
)?;
|
||||
|
||||
let _ = egui_pass(
|
||||
&dev,
|
||||
rg,
|
||||
&mut renderer.texture_manager,
|
||||
&mut renderer.samplers,
|
||||
&mut self.egui_state,
|
||||
&window.egui_platform.context(),
|
||||
output,
|
||||
framebuffer,
|
||||
)?;
|
||||
|
||||
renderer::Result::Ok(())
|
||||
});
|
||||
|
||||
match smol::block_on(render).flatten() {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
tracing::error!("encountered error while rendering: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
for tid in textures_to_remove {
|
||||
self.renderer.texture_manager_mut().remove_texture(tid);
|
||||
}
|
||||
|
||||
window.window.request_redraw();
|
||||
}
|
||||
}
|
||||
|
@ -91,8 +137,7 @@ impl WinitState {
|
|||
fn remove_window(&mut self, window_id: WindowId) {
|
||||
tracing::info!(window = u64::from(window_id), "window close requested");
|
||||
|
||||
self.windows2.remove(&window_id);
|
||||
self.renderer.window_contexts.remove(&window_id);
|
||||
self.windows.remove(&window_id);
|
||||
}
|
||||
|
||||
fn create_window(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||
|
@ -116,14 +161,16 @@ impl WinitState {
|
|||
height: size.height,
|
||||
};
|
||||
|
||||
let _ = self.renderer.new_window_context(
|
||||
let surface = self
|
||||
.renderer
|
||||
.create_surface(
|
||||
window.window_handle().expect("window handle").as_raw(),
|
||||
extent,
|
||||
window_id,
|
||||
window.window_handle().expect("window handle"),
|
||||
);
|
||||
)
|
||||
.expect("surface");
|
||||
|
||||
let scale_factor = window.scale_factor();
|
||||
self.windows2.insert(
|
||||
self.windows.insert(
|
||||
window_id,
|
||||
WindowState {
|
||||
window,
|
||||
|
@ -137,6 +184,7 @@ impl WinitState {
|
|||
..Default::default()
|
||||
},
|
||||
),
|
||||
surface,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -157,7 +205,7 @@ impl ApplicationHandler for WinitState {
|
|||
|
||||
self.last_resize_events.clear();
|
||||
|
||||
if self.windows2.is_empty() {
|
||||
if self.windows.is_empty() {
|
||||
event_loop.exit();
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +222,7 @@ impl ApplicationHandler for WinitState {
|
|||
// }
|
||||
// }
|
||||
|
||||
if let Some(window) = self.windows2.get_mut(&window_id) {
|
||||
if let Some(window) = self.windows.get_mut(&window_id) {
|
||||
window.egui_platform.handle_event(&event);
|
||||
}
|
||||
|
||||
|
@ -233,7 +281,7 @@ impl ApplicationHandler for WinitState {
|
|||
self.handle_draw_request(window_id);
|
||||
}
|
||||
winit::event::WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
||||
if let Some(window) = self.windows2.get_mut(&window_id) {
|
||||
if let Some(window) = self.windows.get_mut(&window_id) {
|
||||
window.scale_factor = scale_factor;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,30 +5,42 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
tinyvec = { workspace = true }
|
||||
arrayvec = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
petgraph = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
ahash = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
# tokio = {workspace = true, features = ["rt", "sync"]}
|
||||
dyn-clone = "1"
|
||||
anyhow = "1.0.89"
|
||||
ash = "0.38.0"
|
||||
ash-window = "0.13.0"
|
||||
parking_lot = { workspace = true }
|
||||
glam = { workspace = true }
|
||||
thiserror = {workspace = true}
|
||||
tracing = "0.1.40"
|
||||
vk-mem = "0.4.0"
|
||||
crossbeam = "0.8.4"
|
||||
parking_lot = "0.12.3"
|
||||
smol.workspace = true
|
||||
bitflags.workspace = true
|
||||
petgraph.workspace = true
|
||||
itertools.workspace = true
|
||||
indexmap.workspace = true
|
||||
futures.workspace = true
|
||||
bytemuck = { version = "1.21.0", features = ["derive"] }
|
||||
bitflags = { workspace = true }
|
||||
thread_local = {workspace = true}
|
||||
|
||||
thiserror = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
ash = { workspace = true }
|
||||
ash-window = { workspace = true }
|
||||
vk-mem = { workspace = true }
|
||||
|
||||
raw-window-handle = { workspace = true }
|
||||
egui = { workspace = true , features = ["bytemuck"]}
|
||||
egui_winit_platform = { workspace = true }
|
||||
|
||||
futures = { workspace = true }
|
||||
smol = { workspace = true }
|
||||
# tokio = {workspace = true, features = ["rt", "sync"]}
|
||||
|
||||
cfg-if = "1.0.0"
|
||||
dyn-clone = "1"
|
||||
crossbeam = "0.8.4"
|
||||
bytemuck = { version = "1.21.0", features = ["derive"] }
|
||||
|
||||
cosmic-text = "0.12.1"
|
||||
sys-locale = "0.3.2"
|
||||
guillotiere = "0.6.2"
|
||||
|
||||
[dev-dependencies]
|
||||
tracing-test = "0.2.5"
|
||||
image = "0.25.5"
|
||||
|
|
Binary file not shown.
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::layout(0)]] float2 pos;
|
||||
[[vk::layout(1)]] float4 color;
|
||||
}
|
||||
struct VertexOut {
|
||||
[[vk::layout(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;
|
||||
}
|
|
@ -70,19 +70,19 @@ impl SingleUseCommandPool {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait CommandBuffer: DeviceOwned<vk::CommandBuffer> {
|
||||
pub trait HasQueue: DeviceOwned<vk::CommandBuffer> {
|
||||
fn queue(&self) -> &Queue;
|
||||
}
|
||||
|
||||
impl CommandBuffer for SingleUseCommand {
|
||||
impl HasQueue for SingleUseCommand {
|
||||
fn queue(&self) -> &Queue {
|
||||
&self.pool.queue
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommandList<T: CommandBuffer>(pub Vec<T>);
|
||||
pub struct CommandList<T: HasQueue>(pub Vec<T>);
|
||||
|
||||
impl<T: CommandBuffer> CommandList<T> {
|
||||
impl<T: HasQueue> CommandList<T> {
|
||||
/// all commands in list must be allocated from the same queue.
|
||||
pub fn submit<'a>(
|
||||
&'a self,
|
||||
|
@ -215,6 +215,11 @@ impl SingleUseCommand {
|
|||
self.state.state()
|
||||
}
|
||||
|
||||
/// Safety: commandbuffer must not be accessed from multiple threads at the same time
|
||||
pub unsafe fn buffer(&self) -> vk::CommandBuffer {
|
||||
self.handle()
|
||||
}
|
||||
|
||||
pub fn end(&self) -> VkResult<()> {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
|
@ -224,14 +229,115 @@ impl SingleUseCommand {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Safety: commandbuffer must not be accessed from multiple threads at the same time
|
||||
pub unsafe fn buffer(&self) -> vk::CommandBuffer {
|
||||
self.handle()
|
||||
pub fn submit_fence(
|
||||
&self,
|
||||
wait: Option<(vk::Semaphore, vk::PipelineStageFlags)>,
|
||||
signal: Option<vk::Semaphore>,
|
||||
fence: Option<vk::Fence>,
|
||||
) -> VkResult<()> {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe { self.device().dev().end_command_buffer(self.handle())? };
|
||||
|
||||
let buffers = [self.handle()];
|
||||
let mut submit_info = vk::SubmitInfo::default().command_buffers(&buffers);
|
||||
|
||||
if let Some(semaphore) = signal.as_ref() {
|
||||
// SAFETY: T and [T;1] have the same layout
|
||||
submit_info = submit_info.signal_semaphores(unsafe {
|
||||
core::mem::transmute::<&vk::Semaphore, &[vk::Semaphore; 1]>(semaphore)
|
||||
});
|
||||
}
|
||||
if let Some((semaphore, stage)) = wait.as_ref() {
|
||||
submit_info = submit_info
|
||||
.wait_semaphores(core::slice::from_ref(semaphore))
|
||||
.wait_dst_stage_mask(core::slice::from_ref(stage));
|
||||
}
|
||||
|
||||
//pub fn copy_buffer_to_image(&self, image: &Image2D, buffer: &Buffer, )
|
||||
let fence = fence.unwrap_or(vk::Fence::null());
|
||||
self.pool.queue().with_locked(|queue| unsafe {
|
||||
self.device()
|
||||
.dev()
|
||||
.queue_submit(queue, &[submit_info], fence)
|
||||
})?;
|
||||
|
||||
pub fn image_barrier(
|
||||
self.state.set_pending();
|
||||
|
||||
tracing::trace!(
|
||||
"submitted queue {:?} and fence {:?}",
|
||||
self.pool.queue(),
|
||||
fence
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn submit_async<'a>(
|
||||
&'a self,
|
||||
wait: Option<(vk::Semaphore, vk::PipelineStageFlags)>,
|
||||
signal: Option<vk::Semaphore>,
|
||||
fence: Arc<sync::Fence>,
|
||||
) -> VkResult<FenceFuture<'a>> {
|
||||
self.submit_fence(wait, signal, Some(fence.fence()))?;
|
||||
|
||||
Ok(FenceFuture::new(fence))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn submit_blocking(
|
||||
self,
|
||||
wait: Option<(vk::Semaphore, vk::PipelineStageFlags)>,
|
||||
signal: Option<vk::Semaphore>,
|
||||
) -> VkResult<()> {
|
||||
let fence = Arc::new(sync::Fence::create(self.device().clone())?);
|
||||
let future = self.submit_async(wait, signal, fence)?;
|
||||
future.block()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl traits::CommandBufferExt for SingleUseCommand {}
|
||||
|
||||
pub mod traits {
|
||||
use super::*;
|
||||
|
||||
pub trait CommandBufferExt: DeviceOwned<vk::CommandBuffer> {
|
||||
fn submit_to_queue(
|
||||
self,
|
||||
queue: &Queue,
|
||||
wait: Option<(vk::Semaphore, vk::PipelineStageFlags)>,
|
||||
signal: Option<vk::Semaphore>,
|
||||
fence: Arc<sync::Fence>,
|
||||
) -> VkResult<impl std::future::Future<Output = ()>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
//assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe { self.device().dev().end_command_buffer(self.handle())? };
|
||||
|
||||
let buffers = [self.handle()];
|
||||
let mut submit_info = vk::SubmitInfo::default().command_buffers(&buffers);
|
||||
|
||||
if let Some(semaphore) = signal.as_ref() {
|
||||
submit_info = submit_info.signal_semaphores(core::slice::from_ref(semaphore));
|
||||
}
|
||||
if let Some((semaphore, stage)) = wait.as_ref() {
|
||||
submit_info = submit_info
|
||||
.wait_semaphores(core::slice::from_ref(semaphore))
|
||||
.wait_dst_stage_mask(core::slice::from_ref(stage));
|
||||
}
|
||||
|
||||
queue.with_locked(|queue| unsafe {
|
||||
self.device()
|
||||
.dev()
|
||||
.queue_submit(queue, &[submit_info], fence.fence())
|
||||
})?;
|
||||
|
||||
tracing::trace!("submitted queue {:?} and fence {:?}", queue, fence);
|
||||
|
||||
Ok(crate::sync::FenceFuture::new(fence))
|
||||
}
|
||||
|
||||
fn image_barrier(
|
||||
&self,
|
||||
image: vk::Image,
|
||||
aspects: vk::ImageAspectFlags,
|
||||
|
@ -243,7 +349,8 @@ impl SingleUseCommand {
|
|||
new_layout: vk::ImageLayout,
|
||||
queue_ownership_op: Option<QueueOwnership>,
|
||||
) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
|
||||
let (src_family, dst_family) = queue_ownership_op
|
||||
.map(|t| (t.src, t.dst))
|
||||
.unwrap_or((vk::QUEUE_FAMILY_IGNORED, vk::QUEUE_FAMILY_IGNORED));
|
||||
|
@ -277,17 +384,17 @@ impl SingleUseCommand {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn blit_images(
|
||||
fn blit_images(
|
||||
&self,
|
||||
src: &Image,
|
||||
src_region: util::Rect2D,
|
||||
dst: &Image,
|
||||
dst_region: util::Rect2D,
|
||||
) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
self.device().dev().cmd_blit_image(
|
||||
self.buffer(),
|
||||
self.handle(),
|
||||
src.image(),
|
||||
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||
dst.image(),
|
||||
|
@ -310,14 +417,14 @@ impl SingleUseCommand {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn copy_buffer_to_image(
|
||||
fn copy_buffer_to_image(
|
||||
&self,
|
||||
buffer: vk::Buffer,
|
||||
image: vk::Image,
|
||||
layout: vk::ImageLayout,
|
||||
regions: &[vk::BufferImageCopy],
|
||||
) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
self.device().dev().cmd_copy_buffer_to_image(
|
||||
self.handle(),
|
||||
|
@ -329,16 +436,16 @@ impl SingleUseCommand {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn copy_buffers(&self, src: vk::Buffer, dst: vk::Buffer, regions: &[vk::BufferCopy]) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
fn copy_buffers(&self, src: vk::Buffer, dst: vk::Buffer, regions: &[vk::BufferCopy]) {
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
self.device()
|
||||
.dev()
|
||||
.cmd_copy_buffer(self.handle(), src, dst, regions);
|
||||
}
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn copy_images(
|
||||
|
||||
fn copy_images(
|
||||
&self,
|
||||
src: vk::Image,
|
||||
src_layout: vk::ImageLayout,
|
||||
|
@ -346,7 +453,7 @@ impl SingleUseCommand {
|
|||
dst_layout: vk::ImageLayout,
|
||||
regions: &[vk::ImageCopy],
|
||||
) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
self.device().dev().cmd_copy_image(
|
||||
self.handle(),
|
||||
|
@ -359,7 +466,7 @@ impl SingleUseCommand {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn clear_color_image(
|
||||
fn clear_color_image(
|
||||
&self,
|
||||
image: vk::Image,
|
||||
format: vk::Format,
|
||||
|
@ -367,7 +474,7 @@ impl SingleUseCommand {
|
|||
color: crate::Rgba,
|
||||
subresources: &[vk::ImageSubresourceRange],
|
||||
) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
let clear_colors = match format.get_component_kind() {
|
||||
crate::util::FormatComponentKind::Float => vk::ClearColorValue {
|
||||
float32: color.into_f32(),
|
||||
|
@ -391,39 +498,39 @@ impl SingleUseCommand {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn begin_rendering(&self, rendering_info: vk::RenderingInfo<'_>) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
fn begin_rendering(&self, rendering_info: vk::RenderingInfo<'_>) {
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
self.device()
|
||||
.dev()
|
||||
.cmd_begin_rendering(self.buffer(), &rendering_info);
|
||||
.cmd_begin_rendering(self.handle(), &rendering_info);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_viewport(&self, viewports: &[vk::Viewport]) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
fn set_viewport(&self, viewports: &[vk::Viewport]) {
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
self.device()
|
||||
.dev()
|
||||
.cmd_set_viewport(self.buffer(), 0, viewports);
|
||||
.cmd_set_viewport(self.handle(), 0, viewports);
|
||||
}
|
||||
}
|
||||
pub fn set_scissors(&self, scissors: &[vk::Rect2D]) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
fn set_scissors(&self, scissors: &[vk::Rect2D]) {
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
self.device()
|
||||
.dev()
|
||||
.cmd_set_scissor(self.buffer(), 0, scissors);
|
||||
.cmd_set_scissor(self.handle(), 0, scissors);
|
||||
}
|
||||
}
|
||||
pub fn push_constants(
|
||||
fn push_constants(
|
||||
&self,
|
||||
layout: &PipelineLayout,
|
||||
stage: vk::ShaderStageFlags,
|
||||
offset: u32,
|
||||
bytes: &[u8],
|
||||
) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
self.device().dev().cmd_push_constants(
|
||||
self.handle(),
|
||||
|
@ -434,44 +541,44 @@ impl SingleUseCommand {
|
|||
);
|
||||
}
|
||||
}
|
||||
pub fn bind_pipeline(&self, pipeline: &Pipeline) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
fn bind_pipeline(&self, pipeline: &Pipeline) {
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
self.device().dev().cmd_bind_pipeline(
|
||||
self.buffer(),
|
||||
self.handle(),
|
||||
pipeline.bind_point(),
|
||||
pipeline.handle(),
|
||||
);
|
||||
}
|
||||
}
|
||||
pub fn bind_vertices(&self, buffer: vk::Buffer, offset: u64) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
fn bind_vertex_buffers(&self, buffers: &[vk::Buffer], offsets: &[u64]) {
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
self.device()
|
||||
.dev()
|
||||
.cmd_bind_vertex_buffers(self.buffer(), 0, &[buffer], &[offset]);
|
||||
.cmd_bind_vertex_buffers(self.handle(), 0, buffers, offsets);
|
||||
}
|
||||
}
|
||||
pub fn bind_indices(&self, buffer: vk::Buffer, offset: u64, kind: vk::IndexType) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
fn bind_indices(&self, buffer: vk::Buffer, offset: u64, kind: vk::IndexType) {
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
self.device()
|
||||
.dev()
|
||||
.cmd_bind_index_buffer(self.buffer(), buffer, offset, kind);
|
||||
.cmd_bind_index_buffer(self.handle(), buffer, offset, kind);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_descriptor_sets(
|
||||
fn bind_descriptor_sets(
|
||||
&self,
|
||||
layout: &PipelineLayout,
|
||||
bind_point: vk::PipelineBindPoint,
|
||||
descriptor_sets: &[vk::DescriptorSet],
|
||||
) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
use crate::device::DeviceOwned;
|
||||
unsafe {
|
||||
self.device().dev().cmd_bind_descriptor_sets(
|
||||
self.buffer(),
|
||||
self.handle(),
|
||||
bind_point,
|
||||
layout.handle(),
|
||||
0,
|
||||
|
@ -481,8 +588,7 @@ impl SingleUseCommand {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn draw_indexed(
|
||||
fn draw_indexed(
|
||||
&self,
|
||||
indices: u32,
|
||||
instances: u32,
|
||||
|
@ -490,10 +596,10 @@ impl SingleUseCommand {
|
|||
vertex_offset: i32,
|
||||
instance_offset: u32,
|
||||
) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
self.device().dev().cmd_draw_indexed(
|
||||
self.buffer(),
|
||||
self.handle(),
|
||||
indices,
|
||||
instances,
|
||||
index_offset,
|
||||
|
@ -503,11 +609,11 @@ impl SingleUseCommand {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn draw_indexed_indirect(&self, buffer: vk::Buffer, offset: u64, count: u32, stride: u32) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
fn draw_indexed_indirect(&self, buffer: vk::Buffer, offset: u64, count: u32, stride: u32) {
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
self.device().dev().cmd_draw_indexed_indirect(
|
||||
self.buffer(),
|
||||
self.handle(),
|
||||
buffer,
|
||||
offset,
|
||||
count,
|
||||
|
@ -516,30 +622,285 @@ impl SingleUseCommand {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn end_rendering(&self) {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
fn end_rendering(&self) {
|
||||
// assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe {
|
||||
self.device().dev().cmd_end_rendering(self.buffer());
|
||||
self.device().dev().cmd_end_rendering(self.handle());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn submit_fence(
|
||||
pub use command_pools::{CommandBuffer, CommandBufferFuture, CommandPool, CommandPools};
|
||||
|
||||
mod command_pools {
|
||||
use std::{borrow::Cow, cell::Cell, mem::ManuallyDrop, sync::Arc};
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use ash::{prelude::VkResult, vk};
|
||||
use parking_lot::Mutex;
|
||||
use smol::future::FutureExt;
|
||||
use thread_local::ThreadLocal;
|
||||
|
||||
use crate::{
|
||||
define_device_owned_handle,
|
||||
device::{Device, DeviceOwned},
|
||||
sync,
|
||||
util::MutexExt,
|
||||
Queue,
|
||||
};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
Vulkan(#[from] vk::Result),
|
||||
#[error("CommandBuffer allocation failed: wrong number of command buffers.")]
|
||||
NoCommandBuffersAllocated,
|
||||
#[error("Attempted to submit commandbuffer to incompatible queue.")]
|
||||
InvalidQueueSubmission,
|
||||
}
|
||||
|
||||
define_device_owned_handle! {
|
||||
#[derive()]
|
||||
pub CommandPool(vk::CommandPool) {
|
||||
family: u32,
|
||||
free_list: Mutex<Cell<(Vec<vk::CommandBufferLevel>, Vec<vk::CommandBuffer>)>>,
|
||||
} => |this| unsafe {
|
||||
this.device().dev().destroy_command_pool(this.handle(), None);
|
||||
}
|
||||
}
|
||||
|
||||
impl !Sync for CommandPool {}
|
||||
|
||||
impl CommandPool {
|
||||
pub fn new(
|
||||
dev: Device,
|
||||
family: u32,
|
||||
flags: vk::CommandPoolCreateFlags,
|
||||
name: Option<Cow<'static, str>>,
|
||||
) -> Result<CommandPool, vk::Result> {
|
||||
let pool_info = vk::CommandPoolCreateInfo::default()
|
||||
.queue_family_index(family)
|
||||
.flags(flags);
|
||||
|
||||
let pool = unsafe { dev.dev().create_command_pool(&pool_info, None)? };
|
||||
|
||||
Self::construct(dev, pool, name, family, Mutex::new(Default::default()))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub unsafe fn reset(&self) -> VkResult<()> {
|
||||
unsafe {
|
||||
self.device()
|
||||
.dev()
|
||||
.reset_command_pool(self.handle(), vk::CommandPoolResetFlags::empty())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free_command_buffers(&self) {
|
||||
self.free_list_mut(|levels, buffers| {
|
||||
unsafe {
|
||||
self.device()
|
||||
.dev()
|
||||
.free_command_buffers(self.handle(), &buffers);
|
||||
}
|
||||
levels.clear();
|
||||
buffers.clear();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn free_command_buffer(
|
||||
self: &Arc<Self>,
|
||||
level: vk::CommandBufferLevel,
|
||||
buffer: vk::CommandBuffer,
|
||||
) {
|
||||
self.free_list_mut(|levels, buffers| {
|
||||
levels.push(level);
|
||||
buffers.push(buffer);
|
||||
});
|
||||
}
|
||||
|
||||
fn free_list_mut<
|
||||
T,
|
||||
F: FnOnce(&mut Vec<vk::CommandBufferLevel>, &mut Vec<vk::CommandBuffer>) -> T,
|
||||
>(
|
||||
&self,
|
||||
cb: F,
|
||||
) -> T {
|
||||
self.free_list.with_locked(|cell| {
|
||||
let (mut levels, mut buffers) = cell.take();
|
||||
let t = cb(&mut levels, &mut buffers);
|
||||
cell.set((levels, buffers));
|
||||
t
|
||||
})
|
||||
}
|
||||
|
||||
pub fn alloc(
|
||||
self: &Arc<Self>,
|
||||
name: Option<Cow<'static, str>>,
|
||||
level: vk::CommandBufferLevel,
|
||||
flags: vk::CommandBufferUsageFlags,
|
||||
) -> Result<CommandBuffer, Error> {
|
||||
// TODO: is this really the right place?
|
||||
self.free_command_buffers();
|
||||
|
||||
let handle = {
|
||||
let info = vk::CommandBufferAllocateInfo::default()
|
||||
.command_pool(self.handle())
|
||||
.command_buffer_count(1)
|
||||
.level(level);
|
||||
|
||||
unsafe { self.device().dev().allocate_command_buffers(&info) }?
|
||||
.pop()
|
||||
.ok_or(Error::NoCommandBuffersAllocated)?
|
||||
};
|
||||
|
||||
let begin_info = vk::CommandBufferBeginInfo::default().flags(flags);
|
||||
unsafe {
|
||||
self.device()
|
||||
.dev()
|
||||
.begin_command_buffer(handle, &begin_info)?;
|
||||
}
|
||||
|
||||
Ok(CommandBuffer::construct(
|
||||
self.device().clone(),
|
||||
handle,
|
||||
name,
|
||||
self.clone(),
|
||||
level,
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn alloc_primary(
|
||||
self: &Arc<Self>,
|
||||
name: Option<Cow<'static, str>>,
|
||||
flags: vk::CommandBufferUsageFlags,
|
||||
) -> Result<CommandBuffer, Error> {
|
||||
self.alloc(name, vk::CommandBufferLevel::PRIMARY, flags)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for CommandPool {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("CommandPool")
|
||||
.field("inner", &self.inner)
|
||||
.field("family", &self.family)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandPoolsShard {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
per_family_pools: ArrayVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn find_or_default(&mut self, dev: &Device, family: u32) -> Arc<CommandPool> {
|
||||
match self
|
||||
.per_family_pools
|
||||
.binary_search_by_key(&family, |entry| entry.0)
|
||||
{
|
||||
Ok(i) => self.per_family_pools[i].1.clone(),
|
||||
Err(i) => {
|
||||
let pool = Arc::new(
|
||||
CommandPool::new(
|
||||
dev.clone(),
|
||||
family,
|
||||
vk::CommandPoolCreateFlags::empty(),
|
||||
Some(
|
||||
format!("commandpool-{:?}-{}", std::thread::current().id(), family)
|
||||
.into(),
|
||||
),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
self.per_family_pools
|
||||
.try_insert(i, (family, pool.clone()))
|
||||
.expect("too many command pools in thread local");
|
||||
|
||||
pool
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: this isn't safe lmao. assumes pools are unused when dropped
|
||||
unsafe impl Send for CommandPoolsShard {}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CommandPoolsShard {
|
||||
per_family_pools: ArrayVec<(u32, Arc<CommandPool>), 8>,
|
||||
}
|
||||
|
||||
pub struct CommandPools {
|
||||
device: Device,
|
||||
shards: ThreadLocal<crossbeam::utils::CachePadded<core::cell::Cell<CommandPoolsShard>>>,
|
||||
}
|
||||
|
||||
impl CommandPools {
|
||||
pub fn get_for_family(&self, family: u32) -> Arc<CommandPool> {
|
||||
let shard = self
|
||||
.shards
|
||||
.get_or(|| core::cell::Cell::new(CommandPoolsShard::new()).into());
|
||||
|
||||
let mut inner = shard.replace(CommandPoolsShard::new());
|
||||
let pool = inner.find_or_default(&self.device, family);
|
||||
_ = shard.replace(inner);
|
||||
|
||||
pool
|
||||
}
|
||||
|
||||
pub fn allocate_buffer(
|
||||
&self,
|
||||
family: u32,
|
||||
name: Option<Cow<'static, str>>,
|
||||
level: vk::CommandBufferLevel,
|
||||
flags: vk::CommandBufferUsageFlags,
|
||||
) -> Result<CommandBuffer, Error> {
|
||||
self.get_for_family(family).alloc(name, level, flags)
|
||||
}
|
||||
}
|
||||
|
||||
define_device_owned_handle! {
|
||||
#[derive(Debug)]
|
||||
pub CommandBuffer(vk::CommandBuffer) {
|
||||
pool: Arc<CommandPool>,
|
||||
#[allow(unused)]
|
||||
level: vk::CommandBufferLevel,
|
||||
} => |this| unsafe {
|
||||
this.device().dev().free_command_buffers(this.pool.handle(), &[this.handle()]);
|
||||
}
|
||||
}
|
||||
|
||||
impl !Sync for CommandBuffer {}
|
||||
impl !Send for CommandBuffer {}
|
||||
|
||||
impl super::traits::CommandBufferExt for CommandBuffer {}
|
||||
|
||||
impl CommandBuffer {
|
||||
pub fn submit(
|
||||
self,
|
||||
queue: &Queue,
|
||||
wait: Option<(vk::Semaphore, vk::PipelineStageFlags)>,
|
||||
signal: Option<vk::Semaphore>,
|
||||
fence: Option<vk::Fence>,
|
||||
) -> VkResult<()> {
|
||||
assert_eq!(self.state(), CommandBufferState::Recording);
|
||||
unsafe { self.device().dev().end_command_buffer(self.handle())? };
|
||||
fence: Arc<sync::Fence>,
|
||||
) -> Result<CommandBufferFuture, Error> {
|
||||
let this = ManuallyDrop::new(self);
|
||||
|
||||
let buffers = [self.handle()];
|
||||
if queue.1 != this.pool.family {
|
||||
tracing::error!("attempted to submit commandbuffer to incompatible queue.");
|
||||
return Err(Error::InvalidQueueSubmission);
|
||||
}
|
||||
unsafe {
|
||||
this.device().dev().end_command_buffer(this.handle())?;
|
||||
}
|
||||
|
||||
let buffers = [this.handle()];
|
||||
let mut submit_info = vk::SubmitInfo::default().command_buffers(&buffers);
|
||||
|
||||
if let Some(semaphore) = signal.as_ref() {
|
||||
// SAFETY: T and [T;1] have the same layout
|
||||
submit_info = submit_info.signal_semaphores(unsafe {
|
||||
core::mem::transmute::<&vk::Semaphore, &[vk::Semaphore; 1]>(semaphore)
|
||||
});
|
||||
submit_info = submit_info.signal_semaphores(core::slice::from_ref(semaphore));
|
||||
}
|
||||
if let Some((semaphore, stage)) = wait.as_ref() {
|
||||
submit_info = submit_info
|
||||
|
@ -547,41 +908,48 @@ impl SingleUseCommand {
|
|||
.wait_dst_stage_mask(core::slice::from_ref(stage));
|
||||
}
|
||||
|
||||
let fence = fence.unwrap_or(vk::Fence::null());
|
||||
self.pool.queue().with_locked(|queue| unsafe {
|
||||
self.device()
|
||||
queue.with_locked(|queue| unsafe {
|
||||
this.device()
|
||||
.dev()
|
||||
.queue_submit(queue, &[submit_info], fence)
|
||||
.queue_submit(queue, &[submit_info], fence.fence())
|
||||
})?;
|
||||
tracing::trace!(
|
||||
"submitted queue {:?} and fence {:?}",
|
||||
self.pool.queue(),
|
||||
fence
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn submit_async<'a>(
|
||||
&'a self,
|
||||
wait: Option<(vk::Semaphore, vk::PipelineStageFlags)>,
|
||||
signal: Option<vk::Semaphore>,
|
||||
fence: Arc<sync::Fence>,
|
||||
) -> VkResult<FenceFuture<'a>> {
|
||||
self.submit_fence(wait, signal, Some(fence.fence()))?;
|
||||
|
||||
Ok(FenceFuture::new(fence))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn submit_blocking(
|
||||
self,
|
||||
wait: Option<(vk::Semaphore, vk::PipelineStageFlags)>,
|
||||
signal: Option<vk::Semaphore>,
|
||||
) -> VkResult<()> {
|
||||
let fence = Arc::new(sync::Fence::create(self.device().clone())?);
|
||||
let future = self.submit_async(wait, signal, fence)?;
|
||||
future.block()?;
|
||||
Ok(())
|
||||
Ok(CommandBufferFuture {
|
||||
inner: sync::FenceFuture::new(fence),
|
||||
pool: this.pool.clone(),
|
||||
buffer: this.handle(),
|
||||
level: this.level,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommandBufferFuture {
|
||||
inner: sync::FenceFuture<'static>,
|
||||
pool: Arc<CommandPool>,
|
||||
buffer: vk::CommandBuffer,
|
||||
level: vk::CommandBufferLevel,
|
||||
}
|
||||
|
||||
impl CommandBufferFuture {
|
||||
pub fn block(&self) -> VkResult<()> {
|
||||
self.inner.block()
|
||||
}
|
||||
}
|
||||
|
||||
impl core::future::Future for CommandBufferFuture {
|
||||
type Output = <sync::FenceFuture<'static> as core::future::Future>::Output;
|
||||
|
||||
fn poll(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Self::Output> {
|
||||
self.inner.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CommandBufferFuture {
|
||||
fn drop(&mut self) {
|
||||
self.pool.free_command_buffer(self.level, self.buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
43
crates/renderer/src/debug.rs
Normal file
43
crates/renderer/src/debug.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use ash::vk;
|
||||
use tracing::{event, Level};
|
||||
|
||||
unsafe fn str_from_raw_parts<'a>(str: *const i8) -> std::borrow::Cow<'a, str> {
|
||||
use std::{borrow::Cow, ffi};
|
||||
if str.is_null() {
|
||||
Cow::from("")
|
||||
} else {
|
||||
ffi::CStr::from_ptr(str).to_string_lossy()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) unsafe extern "system" fn debug_callback(
|
||||
message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
|
||||
message_type: vk::DebugUtilsMessageTypeFlagsEXT,
|
||||
callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>,
|
||||
user_data: *mut core::ffi::c_void,
|
||||
) -> vk::Bool32 {
|
||||
_ = user_data;
|
||||
let callback_data = *callback_data;
|
||||
let message_id_number = callback_data.message_id_number;
|
||||
|
||||
let message_id_name = str_from_raw_parts(callback_data.p_message_id_name);
|
||||
let message = str_from_raw_parts(callback_data.p_message);
|
||||
|
||||
match message_severity {
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => {
|
||||
event!(target: "VK::DebugUtils", Level::ERROR, "{message_type:?} [{message_id_name}({message_id_number})]: {message}");
|
||||
}
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE => {
|
||||
event!(target: "VK::DebugUtils", Level::TRACE, "{message_type:?} [{message_id_name}({message_id_number})]: {message}");
|
||||
}
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::INFO => {
|
||||
event!(target: "VK::DebugUtils", Level::INFO, "{message_type:?} [{message_id_name}({message_id_number})]: {message}");
|
||||
}
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => {
|
||||
event!(target: "VK::DebugUtils", Level::WARN, "{message_type:?} [{message_id_name}({message_id_number})]: {message}");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
vk::FALSE
|
||||
}
|
|
@ -1,13 +1,23 @@
|
|||
use std::{borrow::Cow, collections::BTreeMap, ops::Deref, sync::Arc};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{BTreeMap, BTreeSet, HashMap},
|
||||
ffi::{CStr, CString},
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use ash::{
|
||||
khr,
|
||||
prelude::VkResult,
|
||||
vk::{self, Handle},
|
||||
};
|
||||
use raw_window_handle::RawDisplayHandle;
|
||||
use tinyvec::{array_vec, ArrayVec};
|
||||
|
||||
use crate::{sync, Instance, PhysicalDevice, Queue};
|
||||
use crate::{
|
||||
make_extention_properties, sync, Error, ExtendsDeviceProperties2Debug, Instance,
|
||||
PhysicalDevice, PhysicalDeviceFeatures, PhysicalDeviceProperties, Queue, Result, VkNameList,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DeviceQueueFamilies {
|
||||
|
@ -16,6 +26,7 @@ pub struct DeviceQueueFamilies {
|
|||
pub(crate) present: (u32, u32),
|
||||
pub(crate) async_compute: (u32, u32),
|
||||
pub(crate) transfer: (u32, u32),
|
||||
pub(crate) properties: Box<[vk::QueueFamilyProperties]>,
|
||||
}
|
||||
|
||||
impl DeviceQueueFamilies {
|
||||
|
@ -68,12 +79,12 @@ bitflags::bitflags! {
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct QueueFlags: u32 {
|
||||
const GRAPHICS = 1 << 0;
|
||||
const PRESENT = 1 << 1;
|
||||
const ASYNC_COMPUTE = 1 << 2;
|
||||
const TRANSFER = 1 << 3;
|
||||
const ASYNC_COMPUTE = 1 << 1;
|
||||
const TRANSFER = 1 << 2;
|
||||
const PRESENT = 1 << 3;
|
||||
|
||||
const NONE = 0;
|
||||
const PRESENT_GRAPHICS = 1 << 0 | 1 << 1;
|
||||
const PRESENT_GRAPHICS = 1 << 0 | 1 << 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,11 +135,647 @@ impl core::fmt::Debug for DeviceInner {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Extension<'a> {
|
||||
pub name: &'a str,
|
||||
pub version: u32,
|
||||
}
|
||||
|
||||
impl<'a> std::hash::Hash for Extension<'a> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.name.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DeviceDesc<'a> {
|
||||
pub app_name: Option<&'a str>,
|
||||
pub app_version: u32,
|
||||
pub layers: &'a [&'a CStr],
|
||||
pub layer_settings: &'a [vk::LayerSettingEXT<'a>],
|
||||
pub instance_extensions: &'a [Extension<'a>],
|
||||
pub display_handle: Option<RawDisplayHandle>,
|
||||
pub features: crate::PhysicalDeviceFeatures,
|
||||
}
|
||||
|
||||
const VALIDATION_LAYER_NAME: &'static core::ffi::CStr = c"VK_LAYER_KHRONOS_validation";
|
||||
const DEBUG_LAYERS: [&'static core::ffi::CStr; 1] = [VALIDATION_LAYER_NAME];
|
||||
impl DeviceDesc<'_> {
|
||||
fn debug_layer_settings() -> &'static [vk::LayerSettingEXT<'static>; 3] {
|
||||
static SETTINGS: std::sync::LazyLock<[vk::LayerSettingEXT; 3]> =
|
||||
std::sync::LazyLock::new(|| {
|
||||
[
|
||||
vk::LayerSettingEXT::default()
|
||||
.layer_name(VALIDATION_LAYER_NAME)
|
||||
.setting_name(c"VK_KHRONOS_VALIDATION_VALIDATE_BEST_PRACTICES")
|
||||
.ty(vk::LayerSettingTypeEXT::BOOL32)
|
||||
.values(&[1]),
|
||||
vk::LayerSettingEXT::default()
|
||||
.layer_name(VALIDATION_LAYER_NAME)
|
||||
.setting_name(c"VK_KHRONOS_VALIDATION_VALIDATE_BEST_PRACTICES_AMD")
|
||||
.ty(vk::LayerSettingTypeEXT::BOOL32)
|
||||
.values(&[1]),
|
||||
vk::LayerSettingEXT::default()
|
||||
.layer_name(VALIDATION_LAYER_NAME)
|
||||
.setting_name(c"VK_KHRONOS_VALIDATION_VALIDATE_SYNC")
|
||||
.ty(vk::LayerSettingTypeEXT::BOOL32)
|
||||
.values(&[1]),
|
||||
]
|
||||
});
|
||||
&SETTINGS
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for DeviceDesc<'a> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
app_name: Default::default(),
|
||||
app_version: Default::default(),
|
||||
#[cfg(debug_assertions)]
|
||||
layers: &DEBUG_LAYERS,
|
||||
#[cfg(debug_assertions)]
|
||||
layer_settings: Self::debug_layer_settings(),
|
||||
#[cfg(not(debug_assertions))]
|
||||
layers: &[],
|
||||
#[cfg(not(debug_assertions))]
|
||||
layer_settings: &[],
|
||||
instance_extensions: Default::default(),
|
||||
display_handle: Default::default(),
|
||||
features: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DeviceBuilder;
|
||||
|
||||
impl DeviceBuilder {
|
||||
fn queue_family_supports_presentation(
|
||||
instance: &Instance,
|
||||
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(&instance.entry, &instance.instance);
|
||||
surface.get_physical_device_xlib_presentation_support(
|
||||
pdev,
|
||||
queue_family,
|
||||
display.display.unwrap().as_ptr() as _,
|
||||
display.screen as _,
|
||||
)
|
||||
//todo!("xlib")
|
||||
}
|
||||
RawDisplayHandle::Xcb(_xcb_display_handle) => todo!("xcb"),
|
||||
RawDisplayHandle::Wayland(wayland_display_handle) => {
|
||||
let surface = ash::khr::wayland_surface::Instance::new(
|
||||
&instance.entry,
|
||||
&instance.instance,
|
||||
);
|
||||
surface.get_physical_device_wayland_presentation_support(
|
||||
pdev,
|
||||
queue_family,
|
||||
wayland_display_handle.display.cast().as_mut(),
|
||||
)
|
||||
}
|
||||
RawDisplayHandle::Drm(_) => {
|
||||
todo!()
|
||||
}
|
||||
RawDisplayHandle::Windows(_) => {
|
||||
ash::khr::win32_surface::Instance::new(&instance.entry, &instance.instance)
|
||||
.get_physical_device_win32_presentation_support(pdev, queue_family)
|
||||
}
|
||||
_ => panic!("unsupported platform"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn select_pdev_queue_families(
|
||||
instance: &Instance,
|
||||
display_handle: Option<RawDisplayHandle>,
|
||||
pdev: vk::PhysicalDevice,
|
||||
) -> DeviceQueueFamilies {
|
||||
let queue_familiy_properties = unsafe {
|
||||
instance
|
||||
.instance
|
||||
.get_physical_device_queue_family_properties(pdev)
|
||||
};
|
||||
|
||||
struct QueueFamily {
|
||||
num_queues: u32,
|
||||
is_present: bool,
|
||||
is_compute: bool,
|
||||
is_graphics: bool,
|
||||
is_transfer: bool,
|
||||
}
|
||||
|
||||
impl QueueFamily {
|
||||
#[allow(dead_code)]
|
||||
fn is_graphics_and_compute(&self) -> bool {
|
||||
self.is_compute && self.is_graphics
|
||||
}
|
||||
}
|
||||
|
||||
struct QueueFamilies(Vec<QueueFamily>);
|
||||
impl QueueFamilies {
|
||||
fn find_first<F>(&mut self, mut pred: F) -> Option<u32>
|
||||
where
|
||||
F: FnMut(&QueueFamily) -> bool,
|
||||
{
|
||||
if let Some((q, family)) = self
|
||||
.0
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.filter(|(_, family)| family.num_queues > 0)
|
||||
.find(|(_, family)| pred(family))
|
||||
{
|
||||
family.num_queues -= 1;
|
||||
Some(q as u32)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn find_best<F>(&mut self, mut pred: F) -> Option<u32>
|
||||
where
|
||||
F: FnMut(&QueueFamily) -> Option<u32>,
|
||||
{
|
||||
let (_, q, family) = self
|
||||
.0
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.filter_map(|(i, family)| {
|
||||
if family.num_queues == 0 {
|
||||
return None;
|
||||
}
|
||||
pred(family).map(|score| (score, i, family))
|
||||
})
|
||||
.max_by_key(|(score, _, _)| *score)?;
|
||||
family.num_queues -= 1;
|
||||
Some(q as u32)
|
||||
}
|
||||
}
|
||||
|
||||
let mut queue_families = QueueFamilies(
|
||||
queue_familiy_properties
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, family)| {
|
||||
let q = i as u32;
|
||||
let is_graphics = family.queue_flags.contains(vk::QueueFlags::GRAPHICS);
|
||||
let is_compute = family.queue_flags.contains(vk::QueueFlags::COMPUTE);
|
||||
let is_transfer = family.queue_flags.contains(vk::QueueFlags::TRANSFER)
|
||||
|| is_compute
|
||||
|| is_graphics;
|
||||
let is_present = display_handle
|
||||
.map(|display_handle| {
|
||||
Self::queue_family_supports_presentation(
|
||||
instance,
|
||||
pdev,
|
||||
q,
|
||||
display_handle,
|
||||
)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
QueueFamily {
|
||||
num_queues: family.queue_count,
|
||||
is_compute,
|
||||
is_graphics,
|
||||
is_present,
|
||||
is_transfer,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let graphics = queue_families
|
||||
.find_best(|family| {
|
||||
if !family.is_graphics {
|
||||
return None;
|
||||
}
|
||||
// a queue with Graphics+Compute is guaranteed to exist
|
||||
Some(family.is_compute as u32 * 2 + family.is_present as u32)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// find present queue first because it is rather more important than a secondary compute queue
|
||||
let present =
|
||||
if !queue_families.0.get(graphics as usize).unwrap().is_present {
|
||||
queue_families.find_first(|family| family.is_present)
|
||||
} else {
|
||||
None
|
||||
}.or({
|
||||
if display_handle.is_none() {
|
||||
// in this case the graphics queue will be used by default
|
||||
tracing::info!("no present queue available, using graphics queue as fallback for headless_surface");
|
||||
Some(graphics)
|
||||
} else {
|
||||
tracing::warn!("no present queue available, this is unexpected!");
|
||||
None}
|
||||
});
|
||||
|
||||
let async_compute = queue_families.find_first(|family| family.is_compute);
|
||||
let transfer = queue_families.find_first(|family| family.is_transfer);
|
||||
|
||||
let mut unique_families = BTreeMap::<u32, u32>::new();
|
||||
|
||||
let mut helper = |family: u32| {
|
||||
use std::collections::btree_map::Entry;
|
||||
let index = match unique_families.entry(family) {
|
||||
Entry::Vacant(vacant_entry) => {
|
||||
vacant_entry.insert(1);
|
||||
0
|
||||
}
|
||||
Entry::Occupied(mut occupied_entry) => {
|
||||
let idx = occupied_entry.get_mut();
|
||||
*idx += 1;
|
||||
*idx - 1
|
||||
}
|
||||
};
|
||||
|
||||
(family, index)
|
||||
};
|
||||
|
||||
let graphics = helper(graphics);
|
||||
let async_compute = async_compute.map(|f| helper(f)).unwrap_or(graphics);
|
||||
let transfer = transfer.map(|f| helper(f)).unwrap_or(async_compute);
|
||||
let present = present.map(|f| helper(f)).unwrap_or(graphics);
|
||||
|
||||
let families = unique_families
|
||||
.into_iter()
|
||||
.filter(|&(_family, count)| count > 0)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// family of each queue, of which one is allocated for each queue, with
|
||||
// graphics being the fallback queue for compute and transfer, and
|
||||
// present possibly being `None`, in which case it is Graphics
|
||||
let queues = DeviceQueueFamilies {
|
||||
families,
|
||||
graphics,
|
||||
async_compute,
|
||||
transfer,
|
||||
present,
|
||||
properties: queue_familiy_properties.into_boxed_slice(),
|
||||
};
|
||||
|
||||
queues
|
||||
}
|
||||
|
||||
fn choose_physical_device(
|
||||
instance: &Instance,
|
||||
display_handle: Option<RawDisplayHandle>,
|
||||
requirements: &PhysicalDeviceFeatures,
|
||||
extra_properties: Vec<Box<dyn ExtendsDeviceProperties2Debug>>,
|
||||
) -> Result<PhysicalDevice> {
|
||||
let pdevs = unsafe { instance.instance.enumerate_physical_devices()? };
|
||||
|
||||
let (pdev, properties) = pdevs
|
||||
.into_iter()
|
||||
.map(|pdev| {
|
||||
let mut props = PhysicalDeviceProperties::default().extra_properties(
|
||||
extra_properties
|
||||
.iter()
|
||||
.map(|b| dyn_clone::clone_box(&**b))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
props.query(&instance.instance, pdev);
|
||||
|
||||
(pdev, props)
|
||||
})
|
||||
// filter devices which dont support the version of Vulkan we are requesting
|
||||
.filter(|(_, props)| props.base.api_version >= requirements.version)
|
||||
// filter devices which don't support the device extensions we
|
||||
// are requesting
|
||||
// TODO: figure out a way to fall back to some
|
||||
// device which doesn't support all of the extensions.
|
||||
.filter(|(pdev, _)| {
|
||||
let query_features =
|
||||
PhysicalDeviceFeatures::query(&instance.instance, *pdev).unwrap();
|
||||
|
||||
requirements.compatible_with(&query_features)
|
||||
})
|
||||
.max_by_key(|(_, props)| {
|
||||
let score = match props.base.device_type {
|
||||
vk::PhysicalDeviceType::DISCRETE_GPU => 5,
|
||||
vk::PhysicalDeviceType::INTEGRATED_GPU => 4,
|
||||
vk::PhysicalDeviceType::VIRTUAL_GPU => 3,
|
||||
vk::PhysicalDeviceType::CPU => 2,
|
||||
vk::PhysicalDeviceType::OTHER => 1,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// score based on limits or other properties
|
||||
|
||||
score
|
||||
})
|
||||
.ok_or(Error::NoPhysicalDevice)?;
|
||||
|
||||
Ok(PhysicalDevice {
|
||||
queue_families: Self::select_pdev_queue_families(instance, display_handle, pdev),
|
||||
pdev,
|
||||
properties,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_available_extensions(
|
||||
entry: &ash::Entry,
|
||||
layers: &[&CStr],
|
||||
) -> Result<Vec<ash::vk::ExtensionProperties>> {
|
||||
unsafe {
|
||||
let extensions = core::iter::once(entry.enumerate_instance_extension_properties(None))
|
||||
.chain(
|
||||
layers
|
||||
.iter()
|
||||
.map(|&layer| entry.enumerate_instance_extension_properties(Some(layer))),
|
||||
)
|
||||
.filter_map(|result| result.ok())
|
||||
.flatten()
|
||||
.collect::<Vec<ash::vk::ExtensionProperties>>();
|
||||
|
||||
Ok(extensions)
|
||||
}
|
||||
}
|
||||
|
||||
/// returns a tuple of supported/enabled extensions and unsupported/requested extensions
|
||||
fn get_extensions<'a>(
|
||||
entry: &ash::Entry,
|
||||
layers: &[&'a CStr],
|
||||
extensions: impl Iterator<Item = Extension<'a>> + 'a,
|
||||
display_handle: Option<RawDisplayHandle>,
|
||||
) -> Result<(Vec<Extension<'a>>, Vec<Extension<'a>>)> {
|
||||
unsafe {
|
||||
let available_extensions = Self::get_available_extensions(entry, layers)?;
|
||||
|
||||
let available_extension_names = available_extensions
|
||||
.iter()
|
||||
.filter_map(|ext| {
|
||||
Some((
|
||||
ext.extension_name_as_c_str().ok()?.to_str().ok()?,
|
||||
ext.spec_version,
|
||||
))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut out_extensions = Vec::new();
|
||||
let mut unsupported_extensions = Vec::new();
|
||||
|
||||
for extension in extensions {
|
||||
if let Some(available_version) = available_extension_names.get(&extension.name)
|
||||
&& *available_version >= extension.version
|
||||
{
|
||||
out_extensions.push(extension);
|
||||
} else {
|
||||
unsupported_extensions.push(extension);
|
||||
}
|
||||
}
|
||||
|
||||
let required_extension_names = display_handle
|
||||
.map(|display_handle| ash_window::enumerate_required_extensions(display_handle))
|
||||
.unwrap_or(Ok(&[]))?;
|
||||
|
||||
for &extension in required_extension_names {
|
||||
let extension = core::ffi::CStr::from_ptr(extension);
|
||||
let extension = Extension {
|
||||
name: extension.to_str()?,
|
||||
version: 0,
|
||||
};
|
||||
|
||||
if let Some(available_version) = available_extension_names.get(&extension.name)
|
||||
&& *available_version >= extension.version
|
||||
{
|
||||
out_extensions.push(extension);
|
||||
} else {
|
||||
unsupported_extensions.push(extension);
|
||||
}
|
||||
}
|
||||
|
||||
Ok((out_extensions, unsupported_extensions))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_layers<'a>(
|
||||
entry: &ash::Entry,
|
||||
wants_layers: impl Iterator<Item = &'a CStr> + 'a,
|
||||
) -> core::result::Result<Vec<&'a CStr>, (Vec<&'a CStr>, Vec<&'a CStr>)> {
|
||||
unsafe {
|
||||
let wants_layers = wants_layers.collect::<Vec<_>>();
|
||||
|
||||
let available_layers = entry
|
||||
.enumerate_instance_layer_properties()
|
||||
.map_err(|_| (Vec::<&'a CStr>::new(), wants_layers.clone()))?;
|
||||
let available_layer_names = available_layers
|
||||
.iter()
|
||||
.map(|layer| layer.layer_name_as_c_str())
|
||||
.collect::<core::result::Result<BTreeSet<_>, _>>()
|
||||
.map_err(|_| (Vec::<&'a CStr>::new(), wants_layers.clone()))?;
|
||||
|
||||
let mut out_layers = Vec::new();
|
||||
let mut unsupported_layers = Vec::new();
|
||||
|
||||
for layer in wants_layers {
|
||||
if available_layer_names.contains(&layer) {
|
||||
out_layers.push(layer);
|
||||
} else {
|
||||
unsupported_layers.push(layer);
|
||||
}
|
||||
}
|
||||
|
||||
if !unsupported_layers.is_empty() {
|
||||
Err((out_layers, unsupported_layers))
|
||||
} else {
|
||||
Ok(out_layers)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Device(Arc<DeviceInner>);
|
||||
pub type WeakDevice = std::sync::Weak<DeviceInner>;
|
||||
|
||||
impl Device {
|
||||
pub fn new_from_default_desc(
|
||||
display_handle: Option<RawDisplayHandle>,
|
||||
with_instance_extensions: &[Extension<'_>],
|
||||
) -> crate::Result<Self> {
|
||||
Self::new_from_desc(DeviceDesc {
|
||||
app_name: Some("Vidya"),
|
||||
app_version: vk::make_api_version(0, 0, 1, 0),
|
||||
display_handle,
|
||||
features: crate::PhysicalDeviceFeatures::default()
|
||||
.version(vk::make_api_version(0, 1, 3, 0))
|
||||
.features10(
|
||||
vk::PhysicalDeviceFeatures::default()
|
||||
.sampler_anisotropy(true)
|
||||
.fill_mode_non_solid(true)
|
||||
.multi_draw_indirect(true),
|
||||
)
|
||||
.features11(
|
||||
vk::PhysicalDeviceVulkan11Features::default().shader_draw_parameters(true),
|
||||
)
|
||||
.features12(
|
||||
vk::PhysicalDeviceVulkan12Features::default()
|
||||
.shader_int8(true)
|
||||
.runtime_descriptor_array(true)
|
||||
.descriptor_binding_partially_bound(true)
|
||||
.shader_sampled_image_array_non_uniform_indexing(true)
|
||||
.descriptor_binding_sampled_image_update_after_bind(true)
|
||||
.storage_buffer8_bit_access(true),
|
||||
)
|
||||
.with_extension(
|
||||
make_extention_properties(
|
||||
ash::ext::mesh_shader::NAME,
|
||||
ash::ext::mesh_shader::SPEC_VERSION,
|
||||
),
|
||||
vk::PhysicalDeviceMeshShaderFeaturesEXT::default()
|
||||
.mesh_shader(true)
|
||||
.task_shader(true),
|
||||
)
|
||||
.with_extension(
|
||||
make_extention_properties(
|
||||
ash::ext::index_type_uint8::NAME,
|
||||
ash::ext::index_type_uint8::SPEC_VERSION,
|
||||
),
|
||||
vk::PhysicalDeviceIndexTypeUint8FeaturesEXT::default().index_type_uint8(true),
|
||||
)
|
||||
.with_extensions2([make_extention_properties(
|
||||
khr::spirv_1_4::NAME,
|
||||
khr::spirv_1_4::SPEC_VERSION,
|
||||
)]),
|
||||
instance_extensions: with_instance_extensions,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
pub fn new_from_desc(desc: DeviceDesc) -> crate::Result<Self> {
|
||||
tracing::debug!("creating new device with: {desc:#?}");
|
||||
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 app_info = vk::ApplicationInfo::default()
|
||||
.api_version(desc.features.version)
|
||||
.application_name(&app_name)
|
||||
.application_version(desc.app_version)
|
||||
.engine_name(c"VidyaEngine")
|
||||
.engine_version(vk::make_api_version(0, 0, 1, 0));
|
||||
|
||||
let mut validation_info =
|
||||
vk::LayerSettingsCreateInfoEXT::default().settings(&desc.layer_settings);
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let debug_layers = [Extension {
|
||||
name: "VK_EXT_layer_settings",
|
||||
version: 2,
|
||||
}];
|
||||
} else {
|
||||
let debug_layers = [];
|
||||
}
|
||||
};
|
||||
|
||||
let extra_instance_extensions = [Extension {
|
||||
name: "VK_EXT_debug_utils",
|
||||
version: 1,
|
||||
}]
|
||||
.into_iter()
|
||||
.chain(debug_layers.into_iter());
|
||||
|
||||
let layers = DeviceBuilder::get_layers(&entry, desc.layers.into_iter().cloned()).unwrap();
|
||||
|
||||
let (extensions, unsupported_extensions) = DeviceBuilder::get_extensions(
|
||||
&entry,
|
||||
&layers,
|
||||
desc.instance_extensions
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.chain(extra_instance_extensions),
|
||||
desc.display_handle,
|
||||
)?;
|
||||
|
||||
if !unsupported_extensions.is_empty() {
|
||||
tracing::error!(
|
||||
"extensions were requested but not supported by instance: {:?}",
|
||||
unsupported_extensions
|
||||
);
|
||||
}
|
||||
|
||||
let layers = VkNameList::from_strs(&layers);
|
||||
let extensions = crate::util::CStringList::from_iter(extensions.iter().map(|ext| ext.name));
|
||||
|
||||
let create_info = vk::InstanceCreateInfo::default()
|
||||
.application_info(&app_info)
|
||||
.enabled_extension_names(&extensions.strings)
|
||||
.enabled_layer_names(&layers.names)
|
||||
.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_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 debug_utils_instance = ash::ext::debug_utils::Instance::new(&entry, &instance);
|
||||
let debug_utils_messenger =
|
||||
unsafe { debug_utils_instance.create_debug_utils_messenger(&debug_info, None)? };
|
||||
|
||||
let surface_instance = ash::khr::surface::Instance::new(&entry, &instance);
|
||||
|
||||
let instance = Arc::new(Instance {
|
||||
instance,
|
||||
debug_utils: debug_utils_instance,
|
||||
debug_utils_messenger,
|
||||
surface: surface_instance,
|
||||
entry,
|
||||
});
|
||||
|
||||
let mut features = desc.features.with_extension2(make_extention_properties(
|
||||
khr::swapchain::NAME,
|
||||
khr::swapchain::SPEC_VERSION,
|
||||
));
|
||||
|
||||
//these are required for the renderpass
|
||||
let features13 = features.physical_features_13.get_or_insert_default();
|
||||
features13.synchronization2 = vk::TRUE;
|
||||
features13.dynamic_rendering = vk::TRUE;
|
||||
features13.maintenance4 = vk::TRUE;
|
||||
|
||||
// Consider this: switching physical device in game?
|
||||
// anything above this point is device agnostic, everything below would have to be recreated
|
||||
// additionally, pdev would have to be derived from a device and not a scoring function.
|
||||
|
||||
let pdev = DeviceBuilder::choose_physical_device(
|
||||
&instance,
|
||||
desc.display_handle,
|
||||
&features,
|
||||
vec![Box::new(
|
||||
vk::PhysicalDeviceMeshShaderPropertiesEXT::default(),
|
||||
)],
|
||||
)?;
|
||||
|
||||
tracing::trace!("pdev: {pdev:?}");
|
||||
let device = Device::new(instance.clone(), pdev, features)?;
|
||||
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
instance: Arc<Instance>,
|
||||
physical: PhysicalDevice,
|
||||
|
@ -242,9 +889,12 @@ impl Device {
|
|||
pub fn physical_device(&self) -> &PhysicalDevice {
|
||||
&self.0.physical
|
||||
}
|
||||
pub fn graphics_queue(&self) -> &Queue {
|
||||
pub fn main_queue(&self) -> &Queue {
|
||||
&self.0.main_queue
|
||||
}
|
||||
pub fn transfer_queue(&self) -> &Queue {
|
||||
&self.0.transfer_queue
|
||||
}
|
||||
pub fn present_queue(&self) -> &Queue {
|
||||
&self.0.present_queue
|
||||
}
|
||||
|
@ -331,8 +981,8 @@ impl AsRef<ash::khr::swapchain::Device> for DeviceAndQueues {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct DeviceOwnedDebugObject<T> {
|
||||
device: Device,
|
||||
object: T,
|
||||
pub(crate) device: Device,
|
||||
pub(crate) object: T,
|
||||
#[cfg(debug_assertions)]
|
||||
name: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
@ -403,19 +1053,20 @@ pub trait DeviceOwned<T> {
|
|||
macro_rules! define_device_owned_handle {
|
||||
($(#[$attr:meta])*
|
||||
$ty_vis:vis $ty:ident($handle:ty) {
|
||||
$($field_vis:vis $field_name:ident : $field_ty:ty),*
|
||||
$($(#[$field_attr:meta])* $field_vis:vis $field_name:ident : $field_ty:ty),*
|
||||
$(,)?
|
||||
} $(=> |$this:ident| $dtor:stmt)?) => {
|
||||
$(#[$attr])*
|
||||
$ty_vis struct $ty {
|
||||
inner: crate::device::DeviceOwnedDebugObject<$handle>,
|
||||
$(
|
||||
$(#[$field_attr])*
|
||||
$field_vis $field_name: $field_ty,
|
||||
)*
|
||||
}
|
||||
|
||||
impl crate::device::DeviceOwned<$handle> for $ty {
|
||||
fn device(&self) -> &Device {
|
||||
fn device(&self) -> &crate::device::Device {
|
||||
self.inner.dev()
|
||||
}
|
||||
fn handle(&self) -> $handle {
|
||||
|
|
|
@ -5,6 +5,7 @@ use indexmap::IndexMap;
|
|||
|
||||
use crate::{
|
||||
buffers::{Buffer, BufferDesc},
|
||||
commands::traits::CommandBufferExt,
|
||||
device::{self, DeviceOwned},
|
||||
images::{Image, ImageDesc, ImageViewDesc},
|
||||
render_graph::{
|
||||
|
@ -712,7 +713,7 @@ pub fn egui_pass(
|
|||
|
||||
cmd.bind_pipeline(&pipeline);
|
||||
cmd.bind_indices(indices.buffer(), 0, vk::IndexType::UINT32);
|
||||
cmd.bind_vertices(vertices.buffer(), 0);
|
||||
cmd.bind_vertex_buffers(&[vertices.buffer()], &[0]);
|
||||
cmd.push_constants(
|
||||
&pipeline_layout,
|
||||
vk::ShaderStageFlags::VERTEX,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -8,11 +8,11 @@ use std::{
|
|||
|
||||
use crate::{
|
||||
buffers::{Buffer, BufferDesc},
|
||||
commands, def_monotonic_id,
|
||||
commands::{self, traits::CommandBufferExt},
|
||||
def_monotonic_id,
|
||||
device::{self, DeviceOwned},
|
||||
images::{self, Image, ImageDesc},
|
||||
util::{self, Rgba, WithLifetime},
|
||||
SwapchainFrame,
|
||||
};
|
||||
use ash::vk;
|
||||
|
||||
|
@ -43,7 +43,7 @@ impl From<GraphResourceDesc> for GraphResource {
|
|||
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
pub enum GraphResource {
|
||||
Framebuffer(Arc<SwapchainFrame>),
|
||||
Framebuffer(Arc<Image>),
|
||||
ImportedImage(Arc<Image>),
|
||||
ImportedBuffer(Arc<Buffer>),
|
||||
Image(Arc<Image>),
|
||||
|
@ -63,7 +63,7 @@ impl GraphResource {
|
|||
|
||||
match self {
|
||||
GraphResource::Framebuffer(swapchain_frame) => {
|
||||
(swapchain_frame.index, swapchain_frame.image.handle()).hash(&mut state)
|
||||
(swapchain_frame.handle()).hash(&mut state)
|
||||
}
|
||||
GraphResource::ImportedImage(image) => image.handle().hash(&mut state),
|
||||
GraphResource::ImportedBuffer(buffer) => buffer.handle().hash(&mut state),
|
||||
|
@ -95,6 +95,7 @@ pub struct RenderContext<'a> {
|
|||
pub device: device::Device,
|
||||
pub cmd: commands::SingleUseCommand,
|
||||
pub resources: &'a [GraphResource],
|
||||
pub framebuffer: Option<GraphResourceId>,
|
||||
}
|
||||
|
||||
impl RenderContext<'_> {
|
||||
|
@ -102,7 +103,7 @@ impl RenderContext<'_> {
|
|||
self.resources.get(id.0 as usize).and_then(|res| match res {
|
||||
GraphResource::ImportedImage(arc) => Some(arc),
|
||||
GraphResource::Image(image) => Some(image),
|
||||
GraphResource::Framebuffer(fb) => Some(&fb.image),
|
||||
GraphResource::Framebuffer(fb) => Some(fb),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
@ -113,6 +114,14 @@ impl RenderContext<'_> {
|
|||
_ => None,
|
||||
})
|
||||
}
|
||||
pub fn get_framebuffer(&self) -> Option<&Image> {
|
||||
self.framebuffer
|
||||
.and_then(|rid| self.resources.get(rid.0 as usize))
|
||||
.and_then(|res| match res {
|
||||
GraphResource::Framebuffer(arc) => Some(arc.as_ref()),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
|
@ -349,6 +358,7 @@ pub struct RenderGraph {
|
|||
/// the rendergraph produces these resources. Any passes on which these
|
||||
/// outputs do not depend are pruned.
|
||||
outputs: BTreeMap<GraphResourceId, Access>,
|
||||
pub(crate) framebuffer: Option<GraphResourceId>,
|
||||
}
|
||||
|
||||
impl RenderGraph {
|
||||
|
@ -360,9 +370,14 @@ impl RenderGraph {
|
|||
resources: Vec::new(),
|
||||
pass_descs,
|
||||
outputs: BTreeMap::new(),
|
||||
framebuffer: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_framebuffer(&self) -> Option<GraphResourceId> {
|
||||
self.framebuffer
|
||||
}
|
||||
|
||||
fn get_next_resource_id(&mut self) -> GraphResourceId {
|
||||
GraphResourceId(self.resources.len() as u32)
|
||||
}
|
||||
|
@ -406,8 +421,14 @@ impl RenderGraph {
|
|||
self.import_resource(res, access)
|
||||
}
|
||||
|
||||
pub fn import_framebuffer(&mut self, frame: Arc<SwapchainFrame>) -> GraphResourceId {
|
||||
self.import_resource(GraphResource::Framebuffer(frame), Access::undefined())
|
||||
pub fn import_framebuffer(&mut self, frame: Arc<Image>) -> GraphResourceId {
|
||||
let rid = self.import_resource(
|
||||
GraphResource::Framebuffer(frame.clone()),
|
||||
Access::undefined(),
|
||||
);
|
||||
self.mark_as_output(rid, Access::present());
|
||||
self.framebuffer = Some(rid);
|
||||
rid
|
||||
}
|
||||
|
||||
pub fn add_pass(&mut self, pass: PassDesc) {
|
||||
|
@ -426,6 +447,7 @@ impl RenderGraph {
|
|||
.iter()
|
||||
.map(|(rid, access)| (*rid, *access))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.add_pass(PassDesc {
|
||||
reads: output_reads,
|
||||
writes: vec![],
|
||||
|
@ -444,6 +466,7 @@ impl RenderGraph {
|
|||
});
|
||||
|
||||
// create internal resources:
|
||||
util::timed("Create internal RenderGraph resources:", || {
|
||||
for (i, res) in self.resources.iter_mut().enumerate() {
|
||||
match res {
|
||||
GraphResource::ImageDesc(image_desc) => {
|
||||
|
@ -455,14 +478,19 @@ impl RenderGraph {
|
|||
}
|
||||
GraphResource::BufferDesc(buffer_desc) => {
|
||||
tracing::trace!("creating resource #{i:?} with {buffer_desc:?}");
|
||||
*res = GraphResource::Buffer(Buffer::new(device.clone(), buffer_desc.clone())?);
|
||||
*res = GraphResource::Buffer(Buffer::new(
|
||||
device.clone(),
|
||||
buffer_desc.clone(),
|
||||
)?);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
ash::prelude::VkResult::Ok(())
|
||||
})?;
|
||||
|
||||
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 cmds = topo
|
||||
|
@ -493,6 +521,7 @@ impl RenderGraph {
|
|||
device: device.clone(),
|
||||
cmd,
|
||||
resources,
|
||||
framebuffer: self.framebuffer,
|
||||
};
|
||||
|
||||
for pass in passes {
|
||||
|
@ -531,7 +560,7 @@ impl RenderGraph {
|
|||
) {
|
||||
let barrier: Barrier = match res {
|
||||
GraphResource::Framebuffer(arc) => {
|
||||
image_barrier(arc.image.handle(), arc.image.format(), from, to, None).into()
|
||||
image_barrier(arc.handle(), arc.format(), from, to, None).into()
|
||||
}
|
||||
GraphResource::ImportedImage(arc) => {
|
||||
image_barrier(arc.handle(), arc.format(), from, to, None).into()
|
||||
|
|
|
@ -1 +1,384 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use ash::vk;
|
||||
use glam::{f32::Mat4, vec3};
|
||||
|
||||
pub use crate::egui_pass::{egui_pass, egui_pre_pass};
|
||||
|
||||
use crate::{
|
||||
buffers::{Buffer, BufferDesc},
|
||||
commands::{self, traits::CommandBufferExt},
|
||||
device::{Device, DeviceOwned},
|
||||
images::ImageViewDesc,
|
||||
pipeline,
|
||||
render_graph::{
|
||||
buffer_barrier, Access, GraphResourceId, PassDesc, RecordFn, RenderContext, RenderGraph,
|
||||
},
|
||||
sync,
|
||||
util::Rgba8,
|
||||
Result,
|
||||
};
|
||||
|
||||
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_usage: vk_mem::MemoryUsage::AutoPreferHost,
|
||||
alloc_flags: vk_mem::AllocationCreateFlags::MAPPED
|
||||
| vk_mem::AllocationCreateFlags::HOST_ACCESS_SEQUENTIAL_WRITE
|
||||
| vk_mem::AllocationCreateFlags::STRATEGY_FIRST_FIT,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
{
|
||||
let mut map = staging.map()?;
|
||||
|
||||
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_usage: vk_mem::MemoryUsage::AutoPreferDevice,
|
||||
..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_usage: vk_mem::MemoryUsage::AutoPreferDevice,
|
||||
..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_usage: vk_mem::MemoryUsage::AutoPreferDevice,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let pool = commands::SingleUseCommandPool::new(dev.clone(), dev.main_queue().clone())?;
|
||||
|
||||
let cmd = pool.alloc()?;
|
||||
|
||||
cmd.copy_buffers(
|
||||
staging.handle(),
|
||||
positions.handle(),
|
||||
&[vk::BufferCopy {
|
||||
src_offset: 0,
|
||||
dst_offset: 0,
|
||||
size: positions_size as u64,
|
||||
}],
|
||||
);
|
||||
cmd.copy_buffers(
|
||||
staging.handle(),
|
||||
indices.handle(),
|
||||
&[vk::BufferCopy {
|
||||
src_offset: indices_offset as u64,
|
||||
dst_offset: 0,
|
||||
size: indices_size as u64,
|
||||
}],
|
||||
);
|
||||
cmd.copy_buffers(
|
||||
staging.handle(),
|
||||
colors.handle(),
|
||||
&[vk::BufferCopy {
|
||||
src_offset: colors_offset as u64,
|
||||
dst_offset: 0,
|
||||
size: colors_size as u64,
|
||||
}],
|
||||
);
|
||||
|
||||
let barriers = [
|
||||
buffer_barrier(
|
||||
positions.handle(),
|
||||
0,
|
||||
positions.len(),
|
||||
Access::transfer_write(),
|
||||
Access::vertex_read(),
|
||||
None,
|
||||
),
|
||||
buffer_barrier(
|
||||
indices.handle(),
|
||||
0,
|
||||
indices.len(),
|
||||
Access::transfer_write(),
|
||||
Access::index_read(),
|
||||
None,
|
||||
),
|
||||
buffer_barrier(
|
||||
colors.handle(),
|
||||
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::create(dev.clone())?))?;
|
||||
|
||||
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(
|
||||
device.clone(),
|
||||
pipeline::PipelineDesc::Graphics(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.get_view(ImageViewDesc {
|
||||
kind: vk::ImageViewType::TYPE_2D,
|
||||
format: target.format(),
|
||||
aspect: vk::ImageAspectFlags::COLOR,
|
||||
..Default::default()
|
||||
})?)
|
||||
.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.buffer(), 0, vk::IndexType::UINT32);
|
||||
cmd.bind_vertex_buffers(&[positions.handle(), colors.handle()], &[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(())
|
||||
}
|
||||
}
|
||||
|
|
763
crates/renderer/src/swapchain.rs
Normal file
763
crates/renderer/src/swapchain.rs
Normal file
|
@ -0,0 +1,763 @@
|
|||
use std::{
|
||||
marker::PhantomData,
|
||||
sync::{
|
||||
atomic::{AtomicU32, AtomicU64},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use ash::{
|
||||
prelude::VkResult,
|
||||
vk::{self, Handle},
|
||||
};
|
||||
use parking_lot::{RawMutex, RwLock};
|
||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
||||
|
||||
use crate::{
|
||||
define_device_owned_handle,
|
||||
device::{Device, DeviceOwned},
|
||||
images, sync,
|
||||
util::RawMutexGuard,
|
||||
Instance, Result,
|
||||
};
|
||||
|
||||
define_device_owned_handle! {
|
||||
#[derive(Debug)]
|
||||
pub Surface(vk::SurfaceKHR) {
|
||||
} => |this| unsafe {
|
||||
this.device().instance().surface.destroy_surface(this.handle(), None);
|
||||
}
|
||||
}
|
||||
|
||||
impl Surface {
|
||||
#[allow(dead_code)]
|
||||
pub fn headless(device: Device) -> Result<Self> {
|
||||
unsafe {
|
||||
let instance = device.instance();
|
||||
let headless_instance =
|
||||
ash::ext::headless_surface::Instance::new(&instance.entry, &instance.instance);
|
||||
|
||||
let surface = headless_instance
|
||||
.create_headless_surface(&vk::HeadlessSurfaceCreateInfoEXT::default(), None)?;
|
||||
|
||||
Ok(Self::construct(
|
||||
device,
|
||||
surface,
|
||||
Some("headless-surface".into()),
|
||||
)?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
device: Device,
|
||||
display_handle: RawDisplayHandle,
|
||||
window_handle: raw_window_handle::RawWindowHandle,
|
||||
) -> Result<Self> {
|
||||
let instance = device.instance();
|
||||
let surface = unsafe {
|
||||
ash_window::create_surface(
|
||||
&instance.entry,
|
||||
&instance.instance,
|
||||
display_handle,
|
||||
window_handle,
|
||||
None,
|
||||
)?
|
||||
};
|
||||
|
||||
Ok(Self::construct(
|
||||
device,
|
||||
surface,
|
||||
Some(format!("{:?}_surface", window_handle).into()),
|
||||
)?)
|
||||
}
|
||||
}
|
||||
|
||||
define_device_owned_handle! {
|
||||
pub Swapchain(vk::SwapchainKHR) {
|
||||
mutex: RawMutex,
|
||||
surface: Arc<Surface>,
|
||||
|
||||
#[allow(unused)]
|
||||
present_mode: vk::PresentModeKHR,
|
||||
#[allow(unused)]
|
||||
color_space: vk::ColorSpaceKHR,
|
||||
format: vk::Format,
|
||||
images: Vec<Arc<images::Image>>,
|
||||
image_views: Vec<vk::ImageView>,
|
||||
extent: vk::Extent2D,
|
||||
min_image_count: u32,
|
||||
|
||||
// sync objects:
|
||||
// we need two semaphores per each image, one acquire-semaphore and one release-semaphore.
|
||||
// semaphores must be unique to each frame and cannot be reused per swapchain.
|
||||
acquire_semaphores: Vec<vk::Semaphore>,
|
||||
release_semaphores: Vec<vk::Semaphore>,
|
||||
|
||||
// one fence per in-flight frame, to synchronize image acquisition
|
||||
fences: Vec<Arc<sync::Fence>>,
|
||||
|
||||
current_frame: AtomicU32,
|
||||
|
||||
// for khr_present_id/khr_present_wait
|
||||
#[allow(unused)]
|
||||
present_id: AtomicU64,
|
||||
} => |this| unsafe {
|
||||
_ = this.device().wait_queue_idle(this.device().present_queue());
|
||||
tracing::debug!("dropping swapchain {:?}", this.handle());
|
||||
for view in &this.image_views {
|
||||
this.device().dev().destroy_image_view(*view, None);
|
||||
}
|
||||
|
||||
this.with_locked(|swapchain| {
|
||||
this.device().swapchain().destroy_swapchain(swapchain, None)
|
||||
});
|
||||
|
||||
for &semaphore in this
|
||||
.acquire_semaphores
|
||||
.iter()
|
||||
.chain(&this.release_semaphores)
|
||||
{
|
||||
this.device().dev().destroy_semaphore(semaphore, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Swapchain {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Swapchain")
|
||||
.field("inner", &self.inner)
|
||||
.field("surface", &self.surface)
|
||||
.field("present_mode", &self.present_mode)
|
||||
.field("color_space", &self.color_space)
|
||||
.field("format", &self.format)
|
||||
.field("images", &self.images)
|
||||
.field("image_views", &self.image_views)
|
||||
.field("extent", &self.extent)
|
||||
.field("min_image_count", &self.min_image_count)
|
||||
.field("acquire_semaphores", &self.acquire_semaphores)
|
||||
.field("release_semaphores", &self.release_semaphores)
|
||||
.field("fences", &self.fences)
|
||||
.field("current_frame", &self.current_frame)
|
||||
.field("present_id", &self.present_id)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Swapchain {
|
||||
const PREFERRED_IMAGES_IN_FLIGHT: u32 = 3;
|
||||
|
||||
fn get_swapchain_params_from_surface(
|
||||
instance: &Arc<Instance>,
|
||||
surface: vk::SurfaceKHR,
|
||||
pdev: vk::PhysicalDevice,
|
||||
requested_extent: Option<vk::Extent2D>,
|
||||
) -> Result<SwapchainParams> {
|
||||
let caps = unsafe {
|
||||
instance
|
||||
.surface
|
||||
.get_physical_device_surface_capabilities(pdev, surface)?
|
||||
};
|
||||
let formats = unsafe {
|
||||
instance
|
||||
.surface
|
||||
.get_physical_device_surface_formats(pdev, surface)?
|
||||
};
|
||||
let present_modes = unsafe {
|
||||
instance
|
||||
.surface
|
||||
.get_physical_device_surface_present_modes(pdev, surface)?
|
||||
};
|
||||
|
||||
let present_mode = present_modes
|
||||
.iter()
|
||||
.find(|&mode| mode == &vk::PresentModeKHR::MAILBOX)
|
||||
.cloned()
|
||||
.unwrap_or(vk::PresentModeKHR::FIFO);
|
||||
|
||||
let format = formats
|
||||
.iter()
|
||||
.max_by_key(|&&format| {
|
||||
let is_rgba_unorm = format.format == vk::Format::R8G8B8A8_UNORM
|
||||
|| format.format == vk::Format::B8G8R8A8_UNORM;
|
||||
let is_srgb = format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR;
|
||||
is_rgba_unorm as u8 * 10 + is_srgb as u8
|
||||
})
|
||||
.or(formats.first())
|
||||
.cloned()
|
||||
.expect("no surface format available!");
|
||||
|
||||
// 0 here means no limit
|
||||
let max_image_count = core::num::NonZero::new(caps.max_image_count)
|
||||
.map(|n| n.get())
|
||||
.unwrap_or(u32::MAX);
|
||||
|
||||
// we want PREFERRED_IMAGES_IN_FLIGHT images acquired at the same time,
|
||||
let image_count =
|
||||
(caps.min_image_count + Self::PREFERRED_IMAGES_IN_FLIGHT).min(max_image_count);
|
||||
|
||||
let extent = current_extent_or_clamped(
|
||||
&caps,
|
||||
requested_extent.unwrap_or(vk::Extent2D::default().width(1).height(1)),
|
||||
);
|
||||
|
||||
Ok(SwapchainParams {
|
||||
present_mode,
|
||||
format: format.format,
|
||||
color_space: format.color_space,
|
||||
image_count,
|
||||
extent,
|
||||
min_image_count: caps.min_image_count,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
device: Device,
|
||||
surface: Arc<Surface>,
|
||||
pdev: vk::PhysicalDevice,
|
||||
extent: vk::Extent2D,
|
||||
) -> Result<Self> {
|
||||
Self::create(device, surface, pdev, Some(extent), None)
|
||||
}
|
||||
|
||||
fn create(
|
||||
device: Device,
|
||||
surface: Arc<Surface>,
|
||||
pdev: vk::PhysicalDevice,
|
||||
extent: Option<vk::Extent2D>,
|
||||
old_swapchain: Option<&Self>,
|
||||
) -> Result<Self> {
|
||||
let SwapchainParams {
|
||||
present_mode,
|
||||
format,
|
||||
color_space,
|
||||
image_count,
|
||||
min_image_count,
|
||||
extent,
|
||||
} = Self::get_swapchain_params_from_surface(
|
||||
device.instance(),
|
||||
surface.handle(),
|
||||
pdev,
|
||||
extent,
|
||||
)?;
|
||||
|
||||
let (swapchain, images) = {
|
||||
let lock = old_swapchain.as_ref().map(|handle| handle.lock());
|
||||
|
||||
Self::create_vkswapchainkhr(
|
||||
&device,
|
||||
surface.handle(),
|
||||
&device.queue_families().swapchain_family_indices(),
|
||||
extent,
|
||||
lock.as_ref().map(|lock| **lock),
|
||||
present_mode,
|
||||
format,
|
||||
color_space,
|
||||
image_count,
|
||||
)
|
||||
}?;
|
||||
|
||||
let images = images
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, image)| unsafe {
|
||||
images::Image::from_swapchain_image(
|
||||
device.clone(),
|
||||
*image,
|
||||
Some(format!("swapchain-{:x}-image-{i}", swapchain.as_raw()).into()),
|
||||
vk::Extent3D {
|
||||
width: extent.width,
|
||||
height: extent.height,
|
||||
depth: 1,
|
||||
},
|
||||
format,
|
||||
)
|
||||
.inspect(|img| {
|
||||
_ = img.get_view(images::ImageViewDesc {
|
||||
// TODO: make this a function that uses debug name/surface handle
|
||||
name: Some(
|
||||
format!("swapchain-{:x}-image-{i}-view", swapchain.as_raw()).into(),
|
||||
),
|
||||
kind: vk::ImageViewType::TYPE_2D,
|
||||
format,
|
||||
aspect: vk::ImageAspectFlags::COLOR,
|
||||
..Default::default()
|
||||
});
|
||||
})
|
||||
.map(|img| Arc::new(img))
|
||||
})
|
||||
.collect::<VkResult<Vec<_>>>()?;
|
||||
|
||||
let image_views = images
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, image)| {
|
||||
image.get_view(images::ImageViewDesc {
|
||||
name: Some(format!("swapchain-{:x}-image-{i}-view", swapchain.as_raw()).into()),
|
||||
kind: vk::ImageViewType::TYPE_2D,
|
||||
format,
|
||||
aspect: vk::ImageAspectFlags::COLOR,
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.collect::<VkResult<Vec<_>>>()?;
|
||||
|
||||
let num_images = images.len() as u32;
|
||||
let inflight_frames = num_images - min_image_count;
|
||||
|
||||
let acquire_semaphores = {
|
||||
(0..inflight_frames)
|
||||
.map(|i| unsafe {
|
||||
device
|
||||
.dev()
|
||||
.create_semaphore(&vk::SemaphoreCreateInfo::default(), None)
|
||||
.inspect(|r| {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
device
|
||||
.debug_name_object(
|
||||
*r,
|
||||
&format!("semaphore-{:x}_{i}-acquire", swapchain.as_raw()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<VkResult<Vec<_>>>()?
|
||||
};
|
||||
|
||||
let release_semaphores = {
|
||||
(0..inflight_frames)
|
||||
.map(|i| unsafe {
|
||||
device
|
||||
.dev()
|
||||
.create_semaphore(&vk::SemaphoreCreateInfo::default(), None)
|
||||
.inspect(|r| {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
device
|
||||
.debug_name_object(
|
||||
*r,
|
||||
&format!("semaphore-{:x}_{i}-release", swapchain.as_raw()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<VkResult<Vec<_>>>()?
|
||||
};
|
||||
|
||||
let fences = {
|
||||
(0..inflight_frames)
|
||||
.map(|i| {
|
||||
Ok(Arc::new(sync::Fence::create(device.clone()).inspect(
|
||||
|r| {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
device
|
||||
.debug_name_object(
|
||||
r.fence(),
|
||||
&format!("fence-{:x}_{i}", swapchain.as_raw()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
},
|
||||
)?))
|
||||
})
|
||||
.collect::<VkResult<Vec<_>>>()?
|
||||
};
|
||||
|
||||
tracing::trace!("fences: {fences:?}");
|
||||
|
||||
Ok(Self::construct(
|
||||
device,
|
||||
swapchain,
|
||||
Some(
|
||||
format!(
|
||||
"swapchain-{}_{}",
|
||||
surface.handle().as_raw(),
|
||||
SWAPCHAIN_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
<parking_lot::RawMutex as parking_lot::lock_api::RawMutex>::INIT,
|
||||
surface,
|
||||
present_mode,
|
||||
color_space,
|
||||
format,
|
||||
images,
|
||||
image_views,
|
||||
extent,
|
||||
min_image_count,
|
||||
acquire_semaphores,
|
||||
release_semaphores,
|
||||
fences,
|
||||
AtomicU32::new(0),
|
||||
AtomicU64::new(1),
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn max_in_flight_images(&self) -> u32 {
|
||||
self.num_images() - self.min_image_count
|
||||
}
|
||||
|
||||
pub fn num_images(&self) -> u32 {
|
||||
self.images.len() as u32
|
||||
}
|
||||
|
||||
fn recreate(&self, extent: Option<vk::Extent2D>) -> Result<Self> {
|
||||
Self::create(
|
||||
self.device().clone(),
|
||||
self.surface.clone(),
|
||||
self.device().phy(),
|
||||
extent,
|
||||
Some(&self),
|
||||
)
|
||||
}
|
||||
|
||||
/// returns a future yielding the frame, and true if the swapchain is
|
||||
/// suboptimal and should be recreated.
|
||||
fn acquire_image(
|
||||
self: Arc<Self>,
|
||||
) -> impl std::future::Future<Output = VkResult<(SwapchainFrame, bool)>> {
|
||||
let frame = self
|
||||
.current_frame
|
||||
.fetch_update(
|
||||
std::sync::atomic::Ordering::Release,
|
||||
std::sync::atomic::Ordering::Relaxed,
|
||||
|i| Some((i + 1) % self.max_in_flight_images()),
|
||||
)
|
||||
.unwrap() as usize;
|
||||
|
||||
tracing::trace!(frame, "acquiring image for frame {frame}");
|
||||
|
||||
async move {
|
||||
let fence = self.fences[frame].clone();
|
||||
let acquire = self.acquire_semaphores[frame];
|
||||
let release = self.release_semaphores[frame];
|
||||
|
||||
// spawn on threadpool because it might block.
|
||||
let (idx, suboptimal) = smol::unblock({
|
||||
let this = self.clone();
|
||||
let fence = fence.clone();
|
||||
move || unsafe {
|
||||
this.with_locked(|swapchain| {
|
||||
this.device().swapchain().acquire_next_image(
|
||||
swapchain,
|
||||
u64::MAX,
|
||||
acquire,
|
||||
fence.fence(),
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
// wait for image to become available.
|
||||
sync::FenceFuture::new(fence.clone()).await;
|
||||
|
||||
let idx = idx as usize;
|
||||
let image = self.images[idx].clone();
|
||||
let view = self.image_views[idx];
|
||||
|
||||
Ok((
|
||||
SwapchainFrame {
|
||||
index: idx as u32,
|
||||
swapchain: self.clone(),
|
||||
format: self.format,
|
||||
image,
|
||||
view,
|
||||
acquire,
|
||||
release,
|
||||
},
|
||||
suboptimal,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn present(&self, frame: SwapchainFrame, wait: Option<vk::Semaphore>) -> Result<()> {
|
||||
let swpchain = self.lock();
|
||||
let queue = self.device().present_queue().lock();
|
||||
|
||||
let wait_semaphores = wait
|
||||
.as_ref()
|
||||
.map(|sema| core::slice::from_ref(sema))
|
||||
.unwrap_or_default();
|
||||
|
||||
// TODO: make this optional for devices with no support for present_wait/present_id
|
||||
// let present_id = self
|
||||
// .present_id
|
||||
// .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
// let mut present_id =
|
||||
// vk::PresentIdKHR::default().present_ids(core::slice::from_ref(&present_id));
|
||||
|
||||
let present_info = vk::PresentInfoKHR::default()
|
||||
.image_indices(core::slice::from_ref(&frame.index))
|
||||
.swapchains(core::slice::from_ref(&swpchain))
|
||||
.wait_semaphores(wait_semaphores);
|
||||
//.push_next(&mut present_id)
|
||||
|
||||
// call winits pre_present_notify here
|
||||
|
||||
unsafe {
|
||||
self.device()
|
||||
.swapchain()
|
||||
.queue_present(*queue, &present_info)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_vkswapchainkhr(
|
||||
device: &Device,
|
||||
surface: vk::SurfaceKHR,
|
||||
queue_families: &[u32],
|
||||
image_extent: vk::Extent2D,
|
||||
old_swapchain: Option<vk::SwapchainKHR>,
|
||||
present_mode: vk::PresentModeKHR,
|
||||
image_format: vk::Format,
|
||||
image_color_space: vk::ColorSpaceKHR,
|
||||
image_count: u32,
|
||||
) -> Result<(vk::SwapchainKHR, Vec<vk::Image>)> {
|
||||
let create_info = vk::SwapchainCreateInfoKHR::default()
|
||||
.surface(surface)
|
||||
.present_mode(present_mode)
|
||||
.image_color_space(image_color_space)
|
||||
.image_format(image_format)
|
||||
.min_image_count(image_count)
|
||||
.image_usage(vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::COLOR_ATTACHMENT)
|
||||
.image_array_layers(1)
|
||||
.image_extent(image_extent)
|
||||
.image_sharing_mode(if queue_families.len() <= 1 {
|
||||
vk::SharingMode::EXCLUSIVE
|
||||
} else {
|
||||
vk::SharingMode::CONCURRENT
|
||||
})
|
||||
.queue_family_indices(queue_families)
|
||||
.pre_transform(vk::SurfaceTransformFlagsKHR::IDENTITY)
|
||||
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
|
||||
.old_swapchain(old_swapchain.unwrap_or(vk::SwapchainKHR::null()))
|
||||
.clipped(true);
|
||||
|
||||
let (swapchain, images) = unsafe {
|
||||
let swapchain = device.swapchain().create_swapchain(&create_info, None)?;
|
||||
|
||||
let images = device.swapchain().get_swapchain_images(swapchain)?;
|
||||
|
||||
(swapchain, images)
|
||||
};
|
||||
|
||||
Ok((swapchain, images))
|
||||
}
|
||||
}
|
||||
static SWAPCHAIN_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[derive(Debug)]
|
||||
#[must_use = "This struct represents an acquired image from the swapchain and
|
||||
must be presented in order to free resources on the device."]
|
||||
pub struct SwapchainFrame {
|
||||
pub swapchain: Arc<Swapchain>,
|
||||
pub index: u32,
|
||||
pub image: Arc<images::Image>,
|
||||
pub format: vk::Format,
|
||||
pub view: vk::ImageView,
|
||||
pub acquire: vk::Semaphore,
|
||||
pub release: vk::Semaphore,
|
||||
}
|
||||
|
||||
impl Eq for SwapchainFrame {}
|
||||
impl PartialEq for SwapchainFrame {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.index == other.index && self.image == other.image
|
||||
}
|
||||
}
|
||||
|
||||
impl SwapchainFrame {
|
||||
pub fn present(self, wait: Option<vk::Semaphore>) -> crate::Result<()> {
|
||||
self.swapchain.clone().present(self, wait)
|
||||
}
|
||||
}
|
||||
|
||||
fn current_extent_or_clamped(
|
||||
caps: &vk::SurfaceCapabilitiesKHR,
|
||||
fallback: vk::Extent2D,
|
||||
) -> vk::Extent2D {
|
||||
if caps.current_extent.width == u32::MAX {
|
||||
vk::Extent2D {
|
||||
width: fallback
|
||||
.width
|
||||
.clamp(caps.min_image_extent.width, caps.max_image_extent.width),
|
||||
height: fallback
|
||||
.height
|
||||
.clamp(caps.min_image_extent.height, caps.max_image_extent.height),
|
||||
}
|
||||
} else {
|
||||
caps.current_extent
|
||||
}
|
||||
}
|
||||
|
||||
struct SwapchainParams {
|
||||
present_mode: vk::PresentModeKHR,
|
||||
format: vk::Format,
|
||||
color_space: vk::ColorSpaceKHR,
|
||||
/// the number of images to request from the device
|
||||
image_count: u32,
|
||||
/// the minimum number of images the surface permits
|
||||
min_image_count: u32,
|
||||
extent: vk::Extent2D,
|
||||
}
|
||||
|
||||
impl Swapchain {
|
||||
pub fn lock(&self) -> RawMutexGuard<'_, vk::SwapchainKHR> {
|
||||
use parking_lot::lock_api::RawMutex;
|
||||
self.mutex.lock();
|
||||
RawMutexGuard {
|
||||
mutex: &self.mutex,
|
||||
value: &self.inner.object,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_locked<T, F: FnOnce(vk::SwapchainKHR) -> T>(&self, f: F) -> T {
|
||||
let lock = self.lock();
|
||||
f(*lock)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WindowSurface {
|
||||
// window_handle: RawWindowHandle,
|
||||
pub surface: Arc<Surface>,
|
||||
// this mutex is for guarding the swapchain against being replaced
|
||||
// underneath WindowContext's functions
|
||||
current_swapchain: RwLock<Arc<Swapchain>>,
|
||||
}
|
||||
|
||||
impl WindowSurface {
|
||||
pub fn new(
|
||||
device: Device,
|
||||
requested_extent: vk::Extent2D,
|
||||
window: RawWindowHandle,
|
||||
display: RawDisplayHandle,
|
||||
) -> Result<Self> {
|
||||
let surface = Arc::new(Surface::create(device.clone(), display, window)?);
|
||||
let swapchain = RwLock::new(Arc::new(Swapchain::new(
|
||||
device.clone(),
|
||||
surface.clone(),
|
||||
device.phy(),
|
||||
requested_extent,
|
||||
)?));
|
||||
|
||||
Ok(Self {
|
||||
surface,
|
||||
// window_handle: window,
|
||||
current_swapchain: swapchain,
|
||||
})
|
||||
}
|
||||
|
||||
/// spawns a task that continuously requests images from the current
|
||||
/// swapchain, sending them to a channel. returns the receiver of the
|
||||
/// channel, and a handle to the task, allowing for cancellation.
|
||||
pub fn images(
|
||||
self: Arc<Self>,
|
||||
) -> (
|
||||
smol::channel::Receiver<SwapchainFrame>,
|
||||
smol::Task<std::result::Result<(), crate::Error>>,
|
||||
) {
|
||||
let (tx, rx) = smol::channel::bounded(8);
|
||||
let task = smol::spawn(async move {
|
||||
loop {
|
||||
let frame = self.acquire_image().await?;
|
||||
tx.send(frame)
|
||||
.await
|
||||
.expect("channel closed on swapchain acquiring frame");
|
||||
}
|
||||
});
|
||||
|
||||
(rx, task)
|
||||
}
|
||||
|
||||
pub async fn acquire_image(&self) -> Result<SwapchainFrame> {
|
||||
// clone swapchain to keep it alive
|
||||
let swapchain = self.current_swapchain.read().clone();
|
||||
let (frame, suboptimal) = swapchain.clone().acquire_image().await?;
|
||||
if suboptimal {
|
||||
let mut lock = self.current_swapchain.write();
|
||||
// only recreate our swapchain if it is still same, or else it might have already been recreated.
|
||||
if Arc::ptr_eq(&swapchain, &lock) {
|
||||
*lock = Arc::new(lock.recreate(None)?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(frame)
|
||||
}
|
||||
|
||||
pub fn recreate_with(&self, extent: Option<vk::Extent2D>) -> Result<()> {
|
||||
let mut swapchain = self.current_swapchain.write();
|
||||
*swapchain = Arc::new(swapchain.recreate(extent)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::device;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn create_headless_vk() -> Result<(Device, WindowSurface)> {
|
||||
let device = Device::new_from_default_desc(
|
||||
None,
|
||||
&[
|
||||
device::Extension {
|
||||
name: "VK_EXT_headless_surface",
|
||||
version: ash::ext::headless_surface::SPEC_VERSION,
|
||||
},
|
||||
device::Extension {
|
||||
name: "VK_KHR_surface",
|
||||
version: ash::khr::surface::SPEC_VERSION,
|
||||
},
|
||||
],
|
||||
)?;
|
||||
|
||||
let surface = Arc::new(Surface::headless(device.clone())?);
|
||||
|
||||
let swapchain = Arc::new(Swapchain::new(
|
||||
device.clone(),
|
||||
surface.clone(),
|
||||
device.phy(),
|
||||
vk::Extent2D::default().width(1).height(1),
|
||||
)?);
|
||||
|
||||
let window_ctx = WindowSurface {
|
||||
// window_handle: RawWindowHandle::Web(raw_window_handle::WebWindowHandle::new(0)),
|
||||
surface,
|
||||
current_swapchain: RwLock::new(swapchain),
|
||||
};
|
||||
|
||||
Ok((device, window_ctx))
|
||||
}
|
||||
|
||||
#[tracing_test::traced_test]
|
||||
#[test]
|
||||
fn async_swapchain_acquiring() {
|
||||
let (_dev, ctx) = create_headless_vk().expect("init");
|
||||
let ctx = Arc::new(ctx);
|
||||
let (rx, handle) = ctx.clone().images();
|
||||
|
||||
eprintln!("hello world!");
|
||||
|
||||
let mut count = 0;
|
||||
loop {
|
||||
let now = std::time::Instant::now();
|
||||
let frame = rx.recv_blocking().expect("recv");
|
||||
|
||||
_ = frame.present(None);
|
||||
tracing::info!("mspf: {:.3}ms", now.elapsed().as_micros() as f32 / 1e3);
|
||||
count += 1;
|
||||
if count > 1000 {
|
||||
smol::block_on(handle.cancel());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
424
crates/renderer/src/text.rs
Normal file
424
crates/renderer/src/text.rs
Normal file
|
@ -0,0 +1,424 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
use ash::vk::Extent2D;
|
||||
use cosmic_text::{
|
||||
Attrs, Buffer, CacheKey, Family, FontSystem, Metrics, PhysicalGlyph, SwashCache,
|
||||
};
|
||||
use glam::{IVec2, Vec2};
|
||||
use guillotiere::size2;
|
||||
#[cfg(test)]
|
||||
use image::{GenericImage, GenericImageView};
|
||||
|
||||
use crate::{
|
||||
def_monotonic_id,
|
||||
util::{self, Rect2D, F32},
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
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: &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)))
|
||||
}
|
||||
|
||||
#[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);
|
||||
|
||||
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,6 +1,7 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use ash::vk;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! def_monotonic_id {
|
||||
|
@ -328,6 +329,17 @@ pub fn eq_f32(lhs: f32, rhs: f32) -> bool {
|
|||
_ => 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) {
|
||||
use std::hash::Hash;
|
||||
|
@ -341,6 +353,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 {
|
||||
let now = std::time::Instant::now();
|
||||
let out = f();
|
||||
|
@ -350,8 +428,13 @@ pub fn timed<T, F: FnOnce() -> T>(label: &str, f: F) -> T {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct Rgba(pub [f32; 4]);
|
||||
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(transparent)]
|
||||
pub struct Rgba8(pub [u8; 4]);
|
||||
|
||||
impl std::hash::Hash for Rgba {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.map(|f| hash_f32(state, f));
|
||||
|
@ -369,6 +452,9 @@ impl Rgba {
|
|||
pub fn into_f32(&self) -> [f32; 4] {
|
||||
self.0
|
||||
}
|
||||
pub fn into_u8(&self) -> Rgba8 {
|
||||
Rgba8(self.0.map(|f| (f.clamp(0.0, 1.0) * 255.0) as u8))
|
||||
}
|
||||
pub fn into_snorm(&self) -> [f32; 4] {
|
||||
self.0.map(|f| (f - 0.5) * 2.0)
|
||||
}
|
||||
|
@ -511,3 +597,58 @@ impl<'a> Iterator for ChunkedBitIter<'a> {
|
|||
Some(iter)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawMutexGuard<'a, T> {
|
||||
pub(crate) mutex: &'a parking_lot::RawMutex,
|
||||
pub(crate) value: &'a T,
|
||||
pub(crate) _pd: std::marker::PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a, T> std::ops::Deref for RawMutexGuard<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for RawMutexGuard<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
use parking_lot::lock_api::RawMutex;
|
||||
unsafe {
|
||||
self.mutex.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CStringList {
|
||||
pub strings: Box<[*const i8]>,
|
||||
#[allow(unused)]
|
||||
bytes: Box<[u8]>,
|
||||
}
|
||||
|
||||
impl CStringList {
|
||||
pub fn from_iter<I, S>(i: I) -> CStringList
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let mut bytes = vec![];
|
||||
let mut strings = vec![];
|
||||
i.into_iter().for_each(|s| {
|
||||
let start = bytes.len();
|
||||
bytes.extend_from_slice(s.as_ref().as_bytes());
|
||||
bytes.push(0);
|
||||
strings.push(start);
|
||||
});
|
||||
|
||||
let bytes = bytes.into_boxed_slice();
|
||||
Self {
|
||||
strings: strings
|
||||
.into_iter()
|
||||
.map(|offset| unsafe { bytes.as_ptr().offset(offset as isize) as *const i8 })
|
||||
.collect(),
|
||||
bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue