From 76b3edcf1b3573f31a5d0268f94e0c85371e5ee8 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Wed, 3 Jun 2026 15:27:34 -0500 Subject: [PATCH] audio: run-time mixing and reverberation, with on-screen UI --- Makefile | 24 +- data/font/bitmap/terminus_128x64_6x12.data | Bin 0 -> 8192 bytes filenames.txt | 4 + include/audio.h | 99 +++++ include/font/bitmap.h | 18 + include/font/bitmap/vulkan.h | 64 +++ include/font/instance_data.h | 15 + include/intstring.h | 54 +++ include/mapped_instance_data.h | 22 + include/minmax.h | 8 + include/mouse.h | 17 + include/renpy/interpreter.h | 10 +- include/renpy/language.h | 2 +- include/ui.h | 10 + include/ui/instance_data.h | 16 + include/ui/vulkan.h | 45 ++ include/ui/widget.h | 151 +++++++ include/vulkan_helper.h | 33 +- include/vulkan_state.h | 31 ++ renpy-parser/transform.py | 2 +- shader/font/bitmap.hlsl | 48 +++ shader/ui/solid.hlsl | 38 ++ src/audio.cpp | 303 +++++++++++-- src/font/bitmap/vulkan.cpp | 269 ++++++++++++ src/font/outline.cpp | 2 +- src/main.cpp | 70 ++- src/renpy/script.cpp | 470 ++++++++++----------- src/renpy/vulkan.cpp | 2 +- src/ui.cpp | 169 ++++++++ src/ui/vulkan.cpp | 118 ++++++ src/ui/widget.cpp | 221 ++++++++++ src/vulkan_helper.cpp | 244 ++++++++++- src/vulkan_state.cpp | 43 ++ 33 files changed, 2327 insertions(+), 295 deletions(-) create mode 100644 data/font/bitmap/terminus_128x64_6x12.data create mode 100644 include/font/bitmap.h create mode 100644 include/font/bitmap/vulkan.h create mode 100644 include/font/instance_data.h create mode 100644 include/intstring.h create mode 100644 include/mapped_instance_data.h create mode 100644 include/mouse.h create mode 100644 include/ui.h create mode 100644 include/ui/instance_data.h create mode 100644 include/ui/vulkan.h create mode 100644 include/ui/widget.h create mode 100644 include/vulkan_state.h create mode 100644 shader/font/bitmap.hlsl create mode 100644 shader/ui/solid.hlsl create mode 100644 src/font/bitmap/vulkan.cpp create mode 100644 src/ui.cpp create mode 100644 src/ui/vulkan.cpp create mode 100644 src/ui/widget.cpp create mode 100644 src/vulkan_state.cpp diff --git a/Makefile b/Makefile index a307e73..2236dd5 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ OBJARCH = elf64-x86-64 UNAME := $(shell uname -s) -OPT += -O3 +OPT += -O0 OPT += -march=core-avx2 DEBUG = -g @@ -17,12 +17,13 @@ CXXSTD = -std=gnu++20 CFLAGS += -Wall -Werror CFLAGS += -Wfatal-errors CFLAGS += -Wno-error=unused-variable -#CFLAGS += -Wno-error=unused-but-set-variable +CFLAGS += -Wno-error=unused-but-set-variable CFLAGS += -Wno-format-security CFLAGS += -Wno-format CFLAGS += -Wno-error=unused-function CFLAGS += -Wno-error=array-bounds CFLAGS += -Wno-unknown-pragmas +CFLAGS += -Wno-vla-cxx-extension CFLAGS += -fno-strict-aliasing CFLAGS += -I./include CFLAGS += -I./data @@ -35,13 +36,14 @@ CFLAGS += -fpic CFLAGS += -ffunction-sections CFLAGS += -fdata-sections -#FLAGS += -fstack-protector -fstack-protector-all -fno-omit-frame-pointer -fsanitize=address +FLAGS += -fstack-protector -fstack-protector-all -fno-omit-frame-pointer -fsanitize=address +CXXFLAGS += -fno-exceptions -fno-non-call-exceptions -fno-rtti -fno-threadsafe-statics LDFLAGS += -lm #LDFLAGS += -Wl,--gc-sections #-Wl,--print-gc-sections ifeq ($(UNAME),Linux) -LDFLAGS += -Wl,-z noexecstack +#LDFLAGS += -Wl,-z noexecstack endif ifeq ($(UNAME),Darwin) LDFLAGS += -framework Foundation -framework Cocoa -framework IOKit -framework AVFoundation -framework CoreVideo -framework CoreAudio -framework CoreMedia -framework CoreHaptics -framework AudioToolbox -framework GameController -framework ForceFeedback -framework Carbon -framework Metal -framework QuartzCore -framework UniformTypeIdentifiers @@ -67,8 +69,10 @@ OBJS = \ src/pack.o \ src/dds/validate.o \ src/vulkan_helper.o \ + src/vulkan_state.o \ src/tga/tga.o \ src/font/outline.o \ + src/font/bitmap/vulkan.o \ src/renpy/vulkan.o \ src/renpy/composite/vulkan.o \ src/renpy/script.o \ @@ -77,7 +81,10 @@ OBJS = \ src/audio.o \ src/poem/birdsong.o \ src/poem/eleanorthehero.o \ - src/poem/kiristella.o + src/poem/kiristella.o \ + src/ui/vulkan.o \ + src/ui/widget.o \ + src/ui.o ZLIB = ../zlib-1.3.2 CFLAGS += -I$(ZLIB) @@ -107,11 +114,14 @@ endif all: main +CC = clang +CXX = clang++ + %.o: %.c $(CC) $(ARCH) $(CSTD) $(CFLAGS) $(FLAGS) $(OPT) $(DEBUG) -c $< -o $@ %.o: %.cpp - $(CXX) $(ARCH) $(CXXSTD) $(CFLAGS) $(FLAGS) $(OPT) $(DEBUG) -c $< -o $@ + $(CXX) $(ARCH) $(CXXSTD) $(CFLAGS) $(CXXFLAGS) $(FLAGS) $(OPT) $(DEBUG) -c $< -o $@ %.o: %.s $(AS) $< -o $@ @@ -132,7 +142,7 @@ all: main # ./tools/opus_encode $< $@ main: $(OBJS) $(LIBS) - $(CC) $(ARCH) $(LDFLAGS) $(FLAGS) $(OPT) $(DEBUG) $^ -o $@ + $(CXX) $(ARCH) $(LDFLAGS) $(FLAGS) $(OPT) $(DEBUG) $^ -o $@ %.spv: %.hlsl ../dxc/bin/dxc -spirv -T lib_6_3 -fspv-target-env=vulkan1.3 $< -Fo $@ diff --git a/data/font/bitmap/terminus_128x64_6x12.data b/data/font/bitmap/terminus_128x64_6x12.data new file mode 100644 index 0000000000000000000000000000000000000000..1639010e30f3085a1b186393371a6e7968b6b43d GIT binary patch literal 8192 zcmeHH3wGNu3~TOx=@A4#Qnu^ZS>9W>MRg>8K!B3vetrF*@%Vb2b#t0LE4+|PZtANG zMY1I-OCO8OXT$+}JQB+(ZADfRfv~6xU}0)0(L^G8VTcM{sJ8QDxMS(c3W$eOM&+6QSw=Z0q8uqmy#M49SF{g6l} zmFFeFVQZH`01oljGGZ*PnXITFCRf!p5eXUYWRax7`l)YZa4wxzf2fmSFHz;fOcP|J zlLIoEL)5B9^Ah;52yz}e^{ETb|{{()< z3Cue&ZQZ}~CV7*_42CGlxlG(%495YGofds5@uquLu$H=$X<#z)(2igN$87vQLlNi6 zx1!WGx0u?}#|}5Wu#)kWWy9=)=|&kTe)QEzcsVKrjnc2$(jWXPi3luu;BqyC$_a(M zMi*qj9MVrbV+3yP)Txs?)^AQokVoqBA(Qb+hEc2tayP7=_KXk*=B{wet8+#P3^xW) zTZGTCIKyb1(abud?`&?0_Kb+7YRX9VK$h~TEmK*vi=&f1)47YMQ_4I=eE@&e3HYVr zBJfTy(so$9F}y2>_N+a$qslz~7!U{>GX~Or;6~<!mLw=@1?0h5r25<#|ZrlD%VR`7^p*` zK;&RhHeX>P_&SYdo)pOwlVjq8Yih5IIT7Br%09W80&y2JN6N4>p}aswT+s(D@aAJ} z&lHeIBAGK@YXB%dvqPU6NP6VaOD7LkcI3mVlZOi^v$W|=d#NRsFDz>zX-?Sp2l)TV z@y9pzS_7`~`i^(Ajqp471&d)J!gqd7|?h1#m@R-k@CKKAgt&tBg&{{V&K^7eml~7 zv^4YlV-COOaK`!5CX+A#*MnNBPmBpbig-~b9U~dOG;Q$VZX!FD&bPG#T?_DB7OY!P8b zbkbKZ*stWZF3kjq>&w?(B0GJ9M2T0!bvgT5c7jlZAx!}vJ? GoWL)-)&&3n literal 0 HcmV?d00001 diff --git a/filenames.txt b/filenames.txt index 9ca4650..16edd28 100644 --- a/filenames.txt +++ b/filenames.txt @@ -1,9 +1,13 @@ shader/font.spv +shader/font/bitmap.spv data/font/outline/medieval_sharp_24.data +data/font/bitmap/terminus_128x64_6x12.data shader/renpy.spv shader/renpy_composite.spv +shader/ui/solid.spv + audio/sfx/Chime.opus.bin audio/music/MistAmbience.opus.bin audio/music/Preludium.opus.bin diff --git a/include/audio.h b/include/audio.h index 0035f21..dc5cb12 100644 --- a/include/audio.h +++ b/include/audio.h @@ -4,6 +4,105 @@ #include "poem.h" namespace audio { + constexpr int sample_rate = 48000; + constexpr int channels = 2; + + template + struct FeedbackCombFilter { + private: + //float * buffer; + float buffer[maxDelay]; + int index; + public: + int delay; + float gain0; + float gainM; + + FeedbackCombFilter(int delay, float gain0, float gainM); + void reset(); + float feed(float value); + }; + + template + struct FeedforwardCombFilter { + private: + //float * buffer; + float buffer[maxDelay]; + int index; + public: + int delay; + float gain0; + float gainM; + + FeedforwardCombFilter(int delay, float gain0, float gainM); + void reset(); + float feed(float value); + }; + + template + struct AllpassFilter { + private: + //float * buffer; + float buffer[maxDelay]; + int index; + + public: + int delay; + float gain0; + float gainM; + + AllpassFilter(int delay, float gain0, float gainM); + void reset(); + float feed(float x); + }; + + using FBCF = FeedbackCombFilter<15000>; + using FFCF = FeedforwardCombFilter<15000>; + using AP = AllpassFilter<2500>; + + struct lr { float l; float r; }; + + struct FBReverb { + static constexpr int cfCount = 4; + static constexpr int apCount = 3; + + FBCF cf[cfCount]; + AP ap[apCount]; + + FBReverb(); + void reset(); + lr feed(float x); + }; + + struct FFReverb { + static constexpr int __cfCount = 4; + static constexpr int apCount = 3; + + FFCF cf[__cfCount]; + AP ap[apCount]; + + FFReverb(); + void reset(); + lr feed(float x); + }; + + extern int reverbIndex; + extern FBReverb fbreverb; + extern FFReverb ffreverb; + + extern float wetGain; + extern float dryGain; + + struct mix_channel { + enum { + music = 0, + poem = 1, + voice = 2, + }; + }; + + constexpr int mixChannelCount = 3; + extern float mixChannelGain[mixChannelCount]; extern int poem_timestamp_index; extern int poem_line_index; diff --git a/include/font/bitmap.h b/include/font/bitmap.h new file mode 100644 index 0000000..4847056 --- /dev/null +++ b/include/font/bitmap.h @@ -0,0 +1,18 @@ +#pragma once + +#include "instance_data.h" + +namespace font::bitmap { + static inline uint16_2 glyphIndex(int c) + { + assert(c >= 32 && c <= 127); + + c -= 32; + + int stride = 128 / 6; + + int x = c % stride; + int y = c / stride; + return {(uint16_t)x, (uint16_t)y}; + } +} diff --git a/include/font/bitmap/vulkan.h b/include/font/bitmap/vulkan.h new file mode 100644 index 0000000..1f9f5eb --- /dev/null +++ b/include/font/bitmap/vulkan.h @@ -0,0 +1,64 @@ +#pragma once + +#include "vulkan_state.h" +#include "font/instance_data.h" + +namespace font::bitmap { + struct LoadedFont { + VkImage image; + VkDeviceMemory memory; + VkImageView imageView; + }; + + struct vulkan { + static constexpr int maximumGlyphCount = 1024; + static constexpr VkDeviceSize instanceBufferSize{ maximumGlyphCount * (sizeof (BitmapInstance)) }; + + VulkanState const * const vk; + + VkPipelineLayout pipelineLayout; + VkPipeline pipeline; + ::InstanceBuffer instanceBuffer; + BitmapInstance * bitmapInstance[2]; + + VkDescriptorPool descriptorPool; + static constexpr uint32_t descriptorSetLayoutCount = 1; + VkDescriptorSetLayout descriptorSetLayouts[descriptorSetLayoutCount]; + VkDescriptorSet descriptorSet0; + + LoadedFont loadedFont; + + vulkan(VulkanState const * vk) + : vk(vk) + { + createDescriptorSets(); + createPipeline(); + + loadedFont = loadFont(); + writeDescriptorSets(loadedFont.imageView); + + createInstanceBuffer(vk->device, + vk->physicalDeviceProperties, + vk->physicalDeviceMemoryProperties, + instanceBufferSize, + &instanceBuffer); + + bitmapInstance[0] = (BitmapInstance *)(((ptrdiff_t)instanceBuffer.mappedData) + instanceBuffer.offset[0]); + bitmapInstance[1] = (BitmapInstance *)(((ptrdiff_t)instanceBuffer.mappedData) + instanceBuffer.offset[1]); + } + + LoadedFont loadFont(); + void createDescriptorSets(); + void writeDescriptorSets(VkImageView imageView); + + void update(uint32_t frameIndex, + uint32_t glyphCount) const; + + void draw(VkCommandBuffer commandBuffer, + uint32_t frameIndex, + uint32_t glyphCount) const; + + private: + void createPipeline(); + }; +} diff --git a/include/font/instance_data.h b/include/font/instance_data.h new file mode 100644 index 0000000..f7bcdbe --- /dev/null +++ b/include/font/instance_data.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#include "mapped_instance_data.h" + +namespace font { + + struct BitmapInstance { + uint16_2 position; + uint16_2 glyph; + }; + static_assert((sizeof (BitmapInstance)) == 2 * 2 + 4); +} diff --git a/include/intstring.h b/include/intstring.h new file mode 100644 index 0000000..312fa45 --- /dev/null +++ b/include/intstring.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +namespace string { + static inline int decLength(int32_t n) + { + if (n >= 1000000000) return 10; + if (n >= 100000000) return 9; + if (n >= 10000000) return 8; + if (n >= 1000000) return 7; + if (n >= 100000) return 6; + if (n >= 10000) return 5; + if (n >= 1000) return 4; + if (n >= 100) return 3; + if (n >= 10) return 2; + return 1; + } + + template + static inline int dec(T * c, int len, int32_t n) + { + int index = 0; + if (n < 0) { + c[index++] = '-'; + n = -n; + } + int numLength = fixed > 0 ? fixed : decLength(n); + assert(len >= (numLength + index)); + for (int i = (numLength - 1); i >= 0; i--) { + const int32_t digit = n % 10; + n = n / 10; + c[index + i] = digit + 48; + } + return numLength + index; + } + + template + static inline int flt(T * c, int len, float n) + { + int index = 0; + if (n < 0) { + c[index++] = '-'; + n = -n; + } + int32_t whole = n; + index += dec(&c[index], len - index, whole); + c[index++] = '.'; + int32_t fraction = (int32_t)((n - (float)whole) * 1000.0f); + index += dec(&c[index], len - index, fraction); + return index; + } +} diff --git a/include/mapped_instance_data.h b/include/mapped_instance_data.h new file mode 100644 index 0000000..54d4754 --- /dev/null +++ b/include/mapped_instance_data.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +struct uint16_2 { + uint16_t x; + uint16_t y; +}; + +template +struct MappedInstanceData { + T * buffer; + int length; + int index; + + void append(T const & value) + { + assert(index < length); + buffer[index] = value; + index += 1; + } +}; diff --git a/include/minmax.h b/include/minmax.h index 27cf838..6eceb95 100644 --- a/include/minmax.h +++ b/include/minmax.h @@ -17,3 +17,11 @@ inline static constexpr T clamp(T n, T minVal, T maxVal) { return min(max(n, minVal), maxVal); } + +template +static inline T clamp01(T a) +{ + if (a < 0.0) return 0.0; + if (a > 1.0) return 1.0; + return a; +} diff --git a/include/mouse.h b/include/mouse.h new file mode 100644 index 0000000..ee2a621 --- /dev/null +++ b/include/mouse.h @@ -0,0 +1,17 @@ +namespace mouse +{ + void normalize(int windowWidth, int windowHeight, float mx, float my, float * mxf, float * myf) + { + int canonicalSizeX = 1280; + int canonicalSizeY = 720; + int scaleFactor = 1; + while (canonicalSizeX * (scaleFactor + 1) <= windowWidth && canonicalSizeY * (scaleFactor + 1) <= windowHeight) { + scaleFactor += 1; + } + float scaleFactorInverse = 1.0f / ((float)scaleFactor); + int offsetX = (windowWidth - (canonicalSizeX * scaleFactor)) / 2; + int offsetY = (windowHeight - (canonicalSizeY * scaleFactor)) / 2; + *mxf = ((float)(mx - offsetX)) * scaleFactorInverse; + *myf = ((float)(my - offsetY)) * scaleFactorInverse; + } +} diff --git a/include/renpy/interpreter.h b/include/renpy/interpreter.h index 6191b09..40c30f0 100644 --- a/include/renpy/interpreter.h +++ b/include/renpy/interpreter.h @@ -12,11 +12,11 @@ namespace renpy { }; static constexpr top_left transforms[] = { - [language::transform::left] = { .top = 192, .left = 0 }, - [language::transform::centerleft] = { .top = 192, .left = 240 }, - [language::transform::center] = { .top = 192, .left = 416 }, - [language::transform::centerright] = { .top = 192, .left = 588 }, - [language::transform::right] = { .top = 192, .left = 828 }, + /*[language::transform::left] =*/ { .top = 192, .left = 0 }, + /*[language::transform::centerleft] =*/ { .top = 192, .left = 240 }, + /*[language::transform::center] =*/ { .top = 192, .left = 416 }, + /*[language::transform::centerright] =*/ { .top = 192, .left = 588 }, + /*[language::transform::right] =*/ { .top = 192, .left = 828 }, }; static constexpr int transformsCount = (sizeof (transforms)) / (sizeof (transforms[0])); diff --git a/include/renpy/language.h b/include/renpy/language.h index 0ee4fea..71fc35e 100644 --- a/include/renpy/language.h +++ b/include/renpy/language.h @@ -47,7 +47,7 @@ namespace renpy::language { char const * const path; double loop_end; uint32_t audio_flags; - double attenuation; + double gain; }; struct image { diff --git a/include/ui.h b/include/ui.h new file mode 100644 index 0000000..ed9e71b --- /dev/null +++ b/include/ui.h @@ -0,0 +1,10 @@ +#pragma once + +#include "ui/widget.h" + +namespace ui +{ + void draw(MappedInstanceData & data, + MappedInstanceData & fontData); + void update(float mx, float my, bool mLeft, bool mEdge); +} diff --git a/include/ui/instance_data.h b/include/ui/instance_data.h new file mode 100644 index 0000000..f8a67d6 --- /dev/null +++ b/include/ui/instance_data.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#include "mapped_instance_data.h" + +namespace ui { + + struct SolidInstance { + uint16_2 position; + uint16_2 size; + uint32_t color; + }; + static_assert((sizeof (SolidInstance)) == 2 * 2 * 2 + 4); +} diff --git a/include/ui/vulkan.h b/include/ui/vulkan.h new file mode 100644 index 0000000..d5accee --- /dev/null +++ b/include/ui/vulkan.h @@ -0,0 +1,45 @@ +#pragma once + +#include "vulkan_state.h" +#include "ui/instance_data.h" + +#include "font/instance_data.h" + +namespace ui { + struct vulkan { + static constexpr int maximumUIElements = 1024; + static constexpr VkDeviceSize instanceBufferSize{ maximumUIElements * (sizeof (SolidInstance)) }; + + VulkanState const * const vk; + + VkPipelineLayout pipelineLayout; + VkPipeline pipeline; + ::InstanceBuffer instanceBuffer; + SolidInstance * solidInstance[2]; + + vulkan(VulkanState const * vk) + : vk(vk) + { + createPipeline(); + + createInstanceBuffer(vk->device, + vk->physicalDeviceProperties, + vk->physicalDeviceMemoryProperties, + instanceBufferSize, + &instanceBuffer); + + solidInstance[0] = (SolidInstance *)(((ptrdiff_t)instanceBuffer.mappedData) + instanceBuffer.offset[0]); + solidInstance[1] = (SolidInstance *)(((ptrdiff_t)instanceBuffer.mappedData) + instanceBuffer.offset[1]); + } + + int update(uint32_t frameIndex, + MappedInstanceData & bitmapData) const; + + void draw(VkCommandBuffer commandBuffer, + uint32_t frameIndex, + MappedInstanceData & bitmapData) const; + + private: + void createPipeline(); + }; +} diff --git a/include/ui/widget.h b/include/ui/widget.h new file mode 100644 index 0000000..0d5bd77 --- /dev/null +++ b/include/ui/widget.h @@ -0,0 +1,151 @@ +#pragma once + +#include "ui/instance_data.h" +#include "font/bitmap.h" + +namespace ui::widget +{ + struct BoundingBox + { + int left; + int top; + int width; + int height; + + bool inside(float x, float y) + { + return (x >= left) && (x <= (left + width)) + && (y >= top) && (y <= (top + height)); + } + }; + + struct Widget { + //virtual void mouseDown(float x, float y) {}; + virtual void draw(MappedInstanceData & data, + MappedInstanceData & fontData) = 0; + + virtual void update(float mx, float my, bool mLeft, bool mEdge) = 0; + }; + + template + struct Radio : public Widget + { + char const * label; + char const ** boxLabels; + int width; + int height; + int * selected; + BoundingBox box[optionCount]; + + Radio(char const * label, + char const ** boxLabels, + int left, int top, int width, int height, + int hSpace, int vSpace, + int * selected) + : label(label) + , boxLabels(boxLabels) + , width((optionCount - 1) * hSpace + width) + , height((optionCount - 1) * vSpace + height) + , selected(selected) + { + int x = left; + int y = top; + for (int i = 0; i < optionCount; i++) { + box[i].left = x; + box[i].top = y; + box[i].width = width; + box[i].height = height; + x += hSpace; + y += vSpace; + } + } + + void draw(MappedInstanceData & data, + MappedInstanceData & fontData) override; + + void update(float mx, float my, bool mLeft, bool mEdge) override; + }; + + template + struct Slider : public Widget + { + bool drag; + + char const * label; + + BoundingBox sliderBorder; + BoundingBox slider; + BoundingBox lminus; + BoundingBox lplus; + BoundingBox rminus; + BoundingBox rplus; + + T minValue; + T maxValue; + T minExtent; + T maxExtent; + T * value; + + Slider(char const * label, + int left, int top, int width, int height, + T minValue, T maxValue, + T minExtent, T maxExtent, + T * value) + : drag{ false } + , label{ label } + , sliderBorder{ left, top, width, height } + , slider{ left, top + 2, width, height - 4 } + , lminus{ left - 21, top + 3, 8, 8 } + , lplus{ left - 11, top + 3, 8, 8 } + , rminus{ left + width + 3, top + 3, 8, 8 } + , rplus{ left + width + 13, top + 3, 8, 8 } + , minValue{ minValue } + , maxValue{ maxValue } + , minExtent{ minExtent } + , maxExtent{ maxExtent } + , value{ value } + {} + + void draw(MappedInstanceData & data, + MappedInstanceData & fontData) override; + + void update(float mx, float my, bool mLeft, bool mEdge) override; + }; + + struct DelayGainSlider : public Widget + { + char const * label; + + Slider delay; + Slider gain0; + Slider gainM; + + DelayGainSlider(char const * label, + int left, int top, + int delayMin, int delayMax, int * delayValue, + float gainMin, float gainMax, + float * gain0Value, float * gainMValue) + : label(label) + , delay("delay", + left, top, 150, 14, + delayMin, delayMax, + delayMin, delayMax, + delayValue) + , gain0("gain0", + left, top + 30, 150, 14, + gainMin, gainMax, + gainMin, gainMax, + gain0Value) + , gainM("gainM", + left, top + 60, 150, 14, + gainMin, gainMax, + gainMin, gainMax, + gainMValue) + {} + + void draw(MappedInstanceData & data, + MappedInstanceData & fontData) override; + + void update(float mx, float my, bool mLeft, bool mEdge) override; + }; +} diff --git a/include/vulkan_helper.h b/include/vulkan_helper.h index 4a2f6db..0701b9c 100644 --- a/include/vulkan_helper.h +++ b/include/vulkan_helper.h @@ -94,7 +94,7 @@ void textureTransfer(VkDevice device, VkDeviceSize nonCoherentAtomSize, VkPhysicalDeviceMemoryProperties const & physicalDeviceMemoryProperties, uint32_t imageDataSize, - void * imageData, + void const * imageData, VkImage image, uint32_t width, uint32_t height, @@ -114,3 +114,34 @@ VertexIndex createVertexIndexBuffer(VkDevice device, uint32_t vertexSize, void const * indexStart, uint32_t indexSize); + +void createQuadPipeline(VkDevice device, + VkFormat colorFormat, + VkFormat depthFormat, + uint32_t descriptorSetLayoutCount, + VkDescriptorSetLayout const * descriptorSetLayouts, + uint32_t pushConstantRangeCount, + VkPushConstantRange const * pushConstantRanges, + VkShaderModule shaderModule, + uint32_t perInstanceStride, + uint32_t instanceAttributeDescriptionCount, + VkVertexInputAttributeDescription * instanceAttributeDescriptions, + VkPipelineLayout * pipelineLayout, + VkPipeline * pipeline); + +VkShaderModule loadShader(VkDevice device, + char const * const path); + +struct InstanceBuffer { + VkDeviceSize offset[2]; + VkBuffer buffer; + VkDeviceMemory memory; + VkDeviceSize memorySize; + void * mappedData; +}; + +void createInstanceBuffer(VkDevice device, + VkPhysicalDeviceProperties const & physicalDeviceProperties, + VkPhysicalDeviceMemoryProperties const & physicalDeviceMemoryProperties, + VkDeviceSize bufferSize, + InstanceBuffer * instanceBuffer); diff --git a/include/vulkan_state.h b/include/vulkan_state.h new file mode 100644 index 0000000..4a5998b --- /dev/null +++ b/include/vulkan_state.h @@ -0,0 +1,31 @@ +#pragma once + +#ifdef __APPLE__ +#include "vulkan/vulkan.h" +#else +#include "volk/volk.h" +#endif + +#include "vulkan_helper.h" + +struct VulkanState { + VkInstance instance; + VkDevice device; + VkQueue queue; + VkCommandPool commandPool; + VkPhysicalDeviceProperties physicalDeviceProperties; + VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProperties; + VkFormat colorFormat; + VkFormat depthFormat; + + VertexIndex quadVertexIndex; + + VulkanState(VkInstance instance, + VkDevice device, + VkQueue queue, + VkCommandPool commandPool, + VkPhysicalDeviceProperties const & physicalDeviceProperties, + VkPhysicalDeviceMemoryProperties const & physicalDeviceMemoryProperties, + VkFormat colorFormat, + VkFormat depthFormat); +}; diff --git a/renpy-parser/transform.py b/renpy-parser/transform.py index 66c0ee0..bd9a333 100644 --- a/renpy-parser/transform.py +++ b/renpy-parser/transform.py @@ -333,7 +333,7 @@ def pass2_audio(state): else: audio_type = "0" - yield f"{{ .path = \"audio/{path}.opus.bin\", .loop_end = {float(loop)}, .audio_flags = {audio_type}, .attenuation = {attenuation} }}, // {i} {orig_path}" + yield f"{{ .path = \"audio/{path}.opus.bin\", .loop_end = {float(loop)}, .audio_flags = {audio_type}, .gain = {attenuation} }}, // {i} {orig_path}" yield "};" yield "const int audio_length = (sizeof (audio)) / (sizeof (audio[0]));" diff --git a/shader/font/bitmap.hlsl b/shader/font/bitmap.hlsl new file mode 100644 index 0000000..3422e38 --- /dev/null +++ b/shader/font/bitmap.hlsl @@ -0,0 +1,48 @@ +struct GlyphBitmap +{ + uint2 Position; // x y, in texels + uint2 Size; // width height +}; + +// set 0: constant +[[vk::binding(0, 0)]] Texture2D FontTexture; + +struct VSInput +{ + float2 Position : POSITION0; + float2 Texture : TEXCOORD0; + // per-instance + uint2 InstancePosition : InstancePosition; + uint2 InstanceGlyph : InstanceGlyph; +}; + +struct VSOutput +{ + float4 Position : SV_POSITION; + float2 Texture : NORMAL0; +}; + +[shader("vertex")] +VSOutput VSMain(VSInput input) +{ + //float2 inverseTexel = float2(1.0 / 128.0, 1.0 / 64.0); + float2 inversePixel = float2(1.0 / 1280.0, 1.0 / 720.0); + + float2 Size = float2(6, 12); + + VSOutput output = (VSOutput)0; + float2 position = (input.Texture * Size + input.InstancePosition) * inversePixel; + output.Position = float4(position * 2.0 - 1.0, 0, 1); + output.Texture = (input.Texture * Size + input.InstanceGlyph * Size); + + return output; +} + +[shader("pixel")] +float4 PSMain(VSOutput input) : SV_TARGET +{ + float4 color = FontTexture.Load(int3(input.Texture, 0)); + float c = (color.x == 0) ? 0 : 1; + + return float4(1, 1, 1, c.x); +} diff --git a/shader/ui/solid.hlsl b/shader/ui/solid.hlsl new file mode 100644 index 0000000..0f90d64 --- /dev/null +++ b/shader/ui/solid.hlsl @@ -0,0 +1,38 @@ +struct VSInput +{ + float2 Position : POSITION0; + float2 Texture : TEXCOORD0; + // per-instance + int2 InstancePosition : InstancePosition; + int2 InstanceSize : InstanceSize; + float4 Color : Color; +}; + +struct VSOutput +{ + float4 Position : SV_POSITION; + float2 Texture : Texture; + float4 Color : Color; +}; + +[shader("vertex")] +VSOutput VSMain(VSInput input) +{ + float2 inversePixel = float2(1.0 / 1280.0, 1.0 / 720.0); + + float2 position = (input.Texture * input.InstanceSize + input.InstancePosition) * inversePixel; + + VSOutput output = (VSOutput)0; + output.Position = float4(position * 2.0 - 1.0, 0, 1); + output.Texture = input.Texture; + output.Color = input.Color.zyxw; + + return output; +} + +[shader("pixel")] +float4 PSMain(VSOutput input) : SV_TARGET +{ + //return float4(input.Texture, 0, 1); + return float4(input.Color.xyzw); +} diff --git a/src/audio.cpp b/src/audio.cpp index 6e655e2..e06a7c5 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -13,10 +13,7 @@ #include "poem.h" namespace audio { - static int const frame_samples = 960; // 20 milliseconds @ 48kHz - static int const sample_rate = 48000; - static int const channels = 2; static int const sample_size = (sizeof (int16_t)); static int const max_frame_size = 960 * 3; // 20ms at 48kHz @@ -31,6 +28,79 @@ namespace audio { uint32_t sample_count; }; + template + FeedbackCombFilter::FeedbackCombFilter(int delay, float gain0, float gainM) + : delay(delay), gain0(gain0), gainM(gainM) + { + //buffer = (float *)malloc((sizeof (float)) * maxDelay); + reset(); + } + + template + void FeedbackCombFilter::reset() + { + index = 0; + memset(buffer, 0, (sizeof (float)) * maxDelay); + } + + template + float FeedbackCombFilter::feed(float value) + { + float y = gain0 * value + gainM * buffer[index]; + buffer[index] = y; + index = (index + 1) % delay; + return y; + } + + template + FeedforwardCombFilter::FeedforwardCombFilter(int delay, float gain0, float gainM) + : delay(delay), gain0(gain0), gainM(gainM) + { + //buffer = (float *)malloc((sizeof (float)) * maxDelay); + reset(); + } + + template + void FeedforwardCombFilter::reset() + { + index = 0; + memset(buffer, 0, (sizeof (float)) * maxDelay); + } + + template + float FeedforwardCombFilter::feed(float value) + { + float y = gain0 * value + gainM * buffer[index]; + buffer[index] = value; + index = (index + 1) % delay; + return y; + } + + template + AllpassFilter::AllpassFilter(int delay, float gain0, float gainM) + : delay(delay), gain0(gain0), gainM(gainM) + { + //buffer = (float *)malloc((sizeof (float)) * maxDelay); + reset(); + } + + template + void AllpassFilter::reset() + { + index = 0; + memset(buffer, 0, (sizeof (float)) * maxDelay); + } + + template + float AllpassFilter::feed(float x) + { + float v = x + -gainM * buffer[index]; + float y = buffer[index] + gain0 * v; + buffer[index] = v; + index = (index + 1) % delay; + return y; + } + struct AudioInstance { int audio_index; AudioBuffer * audio_buffer; @@ -41,13 +111,99 @@ namespace audio { poem::poem const * poem; }; + FBReverb::FBReverb() + : cf{FBCF(3229, 1.0f, 0.733f), // 1687 + FBCF(3079, 1.0f, 0.802f), // 1601 + FBCF(3943, 1.0f, 0.753f), // 2053 + FBCF(4327, 1.0f, 0.733f), // 2251 + } + , ap{AP(661, 0.7f, 0.7f), // 347 + AP(257, 0.7f, 0.7f), // 113 + AP(71, 0.7f, 0.7f), // 37 + } + { } + + void FBReverb::reset() + { + for (int i = 0; i < cfCount; i++) + cf[i].reset(); + + for (int i = 0; i < apCount; i++) + ap[i].reset(); + } + + lr FBReverb::feed(float x) + { + for (int i = 0; i < apCount; i++) { + x = ap[i].feed(x); + } + + float x0 = cf[0].feed(x); + float x1 = cf[1].feed(x); + float x2 = cf[2].feed(x); + float x3 = cf[3].feed(x); + + float s0 = x0 + x2; + float s1 = x1 + x3; + + float a = s0 + s1; + float b = s0 - s1; + return {a, b}; + } + + + FFReverb::FFReverb() + : cf{FFCF{9209, 1.0f, 0.742f}, + FFCF{9601, 1.0f, 0.733f}, + FFCF{10369, 1.0f, 0.715f}, + FFCF{11131, 1.0f, 0.697f}, + } + , ap{AP{2017, 0.7f, 0.7f}, + AP{647, 0.7f, 0.7f}, + AP{137, 0.7f, 0.7f}, + } + { } + + void FFReverb::reset() + { + for (int i = 0; i < __cfCount; i++) { + printf("reset cf %d\n", i); + cf[i].reset(); + } + + for (int i = 0; i < apCount; i++) + ap[i].reset(); + } + + lr FFReverb::feed(float x) + { + for (int i = 0; i < apCount; i++) { + x = ap[i].feed(x); + } + + float x0 = cf[0].feed(x); + float x1 = cf[1].feed(x); + float x2 = cf[2].feed(x); + float x3 = cf[3].feed(x); + + float s0 = x0 + x1 + x2 + x3; + //float s0 = x0 + x1 + x2; + return {s0, s0}; + } + + int reverbIndex = 0; + FBReverb fbreverb; + FFReverb ffreverb; + + float dryGain = 1.0; + float wetGain = 0.25; + float mixChannelGain[mixChannelCount]; + // static SDL_AudioStream * audio_stream; static SDL_AudioSpec audio_spec; - static OpusDecoder * opus_decoder; - static AudioBuffer * audio_buffers; static int audio_buffers_count; @@ -64,17 +220,16 @@ namespace audio { assert(audio_stream); SDL_ResumeAudioStreamDevice(audio_stream); - int err; - opus_decoder = opus_decoder_create(sample_rate, channels, &err); - if (err < 0) { - fprintf(stderr, "opus_decoder_create: %s\n", opus_strerror(err)); - assert(!"opus_decoder_create"); - } - audio_instances_count = 0; + fbreverb.reset(); + ffreverb.reset(); + + for (int i = 0; i < mixChannelCount; i++) { + mixChannelGain[i] = 1.0f; + } } - void decode(char const * const filename, AudioBuffer * audio_buffer) + void decode(OpusDecoder * opus_decoder, char const * const filename, AudioBuffer * audio_buffer) { uint32_t size; uint8_t const * buf = (uint8_t const *)file::open(filename, &size); @@ -127,14 +282,63 @@ namespace audio { assert(audio_buffer->sample_count / 2); } + struct LoadState { + OpusDecoder * opus_decoder; + renpy::language::audio const * audio; + int start; + int count; + }; + + static int loadAudio(void * data) + { + LoadState * loadState = (LoadState *)data; + for (int i = loadState->start; i < loadState->start + loadState->count; i++) { + audio_buffers[i].audio = &loadState->audio[i]; + decode(loadState->opus_decoder, loadState->audio[i].path, &audio_buffers[i]); + } + return 0; + } + void load(renpy::language::audio const * const audio, int count) { audio_buffers = NewM(count); audio_buffers_count = count; - for (int i = 0; i < count; i++) { - audio_buffers[i].audio = &audio[i]; - decode(audio[i].path, &audio_buffers[i]); + + int core_count = SDL_GetNumLogicalCPUCores(); + assert(core_count >= 1); + SDL_Thread ** threads = NewM(core_count); + LoadState * loadStates = NewM(core_count); + + int per_core_count = count / core_count; + int remainder = count % core_count; + int start = 0; + for (int i = 0; i < core_count; i++) { + int this_core_count = per_core_count; + if (remainder) { + this_core_count += 1; + remainder -= 1; + } + + int err; + loadStates[i].opus_decoder = opus_decoder_create(sample_rate, channels, &err); + if (err < 0) { + fprintf(stderr, "opus_decoder_create: %s\n", opus_strerror(err)); + assert(!"opus_decoder_create"); + } + + loadStates[i].audio = audio; + loadStates[i].start = start; + loadStates[i].count = this_core_count; + start += this_core_count; + threads[i] = SDL_CreateThread(loadAudio, "loadAudio", &loadStates[i]); } + + for (int i = 0; i < core_count; i++) { + SDL_WaitThread(threads[i], nullptr); + opus_decoder_destroy(loadStates[i].opus_decoder); + } + free(threads); + free(loadStates); } void play(int audio_index) @@ -205,6 +409,7 @@ namespace audio { return 1.0f; if (v < -1.0f) return -1.0f; + return v; } static inline void remove_instance(int instance_index) @@ -223,6 +428,9 @@ namespace audio { uint32_t const sample_count = instance.audio_buffer->sample_count; uint32_t const loop_end = instance.audio_buffer->audio->loop_end * (double)sample_rate; uint32_t mix_index = 0; + + float bufferGain = instance.audio_buffer->audio->gain; + for (int i = 0; i < half_period_samples; i++) { if (loop_end != 0.0) { if (instance.sample_index >= loop_end) { @@ -241,23 +449,19 @@ namespace audio { assert(instance.sample_index < sample_count); assert(instance.tail_index <= sample_count); - double fadeout = 1.0; - double attenuation = instance.audio_buffer->audio->attenuation; - assert(attenuation != 0.0); - //bool is_music = (instance.audio_buffer->audio->audio_flags & renpy::language::audio::music) != 0; - //if (is_music) - //fprintf(stderr, "attenuation %f\n", attenuation); + float fadeout = 1.0; if (instance.fadeout_end != 0) { - fadeout = 1.0 - ((double)instance.fadeout_index / (double)instance.fadeout_end); + fadeout = 1.0 - ((float)instance.fadeout_index / (float)instance.fadeout_end); } for (int ch = 0; ch < channels; ch++) { - int32_t value = buf[instance.sample_index * channels + ch]; + float value = buf[instance.sample_index * channels + ch]; if (instance.tail_index != sample_count) { value += buf[instance.tail_index * channels + ch]; } - constexpr double scale = 1.0f / 32768.0f; - mix_buffer[mix_index * channels + ch] += (double)value * fadeout * attenuation * scale; + constexpr float scale = 1.0f / 32768.0f; + float output = value * fadeout * bufferGain * scale; + mix_buffer[mix_index * channels + ch] += output; } instance.sample_index += 1; instance.fadeout_index += 1; @@ -308,18 +512,32 @@ namespace audio { } } + static inline int getMixChannel(AudioInstance & instance) + { + if (instance.audio_buffer->audio->audio_flags & renpy::language::audio::music) { + return mix_channel::music; + } else if (instance.audio_buffer->audio->audio_flags & renpy::language::audio::poem) { + return mix_channel::poem; + } else { + return mix_channel::voice; + } + } + void update() { float mix_buffer[half_period_samples * channels]; + float channel_buffer[mixChannelCount][half_period_samples * channels]; + static_assert((sizeof (channel_buffer)) == half_period_samples * channels * mixChannelCount * (sizeof (float))); if (SDL_GetAudioStreamQueued(audio_stream) >= (int)(sizeof (mix_buffer))) return; - memset(mix_buffer, 0, (sizeof (mix_buffer))); + memset(&mix_buffer[0], 0, (sizeof (mix_buffer))); + memset(&channel_buffer[0][0], 0, (sizeof (channel_buffer))); poem_playing = nullptr; for (int i = 0; i < audio_instances_count; i++) { - update_instance(mix_buffer, audio_instances[i]); + update_instance(channel_buffer[getMixChannel(audio_instances[i])], audio_instances[i]); update_poem(audio_instances[i]); } @@ -336,6 +554,35 @@ namespace audio { } } + // audio configuration "B" + // mono reverberation + for (int i = 0; i < half_period_samples; i++) { + float value = channel_buffer[mix_channel::voice][i * channels + 0]; + + lr wet; + if (reverbIndex == 0) { + wet = fbreverb.feed(value); + } + else { + wet = ffreverb.feed(value); + } + float left = value * dryGain + wet.l * wetGain; + float right = value * dryGain + wet.r * wetGain; + channel_buffer[mix_channel::voice][i * channels + 0] = left; + channel_buffer[mix_channel::voice][i * channels + 1] = right; + } + + for (int i = 0; i < half_period_samples; i++) { + for (int ch = 0; ch < channels; ch++) { + float value = 0; + for (int mixChannel = 0; mixChannel < mixChannelCount; mixChannel++) { + float gain = mixChannelGain[mixChannel]; + value += channel_buffer[mixChannel][i * channels + ch] * gain; + } + mix_buffer[i * channels + ch] = clampf(value); + } + } + SDL_PutAudioStreamData(audio_stream, (void *)mix_buffer, (int)(sizeof (mix_buffer))); } } diff --git a/src/font/bitmap/vulkan.cpp b/src/font/bitmap/vulkan.cpp new file mode 100644 index 0000000..5345f8d --- /dev/null +++ b/src/font/bitmap/vulkan.cpp @@ -0,0 +1,269 @@ +#include + +#ifdef __APPLE__ +#include "vulkan/vulkan.h" +#else +#include "volk/volk.h" +#endif + +#include "vulkan/vk_enum_string_helper.h" + +#include "vulkan_helper.h" +#include "check.h" +#include "file.h" + +#include "font/bitmap/vulkan.h" + +namespace font::bitmap { + + ////////////////////////////////////////////////////////////////////// + // pipeline + ////////////////////////////////////////////////////////////////////// + + void vulkan::createPipeline() + { + uint32_t pushConstantRangeCount = 0; + VkPushConstantRange const * pushConstantRanges = nullptr; + VkShaderModule shaderModule = loadShader(vk->device, "shader/font/bitmap.spv"); + uint32_t perInstanceStride = (sizeof (BitmapInstance)); + constexpr uint32_t instanceAttributeDescriptionCount = 2; + VkVertexInputAttributeDescription instanceAttributeDescriptions[instanceAttributeDescriptionCount] { + { // position + .location = 2, + .binding = 1, + .format = VK_FORMAT_R16G16_UINT, + .offset = 0, + }, + { // glyph + .location = 3, + .binding = 1, + .format = VK_FORMAT_R16G16_UINT, + .offset = 4, + }, + }; + + createQuadPipeline(vk->device, + vk->colorFormat, + vk->depthFormat, + descriptorSetLayoutCount, + descriptorSetLayouts, + pushConstantRangeCount, + pushConstantRanges, + shaderModule, + perInstanceStride, + instanceAttributeDescriptionCount, + instanceAttributeDescriptions, + &pipelineLayout, + &pipeline); + } + + ////////////////////////////////////////////////////////////////////// + // descriptor sets + ////////////////////////////////////////////////////////////////////// + + void vulkan::createDescriptorSets() + { + // + // pool + // + constexpr int descriptorPoolSizesCount = 1; + VkDescriptorPoolSize descriptorPoolSizes[descriptorPoolSizesCount]{ + { + .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + .descriptorCount = 1, + }, + }; + VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .maxSets = 1, + .poolSizeCount = descriptorPoolSizesCount, + .pPoolSizes = descriptorPoolSizes + }; + VK_CHECK(vkCreateDescriptorPool(vk->device, &descriptorPoolCreateInfo, nullptr, &descriptorPool)); + + // + // (set 0, constant) + // + { + constexpr int bindingCount = 1; + VkDescriptorSetLayoutBinding descriptorSetLayoutBindings[bindingCount]{ + { // font image + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT + }, + }; + + VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = bindingCount, + .pBindings = descriptorSetLayoutBindings + }; + VK_CHECK(vkCreateDescriptorSetLayout(vk->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(vk->device, &descriptorSetAllocateInfo, &descriptorSet0)); + } + } + + ////////////////////////////////////////////////////////////////////// + // descriptor set writes + ////////////////////////////////////////////////////////////////////// + + void vulkan::writeDescriptorSets(VkImageView imageView) + { + constexpr uint32_t writeCount = 1; + VkWriteDescriptorSet writeDescriptorSets[writeCount]; + uint32_t writeIndex = 0; + + // set0 bindings + VkDescriptorImageInfo terrainDescriptorImageInfo = { + .imageView = imageView, + .imageLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL + }; + writeDescriptorSets[writeIndex++] = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptorSet0, + .dstBinding = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + .pImageInfo = &terrainDescriptorImageInfo + }; + + assert(writeIndex == writeCount); + vkUpdateDescriptorSets(vk->device, writeIndex, writeDescriptorSets, 0, nullptr); + } + + ////////////////////////////////////////////////////////////////////// + // load font + ////////////////////////////////////////////////////////////////////// + + LoadedFont vulkan::loadFont() + { + uint32_t font_data_size; + void const * font_data = file::open("data/font/bitmap/terminus_128x64_6x12.data", &font_data_size); + assert(font_data != nullptr); + + void const * texture_data = font_data; + uint32_t texture_size = font_data_size; + uint32_t texture_width = 128; + uint32_t texture_height = 64; + + // transfer texture + + VkCommandBuffer commandBuffer{}; + VkCommandBufferAllocateInfo commandBufferAllocateInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = vk->commandPool, + .commandBufferCount = 1 + }; + VK_CHECK(vkAllocateCommandBuffers(vk->device, &commandBufferAllocateInfo, &commandBuffer)); + + VkFenceCreateInfo fenceCreateInfo{ + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO + }; + VkFence fence{}; + VK_CHECK(vkCreateFence(vk->device, &fenceCreateInfo, nullptr, &fence)); + + void const * imageData = texture_data; + uint32_t imageDataSize = texture_size; + VkFormat format = VK_FORMAT_R8_UNORM; + uint32_t width = texture_width; + uint32_t height = texture_height; + uint32_t levelCount = 1; + uint32_t levelOffset = 0; + VkImage outImage; + VkDeviceMemory outMemory; + VkImageView outImageView; + + createImage(vk->device, + vk->physicalDeviceProperties.limits.nonCoherentAtomSize, + vk->physicalDeviceMemoryProperties, + format, + width, + height, + levelCount, + &outImage, + &outMemory, + &outImageView); + + textureTransfer(vk->device, + vk->queue, + commandBuffer, + fence, + vk->physicalDeviceProperties.limits.nonCoherentAtomSize, + vk->physicalDeviceMemoryProperties, + imageDataSize, + imageData, + outImage, + width, + height, + levelCount, + &levelOffset); + + vkDestroyFence(vk->device, fence, nullptr); + vkFreeCommandBuffers(vk->device, + vk->commandPool, + 1, + &commandBuffer); + + // return + return { + .image = outImage, + .memory = outMemory, + .imageView = outImageView, + }; + } + + void vulkan::update(uint32_t frameIndex, uint32_t glyphCount) const + { + constexpr int mappedMemoryRangesCount = 1; + VkMappedMemoryRange mappedMemoryRanges[mappedMemoryRangesCount]{ + { + .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, + .memory = instanceBuffer.memory, + .offset = instanceBuffer.offset[frameIndex], + .size = (sizeof (BitmapInstance)) * glyphCount, + } + }; + alignMappedMemoryRanges(vk->physicalDeviceProperties.limits.nonCoherentAtomSize, + instanceBuffer.memorySize, + mappedMemoryRangesCount, + mappedMemoryRanges); + vkFlushMappedMemoryRanges(vk->device, mappedMemoryRangesCount, mappedMemoryRanges); + } + + void vulkan::draw(VkCommandBuffer commandBuffer, + uint32_t frameIndex, + uint32_t glyphCount) const + { + update(frameIndex, glyphCount); + // + uint32_t outputIndex = glyphCount; + + 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, vk->quadVertexIndex.buffer, vk->quadVertexIndex.indexOffset, VK_INDEX_TYPE_UINT16); + + VkDeviceSize vertexOffsets[2]{ 0, instanceBuffer.offset[frameIndex] }; + VkBuffer vertexBuffers[2]{ vk->quadVertexIndex.buffer, instanceBuffer.buffer }; + vkCmdBindVertexBuffers(commandBuffer, 0, 2, vertexBuffers, vertexOffsets); + + vkCmdDrawIndexed(commandBuffer, 4, outputIndex, 0, 0, 0); + } +} diff --git a/src/font/outline.cpp b/src/font/outline.cpp index cd1c1cc..86028d4 100644 --- a/src/font/outline.cpp +++ b/src/font/outline.cpp @@ -805,7 +805,7 @@ namespace font::outline { { .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, .memory = instanceMemory, - .offset = 0, + .offset = instanceBufferOffset[frameIndex], .size = (sizeof (GlyphInstance)) * outputIndex, } }; diff --git a/src/main.cpp b/src/main.cpp index a826889..d208d5b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,14 +20,20 @@ #include "shader_data.h" #include "minmax.h" #include "view.h" +#include "mouse.h" #include "font/outline.h" +#include "font/bitmap.h" +#include "font/bitmap/vulkan.h" #include "renpy/vulkan.h" #include "renpy/composite/vulkan.h" #include "renpy/interpreter.h" #include "renpy/interact.h" #include "renpy/script.h" +#include "ui/vulkan.h" +#include "ui.h" + #include "audio.h" VkInstance instance{ VK_NULL_HANDLE }; @@ -369,6 +375,8 @@ void offscreenRender(VkCommandBuffer commandBuffer, int frameIndex, renpy::vulkan const & renpy_state, renpy::interpreter const & interpreter_state, font::outline::font const & font_state, + font::bitmap::vulkan const & bitmap_font_state, + ui::vulkan const & ui_state, VkSurfaceCapabilitiesKHR const & surfaceCapabilities) { // barrier @@ -474,6 +482,11 @@ void offscreenRender(VkCommandBuffer commandBuffer, int frameIndex, font_state.draw(commandBuffer, frameIndex, interpreter_state); } + MappedInstanceData bitmapData{ bitmap_font_state.bitmapInstance[frameIndex], + font::bitmap::vulkan::maximumGlyphCount, 0 }; + ui_state.draw(commandBuffer, frameIndex, bitmapData); + bitmap_font_state.draw(commandBuffer, frameIndex, bitmapData.index); + vkCmdEndRendering(commandBuffer); // barrier @@ -505,13 +518,6 @@ void offscreenRender(VkCommandBuffer commandBuffer, int frameIndex, } } -static inline double clamp01(double a) -{ - if (a < 0.0) return 0.0; - if (a > 1.0) return 1.0; - return a; -} - void handlePause(renpy::interpreter & interpreter_state, int64_t const start_time, double & pause_start, @@ -929,6 +935,15 @@ int main() minecraft_state.init(); */ + VulkanState vulkan_state(instance, + device, + queue, + commandPool, + physicalDeviceProperties, + physicalDeviceMemoryProperties, + surfaceFormat.format, + depthFormat); + ////////////////////////////////////////////////////////////////////// // initialize font ////////////////////////////////////////////////////////////////////// @@ -945,6 +960,16 @@ int main() textureSamplers[2]); font_state.init(); + // bitmap font + + font::bitmap::vulkan bitmap_font_state(&vulkan_state); + + ////////////////////////////////////////////////////////////////////// + // ui + ////////////////////////////////////////////////////////////////////// + + ui::vulkan ui_state(&vulkan_state); + ////////////////////////////////////////////////////////////////////// // initialize renpy ////////////////////////////////////////////////////////////////////// @@ -966,7 +991,14 @@ int main() ////////////////////////////////////////////////////////////////////// audio::init(); + int64_t audio_start; + SDL_GetCurrentTime(&audio_start); audio::load(renpy::script::audio, renpy::script::audio_length); + int64_t audio_end; + SDL_GetCurrentTime(&audio_end); + double audio_time = (double)(audio_end - audio_start) * (0.000000001); + // 2.17 + printf("audio_time %f\n", audio_time); ////////////////////////////////////////////////////////////////////// // interpreter @@ -975,7 +1007,7 @@ int main() renpy::interpreter interpreter_state; //interpreter_state.reset(88); //interpreter_state.reset(427); - interpreter_state.reset(0); + interpreter_state.reset(347); //while (interpreter_state.pc < 543) { /* while (interpreter_state.pc < 26) { @@ -1054,7 +1086,8 @@ int main() bool useGamepad = false; uint32_t whichGamepad = 0; - SDL_Thread * audio_thread = SDL_CreateThread(mainAudio, "audio", &quit); + SDL_Thread * audio_thread = nullptr; + audio_thread = SDL_CreateThread(mainAudio, "audio", &quit); while (quit == false) { ////////////////////////////////////////////////////////////////////// @@ -1125,7 +1158,10 @@ int main() float mx; float my; uint32_t mouseFlags = SDL_GetMouseState(&mx, &my); + static bool mLastLeft = false; bool mLeft = (mouseFlags & SDL_BUTTON_LMASK) != 0; + bool mEdge = (mLeft && (mLeft != mLastLeft)); + mLastLeft = mLeft; bool gUp = false; bool gDown = false; bool gAccept = false; @@ -1140,6 +1176,13 @@ int main() } } + float mxf; + float myf; + mouse::normalize(surfaceCapabilities.currentExtent.width, + surfaceCapabilities.currentExtent.height, + mx, my, &mxf, &myf); + ui::update(mxf, myf, mLeft, mEdge); + renpy::update(interpreter_state, mx, my, mLeft, gUp, gDown, gAccept, useGamepad, surfaceCapabilities.currentExtent.width, @@ -1329,10 +1372,10 @@ int main() ////////////////////////////////////////////////////////////////////// if (interpreter_state.pause.dissolve) { - offscreenRender(commandBuffer, frameIndex, mx, my, 2, true, renpy_state, interpreter_state, font_state, surfaceCapabilities); + offscreenRender(commandBuffer, frameIndex, mx, my, 2, true, renpy_state, interpreter_state, font_state, bitmap_font_state, ui_state, surfaceCapabilities); } else { - offscreenRender(commandBuffer, frameIndex, mx, my, 0, true, renpy_state, interpreter_state, font_state, surfaceCapabilities); - offscreenRender(commandBuffer, frameIndex, mx, my, 1, false, renpy_state, interpreter_state, font_state, surfaceCapabilities); + offscreenRender(commandBuffer, frameIndex, mx, my, 0, true, renpy_state, interpreter_state, font_state, bitmap_font_state, ui_state, surfaceCapabilities); + offscreenRender(commandBuffer, frameIndex, mx, my, 1, false, renpy_state, interpreter_state, font_state, bitmap_font_state, ui_state, surfaceCapabilities); } ////////////////////////////////////////////////////////////////////// @@ -1541,7 +1584,8 @@ int main() } } - SDL_WaitThread(audio_thread, nullptr); + if (audio_thread != nullptr) + SDL_WaitThread(audio_thread, nullptr); VK_CHECK(vkDeviceWaitIdle(device)); //collada_state.vulkan.destroy_all(collada_scene_descriptor); diff --git a/src/renpy/script.cpp b/src/renpy/script.cpp index e918e89..753ba2d 100644 --- a/src/renpy/script.cpp +++ b/src/renpy/script.cpp @@ -285,241 +285,241 @@ const language::character characters[] = { const int characters_length = (sizeof (characters)) / (sizeof (characters[0])); const language::audio audio[] = { - { .path = "audio/music/MistAmbience.opus.bin", .loop_end = 22.0, .audio_flags = audio::music, .attenuation = 1.0 }, // 0 music/MistAmbience.ogg - { .path = "audio/sfx/Chime.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 1 sfx/Chime.ogg - { .path = "audio/nara/n1.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 2 nara/n1.ogg - { .path = "audio/nara/n2.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 3 nara/n2.ogg - { .path = "audio/nara/n3.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 4 nara/n3.ogg - { .path = "audio/music/TinyForestMinstrels.opus.bin", .loop_end = 44.0, .audio_flags = audio::music, .attenuation = 0.45 }, // 5 music/TinyForestMinstrels.ogg - { .path = "audio/nara/n4.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 6 nara/n4.ogg - { .path = "audio/alice/a1.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 7 alice/a1.ogg - { .path = "audio/eily/e1.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 8 eily/e1.ogg - { .path = "audio/alice/a2.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 9 alice/a2.ogg - { .path = "audio/eily/e2.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 10 eily/e2.ogg - { .path = "audio/alice/a3.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 11 alice/a3.ogg - { .path = "audio/eily/e3.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 12 eily/e3.ogg - { .path = "audio/alice/a4.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 13 alice/a4.ogg - { .path = "audio/alice/a5.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 14 alice/a5.ogg - { .path = "audio/eily/e4.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 15 eily/e4.ogg - { .path = "audio/eily/e5.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 16 eily/e5.ogg - { .path = "audio/alice/a6.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 17 alice/a6.ogg - { .path = "audio/eily/e6.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 18 eily/e6.ogg - { .path = "audio/alice/a7.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 19 alice/a7.ogg - { .path = "audio/eily/e7.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 20 eily/e7.ogg - { .path = "audio/nara/n5.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 21 nara/n5.ogg - { .path = "audio/music/PhrygianButterflies.opus.bin", .loop_end = 40.2, .audio_flags = audio::music, .attenuation = 0.5 }, // 22 music/PhrygianButterflies.ogg - { .path = "audio/eily/e8.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 23 eily/e8.ogg - { .path = "audio/alice/a8.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 24 alice/a8.ogg - { .path = "audio/eily/e9.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 25 eily/e9.ogg - { .path = "audio/eily/e10.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 26 eily/e10.ogg - { .path = "audio/alice/a9.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 27 alice/a9.ogg - { .path = "audio/eily/e11.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 28 eily/e11.ogg - { .path = "audio/eily/e12.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 29 eily/e12.ogg - { .path = "audio/alice/a10.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 30 alice/a10.ogg - { .path = "audio/eily/e13.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 31 eily/e13.ogg - { .path = "audio/eily/e14.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 32 eily/e14.ogg - { .path = "audio/alice/a11.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 33 alice/a11.ogg - { .path = "audio/eily/e15.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 34 eily/e15.ogg - { .path = "audio/poem/EleanorTheHero.opus.bin", .loop_end = 0.0, .audio_flags = audio::poem, .attenuation = 1.0 }, // 35 poem/EleanorTheHero.ogg - { .path = "audio/eily/e16.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 36 eily/e16.ogg - { .path = "audio/alice/a12.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 37 alice/a12.ogg - { .path = "audio/leona/c1.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 38 leona/c1.ogg - { .path = "audio/music/ScaredMice.opus.bin", .loop_end = 8.0, .audio_flags = audio::music, .attenuation = 1.0 }, // 39 music/ScaredMice.ogg - { .path = "audio/mousegirls/mg1.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 40 mousegirls/mg1.ogg - { .path = "audio/leona/c2.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 41 leona/c2.ogg - { .path = "audio/leona/c3.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 42 leona/c3.ogg - { .path = "audio/alice/a14.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 43 alice/a14.ogg - { .path = "audio/eily/e18.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 44 eily/e18.ogg - { .path = "audio/eily/e19.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 45 eily/e19.ogg - { .path = "audio/leona/c4.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 46 leona/c4.ogg - { .path = "audio/leona/c5.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 47 leona/c5.ogg - { .path = "audio/leona/c6.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 48 leona/c6.ogg - { .path = "audio/eily/e20.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 49 eily/e20.ogg - { .path = "audio/alice/a15.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 50 alice/a15.ogg - { .path = "audio/eily/e21.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 51 eily/e21.ogg - { .path = "audio/leona/c7.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 52 leona/c7.ogg - { .path = "audio/leona/c8.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 53 leona/c8.ogg - { .path = "audio/eily/e22.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 54 eily/e22.ogg - { .path = "audio/leona/c9.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 55 leona/c9.ogg - { .path = "audio/eily/e23.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 56 eily/e23.ogg - { .path = "audio/leona/c10.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 57 leona/c10.ogg - { .path = "audio/eily/e24.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 58 eily/e24.ogg - { .path = "audio/leona/c11.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 59 leona/c11.ogg - { .path = "audio/eily/e25.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 60 eily/e25.ogg - { .path = "audio/leona/c12.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 61 leona/c12.ogg - { .path = "audio/leona/c13.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 62 leona/c13.ogg - { .path = "audio/eily/e26.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 63 eily/e26.ogg - { .path = "audio/eily/e27.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 64 eily/e27.ogg - { .path = "audio/leona/c14.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 65 leona/c14.ogg - { .path = "audio/alice/a16.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 66 alice/a16.ogg - { .path = "audio/leona/c15.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 67 leona/c15.ogg - { .path = "audio/leona/c16.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 68 leona/c16.ogg - { .path = "audio/leona/c17.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 69 leona/c17.ogg - { .path = "audio/alice/a17.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 70 alice/a17.ogg - { .path = "audio/eily/e28.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 71 eily/e28.ogg - { .path = "audio/alice/a18.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 72 alice/a18.ogg - { .path = "audio/eily/e29.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 73 eily/e29.ogg - { .path = "audio/nara/n6.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 74 nara/n6.ogg - { .path = "audio/eily/e30.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 75 eily/e30.ogg - { .path = "audio/alice/a19.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 76 alice/a19.ogg - { .path = "audio/eily/e31.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 77 eily/e31.ogg - { .path = "audio/alice/a20.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 78 alice/a20.ogg - { .path = "audio/leona/c18.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 79 leona/c18.ogg - { .path = "audio/eily/e32.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 80 eily/e32.ogg - { .path = "audio/alice/a21.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 81 alice/a21.ogg - { .path = "audio/eily/e33.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 82 eily/e33.ogg - { .path = "audio/leona/c19.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 83 leona/c19.ogg - { .path = "audio/leona/c20.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 84 leona/c20.ogg - { .path = "audio/leona/c21.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 85 leona/c21.ogg - { .path = "audio/alice/a22.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 86 alice/a22.ogg - { .path = "audio/leona/c22.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 87 leona/c22.ogg - { .path = "audio/leona/c23.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 88 leona/c23.ogg - { .path = "audio/alice/a23.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 89 alice/a23.ogg - { .path = "audio/eily/e34.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 90 eily/e34.ogg - { .path = "audio/alice/a24.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 91 alice/a24.ogg - { .path = "audio/eily/e35.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 92 eily/e35.ogg - { .path = "audio/leona/c24.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 93 leona/c24.ogg - { .path = "audio/leona/c25.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 94 leona/c25.ogg - { .path = "audio/leona/c26.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 95 leona/c26.ogg - { .path = "audio/eily/e36.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 96 eily/e36.ogg - { .path = "audio/eily/e37.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 97 eily/e37.ogg - { .path = "audio/eily/e38.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 98 eily/e38.ogg - { .path = "audio/eily/e39.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 99 eily/e39.ogg - { .path = "audio/eily/e40.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 100 eily/e40.ogg - { .path = "audio/eily/e41.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 101 eily/e41.ogg - { .path = "audio/eily/e42.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 102 eily/e42.ogg - { .path = "audio/alice/a25.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 103 alice/a25.ogg - { .path = "audio/eily/e43.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 104 eily/e43.ogg - { .path = "audio/alice/a26.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 105 alice/a26.ogg - { .path = "audio/eily/e44.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 106 eily/e44.ogg - { .path = "audio/eily/e45.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 107 eily/e45.ogg - { .path = "audio/alice/a27.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 108 alice/a27.ogg - { .path = "audio/eily/e46.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 109 eily/e46.ogg - { .path = "audio/alice/a28.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 110 alice/a28.ogg - { .path = "audio/leona/c27.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 111 leona/c27.ogg - { .path = "audio/eily/e47.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 112 eily/e47.ogg - { .path = "audio/alice/a29.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 113 alice/a29.ogg - { .path = "audio/leona/c28.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 114 leona/c28.ogg - { .path = "audio/leona/c29.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 115 leona/c29.ogg - { .path = "audio/eily/e48.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 116 eily/e48.ogg - { .path = "audio/leona/c30.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 117 leona/c30.ogg - { .path = "audio/alice/a30.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 118 alice/a30.ogg - { .path = "audio/eily/e49.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 119 eily/e49.ogg - { .path = "audio/eily/e50.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 120 eily/e50.ogg - { .path = "audio/eily/e51.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 121 eily/e51.ogg - { .path = "audio/eily/e52.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 122 eily/e52.ogg - { .path = "audio/eily/e53.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 123 eily/e53.ogg - { .path = "audio/eily/e54.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 124 eily/e54.ogg - { .path = "audio/alice/a31.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 125 alice/a31.ogg - { .path = "audio/leona/c31.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 126 leona/c31.ogg - { .path = "audio/leona/c32.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 127 leona/c32.ogg - { .path = "audio/alice/a32.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 128 alice/a32.ogg - { .path = "audio/leona/c33.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 129 leona/c33.ogg - { .path = "audio/mousegirls/mg2.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 130 mousegirls/mg2.ogg - { .path = "audio/leona/c34.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 131 leona/c34.ogg - { .path = "audio/leona/c35.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 132 leona/c35.ogg - { .path = "audio/alice/a33.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 133 alice/a33.ogg - { .path = "audio/eily/e55.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 134 eily/e55.ogg - { .path = "audio/leona/c36.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 135 leona/c36.ogg - { .path = "audio/alice/a34.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 136 alice/a34.ogg - { .path = "audio/eily/e56.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 137 eily/e56.ogg - { .path = "audio/nara/n7.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 138 nara/n7.ogg - { .path = "audio/music/WheatFields.opus.bin", .loop_end = 34.0, .audio_flags = audio::music, .attenuation = 1.0 }, // 139 music/WheatFields.ogg - { .path = "audio/leona/c37.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 140 leona/c37.ogg - { .path = "audio/alice/a35.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 141 alice/a35.ogg - { .path = "audio/eily/e57.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 142 eily/e57.ogg - { .path = "audio/leona/c38.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 143 leona/c38.ogg - { .path = "audio/leona/c39.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 144 leona/c39.ogg - { .path = "audio/eily/e58.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 145 eily/e58.ogg - { .path = "audio/alice/a36.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 146 alice/a36.ogg - { .path = "audio/eily/e59.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 147 eily/e59.ogg - { .path = "audio/eily/e60.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 148 eily/e60.ogg - { .path = "audio/leona/c40.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 149 leona/c40.ogg - { .path = "audio/eily/e61.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 150 eily/e61.ogg - { .path = "audio/leona/c41.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 151 leona/c41.ogg - { .path = "audio/leona/c42.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 152 leona/c42.ogg - { .path = "audio/alice/a37.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 153 alice/a37.ogg - { .path = "audio/eily/e62.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 154 eily/e62.ogg - { .path = "audio/leona/c43.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 155 leona/c43.ogg - { .path = "audio/eily/e63.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 156 eily/e63.ogg - { .path = "audio/leona/c44.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 157 leona/c44.ogg - { .path = "audio/leona/c45.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 158 leona/c45.ogg - { .path = "audio/leona/c46.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 159 leona/c46.ogg - { .path = "audio/eily/e64.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 160 eily/e64.ogg - { .path = "audio/leona/c47.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 161 leona/c47.ogg - { .path = "audio/eily/e65.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 162 eily/e65.ogg - { .path = "audio/leona/c48.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 163 leona/c48.ogg - { .path = "audio/leona/c49.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 164 leona/c49.ogg - { .path = "audio/leona/c50.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 165 leona/c50.ogg - { .path = "audio/mousegirls/mg3.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 166 mousegirls/mg3.ogg - { .path = "audio/leona/c51.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 167 leona/c51.ogg - { .path = "audio/leona/c52.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 168 leona/c52.ogg - { .path = "audio/leona/c53.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 169 leona/c53.ogg - { .path = "audio/poem/KiriStella.opus.bin", .loop_end = 0.0, .audio_flags = audio::poem, .attenuation = 1.0 }, // 170 poem/KiriStella.ogg - { .path = "audio/leona/c54.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 171 leona/c54.ogg - { .path = "audio/leona/c55.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 172 leona/c55.ogg - { .path = "audio/alice/a39.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 173 alice/a39.ogg - { .path = "audio/eily/e67.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 174 eily/e67.ogg - { .path = "audio/eily/e68.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 175 eily/e68.ogg - { .path = "audio/leona/c56.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 176 leona/c56.ogg - { .path = "audio/nara/n8.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 177 nara/n8.ogg - { .path = "audio/nara/n9.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 178 nara/n9.ogg - { .path = "audio/nara/n10.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 179 nara/n10.ogg - { .path = "audio/hera/h1.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 180 hera/h1.ogg - { .path = "audio/music/Preludium.opus.bin", .loop_end = 58.2, .audio_flags = audio::music, .attenuation = 0.45 }, // 181 music/Preludium.ogg - { .path = "audio/hera/h2.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 182 hera/h2.ogg - { .path = "audio/leona/c57.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 183 leona/c57.ogg - { .path = "audio/hera/h3.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 184 hera/h3.ogg - { .path = "audio/eily/e69.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 185 eily/e69.ogg - { .path = "audio/hera/h4.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 186 hera/h4.ogg - { .path = "audio/sfx/Glass.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 187 sfx/Glass.ogg - { .path = "audio/alice/a40.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 188 alice/a40.ogg - { .path = "audio/alice/a41.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 189 alice/a41.ogg - { .path = "audio/leona/c58.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 190 leona/c58.ogg - { .path = "audio/bird/b1.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 191 bird/b1.ogg - { .path = "audio/bird/b2.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 192 bird/b2.ogg - { .path = "audio/bird/b3.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 193 bird/b3.ogg - { .path = "audio/leona/c59.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 194 leona/c59.ogg - { .path = "audio/eily/e70.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 195 eily/e70.ogg - { .path = "audio/bird/b4.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 196 bird/b4.ogg - { .path = "audio/bird/b5.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 197 bird/b5.ogg - { .path = "audio/leona/c60.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 198 leona/c60.ogg - { .path = "audio/leona/c61.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 199 leona/c61.ogg - { .path = "audio/bird/b6.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 200 bird/b6.ogg - { .path = "audio/bird/b7.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 201 bird/b7.ogg - { .path = "audio/leona/c0.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 202 leona/c0.ogg - { .path = "audio/bird/b8.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 203 bird/b8.ogg - { .path = "audio/bird/b9.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 204 bird/b9.ogg - { .path = "audio/bird/b10.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 205 bird/b10.ogg - { .path = "audio/leona/c62.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 206 leona/c62.ogg - { .path = "audio/bird/b11.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 207 bird/b11.ogg - { .path = "audio/bird/b12.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 208 bird/b12.ogg - { .path = "audio/bird/b13.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 209 bird/b13.ogg - { .path = "audio/leona/c63.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 210 leona/c63.ogg - { .path = "audio/bird/b14.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 211 bird/b14.ogg - { .path = "audio/leona/c64.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 212 leona/c64.ogg - { .path = "audio/bird/b15.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 213 bird/b15.ogg - { .path = "audio/leona/c65.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 214 leona/c65.ogg - { .path = "audio/leona/c66.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 215 leona/c66.ogg - { .path = "audio/bird/b16.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 216 bird/b16.ogg - { .path = "audio/bird/b17.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 217 bird/b17.ogg - { .path = "audio/alice/a42.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 218 alice/a42.ogg - { .path = "audio/eily/e71.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 219 eily/e71.ogg - { .path = "audio/eily/e72.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 220 eily/e72.ogg - { .path = "audio/eily/e73.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 221 eily/e73.ogg - { .path = "audio/eily/e74.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 222 eily/e74.ogg - { .path = "audio/eily/e75.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 223 eily/e75.ogg - { .path = "audio/eily/e76.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 224 eily/e76.ogg - { .path = "audio/bird/b18.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 225 bird/b18.ogg - { .path = "audio/leona/c67.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 226 leona/c67.ogg - { .path = "audio/alice/a43.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 227 alice/a43.ogg - { .path = "audio/eily/e77.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 228 eily/e77.ogg - { .path = "audio/bird/b19.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 229 bird/b19.ogg - { .path = "audio/bird/b20.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 230 bird/b20.ogg - { .path = "audio/poem/BirdSong.opus.bin", .loop_end = 0.0, .audio_flags = audio::poem, .attenuation = 1.0 }, // 231 poem/BirdSong.ogg - { .path = "audio/nara/n11.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 232 nara/n11.ogg - { .path = "audio/nara/n12.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 233 nara/n12.ogg - { .path = "audio/nara/n13.opus.bin", .loop_end = 0.0, .audio_flags = 0, .attenuation = 1.0 }, // 234 nara/n13.ogg + { .path = "audio/music/MistAmbience.opus.bin", .loop_end = 22.0, .audio_flags = audio::music, .gain = 1.0 }, // 0 music/MistAmbience.ogg + { .path = "audio/sfx/Chime.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 1 sfx/Chime.ogg + { .path = "audio/nara/n1.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 2 nara/n1.ogg + { .path = "audio/nara/n2.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 3 nara/n2.ogg + { .path = "audio/nara/n3.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 4 nara/n3.ogg + { .path = "audio/music/TinyForestMinstrels.opus.bin", .loop_end = 44.0, .audio_flags = audio::music, .gain = 0.45 }, // 5 music/TinyForestMinstrels.ogg + { .path = "audio/nara/n4.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 6 nara/n4.ogg + { .path = "audio/alice/a1.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 7 alice/a1.ogg + { .path = "audio/eily/e1.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 8 eily/e1.ogg + { .path = "audio/alice/a2.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 9 alice/a2.ogg + { .path = "audio/eily/e2.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 10 eily/e2.ogg + { .path = "audio/alice/a3.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 11 alice/a3.ogg + { .path = "audio/eily/e3.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 12 eily/e3.ogg + { .path = "audio/alice/a4.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 13 alice/a4.ogg + { .path = "audio/alice/a5.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 14 alice/a5.ogg + { .path = "audio/eily/e4.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 15 eily/e4.ogg + { .path = "audio/eily/e5.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 16 eily/e5.ogg + { .path = "audio/alice/a6.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 17 alice/a6.ogg + { .path = "audio/eily/e6.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 18 eily/e6.ogg + { .path = "audio/alice/a7.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 19 alice/a7.ogg + { .path = "audio/eily/e7.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 20 eily/e7.ogg + { .path = "audio/nara/n5.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 21 nara/n5.ogg + { .path = "audio/music/PhrygianButterflies.opus.bin", .loop_end = 40.2, .audio_flags = audio::music, .gain = 0.5 }, // 22 music/PhrygianButterflies.ogg + { .path = "audio/eily/e8.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 23 eily/e8.ogg + { .path = "audio/alice/a8.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 24 alice/a8.ogg + { .path = "audio/eily/e9.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 25 eily/e9.ogg + { .path = "audio/eily/e10.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 26 eily/e10.ogg + { .path = "audio/alice/a9.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 27 alice/a9.ogg + { .path = "audio/eily/e11.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 28 eily/e11.ogg + { .path = "audio/eily/e12.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 29 eily/e12.ogg + { .path = "audio/alice/a10.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 30 alice/a10.ogg + { .path = "audio/eily/e13.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 31 eily/e13.ogg + { .path = "audio/eily/e14.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 32 eily/e14.ogg + { .path = "audio/alice/a11.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 33 alice/a11.ogg + { .path = "audio/eily/e15.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 34 eily/e15.ogg + { .path = "audio/poem/EleanorTheHero.opus.bin", .loop_end = 0.0, .audio_flags = audio::poem, .gain = 1.0 }, // 35 poem/EleanorTheHero.ogg + { .path = "audio/eily/e16.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 36 eily/e16.ogg + { .path = "audio/alice/a12.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 37 alice/a12.ogg + { .path = "audio/leona/c1.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 38 leona/c1.ogg + { .path = "audio/music/ScaredMice.opus.bin", .loop_end = 8.0, .audio_flags = audio::music, .gain = 1.0 }, // 39 music/ScaredMice.ogg + { .path = "audio/mousegirls/mg1.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 40 mousegirls/mg1.ogg + { .path = "audio/leona/c2.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 41 leona/c2.ogg + { .path = "audio/leona/c3.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 42 leona/c3.ogg + { .path = "audio/alice/a14.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 43 alice/a14.ogg + { .path = "audio/eily/e18.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 44 eily/e18.ogg + { .path = "audio/eily/e19.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 45 eily/e19.ogg + { .path = "audio/leona/c4.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 46 leona/c4.ogg + { .path = "audio/leona/c5.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 47 leona/c5.ogg + { .path = "audio/leona/c6.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 48 leona/c6.ogg + { .path = "audio/eily/e20.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 49 eily/e20.ogg + { .path = "audio/alice/a15.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 50 alice/a15.ogg + { .path = "audio/eily/e21.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 51 eily/e21.ogg + { .path = "audio/leona/c7.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 52 leona/c7.ogg + { .path = "audio/leona/c8.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 53 leona/c8.ogg + { .path = "audio/eily/e22.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 54 eily/e22.ogg + { .path = "audio/leona/c9.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 55 leona/c9.ogg + { .path = "audio/eily/e23.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 56 eily/e23.ogg + { .path = "audio/leona/c10.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 57 leona/c10.ogg + { .path = "audio/eily/e24.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 58 eily/e24.ogg + { .path = "audio/leona/c11.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 59 leona/c11.ogg + { .path = "audio/eily/e25.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 60 eily/e25.ogg + { .path = "audio/leona/c12.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 61 leona/c12.ogg + { .path = "audio/leona/c13.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 62 leona/c13.ogg + { .path = "audio/eily/e26.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 63 eily/e26.ogg + { .path = "audio/eily/e27.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 64 eily/e27.ogg + { .path = "audio/leona/c14.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 65 leona/c14.ogg + { .path = "audio/alice/a16.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 66 alice/a16.ogg + { .path = "audio/leona/c15.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 67 leona/c15.ogg + { .path = "audio/leona/c16.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 68 leona/c16.ogg + { .path = "audio/leona/c17.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 69 leona/c17.ogg + { .path = "audio/alice/a17.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 70 alice/a17.ogg + { .path = "audio/eily/e28.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 71 eily/e28.ogg + { .path = "audio/alice/a18.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 72 alice/a18.ogg + { .path = "audio/eily/e29.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 73 eily/e29.ogg + { .path = "audio/nara/n6.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 74 nara/n6.ogg + { .path = "audio/eily/e30.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 75 eily/e30.ogg + { .path = "audio/alice/a19.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 76 alice/a19.ogg + { .path = "audio/eily/e31.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 77 eily/e31.ogg + { .path = "audio/alice/a20.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 78 alice/a20.ogg + { .path = "audio/leona/c18.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 79 leona/c18.ogg + { .path = "audio/eily/e32.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 80 eily/e32.ogg + { .path = "audio/alice/a21.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 81 alice/a21.ogg + { .path = "audio/eily/e33.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 82 eily/e33.ogg + { .path = "audio/leona/c19.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 83 leona/c19.ogg + { .path = "audio/leona/c20.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 84 leona/c20.ogg + { .path = "audio/leona/c21.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 85 leona/c21.ogg + { .path = "audio/alice/a22.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 86 alice/a22.ogg + { .path = "audio/leona/c22.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 87 leona/c22.ogg + { .path = "audio/leona/c23.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 88 leona/c23.ogg + { .path = "audio/alice/a23.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 89 alice/a23.ogg + { .path = "audio/eily/e34.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 90 eily/e34.ogg + { .path = "audio/alice/a24.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 91 alice/a24.ogg + { .path = "audio/eily/e35.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 92 eily/e35.ogg + { .path = "audio/leona/c24.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 93 leona/c24.ogg + { .path = "audio/leona/c25.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 94 leona/c25.ogg + { .path = "audio/leona/c26.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 95 leona/c26.ogg + { .path = "audio/eily/e36.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 96 eily/e36.ogg + { .path = "audio/eily/e37.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 97 eily/e37.ogg + { .path = "audio/eily/e38.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 98 eily/e38.ogg + { .path = "audio/eily/e39.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 99 eily/e39.ogg + { .path = "audio/eily/e40.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 100 eily/e40.ogg + { .path = "audio/eily/e41.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 101 eily/e41.ogg + { .path = "audio/eily/e42.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 102 eily/e42.ogg + { .path = "audio/alice/a25.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 103 alice/a25.ogg + { .path = "audio/eily/e43.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 104 eily/e43.ogg + { .path = "audio/alice/a26.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 105 alice/a26.ogg + { .path = "audio/eily/e44.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 106 eily/e44.ogg + { .path = "audio/eily/e45.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 107 eily/e45.ogg + { .path = "audio/alice/a27.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 108 alice/a27.ogg + { .path = "audio/eily/e46.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 109 eily/e46.ogg + { .path = "audio/alice/a28.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 110 alice/a28.ogg + { .path = "audio/leona/c27.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 111 leona/c27.ogg + { .path = "audio/eily/e47.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 112 eily/e47.ogg + { .path = "audio/alice/a29.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 113 alice/a29.ogg + { .path = "audio/leona/c28.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 114 leona/c28.ogg + { .path = "audio/leona/c29.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 115 leona/c29.ogg + { .path = "audio/eily/e48.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 116 eily/e48.ogg + { .path = "audio/leona/c30.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 117 leona/c30.ogg + { .path = "audio/alice/a30.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 118 alice/a30.ogg + { .path = "audio/eily/e49.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 119 eily/e49.ogg + { .path = "audio/eily/e50.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 120 eily/e50.ogg + { .path = "audio/eily/e51.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 121 eily/e51.ogg + { .path = "audio/eily/e52.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 122 eily/e52.ogg + { .path = "audio/eily/e53.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 123 eily/e53.ogg + { .path = "audio/eily/e54.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 124 eily/e54.ogg + { .path = "audio/alice/a31.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 125 alice/a31.ogg + { .path = "audio/leona/c31.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 126 leona/c31.ogg + { .path = "audio/leona/c32.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 127 leona/c32.ogg + { .path = "audio/alice/a32.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 128 alice/a32.ogg + { .path = "audio/leona/c33.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 129 leona/c33.ogg + { .path = "audio/mousegirls/mg2.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 130 mousegirls/mg2.ogg + { .path = "audio/leona/c34.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 131 leona/c34.ogg + { .path = "audio/leona/c35.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 132 leona/c35.ogg + { .path = "audio/alice/a33.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 133 alice/a33.ogg + { .path = "audio/eily/e55.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 134 eily/e55.ogg + { .path = "audio/leona/c36.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 135 leona/c36.ogg + { .path = "audio/alice/a34.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 136 alice/a34.ogg + { .path = "audio/eily/e56.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 137 eily/e56.ogg + { .path = "audio/nara/n7.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 138 nara/n7.ogg + { .path = "audio/music/WheatFields.opus.bin", .loop_end = 34.0, .audio_flags = audio::music, .gain = 1.0 }, // 139 music/WheatFields.ogg + { .path = "audio/leona/c37.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 140 leona/c37.ogg + { .path = "audio/alice/a35.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 141 alice/a35.ogg + { .path = "audio/eily/e57.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 142 eily/e57.ogg + { .path = "audio/leona/c38.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 143 leona/c38.ogg + { .path = "audio/leona/c39.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 144 leona/c39.ogg + { .path = "audio/eily/e58.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 145 eily/e58.ogg + { .path = "audio/alice/a36.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 146 alice/a36.ogg + { .path = "audio/eily/e59.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 147 eily/e59.ogg + { .path = "audio/eily/e60.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 148 eily/e60.ogg + { .path = "audio/leona/c40.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 149 leona/c40.ogg + { .path = "audio/eily/e61.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 150 eily/e61.ogg + { .path = "audio/leona/c41.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 151 leona/c41.ogg + { .path = "audio/leona/c42.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 152 leona/c42.ogg + { .path = "audio/alice/a37.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 153 alice/a37.ogg + { .path = "audio/eily/e62.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 154 eily/e62.ogg + { .path = "audio/leona/c43.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 155 leona/c43.ogg + { .path = "audio/eily/e63.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 156 eily/e63.ogg + { .path = "audio/leona/c44.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 157 leona/c44.ogg + { .path = "audio/leona/c45.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 158 leona/c45.ogg + { .path = "audio/leona/c46.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 159 leona/c46.ogg + { .path = "audio/eily/e64.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 160 eily/e64.ogg + { .path = "audio/leona/c47.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 161 leona/c47.ogg + { .path = "audio/eily/e65.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 162 eily/e65.ogg + { .path = "audio/leona/c48.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 163 leona/c48.ogg + { .path = "audio/leona/c49.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 164 leona/c49.ogg + { .path = "audio/leona/c50.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 165 leona/c50.ogg + { .path = "audio/mousegirls/mg3.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 166 mousegirls/mg3.ogg + { .path = "audio/leona/c51.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 167 leona/c51.ogg + { .path = "audio/leona/c52.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 168 leona/c52.ogg + { .path = "audio/leona/c53.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 169 leona/c53.ogg + { .path = "audio/poem/KiriStella.opus.bin", .loop_end = 0.0, .audio_flags = audio::poem, .gain = 1.0 }, // 170 poem/KiriStella.ogg + { .path = "audio/leona/c54.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 171 leona/c54.ogg + { .path = "audio/leona/c55.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 172 leona/c55.ogg + { .path = "audio/alice/a39.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 173 alice/a39.ogg + { .path = "audio/eily/e67.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 174 eily/e67.ogg + { .path = "audio/eily/e68.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 175 eily/e68.ogg + { .path = "audio/leona/c56.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 176 leona/c56.ogg + { .path = "audio/nara/n8.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 177 nara/n8.ogg + { .path = "audio/nara/n9.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 178 nara/n9.ogg + { .path = "audio/nara/n10.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 179 nara/n10.ogg + { .path = "audio/hera/h1.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 180 hera/h1.ogg + { .path = "audio/music/Preludium.opus.bin", .loop_end = 58.2, .audio_flags = audio::music, .gain = 0.45 }, // 181 music/Preludium.ogg + { .path = "audio/hera/h2.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 182 hera/h2.ogg + { .path = "audio/leona/c57.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 183 leona/c57.ogg + { .path = "audio/hera/h3.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 184 hera/h3.ogg + { .path = "audio/eily/e69.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 185 eily/e69.ogg + { .path = "audio/hera/h4.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 186 hera/h4.ogg + { .path = "audio/sfx/Glass.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 187 sfx/Glass.ogg + { .path = "audio/alice/a40.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 188 alice/a40.ogg + { .path = "audio/alice/a41.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 189 alice/a41.ogg + { .path = "audio/leona/c58.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 190 leona/c58.ogg + { .path = "audio/bird/b1.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 191 bird/b1.ogg + { .path = "audio/bird/b2.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 192 bird/b2.ogg + { .path = "audio/bird/b3.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 193 bird/b3.ogg + { .path = "audio/leona/c59.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 194 leona/c59.ogg + { .path = "audio/eily/e70.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 195 eily/e70.ogg + { .path = "audio/bird/b4.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 196 bird/b4.ogg + { .path = "audio/bird/b5.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 197 bird/b5.ogg + { .path = "audio/leona/c60.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 198 leona/c60.ogg + { .path = "audio/leona/c61.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 199 leona/c61.ogg + { .path = "audio/bird/b6.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 200 bird/b6.ogg + { .path = "audio/bird/b7.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 201 bird/b7.ogg + { .path = "audio/leona/c0.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 202 leona/c0.ogg + { .path = "audio/bird/b8.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 203 bird/b8.ogg + { .path = "audio/bird/b9.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 204 bird/b9.ogg + { .path = "audio/bird/b10.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 205 bird/b10.ogg + { .path = "audio/leona/c62.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 206 leona/c62.ogg + { .path = "audio/bird/b11.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 207 bird/b11.ogg + { .path = "audio/bird/b12.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 208 bird/b12.ogg + { .path = "audio/bird/b13.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 209 bird/b13.ogg + { .path = "audio/leona/c63.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 210 leona/c63.ogg + { .path = "audio/bird/b14.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 211 bird/b14.ogg + { .path = "audio/leona/c64.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 212 leona/c64.ogg + { .path = "audio/bird/b15.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 213 bird/b15.ogg + { .path = "audio/leona/c65.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 214 leona/c65.ogg + { .path = "audio/leona/c66.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 215 leona/c66.ogg + { .path = "audio/bird/b16.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 216 bird/b16.ogg + { .path = "audio/bird/b17.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 217 bird/b17.ogg + { .path = "audio/alice/a42.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 218 alice/a42.ogg + { .path = "audio/eily/e71.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 219 eily/e71.ogg + { .path = "audio/eily/e72.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 220 eily/e72.ogg + { .path = "audio/eily/e73.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 221 eily/e73.ogg + { .path = "audio/eily/e74.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 222 eily/e74.ogg + { .path = "audio/eily/e75.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 223 eily/e75.ogg + { .path = "audio/eily/e76.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 224 eily/e76.ogg + { .path = "audio/bird/b18.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 225 bird/b18.ogg + { .path = "audio/leona/c67.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 226 leona/c67.ogg + { .path = "audio/alice/a43.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 227 alice/a43.ogg + { .path = "audio/eily/e77.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 228 eily/e77.ogg + { .path = "audio/bird/b19.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 229 bird/b19.ogg + { .path = "audio/bird/b20.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 230 bird/b20.ogg + { .path = "audio/poem/BirdSong.opus.bin", .loop_end = 0.0, .audio_flags = audio::poem, .gain = 1.0 }, // 231 poem/BirdSong.ogg + { .path = "audio/nara/n11.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 232 nara/n11.ogg + { .path = "audio/nara/n12.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 233 nara/n12.ogg + { .path = "audio/nara/n13.opus.bin", .loop_end = 0.0, .audio_flags = 0, .gain = 1.0 }, // 234 nara/n13.ogg }; const int audio_length = (sizeof (audio)) / (sizeof (audio[0])); diff --git a/src/renpy/vulkan.cpp b/src/renpy/vulkan.cpp index 131e649..7d63519 100644 --- a/src/renpy/vulkan.cpp +++ b/src/renpy/vulkan.cpp @@ -640,7 +640,7 @@ namespace renpy { { .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, .memory = instanceMemory, - .offset = 0, + .offset = instanceBufferOffset[frameIndex], .size = (sizeof (ImageInstance)) * outputIndex, } }; diff --git a/src/ui.cpp b/src/ui.cpp new file mode 100644 index 0000000..3dc980f --- /dev/null +++ b/src/ui.cpp @@ -0,0 +1,169 @@ +#include "ui.h" +#include "audio.h" + +namespace ui +{ + constexpr int top = 75; + constexpr int ySpace = 120; + constexpr int yMixSpace = 60; + + constexpr int inputMixLeft = 100; + constexpr int allpassLeft = 400; + constexpr int combLeft = 700; + constexpr int outputMixLeft = 1000; + +#define BREVERB audio::fbreverb +#define FREVERB audio::ffreverb + + widget::DelayGainSlider fap[3] { + widget::DelayGainSlider("allpass 0", + allpassLeft, top + ySpace * 0, + 1, 2500, &FREVERB.ap[0].delay, + 0.0, 1.0, &FREVERB.ap[0].gain0, &FREVERB.ap[0].gainM), + widget::DelayGainSlider("allpass 1", + allpassLeft, top + ySpace * 1, + 1, 2500, &FREVERB.ap[1].delay, + 0.0, 1.0, &FREVERB.ap[1].gain0, &FREVERB.ap[1].gainM), + widget::DelayGainSlider("allpass 2", + allpassLeft, top + ySpace * 2, + 1, 2500, &FREVERB.ap[2].delay, + 0.0, 1.0, &FREVERB.ap[2].gain0, &FREVERB.ap[2].gainM), + }; + + widget::DelayGainSlider fcomb[4] { + widget::DelayGainSlider("comb 0", + combLeft, top + ySpace * 0, + 100, 15000, &FREVERB.cf[0].delay, + 0.0, 1.0, &FREVERB.cf[0].gain0, &FREVERB.cf[0].gainM), + widget::DelayGainSlider("comb 1", + combLeft, top + ySpace * 1, + 100, 15000, &FREVERB.cf[1].delay, + 0.0, 1.0, &FREVERB.cf[1].gain0, &FREVERB.cf[1].gainM), + widget::DelayGainSlider("comb 2", + combLeft, top + ySpace * 2, + 100, 15000, &FREVERB.cf[2].delay, + 0.0, 1.0, &FREVERB.cf[2].gain0, &FREVERB.cf[2].gainM), + widget::DelayGainSlider("comb 3", + combLeft, top + ySpace * 3, + 100, 15000, &FREVERB.cf[3].delay, + 0.0, 1.0, &FREVERB.cf[3].gain0, &FREVERB.cf[3].gainM), + }; + + widget::DelayGainSlider bap[3] { + widget::DelayGainSlider("allpass 0", + allpassLeft, top + ySpace * 0, + 1, 2500, &BREVERB.ap[0].delay, + 0.0, 1.0, &BREVERB.ap[0].gain0, &BREVERB.ap[0].gainM), + widget::DelayGainSlider("allpass 1", + allpassLeft, top + ySpace * 1, + 1, 2500, &BREVERB.ap[1].delay, + 0.0, 1.0, &BREVERB.ap[1].gain0, &BREVERB.ap[1].gainM), + widget::DelayGainSlider("allpass 2", + allpassLeft, top + ySpace * 2, + 1, 2500, &BREVERB.ap[2].delay, + 0.0, 1.0, &BREVERB.ap[2].gain0, &BREVERB.ap[2].gainM), + }; + + widget::DelayGainSlider bcomb[4] { + widget::DelayGainSlider("comb 0", + combLeft, top + ySpace * 0, + 100, 15000, &BREVERB.cf[0].delay, + 0.0, 1.0, &BREVERB.cf[0].gain0, &BREVERB.cf[0].gainM), + widget::DelayGainSlider("comb 1", + combLeft, top + ySpace * 1, + 100, 15000, &BREVERB.cf[1].delay, + 0.0, 1.0, &BREVERB.cf[1].gain0, &BREVERB.cf[1].gainM), + widget::DelayGainSlider("comb 2", + combLeft, top + ySpace * 2, + 100, 15000, &BREVERB.cf[2].delay, + 0.0, 1.0, &BREVERB.cf[2].gain0, &BREVERB.cf[2].gainM), + widget::DelayGainSlider("comb 3", + combLeft, top + ySpace * 3, + 100, 15000, &BREVERB.cf[3].delay, + 0.0, 1.0, &BREVERB.cf[3].gain0, &BREVERB.cf[3].gainM), + }; + + widget::Slider dryGain("dry gain", + outputMixLeft, top + yMixSpace * 0, 150, 14, + 0.0, 1.0, 0.0, 1.0, + &audio::dryGain); + + widget::Slider wetGain("wet gain", + outputMixLeft, top + yMixSpace * 1, 150, 14, + 0.0, 1.0, 0.0, 1.0, + &audio::wetGain); + + widget::Slider voiceGain("voice gain", + inputMixLeft, top + yMixSpace * 0, 150, 14, + 0.0, 1.0, 0.0, 1.0, + &audio::mixChannelGain[audio::mix_channel::voice]); + + widget::Slider poemGain("poem gain", + inputMixLeft, top + yMixSpace * 1, 150, 14, + 0.0, 1.0, 0.0, 1.0, + &audio::mixChannelGain[audio::mix_channel::poem]); + + widget::Slider musicGain("music gain", + inputMixLeft, top + yMixSpace * 2, 150, 14, + 0.0, 1.0, 0.0, 1.0, + &audio::mixChannelGain[audio::mix_channel::music]); + + const char * reverberatorNames[] = { + "feed-back", + "feed-forward", + }; + widget::Radio<2> reverberators("reverberator", + reverberatorNames, + 950, 200, 100, 40, + 120, 0, + &audio::reverbIndex); + + void draw(MappedInstanceData & data, + MappedInstanceData & fontData) + { + data.append({ + { (uint16_t)0, (uint16_t)0 }, + { (uint16_t)1280, (uint16_t)720 }, + 0x80000000, + }); + + widget::DelayGainSlider * ap = (audio::reverbIndex == 0) ? bap : fap; + widget::DelayGainSlider * comb = (audio::reverbIndex == 0) ? bcomb : fcomb; + + for (int i = 0; i < 3; i++) + ap[i].draw(data, fontData); + + for (int i = 0; i < 4; i++) + comb[i].draw(data, fontData); + + dryGain.draw(data, fontData); + wetGain.draw(data, fontData); + + voiceGain.draw(data, fontData); + poemGain.draw(data, fontData); + musicGain.draw(data, fontData); + + reverberators.draw(data, fontData); + } + + void update(float mx, float my, bool mLeft, bool mEdge) + { + widget::DelayGainSlider * ap = (audio::reverbIndex == 0) ? bap : fap; + widget::DelayGainSlider * comb = (audio::reverbIndex == 0) ? bcomb : fcomb; + + for (int i = 0; i < 3; i++) + ap[i].update(mx, my, mLeft, mEdge); + + for (int i = 0; i < 4; i++) + comb[i].update(mx, my, mLeft, mEdge); + + dryGain.update(mx, my, mLeft, mEdge); + wetGain.update(mx, my, mLeft, mEdge); + + voiceGain.update(mx, my, mLeft, mEdge); + poemGain.update(mx, my, mLeft, mEdge); + musicGain.update(mx, my, mLeft, mEdge); + + reverberators.update(mx, my, mLeft, mEdge); + } +} diff --git a/src/ui/vulkan.cpp b/src/ui/vulkan.cpp new file mode 100644 index 0000000..e56e64c --- /dev/null +++ b/src/ui/vulkan.cpp @@ -0,0 +1,118 @@ +#include + +#ifdef __APPLE__ +#include "vulkan/vulkan.h" +#else +#include "volk/volk.h" +#endif + +#include "vulkan_helper.h" + +#include "ui/vulkan.h" +#include "ui/instance_data.h" +#include "ui.h" + +#include "font/bitmap.h" + +namespace ui { + + void vulkan::createPipeline() + { + uint32_t descriptorSetLayoutCount = 0; + VkDescriptorSetLayout const * descriptorSetLayouts = nullptr; + uint32_t pushConstantRangeCount = 0; + VkPushConstantRange const * pushConstantRanges = nullptr; + VkShaderModule shaderModule = loadShader(vk->device, "shader/ui/solid.spv"); + uint32_t perInstanceStride = (sizeof (SolidInstance)); + constexpr uint32_t instanceAttributeDescriptionCount = 3; + VkVertexInputAttributeDescription instanceAttributeDescriptions[instanceAttributeDescriptionCount] { + { // position + .location = 2, + .binding = 1, + .format = VK_FORMAT_R16G16_UINT, + .offset = 0, + }, + { // size + .location = 3, + .binding = 1, + .format = VK_FORMAT_R16G16_UINT, + .offset = 4, + }, + { // color + .location = 4, + .binding = 1, + .format = VK_FORMAT_R8G8B8A8_UNORM, + .offset = 8, + }, + }; + + createQuadPipeline(vk->device, + vk->colorFormat, + vk->depthFormat, + descriptorSetLayoutCount, + descriptorSetLayouts, + pushConstantRangeCount, + pushConstantRanges, + shaderModule, + perInstanceStride, + instanceAttributeDescriptionCount, + instanceAttributeDescriptions, + &pipelineLayout, + &pipeline); + } + + int vulkan::update(uint32_t frameIndex, MappedInstanceData & bitmapData) const + { + MappedInstanceData solidData{ solidInstance[frameIndex], maximumUIElements, 0 }; + ui::draw(solidData, bitmapData); + + ////////////////////////////////////////////////////////////////////// + // flush + ////////////////////////////////////////////////////////////////////// + + constexpr int mappedMemoryRangesCount = 1; + VkMappedMemoryRange mappedMemoryRanges[mappedMemoryRangesCount]{ + { + .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, + .memory = instanceBuffer.memory, + .offset = instanceBuffer.offset[frameIndex], + .size = (sizeof (SolidInstance)) * solidData.index, + } + }; + alignMappedMemoryRanges(vk->physicalDeviceProperties.limits.nonCoherentAtomSize, + instanceBuffer.memorySize, + mappedMemoryRangesCount, + mappedMemoryRanges); + vkFlushMappedMemoryRanges(vk->device, mappedMemoryRangesCount, mappedMemoryRanges); + + return solidData.index; + } + + void vulkan::draw(VkCommandBuffer commandBuffer, + uint32_t frameIndex, + MappedInstanceData & bitmapData) const + { + int outputIndex = update(frameIndex, bitmapData); + + 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, vk->quadVertexIndex.buffer, vk->quadVertexIndex.indexOffset, VK_INDEX_TYPE_UINT16); + + VkDeviceSize vertexOffsets[2]{ 0, instanceBuffer.offset[frameIndex] }; + VkBuffer vertexBuffers[2]{ vk->quadVertexIndex.buffer, instanceBuffer.buffer }; + vkCmdBindVertexBuffers(commandBuffer, 0, 2, vertexBuffers, vertexOffsets); + + vkCmdDrawIndexed(commandBuffer, 4, outputIndex, 0, 0, 0); + } +} diff --git a/src/ui/widget.cpp b/src/ui/widget.cpp new file mode 100644 index 0000000..e527627 --- /dev/null +++ b/src/ui/widget.cpp @@ -0,0 +1,221 @@ +#include +#include + +#include "ui/widget.h" +#include "intstring.h" +#include "minmax.h" + +namespace ui::widget +{ + static void drawBoundingBox(MappedInstanceData & data, + BoundingBox const & bb, uint32_t color) + { + if ((color & 0xff000000) == 0) + color |= 0xff000000; + data.append({ + { (uint16_t)bb.left, (uint16_t)bb.top }, + { (uint16_t)bb.width, (uint16_t)bb.height }, + color, + }); + } + + static inline void drawGlyph(MappedInstanceData & fontData, + int x, int y, + int c) + { + fontData.append({ {(uint16_t)(x), (uint16_t)(y)}, font::bitmap::glyphIndex(c) }); + } + + template + inline void drawNumberCentered(MappedInstanceData & fontData, + int x, int y, + T number); + + template<> + inline void drawNumberCentered(MappedInstanceData & fontData, + int x, int y, + int number) + { + char buf[16]; + int numLength = string::dec(buf, 16, number); + int width = numLength * 6; + int left = x - (width / 2); + for (int i = 0; i < numLength; i++) { + fontData.append({ {(uint16_t)(left + 6 * i), (uint16_t)(y)}, font::bitmap::glyphIndex(buf[i]) }); + } + } + + template<> + inline void drawNumberCentered(MappedInstanceData & fontData, + int x, int y, + float number) + { + + char buf[64]; + int numLength = string::flt(buf, 64, number); + int width = numLength * 6; + int left = x - (width / 2); + for (int i = 0; i < numLength; i++) { + fontData.append({ {(uint16_t)(left + 6 * i), (uint16_t)(y)}, font::bitmap::glyphIndex(buf[i]) }); + } + } + + static inline void drawStringCentered(MappedInstanceData & fontData, + int x, int y, + const char * string) + { + int length = strlen(string); + int width = length * 6; + int left = x - (width / 2); + for (int i = 0; i < length; i++) { + fontData.append({ {(uint16_t)(left + 6 * i), (uint16_t)(y)}, font::bitmap::glyphIndex(string[i]) }); + } + } + + static inline void drawStringLeft(MappedInstanceData & fontData, + int x, int y, + const char * string) + { + int length = strlen(string); + int left = x; + for (int i = 0; i < length; i++) { + fontData.append({ {(uint16_t)(left + 6 * i), (uint16_t)(y)}, font::bitmap::glyphIndex(string[i]) }); + } + } + + static inline void drawStringRight(MappedInstanceData & fontData, + int x, int y, + const char * string) + { + int length = strlen(string); + + int width = length * 6; + int left = x - width; + for (int i = 0; i < length; i++) { + fontData.append({ {(uint16_t)(left + 6 * i), (uint16_t)(y)}, font::bitmap::glyphIndex(string[i]) }); + } + } + + template + void Slider::draw(MappedInstanceData & data, + MappedInstanceData & fontData) + { + drawBoundingBox(data, sliderBorder, 0x545454); + + float sliderInterp = (float)(*value - minExtent) / (float)(maxExtent - minExtent); + int sliderWidth = (float)slider.width * clamp01(sliderInterp); + + drawBoundingBox(data, { + slider.left, + slider.top, + sliderWidth, + slider.height, + }, 0x800000); + + drawBoundingBox(data, lminus, 0x008080); + drawGlyph(fontData, lminus.left + 2, lminus.top - 3, '-'); + drawBoundingBox(data, lplus, 0x008080); + drawGlyph(fontData, lplus.left + 2, lplus.top - 3, '+'); + + drawBoundingBox(data, rminus, 0x008000); + drawGlyph(fontData, rminus.left + 2, lminus.top - 3, '-'); + drawBoundingBox(data, rplus, 0x008000); + drawGlyph(fontData, rplus.left + 2, lplus.top - 3, '+'); + + constexpr int yOffset = under ? 14 : -16; + + drawNumberCentered(fontData, slider.left, slider.top + yOffset, minExtent); + drawNumberCentered(fontData, slider.left + slider.width, slider.top + yOffset, maxExtent); + + drawNumberCentered(fontData, slider.left + slider.width / 2, slider.top + yOffset, *value); + + drawStringRight(fontData, slider.left - 25, slider.top - 1, label); + //drawStringRight(fontData, slider.left - 25, slider.top + 5, label); + //drawStringLeft(fontData, slider.left + slider.width + 26, slider.top - 1, label); + } + + template + void Slider::update(float mx, float my, bool mLeft, bool mEdge) + { + if (!mLeft) { + drag = false; + return; + } + + if (mEdge) { + double eWidth2 = (double)(maxExtent - minExtent) / 2.0f; + double vWidth2 = (double)(maxValue - minValue) / 2.0f; + double width2 = min(eWidth2, vWidth2); + + if (lminus.inside(mx, my)) { + minExtent = clamp(minExtent + width2, minValue, maxValue); + } else if (lplus.inside(mx, my)) { + minExtent = clamp(minExtent - width2, minValue, maxValue); + } else if (rminus.inside(mx, my)) { + maxExtent = clamp(maxExtent - width2, minValue, maxValue); + } else if (rplus.inside(mx, my)) { + maxExtent = clamp(maxExtent + width2, minValue, maxValue); + } else if (slider.inside(mx, my)) { + drag = true; + } + } + + if (drag) { + float lerp = clamp01((mx - (float)slider.left) / (float)(slider.width)); + + float valueScale = maxExtent - minExtent; + *value = (T)(lerp * valueScale + minExtent); + } + } + + template struct Slider; + template struct Slider; + template struct Slider; + template struct Slider; + + void DelayGainSlider::draw(MappedInstanceData & data, + MappedInstanceData & fontData) + { + drawStringCentered(fontData, delay.slider.left + delay.slider.width / 2, delay.slider.top - 16, label); + + delay.draw(data, fontData); + gain0.draw(data, fontData); + gainM.draw(data, fontData); + } + + void DelayGainSlider::update(float mx, float my, bool mLeft, bool mEdge) + { + delay.update(mx, my, mLeft, mEdge); + gain0.update(mx, my, mLeft, mEdge); + gainM.update(mx, my, mLeft, mEdge); + } + + template + void Radio::draw(MappedInstanceData & data, + MappedInstanceData & fontData) + { + drawStringCentered(fontData, box[0].left + width / 2, box[0].top - 16, label); + + for (int i = 0; i < optionCount; i++) { + uint32_t color = (i == *selected) ? 0xa0800080 : 0x80300030; + drawBoundingBox(data, box[i], color); + drawStringCentered(fontData, box[i].left + box[i].width / 2, box[0].top + box[i].height / 2 - 6, boxLabels[i]); + } + } + + template + void Radio::update(float mx, float my, bool mLeft, bool mEdge) + { + if (!mEdge) + return; + + for (int i = 0; i < optionCount; i++) { + if (box[i].inside(mx, my)) { + *selected = i; + break; + } + } + } + + template struct widget::Radio<2>; +} diff --git a/src/vulkan_helper.cpp b/src/vulkan_helper.cpp index 4170c37..637bc35 100644 --- a/src/vulkan_helper.cpp +++ b/src/vulkan_helper.cpp @@ -9,7 +9,6 @@ #include "volk/volk.h" #endif -#include "vulkan/vulkan.h" #include "vulkan/vk_enum_string_helper.h" #include "minmax.h" @@ -134,7 +133,7 @@ void textureTransfer(VkDevice device, VkDeviceSize nonCoherentAtomSize, VkPhysicalDeviceMemoryProperties const & physicalDeviceMemoryProperties, uint32_t imageDataSize, - void * imageData, + void const * imageData, VkImage image, uint32_t width, uint32_t height, @@ -491,3 +490,244 @@ VertexIndex createVertexIndexBuffer(VkDevice device, return vertexIndex; } + +void createQuadPipeline(VkDevice device, + VkFormat colorFormat, + VkFormat depthFormat, + uint32_t descriptorSetLayoutCount, + VkDescriptorSetLayout const * descriptorSetLayouts, + uint32_t pushConstantRangeCount, + VkPushConstantRange const * pushConstantRanges, + VkShaderModule shaderModule, + uint32_t perInstanceStride, + uint32_t instanceAttributeDescriptionCount, + VkVertexInputAttributeDescription * instanceAttributeDescriptions, + VkPipelineLayout * pipelineLayout, + VkPipeline * pipeline) +{ + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .setLayoutCount = descriptorSetLayoutCount, + .pSetLayouts = descriptorSetLayouts, + .pushConstantRangeCount = pushConstantRangeCount, + .pPushConstantRanges = pushConstantRanges + }; + 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{ + .blendEnable = VK_TRUE, + .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA, + .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD, + .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 uint32_t perVertexStride = 4 * 2; + constexpr int vertexBindingDescriptionsCount = 2; + VkVertexInputBindingDescription vertexBindingDescriptions[vertexBindingDescriptionsCount]{ + { + .binding = 0, + .stride = perVertexStride, + .inputRate = VK_VERTEX_INPUT_RATE_VERTEX + }, + { + .binding = 1, + .stride = perInstanceStride, + .inputRate = VK_VERTEX_INPUT_RATE_INSTANCE + }, + }; + + uint32_t vertexAttributeDescriptionsCount = 2u + instanceAttributeDescriptionCount; + VkVertexInputAttributeDescription vertexAttributeDescriptions[vertexAttributeDescriptionsCount]; + // per-vertex + vertexAttributeDescriptions[0] = { // position + .location = 0, + .binding = 0, + .format = VK_FORMAT_R16G16_SFLOAT, + .offset = 0, + }; + vertexAttributeDescriptions[1] = { // texture + .location = 1, + .binding = 0, + .format = VK_FORMAT_R16G16_SFLOAT, + .offset = 4, + }; + memcpy(&vertexAttributeDescriptions[2], instanceAttributeDescriptions, instanceAttributeDescriptionCount * (sizeof (VkVertexInputAttributeDescription))); + + 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)); +} + +VkShaderModule loadShader(VkDevice device, + char const * const path) +{ + uint32_t shaderSize; + void const * shaderStart = file::open(path, &shaderSize); + + VkShaderModuleCreateInfo shaderModuleCreateInfo{ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = shaderSize, + .pCode = (uint32_t *)shaderStart + }; + VkShaderModule shaderModule; + VK_CHECK(vkCreateShaderModule(device, &shaderModuleCreateInfo, nullptr, &shaderModule)); + return shaderModule; +} + +void createInstanceBuffer(VkDevice device, + VkPhysicalDeviceProperties const & physicalDeviceProperties, + VkPhysicalDeviceMemoryProperties const & physicalDeviceMemoryProperties, + VkDeviceSize bufferSize, + InstanceBuffer * instanceBuffer) +{ + instanceBuffer->memorySize = bufferSize * 2; + instanceBuffer->offset[0] = bufferSize * 0; + instanceBuffer->offset[1] = bufferSize * 1; + + // create buffer + VkBufferCreateInfo bufferCreateInfo{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = instanceBuffer->memorySize, + .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE + }; + VK_CHECK(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &instanceBuffer->buffer)); + + // allocate memory + + VkMemoryRequirements memoryRequirements; + vkGetBufferMemoryRequirements(device, instanceBuffer->buffer, &memoryRequirements); + VkMemoryPropertyFlags memoryPropertyFlags{ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT }; + VkMemoryAllocateFlags memoryAllocateFlags{}; + VkDeviceSize stride; + allocateFromMemoryRequirements(device, + physicalDeviceProperties.limits.nonCoherentAtomSize, + physicalDeviceMemoryProperties, + memoryRequirements, + memoryPropertyFlags, + memoryAllocateFlags, + 1, + &instanceBuffer->memory, + &stride); + + VK_CHECK(vkBindBufferMemory(device, instanceBuffer->buffer, instanceBuffer->memory, 0)); + + // map memory + + VK_CHECK(vkMapMemory(device, instanceBuffer->memory, 0, VK_WHOLE_SIZE, 0, (void **)&instanceBuffer->mappedData)); +} diff --git a/src/vulkan_state.cpp b/src/vulkan_state.cpp new file mode 100644 index 0000000..b98f8d7 --- /dev/null +++ b/src/vulkan_state.cpp @@ -0,0 +1,43 @@ +#include "vulkan_state.h" + +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)); + + +VulkanState::VulkanState(VkInstance instance, + VkDevice device, + VkQueue queue, + VkCommandPool commandPool, + VkPhysicalDeviceProperties const & physicalDeviceProperties, + VkPhysicalDeviceMemoryProperties const & physicalDeviceMemoryProperties, + VkFormat colorFormat, + VkFormat depthFormat) + : instance(instance) + , device(device) + , queue(queue) + , commandPool(commandPool) + , physicalDeviceProperties(physicalDeviceProperties) + , physicalDeviceMemoryProperties(physicalDeviceMemoryProperties) + , colorFormat(colorFormat) + , depthFormat(depthFormat) +{ + void const * vertexStart = (void const *)vertexData; + void const * indexStart = (void const *)indexData; + + quadVertexIndex = createVertexIndexBuffer(device, + physicalDeviceProperties, + physicalDeviceMemoryProperties, + vertexStart, vertexSize, + indexStart, indexSize); +}