diff --git a/README.md b/README.md index 6c96e8f3b..3ac98c21a 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,36 @@ - +# Flycast -Flycast -=========== -**Flycast** is a multi-platform Sega Dreamcast, Naomi and Atomiswave emulator derived from [**reicast**](https://reicast.com/) +[![C/C++ CI](https://github.com/flyinghead/flycast/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/flyinghead/flycast/actions/workflows/c-cpp.yml) +[![Android CI](https://github.com/flyinghead/flycast/actions/workflows/android.yml/badge.svg)](https://github.com/flyinghead/flycast/actions/workflows/android.yml) -Information about configuration and supported features can be found on [**TheArcadeStriker's flycast wiki**](https://github.com/TheArcadeStriker/flycast-wiki/wiki) +![flycast logo](https://github.com/flyinghead/flycast/raw/master/shell/linux/flycast.png) + +**Flycast** is a multi-platform Sega Dreamcast, Naomi and Atomiswave emulator derived from [**reicast**](https://reicast.com/). + +Information about configuration and supported features can be found on [**TheArcadeStriker's flycast wiki**](https://github.com/TheArcadeStriker/flycast-wiki/wiki). Join us on our [**Discord server**](https://discord.gg/X8YWP8w) for a chat. -Binaries -======== +## Install + +### Flatpak + +1. [Set up Flatpak](https://www.flatpak.org/setup/) + +2. Install Flycast from [Flathub](https://flathub.org/apps/details/org.flycast.Flycast) + +`flatpak install -y org.flycast.Flycast` + +3. Run Flycast + +`flatpak run org.flycast.Flycast` + +### Binaries + Get fresh builds for your system [on the builds page](https://flyinghead.github.io/flycast-builds/). **New:** Now automated test results are available as well. -Disclaimer -========== +## Disclaimer + All code contritbuted to this fork is *not* bound by the Individual Contributor License Agreement of the upstream repository (https://github.com/reicast/reicast-emulator) and shall *not* be considered as a contribution to the upstream repository. diff --git a/core/cfg/option.cpp b/core/cfg/option.cpp index 1f435ba01..859e781cd 100644 --- a/core/cfg/option.cpp +++ b/core/cfg/option.cpp @@ -61,6 +61,7 @@ OptionString AudioBackend("backend", "auto", "audio"); RendererOption RendererType; Option UseMipmaps("rend.UseMipmaps", true); Option Widescreen("rend.WideScreen"); +Option SuperWidescreen("rend.SuperWideScreen"); Option ShowFPS("rend.ShowFPS"); Option RenderToTextureBuffer("rend.RenderToTextureBuffer"); Option RenderToTextureUpscale("rend.RenderToTextureUpscale", 1); diff --git a/core/cfg/option.h b/core/cfg/option.h index 47cb730a2..bd2da0d4c 100644 --- a/core/cfg/option.h +++ b/core/cfg/option.h @@ -356,6 +356,7 @@ private: extern RendererOption RendererType; extern Option UseMipmaps; extern Option Widescreen; +extern Option SuperWidescreen; extern Option ShowFPS; extern Option RenderToTextureBuffer; extern Option RenderToTextureUpscale; diff --git a/core/emulator.h b/core/emulator.h index 0c1fb614d..abc28bd92 100644 --- a/core/emulator.h +++ b/core/emulator.h @@ -46,6 +46,7 @@ bool dc_is_load_done(); void dc_cancel_load(); void dc_get_load_status(); bool dc_is_running(); +void dc_resize_renderer(); enum class Event { Start, diff --git a/core/nullDC.cpp b/core/nullDC.cpp index dee514d49..bb89e030c 100644 --- a/core/nullDC.cpp +++ b/core/nullDC.cpp @@ -40,6 +40,7 @@ #include "debug/gdb_server.h" settings_t settings; +extern int screen_width, screen_height; cThread emu_thread(&dc_run, NULL); @@ -527,7 +528,7 @@ static void dc_start_game(const char *path) { // Boot BIOS if (!LoadRomFiles()) - throw ReicastException("No BIOS file found"); + throw ReicastException("No BIOS file found in " + get_writable_data_path("")); TermDrive(); InitDrive(); } @@ -734,15 +735,16 @@ void SaveSettings() #endif } -void dc_resume() +void dc_resize_renderer() { - SetMemoryHandlers(); - settings.aica.NoBatch = config::ForceWindowsCE || config::DSPEnabled; int hres; int vres = config::RenderResolution; if (config::Widescreen && !config::Rotate90) { - hres = config::RenderResolution * 16 / 9; + if (config::SuperWidescreen) + hres = config::RenderResolution * screen_width / screen_height ; + else + hres = config::RenderResolution * 16 / 9; } else if (config::Rotate90) { @@ -755,6 +757,13 @@ void dc_resume() } if (renderer != nullptr) renderer->Resize(hres, vres); +} + +void dc_resume() +{ + SetMemoryHandlers(); + settings.aica.NoBatch = config::ForceWindowsCE || config::DSPEnabled; + dc_resize_renderer(); EventManager::event(Event::Resume); if (!emu_thread.thread.joinable()) diff --git a/core/oslib/audiobackend_directsound.cpp b/core/oslib/audiobackend_directsound.cpp index 9f2adcffe..4a2cb5ee0 100644 --- a/core/oslib/audiobackend_directsound.cpp +++ b/core/oslib/audiobackend_directsound.cpp @@ -1,41 +1,139 @@ -#include "audiostream.h" #ifdef _WIN32 -#include "oslib.h" +#include "audiostream.h" #include #include #ifdef USE_SDL #include "sdl/sdl.h" #endif +#include +#include +#include +#include +#include "stdclass.h" #define verifyc(x) verify(!FAILED(x)) -void* SoundThread(void* param); -#define V2_BUFFERSZ (16*1024) - static IDirectSound8* dsound; static IDirectSoundBuffer8* buffer; +static std::vector notificationEvents; static IDirectSoundCapture8 *dcapture; static IDirectSoundCaptureBuffer8 *capture_buffer; -static u32 ds_ring_size; +static std::atomic_bool audioThreadRunning; +static std::thread audioThread; +static cResetEvent pushWait; + +constexpr u32 SAMPLE_BYTES = SAMPLE_COUNT * 4; + +class RingBuffer +{ + std::vector buffer; + std::atomic_int readCursor { 0 }; + std::atomic_int writeCursor { 0 }; + + u32 readSize() { + return (writeCursor - readCursor + buffer.size()) % buffer.size(); + } + u32 writeSize() { + return (readCursor - writeCursor + buffer.size() - 1) % buffer.size(); + } + +public: + bool write(const u8 *data, u32 size) + { + if (size > writeSize()) + return false; + u32 wc = writeCursor; + u32 chunkSize = std::min(size, buffer.size() - wc); + memcpy(&buffer[wc], data, chunkSize); + wc = (wc + chunkSize) % buffer.size(); + size -= chunkSize; + if (size > 0) + { + data += chunkSize; + memcpy(&buffer[wc], data, size); + wc = (wc + size) % buffer.size(); + } + writeCursor = wc; + return true; + } + + bool read(u8 *data, u32 size) + { + if (size > readSize()) + return false; + u32 rc = readCursor; + u32 chunkSize = std::min(size, buffer.size() - rc); + memcpy(data, &buffer[rc], chunkSize); + rc = (rc + chunkSize) % buffer.size(); + size -= chunkSize; + if (size > 0) + { + data += chunkSize; + memcpy(data, &buffer[rc], size); + rc = (rc + size) % buffer.size(); + } + readCursor = rc; + return true; + } + + void setCapacity(size_t size) + { + std::fill(buffer.begin(), buffer.end(), 0); + buffer.resize(size); + readCursor = 0; + writeCursor = 0; + } +}; +static RingBuffer ringBuffer; + +static u32 notificationOffset(int index) { + return index * SAMPLE_BYTES; +} + +static void audioThreadMain() +{ + audioThreadRunning = true; + while (true) + { + u32 rv = WaitForMultipleObjects(notificationEvents.size(), ¬ificationEvents[0], false, 100); + + if (!audioThreadRunning) + break; + if (rv == WAIT_TIMEOUT || rv == WAIT_FAILED) + continue; + rv -= WAIT_OBJECT_0; + + void *p1, *p2; + DWORD sz1, sz2; + + if (SUCCEEDED(buffer->Lock(notificationOffset(rv), SAMPLE_BYTES, &p1, &sz1, &p2, &sz2, 0))) + { + if (!ringBuffer.read((u8*)p1, sz1)) + memset(p1, 0, sz1); + if (sz2 != 0) + { + if (!ringBuffer.read((u8*)p2, sz2)) + memset(p2, 0, sz2); + } + buffer->Unlock(p1, sz1, p2, sz2); + pushWait.Set(); + } + } +} static void directsound_init() { - verifyc(DirectSoundCreate8(NULL,&dsound,NULL)); + verifyc(DirectSoundCreate8(NULL, &dsound, NULL)); #ifdef USE_SDL verifyc(dsound->SetCooperativeLevel(sdl_get_native_hwnd(), DSSCL_PRIORITY)); #else verifyc(dsound->SetCooperativeLevel((HWND)libPvr_GetRenderTarget(), DSSCL_PRIORITY)); #endif - IDirectSoundBuffer* buffer_; - - WAVEFORMATEX wfx; - DSBUFFERDESC desc; - // Set up WAV format structure. - + WAVEFORMATEX wfx; memset(&wfx, 0, sizeof(WAVEFORMATEX)); wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.nChannels = 2; @@ -45,104 +143,64 @@ static void directsound_init() wfx.wBitsPerSample = 16; // Set up DSBUFFERDESC structure. - - ds_ring_size=8192*wfx.nBlockAlign; - + DSBUFFERDESC desc; memset(&desc, 0, sizeof(DSBUFFERDESC)); desc.dwSize = sizeof(DSBUFFERDESC); desc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS; - - desc.dwBufferBytes = ds_ring_size; + desc.dwBufferBytes = SAMPLE_BYTES * 2; desc.lpwfxFormat = &wfx; - verifyc(dsound->CreateSoundBuffer(&desc,&buffer_,0)); - verifyc(buffer_->QueryInterface(IID_IDirectSoundBuffer8,(void**)&buffer)); + // Create the buffer + IDirectSoundBuffer* buffer_; + verifyc(dsound->CreateSoundBuffer(&desc, &buffer_, 0)); + verifyc(buffer_->QueryInterface(IID_IDirectSoundBuffer8, (void**)&buffer)); buffer_->Release(); - //Play the buffer ! - verifyc(buffer->Play(0,0,DSBPLAY_LOOPING)); - -} - - -static DWORD wc=0; - - -static int directsound_getfreesz() -{ - DWORD pc,wch; - - buffer->GetCurrentPosition(&pc,&wch); - - int fsz=0; - if (wc>=pc) - fsz=ds_ring_size-wc+pc; - else - fsz=pc-wc; - - fsz-=32; - return fsz; -} - -static int directsound_getusedSamples() -{ - return (ds_ring_size-directsound_getfreesz())/4; -} - -static u32 directsound_push_nw(const void* frame, u32 samplesb) -{ - DWORD pc,wch; - - u32 bytes=samplesb*4; - - buffer->GetCurrentPosition(&pc,&wch); - - int fsz=0; - if (wc>=pc) - fsz=ds_ring_size-wc+pc; - else - fsz=pc-wc; - - fsz-=32; - - //printf("%d: r:%d w:%d (f:%d wh:%d)\n",fsz>bytes,pc,wc,fsz,wch); - - if (fsz>bytes) + // Set up notifications + IDirectSoundNotify *bufferNotify; + verifyc(buffer->QueryInterface(IID_IDirectSoundNotify8, (void**)&bufferNotify)); + notificationEvents.clear(); + std::vector posNotify; + for (int i = 0; notificationOffset(i) < desc.dwBufferBytes; i++) { - void* ptr1,* ptr2; - DWORD ptr1sz,ptr2sz; - - const u8* data=(const u8*)frame; - - buffer->Lock(wc,bytes,&ptr1,&ptr1sz,&ptr2,&ptr2sz,0); - memcpy(ptr1,data,ptr1sz); - if (ptr2sz) - { - data+=ptr1sz; - memcpy(ptr2,data,ptr2sz); - } - - buffer->Unlock(ptr1,ptr1sz,ptr2,ptr2sz); - wc=(wc+bytes)%ds_ring_size; - return 1; + notificationEvents.push_back(CreateEvent(nullptr, false, false, nullptr)); + posNotify.push_back({ notificationOffset(i), notificationEvents.back() }); } - return 0; - //ds_ring_size + bufferNotify->SetNotificationPositions(posNotify.size(), &posNotify[0]); + bufferNotify->Release(); + + // Clear the buffers + void *p1, *p2; + DWORD sz1, sz2; + verifyc(buffer->Lock(0, desc.dwBufferBytes, &p1, &sz1, &p2, &sz2, 0)); + verify(p2 == nullptr); + memset(p1, 0, sz1); + verifyc(buffer->Unlock(p1, sz1, p2, sz2)); + ringBuffer.setCapacity(config::AudioBufferSize * 4); + + // Start the thread + audioThread = std::thread(audioThreadMain); + + // Play the buffer ! + verifyc(buffer->Play(0, 0, DSBPLAY_LOOPING)); } static u32 directsound_push(const void* frame, u32 samples, bool wait) { - while (!directsound_push_nw(frame, samples) && wait) - //DEBUG_LOG(AUDIO, "FAILED waiting on audio FAILED %d", directsound_getusedSamples()) - ; + while (!ringBuffer.write((const u8 *)frame, samples * 4) && wait) + pushWait.Wait(); return 1; } static void directsound_term() { + audioThreadRunning = false; + audioThread.join(); buffer->Stop(); + for (HANDLE event : notificationEvents) + CloseHandle(event); buffer->Release(); dsound->Release(); } diff --git a/core/oslib/audiobackend_sdl2.cpp b/core/oslib/audiobackend_sdl2.cpp index 3d3efecb3..7a0244b05 100644 --- a/core/oslib/audiobackend_sdl2.cpp +++ b/core/oslib/audiobackend_sdl2.cpp @@ -111,10 +111,9 @@ static u32 sdl2_audio_push(const void* frame, u32 samples, bool wait) { // If wait, then wait for the buffer to be smaller than a certain size. stream_mutex.lock(); if (wait) { - while (sample_count + samples > config::AudioBufferSize) { + while (sample_count + samples > (u32)config::AudioBufferSize) { stream_mutex.unlock(); read_wait.Wait(); - read_wait.Reset(); stream_mutex.lock(); } } diff --git a/core/rend/game_scanner.h b/core/rend/game_scanner.h index 527a3f5ea..84c37d4cd 100644 --- a/core/rend/game_scanner.h +++ b/core/rend/game_scanner.h @@ -56,21 +56,28 @@ class GameScanner void add_game_directory(const std::string& path) { - // FIXME this won't work anymore - if (game_list.empty()) - { - ++empty_folders_scanned; - if (empty_folders_scanned > 1000) - content_path_looks_incorrect = true; - } - else - { - content_path_looks_incorrect = false; - } - DirectoryTree tree(path); + std::string emptyParentPath = ""; for (const DirectoryTree::item& item : tree) { + if (running == false) + break; + + if (game_list.size() == 0) + { + if(item.parentPath.compare(emptyParentPath)) + { + ++empty_folders_scanned; + emptyParentPath = item.parentPath; + if (empty_folders_scanned > 1000) + content_path_looks_incorrect = true; + } + } + else + { + content_path_looks_incorrect = false; + } + if (item.name.substr(0, 2) == "._") // Ignore Mac OS turds continue; diff --git a/core/rend/gl4/gles.cpp b/core/rend/gl4/gles.cpp index 4a46903e6..b6cdedfdd 100644 --- a/core/rend/gl4/gles.cpp +++ b/core/rend/gl4/gles.cpp @@ -714,15 +714,11 @@ static bool RenderFrame(int width, int height) //setup render target first if (is_rtt) - { output_fbo = BindRTT(false); - if (output_fbo == 0) - return false; - } else - { output_fbo = init_output_framebuffer(rendering_width, rendering_height); - } + if (output_fbo == 0) + return false; glcache.Disable(GL_SCISSOR_TEST); diff --git a/core/rend/gles/gles.cpp b/core/rend/gles/gles.cpp index 7adea3655..017cf407e 100644 --- a/core/rend/gles/gles.cpp +++ b/core/rend/gles/gles.cpp @@ -1101,7 +1101,8 @@ bool RenderFrame(int width, int height) } else { - init_output_framebuffer(width, height); + if (init_output_framebuffer(width, height) == 0) + return false; } bool wide_screen_on = !is_rtt && config::Widescreen && !matrices.IsClipped() && !config::Rotate90; diff --git a/core/rend/gles/gltex.cpp b/core/rend/gles/gltex.cpp index 17187449e..0b5987639 100644 --- a/core/rend/gles/gltex.cpp +++ b/core/rend/gles/gltex.cpp @@ -523,7 +523,8 @@ GLuint init_output_framebuffer(int width, int height) // Check that our FBO creation was successful GLuint uStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); - verify(uStatus == GL_FRAMEBUFFER_COMPLETE); + if (uStatus != GL_FRAMEBUFFER_COMPLETE) + return 0; glcache.Disable(GL_SCISSOR_TEST); glcache.ClearColor(0.f, 0.f, 0.f, 0.f); diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index 173459236..bd9d0126e 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -52,6 +52,7 @@ int screen_dpi = 96; static bool inited = false; float scaling = 1; GuiState gui_state = GuiState::Main; +static bool commandLineStart; #ifdef __ANDROID__ static bool touch_up; #endif @@ -235,7 +236,25 @@ void gui_init() { io.Fonts->AddFontFromFileTTF((fontDir + "PingFang.ttc").c_str(), 17.f * scaling, &font_cfg, GetGlyphRangesChineseSimplifiedOfficial()); } - // TODO linux, Android... +#elif defined(__ANDROID__) + if (getenv("FLYCAST_LOCALE") != nullptr) + { + const ImWchar *glyphRanges = nullptr; + std::string locale = getenv("FLYCAST_LOCALE"); + if (locale.find("ja") == 0) // Japanese + glyphRanges = io.Fonts->GetGlyphRangesJapanese(); + else if (locale.find("ko") == 0) // Korean + glyphRanges = io.Fonts->GetGlyphRangesKorean(); + else if (locale.find("zh_TW") == 0) // Traditional Chinese + glyphRanges = GetGlyphRangesChineseTraditionalOfficial(); + else if (locale.find("zh_CN") == 0) // Simplified Chinese + glyphRanges = GetGlyphRangesChineseSimplifiedOfficial(); + + if (glyphRanges != nullptr) + io.Fonts->AddFontFromFileTTF("/system/fonts/NotoSansCJK-Regular.ttc", 17.f * scaling, &font_cfg, glyphRanges); + } + + // TODO Linux... #endif INFO_LOG(RENDERER, "Screen DPI is %d, size %d x %d. Scaling by %.2f", screen_dpi, screen_width, screen_height, scaling); @@ -472,11 +491,19 @@ static void gui_display_commands() if (ImGui::Button("Exit", ImVec2(300 * scaling + ImGui::GetStyle().ColumnsMinSpacing + ImGui::GetStyle().FramePadding.x * 2 - 1, 50 * scaling))) { - // Exit to main menu - dc_term_game(); - gui_state = GuiState::Main; - game_started = false; - settings.imgread.ImagePath[0] = '\0'; + if (!commandLineStart) + { + // Exit to main menu + dc_term_game(); + gui_state = GuiState::Main; + game_started = false; + settings.imgread.ImagePath[0] = '\0'; + } + else + { + // Exit emulator + dc_exit(); + } } ImGui::End(); @@ -1011,6 +1038,16 @@ static void gui_display_settings() ImGui::SameLine(); ShowHelpMarker("The directories where your games are stored"); +#ifdef __linux__ + if (ImGui::ListBoxHeader("Data Directory", 1)) + { + ImGui::AlignTextToFramePadding(); + ImGui::Text("%s", get_writable_data_path("").c_str()); + ImGui::ListBoxFooter(); + } + ImGui::SameLine(); + ShowHelpMarker("The directory containing BIOS files, as well as saved VMUs and states"); +#else if (ImGui::ListBoxHeader("Home Directory", 1)) { ImGui::AlignTextToFramePadding(); @@ -1024,6 +1061,7 @@ static void gui_display_settings() } ImGui::SameLine(); ShowHelpMarker("The directory where Flycast saves configuration files and VMUs. BIOS files should be in a subfolder named \"data\""); +#endif if (OptionCheckbox("Hide Legacy Naomi Roms", config::HideLegacyNaomiRoms, "Hide .bin, .dat and .lst files from the content browser")) scanner.refresh(); @@ -1287,6 +1325,20 @@ static void gui_display_settings() OptionCheckbox("Fog", config::Fog, "Enable fog effects"); OptionCheckbox("Widescreen", config::Widescreen, "Draw geometry outside of the normal 4:3 aspect ratio. May produce graphical glitches in the revealed areas"); + if (!config::Widescreen) + { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); + } + ImGui::Indent(); + OptionCheckbox("Super Widescreen", config::SuperWidescreen, + "Use the full width of the screen or window when its aspect ratio is greater than 16:9"); + ImGui::Unindent(); + if (!config::Widescreen) + { + ImGui::PopItemFlag(); + ImGui::PopStyleVar(); + } OptionCheckbox("Widescreen Game Cheats", config::WidescreenGameHacks, "Modify the game so that it displays in 16:9 anamorphic format and use horizontal screen stretching. Only some games are supported."); OptionCheckbox("Show FPS Counter", config::ShowFPS, "Show on-screen frame/sec counter"); @@ -1414,7 +1466,6 @@ static void gui_display_settings() OptionCheckbox("Disable Sound", config::DisableSound, "Disable the emulator sound output"); OptionCheckbox("Enable DSP", config::DSPEnabled, "Enable the Dreamcast Digital Sound Processor. Only recommended on fast platforms"); -#if !defined(_WIN32) #ifdef __ANDROID__ OptionCheckbox("Automatic Latency", config::AutoLatency, "Automatically set audio latency. Recommended"); @@ -1427,7 +1478,6 @@ static void gui_display_settings() ImGui::SameLine(); ShowHelpMarker("Sets the maximum audio latency. Not supported by all audio drivers."); } -#endif audiobackend_t* backend = nullptr; std::string backend_name = config::AudioBackend; @@ -1960,6 +2010,9 @@ void gui_display_ui() std::string game_file = settings.imgread.ImagePath; if (!game_file.empty()) { +#ifndef __ANDROID__ + commandLineStart = true; +#endif gui_start_game(game_file); return; } diff --git a/core/rend/mainui.cpp b/core/rend/mainui.cpp index 569e38ce5..f93763ac7 100644 --- a/core/rend/mainui.cpp +++ b/core/rend/mainui.cpp @@ -24,6 +24,7 @@ #include "oslib/oslib.h" #include "wsi/context.h" #include "cfg/option.h" +#include "emulator.h" bool mainui_enabled; u32 MainFrameCount; @@ -59,6 +60,7 @@ bool mainui_rend_frame() void mainui_init() { rend_init_renderer(); + dc_resize_renderer(); } void mainui_term() diff --git a/core/rend/vulkan/vulkan_context.cpp b/core/rend/vulkan/vulkan_context.cpp index 6967be1b6..2eefcb4bf 100644 --- a/core/rend/vulkan/vulkan_context.cpp +++ b/core/rend/vulkan/vulkan_context.cpp @@ -564,25 +564,24 @@ void VulkanContext::CreateSwapChain() // The FIFO present mode is guaranteed by the spec to be supported vk::PresentModeKHR swapchainPresentMode = vk::PresentModeKHR::eFifo; // Use FIFO on mobile, prefer Mailbox on desktop -#if HOST_CPU != CPU_ARM && HOST_CPU != CPU_ARM64 && !defined(__ANDROID__) for (auto& presentMode : physicalDevice.getSurfacePresentModesKHR(GetSurface())) { - if (presentMode == vk::PresentModeKHR::eMailbox && vendorID != VENDOR_ATI && vendorID != VENDOR_AMD) +#if HOST_CPU != CPU_ARM && HOST_CPU != CPU_ARM64 && !defined(__ANDROID__) + if (swapOnVSync && presentMode == vk::PresentModeKHR::eMailbox + && vendorID != VENDOR_ATI && vendorID != VENDOR_AMD) { INFO_LOG(RENDERER, "Using mailbox present mode"); swapchainPresentMode = vk::PresentModeKHR::eMailbox; break; } -#ifdef TEST_AUTOMATION - if (presentMode == vk::PresentModeKHR::eImmediate) +#endif + if (!swapOnVSync && presentMode == vk::PresentModeKHR::eImmediate) { INFO_LOG(RENDERER, "Using immediate present mode"); swapchainPresentMode = vk::PresentModeKHR::eImmediate; break; } -#endif } -#endif vk::SurfaceTransformFlagBitsKHR preTransform = (surfaceCapabilities.supportedTransforms & vk::SurfaceTransformFlagBitsKHR::eIdentity) ? vk::SurfaceTransformFlagBitsKHR::eIdentity : surfaceCapabilities.currentTransform; @@ -802,6 +801,13 @@ void VulkanContext::Present() noexcept } renderDone = false; } +#ifndef TEST_AUTOMATION + if (swapOnVSync == settings.input.fastForwardMode) + { + swapOnVSync = !settings.input.fastForwardMode; + resized = true; + } +#endif if (resized) try { CreateSwapChain(); diff --git a/core/rend/vulkan/vulkan_context.h b/core/rend/vulkan/vulkan_context.h index 75042373d..1d870d490 100644 --- a/core/rend/vulkan/vulkan_context.h +++ b/core/rend/vulkan/vulkan_context.h @@ -140,6 +140,11 @@ private: u32 width = 0; u32 height = 0; bool resized = false; +#ifndef TEST_AUTOMATION + bool swapOnVSync = true; +#else + bool swapOnVSync = false; +#endif vk::UniqueInstance instance; vk::PhysicalDevice physicalDevice; diff --git a/core/wsi/egl.cpp b/core/wsi/egl.cpp index 216cc62c6..9aede4ece 100644 --- a/core/wsi/egl.cpp +++ b/core/wsi/egl.cpp @@ -181,7 +181,12 @@ bool EGLGraphicsContext::Init() #ifdef TARGET_PANDORA fbdev = open("/dev/fb0", O_RDONLY); #else - eglSwapInterval(display, 1); +#ifndef TEST_AUTOMATION + swapOnVSync = true; +#else + swapOnVSync = false; +#endif + eglSwapInterval(display, (int)swapOnVSync); #endif PostInit(); @@ -215,12 +220,11 @@ void EGLGraphicsContext::Swap() { #ifdef TEST_AUTOMATION do_swap_automation(); -#endif -#if 0 && defined(TARGET_PANDORA) - if (fbdev >= 0) +#else + if (swapOnVSync == settings.input.fastForwardMode) { - int arg = 0; - ioctl(fbdev,FBIO_WAITFORVSYNC,&arg); + swapOnVSync = !settings.input.fastForwardMode; + eglSwapInterval(display, (int)swapOnVSync); } #endif eglSwapBuffers(display, surface); diff --git a/core/wsi/egl.h b/core/wsi/egl.h index a1aa122fc..945318fe4 100644 --- a/core/wsi/egl.h +++ b/core/wsi/egl.h @@ -55,6 +55,7 @@ private: #ifdef TARGET_PANDORA int fbdev = -1; #endif + bool swapOnVSync = false; }; extern EGLGraphicsContext theGLContext; diff --git a/core/wsi/sdl.cpp b/core/wsi/sdl.cpp index 0060b92b9..883dbd129 100644 --- a/core/wsi/sdl.cpp +++ b/core/wsi/sdl.cpp @@ -77,6 +77,14 @@ bool SDLGLGraphicsContext::Init() INFO_LOG(RENDERER, "Created SDL Window and GL Context successfully"); SDL_GL_MakeCurrent(window, glcontext); +#ifndef TEST_AUTOMATION + // Swap at vsync + swapOnVSync = true; +#else + // Swap immediately + swapOnVSync = false; +#endif + SDL_GL_SetSwapInterval((int)swapOnVSync); #ifdef GLES load_gles_symbols(); @@ -94,6 +102,15 @@ bool SDLGLGraphicsContext::Init() void SDLGLGraphicsContext::Swap() { +#ifdef TEST_AUTOMATION + do_swap_automation(); +#else + if (swapOnVSync == settings.input.fastForwardMode) + { + swapOnVSync = !settings.input.fastForwardMode; + SDL_GL_SetSwapInterval((int)swapOnVSync); + } +#endif SDL_GL_SwapWindow(window); /* Check if drawable has been resized */ diff --git a/core/wsi/sdl.h b/core/wsi/sdl.h index ec599ebae..d1f9c5b29 100644 --- a/core/wsi/sdl.h +++ b/core/wsi/sdl.h @@ -45,6 +45,7 @@ public: private: SDL_Window* window = nullptr; SDL_GLContext glcontext = nullptr; + bool swapOnVSync = false; }; extern SDLGLGraphicsContext theGLContext; diff --git a/core/wsi/xgl.cpp b/core/wsi/xgl.cpp index d29af2e0d..5e08d27af 100644 --- a/core/wsi/xgl.cpp +++ b/core/wsi/xgl.cpp @@ -82,6 +82,21 @@ bool XGLGraphicsContext::Init() unsigned int tempu; XGetGeometry(display, window, &win, &temp, &temp, (u32 *)&screen_width, (u32 *)&screen_height, &tempu, &tempu); +#ifndef TEST_AUTOMATION + swapOnVSync = true; +#else + swapOnVSync = false; +#endif + glXSwapIntervalMESA = (int (*)(unsigned))glXGetProcAddress((const GLubyte*)"glXSwapIntervalMESA"); + if (glXSwapIntervalMESA != nullptr) + glXSwapIntervalMESA((unsigned)swapOnVSync); + else + { + glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress((const GLubyte*)"glXSwapIntervalEXT"); + if (glXSwapIntervalEXT != nullptr) + glXSwapIntervalEXT(display, window, (int)swapOnVSync); + } + PostInit(); return true; @@ -142,32 +157,22 @@ void XGLGraphicsContext::Swap() { #ifdef TEST_AUTOMATION do_swap_automation(); +#else + if (swapOnVSync == settings.input.fastForwardMode) + { + swapOnVSync = !settings.input.fastForwardMode; + if (glXSwapIntervalMESA != nullptr) + glXSwapIntervalMESA((unsigned)swapOnVSync); + else if (glXSwapIntervalEXT != nullptr) + glXSwapIntervalEXT(display, window, (int)swapOnVSync); + } #endif glXSwapBuffers(display, window); Window win; int temp; - unsigned int tempu, new_w, new_h; - XGetGeometry(display, window, &win, &temp, &temp, &new_w, &new_h, &tempu, &tempu); - - //if resized, clear up the draw buffers, to avoid out-of-draw-area junk data - if (new_w != screen_width || new_h != screen_height) { - screen_width = new_w; - screen_height = new_h; - } - -#if 0 - //handy to debug really stupid render-not-working issues ... - - glcache.ClearColor(0, 0.5, 1, 1); - glClear(GL_COLOR_BUFFER_BIT); - glXSwapBuffers(display, window); - - - glcache.ClearColor(1, 0.5, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glXSwapBuffers(display, window); -#endif + unsigned int tempu; + XGetGeometry(display, window, &win, &temp, &temp, (u32 *)&screen_width, (u32 *)&screen_height, &tempu, &tempu); } void XGLGraphicsContext::Term() diff --git a/core/wsi/xgl.h b/core/wsi/xgl.h index 1280747fb..8f9fd6252 100644 --- a/core/wsi/xgl.h +++ b/core/wsi/xgl.h @@ -41,6 +41,9 @@ private: Display *display; GLXContext context; GLXFBConfig* framebufferConfigs = nullptr; + PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = nullptr; + int (*glXSwapIntervalMESA)(unsigned int interval) = nullptr; + bool swapOnVSync = false; }; extern XGLGraphicsContext theGLContext; diff --git a/shell/android-studio/flycast/src/main/java/com/reicast/emulator/BaseGLActivity.java b/shell/android-studio/flycast/src/main/java/com/reicast/emulator/BaseGLActivity.java index 973597681..025dbbadd 100644 --- a/shell/android-studio/flycast/src/main/java/com/reicast/emulator/BaseGLActivity.java +++ b/shell/android-studio/flycast/src/main/java/com/reicast/emulator/BaseGLActivity.java @@ -35,6 +35,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import tv.ouya.console.api.OuyaController; @@ -83,7 +84,8 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat. OuyaController.init(this); String home_directory = prefs.getString(Config.pref_home, ""); - String result = JNIdc.initEnvironment((Emulator)getApplicationContext(), home_directory); + String result = JNIdc.initEnvironment((Emulator)getApplicationContext(), home_directory, + Locale.getDefault().toString()); if (result != null) { AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); dlgAlert.setMessage("Initialization failed. Please try again and/or reinstall.\n\n" @@ -114,6 +116,9 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat. }, STORAGE_PERM_REQUEST); } + else + storagePermissionGranted = true; + InputDeviceManager.getInstance().startListening(getApplicationContext()); register(this); diff --git a/shell/android-studio/flycast/src/main/java/com/reicast/emulator/emu/JNIdc.java b/shell/android-studio/flycast/src/main/java/com/reicast/emulator/emu/JNIdc.java index 608fce594..f22e3cc07 100644 --- a/shell/android-studio/flycast/src/main/java/com/reicast/emulator/emu/JNIdc.java +++ b/shell/android-studio/flycast/src/main/java/com/reicast/emulator/emu/JNIdc.java @@ -9,7 +9,7 @@ public final class JNIdc { static { System.loadLibrary("flycast"); } - public static native String initEnvironment(Emulator emulator, String homeDirectory); + public static native String initEnvironment(Emulator emulator, String homeDirectory, String locale); public static native void setExternalStorageDirectories(Object[] pathList); public static native void setGameUri(String fileName); public static native void pause(); diff --git a/shell/android-studio/flycast/src/main/jni/src/Android.cpp b/shell/android-studio/flycast/src/main/jni/src/Android.cpp index 31fc113c7..611a9cbac 100644 --- a/shell/android-studio/flycast/src/main/jni/src/Android.cpp +++ b/shell/android-studio/flycast/src/main/jni/src/Android.cpp @@ -85,7 +85,7 @@ JNIEXPORT type JNICALL Java_com_reicast_emulator_emu_JNIdc_get ## setting(JNIEnv extern "C" { -JNIEXPORT jstring JNICALL Java_com_reicast_emulator_emu_JNIdc_initEnvironment(JNIEnv *env, jobject obj, jobject emulator, jstring homeDirectory) __attribute__((visibility("default"))); +JNIEXPORT jstring JNICALL Java_com_reicast_emulator_emu_JNIdc_initEnvironment(JNIEnv *env, jobject obj, jobject emulator, jstring homeDirectory, jstring locale) __attribute__((visibility("default"))); JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_JNIdc_setExternalStorageDirectories(JNIEnv *env, jobject obj, jobjectArray pathList) __attribute__((visibility("default"))); JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_JNIdc_setGameUri(JNIEnv *env,jobject obj,jstring fileName) __attribute__((visibility("default"))); JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_JNIdc_pause(JNIEnv *env,jobject obj) __attribute__((visibility("default"))); @@ -184,7 +184,7 @@ void os_SetWindowText(char const *Text) { } -JNIEXPORT jstring JNICALL Java_com_reicast_emulator_emu_JNIdc_initEnvironment(JNIEnv *env, jobject obj, jobject emulator, jstring homeDirectory) +JNIEXPORT jstring JNICALL Java_com_reicast_emulator_emu_JNIdc_initEnvironment(JNIEnv *env, jobject obj, jobject emulator, jstring homeDirectory, jstring locale) { // Initialize platform-specific stuff common_linux_setup(); @@ -227,7 +227,13 @@ JNIEXPORT jstring JNICALL Java_com_reicast_emulator_emu_JNIdc_initEnvironment(JN } INFO_LOG(BOOT, "Config dir is: %s", get_writable_config_path("").c_str()); INFO_LOG(BOOT, "Data dir is: %s", get_writable_data_path("").c_str()); - + if (locale != nullptr) + { + const char *jchar = env->GetStringUTFChars(locale, 0); + std::string localeName = jchar; + env->ReleaseStringUTFChars(locale, jchar); + setenv("FLYCAST_LOCALE", localeName.c_str(), 1); + } if (first_init) { // Do one-time initialization diff --git a/shell/imgs/3675.png b/shell/imgs/3675.png deleted file mode 100644 index 8bee62622..000000000 Binary files a/shell/imgs/3675.png and /dev/null differ diff --git a/shell/imgs/icon.png b/shell/imgs/icon.png deleted file mode 100644 index 2005ab6d0..000000000 Binary files a/shell/imgs/icon.png and /dev/null differ diff --git a/shell/imgs/screenshot1.png b/shell/imgs/screenshot1.png new file mode 100644 index 000000000..bbc14ae8b Binary files /dev/null and b/shell/imgs/screenshot1.png differ diff --git a/shell/imgs/screenshot2.png b/shell/imgs/screenshot2.png new file mode 100644 index 000000000..24dcd1116 Binary files /dev/null and b/shell/imgs/screenshot2.png differ diff --git a/shell/imgs/screenshot3.png b/shell/imgs/screenshot3.png new file mode 100644 index 000000000..abcf6dfc0 Binary files /dev/null and b/shell/imgs/screenshot3.png differ diff --git a/shell/imgs/screenshot4.png b/shell/imgs/screenshot4.png new file mode 100644 index 000000000..c833ac1b4 Binary files /dev/null and b/shell/imgs/screenshot4.png differ diff --git a/shell/linux/Makefile b/shell/linux/Makefile index d368b51bd..eb412c4b3 100644 --- a/shell/linux/Makefile +++ b/shell/linux/Makefile @@ -277,6 +277,7 @@ else ifneq (,$(findstring win32,$(platform))) CC = gcc CXX = g++ USE_SDL = 1 + USE_SDLAUDIO = 1 STATIC_LIBZIP = 1 ifeq ($(WITH_DYNAREC), x86) X86_REC := 1 diff --git a/shell/linux/org.flycast.Flycast.metainfo.xml b/shell/linux/org.flycast.Flycast.metainfo.xml new file mode 100644 index 000000000..a2c897e49 --- /dev/null +++ b/shell/linux/org.flycast.Flycast.metainfo.xml @@ -0,0 +1,35 @@ + + + org.flycast.Flycast + Flycast + CC0-1.0 + GPL-2.0 + Multiplatform Sega Dreamcast, Naomi and Atomiswave emulator. + +

Flycast is a multi-platform Sega Dreamcast, Naomi and Atomiswave emulator derived from reicast.

+
+ + org.flycast.Flycast.desktop + + flycast + flycast.desktop + + + https://raw.githubusercontent.com/flyinghead/flycast/master/shell/imgs/screenshot1.png + https://raw.githubusercontent.com/flyinghead/flycast/master/shell/imgs/screenshot2.png + https://raw.githubusercontent.com/flyinghead/flycast/master/shell/imgs/screenshot3.png + https://raw.githubusercontent.com/flyinghead/flycast/master/shell/imgs/screenshot4.png + + + Games + Emulator + + https://github.com/flyinghead/flycast#readme + https://github.com/flyinghead/flycast/issues + https://github.com/TheArcadeStriker/flycast-wiki/wiki + https://github.com/TheArcadeStriker/flycast-wiki/wiki/Frequently-Asked-Questions-(F.A.Q.) + Flyinghead + + + +