diff --git a/ares/ares/node/video/screen.cpp b/ares/ares/node/video/screen.cpp index 2fe73a2de5..d9b627cce4 100644 --- a/ares/ares/node/video/screen.cpp +++ b/ares/ares/node/video/screen.cpp @@ -24,6 +24,7 @@ Screen::~Screen() { } auto Screen::main(uintptr_t) -> void { + thread::setName("dev.ares.screen"); while(!_kill) { unique_lock lock(_frameMutex); diff --git a/ares/n64/system/system.cpp b/ares/n64/system/system.cpp index f48c70cf67..6f3db55da5 100644 --- a/ares/n64/system/system.cpp +++ b/ares/n64/system/system.cpp @@ -77,6 +77,11 @@ auto System::game() -> string { } auto System::run() -> void { + // not ideal + if(!vulkanLoaded) { + vulkan.load(node); + vulkanLoaded = true; + } cpu.main(); } @@ -134,9 +139,6 @@ auto System::load(Node::System& root, string name) -> bool { rdp.load(node); if(_DD()) dd.load(node); if(model() == Model::Aleck64) aleck64.load(node); - #if defined(VULKAN) - vulkan.load(node); - #endif initDebugHooks(); @@ -308,6 +310,7 @@ auto System::unload() -> void { if(vi.screen) vi.screen->quit(); //stop video thread #if defined(VULKAN) vulkan.unload(); + vulkanLoaded = false; #endif cartridgeSlot.unload(); controllerPort1.unload(); diff --git a/ares/n64/system/system.hpp b/ares/n64/system/system.hpp index f0b3ecba56..b83454d568 100644 --- a/ares/n64/system/system.hpp +++ b/ares/n64/system/system.hpp @@ -37,6 +37,8 @@ struct System { u32 videoFrequency = 48'681'818; bool dd = false; } information; + + bool vulkanLoaded = false; auto initDebugHooks() -> void; diff --git a/ares/n64/vi/vi.cpp b/ares/n64/vi/vi.cpp index 86f4dcaa44..4231f7efbe 100644 --- a/ares/n64/vi/vi.cpp +++ b/ares/n64/vi/vi.cpp @@ -117,7 +117,7 @@ auto VI::main() -> void { io.vcounter = 0; if(++inactiveCounter >= 200) { inactiveCounter = 0; - screen->frame(); + //screen->frame(); refreshed = true; } step(0x800); diff --git a/desktop-ui/desktop-ui.cpp b/desktop-ui/desktop-ui.cpp index 81c97c4477..98c188655b 100644 --- a/desktop-ui/desktop-ui.cpp +++ b/desktop-ui/desktop-ui.cpp @@ -156,10 +156,10 @@ auto nall::main(Arguments arguments) -> void { Instances::presentation.construct(); Instances::settingsWindow.construct(); - Instances::toolsWindow.construct(); Instances::gameBrowserWindow.construct(); program.create(); + Instances::toolsWindow.construct(&program.programMutex); Application::onMain({&Program::main, &program}); Application::run(); diff --git a/desktop-ui/emulator/emulator.cpp b/desktop-ui/emulator/emulator.cpp index 539cb6b1c1..3e733dfd7a 100644 --- a/desktop-ui/emulator/emulator.cpp +++ b/desktop-ui/emulator/emulator.cpp @@ -111,6 +111,7 @@ auto Emulator::handleLoadResult(LoadResult result) -> void { } auto Emulator::load(const string& location) -> bool { + lock_guard lock(program.programMutex); if(inode::exists(location)) locationQueue.append(location); LoadResult result = load(); @@ -130,6 +131,7 @@ auto Emulator::load(const string& location) -> bool { } auto Emulator::load(shared_pointer pak, string& path) -> string { + lock_guard lock(program.programMutex); string location; if(locationQueue) { location = locationQueue.takeFirst(); //pull from the game queue if an entry is available @@ -160,6 +162,7 @@ auto Emulator::load(shared_pointer pak, string& path) -> string { } auto Emulator::loadFirmware(const Firmware& firmware) -> shared_pointer { + lock_guard lock(program.programMutex); if(firmware.location.iendsWith(".zip")) { Decode::ZIP archive; if(archive.open(firmware.location) && archive.file) { @@ -173,6 +176,7 @@ auto Emulator::loadFirmware(const Firmware& firmware) -> shared_pointer void { + lock_guard lock(program.programMutex); save(); root->unload(); game = {}; @@ -182,6 +186,7 @@ auto Emulator::unload() -> void { } auto Emulator::load(mia::Pak& node, string name) -> bool { + lock_guard lock(program.programMutex); if(auto fp = node.pak->read(name)) { if(auto memory = file::read({node.location, name})) { fp->read(memory); @@ -192,6 +197,7 @@ auto Emulator::load(mia::Pak& node, string name) -> bool { } auto Emulator::save(mia::Pak& node, string name) -> bool { + lock_guard lock(program.programMutex); if(auto memory = node.pak->write(name)) { return file::write({node.location, name}, {memory->data(), memory->size()}); } @@ -199,12 +205,14 @@ auto Emulator::save(mia::Pak& node, string name) -> bool { } auto Emulator::refresh() -> void { + lock_guard lock(program.programMutex); if(auto screen = root->scan("Screen")) { screen->refresh(); } } auto Emulator::setBoolean(const string& name, bool value) -> bool { + lock_guard lock(program.programMutex); if(auto node = root->scan(name)) { node->setValue(value); //setValue() will not call modify() if value has not changed; node->modify(value); //but that may prevent the initial setValue() from working @@ -214,6 +222,7 @@ auto Emulator::setBoolean(const string& name, bool value) -> bool { } auto Emulator::setOverscan(bool value) -> bool { + lock_guard lock(program.programMutex); if(auto screen = root->scan("Screen")) { screen->setOverscan(value); return true; @@ -222,6 +231,7 @@ auto Emulator::setOverscan(bool value) -> bool { } auto Emulator::setColorBleed(bool value) -> bool { + lock_guard lock(program.programMutex); if(auto screen = root->scan("Screen")) { screen->setColorBleed(screen->height() < 720 ? value : false); //only apply to sub-HD content return true; @@ -235,6 +245,7 @@ auto Emulator::error(const string& text) -> void { } auto Emulator::input(ares::Node::Input::Input input) -> void { + lock_guard lock(program.inputMutex); //necessary? //looking up inputs is very time-consuming; skip call if input was called too recently //note: allow rumble to be polled at full speed to prevent missed motor events if(!input->cast()) { @@ -281,6 +292,7 @@ auto Emulator::input(ares::Node::Input::Input input) -> void { } auto Emulator::inputKeyboard(string name) -> bool { + lock_guard lock(program.inputMutex); for (auto& device : inputManager.devices) { if (!device->isKeyboard()) continue; diff --git a/desktop-ui/input/input.cpp b/desktop-ui/input/input.cpp index c71adbc6ab..d282b51daa 100644 --- a/desktop-ui/input/input.cpp +++ b/desktop-ui/input/input.cpp @@ -5,6 +5,7 @@ VirtualPort virtualPorts[5]; InputManager inputManager; auto InputMapping::bind() -> void { + lock_guard lock(program.inputMutex); for(auto& binding : bindings) binding = {}; for(u32 index : range(BindingLimit)) { @@ -32,22 +33,26 @@ auto InputMapping::bind() -> void { } auto InputMapping::bind(u32 binding, string assignment) -> void { + lock_guard lock(program.inputMutex); if(binding >= BindingLimit) return; assignments[binding] = assignment; bind(); } auto InputMapping::unbind() -> void { + lock_guard lock(program.inputMutex); for(u32 binding : range(BindingLimit)) unbind(binding); } auto InputMapping::unbind(u32 binding) -> void { + lock_guard lock(program.inputMutex); if(binding >= BindingLimit) return; bindings[binding] = {}; assignments[binding] = {}; } auto InputMapping::Binding::icon() -> multiFactorImage { + lock_guard lock(program.inputMutex); if(!device && deviceID) return Icon::Device::Joypad; if(!device) return {}; if(device->isKeyboard()) return Icon::Device::Keyboard; @@ -57,6 +62,7 @@ auto InputMapping::Binding::icon() -> multiFactorImage { } auto InputMapping::Binding::text() -> string { + lock_guard lock(program.inputMutex); if(!device && deviceID) return "(disconnected)"; if(!device) return {}; if(groupID >= device->size()) return {}; @@ -90,6 +96,7 @@ auto InputMapping::Binding::text() -> string { // auto InputDigital::bind(u32 binding, shared_pointer device, u32 groupID, u32 inputID, s16 oldValue, s16 newValue) -> bool { + lock_guard lock(program.inputMutex); string assignment = {"0x", hex(device->id()), "/", groupID, "/", inputID}; if(device->isNull()) { @@ -128,6 +135,7 @@ auto InputDigital::bind(u32 binding, shared_pointer device, u32 gro } auto InputDigital::value() -> s16 { + lock_guard lock(program.inputMutex); s16 result = 0; for(auto& binding : bindings) { @@ -165,11 +173,13 @@ auto InputDigital::value() -> s16 { } auto InputDigital::pressed() -> bool { + lock_guard lock(program.inputMutex); return value() != 0; } auto InputHotkey::value() -> s16 { + lock_guard lock(program.inputMutex); s16 result = 0; for(auto& binding : bindings) { @@ -210,6 +220,7 @@ auto InputHotkey::value() -> s16 { // auto InputAnalog::bind(u32 binding, shared_pointer device, u32 groupID, u32 inputID, s16 oldValue, s16 newValue) -> bool { + lock_guard lock(program.inputMutex); string assignment = {"0x", hex(device->id()), "/", groupID, "/", inputID}; if(device->isNull()) { @@ -244,6 +255,7 @@ auto InputAnalog::bind(u32 binding, shared_pointer device, u32 grou } auto InputAnalog::value() -> s16 { + lock_guard lock(program.inputMutex); s32 result = 0; for(auto& binding : bindings) { @@ -274,12 +286,14 @@ auto InputAnalog::value() -> s16 { } auto InputAnalog::pressed() -> bool { + lock_guard lock(program.inputMutex); return value() > 16384; } // auto InputAbsolute::bind(u32 binding, shared_pointer device, u32 groupID, u32 inputID, s16 oldValue, s16 newValue) -> bool { + lock_guard lock(program.inputMutex); string assignment = {"0x", hex(device->id()), "/", groupID, "/", inputID}; if(device->isNull()) { @@ -310,6 +324,7 @@ auto InputAbsolute::bind(u32 binding, shared_pointer device, u32 gr } auto InputAbsolute::value() -> s16 { + lock_guard lock(program.inputMutex); s32 result = 0; for(auto& binding : bindings) { @@ -337,6 +352,7 @@ auto InputAbsolute::value() -> s16 { // auto InputRelative::bind(u32 binding, shared_pointer device, u32 groupID, u32 inputID, s16 oldValue, s16 newValue) -> bool { + lock_guard lock(program.inputMutex); string assignment = {"0x", hex(device->id()), "/", groupID, "/", inputID}; if(device->isNull()) { @@ -367,6 +383,7 @@ auto InputRelative::bind(u32 binding, shared_pointer device, u32 gr } auto InputRelative::value() -> s16 { + lock_guard lock(program.inputMutex); s32 result = 0; for(auto& binding : bindings) { @@ -394,6 +411,7 @@ auto InputRelative::value() -> s16 { // auto InputRumble::bind(u32 binding, shared_pointer device, u32 groupID, u32 inputID, s16 oldValue, s16 newValue) -> bool { + lock_guard lock(program.inputMutex); string assignment = {"0x", hex(device->id()), "/", groupID, "/", inputID}; if(device->isNull()) { @@ -468,10 +486,12 @@ VirtualMouse::VirtualMouse() { // auto InputManager::create() -> void { + lock_guard lock(program.inputMutex); createHotkeys(); } auto InputManager::bind() -> void { + lock_guard lock(program.inputMutex); for(auto& port : virtualPorts) { for(auto& input : port.pad.inputs) input.mapping->bind(); for(auto& input : port.mouse.inputs) input.mapping->bind(); @@ -480,6 +500,7 @@ auto InputManager::bind() -> void { } auto InputManager::poll(bool force) -> void { + lock_guard lock(program.inputMutex); //polling actual hardware is very time-consuming; skip call if poll was called too recently auto thisPoll = chrono::millisecond(); if(thisPoll - lastPoll < pollFrequency && !force) return; @@ -502,6 +523,7 @@ auto InputManager::poll(bool force) -> void { } auto InputManager::eventInput(shared_pointer device, u32 groupID, u32 inputID, s16 oldValue, s16 newValue) -> void { + lock_guard lock(program.inputMutex); inputSettings.eventInput(device, groupID, inputID, oldValue, newValue); hotkeySettings.eventInput(device, groupID, inputID, oldValue, newValue); } diff --git a/desktop-ui/presentation/presentation.cpp b/desktop-ui/presentation/presentation.cpp index 9c11d4bd92..71cbb4f3eb 100644 --- a/desktop-ui/presentation/presentation.cpp +++ b/desktop-ui/presentation/presentation.cpp @@ -136,6 +136,7 @@ Presentation::Presentation() { for(u32 slot : range(9)) { MenuItem item{&saveStateMenu}; item.setText({"Slot ", 1 + slot}).onActivate([=] { + lock_guard lock(program.programMutex); if(program.stateSave(1 + slot)) { undoSaveStateMenu.setEnabled(true); } @@ -145,6 +146,7 @@ Presentation::Presentation() { for(u32 slot : range(9)) { MenuItem item{&loadStateMenu}; item.setText({"Slot ", 1 + slot}).onActivate([=] { + lock_guard lock(program.programMutex); if(program.stateLoad(1 + slot)) { undoLoadStateMenu.setEnabled(true); } @@ -152,24 +154,30 @@ Presentation::Presentation() { } undoSaveStateMenu.setText("Undo Last Save State").setIcon(Icon::Edit::Undo).setEnabled(false); undoSaveStateMenu.onActivate([&] { + lock_guard lock(program.programMutex); program.undoStateSave(); undoSaveStateMenu.setEnabled(false); }); undoLoadStateMenu.setText("Undo Last Load State").setIcon(Icon::Edit::Undo).setEnabled(false); undoLoadStateMenu.onActivate([&] { + lock_guard lock(program.programMutex); program.undoStateLoad(); undoLoadStateMenu.setEnabled(false); }); captureScreenshot.setText("Capture Screenshot").setIcon(Icon::Emblem::Image).onActivate([&] { + lock_guard lock(program.programMutex); program.requestScreenshot = true; }); pauseEmulation.setText("Pause Emulation").onToggle([&] { + lock_guard lock(program.programMutex); program.pause(!program.paused); }); reloadGame.setText("Reload Game").setIcon(Icon::Action::Refresh).onActivate([&] { + lock_guard lock(program.programMutex); program.load(emulator, emulator->game->location); }); frameAdvance.setText("Frame Advance").setIcon(Icon::Media::Play).onActivate([&] { + lock_guard lock(program.programMutex); if (!program.paused) program.pause(true); program.requestFrameAdvance = true; }); @@ -211,6 +219,7 @@ Presentation::Presentation() { }); viewport.setDroppable().onDrop([&](auto filenames) { + lock_guard lock(program.programMutex); if(filenames.size() != 1) return; if(auto emulator = program.identify(filenames.first())) { program.load(emulator, filenames.first()); @@ -218,6 +227,7 @@ Presentation::Presentation() { }); Application::onOpenFile([&](auto filename) { + lock_guard lock(program.programMutex); if(auto emulator = program.identify(filename)) { program.load(emulator, filename); } @@ -355,6 +365,7 @@ auto Presentation::loadEmulators() -> void { item.setIconForFile(location); item.setText({Location::base(location).trimRight("/"), " (", system, ")"}); item.onActivate([=] { + lock_guard lock(program.programMutex); if(!inode::exists(location)) { MessageDialog() .setTitle("Error") @@ -369,7 +380,8 @@ auto Presentation::loadEmulators() -> void { } for(auto& emulator : emulators) { if(emulator->name == system) { - return (void)program.load(emulator, location); + program.load(emulator, location); + return; } } }); @@ -575,6 +587,7 @@ auto Presentation::refreshSystemMenu() -> void { MenuItem reset{&systemMenu}; reset.setText("Reset").setIcon(Icon::Action::Refresh).onActivate([&] { + lock_guard lock(program.programMutex); emulator->root->power(true); program.showMessage("System reset"); }); @@ -582,8 +595,9 @@ auto Presentation::refreshSystemMenu() -> void { MenuItem unload{&systemMenu}; unload.setText("Unload").setIcon(Icon::Media::Eject).onActivate([&] { - program.unload(); - if(settings.video.adaptiveSizing) presentation.resizeWindow(); + lock_guard lock(program.programMutex); + program.unload(); + if(settings.video.adaptiveSizing) presentation.resizeWindow(); presentation.showIcon(true); }); } diff --git a/desktop-ui/program/drivers.cpp b/desktop-ui/program/drivers.cpp index 73ef72fe31..da5a3778a7 100644 --- a/desktop-ui/program/drivers.cpp +++ b/desktop-ui/program/drivers.cpp @@ -1,4 +1,5 @@ auto Program::videoDriverUpdate() -> void { + lock_guard lock(programMutex); ruby::video.create(settings.video.driver); ruby::video.setContext(presentation.viewport.handle()); videoMonitorUpdate(); @@ -21,6 +22,7 @@ auto Program::videoDriverUpdate() -> void { } auto Program::videoMonitorUpdate() -> void { + lock_guard lock(programMutex); if(!ruby::video.hasMonitor(settings.video.monitor)) { settings.video.monitor = ruby::video.monitor(); } @@ -28,6 +30,7 @@ auto Program::videoMonitorUpdate() -> void { } auto Program::videoFormatUpdate() -> void { + lock_guard lock(programMutex); if(!ruby::video.hasFormat(settings.video.format)) { settings.video.format = ruby::video.format(); } @@ -35,6 +38,7 @@ auto Program::videoFormatUpdate() -> void { } auto Program::videoFullScreenToggle() -> void { + lock_guard lock(programMutex); if(!ruby::video.hasFullScreen()) return; ruby::video.clear(); @@ -55,6 +59,7 @@ auto Program::videoFullScreenToggle() -> void { } auto Program::videoPseudoFullScreenToggle() -> void { + lock_guard lock(programMutex); if(ruby::video.fullScreen()) return; if(!presentation.fullScreen()) { @@ -69,6 +74,7 @@ auto Program::videoPseudoFullScreenToggle() -> void { } auto Program::audioDriverUpdate() -> void { + lock_guard lock(programMutex); ruby::audio.create(settings.audio.driver); ruby::audio.setContext(presentation.viewport.handle()); audioDeviceUpdate(); @@ -86,6 +92,7 @@ auto Program::audioDriverUpdate() -> void { } auto Program::audioDeviceUpdate() -> void { + lock_guard lock(programMutex); if(!ruby::audio.hasDevice(settings.audio.device)) { settings.audio.device = ruby::audio.device(); } @@ -93,6 +100,7 @@ auto Program::audioDeviceUpdate() -> void { } auto Program::audioFrequencyUpdate() -> void { + lock_guard lock(programMutex); if(!ruby::audio.hasFrequency(settings.audio.frequency)) { settings.audio.frequency = ruby::audio.frequency(); } @@ -104,6 +112,7 @@ auto Program::audioFrequencyUpdate() -> void { } auto Program::audioLatencyUpdate() -> void { + lock_guard lock(programMutex); if(!ruby::audio.hasLatency(settings.audio.latency)) { settings.audio.latency = ruby::audio.latency(); } @@ -113,6 +122,7 @@ auto Program::audioLatencyUpdate() -> void { // auto Program::inputDriverUpdate() -> void { + lock_guard lock(programMutex); ruby::input.create(settings.input.driver); ruby::input.setContext(presentation.viewport.handle()); ruby::input.onChange({&InputManager::eventInput, &inputManager}); diff --git a/desktop-ui/program/load.cpp b/desktop-ui/program/load.cpp index 259818469e..e4c1d35cec 100644 --- a/desktop-ui/program/load.cpp +++ b/desktop-ui/program/load.cpp @@ -1,4 +1,5 @@ auto Program::identify(const string& filename) -> shared_pointer { + lock_guard lock(programMutex); if(auto system = mia::identify(filename)) { for(auto& emulator : emulators) { if(emulator->name == system) return emulator; @@ -13,8 +14,9 @@ auto Program::identify(const string& filename) -> shared_pointer { return {}; } -//location is an optional game to load automatically (for command-line loading) +//location is an optional game t o load automatically (for command-line loading) auto Program::load(shared_pointer emulator, string location) -> bool { + lock_guard lock(programMutex); unload(); ::emulator = emulator; @@ -33,6 +35,7 @@ auto Program::load(shared_pointer emulator, string location) -> bool { } auto Program::load(string location) -> bool { + lock_guard lock(programMutex); if(settings.debugServer.enabled) { nall::GDB::server.reset(); } @@ -90,10 +93,12 @@ auto Program::load(string location) -> bool { settings.recent.game[0] = {emulator->name, ";", location}; presentation.loadEmulators(); + loaded = true; return true; } auto Program::unload() -> void { + lock_guard lock(programMutex); if(!emulator) return; nall::GDB::server.close(); diff --git a/desktop-ui/program/program.cpp b/desktop-ui/program/program.cpp index 8977abeb25..72b64847c3 100644 --- a/desktop-ui/program/program.cpp +++ b/desktop-ui/program/program.cpp @@ -8,6 +8,8 @@ #include "drivers.cpp" Program program; +bool initialized = false; +thread* worker; auto Program::create() -> void { ares::platform = this; @@ -19,6 +21,8 @@ auto Program::create() -> void { driverSettings.videoRefresh(); driverSettings.audioRefresh(); driverSettings.inputRefresh(); + + thread::create({&Program::emulatorRunLoop, this}); if(startGameLoad) { auto gameToLoad = startGameLoad.takeFirst(); @@ -41,71 +45,85 @@ auto Program::create() -> void { } } -auto Program::main() -> void { - if(Application::modal()) { - ruby::audio.clear(); - return; - } +auto Program::emulatorRunLoop(uintptr_t) -> void { + thread::setName("dev.ares.worker"); + while(!_quitting) { + // Allow other threads to access the program mutex + usleep(1); + + lock_guard lock(programMutex); + if(!loaded || !emulator) { + usleep(20 * 1000); + continue; + } - updateMessage(); - inputManager.poll(); - inputManager.pollHotkeys(); + if(emulator && nall::GDB::server.isHalted()) { + ruby::audio.clear(); + nall::GDB::server.updateLoop(); // sleeps internally + continue; + } - bool defocused = driverSettings.inputDefocusPause.checked() && !ruby::video.fullScreen() && !presentation.focused(); - if(emulator && defocused) message.text = "Paused"; + bool defocused = driverSettings.inputDefocusPause.checked() && !ruby::video.fullScreen() && !presentation.focused(); - if(settings.debugServer.enabled) { - presentation.statusDebug.setText( - nall::GDB::server.getStatusText(settings.debugServer.port, settings.debugServer.useIPv4) - ); - } + if(!emulator || (paused && !program.requestFrameAdvance) || defocused) { + ruby::audio.clear(); + nall::GDB::server.updateLoop(); + usleep(20 * 1000); + continue; + } - if(emulator && nall::GDB::server.isHalted()) { - ruby::audio.clear(); - nall::GDB::server.updateLoop(); // sleeps internally - return; - } + rewindRun(); - if(!emulator || (paused && !program.requestFrameAdvance) || defocused) { - ruby::audio.clear(); nall::GDB::server.updateLoop(); - usleep(20 * 1000); - return; - } - rewindRun(); - - nall::GDB::server.updateLoop(); - - program.requestFrameAdvance = false; - if(!runAhead || fastForwarding || rewinding) { - emulator->root->run(); - } else { - ares::setRunAhead(true); - emulator->root->run(); - auto state = emulator->root->serialize(false); - ares::setRunAhead(false); - emulator->root->run(); - state.setReading(); - emulator->root->unserialize(state); - } + program.requestFrameAdvance = false; + if(!runAhead || fastForwarding || rewinding) { + emulator->root->run(); + } else { + ares::setRunAhead(true); + emulator->root->run(); + auto state = emulator->root->serialize(false); + ares::setRunAhead(false); + emulator->root->run(); + state.setReading(); + emulator->root->unserialize(state); + } - nall::GDB::server.updateLoop(); + nall::GDB::server.updateLoop(); - if(settings.general.autoSaveMemory) { - static u64 previousTime = chrono::timestamp(); - u64 currentTime = chrono::timestamp(); - if(currentTime - previousTime >= 30) { - previousTime = currentTime; - emulator->save(); + if(settings.general.autoSaveMemory) { + static u64 previousTime = chrono::timestamp(); + u64 currentTime = chrono::timestamp(); + if(currentTime - previousTime >= 30) { + previousTime = currentTime; + emulator->save(); + } + } + + if(emulator->latch.changed) { + emulator->latch.changed = false; + _needsResize = true; } } +} + +auto Program::main() -> void { + if(Application::modal()) { + ruby::audio.clear(); + return; + } + + inputManager.poll(); + inputManager.pollHotkeys(); + + updateMessage(); - //if Platform::video() changed the screen resolution, resize the presentation window here. - //window operations must be performed from the main thread. - if(emulator->latch.changed) { - emulator->latch.changed = false; + //If Platform::video() changed the screen resolution, resize the presentation window here. + //Window operations must be performed from the main thread. + + if(_needsResize) { if(settings.video.adaptiveSizing) presentation.resizeWindow(); + _needsResize = false; } memoryEditor.liveRefresh(); @@ -114,6 +132,7 @@ auto Program::main() -> void { } auto Program::quit() -> void { + _quitting = true; unload(); Application::processEvents(); Application::quit(); diff --git a/desktop-ui/program/program.hpp b/desktop-ui/program/program.hpp index fd5ad593e5..5eb05368f5 100644 --- a/desktop-ui/program/program.hpp +++ b/desktop-ui/program/program.hpp @@ -1,6 +1,7 @@ struct Program : ares::Platform { auto create() -> void; auto main() -> void; + auto emulatorRunLoop(uintptr_t) -> void; auto quit() -> void; //platform.cpp @@ -73,6 +74,8 @@ struct Program : ares::Platform { bool requestFrameAdvance = false; bool requestScreenshot = false; bool keyboardCaptured = false; + atomic loaded = false; + atomic unloading = false; struct State { u32 slot = 1; @@ -97,7 +100,19 @@ struct Program : ares::Platform { } message; vector messages; - maybe vblanksPerSecond; + atomic vblanksPerSecond = 0; + /// The emulator run loop mutex. The emulator thread will hold a lock on this mutex for the duration of its run loop, including while the thread is suspended. This means that the UI thread should only acquire this mutex when absolutely necessary, as there will be a severe UI responsiveness penalty acquiring it. + std::recursive_mutex programMutex; + + /// Mutex used to synchronize access to the status message queue. + std::recursive_mutex messageMutex; + + /// Mutex used to synchronize access to the input system. Polling occurs on the main thread while the results are read by the emulation thread. + std::recursive_mutex inputMutex; + +private: + atomic _quitting = false; + atomic _needsResize = false; }; extern Program program; diff --git a/desktop-ui/program/rewind.cpp b/desktop-ui/program/rewind.cpp index 046a15a89c..771de77c98 100644 --- a/desktop-ui/program/rewind.cpp +++ b/desktop-ui/program/rewind.cpp @@ -1,9 +1,11 @@ auto Program::rewindSetMode(Rewind::Mode mode) -> void { + lock_guard lock(programMutex); rewind.mode = mode; rewind.counter = 0; } auto Program::rewindReset() -> void { + lock_guard lock(programMutex); rewindSetMode(Rewind::Mode::Playing); rewind.history.reset(); rewind.length = settings.rewind.length; @@ -11,6 +13,7 @@ auto Program::rewindReset() -> void { } auto Program::rewindRun() -> void { + lock_guard lock(programMutex); if(!settings.general.rewind) return; //rewind disabled? if(rewind.mode == Rewind::Mode::Playing) { diff --git a/desktop-ui/program/states.cpp b/desktop-ui/program/states.cpp index 483a0aaa82..1ef0db9bc0 100644 --- a/desktop-ui/program/states.cpp +++ b/desktop-ui/program/states.cpp @@ -1,4 +1,5 @@ auto Program::stateSave(u32 slot) -> bool { + lock_guard lock(programMutex); if(!emulator) return false; auto location = emulator->locate(emulator->game->location, {".bs", slot}, settings.paths.saves); @@ -19,6 +20,7 @@ auto Program::stateSave(u32 slot) -> bool { } auto Program::stateLoad(u32 slot) -> bool { + lock_guard lock(programMutex); if(!emulator) return false; //Store current state for undo @@ -41,6 +43,7 @@ auto Program::stateLoad(u32 slot) -> bool { } auto Program::undoStateSave() -> bool { + lock_guard lock(programMutex); if(!emulator) return false; auto undoLocation = emulator->locate(emulator->game->location, ".bsu", settings.paths.saves); @@ -55,6 +58,7 @@ auto Program::undoStateSave() -> bool { } auto Program::undoStateLoad() -> bool { + lock_guard lock(programMutex); if(!emulator) return false; auto undoLocation = emulator->locate(emulator->game->location, ".blu", settings.paths.saves); @@ -75,6 +79,7 @@ auto Program::undoStateLoad() -> bool { } auto Program::clearUndoStates() -> void { + lock_guard lock(programMutex); if(!emulator) return; auto location = emulator->locate(emulator->game->location, ".blu", settings.paths.saves); diff --git a/desktop-ui/program/status.cpp b/desktop-ui/program/status.cpp index bf2614604c..48835ba760 100644 --- a/desktop-ui/program/status.cpp +++ b/desktop-ui/program/status.cpp @@ -1,16 +1,21 @@ auto Program::updateMessage() -> void { + //This function is called every iteration of the GUI run loop. Acquiring the emulator mutex would incur a severe responsiveness penalty, so instead synchronize vblanksPerSecond using std::atomics. + + /// `messages` may be accessed by the emulator thread, so synchronize access with messageMutex + messageMutex.lock(); + presentation.statusLeft.setText(message.text); - if(chrono::millisecond() - message.timestamp >= 2000) { message = {}; if (messages.size()) { - message = messages.takeFirst(); + message = messages.takeFirst(); } } + + messageMutex.unlock(); - if(vblanksPerSecond) { - presentation.statusRight.setText({vblanksPerSecond(), " VPS"}); - vblanksPerSecond.reset(); + if(vblanksPerSecond > 0) { + presentation.statusRight.setText({vblanksPerSecond.load(), " VPS"}); } if(!emulator) { @@ -22,9 +27,19 @@ auto Program::updateMessage() -> void { presentation.statusLeft.setText("Keyboard capture is active"); } } + + if(settings.debugServer.enabled) { + presentation.statusDebug.setText( + nall::GDB::server.getStatusText(settings.debugServer.port, settings.debugServer.useIPv4) + ); + } + + bool defocused = driverSettings.inputDefocusPause.checked() && !ruby::video.fullScreen() && !presentation.focused(); + if(emulator && defocused) message.text = "Paused"; } auto Program::showMessage(const string& text) -> void { + lock_guard messageLock(messageMutex); messages.append({chrono::millisecond(), text}); printf("%s\n", (const char*)text); } diff --git a/desktop-ui/program/utility.cpp b/desktop-ui/program/utility.cpp index 2940219819..d187c57d32 100644 --- a/desktop-ui/program/utility.cpp +++ b/desktop-ui/program/utility.cpp @@ -1,4 +1,5 @@ auto Program::pause(bool state) -> void { + lock_guard lock(programMutex); if(paused == state) return; paused = state; presentation.pauseEmulation.setChecked(paused); @@ -10,11 +11,13 @@ auto Program::pause(bool state) -> void { } auto Program::mute() -> void { + lock_guard lock(programMutex); settings.audio.mute = !settings.audio.mute; presentation.muteAudioSetting.setChecked(settings.audio.mute); } auto Program::paletteUpdate() -> void { + lock_guard lock(programMutex); if(!emulator) return; for(auto& screen : emulator->root->find()) { screen->setLuminance(settings.video.luminance); @@ -24,6 +27,7 @@ auto Program::paletteUpdate() -> void { } auto Program::runAheadUpdate() -> void { + lock_guard lock(programMutex); runAhead = settings.general.runAhead; if(!emulator) return; if(emulator->name == "Game Boy Advance") runAhead = false; //crashes immediately @@ -42,6 +46,7 @@ auto Program::captureScreenshot(const u32* data, u32 pitch, u32 width, u32 heigh } auto Program::openFile(BrowserDialog& dialog) -> string { + lock_guard lock(programMutex); BrowserWindow window; window.setTitle(dialog.title()); window.setPath(dialog.path()); @@ -53,6 +58,7 @@ auto Program::openFile(BrowserDialog& dialog) -> string { } auto Program::selectFolder(BrowserDialog& dialog) -> string { + lock_guard lock(programMutex); BrowserWindow window; window.setTitle(dialog.title()); window.setPath(dialog.path()); diff --git a/desktop-ui/settings/drivers.cpp b/desktop-ui/settings/drivers.cpp index e67882c5e4..d003a0d7ab 100644 --- a/desktop-ui/settings/drivers.cpp +++ b/desktop-ui/settings/drivers.cpp @@ -179,6 +179,7 @@ auto DriverSettings::videoRefresh() -> void { } auto DriverSettings::videoDriverUpdate() -> bool { + lock_guard lock(program.programMutex); if(emulator && settings.video.driver != "None" && MessageDialog( "Warning: incompatible drivers may cause this software to crash.\n" "Are you sure you want to change this driver while a game is loaded?" @@ -224,6 +225,7 @@ auto DriverSettings::audioRefresh() -> void { } auto DriverSettings::audioDriverUpdate() -> bool { + lock_guard lock(program.programMutex); if(emulator && settings.audio.driver != "None" && MessageDialog( "Warning: incompatible drivers may cause this software to crash.\n" "Are you sure you want to change this driver while a game is loaded?" @@ -247,6 +249,7 @@ auto DriverSettings::inputRefresh() -> void { } auto DriverSettings::inputDriverUpdate() -> bool { + lock_guard lock(program.programMutex); if(emulator && settings.input.driver != "None" && MessageDialog( "Warning: incompatible drivers may cause this software to crash.\n" "Are you sure you want to change this driver while a game is loaded?" diff --git a/desktop-ui/settings/hotkeys.cpp b/desktop-ui/settings/hotkeys.cpp index 257fb29352..b2111b40f4 100644 --- a/desktop-ui/settings/hotkeys.cpp +++ b/desktop-ui/settings/hotkeys.cpp @@ -48,6 +48,7 @@ auto HotkeySettings::refresh() -> void { } auto HotkeySettings::eventChange() -> void { + lock_guard lock(program.programMutex); assignButton.setEnabled(inputList.batched().size() == 1); clearButton.setEnabled(inputList.batched().size() >= 1); } diff --git a/desktop-ui/settings/input.cpp b/desktop-ui/settings/input.cpp index 21eeb9cced..84d4abd17c 100644 --- a/desktop-ui/settings/input.cpp +++ b/desktop-ui/settings/input.cpp @@ -160,6 +160,7 @@ auto InputSettings::eventAssign(TableViewCell cell, string binding) -> void { } auto InputSettings::eventAssign(TableViewCell cell) -> void { + lock_guard lock(program.programMutex); inputManager.poll(true); //clear any pending events first if(ruby::input.driver() == "None") return (void)MessageDialog().setText( diff --git a/desktop-ui/tools/cheats.cpp b/desktop-ui/tools/cheats.cpp index dc53201b8b..52857fcf48 100644 --- a/desktop-ui/tools/cheats.cpp +++ b/desktop-ui/tools/cheats.cpp @@ -16,7 +16,8 @@ auto CheatEditor::Cheat::update(string description, string code, bool enabled) - return *this; } -auto CheatEditor::construct() -> void { +auto CheatEditor::construct(std::recursive_mutex *programMutexIn) -> void { + this->programMutex = programMutexIn; setCollapsible(); setVisible(false); cheatsLabel.setText("Cheats").setFont(Font().setBold()); @@ -24,6 +25,7 @@ auto CheatEditor::construct() -> void { descriptionLabel.setText("Description:"); codeLabel.setText("Code:"); deleteButton.setText("Delete").onActivate([&] { + lock_guard lock(*programMutex); if(auto item = cheatList.selected()) { if(auto cheat = item.attribute("cheat")) { if(auto c = cheats.find([cheat](auto& c) { return &c == cheat; })) { @@ -38,6 +40,7 @@ auto CheatEditor::construct() -> void { }).setEnabled(false); saveButton.setText("Save").onActivate([&] { + lock_guard lock(*programMutex); string description = descriptionEdit.text(); string code = codeEdit.text(); @@ -51,6 +54,7 @@ auto CheatEditor::construct() -> void { }); cheatList.onToggle([&](auto cell) { + lock_guard lock(*programMutex); if(auto item = cheatList.selected()) { if(auto cheat = item.attribute("cheat")) { cheat->enabled = cell.checked(); @@ -59,6 +63,7 @@ auto CheatEditor::construct() -> void { }); cheatList.onChange([&] { + lock_guard lock(*programMutex); deleteButton.setEnabled(false); descriptionEdit.setText(""); codeEdit.setText(""); @@ -74,6 +79,7 @@ auto CheatEditor::construct() -> void { } auto CheatEditor::reload() -> void { + lock_guard lock(*programMutex); cheats.reset(); location = emulator->locate(emulator->game->location, {".cheats.bml"}); @@ -92,6 +98,7 @@ auto CheatEditor::reload() -> void { } auto CheatEditor::refresh() -> void { + lock_guard lock(*programMutex); cheatList.reset(); cheatList.setHeadered(); cheatList.append(TableViewColumn()); @@ -111,6 +118,7 @@ auto CheatEditor::refresh() -> void { } auto CheatEditor::unload() -> void { + lock_guard lock(*programMutex); bool hasCheats = cheats.size() > 0; bool isCheatLocation = location.endsWith(".cheats.bml"); @@ -137,6 +145,7 @@ auto CheatEditor::unload() -> void { } auto CheatEditor::find(uint address) -> maybe { + lock_guard lock(*programMutex); for(auto& cheat : cheats) { if(!cheat.enabled) continue; if(auto result = cheat.addressValuePairs.find(address)) return result(); diff --git a/desktop-ui/tools/graphics.cpp b/desktop-ui/tools/graphics.cpp index eeab3bb9e0..5dbcfe5996 100644 --- a/desktop-ui/tools/graphics.cpp +++ b/desktop-ui/tools/graphics.cpp @@ -1,4 +1,5 @@ -auto GraphicsViewer::construct() -> void { +auto GraphicsViewer::construct(std::recursive_mutex *programMutexIn) -> void { + this->programMutex = programMutexIn; setCollapsible(); setVisible(false); @@ -6,6 +7,7 @@ auto GraphicsViewer::construct() -> void { graphicsList.onChange([&] { eventChange(); }); graphicsView.setAlignment({0.0, 0.0}); exportButton.setText("Export").onActivate([&] { + lock_guard lock(*programMutex); eventExport(); }); liveOption.setText("Live"); diff --git a/desktop-ui/tools/memory.cpp b/desktop-ui/tools/memory.cpp index 17c089fe94..85860c5f2a 100644 --- a/desktop-ui/tools/memory.cpp +++ b/desktop-ui/tools/memory.cpp @@ -1,4 +1,5 @@ -auto MemoryEditor::construct() -> void { +auto MemoryEditor::construct(std::recursive_mutex *programMutexIn) -> void { + this->programMutex = programMutexIn; setCollapsible(); setVisible(false); @@ -8,11 +9,13 @@ auto MemoryEditor::construct() -> void { memoryEditor.setRows(24); exportButton.setText("Export").onActivate([&] { + lock_guard lock(*programMutex); eventExport(); }); gotoLabel.setText("Goto:"); gotoAddress.setFont(Font().setFamily(Font::Mono)).onActivate([&] { + lock_guard lock(*programMutex); auto address = gotoAddress.text().hex(); memoryEditor.setAddress(address); gotoAddress.setText(); @@ -21,6 +24,7 @@ auto MemoryEditor::construct() -> void { liveOption.setText("Live"); refreshButton.setText("Refresh").onActivate([&] { + lock_guard lock(*programMutex); memoryEditor.update(); }); } @@ -58,6 +62,7 @@ auto MemoryEditor::eventChange() -> void { return memory->read(address); }); memoryEditor.onWrite([=](u32 address, u8 data) -> void { + lock_guard lock(*programMutex); return memory->write(address, data); }); } @@ -71,6 +76,7 @@ auto MemoryEditor::eventChange() -> void { } auto MemoryEditor::eventExport() -> void { + lock_guard lock(*programMutex); if(auto item = memoryList.selected()) { if(auto memory = item.attribute("node")) { auto identifier = memory->name().downcase().replace(" ", "-"); diff --git a/desktop-ui/tools/tools.cpp b/desktop-ui/tools/tools.cpp index 09b4ed66f4..2d2a2ae525 100644 --- a/desktop-ui/tools/tools.cpp +++ b/desktop-ui/tools/tools.cpp @@ -17,7 +17,7 @@ StreamManager& streamManager = toolsWindow.streamManager; PropertiesViewer& propertiesViewer = toolsWindow.propertiesViewer; TraceLogger& traceLogger = toolsWindow.traceLogger; -ToolsWindow::ToolsWindow() { +ToolsWindow::ToolsWindow(std::recursive_mutex *programMutex) { panelList.append(ListViewItem().setText("Manifest").setIcon(Icon::Emblem::Binary)); panelList.append(ListViewItem().setText("Cheats").setIcon(Icon::Emblem::Text)); @@ -43,9 +43,9 @@ ToolsWindow::ToolsWindow() { panelContainer.append(homePanel, Size{~0, ~0}); manifestViewer.construct(); - cheatEditor.construct(); - memoryEditor.construct(); - graphicsViewer.construct(); + cheatEditor.construct(programMutex); + memoryEditor.construct(programMutex); + graphicsViewer.construct(programMutex); streamManager.construct(); propertiesViewer.construct(); traceLogger.construct(); diff --git a/desktop-ui/tools/tools.hpp b/desktop-ui/tools/tools.hpp index d58b9cf41f..1c3464cbcd 100644 --- a/desktop-ui/tools/tools.hpp +++ b/desktop-ui/tools/tools.hpp @@ -12,13 +12,15 @@ struct ManifestViewer : VerticalLayout { }; struct CheatEditor : VerticalLayout { - auto construct() -> void; + auto construct(std::recursive_mutex *programMutex) -> void; auto reload() -> void; auto unload() -> void; auto refresh() -> void; auto setVisible(bool visible = true) -> CheatEditor&; auto find(u32 address) -> maybe; + + std::recursive_mutex *programMutex; Label cheatsLabel{this, Size{~0, 0}, 5}; HorizontalLayout editLayout{this, Size{~0, 0}}; @@ -47,7 +49,7 @@ struct CheatEditor : VerticalLayout { }; struct MemoryEditor : VerticalLayout { - auto construct() -> void; + auto construct(std::recursive_mutex *programMutex) -> void; auto reload() -> void; auto unload() -> void; auto refresh() -> void; @@ -55,6 +57,8 @@ struct MemoryEditor : VerticalLayout { auto eventChange() -> void; auto eventExport() -> void; auto setVisible(bool visible = true) -> MemoryEditor&; + + std::recursive_mutex *programMutex; Label memoryLabel{this, Size{~0, 0}, 5}; ComboButton memoryList{this, Size{~0, 0}}; @@ -69,7 +73,7 @@ struct MemoryEditor : VerticalLayout { }; struct GraphicsViewer : VerticalLayout { - auto construct() -> void; + auto construct(std::recursive_mutex *programMutex) -> void; auto reload() -> void; auto unload() -> void; auto refresh() -> void; @@ -77,6 +81,8 @@ struct GraphicsViewer : VerticalLayout { auto eventChange() -> void; auto eventExport() -> void; auto setVisible(bool visible = true) -> GraphicsViewer&; + + std::recursive_mutex *programMutex; Label graphicsLabel{this, Size{~0, 0}, 5}; ComboButton graphicsList{this, Size{~0, 0}}; @@ -128,7 +134,7 @@ struct TraceLogger : VerticalLayout { }; struct ToolsWindow : Window { - ToolsWindow(); + ToolsWindow(std::recursive_mutex *programMutex); auto show(const string& panel) -> void; auto eventChange() -> void; diff --git a/hiro/cocoa/application.cpp b/hiro/cocoa/application.cpp index b796ca4d59..fbced19ef5 100644 --- a/hiro/cocoa/application.cpp +++ b/hiro/cocoa/application.cpp @@ -49,7 +49,7 @@ auto pApplication::run() -> void { applicationTimer = [NSTimer scheduledTimerWithTimeInterval:0.0 target:cocoaDelegate selector:@selector(run:) userInfo:nil repeats:YES]; //below line is needed to run application during window resize; however it has a large performance penalty on the resize smoothness - //[[NSRunLoop currentRunLoop] addTimer:applicationTimer forMode:NSEventTrackingRunLoopMode]; + [[NSRunLoop currentRunLoop] addTimer:applicationTimer forMode:NSRunLoopCommonModes]; } [[NSUserDefaults standardUserDefaults] registerDefaults:@{ //@"NO" is not a mistake; the value really needs to be a string diff --git a/hiro/core/core.hpp b/hiro/core/core.hpp index 6fc8179b02..fffacc8203 100644 --- a/hiro/core/core.hpp +++ b/hiro/core/core.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/hiro/windows/window.cpp b/hiro/windows/window.cpp index 118ff0d799..b94efb9b31 100644 --- a/hiro/windows/window.cpp +++ b/hiro/windows/window.cpp @@ -24,7 +24,7 @@ u32 pWindow::minimumStatusHeight = 0; auto pWindow::initialize() -> void { pApplication::state().modalTimer.setInterval(1); - pApplication::state().modalTimer.onActivate([] { Application::doMain(); }); + pApplication::state().modalTimer.onActivate([] { /*Application::doMain();*/ }); HWND hwnd = CreateWindow(L"hiroWindow", L"", ResizableStyle, 128, 128, 256, 256, 0, 0, GetModuleHandle(0), 0); HWND hstatus = CreateWindow(STATUSCLASSNAME, L"", WS_CHILD, 0, 0, 0, 0, hwnd, nullptr, GetModuleHandle(0), 0); diff --git a/nall/nall/thread.cpp b/nall/nall/thread.cpp index 73e3cc311b..2446fcfee9 100644 --- a/nall/nall/thread.cpp +++ b/nall/nall/thread.cpp @@ -29,11 +29,11 @@ NALL_HEADER_INLINE auto thread::join() -> void { NALL_HEADER_INLINE auto thread::create(const function& callback, uintptr parameter, u32 stacksize) -> thread { thread instance; - + auto context = new thread::context; context->callback = callback; context->parameter = parameter; - + instance.handle = CreateThread(nullptr, stacksize, _threadCallback, (void*)context, 0, nullptr); return instance; } @@ -48,6 +48,11 @@ NALL_HEADER_INLINE auto thread::exit() -> void { ExitThread(0); } +NALL_HEADER_INLINE auto thread::setName(string name) -> void { + HANDLE hThread = GetCurrentThread(); + HRESULT hr = SetThreadDescription(hThread, (wchar_t*)utf16_t(name)); +} + #endif } diff --git a/nall/nall/thread.hpp b/nall/nall/thread.hpp index 17714a1a66..9edb20632e 100644 --- a/nall/nall/thread.hpp +++ b/nall/nall/thread.hpp @@ -44,6 +44,7 @@ struct thread { static auto create(const function& callback, uintptr parameter = 0, u32 stacksize = 0) -> thread; static auto detach() -> void; static auto exit() -> void; + static auto setName(string name) -> void; struct context { function void> callback; @@ -91,6 +92,14 @@ inline auto thread::exit() -> void { pthread_exit(nullptr); } +inline auto thread::setName(string name) -> void { +#if defined(__APPLE__) + pthread_setname_np(name); +#else + pthread_setname_np(pthread_self(), name); +#endif +} + } #elif defined(API_WINDOWS) @@ -120,6 +129,7 @@ struct thread { static auto create(const function& callback, uintptr parameter = 0, u32 stacksize = 0) -> thread; static auto detach() -> void; static auto exit() -> void; + static auto setName(string name) -> void; struct context { function void> callback; diff --git a/ruby/video/metal/metal.cpp b/ruby/video/metal/metal.cpp index d2961c6c71..f266681778 100644 --- a/ruby/video/metal/metal.cpp +++ b/ruby/video/metal/metal.cpp @@ -159,29 +159,27 @@ struct VideoMetal : VideoDriver, Metal { } auto setShader(string pathname) -> bool override { - if (_filterChain != NULL) { - _libra.mtl_filter_chain_free(&_filterChain); - } - - if (_preset != NULL) { - _libra.preset_free(&_preset); - } - - if(file::exists(pathname)) { - if (auto error = _libra.preset_create(pathname.data(), &_preset)) { - print(string{"Metal: Failed to load shader: ", pathname, "\n"}); - _libra.error_print(error); - return false; + dispatch_async(_renderQueue, ^{ + if (_filterChain != NULL) { + _libra.mtl_filter_chain_free(&_filterChain); } - - if (auto error = _libra.mtl_filter_chain_create(&_preset, _commandQueue, nil, &_filterChain)) { - print(string{"Metal: Failed to create filter chain for: ", pathname, "\n"}); - _libra.error_print(error); - return false; - }; - } else { - return false; - } + + if (_preset != NULL) { + _libra.preset_free(&_preset); + } + + if(file::exists(pathname)) { + if (auto error = _libra.preset_create(pathname.data(), &_preset)) { + print(string{"Metal: Failed to load shader: ", pathname, "\n"}); + _libra.error_print(error); + } + + if (auto error = _libra.mtl_filter_chain_create(&_preset, _commandQueue, nil, &_filterChain)) { + print(string{"Metal: Failed to create filter chain for: ", pathname, "\n"}); + _libra.error_print(error); + }; + } + }); return true; } @@ -203,6 +201,7 @@ struct VideoMetal : VideoDriver, Metal { } auto size(u32& width, u32& height) -> void override { + lock_guard lock(mutex); if ((_viewWidth == width && _viewHeight == height) && (_viewWidth != 0 && _viewHeight != 0)) { return; } auto area = [view convertRectToBacking:[view bounds]]; width = area.size.width; @@ -297,6 +296,7 @@ struct VideoMetal : VideoDriver, Metal { /// Synchronously copy the current framebuffer to a Metal texture, then call into the render dispatch queue /// either synchronously or asynchronously depending on whether blocking is on and VRR is supported. + lock_guard lock(mutex); if (depth >= kMaxSourceBuffersInFlight) { //if we are running very behind, drop this frame return; @@ -348,6 +348,8 @@ struct VideoMetal : VideoDriver, Metal { /// acquiring for as long as possible. dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); + + //lock_guard lock(mutex); id commandBuffer = [_commandQueue commandBuffer]; @@ -417,9 +419,11 @@ struct VideoMetal : VideoDriver, Metal { id drawable = view.currentDrawable; + __block VideoMetal &strongSelf = self; + if (@available(macOS 10.15.4, *)) { [drawable addPresentedHandler:^(id drawable) { - self.drawableWasPresented(drawable); + strongSelf.drawableWasPresented(drawable); depth--; }]; } @@ -595,31 +599,35 @@ struct VideoMetal : VideoDriver, Metal { } auto terminate() -> void { - _ready = false; - - _commandQueue = nullptr; - _library = nullptr; - - _vertexBuffer = nullptr; - for (int i = 0; i < kMaxSourceBuffersInFlight; i++) { - _sourceTextures[i] = nullptr; - } - _mtlVertexDescriptor = nullptr; - - _renderToTextureRenderPassDescriptor = nullptr; - _renderTargetTexture = nullptr; - _renderToTextureRenderPipeline = nullptr; - - _drawableRenderPipeline = nullptr; - - if (_filterChain) { - _libra.mtl_filter_chain_free(&_filterChain); - } - _device = nullptr; - - if (view) { - [view removeFromSuperview]; - view = nil; + if(_renderQueue) { + dispatch_sync(_renderQueue, ^{ + _ready = false; + + _commandQueue = nullptr; + _library = nullptr; + + _vertexBuffer = nullptr; + for (int i = 0; i < kMaxSourceBuffersInFlight; i++) { + _sourceTextures[i] = nullptr; + } + _mtlVertexDescriptor = nullptr; + + _renderToTextureRenderPassDescriptor = nullptr; + _renderTargetTexture = nullptr; + _renderToTextureRenderPipeline = nullptr; + + _drawableRenderPipeline = nullptr; + + if (_filterChain) { + _libra.mtl_filter_chain_free(&_filterChain); + } + _device = nullptr; + + if (view) { + [view removeFromSuperview]; + view = nil; + } + }); } }