diff --git a/Common/File/PathBrowser.cpp b/Common/File/PathBrowser.cpp index 5f29866799..40689c0c9f 100644 --- a/Common/File/PathBrowser.cpp +++ b/Common/File/PathBrowser.cpp @@ -111,12 +111,12 @@ bool LoadRemoteFileList(const Path &url, const std::string &userAgent, bool *can } PathBrowser::~PathBrowser() { - std::unique_lock guard(pendingLock_); - pendingCancel_ = true; - pendingStop_ = true; - pendingCond_.notify_all(); - guard.unlock(); - + { + std::unique_lock guard(pendingLock_); + pendingCancel_ = true; + pendingStop_ = true; + pendingCond_.notify_all(); + } if (pendingThread_.joinable()) { pendingThread_.join(); } diff --git a/Common/File/VFS/VFS.cpp b/Common/File/VFS/VFS.cpp index 041616c14e..0b6636d83e 100644 --- a/Common/File/VFS/VFS.cpp +++ b/Common/File/VFS/VFS.cpp @@ -63,7 +63,7 @@ uint8_t *VFS::ReadFile(const char *filename, size_t *size) { if (!fileSystemFound) { ERROR_LOG(IO, "Missing filesystem for '%s'", filename); } // Otherwise, the file was just missing. No need to log. - return 0; + return nullptr; } bool VFS::GetFileListing(const char *path, std::vector *listing, const char *filter) { diff --git a/Common/File/VFS/ZipFileReader.cpp b/Common/File/VFS/ZipFileReader.cpp index 8c9ddc505c..5597ad358d 100644 --- a/Common/File/VFS/ZipFileReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -107,7 +107,7 @@ bool ZipFileReader::GetFileListing(const char *orig_path, std::vectorclear(); - INFO_LOG(SYSTEM, "Listing %s", orig_path); + // INFO_LOG(SYSTEM, "Zip: Listing '%s'", orig_path); listing->reserve(directories.size() + files.size()); for (auto diter = directories.begin(); diter != directories.end(); ++diter) { diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 32abff3143..d86452e96a 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -385,13 +385,7 @@ void VulkanRenderManager::StopThreads() { pushCondVar_.notify_one(); // Once the render thread encounters the above exit task, it'll exit. renderThread_.join(); - } - - // Compiler and present thread still relies on this. - runCompileThread_ = false; - - if (presentWaitThread_.joinable()) { - presentWaitThread_.join(); + INFO_LOG(G3D, "Vulkan submission thread joined. Frame=%d", vulkan_->GetCurFrame()); } for (int i = 0; i < vulkan_->GetInflightFrames(); i++) { @@ -400,12 +394,18 @@ void VulkanRenderManager::StopThreads() { frameData.profile.timestampDescriptions.clear(); } - INFO_LOG(G3D, "Vulkan submission thread joined. Frame=%d", vulkan_->GetCurFrame()); - - _assert_(compileThread_.joinable()); - compileCond_.notify_all(); + { + std::unique_lock lock(compileMutex_); + runCompileThread_ = false; // Compiler and present thread both look at this bool. + _assert_(compileThread_.joinable()); + compileCond_.notify_one(); + } compileThread_.join(); + if (presentWaitThread_.joinable()) { + presentWaitThread_.join(); + } + INFO_LOG(G3D, "Vulkan compiler thread joined. Now wait for any straggling compile tasks."); CreateMultiPipelinesTask::WaitForAll(); @@ -462,7 +462,7 @@ void VulkanRenderManager::CompileThreadFunc() { std::vector toCompile; { std::unique_lock lock(compileMutex_); - if (compileQueue_.empty() && runCompileThread_) { + while (compileQueue_.empty() && runCompileThread_) { compileCond_.wait(lock); } toCompile = std::move(compileQueue_); @@ -786,7 +786,7 @@ VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipe VKRRenderPassStoreAction::STORE, VKRRenderPassStoreAction::DONT_CARE, VKRRenderPassStoreAction::DONT_CARE, }; VKRRenderPass *compatibleRenderPass = queueRunner_.GetRenderPass(key); - std::lock_guard lock(compileMutex_); + std::unique_lock lock(compileMutex_); bool needsCompile = false; for (size_t i = 0; i < (size_t)RenderPassType::TYPE_COUNT; i++) { if (!(variantBitmask & (1 << i))) @@ -809,7 +809,7 @@ VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipe } pipeline->pipeline[i] = Promise::CreateEmpty(); - compileQueue_.push_back(CompileQueueEntry(pipeline, compatibleRenderPass->Get(vulkan_, rpType, sampleCount), rpType, sampleCount)); + compileQueue_.emplace_back(pipeline, compatibleRenderPass->Get(vulkan_, rpType, sampleCount), rpType, sampleCount); needsCompile = true; } if (needsCompile) @@ -1551,10 +1551,12 @@ void VulkanRenderManager::FlushSync() { VLOG("PUSH: Frame[%d]", curFrame); VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::SYNC); task->frame = curFrame; - std::unique_lock lock(pushMutex_); - renderThreadQueue_.push(task); - renderThreadQueue_.back()->steps = std::move(steps_); - pushCondVar_.notify_one(); + { + std::unique_lock lock(pushMutex_); + renderThreadQueue_.push(task); + renderThreadQueue_.back()->steps = std::move(steps_); + pushCondVar_.notify_one(); + } steps_.clear(); } diff --git a/Common/Render/Text/draw_text_sdl.cpp b/Common/Render/Text/draw_text_sdl.cpp index 562b5f85c0..0fd0ff1817 100644 --- a/Common/Render/Text/draw_text_sdl.cpp +++ b/Common/Render/Text/draw_text_sdl.cpp @@ -285,7 +285,6 @@ void TextDrawerSDL::MeasureString(const char *str, size_t len, float *w, float * if (iter != sizeCache_.end()) { entry = iter->second.get(); } else { - printf("re-measuring %s\n", str); TTF_Font *font = fontMap_.find(fontHash_)->second; int ptSize = TTF_FontHeight(font) / 1.35; diff --git a/Common/Thread/ThreadManager.cpp b/Common/Thread/ThreadManager.cpp index d6eb76e5ec..7bacb69494 100644 --- a/Common/Thread/ThreadManager.cpp +++ b/Common/Thread/ThreadManager.cpp @@ -59,8 +59,8 @@ ThreadManager::~ThreadManager() { void ThreadManager::Teardown() { for (TaskThreadContext *&threadCtx : global_->threads_) { - threadCtx->cancelled = true; std::unique_lock lock(threadCtx->mutex); + threadCtx->cancelled = true; threadCtx->cond.notify_one(); } diff --git a/Core/FrameTiming.cpp b/Core/FrameTiming.cpp index 3fb6e34003..b6816e5c58 100644 --- a/Core/FrameTiming.cpp +++ b/Core/FrameTiming.cpp @@ -81,6 +81,8 @@ void FrameTiming::PostSubmit() { } Draw::PresentMode ComputePresentMode(Draw::DrawContext *draw, int *interval) { + _assert_(draw); + Draw::PresentMode mode = Draw::PresentMode::FIFO; if (draw->GetDeviceCaps().presentModesSupported & (Draw::PresentMode::IMMEDIATE | Draw::PresentMode::MAILBOX)) { diff --git a/Core/HLE/scePsmf.cpp b/Core/HLE/scePsmf.cpp index ad0b6e5153..8f142bb456 100644 --- a/Core/HLE/scePsmf.cpp +++ b/Core/HLE/scePsmf.cpp @@ -312,6 +312,7 @@ public: psmf->EPMap.clear(); for (u32 i = 0; i < psmf->EPMapEntriesNum; i++) { + // TODO: Should look into validating these offsets. Got a crash report here. const u8 *const entryAddr = data + psmf->EPMapOffset + EP_MAP_STRIDE * i; PsmfEntry entry; entry.EPIndex = entryAddr[0]; diff --git a/Core/Reporting.cpp b/Core/Reporting.cpp index c00c2cbc2f..84359afa35 100644 --- a/Core/Reporting.cpp +++ b/Core/Reporting.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -113,11 +114,11 @@ namespace Reporting static std::condition_variable crcCond; static Path crcFilename; static std::map crcResults; - static volatile bool crcPending = false; - static volatile bool crcCancel = false; + static std::atomic crcPending{}; + static std::atomic crcCancel{}; static std::thread crcThread; - static u32 CalculateCRC(BlockDevice *blockDevice, volatile bool *cancel) { + static u32 CalculateCRC(BlockDevice *blockDevice, std::atomic *cancel) { auto ga = GetI18NCategory(I18NCat::GAME); u32 crc = crc32(0, Z_NULL, 0); @@ -162,7 +163,6 @@ namespace Reporting crcResults[crcFilename] = crc; crcPending = false; crcCond.notify_one(); - return 0; } diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index a3c1a8a02d..a40be84fd4 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -397,8 +397,10 @@ GPUCommonHW::~GPUCommonHW() { framebufferManager_->DestroyAllFBOs(); delete framebufferManager_; delete textureCache_; - shaderManager_->ClearShaders(); - delete shaderManager_; + if (shaderManager_) { + shaderManager_->ClearShaders(); + delete shaderManager_; + } } // Called once per frame. Might also get called during the pause screen diff --git a/UI/BackgroundAudio.cpp b/UI/BackgroundAudio.cpp index c9ddd9c190..742e0c4cc4 100644 --- a/UI/BackgroundAudio.cpp +++ b/UI/BackgroundAudio.cpp @@ -380,9 +380,9 @@ inline int16_t ConvertU8ToI16(uint8_t value) { } Sample *Sample::Load(const std::string &path) { - size_t bytes; + size_t bytes = 0; uint8_t *data = g_VFS.ReadFile(path.c_str(), &bytes); - if (!data) { + if (!data || bytes > 100000000) { WARN_LOG(AUDIO, "Failed to load sample '%s'", path.c_str()); return nullptr; } diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 107e0af425..b5163c5e26 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -832,7 +832,7 @@ void SystemInfoScreen::CreateTabs() { vulkanExtensions->Add(new TextView(extension, new LayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetFocusable(true); } - vulkanExtensions->Add(new ItemHeader("Instance")); + vulkanExtensions->Add(new ItemHeader(si->T("Instance"))); // Also get instance extensions extensions = draw->GetExtensionList(false, false); std::sort(extensions.begin(), extensions.end()); diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index 7763ed37f4..15718d4ec9 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -355,13 +355,20 @@ void GameInfo::FinishPendingTextureLoads(Draw::DrawContext *draw) { } void GameInfo::SetupTexture(Draw::DrawContext *thin3d, GameInfoTex &tex) { + if (tex.timeLoaded) { + // Failed before, skip. + return; + } + if (tex.data.empty()) { + tex.timeLoaded = time_now_d(); + return; + } using namespace Draw; // TODO: Use TempImage to semi-load the image in the worker task, then here we // could just call CreateTextureFromTempImage. tex.texture = CreateTextureFromFileData(thin3d, (const uint8_t *)tex.data.data(), (int)tex.data.size(), ImageFileType::DETECT, false, GetTitle().c_str()); - if (tex.texture) { - tex.timeLoaded = time_now_d(); - } else { + tex.timeLoaded = time_now_d(); + if (!tex.texture) { ERROR_LOG(G3D, "Failed creating texture (%s) from %d-byte file", GetTitle().c_str(), (int)tex.data.size()); } } @@ -801,10 +808,12 @@ void GameInfoCache::Shutdown() { void GameInfoCache::Clear() { CancelAll(); + std::lock_guard lock(mapLock_); info_.clear(); } void GameInfoCache::CancelAll() { + std::lock_guard lock(mapLock_); for (auto info : info_) { // GetFileLoader will create one if there isn't one already. // Avoid that by checking. @@ -818,6 +827,7 @@ void GameInfoCache::CancelAll() { } void GameInfoCache::FlushBGs() { + std::lock_guard lock(mapLock_); for (auto iter = info_.begin(); iter != info_.end(); iter++) { std::lock_guard lock(iter->second->lock); iter->second->pic0.Clear(); @@ -831,19 +841,28 @@ void GameInfoCache::FlushBGs() { } void GameInfoCache::PurgeType(IdentifiedFileType fileType) { - for (auto iter = info_.begin(); iter != info_.end();) { - auto &info = iter->second; + bool retry = false; + // Trickery to avoid sleeping with the lock held. + do { + { + std::lock_guard lock(mapLock_); + for (auto iter = info_.begin(); iter != info_.end();) { + auto &info = iter->second; - // TODO: Find a better way to wait here. - while (info->pendingFlags != (GameInfoFlags)0) { - sleep_ms(1); + // TODO: Find a better way to wait here. + while (info->pendingFlags != (GameInfoFlags)0) { + retry = true; + break; + } + if (info->fileType == fileType) { + iter = info_.erase(iter); + } else { + iter++; + } + } } - if (info->fileType == fileType) { - iter = info_.erase(iter); - } else { - iter++; - } - } + sleep_ms(1); + } while (retry); } // Call on the main thread ONLY - that is from stuff called from NativeFrame. diff --git a/UI/GameInfoCache.h b/UI/GameInfoCache.h index 5550276a5b..f251aea494 100644 --- a/UI/GameInfoCache.h +++ b/UI/GameInfoCache.h @@ -69,11 +69,15 @@ struct GameInfoTex { Draw::Texture *texture = nullptr; // The time at which the Icon and the BG were loaded. // Can be useful to fade them in smoothly once they appear. + // Also, timeLoaded != 0 && texture == nullptr means that the load failed. double timeLoaded = 0.0; std::atomic dataLoaded{}; // Can ONLY be called from the main thread! void Clear(); + bool Failed() const { + return timeLoaded != 0.0 && !texture; + } }; class GameInfo { diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 984c0f827b..3ddc1a67e1 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1396,7 +1396,7 @@ UI::EventReturn GameSettingsScreen::OnChangeBackground(UI::EventParams &e) { FILE *f = File::OpenCFile(path, "rb"); uint8_t buffer[8]; ImageFileType type = ImageFileType::UNKNOWN; - if (8 == fread(buffer, 1, ARRAY_SIZE(buffer), f)) { + if (f != nullptr && 8 == fread(buffer, 1, ARRAY_SIZE(buffer), f)) { type = DetectImageFileType(buffer, ARRAY_SIZE(buffer)); } diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index c963a8fd69..1ee708817f 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -175,8 +175,11 @@ public: }, this); } ~HostnameSelectScreen() { - resolverState_ = ResolverState::QUIT; - resolverCond_.notify_one(); + { + std::unique_lock guard(resolverLock_); + resolverState_ = ResolverState::QUIT; + resolverCond_.notify_one(); + } resolver_.join(); } diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 49ed7cd003..38e1e60b7d 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -924,6 +924,7 @@ void NativeShutdownGraphics() { } g_iconCache.ClearTextures(); + // TODO: This is not really necessary with Vulkan on Android - could keep shaders etc in memory if (gpu) gpu->DeviceLost(); diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp index 5669f55b52..b32e3e63ef 100644 --- a/UI/SavedataScreen.cpp +++ b/UI/SavedataScreen.cpp @@ -87,7 +87,7 @@ public: UIContext &dc = *screenManager()->getUIContext(); const Style &textStyle = dc.theme->popupStyle; - std::shared_ptr ginfo = g_gameInfoCache->GetInfo(screenManager()->getDrawContext(), savePath_, GameInfoFlags::PARAM_SFO | GameInfoFlags::SIZE); + std::shared_ptr ginfo = g_gameInfoCache->GetInfo(screenManager()->getDrawContext(), savePath_, GameInfoFlags::PARAM_SFO | GameInfoFlags::ICON | GameInfoFlags::SIZE); if (!ginfo->Ready(GameInfoFlags::PARAM_SFO)) return; @@ -294,7 +294,7 @@ void SavedataButton::UpdateText(const std::shared_ptr &ginfo) { } void SavedataButton::Draw(UIContext &dc) { - std::shared_ptr ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), savePath_, GameInfoFlags::PARAM_SFO | GameInfoFlags::SIZE); + std::shared_ptr ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), savePath_, GameInfoFlags::ICON | GameInfoFlags::PARAM_SFO | GameInfoFlags::SIZE); Draw::Texture *texture = 0; u32 color = 0, shadowColor = 0; using namespace UI; diff --git a/android/jni/OpenSLContext.cpp b/android/jni/OpenSLContext.cpp index 5a67b71484..c5ebffbe6a 100644 --- a/android/jni/OpenSLContext.cpp +++ b/android/jni/OpenSLContext.cpp @@ -10,7 +10,6 @@ #include #include "Common/Log.h" -#include "Common/StringUtils.h" #include "OpenSLContext.h" #include "Core/HLE/sceUsbMic.h" @@ -58,7 +57,9 @@ void OpenSLContext::BqPlayerCallback(SLAndroidSimpleBufferQueueItf bq) { memset(buffer[curBuffer] + renderedFrames * 2, 0, byteCount); } SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[curBuffer], sizeInBytes); - CheckResult(result, StringFromFormat("Failed to enqueue: %d %d", renderedFrames, sizeInBytes).c_str()); + + // TODO: get rid of this snprintf too + CheckResult(result, "Failed to enqueue"); // Comment from sample code: // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT, // which for this code example would indicate a programming error diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 04d69e06e7..a568076fbe 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -1162,13 +1162,11 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendRequestResult(JNIEn } } -void LockedNativeUpdateRender() { - std::lock_guard renderGuard(renderLock); - NativeFrame(graphicsContext); -} - void UpdateRunLoopAndroid(JNIEnv *env) { - LockedNativeUpdateRender(); + { + std::lock_guard renderGuard(renderLock); + NativeFrame(graphicsContext); + } std::lock_guard guard(frameCommandLock); if (!nativeActivity) { @@ -1639,20 +1637,24 @@ static void VulkanEmuThread(ANativeWindow *wnd) { renderer_inited = true; while (!exitRenderLoop) { - LockedNativeUpdateRender(); + { + std::lock_guard renderGuard(renderLock); + NativeFrame(graphicsContext); + } ProcessFrameCommands(env); } + INFO_LOG(G3D, "Leaving Vulkan main loop."); + } else { + INFO_LOG(G3D, "Not entering main loop."); } - INFO_LOG(G3D, "Leaving EGL/Vulkan render loop."); - NativeShutdownGraphics(); renderer_inited = false; graphicsContext->ThreadEnd(); // Shut the graphics context down to the same state it was in when we entered the render thread. - INFO_LOG(G3D, "Shutting down graphics context from render thread..."); + INFO_LOG(G3D, "Shutting down graphics context..."); graphicsContext->ShutdownFromRenderThread(); renderLoopRunning = false; exitRenderLoop = false; diff --git a/assets/lang/sv_SE.ini b/assets/lang/sv_SE.ini index e93196737d..aa561aa462 100644 --- a/assets/lang/sv_SE.ini +++ b/assets/lang/sv_SE.ini @@ -1172,33 +1172,33 @@ Size = Storlek Uninstall = Avinstallera [SysInfo] -%d (%d per core, %d cores) = %d (%d per core, %d cores) -%d bytes = %d bytes +%d (%d per core, %d cores) = %d (%d per kärna, %d kärnor) +%d bytes = %d byte %d Hz = %d hz %0.2f Hz = %0.2f Hz -(none detected) = (none detected) -3D API = 3D API +(none detected) = (inget upptäckt) +3D API = 3D-API ABI = ABI -API Version = API version +API Version = API-version Audio Information = Audio-information Board = Board -Build Config = Build config -Build Configuration = Build Configuration +Build Config = Byggkonfiguration +Build Configuration = Byggkonfiguration Built by = Built by -Compressed texture formats = Compressed texture formats +Compressed texture formats = Komprimerade texturformat Core Context = Core context -Cores = Cores -CPU Extensions = CPU-extensions +Cores = Kärnor +CPU Extensions = CPU-extensioner CPU Information = CPU-information CPU Name = Name -D3DCompiler Version = D3DCompiler version +D3DCompiler Version = D3DCompiler-version Debug = Debug Debugger Present = Debugger närvarande -Depth buffer format = Depth buffer-format -Device Info = Device-information -Directories = Directories -Display Color Formats = Display Color Formats -Display Information = Display information +Depth buffer format = Djupbufferformat +Device Info = Enhetsinformation +Directories = Kataloger +Display Color Formats = Displayens färgformat +Display Information = Display-information DPI = DPI Driver bugs = Drivrutinsbuggar Driver Version = Drivrutinsversion @@ -1208,31 +1208,31 @@ GPU Flags = GPU Flags GPU Information = GPU-information High precision float range = High precision float range High precision int range = High precision int range -Icon cache = Icon cache -Instance = Instance -JIT available = JIT available +Icon cache = Ikon-bildcache +Instance = Instans +JIT available = JIT är tillgänglig Lang/Region = Språk/Region -Memory Page Size = Memory page size -Native resolution = Native resolution +Memory Page Size = Minnessidesstorlek +Native resolution = Native upplösning No GPU driver bugs detected = No GPU driver bugs detected -OGL Extensions = OGL extensions -OpenGL ES 2.0 Extensions = OpenGL ES 2.0 extensions -OpenGL ES 3.0 Extensions = OpenGL ES 3.0 extensions -OpenGL Extensions = OpenGL extensions -Optimal frames per buffer = Optimal frames per buffer -Optimal sample rate = Optimal sample rate +OGL Extensions = OGL extensioner +OpenGL ES 2.0 Extensions = OpenGL ES 2.0 extensioner +OpenGL ES 3.0 Extensions = OpenGL ES 3.0 extensioner +OpenGL Extensions = OpenGL extensioner +Optimal frames per buffer = Optimala frames per buffer +Optimal sample rate = Optimal Samplingsfrekvens OS Information = OS-information -Pixel resolution = Pixel resolution +Pixel resolution = Pixel-upplösning PPSSPP build = PPSSPP bygge -Present modes = Present modes -Refresh rate = Refresh rate +Present modes = Presentationslägen +Refresh rate = Bilduppdateringsfrekvens Release = Release RW/RX exclusive = RW/RX exclusive Sample rate = Samplingsfrekvens Screen notch insets = Screen notch insets Shading Language = Shadingspråk -Storage = Storage -Sustained perf mode = Sustained perf mode +Storage = Lagring +Sustained perf mode = Hållbar-prestanda-läge System Information = Systeminformation System Name = Name System Version = Systemversion @@ -1241,8 +1241,8 @@ UI resolution = UI-upplösning Vendor = Vendor Vendor (detected) = Vendor (detected) Version Information = Versionsinformation -Vulkan Extensions = Vulkan extensions -Vulkan Features = Vulkan features +Vulkan Extensions = Vulkan-extensioner +Vulkan Features = Vulkan-features [System] (broken) = (broken)