Merge remote-tracking branch 'origin/master' into fh/directx

This commit is contained in:
Flyinghead 2021-04-29 19:06:38 +02:00
commit 150cfa29e7
32 changed files with 407 additions and 172 deletions

View File

@ -1,19 +1,36 @@
<img src="https://github.com/flyinghead/flycast/raw/master/shell/linux/flycast.png" width="200"/> # Flycast
Flycast [![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)
**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) ![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. 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/). 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. **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. 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.

View File

@ -61,6 +61,7 @@ OptionString AudioBackend("backend", "auto", "audio");
RendererOption RendererType; RendererOption RendererType;
Option<bool> UseMipmaps("rend.UseMipmaps", true); Option<bool> UseMipmaps("rend.UseMipmaps", true);
Option<bool> Widescreen("rend.WideScreen"); Option<bool> Widescreen("rend.WideScreen");
Option<bool> SuperWidescreen("rend.SuperWideScreen");
Option<bool> ShowFPS("rend.ShowFPS"); Option<bool> ShowFPS("rend.ShowFPS");
Option<bool> RenderToTextureBuffer("rend.RenderToTextureBuffer"); Option<bool> RenderToTextureBuffer("rend.RenderToTextureBuffer");
Option<int> RenderToTextureUpscale("rend.RenderToTextureUpscale", 1); Option<int> RenderToTextureUpscale("rend.RenderToTextureUpscale", 1);

View File

@ -356,6 +356,7 @@ private:
extern RendererOption RendererType; extern RendererOption RendererType;
extern Option<bool> UseMipmaps; extern Option<bool> UseMipmaps;
extern Option<bool> Widescreen; extern Option<bool> Widescreen;
extern Option<bool> SuperWidescreen;
extern Option<bool> ShowFPS; extern Option<bool> ShowFPS;
extern Option<bool> RenderToTextureBuffer; extern Option<bool> RenderToTextureBuffer;
extern Option<int> RenderToTextureUpscale; extern Option<int> RenderToTextureUpscale;

View File

@ -46,6 +46,7 @@ bool dc_is_load_done();
void dc_cancel_load(); void dc_cancel_load();
void dc_get_load_status(); void dc_get_load_status();
bool dc_is_running(); bool dc_is_running();
void dc_resize_renderer();
enum class Event { enum class Event {
Start, Start,

View File

@ -40,6 +40,7 @@
#include "debug/gdb_server.h" #include "debug/gdb_server.h"
settings_t settings; settings_t settings;
extern int screen_width, screen_height;
cThread emu_thread(&dc_run, NULL); cThread emu_thread(&dc_run, NULL);
@ -527,7 +528,7 @@ static void dc_start_game(const char *path)
{ {
// Boot BIOS // Boot BIOS
if (!LoadRomFiles()) if (!LoadRomFiles())
throw ReicastException("No BIOS file found"); throw ReicastException("No BIOS file found in " + get_writable_data_path(""));
TermDrive(); TermDrive();
InitDrive(); InitDrive();
} }
@ -734,15 +735,16 @@ void SaveSettings()
#endif #endif
} }
void dc_resume() void dc_resize_renderer()
{ {
SetMemoryHandlers();
settings.aica.NoBatch = config::ForceWindowsCE || config::DSPEnabled;
int hres; int hres;
int vres = config::RenderResolution; int vres = config::RenderResolution;
if (config::Widescreen && !config::Rotate90) 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) else if (config::Rotate90)
{ {
@ -755,6 +757,13 @@ void dc_resume()
} }
if (renderer != nullptr) if (renderer != nullptr)
renderer->Resize(hres, vres); renderer->Resize(hres, vres);
}
void dc_resume()
{
SetMemoryHandlers();
settings.aica.NoBatch = config::ForceWindowsCE || config::DSPEnabled;
dc_resize_renderer();
EventManager::event(Event::Resume); EventManager::event(Event::Resume);
if (!emu_thread.thread.joinable()) if (!emu_thread.thread.joinable())

View File

@ -1,41 +1,139 @@
#include "audiostream.h"
#ifdef _WIN32 #ifdef _WIN32
#include "oslib.h" #include "audiostream.h"
#include <initguid.h> #include <initguid.h>
#include <dsound.h> #include <dsound.h>
#ifdef USE_SDL #ifdef USE_SDL
#include "sdl/sdl.h" #include "sdl/sdl.h"
#endif #endif
#include <vector>
#include <algorithm>
#include <atomic>
#include <thread>
#include "stdclass.h"
#define verifyc(x) verify(!FAILED(x)) #define verifyc(x) verify(!FAILED(x))
void* SoundThread(void* param);
#define V2_BUFFERSZ (16*1024)
static IDirectSound8* dsound; static IDirectSound8* dsound;
static IDirectSoundBuffer8* buffer; static IDirectSoundBuffer8* buffer;
static std::vector<HANDLE> notificationEvents;
static IDirectSoundCapture8 *dcapture; static IDirectSoundCapture8 *dcapture;
static IDirectSoundCaptureBuffer8 *capture_buffer; 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<u8> 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<u32>(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<u32>(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(), &notificationEvents[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() static void directsound_init()
{ {
verifyc(DirectSoundCreate8(NULL,&dsound,NULL)); verifyc(DirectSoundCreate8(NULL, &dsound, NULL));
#ifdef USE_SDL #ifdef USE_SDL
verifyc(dsound->SetCooperativeLevel(sdl_get_native_hwnd(), DSSCL_PRIORITY)); verifyc(dsound->SetCooperativeLevel(sdl_get_native_hwnd(), DSSCL_PRIORITY));
#else #else
verifyc(dsound->SetCooperativeLevel((HWND)libPvr_GetRenderTarget(), DSSCL_PRIORITY)); verifyc(dsound->SetCooperativeLevel((HWND)libPvr_GetRenderTarget(), DSSCL_PRIORITY));
#endif #endif
IDirectSoundBuffer* buffer_;
WAVEFORMATEX wfx;
DSBUFFERDESC desc;
// Set up WAV format structure. // Set up WAV format structure.
WAVEFORMATEX wfx;
memset(&wfx, 0, sizeof(WAVEFORMATEX)); memset(&wfx, 0, sizeof(WAVEFORMATEX));
wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = 2; wfx.nChannels = 2;
@ -45,104 +143,64 @@ static void directsound_init()
wfx.wBitsPerSample = 16; wfx.wBitsPerSample = 16;
// Set up DSBUFFERDESC structure. // Set up DSBUFFERDESC structure.
DSBUFFERDESC desc;
ds_ring_size=8192*wfx.nBlockAlign;
memset(&desc, 0, sizeof(DSBUFFERDESC)); memset(&desc, 0, sizeof(DSBUFFERDESC));
desc.dwSize = sizeof(DSBUFFERDESC); desc.dwSize = sizeof(DSBUFFERDESC);
desc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS; desc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS;
desc.dwBufferBytes = SAMPLE_BYTES * 2;
desc.dwBufferBytes = ds_ring_size;
desc.lpwfxFormat = &wfx; desc.lpwfxFormat = &wfx;
verifyc(dsound->CreateSoundBuffer(&desc,&buffer_,0)); // Create the buffer
verifyc(buffer_->QueryInterface(IID_IDirectSoundBuffer8,(void**)&buffer)); IDirectSoundBuffer* buffer_;
verifyc(dsound->CreateSoundBuffer(&desc, &buffer_, 0));
verifyc(buffer_->QueryInterface(IID_IDirectSoundBuffer8, (void**)&buffer));
buffer_->Release(); buffer_->Release();
//Play the buffer ! // Set up notifications
verifyc(buffer->Play(0,0,DSBPLAY_LOOPING)); IDirectSoundNotify *bufferNotify;
verifyc(buffer->QueryInterface(IID_IDirectSoundNotify8, (void**)&bufferNotify));
} notificationEvents.clear();
std::vector<DSBPOSITIONNOTIFY> posNotify;
for (int i = 0; notificationOffset(i) < desc.dwBufferBytes; i++)
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)
{ {
void* ptr1,* ptr2; notificationEvents.push_back(CreateEvent(nullptr, false, false, nullptr));
DWORD ptr1sz,ptr2sz; posNotify.push_back({ notificationOffset(i), notificationEvents.back() });
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;
} }
return 0; bufferNotify->SetNotificationPositions(posNotify.size(), &posNotify[0]);
//ds_ring_size 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) static u32 directsound_push(const void* frame, u32 samples, bool wait)
{ {
while (!directsound_push_nw(frame, samples) && wait) while (!ringBuffer.write((const u8 *)frame, samples * 4) && wait)
//DEBUG_LOG(AUDIO, "FAILED waiting on audio FAILED %d", directsound_getusedSamples()) pushWait.Wait();
;
return 1; return 1;
} }
static void directsound_term() static void directsound_term()
{ {
audioThreadRunning = false;
audioThread.join();
buffer->Stop(); buffer->Stop();
for (HANDLE event : notificationEvents)
CloseHandle(event);
buffer->Release(); buffer->Release();
dsound->Release(); dsound->Release();
} }

View File

@ -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. // If wait, then wait for the buffer to be smaller than a certain size.
stream_mutex.lock(); stream_mutex.lock();
if (wait) { if (wait) {
while (sample_count + samples > config::AudioBufferSize) { while (sample_count + samples > (u32)config::AudioBufferSize) {
stream_mutex.unlock(); stream_mutex.unlock();
read_wait.Wait(); read_wait.Wait();
read_wait.Reset();
stream_mutex.lock(); stream_mutex.lock();
} }
} }

View File

@ -56,21 +56,28 @@ class GameScanner
void add_game_directory(const std::string& path) 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); DirectoryTree tree(path);
std::string emptyParentPath = "";
for (const DirectoryTree::item& item : tree) 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) == "._") if (item.name.substr(0, 2) == "._")
// Ignore Mac OS turds // Ignore Mac OS turds
continue; continue;

View File

@ -714,15 +714,11 @@ static bool RenderFrame(int width, int height)
//setup render target first //setup render target first
if (is_rtt) if (is_rtt)
{
output_fbo = BindRTT(false); output_fbo = BindRTT(false);
if (output_fbo == 0)
return false;
}
else else
{
output_fbo = init_output_framebuffer(rendering_width, rendering_height); output_fbo = init_output_framebuffer(rendering_width, rendering_height);
} if (output_fbo == 0)
return false;
glcache.Disable(GL_SCISSOR_TEST); glcache.Disable(GL_SCISSOR_TEST);

View File

@ -1101,7 +1101,8 @@ bool RenderFrame(int width, int height)
} }
else 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; bool wide_screen_on = !is_rtt && config::Widescreen && !matrices.IsClipped() && !config::Rotate90;

View File

@ -523,7 +523,8 @@ GLuint init_output_framebuffer(int width, int height)
// Check that our FBO creation was successful // Check that our FBO creation was successful
GLuint uStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); GLuint uStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
verify(uStatus == GL_FRAMEBUFFER_COMPLETE); if (uStatus != GL_FRAMEBUFFER_COMPLETE)
return 0;
glcache.Disable(GL_SCISSOR_TEST); glcache.Disable(GL_SCISSOR_TEST);
glcache.ClearColor(0.f, 0.f, 0.f, 0.f); glcache.ClearColor(0.f, 0.f, 0.f, 0.f);

View File

@ -52,6 +52,7 @@ int screen_dpi = 96;
static bool inited = false; static bool inited = false;
float scaling = 1; float scaling = 1;
GuiState gui_state = GuiState::Main; GuiState gui_state = GuiState::Main;
static bool commandLineStart;
#ifdef __ANDROID__ #ifdef __ANDROID__
static bool touch_up; static bool touch_up;
#endif #endif
@ -235,7 +236,25 @@ void gui_init()
{ {
io.Fonts->AddFontFromFileTTF((fontDir + "PingFang.ttc").c_str(), 17.f * scaling, &font_cfg, GetGlyphRangesChineseSimplifiedOfficial()); 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 #endif
INFO_LOG(RENDERER, "Screen DPI is %d, size %d x %d. Scaling by %.2f", screen_dpi, screen_width, screen_height, scaling); 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, if (ImGui::Button("Exit", ImVec2(300 * scaling + ImGui::GetStyle().ColumnsMinSpacing + ImGui::GetStyle().FramePadding.x * 2 - 1,
50 * scaling))) 50 * scaling)))
{ {
// Exit to main menu if (!commandLineStart)
dc_term_game(); {
gui_state = GuiState::Main; // Exit to main menu
game_started = false; dc_term_game();
settings.imgread.ImagePath[0] = '\0'; gui_state = GuiState::Main;
game_started = false;
settings.imgread.ImagePath[0] = '\0';
}
else
{
// Exit emulator
dc_exit();
}
} }
ImGui::End(); ImGui::End();
@ -1011,6 +1038,16 @@ static void gui_display_settings()
ImGui::SameLine(); ImGui::SameLine();
ShowHelpMarker("The directories where your games are stored"); 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)) if (ImGui::ListBoxHeader("Home Directory", 1))
{ {
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
@ -1024,6 +1061,7 @@ static void gui_display_settings()
} }
ImGui::SameLine(); ImGui::SameLine();
ShowHelpMarker("The directory where Flycast saves configuration files and VMUs. BIOS files should be in a subfolder named \"data\""); 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, if (OptionCheckbox("Hide Legacy Naomi Roms", config::HideLegacyNaomiRoms,
"Hide .bin, .dat and .lst files from the content browser")) "Hide .bin, .dat and .lst files from the content browser"))
scanner.refresh(); scanner.refresh();
@ -1287,6 +1325,20 @@ static void gui_display_settings()
OptionCheckbox("Fog", config::Fog, "Enable fog effects"); OptionCheckbox("Fog", config::Fog, "Enable fog effects");
OptionCheckbox("Widescreen", config::Widescreen, OptionCheckbox("Widescreen", config::Widescreen,
"Draw geometry outside of the normal 4:3 aspect ratio. May produce graphical glitches in the revealed areas"); "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, 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."); "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"); 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("Disable Sound", config::DisableSound, "Disable the emulator sound output");
OptionCheckbox("Enable DSP", config::DSPEnabled, OptionCheckbox("Enable DSP", config::DSPEnabled,
"Enable the Dreamcast Digital Sound Processor. Only recommended on fast platforms"); "Enable the Dreamcast Digital Sound Processor. Only recommended on fast platforms");
#if !defined(_WIN32)
#ifdef __ANDROID__ #ifdef __ANDROID__
OptionCheckbox("Automatic Latency", config::AutoLatency, OptionCheckbox("Automatic Latency", config::AutoLatency,
"Automatically set audio latency. Recommended"); "Automatically set audio latency. Recommended");
@ -1427,7 +1478,6 @@ static void gui_display_settings()
ImGui::SameLine(); ImGui::SameLine();
ShowHelpMarker("Sets the maximum audio latency. Not supported by all audio drivers."); ShowHelpMarker("Sets the maximum audio latency. Not supported by all audio drivers.");
} }
#endif
audiobackend_t* backend = nullptr; audiobackend_t* backend = nullptr;
std::string backend_name = config::AudioBackend; std::string backend_name = config::AudioBackend;
@ -1960,6 +2010,9 @@ void gui_display_ui()
std::string game_file = settings.imgread.ImagePath; std::string game_file = settings.imgread.ImagePath;
if (!game_file.empty()) if (!game_file.empty())
{ {
#ifndef __ANDROID__
commandLineStart = true;
#endif
gui_start_game(game_file); gui_start_game(game_file);
return; return;
} }

View File

@ -24,6 +24,7 @@
#include "oslib/oslib.h" #include "oslib/oslib.h"
#include "wsi/context.h" #include "wsi/context.h"
#include "cfg/option.h" #include "cfg/option.h"
#include "emulator.h"
bool mainui_enabled; bool mainui_enabled;
u32 MainFrameCount; u32 MainFrameCount;
@ -59,6 +60,7 @@ bool mainui_rend_frame()
void mainui_init() void mainui_init()
{ {
rend_init_renderer(); rend_init_renderer();
dc_resize_renderer();
} }
void mainui_term() void mainui_term()

View File

@ -564,25 +564,24 @@ void VulkanContext::CreateSwapChain()
// The FIFO present mode is guaranteed by the spec to be supported // The FIFO present mode is guaranteed by the spec to be supported
vk::PresentModeKHR swapchainPresentMode = vk::PresentModeKHR::eFifo; vk::PresentModeKHR swapchainPresentMode = vk::PresentModeKHR::eFifo;
// Use FIFO on mobile, prefer Mailbox on desktop // 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())) 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"); INFO_LOG(RENDERER, "Using mailbox present mode");
swapchainPresentMode = vk::PresentModeKHR::eMailbox; swapchainPresentMode = vk::PresentModeKHR::eMailbox;
break; break;
} }
#ifdef TEST_AUTOMATION #endif
if (presentMode == vk::PresentModeKHR::eImmediate) if (!swapOnVSync && presentMode == vk::PresentModeKHR::eImmediate)
{ {
INFO_LOG(RENDERER, "Using immediate present mode"); INFO_LOG(RENDERER, "Using immediate present mode");
swapchainPresentMode = vk::PresentModeKHR::eImmediate; swapchainPresentMode = vk::PresentModeKHR::eImmediate;
break; break;
} }
#endif
} }
#endif
vk::SurfaceTransformFlagBitsKHR preTransform = (surfaceCapabilities.supportedTransforms & vk::SurfaceTransformFlagBitsKHR::eIdentity) ? vk::SurfaceTransformFlagBitsKHR::eIdentity : surfaceCapabilities.currentTransform; vk::SurfaceTransformFlagBitsKHR preTransform = (surfaceCapabilities.supportedTransforms & vk::SurfaceTransformFlagBitsKHR::eIdentity) ? vk::SurfaceTransformFlagBitsKHR::eIdentity : surfaceCapabilities.currentTransform;
@ -802,6 +801,13 @@ void VulkanContext::Present() noexcept
} }
renderDone = false; renderDone = false;
} }
#ifndef TEST_AUTOMATION
if (swapOnVSync == settings.input.fastForwardMode)
{
swapOnVSync = !settings.input.fastForwardMode;
resized = true;
}
#endif
if (resized) if (resized)
try { try {
CreateSwapChain(); CreateSwapChain();

View File

@ -140,6 +140,11 @@ private:
u32 width = 0; u32 width = 0;
u32 height = 0; u32 height = 0;
bool resized = false; bool resized = false;
#ifndef TEST_AUTOMATION
bool swapOnVSync = true;
#else
bool swapOnVSync = false;
#endif
vk::UniqueInstance instance; vk::UniqueInstance instance;
vk::PhysicalDevice physicalDevice; vk::PhysicalDevice physicalDevice;

View File

@ -181,7 +181,12 @@ bool EGLGraphicsContext::Init()
#ifdef TARGET_PANDORA #ifdef TARGET_PANDORA
fbdev = open("/dev/fb0", O_RDONLY); fbdev = open("/dev/fb0", O_RDONLY);
#else #else
eglSwapInterval(display, 1); #ifndef TEST_AUTOMATION
swapOnVSync = true;
#else
swapOnVSync = false;
#endif
eglSwapInterval(display, (int)swapOnVSync);
#endif #endif
PostInit(); PostInit();
@ -215,12 +220,11 @@ void EGLGraphicsContext::Swap()
{ {
#ifdef TEST_AUTOMATION #ifdef TEST_AUTOMATION
do_swap_automation(); do_swap_automation();
#endif #else
#if 0 && defined(TARGET_PANDORA) if (swapOnVSync == settings.input.fastForwardMode)
if (fbdev >= 0)
{ {
int arg = 0; swapOnVSync = !settings.input.fastForwardMode;
ioctl(fbdev,FBIO_WAITFORVSYNC,&arg); eglSwapInterval(display, (int)swapOnVSync);
} }
#endif #endif
eglSwapBuffers(display, surface); eglSwapBuffers(display, surface);

View File

@ -55,6 +55,7 @@ private:
#ifdef TARGET_PANDORA #ifdef TARGET_PANDORA
int fbdev = -1; int fbdev = -1;
#endif #endif
bool swapOnVSync = false;
}; };
extern EGLGraphicsContext theGLContext; extern EGLGraphicsContext theGLContext;

View File

@ -77,6 +77,14 @@ bool SDLGLGraphicsContext::Init()
INFO_LOG(RENDERER, "Created SDL Window and GL Context successfully"); INFO_LOG(RENDERER, "Created SDL Window and GL Context successfully");
SDL_GL_MakeCurrent(window, glcontext); 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 #ifdef GLES
load_gles_symbols(); load_gles_symbols();
@ -94,6 +102,15 @@ bool SDLGLGraphicsContext::Init()
void SDLGLGraphicsContext::Swap() 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); SDL_GL_SwapWindow(window);
/* Check if drawable has been resized */ /* Check if drawable has been resized */

View File

@ -45,6 +45,7 @@ public:
private: private:
SDL_Window* window = nullptr; SDL_Window* window = nullptr;
SDL_GLContext glcontext = nullptr; SDL_GLContext glcontext = nullptr;
bool swapOnVSync = false;
}; };
extern SDLGLGraphicsContext theGLContext; extern SDLGLGraphicsContext theGLContext;

View File

@ -82,6 +82,21 @@ bool XGLGraphicsContext::Init()
unsigned int tempu; unsigned int tempu;
XGetGeometry(display, window, &win, &temp, &temp, (u32 *)&screen_width, (u32 *)&screen_height, &tempu, &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(); PostInit();
return true; return true;
@ -142,32 +157,22 @@ void XGLGraphicsContext::Swap()
{ {
#ifdef TEST_AUTOMATION #ifdef TEST_AUTOMATION
do_swap_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 #endif
glXSwapBuffers(display, window); glXSwapBuffers(display, window);
Window win; Window win;
int temp; int temp;
unsigned int tempu, new_w, new_h; unsigned int tempu;
XGetGeometry(display, window, &win, &temp, &temp, &new_w, &new_h, &tempu, &tempu); XGetGeometry(display, window, &win, &temp, &temp, (u32 *)&screen_width, (u32 *)&screen_height, &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
} }
void XGLGraphicsContext::Term() void XGLGraphicsContext::Term()

View File

@ -41,6 +41,9 @@ private:
Display *display; Display *display;
GLXContext context; GLXContext context;
GLXFBConfig* framebufferConfigs = nullptr; GLXFBConfig* framebufferConfigs = nullptr;
PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = nullptr;
int (*glXSwapIntervalMESA)(unsigned int interval) = nullptr;
bool swapOnVSync = false;
}; };
extern XGLGraphicsContext theGLContext; extern XGLGraphicsContext theGLContext;

View File

@ -35,6 +35,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import tv.ouya.console.api.OuyaController; import tv.ouya.console.api.OuyaController;
@ -83,7 +84,8 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat.
OuyaController.init(this); OuyaController.init(this);
String home_directory = prefs.getString(Config.pref_home, ""); 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) { if (result != null) {
AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
dlgAlert.setMessage("Initialization failed. Please try again and/or reinstall.\n\n" 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); STORAGE_PERM_REQUEST);
} }
else
storagePermissionGranted = true;
InputDeviceManager.getInstance().startListening(getApplicationContext()); InputDeviceManager.getInstance().startListening(getApplicationContext());
register(this); register(this);

View File

@ -9,7 +9,7 @@ public final class JNIdc
{ {
static { System.loadLibrary("flycast"); } 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 setExternalStorageDirectories(Object[] pathList);
public static native void setGameUri(String fileName); public static native void setGameUri(String fileName);
public static native void pause(); public static native void pause();

View File

@ -85,7 +85,7 @@ JNIEXPORT type JNICALL Java_com_reicast_emulator_emu_JNIdc_get ## setting(JNIEnv
extern "C" 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_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_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"))); 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 // Initialize platform-specific stuff
common_linux_setup(); 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, "Config dir is: %s", get_writable_config_path("").c_str());
INFO_LOG(BOOT, "Data dir is: %s", get_writable_data_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) if (first_init)
{ {
// Do one-time initialization // Do one-time initialization

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

BIN
shell/imgs/screenshot1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
shell/imgs/screenshot2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
shell/imgs/screenshot3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 KiB

BIN
shell/imgs/screenshot4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 KiB

View File

@ -277,6 +277,7 @@ else ifneq (,$(findstring win32,$(platform)))
CC = gcc CC = gcc
CXX = g++ CXX = g++
USE_SDL = 1 USE_SDL = 1
USE_SDLAUDIO = 1
STATIC_LIBZIP = 1 STATIC_LIBZIP = 1
ifeq ($(WITH_DYNAREC), x86) ifeq ($(WITH_DYNAREC), x86)
X86_REC := 1 X86_REC := 1

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>org.flycast.Flycast</id>
<name>Flycast</name>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-2.0</project_license>
<summary>Multiplatform Sega Dreamcast, Naomi and Atomiswave emulator.</summary>
<description>
<p>Flycast is a multi-platform Sega Dreamcast, Naomi and Atomiswave emulator derived from reicast.</p>
</description>
<content_rating type="oars-1.1" />
<launchable type="desktop-id">org.flycast.Flycast.desktop</launchable>
<provides>
<binary>flycast</binary>
<id>flycast.desktop</id>
</provides>
<screenshots>
<screenshot type="default"><image>https://raw.githubusercontent.com/flyinghead/flycast/master/shell/imgs/screenshot1.png</image></screenshot>
<screenshot><image>https://raw.githubusercontent.com/flyinghead/flycast/master/shell/imgs/screenshot2.png</image></screenshot>
<screenshot><image>https://raw.githubusercontent.com/flyinghead/flycast/master/shell/imgs/screenshot3.png</image></screenshot>
<screenshot><image>https://raw.githubusercontent.com/flyinghead/flycast/master/shell/imgs/screenshot4.png</image></screenshot>
</screenshots>
<categories>
<category>Games</category>
<category>Emulator</category>
</categories>
<url type="homepage">https://github.com/flyinghead/flycast#readme</url>
<url type="bugtracker">https://github.com/flyinghead/flycast/issues</url>
<url type="help">https://github.com/TheArcadeStriker/flycast-wiki/wiki</url>
<url type="faq">https://github.com/TheArcadeStriker/flycast-wiki/wiki/Frequently-Asked-Questions-(F.A.Q.)</url>
<developer_name>Flyinghead</developer_name>
<releases>
<release date="2021-04-08" version="nightly" />
</releases>
</component>