From 532c4054b67795e9005267d038f9ed4a6e228a47 Mon Sep 17 00:00:00 2001 From: ecker Date: Sun, 24 Aug 2025 11:38:37 -0500 Subject: [PATCH] audio cleanup or something (because it's lagging the dreamcast build when emitting sounds) --- Makefile | 38 ++++---- bin/data/config.json | 5 +- .../entities/gui/mainmenu/scripts/menu.lua | 1 + bin/data/entities/gui/pause/scripts/menu.lua | 3 +- bin/data/scenes/startmenu/scene.json | 2 +- bin/dreamcast/data/config.json | 48 ++++++---- bin/exe/default/renderer | 2 +- engine/inc/uf/macros.h | 4 +- engine/inc/uf/utils/audio/audio.h | 7 ++ engine/inc/uf/utils/audio/emitter.h | 3 +- engine/inc/uf/utils/audio/metadata.h | 1 + engine/inc/uf/utils/io/file.h | 2 + engine/lib/win64/zig/libalut.pdb | Bin 249856 -> 0 bytes .../src/engine/ext/audio/emitter/behavior.cpp | 26 ++++-- engine/src/engine/ext/ext.cpp | 1 + engine/src/engine/object/behavior.cpp | 10 ++- engine/src/ext/freetype/freetype.cpp | 2 +- engine/src/utils/audio/audio.cpp | 42 ++++++++- engine/src/utils/audio/emitter.cpp | 26 ++++-- engine/src/utils/image/image.cpp | 18 ++-- engine/src/utils/io/file.cpp | 83 ++++++++++++++---- makefiles/dreamcast.gcc.make | 7 +- makefiles/linux.clang.make | 5 +- makefiles/linux.gcc.make | 5 +- makefiles/win64.clang.make | 10 ++- makefiles/win64.gcc.make | 11 ++- makefiles/win64.gcc7.make | 5 -- makefiles/win64.tdm-gcc.make | 8 -- 28 files changed, 265 insertions(+), 110 deletions(-) delete mode 100644 engine/lib/win64/zig/libalut.pdb delete mode 100644 makefiles/win64.gcc7.make delete mode 100644 makefiles/win64.tdm-gcc.make diff --git a/Makefile b/Makefile index 25b2c695..f6bb3060 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,9 @@ ARCH = $(shell cat "./makefiles/default/arch") CC = $(shell cat "./makefiles/default/cc") RENDERER = $(shell cat "./makefiles/default/renderer") TARGET_NAME = program -TARGET_EXTENSION = exe -LIB_EXTENSION = dll -LIB_EXTENSION_A = .a +TARGET_EXTENSION = .exe +DLIB_EXTENSION = .dll +SLIB_EXTENSION = .a PREFIX = $(ARCH).$(CC) include makefiles/$(PREFIX).make @@ -213,10 +213,15 @@ ifneq (,$(findstring lua,$(REQ_DEPS))) FLAGS += -DUF_USE_LUAJIT DEPS += -lluajit-5.1 + # sol directly includes ifneq (,$(findstring linux,$(ARCH))) INCS += -I/usr/include/luajit-2.1 else - INCS += -I/mingw64/include/luajit-2.1 + ifneq (,$(findstring clang,$(CC))) + INCS += -I/clang64/include/luajit-2.1 + else + INCS += -I/mingw64/include/luajit-2.1 + endif endif else ifneq (,$(findstring dreamcast,$(ARCH))) @@ -288,8 +293,8 @@ endif SRCS_DLL := $(shell find $(ENGINE_SRC_DIR) -name "*.cpp") $(shell find $(DEP_SRC_DIR) -name "*.cpp") OBJS_DLL += $(patsubst %.cpp,%.$(PREFIX).o,$(SRCS_DLL)) BASE_DLL += lib$(LIB_NAME) -IM_DLL += $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_DLL).$(LIB_EXTENSION)$(LIB_EXTENSION_A) -EX_DLL += $(BIN_DIR)/exe/lib/$(PREFIX_PATH)/$(BASE_DLL).$(LIB_EXTENSION) +IM_DLL += $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_DLL)$(DLIB_EXTENSION) +EX_DLL += $(BIN_DIR)/exe/lib/$(PREFIX_PATH)/$(BASE_DLL)$(DLIB_EXTENSION) # External Engine's DLL EXT_INC_DIR += $(INC_DIR) EXT_LB_FLAGS += $(LIB_DIR) @@ -304,13 +309,13 @@ EXT_LIBS += $(LIBS) SRCS_EXT_DLL := $(shell find $(EXT_SRC_DIR) -name "*.cpp") OBJS_EXT_DLL += $(patsubst %.cpp,%.$(PREFIX).o,$(SRCS_EXT_DLL)) BASE_EXT_DLL += lib$(EXT_LIB_NAME) -EXT_IM_DLL += $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_EXT_DLL).$(LIB_EXTENSION)$(LIB_EXTENSION_A) -EXT_EX_DLL += $(BIN_DIR)/exe/lib/$(PREFIX_PATH)/$(BASE_EXT_DLL).$(LIB_EXTENSION) +EXT_IM_DLL += $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_EXT_DLL)$(DLIB_EXTENSION) +EXT_EX_DLL += $(BIN_DIR)/exe/lib/$(PREFIX_PATH)/$(BASE_EXT_DLL)$(DLIB_EXTENSION) # Client EXE #SRCS += $(wildcard $(CLIENT_SRC_DIR)/*.cpp) $(wildcard $(CLIENT_SRC_DIR)/*/*.cpp) SRCS := $(shell find $(CLIENT_SRC_DIR) -name "*.cpp") OBJS += $(patsubst %.cpp,%.$(PREFIX).o,$(SRCS)) -TARGET += $(BIN_DIR)/exe/$(TARGET_NAME).$(PREFIX).$(TARGET_EXTENSION) +TARGET += $(BIN_DIR)/exe/$(TARGET_NAME).$(PREFIX)$(TARGET_EXTENSION) # Shaders #SRCS_SHADERS += $(wildcard bin/data/shaders/*.glsl) $(wildcard bin/data/shaders/*/*.glsl) $(wildcard bin/data/shaders/*/*/*.glsl) SRCS_SHADERS := $(shell find bin/data/shaders/ -name "*.glsl") @@ -337,13 +342,13 @@ $(EX_DLL): FLAGS += -DUF_EXPORTS $(EX_DLL): $(OBJS_DLL) $(KOS_AR) cru $@ $^ $(KOS_RANLIB) $@ - cp $@ $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_DLL)$(LIB_EXTENSION_A) + cp $@ $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_DLL) $(EXT_EX_DLL): FLAGS += -DEXT_EXPORTS $(EXT_EX_DLL): $(OBJS_EXT_DLL) $(KOS_AR) cru $@ $^ $(KOS_RANLIB) $@ - cp $@ $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_EXT_DLL)$(LIB_EXTENSION_A) + cp $@ $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_EXT_DLL) ./bin/dreamcast/romdisk.img: $(KOS_GENROMFS) -f ./bin/dreamcast/romdisk.img -d ./bin/dreamcast/romdisk/ -v @@ -372,7 +377,7 @@ ifneq (,$(findstring linux,$(ARCH))) #$(EX_DLL): FLAGS += -DUF_EXPORTS -DEXT_EXPORTS $(EX_DLL): FLAGS += -DUF_EXPORTS $(EX_DLL): $(OBJS_DLL) - $(CXX) $(FLAGS) -shared -Wl,-soname,$(BASE_DLL).$(LIB_EXTENSION) $(OBJS_DLL) $(LIBS) $(INCS) $(LINKS) -o $(EX_DLL) + $(CXX) $(FLAGS) -shared -Wl,-soname,$(BASE_DLL)$(DLIB_EXTENSION) $(OBJS_DLL) $(LIBS) $(INCS) $(LINKS) -o $(EX_DLL) cp $(EX_DLL) $(IM_DLL) @echo -n $(ARCH) > "./bin/exe/default/arch" @echo -n $(CC) > "./bin/exe/default/cc" @@ -380,7 +385,7 @@ $(EX_DLL): $(OBJS_DLL) @echo "Setting defaults: $(ARCH).$(CC).$(RENDERER)" $(EXT_EX_DLL): FLAGS += -DEXT_EXPORTS $(EXT_EX_DLL): $(OBJS_EXT_DLL) - $(CXX) $(FLAGS) -shared -Wl,-soname,$(BASE_EXT_DLL).$(LIB_EXTENSION) $(OBJS_EXT_DLL) $(EXT_LIBS) $(EXT_INCS) $(EXT_LINKS) -o $(EXT_EX_DLL) + $(CXX) $(FLAGS) -shared -Wl,-soname,$(BASE_EXT_DLL)$(DLIB_EXTENSION) $(OBJS_EXT_DLL) $(EXT_LIBS) $(EXT_INCS) $(EXT_LINKS) -o $(EXT_EX_DLL) cp $(EXT_EX_DLL) $(EXT_IM_DLL) else @@ -388,8 +393,7 @@ else #$(EX_DLL): FLAGS += -DUF_EXPORTS -DEXT_EXPORTS $(EX_DLL): FLAGS += -DUF_EXPORTS $(EX_DLL): $(OBJS_DLL) - $(CXX) $(FLAGS) -shared -o $(EX_DLL) -Wl,--out-implib=$(IM_DLL) $(OBJS_DLL) $(LIBS) $(INCS) $(LINKS) - cp $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_DLL).$(LIB_EXTENSION).a $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_DLL).a + $(CXX) $(FLAGS) -shared -o $(EX_DLL) -Wl,--out-implib=$(IM_DLL)$(SLIB_EXTENSION) $(OBJS_DLL) $(LIBS) $(INCS) $(LINKS) @echo -n $(ARCH) > "./bin/exe/default/arch" @echo -n $(CC) > "./bin/exe/default/cc" @echo -n $(RENDERER) > "./bin/exe/default/renderer" @@ -398,8 +402,8 @@ $(EX_DLL): $(OBJS_DLL) $(EXT_EX_DLL): FLAGS += -DEXT_EXPORTS $(EXT_EX_DLL): $(OBJS_EXT_DLL) - $(CXX) $(FLAGS) -shared -o $(EXT_EX_DLL) -Wl,--out-implib=$(EXT_IM_DLL) $(OBJS_EXT_DLL) $(EXT_LIBS) $(EXT_INCS) $(EXT_LINKS) - cp $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_EXT_DLL).$(LIB_EXTENSION).a $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_EXT_DLL).a + $(CXX) $(FLAGS) -shared -o $(EXT_EX_DLL) -Wl,--out-implib=$(EXT_IM_DLL)$(SLIB_EXTENSION) $(OBJS_EXT_DLL) $(EXT_LIBS) $(EXT_INCS) $(EXT_LINKS) + endif $(TARGET): $(OBJS) diff --git a/bin/data/config.json b/bin/data/config.json index f75a6dbf..efab3978 100644 --- a/bin/data/config.json +++ b/bin/data/config.json @@ -328,6 +328,8 @@ }, "audio": { "mute": false, + "async update": false, + "streams by default": true, "buffers": { "size": 32768, "count": 4 @@ -336,8 +338,7 @@ "sfx": 0.35, "bgm": 0.15, "voice": 1.0 - }, - "streams by default": true + } }, "memory pool": { "enabled": true, // needs to be kept on diff --git a/bin/data/entities/gui/mainmenu/scripts/menu.lua b/bin/data/entities/gui/mainmenu/scripts/menu.lua index 58c0ef68..fe2914fa 100644 --- a/bin/data/entities/gui/mainmenu/scripts/menu.lua +++ b/bin/data/entities/gui/mainmenu/scripts/menu.lua @@ -187,6 +187,7 @@ ent:bind( "tick", function(self) -- simulate click on input press if (inputs.key("A") or inputs.key("START") or inputs.key("Enter")) and timer:elapsed() >= INPUT_DELAY then timer:reset() + --playSound( "buttonclickrelease" ) v:callHook("gui:Clicked.%UID%", {}) end else diff --git a/bin/data/entities/gui/pause/scripts/menu.lua b/bin/data/entities/gui/pause/scripts/menu.lua index d9e340e8..1569e7eb 100644 --- a/bin/data/entities/gui/pause/scripts/menu.lua +++ b/bin/data/entities/gui/pause/scripts/menu.lua @@ -136,7 +136,7 @@ ent:bind( "tick", function(self) static.alpha = 0 end - if (window.keyPressed("Escape") or inputs.key("START")) and timer:elapsed() >= INPUT_DELAY then + if window.keyPressed("Escape") and timer:elapsed() >= INPUT_DELAY then timer:reset() self:callHook("menu:Close.%UID%", {}) end @@ -210,6 +210,7 @@ ent:bind( "tick", function(self) -- simulate click on input press if (inputs.key("A") or inputs.key("START") or inputs.key("Enter")) and timer:elapsed() >= INPUT_DELAY then timer:reset() + --playSound( "buttonclickrelease" ) v:callHook("gui:Clicked.%UID%", {}) end else diff --git a/bin/data/scenes/startmenu/scene.json b/bin/data/scenes/startmenu/scene.json index 293f619c..0571c89d 100644 --- a/bin/data/scenes/startmenu/scene.json +++ b/bin/data/scenes/startmenu/scene.json @@ -17,7 +17,7 @@ }, "bgm": { "tracks": { - "/ui/tmrwah.wav": {} + "/ui/tmrwah.ogg": {} // "/ui/main menu.ogg": {} // "/ui/tainted.ogg": { // "intro": "/ui/tainted_intro.ogg", diff --git a/bin/dreamcast/data/config.json b/bin/dreamcast/data/config.json index f7c6da8e..ef9d69c2 100644 --- a/bin/dreamcast/data/config.json +++ b/bin/dreamcast/data/config.json @@ -24,20 +24,33 @@ "opengl": { "validation": { "enabled": true }, "framebuffer": { "size": 1, "msaa": 1 }, + "experimental": { + "rebuild on tick begin": false + }, "pipelines": { - "culling": true + "culling": false + }, + "experimental": { + "rebuild on tick begin": false, + "batch queue submissions": true, + "dedicated thread": false, + "memory budget": false, + "register render modes": false + }, + "invariant": { + "default stage buffers": true, + "default defer buffer destroy": true, + "default command buffer immediate": true, + "multithreaded recording": false }, "formats": { "depth": "D32_SFLOAT", - "color": "R8G8B8A8_UNORM", + "color": "R8G8B8A8_UNORM", // "R32G32B32A32_SFLOAT", "normal": "R16G16B16A16_SFLOAT", "position": "R16G16B16A16_SFLOAT" }, "features": [], - "extensions": { - "instance": [], - "device": [] - } + "extensions": { "instance": [], "device": [] } }, "lua": { "enabled": true, @@ -54,8 +67,9 @@ "enabled": false }, "reactphysics": { - "timescale": 0.03333333333, - "interpolate": false, + "global storage": false, + "timescale": 0.01666666666, + "interpolate": true, "gravity": { "mode": "default", "constant": 6.67408e-11 @@ -69,7 +83,9 @@ } }, "audio": { - "mute": true, + "mute": false, + "async update": false, + "streams by default": true, "buffers": { "size": 24576, "count": 6 @@ -78,8 +94,7 @@ "sfx": 1.0, "bgm": 1.0, "voice": 1.0 - }, - "streams by default": true + } }, "memory pool": { "enabled": true, // probably should be enabled @@ -100,7 +115,7 @@ }, "threads": { "workers" : 1, - "frame limiter": 15 // "auto" + "frame limiter": 15 // 60 // "auto" }, "debug": { "framerate": { @@ -115,14 +130,15 @@ }, "entity": { "delete children on destroy": false, - "delete components on destroy": false + "delete components on destroy": true }, "userdata": { "auto destruct": true, "auto validate": false }, "loader": { - "assert": true + "assert": true, + "async": true }, "hooks": { "defer lazy calls": true @@ -143,8 +159,8 @@ "mouse" : { "visible" : true, "center" : false, - "sensitivity": [ 2, 2 ], - "smoothing": [ 4, 4 ] + "sensitivity": [ 50, 50 ], + "smoothing": [ 0.25, 0.25 ] }, "mode" : "windowed", // fullscreen, borderless, windowed "icon" : "./data/textures/icon.png", diff --git a/bin/exe/default/renderer b/bin/exe/default/renderer index 53395cff..91caa7c1 100644 --- a/bin/exe/default/renderer +++ b/bin/exe/default/renderer @@ -1 +1 @@ -vulkan \ No newline at end of file +opengl \ No newline at end of file diff --git a/engine/inc/uf/macros.h b/engine/inc/uf/macros.h index 702ca55a..25776da9 100644 --- a/engine/inc/uf/macros.h +++ b/engine/inc/uf/macros.h @@ -106,8 +106,8 @@ #if UF_ENV_DREAMCAST #define DC_STATS() {\ - UF_MSG_DEBUG(spec::dreamcast::malloc_stats());\ - UF_MSG_DEBUG(spec::dreamcast::pvr_malloc_stats());\ + UF_MSG_DEBUG("{}", spec::dreamcast::malloc_stats());\ + UF_MSG_DEBUG("{}", spec::dreamcast::pvr_malloc_stats());\ } #endif diff --git a/engine/inc/uf/utils/audio/audio.h b/engine/inc/uf/utils/audio/audio.h index 33d8d3b1..4476c4c5 100644 --- a/engine/inc/uf/utils/audio/audio.h +++ b/engine/inc/uf/utils/audio/audio.h @@ -26,9 +26,11 @@ namespace uf { namespace audio { extern UF_API bool muted; + extern UF_API bool asyncUpdate; extern UF_API bool streamsByDefault; extern UF_API uint8_t buffers; extern UF_API size_t bufferSize; + #if UF_AUDIO_MAPPED_VOLUMES extern UF_API uf::stl::unordered_map volumes; #else @@ -47,6 +49,7 @@ namespace uf { public: bool initialized() const; bool playing() const; + bool played() const; void open( const uf::stl::string& ); void open( const uf::stl::string&, bool ); @@ -57,6 +60,7 @@ namespace uf { void stream( const uf::stl::string& ); void stream( const pod::PCM& ); void update(); + void update( const pod::Vector3f&, const pod::Quaternion<>& ); void destroy(); void play(); @@ -67,6 +71,9 @@ namespace uf { float getTime() const; void setTime( float ); + bool spatial() const; + void setSpatial( bool ); + void setPosition( const pod::Vector3f& ); void setOrientation( const pod::Quaternion<>& ); diff --git a/engine/inc/uf/utils/audio/emitter.h b/engine/inc/uf/utils/audio/emitter.h index c61e53d7..713a2652 100644 --- a/engine/inc/uf/utils/audio/emitter.h +++ b/engine/inc/uf/utils/audio/emitter.h @@ -1,6 +1,5 @@ #pragma once - namespace uf { class UF_API AudioEmitter { public: @@ -24,6 +23,7 @@ namespace uf { const container_t& get() const; void update(); + void update( const pod::Vector3f&, const pod::Quaternion<>& ); void cleanup( bool = false ); }; class UF_API MappedAudioEmitter { @@ -47,6 +47,7 @@ namespace uf { const container_t& get() const; void update(); + void update( const pod::Vector3f&, const pod::Quaternion<>& ); void cleanup( bool = false ); }; diff --git a/engine/inc/uf/utils/audio/metadata.h b/engine/inc/uf/utils/audio/metadata.h index 7d7b0700..a07339c1 100644 --- a/engine/inc/uf/utils/audio/metadata.h +++ b/engine/inc/uf/utils/audio/metadata.h @@ -51,6 +51,7 @@ namespace uf { struct { bool streamed = true; bool loop = false; + bool spatial = false; uint8_t buffers = 4; uint8_t loopMode = 0; } settings; diff --git a/engine/inc/uf/utils/io/file.h b/engine/inc/uf/utils/io/file.h index 8f7bc596..8dc1aba3 100644 --- a/engine/inc/uf/utils/io/file.h +++ b/engine/inc/uf/utils/io/file.h @@ -94,6 +94,8 @@ namespace uf { bool UF_API exists( const uf::stl::string& ); size_t UF_API mtime( const uf::stl::string& ); bool UF_API mkdir( const uf::stl::string& ); + uf::stl::string UF_API assetType( const uf::stl::string _filename ); uf::stl::string UF_API resolveURI( const uf::stl::string&, const uf::stl::string& = "" ); + uf::stl::string UF_API preferred( const uf::stl::string& filename ); } } \ No newline at end of file diff --git a/engine/lib/win64/zig/libalut.pdb b/engine/lib/win64/zig/libalut.pdb deleted file mode 100644 index 1ba425e91a977ca311cf57532485e27a03a785de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 249856 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 diff --git a/engine/src/engine/ext/audio/emitter/behavior.cpp b/engine/src/engine/ext/audio/emitter/behavior.cpp index 82d6472e..21356903 100644 --- a/engine/src/engine/ext/audio/emitter/behavior.cpp +++ b/engine/src/engine/ext/audio/emitter/behavior.cpp @@ -9,11 +9,12 @@ #include UF_BEHAVIOR_REGISTER_CPP(ext::SoundEmitterBehavior) -UF_BEHAVIOR_TRAITS_CPP(ext::SoundEmitterBehavior, ticks = true, renders = false, multithread = true) +UF_BEHAVIOR_TRAITS_CPP(ext::SoundEmitterBehavior, ticks = false, renders = false, multithread = true) #define this ((uf::Object*) &self) void ext::SoundEmitterBehavior::initialize( uf::Object& self ) { auto& metadata = this->getComponent(); auto& emitter = this->getComponent(); + //auto& emitter = this->getComponent(); auto& sounds = emitter.get(); auto& scene = uf::scene::getCurrentScene(); @@ -24,11 +25,10 @@ void ext::SoundEmitterBehavior::initialize( uf::Object& self ) { this->addHook( "sound:Stop.%UID%", [&](ext::json::Value& json){ uf::stl::string filename = json["filename"].as(); - for ( size_t i = 0; i < sounds.size(); ++i ) { - if ( sounds[i].getFilename() != filename ) continue; - sounds[i].destroy(); - sounds.erase(sounds.begin() + i); - metadata["sounds"].erase(i); + for ( auto& audio : sounds ) { + if ( audio.getFilename() != filename ) continue; + audio.stop(); + audio.destroy(); } }); this->addHook( "sound:Emit.%UID%", [&]( pod::PCM& waveform ){ @@ -46,7 +46,7 @@ void ext::SoundEmitterBehavior::initialize( uf::Object& self ) { if ( ext::json::isNull(json["loop"]) ) json["loop"] = metadata["audio"]["loop"]; if ( ext::json::isNull(json["streamed"]) ) json["streamed"] = metadata["audio"]["streamed"]; if ( ext::json::isNull(json["unique"]) ) json["unique"] = metadata["audio"]["unique"]; - metadata["sounds"].emplace_back(json); + if ( ext::json::isNull(json["spatial"]) ) json["spatial"] = metadata["audio"]["spatial"]; uf::stl::string filename = json["filename"].as(); bool unique = json["unique"].as(); @@ -62,6 +62,7 @@ void ext::SoundEmitterBehavior::initialize( uf::Object& self ) { if ( json["rolloffFactor"].is() ) audio.setRolloffFactor(json["rolloffFactor"].as()); if ( json["maxDistance"].is() ) audio.setMaxDistance(json["maxDistance"].as()); if ( json["loop"].is() ) audio.loop(json["loop"].as()); + if ( json["spatial"].is() ) audio.setSpatial(json["spatial"].as()); float volume = 1.0f; if ( json["volume"].is() ) volume = json["volume"].as(); @@ -90,6 +91,7 @@ void ext::SoundEmitterBehavior::initialize( uf::Object& self ) { }); } void ext::SoundEmitterBehavior::tick( uf::Object& self ) { +/* auto& transform = this->getComponent>(); auto flatten = uf::transform::flatten( transform ); @@ -101,18 +103,24 @@ void ext::SoundEmitterBehavior::tick( uf::Object& self ) { auto& audio = sounds[i]; auto& json = metadata["sounds"][i]; - if ( audio.playing() ) audio.update(); + // ObjectBehavior already handles it + // if ( audio.playing() ) audio.update(); + // could probably have ObjectBehavior instead handle updating the spatial but there's no way to signal it at the moment if ( json["spatial"].as() && audio.playing() ) { audio.setPosition( flatten.position ); audio.setOrientation( flatten.orientation ); } - if ( audio.loops() && !audio.playing() ) { + if ( !audio.playing() ) { audio.destroy(); sounds.erase(sounds.begin() + i); metadata["sounds"].erase(i); + --i; } } + + emitter.cleanup(); +*/ } void ext::SoundEmitterBehavior::render( uf::Object& self ){} diff --git a/engine/src/engine/ext/ext.cpp b/engine/src/engine/ext/ext.cpp index b33f316a..867351b4 100644 --- a/engine/src/engine/ext/ext.cpp +++ b/engine/src/engine/ext/ext.cpp @@ -198,6 +198,7 @@ void UF_API uf::load( ext::json::Value& json ) { auto& configEngineAudioJson = json["engine"]["audio"]; uf::audio::muted = configEngineAudioJson["mute"].as( uf::audio::muted ); + uf::audio::asyncUpdate = configEngineAudioJson["async update"].as( uf::audio::asyncUpdate ); uf::audio::streamsByDefault = configEngineAudioJson["streams by default"].as( uf::audio::streamsByDefault ); uf::audio::bufferSize = configEngineAudioJson["buffers"]["size"].as( uf::audio::bufferSize ); uf::audio::buffers = configEngineAudioJson["buffers"]["count"].as( uf::audio::buffers ); diff --git a/engine/src/engine/object/behavior.cpp b/engine/src/engine/object/behavior.cpp index aab75877..9026b6ac 100644 --- a/engine/src/engine/object/behavior.cpp +++ b/engine/src/engine/object/behavior.cpp @@ -234,18 +234,22 @@ void uf::ObjectBehavior::destroy( uf::Object& self ) { #endif } void uf::ObjectBehavior::tick( uf::Object& self ) { + auto& transform = this->getComponent>(); + auto flattened = uf::transform::flatten( transform ); // update audios if ( this->hasComponent() ) { auto& audio = this->getComponent(); - audio.update(); + audio.update( flattened.position, flattened.orientation ); } if ( this->hasComponent() ) { auto& audio = this->getComponent(); - audio.update(); + audio.update( flattened.position, flattened.orientation ); + audio.cleanup(); } if ( this->hasComponent() ) { auto& audio = this->getComponent(); - audio.update(); + audio.update( flattened.position, flattened.orientation ); + audio.cleanup(); } if ( this->hasComponent() ) { auto& graph = this->getComponent(); diff --git a/engine/src/ext/freetype/freetype.cpp b/engine/src/ext/freetype/freetype.cpp index f0ec31ee..94143d54 100644 --- a/engine/src/ext/freetype/freetype.cpp +++ b/engine/src/ext/freetype/freetype.cpp @@ -4,7 +4,7 @@ namespace { unsigned long first_codepoint(const std::u8string& str) { - if (str.empty()) throw std::runtime_error("Empty string"); + if (str.empty()) UF_EXCEPTION("Empty string"); const unsigned char* bytes = reinterpret_cast(str.data()); unsigned char b0 = bytes[0]; diff --git a/engine/src/utils/audio/audio.cpp b/engine/src/utils/audio/audio.cpp index 8c950b3f..1e85e541 100644 --- a/engine/src/utils/audio/audio.cpp +++ b/engine/src/utils/audio/audio.cpp @@ -1,5 +1,6 @@ #include #include +#include #if UF_USE_OPENAL #include @@ -11,11 +12,19 @@ bool uf::audio::muted = true; #endif +bool uf::audio::asyncUpdate = false; bool uf::audio::streamsByDefault = true; uint8_t uf::audio::buffers = 4; size_t uf::audio::bufferSize = 1024 * 16; uf::Audio uf::audio::null; +// to-do: make this a global setting +#if UF_ENV_DREAMCAST +#define UF_AUDIO_ASYNC 0 +#else +#define UF_AUDIO_ASYNC 0 +#endif + #if UF_AUDIO_MAPPED_VOLUMES uf::stl::unordered_map uf::audio::volumes; #else @@ -38,6 +47,13 @@ bool uf::Audio::playing() const { return false; #endif } +bool uf::Audio::played() const { +#if UF_USE_OPENAL + return this->getDuration() > 0 && this->getTime() >= this->getDuration(); +#else + return false; +#endif +} void uf::Audio::open( const uf::stl::string& filename ) { this->open( filename, uf::audio::streamsByDefault ); @@ -82,9 +98,20 @@ void uf::Audio::stream( const pod::PCM& buffer ) { void uf::Audio::update() { #if UF_USE_OPENAL if ( !this->m_metadata ) return; - ext::al::update( *this->m_metadata ); + if ( !this->m_metadata->settings.streamed ) return; + + if ( uf::audio::asyncUpdate ) uf::thread::queue( uf::thread::fetchWorker(), [&]{ + ext::al::update( *this->m_metadata ); + }); else ext::al::update( *this->m_metadata ); #endif } +void uf::Audio::update( const pod::Vector3f& position, const pod::Quaternion<>& orientation ) { + if ( this->spatial() ) { + this->setPosition( position ); + this->setOrientation( orientation ); + } + this->update(); +} void uf::Audio::destroy() { #if UF_USE_OPENAL if ( !this->m_metadata ) return; @@ -129,6 +156,19 @@ bool uf::Audio::loops() const { #endif } +bool uf::Audio::spatial() const { +#if UF_USE_OPENAL + if ( !this->m_metadata ) return false; + return this->m_metadata->settings.spatial; +#endif +} +void uf::Audio::setSpatial( bool state ) { +#if UF_USE_OPENAL + if ( !this->m_metadata ) return; + this->m_metadata->settings.spatial = state; +#endif +} + float uf::Audio::getTime() const { #if UF_USE_OPENAL if ( !this->m_metadata ) return 0; diff --git a/engine/src/utils/audio/emitter.cpp b/engine/src/utils/audio/emitter.cpp index 7666d2f3..0ea0c897 100644 --- a/engine/src/utils/audio/emitter.cpp +++ b/engine/src/utils/audio/emitter.cpp @@ -48,11 +48,22 @@ const uf::AudioEmitter::container_t& uf::AudioEmitter::get() const { void uf::AudioEmitter::update() { for ( auto& audio : this->m_container ) if ( audio.playing() ) audio.update(); } +void uf::AudioEmitter::update( const pod::Vector3f& position, const pod::Quaternion<>& orientation ) { + for ( auto& audio : this->m_container ) { + if ( audio.playing() ) audio.update( position, orientation ); + } +} void uf::AudioEmitter::cleanup( bool purge ) { - for ( size_t i = 0; i < this->m_container.size(); ++i ) { - if ( !purge && this->m_container[i].playing() ) continue; - this->m_container[i].destroy(); - this->m_container.erase(this->m_container.begin() + i); + for ( auto it = this->m_container.begin(); it != this->m_container.end(); ) { + auto& audio = *it; + if ( purge || audio.played() ) { + audio.stop(); + audio.destroy(); + // because cleanup might only happen on nonplaying audio (for some reason) we're erasing here instead of just clearing the container + it = this->m_container.erase(it); + } else { + ++it; + } } } @@ -104,10 +115,15 @@ void uf::MappedAudioEmitter::update() { pair.second.update(); } } +void uf::MappedAudioEmitter::update( const pod::Vector3f& position, const pod::Quaternion<>& orientation ) { + for ( auto& pair : this->m_container ) { + if ( pair.second.playing() ) pair.second.update( position, orientation ); + } +} void uf::MappedAudioEmitter::cleanup( bool purge ) { for ( auto it = this->m_container.begin(); it != this->m_container.end(); ) { auto& pair = *it; - if (purge || !pair.second.playing()) { + if ( purge || pair.second.played() ) { pair.second.stop(); pair.second.destroy(); // because cleanup might only happen on nonplaying audio (for some reason) we're erasing here instead of just clearing the container diff --git a/engine/src/utils/image/image.cpp b/engine/src/utils/image/image.cpp index 9caa82a0..b44ad1e0 100644 --- a/engine/src/utils/image/image.cpp +++ b/engine/src/utils/image/image.cpp @@ -79,18 +79,14 @@ void uf::Image::setFilename( const uf::stl::string& filename ) { ((((uint16_t)r & 0xf8) << 8) | (((uint16_t) g & 0xfc) << 3) | ((uint16_t) b >> 3)) // from file -bool uf::Image::open( const uf::stl::string& filename, bool flip ) { -#if UF_USE_OPENGL_GLDC - uf::stl::string extension = uf::io::extension(filename); - if ( extension != "dtex" ) { - uf::stl::string dtex = uf::string::replace( filename, ".png", ".dtex" ); - if ( uf::io::exists(dtex) ) return this->open(dtex, flip); - - UF_MSG_WARNING("non-dtex loading is highly discouraged on this platform: {}", filename); - // return false; - } -#endif +bool uf::Image::open( const uf::stl::string& _filename, bool flip ) { + // to-do: use preferred + uf::stl::string filename = uf::io::preferred( _filename ); if ( !uf::io::exists(filename) ) UF_EXCEPTION("IO error: file does not exist: {}", filename); +#if UF_USE_OPENGL_GLDC + auto extension = uf::io::extension( filename ); + if ( extension != "dtex" ) UF_MSG_WARNING("non-dtex loading is highly discouraged on this platform: {}", filename); +#endif this->m_filename = filename; this->m_pixels.clear(); diff --git a/engine/src/utils/io/file.cpp b/engine/src/utils/io/file.cpp index c79227d4..272d5aea 100644 --- a/engine/src/utils/io/file.cpp +++ b/engine/src/utils/io/file.cpp @@ -279,36 +279,83 @@ bool uf::io::mkdir( const uf::stl::string& _filename ) { return status != -1; #endif } + +uf::stl::string uf::io::assetType( const uf::stl::string _filename ) { + // remove .gz + uf::stl::string filename = uf::string::replace( _filename, ".gz", "" ); + + // grab filename and extension + uf::stl::string basename = uf::io::filename( filename ); + uf::stl::string extension = uf::io::extension( filename ); + + // a map does allocations, an if ladder is easy + if ( basename == "graph.json" ) return "model"; + if ( basename == "scene.json" ) return "scene"; + if ( extension == "json" ) return "entity"; + if ( extension == "png" ) return "texture"; + if ( extension == "glb" ) return "model"; + if ( extension == "gltf" ) return "model"; + if ( extension == "graph" ) return "model"; + if ( extension == "ogg" ) return "audio"; + if ( extension == "wav" ) return "audio"; + if ( extension == "spv" ) return "shader"; + if ( extension == "lua" ) return "script"; + return ""; +} + uf::stl::string uf::io::resolveURI( const uf::stl::string& filename, const uf::stl::string& _root ) { uf::stl::string root = _root; if ( filename.substr(0,8) == "https://" ) return filename; const uf::stl::string extension = uf::io::extension(filename); // just sanitize if ( filename.find(uf::io::root) == 0 ) { - return uf::io::sanitize( uf::io::filename( filename ), uf::io::directory( filename ) ); + return uf::io::preferred( uf::io::sanitize( uf::io::filename( filename ), uf::io::directory( filename ) ) ); } if ( filename.find("%root%") == 0 ) { const uf::stl::string f = uf::string::replace( filename, "%root%", uf::io::root ); - return uf::io::sanitize( uf::io::filename( f ), uf::io::directory( f ) ); + return uf::io::preferred( uf::io::sanitize( uf::io::filename( f ), uf::io::directory( f ) ) ); } // if the filename contains an absolute path or if no root is provided if ( filename[0] == '/' || root == "" ) { - const uf::stl::string basename = uf::io::filename( filename ); - const uf::stl::string extensions = uf::io::extension( filename, -1 ); + const uf::stl::string assetType = uf::io::assetType( filename ); + if ( filename[0] == '/' && filename[1] == '/' ) root = uf::io::root; - // else if ( uf::io::extension(filename, -1) == "graph.json" ) root = uf::io::root + "/models/"; - // else if ( uf::io::extension(filename, -1) == "scene.json" ) root = uf::io::root + "/scenes/"; - else if ( basename == "graph.json" || basename == "graph.json.gz" ) root = uf::io::root + "/models/"; - else if ( basename == "scene.json" || basename == "scene.json.gz" ) root = uf::io::root + "/scenes/"; - else if ( extension == "json" || extensions == "json.gz" ) root = uf::io::root + "/entities/"; - else if ( extension == "png" || extensions == "png.gz" ) root = uf::io::root + "/textures/"; - else if ( extension == "glb" || extensions == "glb.gz" ) root = uf::io::root + "/models/"; - else if ( extension == "gltf" || extensions == "gltf.gz" ) root = uf::io::root + "/models/"; - else if ( extension == "graph" || extensions == "graph.gz" ) root = uf::io::root + "/models/"; - else if ( extension == "ogg" || extensions == "ogg.gz" ) root = uf::io::root + "/audio/"; - else if ( extension == "wav" || extensions == "wav.gz" ) root = uf::io::root + "/audio/"; - else if ( extension == "spv" || extensions == "spv.gz" ) root = uf::io::root + "/shaders/"; - else if ( extension == "lua" || extensions == "lua.gz" ) root = uf::io::root + "/scripts/"; + else if ( assetType != "" ) { + if ( assetType == "model" ) root = uf::io::root + "/models/"; + else if ( assetType == "scene" ) root = uf::io::root + "/scenes/"; + else if ( assetType == "entity" ) root = uf::io::root + "/entities/"; + else if ( assetType == "texture" ) root = uf::io::root + "/textures/"; + else if ( assetType == "audio" ) root = uf::io::root + "/audio/"; + else if ( assetType == "shader" ) root = uf::io::root + "/shaders/"; + else if ( assetType == "script" ) root = uf::io::root + "/scripts/"; + + else root = uf::io::root + "/" + assetType + "/"; + } } - return uf::io::sanitize(filename, root); + return uf::io::preferred( uf::io::sanitize( filename, root ) ); +} + +// attempts to coerce files into a preferred one if it exists +uf::stl::string uf::io::preferred( const uf::stl::string& filename ) { + // remove .gz + auto extension = "." + uf::io::extension( uf::string::replace( filename, ".gz", "" ) ); + auto preferredExtension = extension; + // deduce asset type + auto assetType = uf::io::assetType( extension ); + + // to-do: make this config.json defineable +#if UF_ENV_DREAMCAST + if ( assetType == "texture" ) preferredExtension = ".dtex"; + else if ( assetType == "audio" ) preferredExtension = ".ogg"; +#else + if ( assetType == "texture" ) preferredExtension = ".png"; + else if ( assetType == "audio" ) preferredExtension = ".ogg"; +#endif + + // no change + if ( extension == preferredExtension ) return filename; + // create preferred path + uf::stl::string preferredPath = uf::string::replace( filename, extension, preferredExtension ); + // pick it if exists + return uf::io::exists( preferredPath ) ? preferredPath : filename; } \ No newline at end of file diff --git a/makefiles/dreamcast.gcc.make b/makefiles/dreamcast.gcc.make index 04ad39fc..ffebb3fb 100644 --- a/makefiles/dreamcast.gcc.make +++ b/makefiles/dreamcast.gcc.make @@ -3,9 +3,12 @@ CDIR = CC = gcc CXX = $(KOS_CCPLUS) RENDERER = opengl -TARGET_EXTENSION = elf OPTIMIZATIONS = -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -fstrict-aliasing -ffast-math -fno-unroll-all-loops -fno-optimize-sibling-calls -fschedule-insns2 -fomit-frame-pointer -DUF_NO_EXCEPTIONS -fno-exceptions -g # -flto WARNINGS = -Wall -Wno-unknown-pragmas -Wno-unused-function -Wno-unused-variable -Wno-switch -Wno-reorder -Wno-sign-compare -Wno-unused-but-set-variable -Wno-ignored-attributes -Wno-narrowing -Wno-misleading-indentation -Wno-attributes -Wno-conversion-null -FLAGS += $(KOS_CPPFLAGS) -m4-single -std=c++17 $(OPTIMIZATIONS) $(WARNINGS) -fdiagnostics-color=always +FLAGS += $(KOS_CPPFLAGS) -m4-single -std=c++2b $(OPTIMIZATIONS) $(WARNINGS) -fdiagnostics-color=always + +TARGET_EXTENSION = .elf + +# KOS INCS += $(KOS_INC_PATHS) -I/opt/dreamcast/sh-elf/sh-elf/include LIBS += $(KOS_LIB_PATHS) -L/opt/dreamcast/sh-elf/sh-elf/lib \ No newline at end of file diff --git a/makefiles/linux.clang.make b/makefiles/linux.clang.make index 8c6f8540..b98d4975 100644 --- a/makefiles/linux.clang.make +++ b/makefiles/linux.clang.make @@ -7,5 +7,6 @@ WARNINGS = -Wall -Wno-pointer-arith -Wno-unused-function -Wno-unused-variable SANITIZE = -fsanitize=address # -fuse-ld=lld -fno-omit-frame-pointer FLAGS += -std=c++2b $(OPTIMIZATIONS) $(WARNINGS) $(SANITIZE) -fcolor-diagnostics -fansi-escape-codes -LIB_EXTENSION = so -LIB_EXTENSION_A = \ No newline at end of file +TARGET_EXTENSION = +DLIB_EXTENSION = .so +SLIB_EXTENSION = \ No newline at end of file diff --git a/makefiles/linux.gcc.make b/makefiles/linux.gcc.make index f1b203d5..e045c4b2 100644 --- a/makefiles/linux.gcc.make +++ b/makefiles/linux.gcc.make @@ -6,5 +6,6 @@ OPTIMIZATIONS = -O3 -fstrict-aliasing -DUF_NO_EXCEPTIONS WARNINGS = -Wall -Wno-attributes -Wno-dangling-reference -Wno-unknown-pragmas -Wno-unused-function -Wno-unused-variable -Wno-switch -Wno-reorder -Wno-sign-compare -Wno-unused-but-set-variable -Wno-ignored-attributes -Wno-narrowing -Wno-misleading-indentation FLAGS += -std=c++2b $(OPTIMIZATIONS) $(WARNINGS) -fdiagnostics-color=always -LIB_EXTENSION = so -LIB_EXTENSION_A = \ No newline at end of file +TARGET_EXTENSION = +DLIB_EXTENSION = .so +SLIB_EXTENSION = \ No newline at end of file diff --git a/makefiles/win64.clang.make b/makefiles/win64.clang.make index 2ebcab8f..3ef2fede 100644 --- a/makefiles/win64.clang.make +++ b/makefiles/win64.clang.make @@ -5,4 +5,12 @@ CXX = clang++ OPTIMIZATIONS = -O3 -fstrict-aliasing -DUF_NO_EXCEPTIONS # -flto # -march=native WARNINGS = -Wall -Wno-deprecated-literal-operator -Wno-pointer-arith -Wno-unused-function -Wno-unused-variable -Wno-switch -Wno-reorder-ctor -Wno-ignored-attributes -Wno-c++11-narrowing -Wno-unknown-pragmas -Wno-nullability-completeness -Wno-defaulted-function-deleted -Wno-mismatched-tags SANITIZE = -fsanitize=address # -fuse-ld=lld -fno-omit-frame-pointer -FLAGS += -std=c++2b $(OPTIMIZATIONS) $(WARNINGS) $(SANITIZE) -fcolor-diagnostics -fansi-escape-codes \ No newline at end of file +FLAGS += -std=c++2b $(OPTIMIZATIONS) $(WARNINGS) $(SANITIZE) -fcolor-diagnostics -fansi-escape-codes + +# MSYS2 +INCS += -I/clang64/include/ +LIBS += -L/clang64/lib/ -L/mingw64/lib/ + +TARGET_EXTENSION = .exe +DLIB_EXTENSION = .dll +SLIB_EXTENSION = .a \ No newline at end of file diff --git a/makefiles/win64.gcc.make b/makefiles/win64.gcc.make index 8d3ec509..5033fd41 100644 --- a/makefiles/win64.gcc.make +++ b/makefiles/win64.gcc.make @@ -4,4 +4,13 @@ CC = gcc CXX = g++ OPTIMIZATIONS = -O3 -fstrict-aliasing -DUF_NO_EXCEPTIONS WARNINGS = -Wall -Wno-unknown-pragmas -Wno-unused-function -Wno-unused-variable -Wno-switch -Wno-reorder -Wno-sign-compare -Wno-unused-but-set-variable -Wno-ignored-attributes -Wno-narrowing -Wno-misleading-indentation -FLAGS += -std=c++2b $(OPTIMIZATIONS) $(WARNINGS) -fdiagnostics-color=always \ No newline at end of file + +FLAGS += -std=c++2b $(OPTIMIZATIONS) $(WARNINGS) -fdiagnostics-color=always + +# MSYS2 +INCS += -I/mingw64/include/ +LIBS += -I/mingw64/lib/ + +TARGET_EXTENSION = .exe +DLIB_EXTENSION = .dll +SLIB_EXTENSION = .a \ No newline at end of file diff --git a/makefiles/win64.gcc7.make b/makefiles/win64.gcc7.make deleted file mode 100644 index 0ea2f2ef..00000000 --- a/makefiles/win64.gcc7.make +++ /dev/null @@ -1,5 +0,0 @@ -ARCH = win64 -CDIR = /opt/mingw-w64/x86_64-7.2.0-posix-seh-rt_v5-rev1/mingw64/bin/ -CC = gcc7 -CXX = g++ -FLAGS += -Wno-unknown-pragmas -std=c++17 -Og -g -fdiagnostics-color=always -Wall -Wno-conversion-null -Wno-unused-function -Wno-unused-variable -Wno-switch -Wno-reorder -Wno-sign-compare -Wno-unused-but-set-variable -Wno-ignored-attributes -Wno-narrowing -Wno-misleading-indentation \ No newline at end of file diff --git a/makefiles/win64.tdm-gcc.make b/makefiles/win64.tdm-gcc.make deleted file mode 100644 index 77420d9d..00000000 --- a/makefiles/win64.tdm-gcc.make +++ /dev/null @@ -1,8 +0,0 @@ -ARCH = win64 -CDIR = /tdm-gcc/bin/ -CC = gcc -CXX = g++ -OPTIMIZATIONS = -O3 -fstrict-aliasing # -flto -WARNINGS = -Wall -Wno-pointer-arith -Wno-unknown-pragmas -Wno-unused-function -Wno-unused-variable -Wno-switch -Wno-reorder -Wno-sign-compare -Wno-unused-but-set-variable -Wno-ignored-attributes -Wno-narrowing -Wno-misleading-indentation -FLAGS = -std=c++17 $(OPTIMIZATIONS) $(WARNINGS) -fdiagnostics-color=always -PREFIX = $(ARCH).tdm-gcc \ No newline at end of file