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..fd50560065 100644 --- a/ares/n64/system/system.cpp +++ b/ares/n64/system/system.cpp @@ -77,6 +77,10 @@ auto System::game() -> string { } auto System::run() -> void { + if(_vulkanNeedsLoad) { + vulkan.load(node); + _vulkanNeedsLoad = false; + } cpu.main(); } @@ -134,11 +138,9 @@ 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(); + _vulkanNeedsLoad = true; return true; } @@ -308,6 +310,7 @@ auto System::unload() -> void { if(vi.screen) vi.screen->quit(); //stop video thread #if defined(VULKAN) vulkan.unload(); + _vulkanNeedsLoad = false; #endif cartridgeSlot.unload(); controllerPort1.unload(); diff --git a/ares/n64/system/system.hpp b/ares/n64/system/system.hpp index f0b3ecba56..f03809010d 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; + + atomic _vulkanNeedsLoad = false; auto initDebugHooks() -> void; diff --git a/ares/n64/vi/vi.cpp b/ares/n64/vi/vi.cpp index 86f4dcaa44..404ec274d5 100644 --- a/ares/n64/vi/vi.cpp +++ b/ares/n64/vi/vi.cpp @@ -111,13 +111,12 @@ auto VI::main() -> void { lineDuration = io.hsyncLeap[io.leapPattern.bit(io.leapCounter)]; step(io.quarterLineDuration); } else { - // arbitrarily call screen->frame() every once in a while to keep the UI responsive. + // Arbitrarily call screen->frame() every once in a while to keep the UI responsive. // We do that every 200 simulated lines of 0x800 quarter-clocks. This is just arbitrary, // the real VI is not clocking at all when inactive. io.vcounter = 0; if(++inactiveCounter >= 200) { inactiveCounter = 0; - 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..02fbfc5a51 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 programLock(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 programLock(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 programLock(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 programLock(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 programLock(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 programLock(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 programLock(program.programMutex); if(auto screen = root->scan("Screen")) { screen->refresh(); } } auto Emulator::setBoolean(const string& name, bool value) -> bool { + lock_guard programLock(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 programLock(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 programLock(program.programMutex); if(auto screen = root->scan("Screen")) { screen->setColorBleed(screen->height() < 720 ? value : false); //only apply to sub-HD content return true; @@ -243,6 +253,7 @@ auto Emulator::input(ares::Node::Input::Input input) -> void { input->lastPoll = thisPoll; } + lock_guard programinputLock(program.inputMutex); auto device = ares::Node::parent(input); if(!device) return; @@ -281,6 +292,7 @@ auto Emulator::input(ares::Node::Input::Input input) -> void { } auto Emulator::inputKeyboard(string name) -> bool { + lock_guard programinputLock(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..84430d3108 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 inputLock(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 inputLock(program.inputMutex); if(binding >= BindingLimit) return; assignments[binding] = assignment; bind(); } auto InputMapping::unbind() -> void { + lock_guard inputLock(program.inputMutex); for(u32 binding : range(BindingLimit)) unbind(binding); } auto InputMapping::unbind(u32 binding) -> void { + lock_guard inputLock(program.inputMutex); if(binding >= BindingLimit) return; bindings[binding] = {}; assignments[binding] = {}; } auto InputMapping::Binding::icon() -> multiFactorImage { + lock_guard inputLock(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 inputLock(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 inputLock(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 inputLock(program.inputMutex); s16 result = 0; for(auto& binding : bindings) { @@ -165,11 +173,13 @@ auto InputDigital::value() -> s16 { } auto InputDigital::pressed() -> bool { + lock_guard inputLock(program.inputMutex); return value() != 0; } auto InputHotkey::value() -> s16 { + lock_guard inputLock(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 inputLock(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 inputLock(program.inputMutex); s32 result = 0; for(auto& binding : bindings) { @@ -274,12 +286,14 @@ auto InputAnalog::value() -> s16 { } auto InputAnalog::pressed() -> bool { + lock_guard inputLock(program.inputMutex); return value() > 16384; } // auto InputAbsolute::bind(u32 binding, shared_pointer device, u32 groupID, u32 inputID, s16 oldValue, s16 newValue) -> bool { + lock_guard inputLock(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 inputLock(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 inputLock(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 inputLock(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 inputLock(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 inputLock(program.inputMutex); createHotkeys(); } auto InputManager::bind() -> void { + lock_guard inputLock(program.inputMutex); for(auto& port : virtualPorts) { for(auto& input : port.pad.inputs) input.mapping->bind(); for(auto& input : port.mouse.inputs) input.mapping->bind(); @@ -485,6 +505,7 @@ auto InputManager::poll(bool force) -> void { if(thisPoll - lastPoll < pollFrequency && !force) return; lastPoll = thisPoll; + lock_guard inputLock(program.inputMutex); auto devices = ruby::input.poll(); bool changed = devices.size() != this->devices.size(); if(!changed) { @@ -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 inputLock(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..cf837d1273 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 programLock(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 programLock(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 programLock(program.programMutex); program.undoStateSave(); undoSaveStateMenu.setEnabled(false); }); undoLoadStateMenu.setText("Undo Last Load State").setIcon(Icon::Edit::Undo).setEnabled(false); undoLoadStateMenu.onActivate([&] { + lock_guard programLock(program.programMutex); program.undoStateLoad(); undoLoadStateMenu.setEnabled(false); }); captureScreenshot.setText("Capture Screenshot").setIcon(Icon::Emblem::Image).onActivate([&] { + lock_guard programLock(program.programMutex); program.requestScreenshot = true; }); pauseEmulation.setText("Pause Emulation").onToggle([&] { + lock_guard programLock(program.programMutex); program.pause(!program.paused); }); reloadGame.setText("Reload Game").setIcon(Icon::Action::Refresh).onActivate([&] { + lock_guard programLock(program.programMutex); program.load(emulator, emulator->game->location); }); frameAdvance.setText("Frame Advance").setIcon(Icon::Media::Play).onActivate([&] { + lock_guard programLock(program.programMutex); if (!program.paused) program.pause(true); program.requestFrameAdvance = true; }); @@ -211,6 +219,7 @@ Presentation::Presentation() { }); viewport.setDroppable().onDrop([&](auto filenames) { + lock_guard programLock(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 programLock(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 programLock(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 programLock(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 programLock(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..f49283cca8 100644 --- a/desktop-ui/program/drivers.cpp +++ b/desktop-ui/program/drivers.cpp @@ -1,4 +1,5 @@ auto Program::videoDriverUpdate() -> void { + lock_guard programLock(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 programLock(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 programLock(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 programLock(programMutex); if(!ruby::video.hasFullScreen()) return; ruby::video.clear(); @@ -55,6 +59,7 @@ auto Program::videoFullScreenToggle() -> void { } auto Program::videoPseudoFullScreenToggle() -> void { + lock_guard programLock(programMutex); if(ruby::video.fullScreen()) return; if(!presentation.fullScreen()) { @@ -69,6 +74,7 @@ auto Program::videoPseudoFullScreenToggle() -> void { } auto Program::audioDriverUpdate() -> void { + lock_guard programLock(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 programLock(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 programLock(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 programLock(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 programLock(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..d8c4000aee 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 programLock(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) +/// Loads an emulator and, optionally, a ROM from the given location. auto Program::load(shared_pointer emulator, string location) -> bool { + lock_guard programLock(programMutex); unload(); ::emulator = emulator; @@ -22,17 +24,15 @@ auto Program::load(shared_pointer emulator, string location) -> bool { // For arcade systems, show the game browser dialog as we're using MAME-compatible roms if(emulator->arcade() && !location) { gameBrowserWindow.show(emulator); - - // Temporarily pretend that the load failed to prevent UI hang - // The browser dialog will call load() again when necessary - ::emulator.reset(); return false; } return load(location); } +/// Loads a ROM for an already-loaded emulator. auto Program::load(string location) -> bool { + lock_guard programLock(programMutex); if(settings.debugServer.enabled) { nall::GDB::server.reset(); } @@ -94,6 +94,7 @@ auto Program::load(string location) -> bool { } auto Program::unload() -> void { + lock_guard programLock(programMutex); if(!emulator) return; nall::GDB::server.close(); diff --git a/desktop-ui/program/program.cpp b/desktop-ui/program/program.cpp index 8977abeb25..fd280448e1 100644 --- a/desktop-ui/program/program.cpp +++ b/desktop-ui/program/program.cpp @@ -8,6 +8,7 @@ #include "drivers.cpp" Program program; +thread worker; auto Program::create() -> void { ares::platform = this; @@ -19,6 +20,8 @@ auto Program::create() -> void { driverSettings.videoRefresh(); driverSettings.audioRefresh(); driverSettings.inputRefresh(); + + worker = thread::create({&Program::emulatorRunLoop, this}); if(startGameLoad) { auto gameToLoad = startGameLoad.takeFirst(); @@ -41,71 +44,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 programLock(programMutex); + if(!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 +131,8 @@ auto Program::main() -> void { } auto Program::quit() -> void { + _quitting = true; + worker.join(); unload(); Application::processEvents(); Application::quit(); diff --git a/desktop-ui/program/program.hpp b/desktop-ui/program/program.hpp index fd5ad593e5..5903984043 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 @@ -97,7 +98,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. 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 manage access to the status message queue. + std::recursive_mutex messageMutex; + + /// Mutex used to manage 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..5295718a81 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 programLock(programMutex); rewind.mode = mode; rewind.counter = 0; } auto Program::rewindReset() -> void { + lock_guard programLock(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 programLock(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..f658cde932 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 programLock(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 programLock(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 programLock(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 programLock(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 programLock(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..5ddaa35e1d 100644 --- a/desktop-ui/program/status.cpp +++ b/desktop-ui/program/status.cpp @@ -1,16 +1,19 @@ auto Program::updateMessage() -> void { - presentation.statusLeft.setText(message.text); - - if(chrono::millisecond() - message.timestamp >= 2000) { - message = {}; - if (messages.size()) { + { + // This function is called every iteration of the GUI run loop. Acquiring the emulator mutex would incur a severe + // responsiveness penalty, so use a dedicated mutex for message passing. + lock_guard messageLock(messageMutex); + presentation.statusLeft.setText(message.text); + if(chrono::millisecond() - message.timestamp >= 2000) { + message = {}; + if (messages.size()) { message = messages.takeFirst(); + } } } - if(vblanksPerSecond) { - presentation.statusRight.setText({vblanksPerSecond(), " VPS"}); - vblanksPerSecond.reset(); + if(vblanksPerSecond > 0) { + presentation.statusRight.setText({vblanksPerSecond.load(), " VPS"}); } if(!emulator) { @@ -22,9 +25,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..d50a69e9bd 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 programLock(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 programLock(programMutex); settings.audio.mute = !settings.audio.mute; presentation.muteAudioSetting.setChecked(settings.audio.mute); } auto Program::paletteUpdate() -> void { + lock_guard programLock(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 programLock(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 programLock(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 programLock(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..4e5b78b9d5 100644 --- a/desktop-ui/settings/drivers.cpp +++ b/desktop-ui/settings/drivers.cpp @@ -183,6 +183,7 @@ auto DriverSettings::videoDriverUpdate() -> bool { "Warning: incompatible drivers may cause this software to crash.\n" "Are you sure you want to change this driver while a game is loaded?" ).setAlignment(settingsWindow).question() != "Yes") return false; + lock_guard programLock(program.programMutex); program.videoDriverUpdate(); videoRefresh(); return true; @@ -228,6 +229,7 @@ auto DriverSettings::audioDriverUpdate() -> bool { "Warning: incompatible drivers may cause this software to crash.\n" "Are you sure you want to change this driver while a game is loaded?" ).setAlignment(settingsWindow).question() != "Yes") return false; + lock_guard programLock(program.programMutex); program.audioDriverUpdate(); audioRefresh(); return true; @@ -251,6 +253,7 @@ auto DriverSettings::inputDriverUpdate() -> bool { "Warning: incompatible drivers may cause this software to crash.\n" "Are you sure you want to change this driver while a game is loaded?" ).setAlignment(settingsWindow).question() != "Yes") return false; + lock_guard programLock(program.programMutex); program.inputDriverUpdate(); inputRefresh(); return true; diff --git a/desktop-ui/settings/hotkeys.cpp b/desktop-ui/settings/hotkeys.cpp index 257fb29352..b8247c1adf 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 programLock(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..bf568838cd 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 programLock(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 ba6d15f53f..867a674497 100644 --- a/hiro/cocoa/application.cpp +++ b/hiro/cocoa/application.cpp @@ -144,8 +144,7 @@ auto pApplication::run() -> void { if(Application::state().onMain) { 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..6e69feaabe 100644 --- a/hiro/windows/window.cpp +++ b/hiro/windows/window.cpp @@ -24,7 +24,6 @@ u32 pWindow::minimumStatusHeight = 0; auto pWindow::initialize() -> void { pApplication::state().modalTimer.setInterval(1); - 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..38f358cc06 100644 --- a/nall/nall/thread.cpp +++ b/nall/nall/thread.cpp @@ -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..a2041d217d 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; @@ -417,9 +416,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 +596,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; + } + }); } }