#include "config.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "alc/context.h" #include "alc/device.h" #include "alnumeric.h" #include "alspan.h" #include "auxeffectslot.h" #include "buffer.h" #include "core/except.h" #include "core/logging.h" #include "core/voice.h" #include "direct_defs.h" #include "effect.h" #include "filter.h" #include "fmt/core.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "source.h" /* Declared here to prevent compilers from thinking it should be inlined, which * GCC warns about increasing code size. */ DebugGroup::~DebugGroup() = default; namespace { using namespace std::string_view_literals; static_assert(DebugSeverityBase+DebugSeverityCount <= 32, "Too many debug bits"); template constexpr auto make_array_sequence(std::integer_sequence) { return std::array{Vals...}; } template constexpr auto make_array_sequence() { return make_array_sequence(std::make_integer_sequence{}); } constexpr auto GetDebugSource(ALenum source) noexcept -> std::optional { switch(source) { case AL_DEBUG_SOURCE_API_EXT: return DebugSource::API; case AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT: return DebugSource::System; case AL_DEBUG_SOURCE_THIRD_PARTY_EXT: return DebugSource::ThirdParty; case AL_DEBUG_SOURCE_APPLICATION_EXT: return DebugSource::Application; case AL_DEBUG_SOURCE_OTHER_EXT: return DebugSource::Other; } return std::nullopt; } constexpr auto GetDebugType(ALenum type) noexcept -> std::optional { switch(type) { case AL_DEBUG_TYPE_ERROR_EXT: return DebugType::Error; case AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT: return DebugType::DeprecatedBehavior; case AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT: return DebugType::UndefinedBehavior; case AL_DEBUG_TYPE_PORTABILITY_EXT: return DebugType::Portability; case AL_DEBUG_TYPE_PERFORMANCE_EXT: return DebugType::Performance; case AL_DEBUG_TYPE_MARKER_EXT: return DebugType::Marker; case AL_DEBUG_TYPE_PUSH_GROUP_EXT: return DebugType::PushGroup; case AL_DEBUG_TYPE_POP_GROUP_EXT: return DebugType::PopGroup; case AL_DEBUG_TYPE_OTHER_EXT: return DebugType::Other; } return std::nullopt; } constexpr auto GetDebugSeverity(ALenum severity) noexcept -> std::optional { switch(severity) { case AL_DEBUG_SEVERITY_HIGH_EXT: return DebugSeverity::High; case AL_DEBUG_SEVERITY_MEDIUM_EXT: return DebugSeverity::Medium; case AL_DEBUG_SEVERITY_LOW_EXT: return DebugSeverity::Low; case AL_DEBUG_SEVERITY_NOTIFICATION_EXT: return DebugSeverity::Notification; } return std::nullopt; } constexpr auto GetDebugSourceEnum(DebugSource source) -> ALenum { switch(source) { case DebugSource::API: return AL_DEBUG_SOURCE_API_EXT; case DebugSource::System: return AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT; case DebugSource::ThirdParty: return AL_DEBUG_SOURCE_THIRD_PARTY_EXT; case DebugSource::Application: return AL_DEBUG_SOURCE_APPLICATION_EXT; case DebugSource::Other: return AL_DEBUG_SOURCE_OTHER_EXT; } throw std::runtime_error{fmt::format("Unexpected debug source value: {}", int{al::to_underlying(source)})}; } constexpr auto GetDebugTypeEnum(DebugType type) -> ALenum { switch(type) { case DebugType::Error: return AL_DEBUG_TYPE_ERROR_EXT; case DebugType::DeprecatedBehavior: return AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT; case DebugType::UndefinedBehavior: return AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT; case DebugType::Portability: return AL_DEBUG_TYPE_PORTABILITY_EXT; case DebugType::Performance: return AL_DEBUG_TYPE_PERFORMANCE_EXT; case DebugType::Marker: return AL_DEBUG_TYPE_MARKER_EXT; case DebugType::PushGroup: return AL_DEBUG_TYPE_PUSH_GROUP_EXT; case DebugType::PopGroup: return AL_DEBUG_TYPE_POP_GROUP_EXT; case DebugType::Other: return AL_DEBUG_TYPE_OTHER_EXT; } throw std::runtime_error{fmt::format("Unexpected debug type value: {}", int{al::to_underlying(type)})}; } constexpr auto GetDebugSeverityEnum(DebugSeverity severity) -> ALenum { switch(severity) { case DebugSeverity::High: return AL_DEBUG_SEVERITY_HIGH_EXT; case DebugSeverity::Medium: return AL_DEBUG_SEVERITY_MEDIUM_EXT; case DebugSeverity::Low: return AL_DEBUG_SEVERITY_LOW_EXT; case DebugSeverity::Notification: return AL_DEBUG_SEVERITY_NOTIFICATION_EXT; } throw std::runtime_error{fmt::format("Unexpected debug severity value: {}", int{al::to_underlying(severity)})}; } constexpr auto GetDebugSourceName(DebugSource source) noexcept -> std::string_view { switch(source) { case DebugSource::API: return "API"sv; case DebugSource::System: return "Audio System"sv; case DebugSource::ThirdParty: return "Third Party"sv; case DebugSource::Application: return "Application"sv; case DebugSource::Other: return "Other"sv; } return ""sv; } constexpr auto GetDebugTypeName(DebugType type) noexcept -> std::string_view { switch(type) { case DebugType::Error: return "Error"sv; case DebugType::DeprecatedBehavior: return "Deprecated Behavior"sv; case DebugType::UndefinedBehavior: return "Undefined Behavior"sv; case DebugType::Portability: return "Portability"sv; case DebugType::Performance: return "Performance"sv; case DebugType::Marker: return "Marker"sv; case DebugType::PushGroup: return "Push Group"sv; case DebugType::PopGroup: return "Pop Group"sv; case DebugType::Other: return "Other"sv; } return ""sv; } constexpr auto GetDebugSeverityName(DebugSeverity severity) noexcept -> std::string_view { switch(severity) { case DebugSeverity::High: return "High"sv; case DebugSeverity::Medium: return "Medium"sv; case DebugSeverity::Low: return "Low"sv; case DebugSeverity::Notification: return "Notification"sv; } return ""sv; } } // namespace void ALCcontext::sendDebugMessage(std::unique_lock &debuglock, DebugSource source, DebugType type, ALuint id, DebugSeverity severity, std::string_view message) { if(!mDebugEnabled.load(std::memory_order_relaxed)) UNLIKELY return; if(message.length() >= MaxDebugMessageLength) UNLIKELY { ERR("Debug message too long ({} >= {}):\n-> {}", message.length(), MaxDebugMessageLength, message); return; } DebugGroup &debug = mDebugGroups.back(); const uint64_t idfilter{(1_u64 << (DebugSourceBase+al::to_underlying(source))) | (1_u64 << (DebugTypeBase+al::to_underlying(type))) | (uint64_t{id} << 32)}; auto iditer = std::lower_bound(debug.mIdFilters.cbegin(), debug.mIdFilters.cend(), idfilter); if(iditer != debug.mIdFilters.cend() && *iditer == idfilter) return; const uint filter{(1u << (DebugSourceBase+al::to_underlying(source))) | (1u << (DebugTypeBase+al::to_underlying(type))) | (1u << (DebugSeverityBase+al::to_underlying(severity)))}; auto iter = std::lower_bound(debug.mFilters.cbegin(), debug.mFilters.cend(), filter); if(iter != debug.mFilters.cend() && *iter == filter) return; if(mDebugCb) { auto callback = mDebugCb; auto param = mDebugParam; debuglock.unlock(); callback(GetDebugSourceEnum(source), GetDebugTypeEnum(type), id, GetDebugSeverityEnum(severity), static_cast(message.length()), message.data(), param); } else { if(mDebugLog.size() < MaxDebugLoggedMessages) mDebugLog.emplace_back(source, type, id, severity, message); else UNLIKELY ERR("Debug message log overflow. Lost message:\n" " Source: {}\n" " Type: {}\n" " ID: {}\n" " Severity: {}\n" " Message: \"{}\"", GetDebugSourceName(source), GetDebugTypeName(type), id, GetDebugSeverityName(severity), message); } } FORCE_ALIGN DECL_FUNCEXT2(void, alDebugMessageCallback,EXT, ALDEBUGPROCEXT,callback, void*,userParam) FORCE_ALIGN void AL_APIENTRY alDebugMessageCallbackDirectEXT(ALCcontext *context, ALDEBUGPROCEXT callback, void *userParam) noexcept { std::lock_guard debuglock{context->mDebugCbLock}; context->mDebugCb = callback; context->mDebugParam = userParam; } FORCE_ALIGN DECL_FUNCEXT6(void, alDebugMessageInsert,EXT, ALenum,source, ALenum,type, ALuint,id, ALenum,severity, ALsizei,length, const ALchar*,message) FORCE_ALIGN void AL_APIENTRY alDebugMessageInsertDirectEXT(ALCcontext *context, ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) noexcept try { if(!context->mContextFlags.test(ContextFlags::DebugBit)) return; if(!message) context->throw_error(AL_INVALID_VALUE, "Null message pointer"); auto msgview = (length < 0) ? std::string_view{message} : std::string_view{message, static_cast(length)}; if(msgview.size() >= MaxDebugMessageLength) context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", msgview.size(), MaxDebugMessageLength); auto dsource = GetDebugSource(source); if(!dsource) context->throw_error(AL_INVALID_ENUM, "Invalid debug source {:#04x}", as_unsigned(source)); if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application) context->throw_error(AL_INVALID_ENUM, "Debug source {:#04x} not allowed", as_unsigned(source)); auto dtype = GetDebugType(type); if(!dtype) context->throw_error(AL_INVALID_ENUM, "Invalid debug type {:#04x}", as_unsigned(type)); auto dseverity = GetDebugSeverity(severity); if(!dseverity) context->throw_error(AL_INVALID_ENUM, "Invalid debug severity {:#04x}", as_unsigned(severity)); context->debugMessage(*dsource, *dtype, id, *dseverity, msgview); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT6(void, alDebugMessageControl,EXT, ALenum,source, ALenum,type, ALenum,severity, ALsizei,count, const ALuint*,ids, ALboolean,enable) FORCE_ALIGN void AL_APIENTRY alDebugMessageControlDirectEXT(ALCcontext *context, ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) noexcept try { if(count > 0) { if(!ids) context->throw_error(AL_INVALID_VALUE, "IDs is null with non-0 count"); if(source == AL_DONT_CARE_EXT) context->throw_error(AL_INVALID_OPERATION, "Debug source cannot be AL_DONT_CARE_EXT with IDs"); if(type == AL_DONT_CARE_EXT) context->throw_error(AL_INVALID_OPERATION, "Debug type cannot be AL_DONT_CARE_EXT with IDs"); if(severity != AL_DONT_CARE_EXT) context->throw_error(AL_INVALID_OPERATION, "Debug severity must be AL_DONT_CARE_EXT with IDs"); } if(enable != AL_TRUE && enable != AL_FALSE) context->throw_error(AL_INVALID_ENUM, "Invalid debug enable {}", enable); static constexpr size_t ElemCount{DebugSourceCount + DebugTypeCount + DebugSeverityCount}; static constexpr auto Values = make_array_sequence(); auto srcIndices = al::span{Values}.subspan(DebugSourceBase,DebugSourceCount); if(source != AL_DONT_CARE_EXT) { auto dsource = GetDebugSource(source); if(!dsource) context->throw_error(AL_INVALID_ENUM, "Invalid debug source {:#04x}", as_unsigned(source)); srcIndices = srcIndices.subspan(al::to_underlying(*dsource), 1); } auto typeIndices = al::span{Values}.subspan(DebugTypeBase,DebugTypeCount); if(type != AL_DONT_CARE_EXT) { auto dtype = GetDebugType(type); if(!dtype) context->throw_error(AL_INVALID_ENUM, "Invalid debug type {:#04x}", as_unsigned(type)); typeIndices = typeIndices.subspan(al::to_underlying(*dtype), 1); } auto svrIndices = al::span{Values}.subspan(DebugSeverityBase,DebugSeverityCount); if(severity != AL_DONT_CARE_EXT) { auto dseverity = GetDebugSeverity(severity); if(!dseverity) context->throw_error(AL_INVALID_ENUM, "Invalid debug severity {:#04x}", as_unsigned(severity)); svrIndices = svrIndices.subspan(al::to_underlying(*dseverity), 1); } std::lock_guard debuglock{context->mDebugCbLock}; DebugGroup &debug = context->mDebugGroups.back(); if(count > 0) { const uint filterbase{(1u<(count)}) { const uint64_t filter{filterbase | (uint64_t{id} << 32)}; auto iter = std::lower_bound(debug.mIdFilters.cbegin(), debug.mIdFilters.cend(), filter); if(!enable && (iter == debug.mIdFilters.cend() || *iter != filter)) debug.mIdFilters.insert(iter, filter); else if(enable && iter != debug.mIdFilters.cend() && *iter == filter) debug.mIdFilters.erase(iter); } } else { auto apply_filter = [enable,&debug](const uint filter) { auto iter = std::lower_bound(debug.mFilters.cbegin(), debug.mFilters.cend(), filter); if(!enable && (iter == debug.mFilters.cend() || *iter != filter)) debug.mFilters.insert(iter, filter); else if(enable && iter != debug.mFilters.cend() && *iter == filter) debug.mFilters.erase(iter); }; auto apply_severity = [apply_filter,svrIndices](const uint filter) { std::for_each(svrIndices.cbegin(), svrIndices.cend(), [apply_filter,filter](const uint idx){ apply_filter(filter | (1<= MaxDebugMessageLength) context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", newlen, MaxDebugMessageLength); length = static_cast(newlen); } else if(length >= MaxDebugMessageLength) context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", length, MaxDebugMessageLength); auto dsource = GetDebugSource(source); if(!dsource) context->throw_error(AL_INVALID_ENUM, "Invalid debug source {:#04x}", as_unsigned(source)); if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application) context->throw_error(AL_INVALID_ENUM, "Debug source {:#04x} not allowed", as_unsigned(source)); std::unique_lock debuglock{context->mDebugCbLock}; if(context->mDebugGroups.size() >= MaxDebugGroupDepth) context->throw_error(AL_STACK_OVERFLOW_EXT, "Pushing too many debug groups"); context->mDebugGroups.emplace_back(*dsource, id, std::string_view{message, static_cast(length)}); auto &oldback = *(context->mDebugGroups.end()-2); auto &newback = context->mDebugGroups.back(); newback.mFilters = oldback.mFilters; newback.mIdFilters = oldback.mIdFilters; if(context->mContextFlags.test(ContextFlags::DebugBit)) context->sendDebugMessage(debuglock, newback.mSource, DebugType::PushGroup, newback.mId, DebugSeverity::Notification, newback.mMessage); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT(void, alPopDebugGroup,EXT) FORCE_ALIGN void AL_APIENTRY alPopDebugGroupDirectEXT(ALCcontext *context) noexcept try { std::unique_lock debuglock{context->mDebugCbLock}; if(context->mDebugGroups.size() <= 1) context->throw_error(AL_STACK_UNDERFLOW_EXT, "Attempting to pop the default debug group"); DebugGroup &debug = context->mDebugGroups.back(); const auto source = debug.mSource; const auto id = debug.mId; std::string message{std::move(debug.mMessage)}; context->mDebugGroups.pop_back(); if(context->mContextFlags.test(ContextFlags::DebugBit)) context->sendDebugMessage(debuglock, source, DebugType::PopGroup, id, DebugSeverity::Notification, message); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT8(ALuint, alGetDebugMessageLog,EXT, ALuint,count, ALsizei,logBufSize, ALenum*,sources, ALenum*,types, ALuint*,ids, ALenum*,severities, ALsizei*,lengths, ALchar*,logBuf) FORCE_ALIGN ALuint AL_APIENTRY alGetDebugMessageLogDirectEXT(ALCcontext *context, ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) noexcept try { if(logBuf && logBufSize < 0) context->throw_error(AL_INVALID_VALUE, "Negative debug log buffer size"); const auto sourcesSpan = al::span{sources, sources ? count : 0u}; const auto typesSpan = al::span{types, types ? count : 0u}; const auto idsSpan = al::span{ids, ids ? count : 0u}; const auto severitiesSpan = al::span{severities, severities ? count : 0u}; const auto lengthsSpan = al::span{lengths, lengths ? count : 0u}; const auto logSpan = al::span{logBuf, logBuf ? static_cast(logBufSize) : 0u}; auto sourceiter = sourcesSpan.begin(); auto typeiter = typesSpan.begin(); auto iditer = idsSpan.begin(); auto severityiter = severitiesSpan.begin(); auto lengthiter = lengthsSpan.begin(); auto logiter = logSpan.begin(); auto debuglock = std::lock_guard{context->mDebugCbLock}; for(ALuint i{0};i < count;++i) { if(context->mDebugLog.empty()) return i; auto &entry = context->mDebugLog.front(); const auto tocopy = size_t{entry.mMessage.size() + 1}; if(al::to_address(logiter) != nullptr) { if(static_cast(std::distance(logiter, logSpan.end())) < tocopy) return i; logiter = std::copy(entry.mMessage.cbegin(), entry.mMessage.cend(), logiter); *(logiter++) = '\0'; } if(al::to_address(sourceiter) != nullptr) *(sourceiter++) = GetDebugSourceEnum(entry.mSource); if(al::to_address(typeiter) != nullptr) *(typeiter++) = GetDebugTypeEnum(entry.mType); if(al::to_address(iditer) != nullptr) *(iditer++) = entry.mId; if(al::to_address(severityiter) != nullptr) *(severityiter++) = GetDebugSeverityEnum(entry.mSeverity); if(al::to_address(lengthiter) != nullptr) *(lengthiter++) = static_cast(tocopy); context->mDebugLog.pop_front(); } return count; } catch(al::base_exception&) { return 0; } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); return 0; } FORCE_ALIGN DECL_FUNCEXT4(void, alObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,length, const ALchar*,label) FORCE_ALIGN void AL_APIENTRY alObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, ALuint name, ALsizei length, const ALchar *label) noexcept try { if(!label && length != 0) context->throw_error(AL_INVALID_VALUE, "Null label pointer"); auto objname = (length < 0) ? std::string_view{label} : std::string_view{label, static_cast(length)}; if(objname.size() >= MaxObjectLabelLength) context->throw_error(AL_INVALID_VALUE, "Object label length too long ({} >= {})", objname.size(), MaxObjectLabelLength); switch(identifier) { case AL_SOURCE_EXT: ALsource::SetName(context, name, objname); return; case AL_BUFFER: ALbuffer::SetName(context, name, objname); return; case AL_FILTER_EXT: ALfilter::SetName(context, name, objname); return; case AL_EFFECT_EXT: ALeffect::SetName(context, name, objname); return; case AL_AUXILIARY_EFFECT_SLOT_EXT: ALeffectslot::SetName(context, name, objname); return; } context->throw_error(AL_INVALID_ENUM, "Invalid name identifier {:#04x}", as_unsigned(identifier)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT5(void, alGetObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,bufSize, ALsizei*,length, ALchar*,label) FORCE_ALIGN void AL_APIENTRY alGetObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) noexcept try { if(bufSize < 0) context->throw_error(AL_INVALID_VALUE, "Negative label bufSize"); if(!label && !length) context->throw_error(AL_INVALID_VALUE, "Null length and label"); if(label && bufSize == 0) context->throw_error(AL_INVALID_VALUE, "Zero label bufSize"); const auto labelOut = al::span{label, label ? static_cast(bufSize) : 0u}; auto copy_name = [name,length,labelOut](std::unordered_map &names) { std::string_view objname; auto iter = names.find(name); if(iter != names.end()) objname = iter->second; if(labelOut.empty()) *length = static_cast(objname.size()); else { const size_t tocopy{std::min(objname.size(), labelOut.size()-1)}; auto oiter = std::copy_n(objname.cbegin(), tocopy, labelOut.begin()); *oiter = '\0'; if(length) *length = static_cast(tocopy); } }; if(identifier == AL_SOURCE_EXT) { std::lock_guard srclock{context->mSourceLock}; copy_name(context->mSourceNames); } else if(identifier == AL_BUFFER) { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; copy_name(device->mBufferNames); } else if(identifier == AL_FILTER_EXT) { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->FilterLock}; copy_name(device->mFilterNames); } else if(identifier == AL_EFFECT_EXT) { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->EffectLock}; copy_name(device->mEffectNames); } else if(identifier == AL_AUXILIARY_EFFECT_SLOT_EXT) { std::lock_guard slotlock{context->mEffectSlotLock}; copy_name(context->mEffectSlotNames); } else context->throw_error(AL_INVALID_ENUM, "Invalid name identifier {:#04x}", as_unsigned(identifier)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); }