diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 934545f8..61656efd 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -12,7 +12,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "103.16"; + static const string Version = "103.17"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/target-tomoko/GNUmakefile b/higan/target-tomoko/GNUmakefile index 5506d0da..91e86567 100644 --- a/higan/target-tomoko/GNUmakefile +++ b/higan/target-tomoko/GNUmakefile @@ -19,18 +19,18 @@ ui_objects += $(if $(call streq,$(platform),windows),ui-resource) # platform ifeq ($(platform),windows) ruby += video.direct3d #video.wgl video.directdraw video.gdi - ruby += audio.asio #audio.wasapi audio.xaudio2 audio.directsound + ruby += audio.asio audio.wasapi #audio.xaudio2 audio.directsound ruby += input.windows else ifeq ($(platform),macosx) ruby += #video.cgl ruby += audio.openal ruby += #input.quartz input.carbon else ifeq ($(platform),linux) - ruby += video.xshm #video.glx video.xv video.xshm video.sdl - ruby += audio.oss audio.openal #audio.alsa audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao - ruby += input.sdl input.xlib #input.udev input.sdl input.xlib + ruby += video.xshm video.sdl #video.glx video.xv + ruby += audio.oss audio.openal #audio.alsa audio.pulseaudio audio.pulseaudiosimple audio.ao + ruby += input.sdl input.xlib #input.udev else ifeq ($(platform),bsd) - ruby += video.xshm #video.glx video.xv video.xshm video.sdl + ruby += video.xshm #video.glx video.xv ruby += audio.oss audio.openal ruby += input.sdl input.xlib endif diff --git a/higan/target-tomoko/presentation/presentation.cpp b/higan/target-tomoko/presentation/presentation.cpp index d41aeaf2..d71d942e 100644 --- a/higan/target-tomoko/presentation/presentation.cpp +++ b/higan/target-tomoko/presentation/presentation.cpp @@ -82,7 +82,7 @@ Presentation::Presentation() { program->updateVideoShader(); }); loadShaders(); - synchronizeVideo.setText("Synchronize Video").setChecked(settings["Video/Synchronize"].boolean()).onToggle([&] { + synchronizeVideo.setText("Synchronize Video").setChecked(settings["Video/Synchronize"].boolean()).setVisible(false).onToggle([&] { settings["Video/Synchronize"].setValue(synchronizeVideo.checked()); video->setBlocking(synchronizeVideo.checked()); }); @@ -99,9 +99,9 @@ Presentation::Presentation() { statusBar.setVisible(showStatusBar.checked()); if(visible()) resizeViewport(); }); - showVideoSettings.setText("Video Settings ...").onActivate([&] { settingsManager->show(0); }); - showAudioSettings.setText("Audio Settings ...").onActivate([&] { settingsManager->show(1); }); - showInputSettings.setText("Input Settings ...").onActivate([&] { + showVideoSettings.setText("Video ...").onActivate([&] { settingsManager->show(0); }); + showAudioSettings.setText("Audio ...").onActivate([&] { settingsManager->show(1); }); + showInputSettings.setText("Input ...").onActivate([&] { if(emulator) { //default input panel to current core's input settings for(auto item : settingsManager->input.emulatorList.items()) { @@ -114,8 +114,8 @@ Presentation::Presentation() { } settingsManager->show(2); }); - showHotkeySettings.setText("Hotkey Settings ...").onActivate([&] { settingsManager->show(3); }); - showAdvancedSettings.setText("Advanced Settings ...").onActivate([&] { settingsManager->show(4); }); + showHotkeySettings.setText("Hotkeys ...").onActivate([&] { settingsManager->show(3); }); + showAdvancedSettings.setText("Advanced ...").onActivate([&] { settingsManager->show(4); }); toolsMenu.setText("Tools").setVisible(false); saveQuickStateMenu.setText("Save Quick State"); @@ -130,9 +130,9 @@ Presentation::Presentation() { loadSlot3.setText("Slot 3").onActivate([&] { program->loadState(3); }); loadSlot4.setText("Slot 4").onActivate([&] { program->loadState(4); }); loadSlot5.setText("Slot 5").onActivate([&] { program->loadState(5); }); - cheatEditor.setText("Cheat Editor").onActivate([&] { toolsManager->show(0); }); - stateManager.setText("State Manager").onActivate([&] { toolsManager->show(1); }); - manifestViewer.setText("Manifest Viewer").onActivate([&] { toolsManager->show(2); }); + cheatEditor.setText("Cheat Editor ...").onActivate([&] { toolsManager->show(0); }); + stateManager.setText("State Manager ...").onActivate([&] { toolsManager->show(1); }); + manifestViewer.setText("Manifest Viewer ...").onActivate([&] { toolsManager->show(2); }); helpMenu.setText("Help"); documentation.setText("Documentation ...").onActivate([&] { diff --git a/higan/target-tomoko/program/program.cpp b/higan/target-tomoko/program/program.cpp index 3c3255af..4da6a1d4 100644 --- a/higan/target-tomoko/program/program.cpp +++ b/higan/target-tomoko/program/program.cpp @@ -41,6 +41,7 @@ Program::Program(string_vector args) { presentation->clearViewport(); audio = Audio::create(settings["Audio/Driver"].text()); + audio->setExclusive(settings["Audio/Exclusive"].boolean()); audio->setContext(presentation->viewport.handle()); audio->setDevice(settings["Audio/Device"].text()); audio->setBlocking(settings["Audio/Synchronize"].boolean()); diff --git a/ruby/audio/wasapi.cpp b/ruby/audio/wasapi.cpp index 59decb3a..50932fe0 100644 --- a/ruby/audio/wasapi.cpp +++ b/ruby/audio/wasapi.cpp @@ -15,7 +15,7 @@ struct AudioWASAPI : Audio { Information information; information.devices = {"Default"}; information.channels = {2}; - information.frequencies = {}; + information.frequencies = {(double)_frequency}; information.latencies = {20, 40, 60, 80, 100}; return information; } @@ -51,32 +51,31 @@ struct AudioWASAPI : Audio { } auto clear() -> void { + _queue.read = 0; + _queue.write = 0; + _queue.count = 0; + if(!_audioClient) return; _audioClient->Stop(); _audioClient->Reset(); - for(auto n : range(available())) write(0, 0); _audioClient->Start(); } auto output(const double samples[]) -> void { - _queuedFrames.append(int16_t(samples[0] * 32768.0) << 0 | int16_t(samples[1] * 32768.0) << 16); - - if(!available() && _queuedFrames.size() >= _bufferSize) { - if(_blocking) { - while(!available()); //wait for free sample slot - } else { - _queuedFrames.takeLeft(); //drop sample (run ahead) + if(_queue.count < _bufferSize) { + for(uint n : range(_channels)) { + _queue.samples[_queue.write][n] = samples[n]; } - } - - uint32_t cachedFrame = 0; - for(auto n : range(available())) { - if(_queuedFrames) cachedFrame = _queuedFrames.takeLeft(); - write(cachedFrame >> 0, cachedFrame >> 16); + _queue.write++; + _queue.count++; + } else if(WaitForSingleObject(_eventHandle, _blocking ? INFINITE : 0) == WAIT_OBJECT_0) { + write(); } } private: auto initialize() -> bool { + terminate(); + if(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&_enumerator) != S_OK) return false; if(_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &_audioDevice) != S_OK) return false; if(_audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&_audioClient) != S_OK) return false; @@ -87,25 +86,35 @@ private: _waveFormat = (WAVEFORMATEX*)_propVariant.blob.pBlobData; if(_audioClient->GetDevicePeriod(nullptr, &_devicePeriod) != S_OK) return false; auto latency = max(_devicePeriod, (REFERENCE_TIME)_latency * 10'000); //1ms to 100ns units - if(_audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, 0, latency, latency, _waveFormat, nullptr) != S_OK) return false; + auto result = _audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, _waveFormat, nullptr); + if(result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { + if(_audioClient->GetBufferSize(&_bufferSize) != S_OK) return false; + _audioClient->Release(); + latency = (REFERENCE_TIME)(10'000 * 1'000 * _bufferSize / _waveFormat->nSamplesPerSec); + if(_audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&_audioClient) != S_OK) return false; + result = _audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, latency, _waveFormat, nullptr); + } + if(result != S_OK) return false; DWORD taskIndex = 0; _taskHandle = AvSetMmThreadCharacteristics(L"Pro Audio", &taskIndex); } else { - if(_audioClient->GetMixFormat(&waveFormat) != S_OK) return false; + if(_audioClient->GetMixFormat(&_waveFormat) != S_OK) return false; if(_audioClient->GetDevicePeriod(&_devicePeriod, nullptr)) return false; auto latency = max(_devicePeriod, (REFERENCE_TIME)_latency * 10'000); //1ms to 100ns units - if(_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, latency, 0, _waveFormat, nullptr) != S_OK) return false; + if(_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, latency, 0, _waveFormat, nullptr) != S_OK) return false; } + _eventHandle = CreateEvent(nullptr, false, false, nullptr); + if(_audioClient->SetEventHandle(_eventHandle) != S_OK) return false; if(_audioClient->GetService(IID_IAudioRenderClient, (void**)&_renderClient) != S_OK) return false; if(_audioClient->GetBufferSize(&_bufferSize) != S_OK) return false; - _channels = waveFormat->nChannels; - _frequency = waveFormat->nSamplesPerSec; + _channels = _waveFormat->nChannels; + _frequency = _waveFormat->nSamplesPerSec; _mode = ((WAVEFORMATEXTENSIBLE*)_waveFormat)->SubFormat.Data1; _precision = _waveFormat->wBitsPerSample; - _audioClient->Start(); + clear(); return _ready = true; } @@ -115,33 +124,52 @@ private: if(_waveFormat) CoTaskMemFree(_waveFormat), _waveFormat = nullptr; if(_audioClient) _audioClient->Release(), _audioClient = nullptr; if(_audioDevice) _audioDevice->Release(), _audioDevice = nullptr; + if(_eventHandle) CloseHandle(_eventHandle), _eventHandle = nullptr; if(_taskHandle) AvRevertMmThreadCharacteristics(_taskHandle), _taskHandle = nullptr; } - auto available() -> uint { + auto write() -> void { uint32_t padding = 0; _audioClient->GetCurrentPadding(&padding); - return bufferSize - padding; - } - - auto write(int16_t left, int16_t right) -> void { - if(_renderClient->GetBuffer(1, &_bufferData) != S_OK) return; - - if(_channels >= 2 && _mode == 1 && _precision == 16) { - auto buffer = (int16_t*)_bufferData; - buffer[0] = left; - buffer[1] = right; - } - - if(_channels >= 2 && _mode == 3 && _precision == 32) { - auto buffer = (float*)_bufferData; - buffer[0] = left / 32768.0; - buffer[1] = right / 32768.0; - } - - _renderClient->ReleaseBuffer(1, 0); + uint32_t available = _bufferSize - padding; + + uint8_t* buffer = nullptr; + if(_renderClient->GetBuffer(available, &buffer) == S_OK) { + uint bufferFlags = 0; + for(uint _ : range(available)) { + //if more samples are available than we have queued, fill remainder with silence + double samples[8] = {}; + if(_queue.count) { + for(uint n : range(_channels)) { + samples[n] = _queue.samples[_queue.read][n]; + } + _queue.read++; + _queue.count--; + } + + if(_mode == 1 && _precision == 16) { + auto output = (int16_t*)buffer; + for(uint n : range(_channels)) *output++ = int16_t(samples[n] * 32768.0); + buffer = (uint8_t*)output; + } else if(_mode == 1 && _precision == 32) { + auto output = (int32_t*)buffer; + for(uint n : range(_channels)) *output++ = int32_t(samples[n] * 65536.0 * 32768.0); + buffer = (uint8_t*)output; + } else if(_mode == 3 && _precision == 32) { + auto output = (float*)buffer; + for(uint n : range(_channels)) *output++ = float(samples[n]); + buffer = (uint8_t*)output; + } else { + //output silence for unsupported sample formats + bufferFlags = AUDCLNT_BUFFERFLAGS_SILENT; + break; + } + } + _renderClient->ReleaseBuffer(available, bufferFlags); + } } + bool _ready = false; bool _exclusive = false; bool _blocking = true; uint _channels = 2; @@ -151,6 +179,13 @@ private: uint _mode = 0; uint _precision = 0; + struct Queue { + double samples[65536][8]; + uint16_t read; + uint16_t write; + uint16_t count; + } _queue; + IMMDeviceEnumerator* _enumerator = nullptr; IMMDevice* _audioDevice = nullptr; IPropertyStore* _propertyStore = nullptr; @@ -158,9 +193,8 @@ private: IAudioRenderClient* _renderClient = nullptr; WAVEFORMATEX* _waveFormat = nullptr; PROPVARIANT _propVariant; + HANDLE _eventHandle = nullptr; HANDLE _taskHandle = nullptr; REFERENCE_TIME _devicePeriod = 0; uint32_t _bufferSize = 0; //in frames - uint8_t* _bufferData = nullptr; - vector _queuedFrames; }; diff --git a/ruby/video/sdl.cpp b/ruby/video/sdl.cpp index be74dc0a..710a28b4 100644 --- a/ruby/video/sdl.cpp +++ b/ruby/video/sdl.cpp @@ -1,3 +1,6 @@ +//note: this driver works under Linux, but crashes with SIGSEGV under FreeBSD +//exact reason is unknown; but I suspect it's a bug in FreeBSD's SDL 1.2 package + #include #include #include @@ -6,130 +9,124 @@ #include struct VideoSDL : Video { - ~VideoSDL() { term(); } + VideoSDL() { initialize(); } + ~VideoSDL() { terminate(); } - Display* display = nullptr; - SDL_Surface* screen = nullptr; - SDL_Surface* buffer = nullptr; - uint iwidth = 0; - uint iheight = 0; + auto ready() -> bool { return _ready; } - struct { - uintptr handle = 0; + auto context() -> uintptr { return _context; } - uint width = 0; - uint height = 0; - } settings; - - auto cap(const string& name) -> bool { - if(name == Video::Handle) return true; - return false; - } - - auto get(const string& name) -> any { - if(name == Video::Handle) return settings.handle; - return {}; - } - - auto set(const string& name, const any& value) -> bool { - if(name == Video::Handle && value.is()) { - settings.handle = value.get(); - return true; - } - - return false; - } - - auto resize(uint width, uint height) -> void { - if(iwidth >= width && iheight >= height) return; - - iwidth = max(width, iwidth); - iheight = max(height, iheight); - - if(buffer) SDL_FreeSurface(buffer); - buffer = SDL_CreateRGBSurface( - SDL_SWSURFACE, iwidth, iheight, 32, - 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 - ); - } - - auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool { - if(width != settings.width || height != settings.height) { - resize(settings.width = width, settings.height = height); - } - - if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer); - pitch = buffer->pitch; - return data = (uint32_t*)buffer->pixels; - } - - auto unlock() -> void { - if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer); + auto setContext(uintptr context) -> bool { + if(_context == context) return true; + _context = context; + return initialize(); } auto clear() -> void { - if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer); - for(uint y : range(iheight)) { - uint32_t* data = (uint32_t*)buffer->pixels + y * (buffer->pitch >> 2); - for(uint x : range(iwidth)) *data++ = 0xff000000; + if(SDL_MUSTLOCK(_buffer)) SDL_LockSurface(_buffer); + for(uint y : range(_bufferHeight)) { + uint32_t* data = (uint32_t*)_buffer->pixels + y * (_buffer->pitch >> 2); + for(uint x : range(_bufferWidth)) *data++ = 0xff000000; } - if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer); - refresh(); + if(SDL_MUSTLOCK(_buffer)) SDL_UnlockSurface(_buffer); + output(); } - auto refresh() -> void { + auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool { + if(width != _width || height != _height) { + resize(_width = width, _height = height); + } + + if(SDL_MUSTLOCK(_buffer)) SDL_LockSurface(_buffer); + pitch = _buffer->pitch; + return data = (uint32_t*)_buffer->pixels; + } + + auto unlock() -> void { + if(SDL_MUSTLOCK(_buffer)) SDL_UnlockSurface(_buffer); + } + + auto output() -> void { //ruby input is X8R8G8B8, top 8-bits are ignored. //as SDL forces us to use a 32-bit buffer, we must set alpha to 255 (full opacity) //to prevent blending against the window beneath when X window visual is 32-bits. - if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer); - for(uint y : range(settings.height)) { - uint32_t* data = (uint32_t*)buffer->pixels + y * (buffer->pitch >> 2); - for(uint x : range(settings.width)) *data++ |= 0xff000000; + if(SDL_MUSTLOCK(_buffer)) SDL_LockSurface(_buffer); + for(uint y : range(_height)) { + uint32_t* data = (uint32_t*)_buffer->pixels + y * (_buffer->pitch >> 2); + for(uint x : range(_width)) *data++ |= 0xff000000; } - if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer); + if(SDL_MUSTLOCK(_buffer)) SDL_UnlockSurface(_buffer); XWindowAttributes attributes; - XGetWindowAttributes(display, settings.handle, &attributes); + XGetWindowAttributes(_display, _context, &attributes); - SDL_Rect src, dest; + SDL_Rect source; + SDL_Rect target; - src.x = 0; - src.y = 0; - src.w = settings.width; - src.h = settings.height; + source.x = 0; + source.y = 0; + source.w = _width; + source.h = _height; - dest.x = 0; - dest.y = 0; - dest.w = attributes.width; - dest.h = attributes.height; + target.x = 0; + target.y = 0; + target.w = attributes.width; + target.h = attributes.height; - SDL_SoftStretch(buffer, &src, screen, &dest); - SDL_UpdateRect(screen, dest.x, dest.y, dest.w, dest.h); + SDL_SoftStretch(_buffer, &source, _screen, &target); + SDL_UpdateRect(_screen, target.x, target.y, target.w, target.h); } - auto init() -> bool { - display = XOpenDisplay(0); +private: + auto initialize() -> bool { + terminate(); + if(!_context) return false; + + _display = XOpenDisplay(0); - //todo: this causes a segfault inside SDL_SetVideoMode on FreeBSD (works under Linux) char env[512]; - sprintf(env, "SDL_WINDOWID=%ld", (long)settings.handle); + sprintf(env, "SDL_WINDOWID=%ld", (long)_context); putenv(env); SDL_InitSubSystem(SDL_INIT_VIDEO); - screen = SDL_SetVideoMode(2560, 1600, 32, SDL_HWSURFACE); - XUndefineCursor(display, settings.handle); + _screen = SDL_SetVideoMode(2560, 1600, 32, SDL_HWSURFACE); + XUndefineCursor(_display, _context); - buffer = nullptr; - iwidth = 0; - iheight = 0; - resize(settings.width = 256, settings.height = 256); + _buffer = nullptr; + _bufferWidth = 0; + _bufferHeight = 0; + resize(_width = 256, _height = 256); return true; } - auto term() -> void { - XCloseDisplay(display); - SDL_FreeSurface(buffer); - SDL_QuitSubSystem(SDL_INIT_VIDEO); + auto terminate() -> void { + if(_buffer) SDL_FreeSurface(_buffer), _buffer = nullptr; + if(_screen) SDL_QuitSubSystem(SDL_INIT_VIDEO), _screen = nullptr; + if(_display) XCloseDisplay(_display), _display = nullptr; } + + auto resize(uint width, uint height) -> void { + if(_bufferWidth >= width && _bufferHeight >= height) return; + + _bufferWidth = max(width, _bufferWidth); + _bufferHeight = max(height, _bufferHeight); + + if(_buffer) SDL_FreeSurface(_buffer); + _buffer = SDL_CreateRGBSurface( + SDL_SWSURFACE, _bufferWidth, _bufferHeight, 32, + 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 + ); + } + + bool _ready = false; + uintptr _context = 0; + + Display* _display = nullptr; + SDL_Surface* _screen = nullptr; + SDL_Surface* _buffer = nullptr; + uint _bufferWidth = 0; + uint _bufferHeight = 0; + uint _width = 0; + uint _height = 0; }; diff --git a/ruby/video/xshm.cpp b/ruby/video/xshm.cpp index 9a493b11..79fc2a95 100644 --- a/ruby/video/xshm.cpp +++ b/ruby/video/xshm.cpp @@ -28,6 +28,14 @@ struct VideoXShm : Video { return true; } + auto clear() -> void { + if(!_ready) return; + auto dp = _inputBuffer; + uint length = _inputWidth * _inputHeight; + while(length--) *dp++ = 255u << 24; + output(); + } + auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool { if(!_inputBuffer || _inputWidth != width || _inputHeight != height) { if(_inputBuffer) delete[] _inputBuffer; @@ -44,14 +52,6 @@ struct VideoXShm : Video { auto unlock() -> void { } - auto clear() -> void { - if(!_ready) return; - auto dp = _inputBuffer; - uint length = _inputWidth * _inputHeight; - while(length--) *dp++ = 255u << 24; - output(); - } - auto output() -> void { if(!_ready) return; size();