diff --git a/.gitignore b/.gitignore index ee80f3d..798df0c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ main tool/pack_file *.zip *.tar -*.pyc \ No newline at end of file +*.pyc +*.dds \ No newline at end of file diff --git a/Makefile b/Makefile index f44f3b4..8d4ad62 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,8 @@ OBJS = \ src/minecraft/entry_table.o \ src/minecraft/vulkan.o \ src/minecraft/vulkan/per_world.o \ - src/font/outline.o + src/font/outline.o \ + src/renpy/vulkan.o WORLDS = \ data/minecraft/midnightmeadow/inthash.o \ @@ -83,6 +84,9 @@ all: main %.o: %.s $(AS) $< -o $@ +#%.dds: %.png +# WINEDEBUG=-all wine $(HOME)/Texconv.exe -y -nogpu -nowic -dx10 --format BC7_UNORM_SRGB -m 1 $< -o $(dir $@) + main: $(OBJS) $(LIBS) $(SCENES) $(WORLDS) $(CC) $(ARCH) $(LDFLAGS) $(FLAGS) $(OPT) $(DEBUG) $^ -o $@ diff --git a/data/renpy/images/bg/flowerfield1.png b/data/renpy/images/bg/flowerfield1.png new file mode 100644 index 0000000..d67bde1 Binary files /dev/null and b/data/renpy/images/bg/flowerfield1.png differ diff --git a/data/renpy/images/bg/forest1.png b/data/renpy/images/bg/forest1.png new file mode 100644 index 0000000..dad46df Binary files /dev/null and b/data/renpy/images/bg/forest1.png differ diff --git a/data/renpy/images/bg/wheatfield1.png b/data/renpy/images/bg/wheatfield1.png new file mode 100644 index 0000000..51ab005 Binary files /dev/null and b/data/renpy/images/bg/wheatfield1.png differ diff --git a/data/renpy/images/bg/white.png b/data/renpy/images/bg/white.png new file mode 100644 index 0000000..cef66ca Binary files /dev/null and b/data/renpy/images/bg/white.png differ diff --git a/data/renpy/images/ch/Alice.png b/data/renpy/images/ch/Alice.png new file mode 100644 index 0000000..5f91897 Binary files /dev/null and b/data/renpy/images/ch/Alice.png differ diff --git a/data/renpy/images/ch/Eily.png b/data/renpy/images/ch/Eily.png new file mode 100644 index 0000000..b6e71d4 Binary files /dev/null and b/data/renpy/images/ch/Eily.png differ diff --git a/data/renpy/images/ch/cat.png b/data/renpy/images/ch/cat.png new file mode 100644 index 0000000..0491871 Binary files /dev/null and b/data/renpy/images/ch/cat.png differ diff --git a/data/renpy/images/ch/catw.png b/data/renpy/images/ch/catw.png new file mode 100644 index 0000000..16cc9a0 Binary files /dev/null and b/data/renpy/images/ch/catw.png differ diff --git a/data/renpy/script.rpy b/data/renpy/script.rpy new file mode 100644 index 0000000..c43b0f6 --- /dev/null +++ b/data/renpy/script.rpy @@ -0,0 +1,542 @@ +# The script of the game goes in this file. + +# Declare characters used by this game. The color argument colorizes the +# name of the character. +image bgwhite = "bg/white.png" +image bgforest1 = "bg/forest1.png" +image bgforest2 = "bg/forest2.png" +image bgflower1 = "bg/flowerfield1.png" +image bgwheatfield1 = "bg/wheatfield1.png" + +image cat = "ch/cat.png" +image catw = "ch/catw.png" +image ei = "ch/Eily.png" +image al = "ch/Alice.png" +#image ei1 = "ch/eily1.png" +#image al1 = "ch/alice1.png" + +define a = Character("Alice",color="00765e") +define c = Character("Cat",color="590093") +define e = Character("Eily",color="0b6092") +define mg = Character("Mouse Girls",color="000000") +define n = Character("Narrator",color="000000") +define l = Character("Leona",color="590093") + +#this centers the name display +define gui.name_xpos = 0.5 +define gui.name_xalign = 0.5 + +#this centers the dialogue display +define gui.dialogue_xpos = 0.5 +define gui.dialogue_text_xalign = 0.5 + +transform centerleft: + xalign 0.3 + yalign 1.0 + + + +transform centerright: + xalign 0.7 + yalign 1.0 + + +init: + python: + renpy.music.register_channel("ScaredMice", "music", loop=True) + renpy.music.register_channel("TinyForestMinstrels", "music", loop=True) + renpy.music.register_channel("MistAmbience", "music", loop=False) + renpy.music.register_channel("PhrygianButterflies", "music", loop=True) + renpy.music.register_channel("WheatFields", "music", loop=True) + + + +# The game starts here. + +label start: + + # Show a background. This uses a placeholder by default, but you can + # add a file (named either "bg room.png" or "bg room.jpg") to the + # images directory to show it. + + #play Chime "sfx/Chime.ogg" fadeout 1.0 + play sound "sfx/Chime.ogg" + play MistAmbience "sfx/MistAmbience.ogg" + scene bgwhite + with Dissolve(3.0) + #play music "IntroMusicTest.ogg" + + #play NarratorLoop "n1test.mp3" + #voice "n1test.mp3" + n "Far over the mountains of Almystice" + #voice "n2test.mp3" + n "Beyond the tumultuous waters of the Lilac Bay" + #voice "n3test.mp3" + n "And across the vast fields of Alysen" + #voice "n4test.mp3" + play TinyForestMinstrels "music/TinyForestMinstrels.ogg" + n "Tiny minstrels can be heard amongst the trees" + + + scene bgforest1 + with Dissolve(3.0) + + show al at left + a "Are we almost there?" + + show ei at right + + e "Hmmm... Not really" + + a "How much further have we to go?" + + e "About two more moons" + + menu: + "Complain" : + + a "We are still sooo far awayyy" + + e "And it will be even further if you dont stop complaining" + + a "Easy for you to say, all you have to carry is a little memory pipe!" + a "I'm tired ><" + + e "Don't start whining now!" + + e "You need to remember why we have come all this way" + + a "I understand... I suppose it is for an important purpose" + + "Rationalize" : + + a "I understand... I suppose it is for an important purpose" + jump mainbranch1 + + + label mainbranch1: + e "We're almost out of the forest, we can take a little break once we clear the tree line" + + a "Is that where the flora field is?" + + e "Why yes, If I remember correctly, it should be just up ahead" + stop TinyForestMinstrels fadeout 5.5 + + scene bgwhite + play sound "sfx/Chime.ogg" + with Dissolve(1.0) + + + voice "n5test.ogg" + n "As the minstrel mice girls continue along the path, the forest opens up into a beautiful field of flowers" + + play PhrygianButterflies "music/PhrygianButterflies.ogg" + + + scene bgflower1 + with Dissolve(1.0) + + show ei at right + + e "Look at all the butterflies! They are all so pretty!" + + show al at left + + + a "This place is like a dream..." + + e "There are so many flowers this time of year" + e "I told you it would be worth the journey!" + + a "Can we stop for a bit now?" + + e "Of course" + e "Ya know, Its a shame we didnt save some of those giant strawberries you found" + + a "I told you not to eat them all!" + + e "Yah yah" + e "Anyways, shall I recite a tale?" + + menu: + + + "Good idea" : + stop PhrygianButterflies fadeout 4.2 + a "Why dont you sing the story of Eleanor the Hero!" + e "Sure" + a "..." + play music "music/Poem1.ogg" noloop + pause 40 + "I am too tired" : + e "Serves you right for scaring those elephant-dogs" + stop PhrygianButterflies fadeout 4.2 + a "They were asking for it, you know" + + + + + + + + jump mainbranch2 + + #Improvise music in the butterfly field + #Choose Between: Fleeting and + #Elri scares the mice + #We learn why they want to petition the queen + # elri: "Arent you sacred she will eat you?" + # elri: "Also, why would two little mice" + #Elri shows them to the wheat town with the castle in the distance + #They wish to speak to the queen of birds, but arent important enough + #Elri helps + + + + + #Cat is summoned to see the Queen of birds + + #Mouse girls realize its their only chance + + + # + + #Lenient on Taxes + #Removed from the map + + #And what is this? A cat and 2 mice stumbling about into my courtroom? + # + # + label mainbranch2: + hide ei + show catw at right + show ei at centerleft: + xzoom -1 + voice "placeholdermeow.mp3" + c "Rawrrrr" + hide catw + show cat at right + play ScaredMice "music/ScaredMice.ogg" + + + mg "AHHHHHHHHHH!!!!!" + + + c "Nyanyanyanya" + c "Well, what do we have here? If it isn't two little meowse girls, all alone amongst the flowers" + + menu: + "Beg for mercy" : + a "Please don't eat us!!!" + + "Run" : + e "Alice don't run, our only chance is through pleading!" + e "Please don't eat us, miss kitty cat!!! ><" + + + + jump mainbranch3 + + + + + + label mainbranch3: + stop ScaredMice fadeout 2.0 + c "I'm not gonna eat you nyanyanya" + play TinyForestMinstrels "music/TinyForestMinstrels.ogg" + c "I just want to know what two little meowses are doing so very far away from home" + c "Also, are you minstrels?" + + e "Y-Yes" + a "W-We are on a quest to Castle Alysen..." + e "Shh don't tell her that" + + c "The Castle of Alysen you say?!?" + c "Why, that's where I am headed!" + + e "You don't say..." + + c "Yah, I do actually" + + e "So... Why might you be traveling to the castle?" + + c "I belong to the lineage of Agrepen" + + e "And what might that mean?" + + c "The Agrepens are a long line of felines loyal to the crown of corvidae" + + e "Really? That must mean you are a noble?" + + c "Well, not really..." + c "My father was one of the queens knights many years ago" + + e "Ah I see" + + e "So do you live at the castle or something?" + + c "Well, no..." + + a "Then why are you traveling to Castle Alysen?" + + + c "uhhh" + play MistAmbience "sfx/MistAmbience.ogg" + stop TinyForestMinstrels fadeout 2.0 + c "I DONT NEED TO BE PRESSURED BY LITTLE MICE TO SAY ANYTHING!!!!" + c "GOOD DAY!" + + hide cat + + a "Wha..." + + e "Phew, I was scared she was gonna follow us the whole way" + + a "She didn't seem so bad" + #play MistAmbience "sfx/MistAmbience.ogg" + + e "Are you kidding? She's a crazy kitty!" + + scene bgwhite + play sound "sfx/Chime.ogg" + with Dissolve(3.0) + + n "After their encounter with the weird cat, the mice scurry out of the flower field and into the nearby meadow" + scene forest2 + with Dissolve(3.0) + + show ei at right + e "I think this is the right way..." + show al at left + a "Then where did the path go?" + + e "How am I supposed to know?" + + + a "Did you hear that?!?!" + + show ei at centerleft: + xzoom -1 + + show cat at right + play PhrygianButterflies "music/PhrygianButterflies.ogg" + c "Hey there..." + c "I apologize" + c "I didn't mean to storm off like that" + + e "Ha ha, no problem..." + + a "So... Why are you traveling to Castle Alysen?" + + e "Alice!!!" + + c "If you must know, I have been summoned by the queen" + c "I suspect that my poor reputation amongst the locals of Eastern Nidus has come back to haunt me" + c "Though I know not what what she has summoned me for" + + a "Ahhhhhh" + + c "So then..." + c "Why are YOU traveling to the Castle?" + + a "We are delivering a feather!!!" + + e "Alice no!" + + a "A feather that belonged to the queen herself!!!" + + e "Why you little..." + + c "A feather you say? One of the queens?" + c "How on the face of Al Mot might you have aquired such a thing?" + c "If it is authentic, that is..." + + e "Since Alice cannot keep a secret, I shall tell you" + e "Seven moons ago, our town was attacked by three owls and a band of mice from the northern principalities" + e "Eventually word spread to greater nearby settlements, and so" + e "Messengers from the keep in Musia sent for aid from the Ravens" + e "Four moons ago, the request was answered" + e "And a small group of mice accompanied by two ravens a fox, and three squirrels set out to the northern principalities" + e "Anyways, long story short, we drove those barbaric rats out of their home" + + a "They arent actual rats you know" + + e "Obviously, but you wont catch me speaking kindly of them" + + a "And you forgot the most important part" + + e "Yah yah, I am getting there" + e "So, essentially, my brother is trained in archery, and..." + + a "Speed it up already" + + e "You tell it then!" + + a "My cousin found this feather in one of the highest towers of a castle far to the north" + + c "How do you know it belongs to the queen?" + + e "It said so itself above the display on the wall" + + a "Supposedly, it was in a room filled with treasures!" + + c "That is very nice and all, but what are the two of you doing out here all alone?" + + c "Do you expect every bird in Avia to respect your alliance with Castle Alysen?" + + e "What do you mean?" + + c "I mean, the two of you probably look like walking dinner to most creatures" + + a "I could go for some dinner..." + e "Anyways..." + e "To answer your question, upon returning to the village, the feather was taken from my brother by the needle guild" + e "So... Yesterday, after sundown" + e "We stole the feather from the guild hall before vanishing into the night" + + e "Can you imagine the look on their stupid faces, when they woke up, and not only is the feather missing" + + e "But so are we!" + + a "Hahahaha" + + c "Are the two of you mad?" + c "I assume you are attempting to return the Queens feather?" + + a "Yes, we intend to deliver the feather to its rightful owner" + + c "Absolute madness!" + + mg "..." + + + c "I will follow the two of you" + c "To keep you safe, that is" + + a "Alright!" + + e "Ha ha... Okay..." + + c "Great! Follow me, I know a shortcut! :3" + + hide cat + + a "Sounds good!" + + hide al + stop PhrygianButterflies fadeout 2.0 + + + e "Oh dear!" + hide ei + + #hide al + #hide ei + + + + #pause 10 + scene bgwhite + play sound "sfx/Chime.ogg" + with Dissolve(2.0) + n "And so the mice girls follow the noble cat further towards their destination" + + scene bgwheatfield1 + play WheatFields "music/WheatFields.ogg" + show cat at right + with Dissolve(1.3) + c "Nya" + show al at left + a "Look, your right, the castle is just up ahead!" + show ei at centerleft: + xzoom -1 + e "Wait up" + + c "I told you I knew a shortcut!" + c "Most people take the long way around" + + e "Yah because these are royal wheatfields!" + a "Who cares?" + e "Are you trying to get us killed?" + e "Its trespassing on royal land!" + + c "Calm down, I have done this a million times" + + e "That doesnt make me calm!" + + c "How can the rolling fields of wheat not calm your spirit?" + c "You little mice truly are mad!" + + a "I like the wheat!" + + e "Shut up!" + + c "Sounds like someone needs a nap!" + + e "Why? because I'm not insane like you?" + + c "Yah, your so sane, that you decided to steal from your town and then run off alone to the country of birds" + c "The power of friendship wont protect the two of you from becoming dinner" + c "And that, is why I feel obligated to accompany you!" + + e "Hey, we have a good reason!" + + c "And what might that be?" + + e "My brother found the feather, not the town guild, its a matter of family pride!" + + c "Pride has touched the chosen meouse" + c "Flies she towards the Castle" + c "But her ambition burns far too bright, and silly myice dont have any wings to myelt" + + mg "What?" + + c "Nyanyanya" + c "Nyevermind" + c "Sing me a song little minstrels!" + + c "Very Nyice!" + c "Now tell me little minstrels, what are your names?" + + a "My name is Alice" + e "And my name is Eily" + e "What is your name?" + + l "My name is Leona!" + + scene bgwhite + with Dissolve(3.0) + + n "And so, the odd trio walked through the wheatfields and towards the castle walls" + n "Upon approaching the castle walls" + + + + + # birds!!! + + + + + + + + + + + + + + + + + + + + + + + + return + diff --git a/filenames.txt b/filenames.txt index de3e097..430da58 100644 --- a/filenames.txt +++ b/filenames.txt @@ -38,3 +38,13 @@ data/minecraft/terrain2.dds shader/font.spv data/font/outline/uncial_antiqua_36.data + +shader/renpy.spv +data/renpy/images/bg/white.dds +data/renpy/images/bg/forest1.dds +data/renpy/images/bg/flowerfield1.dds +data/renpy/images/bg/wheatfield1.dds +data/renpy/images/ch/cat.dds +data/renpy/images/ch/catw.dds +data/renpy/images/ch/Eily.dds +data/renpy/images/ch/Alice.dds diff --git a/renpy-parser/language/statement.h b/include/renpy/language.h similarity index 75% rename from renpy-parser/language/statement.h rename to include/renpy/language.h index c0f113b..1d1c139 100644 --- a/renpy-parser/language/statement.h +++ b/include/renpy/language.h @@ -1,6 +1,6 @@ #include -namespace language { +namespace renpy::language { struct option { char const * const string; uint32_t statementIndex; @@ -88,13 +88,16 @@ namespace language { struct statement { enum type type; union { - jump jump; - menu menu; - play play; - say say; - scene scene; - show show; - voice voice; + renpy::language::jump jump; + renpy::language::menu menu; + renpy::language::play play; + renpy::language::say say; + renpy::language::scene scene; + renpy::language::show show; + renpy::language::voice voice; + renpy::language::stop stop; + renpy::language::pause pause; + renpy::language::hide hide; }; }; } diff --git a/include/renpy/script.h b/include/renpy/script.h new file mode 100644 index 0000000..5f6f664 --- /dev/null +++ b/include/renpy/script.h @@ -0,0 +1,221 @@ +#include "renpy/language.h" + +namespace renpy::script { +using namespace renpy::language; + +char const * const strings[] = { + "Far over the mountains of Almystice", // 0 + "Beyond the tumultuous waters of the Lilac Bay", // 1 + "And across the vast fields of Alysen", // 2 + "Tiny minstrels can be heard amongst the trees", // 3 + "Are we almost there?", // 4 + "Hmmm... Not really", // 5 + "How much further have we to go?", // 6 + "About a day or two of walking", // 7 + "We are still sooo far awayyy", // 8 + "And it will be even further if we dont stop taking breaks", // 9 + "Easy for you to say, all you have to carry is a little memory pipe!", // 10 + "I'm tired ><", // 11 + "Don't start whining now!", // 12 + "You need to remember why we have come all this way", // 13 + "I understand... I suppose it is for an important purpose", // 14 + "We're almost out of the forest, we can take a little break once we clear the tree line", // 15 + "Is that where the flora field is?", // 16 + "Why yes, If I remember correctly, it should be just up ahead", // 17 + "As the minstrel mice girls continue along the path, the forest opens up into a beautiful field of flowers", // 18 + "Look at all the butterflies! They are all so pretty!", // 19 + "This place is like a dream...", // 20 + "There are so many flowers this time of year", // 21 + "I told you it would be worth the journey!", // 22 + "Can we stop for a bit now?", // 23 + "Of course", // 24 + "Ya know, Its a shame we didnt save some of those giant strawberries you found", // 25 + "I told you not to eat them all!", // 26 + "Yah yah", // 27 + "Anyways, shall I recite a tale?", // 28 + "Why dont you sing the story of Eleanor the Hero!", // 29 + "Let us sing of the Hero Eleanor", // 30 + "...", // 31 + "Serves you right for scaring those elephant-dogs", // 32 + "They were asking for it, you know", // 33 + "Rawrrrr", // 34 + "AHHHHHHHHHH!!!!!", // 35 + "Nyanyanyanya", // 36 + "Well, what do we have here? If it isnt 2 little meowse girls, all alone in the flowers", // 37 + "Please dont eat us!!!", // 38 + "Alice dont run, our only chance is through pleading!", // 39 + "Please dont eat us, miss kitty cat!!! ><", // 40 + "I'm not gonna eat you nyanyanya", // 41 + "I just want to know what two little meowses are doing so very far away from home", // 42 + "Also, are you minstrels?", // 43 + "Y-Yes", // 44 + "W-We are on a quest to Castle Alysen...", // 45 + "Shh dont tell her that", // 46 + "The Castle of Alysen you say?!?", // 47 + "Why, that's where I am headed!", // 48 + "You dont say...", // 49 + "Yah I do actually", // 50 + "So... Why might you be traveling to the castle?", // 51 + "nya", // 52 + "Look, your right, the castle is just up ahead!", // 53 +}; + +constexpr int strings_length = (sizeof (strings)) / (sizeof (strings[0])); + +const character characters[] = { + { .characterName = "Alice" }, // 0 + { .characterName = "Cat" }, // 1 + { .characterName = "Eily" }, // 2 + { .characterName = "Mouse Girls" }, // 3 + { .characterName = "Narrator" }, // 4 +}; + +constexpr int characters_length = (sizeof (characters)) / (sizeof (characters[0])); + +const audio audio[] = { + { .path = "sfx/Chime.opus" }, // 0 sfx/Chime.ogg + { .path = "sfx/MistAmbience.opus" }, // 1 sfx/MistAmbience.ogg + { .path = "n1test.opus" }, // 2 n1test.mp3 + { .path = "n2test.opus" }, // 3 n2test.mp3 + { .path = "n3test.opus" }, // 4 n3test.mp3 + { .path = "n4test.opus" }, // 5 n4test.mp3 + { .path = "music/TinyForestMinstrels.opus" }, // 6 music/TinyForestMinstrels.ogg + { .path = "n5test.opus" }, // 7 n5test.ogg + { .path = "music/PhrygianButterflies.opus" }, // 8 music/PhrygianButterflies.ogg + { .path = "music/Poem1.opus" }, // 9 music/Poem1.ogg + { .path = "placeholdermeow.opus" }, // 10 placeholdermeow.mp3 + { .path = "music/ScaredMice.opus" }, // 11 music/ScaredMice.ogg +}; + +constexpr int audio_length = (sizeof (audio)) / (sizeof (audio[0])); + +const image images[] = { + { .path = "data/renpy/images/bg/white.dds" }, // 0 bg/white.png + { .path = "data/renpy/images/bg/forest1.dds" }, // 1 bg/forest1.png + { .path = "data/renpy/images/bg/flowerfield1.dds" }, // 2 bg/flowerfield1.png + { .path = "data/renpy/images/bg/wheatfield1.dds" }, // 3 bg/wheatfield1.png + { .path = "data/renpy/images/ch/cat.dds" }, // 4 ch/cat.png + { .path = "data/renpy/images/ch/catw.dds" }, // 5 ch/catw.png + { .path = "data/renpy/images/ch/Eily.dds" }, // 6 ch/Eily.png + { .path = "data/renpy/images/ch/Alice.dds" }, // 7 ch/Alice.png +}; + +constexpr int images_length = (sizeof (images)) / (sizeof (images[0])); + +const option options[] = { + { .string = "Complain", .statementIndex = 22 }, // 0 + { .string = "Rationalize", .statementIndex = 30 }, // 1 + { .string = "Good idea", .statementIndex = 57 }, // 2 + { .string = "I am too tired", .statementIndex = 64 }, // 3 + { .string = "Beg for mercy", .statementIndex = 80 }, // 4 + { .string = "Run", .statementIndex = 82 }, // 5 +}; + +constexpr int options_length = (sizeof (options)) / (sizeof (options[0])); + +const statement statements[] = { + { .type = type::play, .play = { .audioIndex = 0, /* FIXME channel */ } }, // 0 sfx/Chime.ogg + { .type = type::play, .play = { .audioIndex = 1, /* FIXME channel */ } }, // 1 sfx/MistAmbience.ogg + { .type = type::scene, .scene = { .imageIndex = 0 } }, // 2 bgwhite + { .type = type::voice, .voice = { .audioIndex = 2 } }, // 4 n1test.mp3 + { .type = type::say, .say = { .characterIndex = 4, .stringIndex = 0 } }, // 5 n "Far over the mountains of Almystice" + { .type = type::voice, .voice = { .audioIndex = 3 } }, // 6 n2test.mp3 + { .type = type::say, .say = { .characterIndex = 4, .stringIndex = 1 } }, // 7 n "Beyond the tumultuous waters of the Lilac Bay" + { .type = type::voice, .voice = { .audioIndex = 4 } }, // 8 n3test.mp3 + { .type = type::say, .say = { .characterIndex = 4, .stringIndex = 2 } }, // 9 n "And across the vast fields of Alysen" + { .type = type::voice, .voice = { .audioIndex = 5 } }, // 10 n4test.mp3 + { .type = type::play, .play = { .audioIndex = 6, /* FIXME channel */ } }, // 11 music/TinyForestMinstrels.ogg + { .type = type::say, .say = { .characterIndex = 4, .stringIndex = 3 } }, // 12 n "Tiny minstrels can be heard amongst the trees" + { .type = type::scene, .scene = { .imageIndex = 1 } }, // 13 bgforest1 + { .type = type::show, .show = { .imageIndex = 7 } }, // 15 al + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 4 } }, // 16 a "Are we almost there?" + { .type = type::show, .show = { .imageIndex = 6 } }, // 17 ei + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 5 } }, // 18 e "Hmmm... Not really" + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 6 } }, // 19 a "How much further have we to go?" + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 7 } }, // 20 e "About a day or two of walking" + { .type = type::menu, .menu = { .count = 2, .optionIndex = 0 } }, // 21 "Complain", "Rationalize" + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 8 } }, // 22 a "We are still sooo far awayyy" + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 9 } }, // 23 e "And it will be even further if we dont stop taking breaks" + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 10 } }, // 24 a "Easy for you to say, all you have to carry is a little memory pipe!" + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 11 } }, // 25 a "I'm tired ><" + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 12 } }, // 26 e "Don't start whining now!" + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 13 } }, // 27 e "You need to remember why we have come all this way" + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 14 } }, // 28 a "I understand... I suppose it is for an important purpose" + { .type = type::jump, .jump = { .statementIndex = 32 } }, // 29 internal jump (b'__menu_end', 0) + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 14 } }, // 30 a "I understand... I suppose it is for an important purpose" + { .type = type::jump, .jump = { .statementIndex = 32 } }, // 31 internal jump (b'__menu_end', 0) + { .type = type::jump, .jump = { .statementIndex = 33 } }, // 32 mainbranch1 + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 15 } }, // 33 e "We're almost out of the forest, we can take a little break once we clear the tree line" + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 16 } }, // 34 a "Is that where the flora field is?" + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 17 } }, // 35 e "Why yes, If I remember correctly, it should be just up ahead" + { .type = type::stop, .stop = { /* FIXME channel */ } }, // 36 + { .type = type::scene, .scene = { .imageIndex = 0 } }, // 37 bgwhite + { .type = type::voice, .voice = { .audioIndex = 7 } }, // 39 n5test.ogg + { .type = type::say, .say = { .characterIndex = 4, .stringIndex = 18 } }, // 40 n "As the minstrel mice girls continue along the path, the forest opens up into a beautiful field of flowers" + { .type = type::play, .play = { .audioIndex = 8, /* FIXME channel */ } }, // 41 music/PhrygianButterflies.ogg + { .type = type::scene, .scene = { .imageIndex = 2 } }, // 42 bgflower1 + { .type = type::show, .show = { .imageIndex = 6 } }, // 44 ei + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 19 } }, // 45 e "Look at all the butterflies! They are all so pretty!" + { .type = type::show, .show = { .imageIndex = 7 } }, // 46 al + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 20 } }, // 47 a "This place is like a dream..." + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 21 } }, // 48 e "There are so many flowers this time of year" + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 22 } }, // 49 e "I told you it would be worth the journey!" + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 23 } }, // 50 a "Can we stop for a bit now?" + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 24 } }, // 51 e "Of course" + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 25 } }, // 52 e "Ya know, Its a shame we didnt save some of those giant strawberries you found" + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 26 } }, // 53 a "I told you not to eat them all!" + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 27 } }, // 54 e "Yah yah" + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 28 } }, // 55 e "Anyways, shall I recite a tale?" + { .type = type::menu, .menu = { .count = 2, .optionIndex = 2 } }, // 56 "Good idea", "I am too tired" + { .type = type::stop, .stop = { /* FIXME channel */ } }, // 57 + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 29 } }, // 58 a "Why dont you sing the story of Eleanor the Hero!" + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 30 } }, // 59 e "Let us sing of the Hero Eleanor" + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 31 } }, // 60 a "..." + { .type = type::play, .play = { .audioIndex = 9, /* FIXME channel */ } }, // 61 music/Poem1.ogg + { .type = type::pause, .pause = { .duration = 40 } }, // 62 + { .type = type::jump, .jump = { .statementIndex = 67 } }, // 63 internal jump (b'__menu_end', 1) + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 32 } }, // 64 e "Serves you right for scaring those elephant-dogs" + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 33 } }, // 65 a "They were asking for it, you know" + { .type = type::jump, .jump = { .statementIndex = 67 } }, // 66 internal jump (b'__menu_end', 1) + { .type = type::jump, .jump = { .statementIndex = 68 } }, // 67 mainbranch2 + { .type = type::hide, .hide = { .imageIndex = 6 } }, // 68 ei + { .type = type::show, .show = { .imageIndex = 5 } }, // 69 catw + { .type = type::show, .show = { .imageIndex = 6 } }, // 70 ei + { .type = type::voice, .voice = { .audioIndex = 10 } }, // 71 placeholdermeow.mp3 + { .type = type::say, .say = { .characterIndex = 1, .stringIndex = 34 } }, // 72 c "Rawrrrr" + { .type = type::hide, .hide = { .imageIndex = 5 } }, // 73 catw + { .type = type::show, .show = { .imageIndex = 4 } }, // 74 cat + { .type = type::play, .play = { .audioIndex = 11, /* FIXME channel */ } }, // 75 music/ScaredMice.ogg + { .type = type::say, .say = { .characterIndex = 3, .stringIndex = 35 } }, // 76 mg "AHHHHHHHHHH!!!!!" + { .type = type::say, .say = { .characterIndex = 1, .stringIndex = 36 } }, // 77 c "Nyanyanyanya" + { .type = type::say, .say = { .characterIndex = 1, .stringIndex = 37 } }, // 78 c "Well, what do we have here? If it isnt 2 little meowse girls, all alone in the flowers" + { .type = type::menu, .menu = { .count = 2, .optionIndex = 4 } }, // 79 "Beg for mercy", "Run" + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 38 } }, // 80 a "Please dont eat us!!!" + { .type = type::jump, .jump = { .statementIndex = 85 } }, // 81 internal jump (b'__menu_end', 2) + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 39 } }, // 82 e "Alice dont run, our only chance is through pleading!" + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 40 } }, // 83 e "Please dont eat us, miss kitty cat!!! ><" + { .type = type::jump, .jump = { .statementIndex = 85 } }, // 84 internal jump (b'__menu_end', 2) + { .type = type::jump, .jump = { .statementIndex = 86 } }, // 85 mainbranch3 + { .type = type::stop, .stop = { /* FIXME channel */ } }, // 86 + { .type = type::say, .say = { .characterIndex = 1, .stringIndex = 41 } }, // 87 c "I'm not gonna eat you nyanyanya" + { .type = type::play, .play = { .audioIndex = 6, /* FIXME channel */ } }, // 88 music/TinyForestMinstrels.ogg + { .type = type::say, .say = { .characterIndex = 1, .stringIndex = 42 } }, // 89 c "I just want to know what two little meowses are doing so very far away from home" + { .type = type::say, .say = { .characterIndex = 1, .stringIndex = 43 } }, // 90 c "Also, are you minstrels?" + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 44 } }, // 91 e "Y-Yes" + { .type = type::say, .say = { .characterIndex = 0, .stringIndex = 45 } }, // 92 a "W-We are on a quest to Castle Alysen..." + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 46 } }, // 93 e "Shh dont tell her that" + { .type = type::say, .say = { .characterIndex = 1, .stringIndex = 47 } }, // 94 c "The Castle of Alysen you say?!?" + { .type = type::say, .say = { .characterIndex = 1, .stringIndex = 48 } }, // 95 c "Why, that's where I am headed!" + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 49 } }, // 96 e "You dont say..." + { .type = type::say, .say = { .characterIndex = 1, .stringIndex = 50 } }, // 97 c "Yah I do actually" + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 51 } }, // 98 e "So... Why might you be traveling to the castle?" + { .type = type::scene, .scene = { .imageIndex = 0 } }, // 99 bgwhite + { .type = type::say, .say = { .characterIndex = 4, .stringIndex = 52 } }, // 100 n "nya" + { .type = type::scene, .scene = { .imageIndex = 3 } }, // 101 bgwheatfield1 + { .type = type::say, .say = { .characterIndex = 2, .stringIndex = 53 } }, // 102 e "Look, your right, the castle is just up ahead!" + { .type = type::_return }, // 103 +}; + +constexpr int statements_length = (sizeof (statements)) / (sizeof (statements[0])); + +} diff --git a/include/renpy/vulkan.h b/include/renpy/vulkan.h new file mode 100644 index 0000000..93e3fbf --- /dev/null +++ b/include/renpy/vulkan.h @@ -0,0 +1,58 @@ +namespace renpy { + struct Image { + VkImage image; + VkDeviceMemory memory; + VkImageView imageView; + }; + + struct vulkan { + static constexpr int perVertexSize = (4) * 2; + + // externally initialized, opaque handle + VkInstance instance; + VkDevice device; + VkQueue queue; + VkCommandPool commandPool; + // externally initialized, structures + VkPhysicalDeviceProperties physicalDeviceProperties; + VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProperties; + // externally initialized, enum + VkFormat colorFormat; + VkFormat depthFormat; + VkSampler linearSampler; + + // internal vulkan state + VkPipelineLayout pipelineLayout; + VkShaderModule shaderModule; + VkPipeline pipeline; + VertexIndex vertexIndex; + + VkDescriptorPool descriptorPool{ VK_NULL_HANDLE }; + static constexpr int descriptorSetLayoutCount = 1; + VkDescriptorSetLayout descriptorSetLayouts[descriptorSetLayoutCount]; // unrelated to maxFrames, unrelated to descriptorCount + VkDescriptorSet descriptorSet0; + + Image * images; + + void initial_state(VkInstance instance, + VkDevice device, + VkQueue queue, + VkCommandPool commandPool, + VkPhysicalDeviceProperties physicalDeviceProperties, + VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProperties, + VkFormat colorFormat, + VkFormat depthFormat, + VkSampler linearSampler); + void init(); + void load_vertex_index_buffer(); + void load_shader(); + void create_descriptor_sets(); + void write_descriptor_sets(); + void load_image_inner(VkCommandBuffer commandBuffer, VkFence fence, int i, char const * filename); + void load_images(); + void create_pipeline(); + void draw(VkCommandBuffer commandBuffer, + uint32_t frameIndex); + + }; +} diff --git a/renpy-parser/transform.py b/renpy-parser/transform.py index 30fa356..4b85dc9 100644 --- a/renpy-parser/transform.py +++ b/renpy-parser/transform.py @@ -215,7 +215,7 @@ def pass2_images(state): path = path.removesuffix(".png") else: assert False, path - yield f"{{ .path = \"{path}.dds\" }}, // {i} {orig_path}" + yield f"{{ .path = \"data/renpy/images/{path}.dds\" }}, // {i} {orig_path}" yield "};" yield "constexpr int images_length = (sizeof (images)) / (sizeof (images[0]));" diff --git a/shader/renpy.hlsl b/shader/renpy.hlsl new file mode 100644 index 0000000..01522a0 --- /dev/null +++ b/shader/renpy.hlsl @@ -0,0 +1,31 @@ +[[vk::binding(0, 0)]] SamplerState ClosestSampler; +[[vk::binding(1, 0)]] Texture2D Texture[]; + +struct VSInput +{ + float2 Position : POSITION0; + float2 Texture : TEXCOORD0; +}; + +struct VSOutput +{ + float4 Position : SV_POSITION; + float2 Texture : NORMAL0; +}; + +[shader("vertex")] +VSOutput VSMain(VSInput input) +{ + VSOutput output = (VSOutput)0; + output.Position = float4(input.Position, 0, 1); + output.Texture = input.Texture; + + return output; +} + +[shader("pixel")] +float4 PSMain(VSOutput input) : SV_TARGET +{ + float4 color = Texture[1].Sample(ClosestSampler, input.Texture); + return float4(color.xyz, 1.0); +} diff --git a/src/dds/validate.cpp b/src/dds/validate.cpp index 0a1d8c9..439121f 100644 --- a/src/dds/validate.cpp +++ b/src/dds/validate.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "new.h" #include "dds/validate.h" @@ -71,6 +72,13 @@ static inline dds_size_levels dds_mip_total_size(DXGI_FORMAT dxgiFormat, return {mip_total_size, mip_levels}; } +/* +static inline bool is_power_of_two(uint32_t n) +{ + return (n & (n - 1)) == 0; +} +*/ + namespace dds { DDS_FILE const * validate(void const * data, uint32_t size, uint32_t ** out_offsets, void ** out_data, uint32_t * out_size) { @@ -84,14 +92,21 @@ namespace dds { uint32_t * offsets = NewM(dds->header.dwMipMapCount); + //bool power_of_two = is_power_of_two(dds->header.dwHeight) && is_power_of_two(dds->header.dwWidth); + //uint32_t max_mip_levels = power_of_two ? dds->header.dwMipMapCount : 1; + uint32_t max_mip_levels = dds->header.dwMipMapCount; + uintptr_t image_data = ((uintptr_t)dds) + (sizeof (DDS_FILE)); dds_size_levels ret = dds_mip_total_size(dds->header10.dxgiFormat, dds->header.dwHeight, dds->header.dwWidth, - dds->header.dwMipMapCount, + max_mip_levels, offsets); + if (ret.size + (sizeof (DDS_FILE)) != size) { + fprintf(stderr, "%d %ld %d\n", ret.size, (sizeof (DDS_FILE)), size); + } assert(ret.size + (sizeof (DDS_FILE)) == size); - assert(ret.levels == dds->header.dwMipMapCount); + assert(ret.levels == max_mip_levels); *out_offsets = offsets; *out_data = (void *)image_data; diff --git a/src/main.cpp b/src/main.cpp index c7bc7fc..fb0fcff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,6 +21,7 @@ #include "minecraft/vulkan.h" #include "font/outline.h" +#include "renpy/vulkan.h" #include "scenes/shadow_test/shadow_test.h" #include "scenes/eidelwind/eidelwind.h" @@ -496,7 +497,7 @@ int main() // window and surface ////////////////////////////////////////////////////////////////////// - SDL_Window * window = SDL_CreateWindow("Vulkan", 1024, 1024, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE); + SDL_Window * window = SDL_CreateWindow("Vulkan", 1280, 720, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE); SDL_CHECK_NONNULL(window); SDL_CHECK(SDL_Vulkan_CreateSurface(window, instance, nullptr, &surface)); SDL_CHECK(SDL_GetWindowSize(window, &windowSize.x, &windowSize.y)); @@ -546,8 +547,8 @@ int main() createDepth(physicalDeviceProperties.limits.nonCoherentAtomSize, physicalDeviceMemoryProperties, - 1024, - 1024, + 1280, + 720, depthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, shadowArrayLayers, @@ -646,6 +647,7 @@ int main() // initialize collada ////////////////////////////////////////////////////////////////////// + /* collada::scene::state collada_state; collada_state.vulkan.initial_state(instance, @@ -661,11 +663,13 @@ int main() collada::types::descriptor const * collada_scene_descriptor = &eidelwind::descriptor; collada_state.load_scene(collada_scene_descriptor); + */ ////////////////////////////////////////////////////////////////////// // initialize minecraft ////////////////////////////////////////////////////////////////////// + /* minecraft::vulkan::vulkan minecraft_state; minecraft_state.initial_state(instance, device, @@ -678,6 +682,7 @@ int main() textureSamplers[2], shadowDepthImageViewDepth); minecraft_state.init(); + */ ////////////////////////////////////////////////////////////////////// // initialize font @@ -695,10 +700,27 @@ int main() textureSamplers[2]); font_state.init(); + ////////////////////////////////////////////////////////////////////// + // initialize renpy + ////////////////////////////////////////////////////////////////////// + + renpy::vulkan renpy_state; + renpy_state.initial_state(instance, + device, + queue, + commandPool, + physicalDeviceProperties, + physicalDeviceMemoryProperties, + surfaceFormat.format, + depthFormat, + textureSamplers[2]); + renpy_state.init(); + ////////////////////////////////////////////////////////////////////// // initialize view ////////////////////////////////////////////////////////////////////// + /* int cameraIndex = collada_state.find_node_index_by_name("Camera001"); int cameraTargetIndex = collada_state.find_node_index_by_name("EidelwindRigPelvis"); int lightIndex = collada_state.find_node_index_by_name("Camera001"); @@ -719,6 +741,7 @@ int main() viewState.forward = XMVectorSetZ(XMVector3Normalize(at - eye), 0); viewState.pitch = 0; viewState.applyTransform(0, 0, 0, 0, 0); + */ ////////////////////////////////////////////////////////////////////// // loop @@ -732,7 +755,7 @@ int main() int64_t start_time; SDL_GetCurrentTime(&start_time); - collada_state.update(0); + //collada_state.update(0); while (quit == false) { SDL_Event event; @@ -761,10 +784,10 @@ int main() } if (event.type == SDL_EVENT_MOUSE_MOTION) { if (event.motion.state & SDL_BUTTON_LMASK) { - collada_state.mouse_motion(cameraIndex, cameraTargetIndex, event.motion.xrel, event.motion.yrel, 0); + //collada_state.mouse_motion(cameraIndex, cameraTargetIndex, event.motion.xrel, event.motion.yrel, 0); } if (event.motion.state & SDL_BUTTON_RMASK) { - collada_state.mouse_motion(cameraIndex, cameraTargetIndex, event.motion.xrel, event.motion.yrel, 1); + //collada_state.mouse_motion(cameraIndex, cameraTargetIndex, event.motion.xrel, event.motion.yrel, 1); } } if (event.type == SDL_EVENT_WINDOW_RESIZED) { @@ -782,14 +805,14 @@ int main() // gamepad update ////////////////////////////////////////////////////////////////////// - gamepad_update(viewState); + //gamepad_update(viewState); ////////////////////////////////////////////////////////////////////// // collada update ////////////////////////////////////////////////////////////////////// - double time = getTime(start_time); - collada_state.update(time / 1.5f); + //double time = getTime(start_time); + //collada_state.update(time / 1.5f); ////////////////////////////////////////////////////////////////////// // fence @@ -816,6 +839,7 @@ int main() ////////////////////////////////////////////////////////////////////// if (0) { + /* collada_state.vulkan.change_frame(commandBuffer, frameIndex); XMMATRIX projection = currentProjection(); @@ -836,13 +860,16 @@ int main() lightPositionWorld, collada_state.descriptor->nodes_count, collada_state.node_state.node_instances); + */ // minecraft + /* minecraft_state.transfer_transforms(projection, view, lightPositionWorld, frameIndex); + */ } ////////////////////////////////////////////////////////////////////// @@ -887,7 +914,7 @@ int main() VkRenderingInfo shadowRenderingInfo{ .sType = VK_STRUCTURE_TYPE_RENDERING_INFO, - .renderArea{ .extent{ .width = 1024, .height = 1024 } }, + .renderArea{ .extent{ .width = 1280, .height = 720 } }, .layerCount = 6, .colorAttachmentCount = 0, .pDepthAttachment = &shadowDepthRenderingAttachmentInfo, @@ -900,24 +927,24 @@ int main() VkViewport shadowViewport{ .x = 0, .y = 0, - .width = 1024, - .height = 1024, + .width = 1280, + .height = 720, .minDepth = 0.0f, .maxDepth = 1.0f }; vkCmdSetViewport(commandBuffer, 0, 1, &shadowViewport); VkRect2D shadowScissor{ .extent{ - .width = 1024, - .height = 1024 + .width = 1280, + .height = 720 } }; vkCmdSetScissor(commandBuffer, 0, 1, &shadowScissor); // draw - collada_state.vulkan.excludeMaterialIndex = lightMaterialIndex; - collada_state.vulkan.pipelineIndex = 0; // shadow pipeline + //collada_state.vulkan.excludeMaterialIndex = lightMaterialIndex; + //collada_state.vulkan.pipelineIndex = 0; // shadow pipeline //collada_state.draw(); vkCmdEndRendering(commandBuffer); @@ -1038,8 +1065,8 @@ int main() // draw - collada_state.vulkan.excludeMaterialIndex = -1; - collada_state.vulkan.pipelineIndex = 1; // non-shadow pipeline + //collada_state.vulkan.excludeMaterialIndex = -1; + //collada_state.vulkan.pipelineIndex = 1; // non-shadow pipeline //collada_state.draw(); //collada_state.vulkan.pipelineIndex = 2; // geometry shader pipeline //collada_state.draw(); @@ -1048,6 +1075,8 @@ int main() font_state.draw(commandBuffer, frameIndex); + renpy_state.draw(commandBuffer, frameIndex); + vkCmdEndRendering(commandBuffer); // barrier @@ -1138,8 +1167,8 @@ int main() VK_CHECK(vkDeviceWaitIdle(device)); - collada_state.vulkan.destroy_all(collada_scene_descriptor); - collada_state.unload_scene(); + //collada_state.vulkan.destroy_all(collada_scene_descriptor); + //collada_state.unload_scene(); for (uint32_t i = 0; i < maxFramesInFlight; i++) { vkDestroyFence(device, fences[i], nullptr); diff --git a/src/renpy/vulkan.cpp b/src/renpy/vulkan.cpp new file mode 100644 index 0000000..eef9669 --- /dev/null +++ b/src/renpy/vulkan.cpp @@ -0,0 +1,451 @@ +#include + +#include "volk/volk.h" +#include "vulkan/vk_enum_string_helper.h" + +#include "dds/validate.h" +#include "vulkan_helper.h" +#include "check.h" +#include "new.h" +#include "file.h" + +#include "renpy/vulkan.h" +#include "renpy/script.h" + +namespace renpy { + static const _Float16 vertexData[] = { + // x y u v + (_Float16)-1.0, (_Float16)-1.0, (_Float16)0.0, (_Float16)0.0, + (_Float16)1.0, (_Float16)-1.0, (_Float16)1.0, (_Float16)0.0, + (_Float16)-1.0, (_Float16)1.0, (_Float16)0.0, (_Float16)1.0, + (_Float16)1.0, (_Float16)1.0, (_Float16)1.0, (_Float16)1.0, + }; + static const uint32_t vertexSize = (sizeof (vertexData)); + + static const uint16_t indexData[] = { + 0, 1, 2, 3, + }; + static const uint32_t indexSize = (sizeof (indexData)); + + void vulkan::initial_state(VkInstance instance, + VkDevice device, + VkQueue queue, + VkCommandPool commandPool, + VkPhysicalDeviceProperties physicalDeviceProperties, + VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProperties, + VkFormat colorFormat, + VkFormat depthFormat, + VkSampler linearSampler) + { + this->instance = instance; + this->device = device; + this->queue = queue; + this->commandPool = commandPool; + + this->physicalDeviceProperties = physicalDeviceProperties; + this->physicalDeviceMemoryProperties = physicalDeviceMemoryProperties; + + this->colorFormat = colorFormat; + this->depthFormat = depthFormat; + + this->linearSampler = linearSampler; + } + + void vulkan::init() + { + load_vertex_index_buffer(); + load_shader(); + create_descriptor_sets(); + load_images(); + write_descriptor_sets(); + create_pipeline(); + } + + ////////////////////////////////////////////////////////////////////// + // vertex index buffer + ////////////////////////////////////////////////////////////////////// + + void vulkan::load_vertex_index_buffer() + { + void const * vertexStart = (void const *)vertexData; + void const * indexStart = (void const *)indexData; + + vertexIndex = createVertexIndexBuffer(device, + physicalDeviceProperties, + physicalDeviceMemoryProperties, + vertexStart, vertexSize, + indexStart, indexSize); + } + + ////////////////////////////////////////////////////////////////////// + // shader + ////////////////////////////////////////////////////////////////////// + + void vulkan::load_shader() + { + uint32_t shaderSize; + void const * shaderStart = file::open("shader/renpy.spv", &shaderSize); + + VkShaderModuleCreateInfo shaderModuleCreateInfo{ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = shaderSize, + .pCode = (uint32_t *)shaderStart + }; + VK_CHECK(vkCreateShaderModule(device, &shaderModuleCreateInfo, nullptr, &shaderModule)); + } + + ////////////////////////////////////////////////////////////////////// + // descriptor sets + ////////////////////////////////////////////////////////////////////// + + void vulkan::create_descriptor_sets() + { + // + // pool + // + constexpr int descriptorPoolSizesCount = 2; + VkDescriptorPoolSize descriptorPoolSizes[descriptorPoolSizesCount]{ + { // linear sampler + .type = VK_DESCRIPTOR_TYPE_SAMPLER, + .descriptorCount = 1, + }, + { + .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + .descriptorCount = script::images_length, + }, + }; + VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .maxSets = 1, + .poolSizeCount = descriptorPoolSizesCount, + .pPoolSizes = descriptorPoolSizes + }; + VK_CHECK(vkCreateDescriptorPool(device, &descriptorPoolCreateInfo, nullptr, &descriptorPool)); + + // + // (set 0, constant) + // + { + constexpr int bindingCount = 2; + VkDescriptorSetLayoutBinding descriptorSetLayoutBindings[bindingCount]{ + { + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT + }, + { // font image + .binding = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + .descriptorCount = script::images_length, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT + } + }; + + VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = bindingCount, + .pBindings = descriptorSetLayoutBindings + }; + VK_CHECK(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCreateInfo, nullptr, &descriptorSetLayouts[0])); + + VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = descriptorPool, + .descriptorSetCount = 1, + .pSetLayouts = &descriptorSetLayouts[0] + }; + VK_CHECK(vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, &descriptorSet0)); + } + } + + ////////////////////////////////////////////////////////////////////// + // descriptor set writes + ////////////////////////////////////////////////////////////////////// + + void vulkan::write_descriptor_sets() + { + constexpr uint32_t writeCount = 2; + VkWriteDescriptorSet writeDescriptorSets[writeCount]; + uint32_t writeIndex = 0; + + // set0 bindings + VkDescriptorImageInfo samplerDescriptorImageInfo = { + .sampler = linearSampler, + }; + writeDescriptorSets[writeIndex++] = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptorSet0, + .dstBinding = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER, + .pImageInfo = &samplerDescriptorImageInfo + }; + + VkDescriptorImageInfo * sceneDescriptorImageInfos = NewM(script::images_length); + for (int i = 0; i < script::images_length; i++) { + sceneDescriptorImageInfos[i] = { + .imageView = images[i].imageView, + .imageLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL + }; + } + + writeDescriptorSets[writeIndex++] = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptorSet0, + .dstBinding = 1, + .descriptorCount = (uint32_t)script::images_length, + .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + .pImageInfo = sceneDescriptorImageInfos + }; + + assert(writeIndex == writeCount); + vkUpdateDescriptorSets(device, writeIndex, writeDescriptorSets, 0, nullptr); + //free(sceneDescriptorImageInfos); + } + + ////////////////////////////////////////////////////////////////////// + // images + ////////////////////////////////////////////////////////////////////// + + void vulkan::load_image_inner(VkCommandBuffer commandBuffer, VkFence fence, int i, char const * filename) + { + size_t length = strlen(filename); + if (dds::isDDSExtension(filename, length)) { + createImageFromFilenameDDS(device, + queue, + commandBuffer, + fence, + physicalDeviceProperties.limits.nonCoherentAtomSize, + physicalDeviceMemoryProperties, + filename, + &images[i].image, + &images[i].memory, + &images[i].imageView); + } else { + fprintf(stderr, "filename: %s\n", filename); + ASSERT(false, "invalid image filename extension"); + } + } + + void vulkan::load_images() + { + VkCommandBuffer commandBuffer{}; + VkCommandBufferAllocateInfo commandBufferAllocateInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = commandPool, + .commandBufferCount = 1 + }; + VK_CHECK(vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffer)); + + VkFenceCreateInfo fenceCreateInfo{ + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO + }; + VkFence fence{}; + VK_CHECK(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence)); + + // images + images = NewM(script::images_length); + + for (int i = 0; i < script::images_length; i++) { + char const * filename = script::images[i].path; + load_image_inner(commandBuffer, fence, i, filename); + } + + // cleanup + + vkDestroyFence(device, fence, nullptr); + vkFreeCommandBuffers(device, + commandPool, + 1, + &commandBuffer); + } + + ////////////////////////////////////////////////////////////////////// + // pipeline + ////////////////////////////////////////////////////////////////////// + + void vulkan::create_pipeline() + { + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .setLayoutCount = descriptorSetLayoutCount, + .pSetLayouts = descriptorSetLayouts, + .pushConstantRangeCount = 0, + .pPushConstantRanges = nullptr + }; + VK_CHECK(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); + + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP + }; + + VkPipelineShaderStageCreateInfo shaderStages[2]{ + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_VERTEX_BIT, + .module = shaderModule, + .pName = "VSMain" + }, + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = shaderModule, + .pName = "PSMain" + } + }; + + VkPipelineViewportStateCreateInfo viewportState{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, + .scissorCount = 1 + }; + + constexpr uint32_t dynamicStateCount = 2; + VkDynamicState dynamicStates[dynamicStateCount]{ + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, + }; + VkPipelineDynamicStateCreateInfo dynamicState{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .dynamicStateCount = dynamicStateCount, + .pDynamicStates = dynamicStates + }; + + VkPipelineDepthStencilStateCreateInfo depthStencilState{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .depthTestEnable = VK_FALSE, + .depthWriteEnable = VK_FALSE, + .depthCompareOp = VK_COMPARE_OP_ALWAYS, + .stencilTestEnable = VK_FALSE, + .front = { + .failOp = VK_STENCIL_OP_REPLACE, + .passOp = VK_STENCIL_OP_REPLACE, + .depthFailOp = VK_STENCIL_OP_REPLACE, + .compareOp = VK_COMPARE_OP_ALWAYS, + .compareMask = 0x01, + .writeMask = 0x01, + .reference = 1, + }, + .back = { + .failOp = VK_STENCIL_OP_REPLACE, + .passOp = VK_STENCIL_OP_REPLACE, + .depthFailOp = VK_STENCIL_OP_REPLACE, + .compareOp = VK_COMPARE_OP_ALWAYS, + .compareMask = 0x01, + .writeMask = 0x01, + .reference = 1, + }, + }; + + VkPipelineRenderingCreateInfo renderingCreateInfo{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO, + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &colorFormat, + .depthAttachmentFormat = depthFormat, + .stencilAttachmentFormat = depthFormat + }; + + VkPipelineColorBlendAttachmentState blendAttachment{ + .colorWriteMask = 0xF + }; + VkPipelineColorBlendStateCreateInfo colorBlendState{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .attachmentCount = 1, + .pAttachments = &blendAttachment + }; + VkPipelineRasterizationStateCreateInfo rasterizationState{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + //.cullMode = VK_CULL_MODE_BACK_BIT, + .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, + .lineWidth = 1.0f + }; + VkPipelineMultisampleStateCreateInfo multisampleState{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT + }; + + constexpr int vertexBindingDescriptionsCount = 1; + VkVertexInputBindingDescription vertexBindingDescriptions[vertexBindingDescriptionsCount]{ + { + .binding = 0, + .stride = perVertexSize, + .inputRate = VK_VERTEX_INPUT_RATE_VERTEX + }, + }; + + constexpr int vertexAttributeDescriptionsCount = 2; + VkVertexInputAttributeDescription vertexAttributeDescriptions[vertexAttributeDescriptionsCount]{ + // per-vertex + { // position + .location = 0, + .binding = 0, + .format = VK_FORMAT_R16G16_SFLOAT, + .offset = 0, + }, + { // texture + .location = 1, + .binding = 0, + .format = VK_FORMAT_R16G16_SFLOAT, + .offset = 4, + }, + }; + + VkPipelineVertexInputStateCreateInfo vertexInputState{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .vertexBindingDescriptionCount = vertexBindingDescriptionsCount, + .pVertexBindingDescriptions = vertexBindingDescriptions, + .vertexAttributeDescriptionCount = vertexAttributeDescriptionsCount, + .pVertexAttributeDescriptions = vertexAttributeDescriptions, + }; + + VkGraphicsPipelineCreateInfo pipelineCreateInfos[1]{ + { + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = &renderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputState, + .pInputAssemblyState = &inputAssemblyState, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizationState, + .pMultisampleState = &multisampleState, + .pDepthStencilState = &depthStencilState, + .pColorBlendState = &colorBlendState, + .pDynamicState = &dynamicState, + .layout = pipelineLayout + }, + }; + + VK_CHECK(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, pipelineCreateInfos, nullptr, &pipeline)); + } + + ////////////////////////////////////////////////////////////////////// + // draw + ////////////////////////////////////////////////////////////////////// + + void vulkan::draw(VkCommandBuffer commandBuffer, + uint32_t frameIndex) + { + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + + VkDescriptorSet descriptorSets[1] = { + descriptorSet0, + }; + vkCmdBindDescriptorSets(commandBuffer, + VK_PIPELINE_BIND_POINT_GRAPHICS, + pipelineLayout, + 0, 1, descriptorSets, + 0, nullptr); + + vkCmdBindIndexBuffer(commandBuffer, vertexIndex.buffer, vertexIndex.indexOffset, VK_INDEX_TYPE_UINT16); + + VkDeviceSize vertexOffsets[1]{ 0 }; + VkBuffer vertexBuffers[1]{ vertexIndex.buffer }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, vertexOffsets); + + vkCmdDrawIndexed(commandBuffer, 4, 1, 0, 0, 0); + } +}