Merge remote-tracking branch 'origin/dev'

This commit is contained in:
Flyinghead 2022-09-26 11:07:33 +02:00
commit d85baba1d3
105 changed files with 28795 additions and 1333 deletions

View File

@ -41,7 +41,7 @@ jobs:
- name: Set up build environment (Linux)
run: |
sudo apt-get update
sudo apt-get -y install ccache libao-dev libasound2-dev libevdev-dev libgl1-mesa-dev liblua5.3-dev libminiupnpc-dev libpulse-dev libsdl2-dev libudev-dev libzip-dev ninja-build
sudo apt-get -y install ccache libao-dev libasound2-dev libevdev-dev libgl1-mesa-dev liblua5.3-dev libminiupnpc-dev libpulse-dev libsdl2-dev libudev-dev libzip-dev ninja-build libcurl4-openssl-dev
if: runner.os == 'Linux'
- name: Set up build environment (Windows, MinGW)

View File

@ -186,7 +186,7 @@ if(IOS)
GLES_SILENCE_DEPRECATION)
endif()
target_include_directories(${PROJECT_NAME} PRIVATE core core/deps core/deps/stb core/khronos)
target_include_directories(${PROJECT_NAME} PRIVATE core core/deps core/deps/stb core/khronos core/deps/json)
if(LIBRETRO)
target_include_directories(${PROJECT_NAME} PRIVATE shell/libretro)
endif()
@ -334,6 +334,11 @@ if(NOT LIBRETRO)
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_SDL USE_SDL_AUDIO)
target_sources(${PROJECT_NAME} PRIVATE core/sdl/sdl.cpp core/sdl/sdl.h core/sdl/sdl_gamepad.h core/sdl/sdl_keyboard.h)
if((UNIX AND NOT APPLE) OR NINTENDO_SWITCH)
find_package(CURL REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl)
endif()
endif()
find_package(ZLIB)
@ -827,7 +832,10 @@ target_sources(${PROJECT_NAME} PRIVATE
core/imgread/cue.cpp
core/imgread/gdi.cpp
core/imgread/ImgReader.cpp
core/imgread/ioctl.cpp)
core/imgread/ioctl.cpp
core/imgread/iso9660.h
core/imgread/isofs.cpp
core/imgread/isofs.h)
if(NOT LIBRETRO)
target_sources(${PROJECT_NAME} PRIVATE
@ -898,6 +906,8 @@ target_sources(${PROJECT_NAME} PRIVATE
core/network/net_handshake.cpp
core/network/net_handshake.h
core/network/net_platform.h
core/network/output.cpp
core/network/output.h
core/network/picoppp.cpp
core/network/picoppp.h)
@ -943,7 +953,6 @@ target_sources(${PROJECT_NAME} PRIVATE
core/reios/font.h
core/reios/gdrom_hle.cpp
core/reios/gdrom_hle.h
core/reios/iso9660.h
core/reios/reios.cpp
core/reios/reios.h
core/reios/reios_elf.cpp
@ -1047,7 +1056,15 @@ if(NOT LIBRETRO)
core/rend/gui_util.cpp
core/rend/gui_util.h
core/rend/mainui.cpp
core/rend/mainui.h)
core/rend/mainui.h
core/rend/boxart/boxart.cpp
core/rend/boxart/boxart.h
core/rend/boxart/gamesdb.cpp
core/rend/boxart/gamesdb.h
core/rend/boxart/http_client.cpp
core/rend/boxart/http_client.h
core/rend/boxart/scraper.cpp
core/rend/boxart/scraper.h)
endif()
if(USE_VULKAN)
@ -1121,6 +1138,9 @@ if(USE_VULKAN)
core/rend/vulkan/vulkan_context.cpp
core/rend/vulkan/imgui_impl_vulkan.cpp
core/rend/vulkan/imgui_impl_vulkan.h)
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
target_compile_definitions(${PROJECT_NAME} PRIVATE ImTextureID=ImU64)
endif()
endif()
endif()
@ -1335,7 +1355,10 @@ if(NOT LIBRETRO)
if(ANDROID)
target_compile_definitions(${PROJECT_NAME} PRIVATE GLES GLES3)
target_sources(${PROJECT_NAME} PRIVATE shell/android-studio/flycast/src/main/jni/src/Android.cpp)
target_sources(${PROJECT_NAME} PRIVATE
shell/android-studio/flycast/src/main/jni/src/Android.cpp
shell/android-studio/flycast/src/main/jni/src/android_gamepad.h
shell/android-studio/flycast/src/main/jni/src/android_keyboard.h)
target_link_libraries(${PROJECT_NAME} PRIVATE android EGL GLESv2 log)
elseif(APPLE)
@ -1347,6 +1370,7 @@ if(NOT LIBRETRO)
target_link_libraries(${PROJECT_NAME} PRIVATE AltKit)
target_sources(${PROJECT_NAME} PRIVATE
shell/apple/common/http_client.mm
shell/apple/emulator-ios/emulator/AppDelegate.h
shell/apple/emulator-ios/emulator/AppDelegate.mm
shell/apple/emulator-ios/emulator/ios_main.mm
@ -1429,6 +1453,7 @@ if(NOT LIBRETRO)
COMMAND ${CMAKE_COMMAND} -E tar "cfv" "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>-${CMAKE_OSX_SYSROOT}/Flycast.ipa" --format=zip ${CMAKE_CURRENT_BINARY_DIR}/Payload)
else()
target_sources(${PROJECT_NAME} PRIVATE
shell/apple/common/http_client.mm
shell/apple/emulator-osx/emulator-osx/SDLMain.h
shell/apple/emulator-osx/emulator-osx/SDLMain.mm
shell/apple/emulator-osx/emulator-osx/osx-main.mm)
@ -1507,11 +1532,12 @@ if(NOT LIBRETRO)
core/deps/SDL/src/main/winrt/SDL2-WinRTResources.rc)
target_sources(${PROJECT_NAME} PRIVATE
${ResourceFiles}
core/deps/SDL/src/main/winrt/SDL_winrt_main_NonXAML.cpp)
core/deps/SDL/src/main/winrt/SDL_winrt_main_NonXAML.cpp
shell/uwp/http_client.cpp)
set_target_properties(${PROJECT_NAME} PROPERTIES RESOURCE "${ResourceFiles}")
else()
target_sources(${PROJECT_NAME} PRIVATE shell/windows/flycast.rc)
target_link_libraries(${PROJECT_NAME} PRIVATE dsound opengl32 winmm ws2_32 wsock32 xinput9_1_0 cfgmgr32)
target_link_libraries(${PROJECT_NAME} PRIVATE dsound opengl32 winmm ws2_32 wsock32 xinput9_1_0 cfgmgr32 wininet)
endif()
endif()
endif()

View File

@ -38,6 +38,8 @@ Option<bool> AutoLoadState("Dreamcast.AutoLoadState");
Option<bool> AutoSaveState("Dreamcast.AutoSaveState");
Option<int> SavestateSlot("Dreamcast.SavestateSlot");
Option<bool> ForceFreePlay("ForceFreePlay", true);
Option<bool> FetchBoxart("FetchBoxart", true);
Option<bool> BoxartDisplayMode("BoxartDisplayMode", true);
// Sound
@ -131,6 +133,7 @@ Option<int> GGPOAnalogAxes("GGPOAnalogAxes", 0, "network");
Option<bool> GGPOChat("GGPOChat", true, "network");
Option<bool> GGPOChatTimeoutToggle("GGPOChatTimeoutToggle", true, "network");
Option<int> GGPOChatTimeout("GGPOChatTimeout", 10, "network");
Option<bool> NetworkOutput("NetworkOutput", false, "network");
#ifdef SUPPORT_DISPMANX
Option<bool> DispmanxMaintainAspect("maintain_aspect", true, "dispmanx");

View File

@ -374,6 +374,8 @@ extern Option<bool> AutoLoadState;
extern Option<bool> AutoSaveState;
extern Option<int> SavestateSlot;
extern Option<bool> ForceFreePlay;
extern Option<bool> FetchBoxart;
extern Option<bool> BoxartDisplayMode;
// Sound
@ -493,6 +495,7 @@ extern Option<int> GGPOAnalogAxes;
extern Option<bool> GGPOChat;
extern Option<bool> GGPOChatTimeoutToggle;
extern Option<int> GGPOChatTimeout;
extern Option<bool> NetworkOutput;
#ifdef SUPPORT_DISPMANX
extern Option<bool> DispmanxMaintainAspect;

View File

@ -639,10 +639,10 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool
{
g.NavDisableHighlight = true;
// Check if dragging (except for scrollbars)
if (held && !hovered && !pressed)
if (held && !pressed)
{
ImVec2 delta = GetMouseDragDelta(ImGuiMouseButton_Left);
if (delta.x != 0.f || delta.y != 0.f)
if (ImAbs(delta.x) >= 5.f || ImAbs(delta.y) >= 5.f)
{
ClearActiveID();
// Find an ancestor window that allows drag scrolling.
@ -654,9 +654,10 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool
&& scrollableWindow->ScrollMax.y == 0.0f)
scrollableWindow = scrollableWindow->ParentWindow;
if (scrollableWindow != nullptr && (scrollableWindow->Flags & ImGuiWindowFlags_DragScrolling))
{
scrollableWindow->DragScrolling = true;
held = false;
pressed = false;
held = false;
}
}
}
}

23746
core/deps/json/json.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -128,6 +128,12 @@ static void loadSpecialSettings()
INFO_LOG(BOOT, "Enabling Extra depth scaling for game %s", prod_id.c_str());
config::ExtraDepthScale.override(0.1f);
}
// South Park Rally
else if (prod_id == "T-8116N" || prod_id == "T-8112D-50")
{
INFO_LOG(BOOT, "Enabling Extra depth scaling for game %s", prod_id.c_str());
config::ExtraDepthScale.override(1000.f);
}
std::string areas(ip_meta.area_symbols, sizeof(ip_meta.area_symbols));
bool region_usa = areas.find('U') != std::string::npos;
@ -428,7 +434,7 @@ void Emulator::init()
state = Init;
}
static int getGamePlatform(const char *path)
int getGamePlatform(const char *path)
{
if (path == NULL)
// Dreamcast BIOS
@ -823,6 +829,7 @@ bool Emulator::checkStatus()
bool Emulator::render()
{
rend_resize_renderer_if_needed();
if (!config::ThreadedRendering)
{
if (state != Running)

View File

@ -173,3 +173,5 @@ private:
bool renderTimeout = false;
};
extern Emulator emu;
int getGamePlatform(const char *path);

View File

@ -841,11 +841,8 @@ void gd_process_spi_cmd()
if (param_type == 1 || param_type == 2)
{
cdda.status = cdda_t::Playing;
SecNumber.Status = GD_PLAY;
bool min_sec_frame = param_type == 2;
cdda.StartAddr.FAD = cdda.CurrAddr.FAD = GetFAD(&packet_cmd.data_8[2], min_sec_frame);
cdda.StartAddr.FAD = GetFAD(&packet_cmd.data_8[2], min_sec_frame);
cdda.EndAddr.FAD = GetFAD(&packet_cmd.data_8[8], min_sec_frame);
if (cdda.EndAddr.FAD == 0)
{
@ -856,6 +853,13 @@ void gd_process_spi_cmd()
cdda.EndAddr.FAD = ses_inf[3] << 16 | ses_inf[4] << 8 | ses_inf[5];
}
cdda.repeats = packet_cmd.data_8[6] & 0xF;
if ((cdda.status != cdda_t::Playing && cdda.status != cdda_t::Paused)
|| cdda.CurrAddr.FAD < cdda.StartAddr.FAD
|| cdda.CurrAddr.FAD > cdda.EndAddr.FAD)
cdda.CurrAddr.FAD = cdda.StartAddr.FAD;
cdda.status = cdda_t::Playing;
SecNumber.Status = GD_PLAY;
GDStatus.DSC = 1;
}
else if (param_type == 7)

View File

@ -24,6 +24,7 @@
#include "oslib/oslib.h"
#include "stdclass.h"
#include "cfg/option.h"
#include "network/output.h"
#define LOGJVS(...) DEBUG_LOG(JVS, __VA_ARGS__)
@ -195,7 +196,23 @@ protected:
}
}
virtual void write_digital_out(int count, u8 *data) { }
virtual void write_digital_out(int count, u8 *data)
{
u32 newOutput = digOutput;
for (int i = 0; i < count && i < 4; i++)
{
u32 mask = 0xff << (i * 8);
newOutput = (newOutput & ~mask) | (data[i] << (i * 8));
}
u32 changes = newOutput ^ digOutput;
for (int i = 0; i < 32; i++)
if (changes & (1 << i))
{
std::string name = "lamp" + std::to_string(i);
networkOutput.output(name.c_str(), (newOutput >> i) & 1);
}
digOutput = newOutput;
}
u32 player_count = 0;
u32 digital_in_count = 0;
@ -242,6 +259,7 @@ private:
std::array<u32, 32> cur_mapping;
std::array<u32, 32> p1_mapping;
std::array<u32, 32> p2_mapping;
u32 digOutput = 0;
};
// Most common JVS board
@ -1632,13 +1650,18 @@ u32 jvs_io_board::handle_jvs_message(u8 *buffer_in, u32 length_in, u8 *buffer_ou
break;
case 0x32: // switched outputs
case 0x33:
LOGJVS("output(%d) %x", buffer_in[cmdi + 1], buffer_in[cmdi + 2]);
write_digital_out(buffer_in[cmdi + 1], &buffer_in[cmdi + 2]);
JVS_STATUS1(); // report byte
cmdi += buffer_in[cmdi + 1] + 2;
break;
case 0x33: // Analog output
LOGJVS("analog output(%d) %x", buffer_in[cmdi + 1], buffer_in[cmdi + 2]);
JVS_STATUS1(); // report byte
cmdi += buffer_in[cmdi + 1] + 2;
break;
case 0x30: // substract coin
if (buffer_in[cmdi + 1] > 0 && first_player + buffer_in[cmdi + 1] - 1 < (int)ARRAY_SIZE(coin_count))
coin_count[first_player + buffer_in[cmdi + 1] - 1] -= (buffer_in[cmdi + 2] << 8) + buffer_in[cmdi + 3];

View File

@ -16,6 +16,7 @@
#include "naomi_regs.h"
#include "naomi_m3comm.h"
#include "serialize.h"
#include "network/output.h"
//#define NAOMI_COMM
@ -525,6 +526,7 @@ void naomi_reg_Init()
}
#endif
NaomiInit();
networkOutput.init();
}
void naomi_reg_Term()
@ -540,6 +542,7 @@ void naomi_reg_Term()
}
#endif
m3comm.closeNetwork();
networkOutput.term();
}
void naomi_reg_Reset(bool hard)
@ -578,7 +581,7 @@ void naomi_reg_Reset(bool hard)
static u8 aw_maple_devs;
static u64 coin_chute_time[4];
static u8 ffbOuput;
static u8 awDigitalOuput;
u32 libExtDevice_ReadMem_A0_006(u32 addr,u32 size) {
addr &= 0x7ff;
@ -636,7 +639,7 @@ u32 libExtDevice_ReadMem_A0_006(u32 addr,u32 size) {
// ??? Dolphin Blue
return 0;
case 0x28c:
return ffbOuput;
return awDigitalOuput;
}
INFO_LOG(NAOMI, "Unhandled read @ %x sz %d", addr, size);
return 0xFF;
@ -654,11 +657,27 @@ void libExtDevice_WriteMem_A0_006(u32 addr,u32 data,u32 size) {
case 0x288:
// ??? Dolphin Blue
return;
case 0x28C: // Wheel force feedback
// bit 0 direction (0 pos, 1 neg)
// bit 1-4 strength
ffbOuput = data;
DEBUG_LOG(NAOMI, "AW output %02x", data);
case 0x28C: // Digital output
if ((u8)data != awDigitalOuput)
{
if (atomiswaveForceFeedback)
// Wheel force feedback:
// bit 0 direction (0 pos, 1 neg)
// bit 1-4 strength
networkOutput.output("awffb", (u8)data);
else
{
u8 changes = data ^ awDigitalOuput;
for (int i = 0; i < 8; i++)
if (changes & (1 << i))
{
std::string name = "lamp" + std::to_string(i);
networkOutput.output(name.c_str(), (data >> i) & 1);
}
}
awDigitalOuput = data;
DEBUG_LOG(NAOMI, "AW output %02x", data);
}
return;
default:
break;
@ -780,6 +799,8 @@ static void forceFeedbackMidiReceiver(u8 data)
// https://github.com/Boomslangnz/FFBArcadePlugin/blob/master/Game%20Files/Demul.cpp
if (midiTxBuf[0] == 0x85 && midiTxBuf[1] == 0x3f)
MapleConfigMap::UpdateVibration(0, std::max(0.f, (float)(midiTxBuf[2] - 1) / 24.f), 0.f, 5);
if (midiTxBuf[0] != 0xfd)
networkOutput.output("midiffb", (midiTxBuf[0] << 16) | (midiTxBuf[1]) << 8 | midiTxBuf[2]);
}
midiTxBufIndex = (midiTxBufIndex + 1) % ARRAY_SIZE(midiTxBuf);
}

View File

@ -47,6 +47,8 @@ char naomi_game_id[33];
InputDescriptors *NaomiGameInputs;
u8 *naomi_default_eeprom;
bool atomiswaveForceFeedback;
extern MemChip *sys_rom;
static bool loadBios(const char *filename, Archive *child_archive, Archive *parent_archive, int region)
@ -570,6 +572,7 @@ void naomi_cart_LoadRom(const char* file, LoadProgress *progress)
else
loadDecryptedRom(file, progress);
atomiswaveForceFeedback = false;
RomBootID bootId;
if (CurrentCartridge->GetBootId(&bootId))
{
@ -586,6 +589,10 @@ void naomi_cart_LoadRom(const char* file, LoadProgress *progress)
card_reader::initialDCardReader.init();
initMidiForceFeedback();
}
else if (gameId == "MAXIMUM SPEED" || gameId == "FASTER THAN SPEED")
{
atomiswaveForceFeedback = true;
}
else if (gameId == "SAMPLE GAME MAX LONG NAME-") // Driving Simulator
{
initMidiForceFeedback();

View File

@ -166,3 +166,4 @@ struct InputDescriptors
};
extern InputDescriptors *NaomiGameInputs;
extern bool atomiswaveForceFeedback;

File diff suppressed because it is too large Load Diff

View File

@ -38,6 +38,7 @@ bool fb_dirty;
static bool pend_rend;
TA_context* _pvrrc;
extern bool rend_needs_resize;
static bool rend_frame(TA_context* ctx)
{
@ -363,32 +364,42 @@ void rend_deserialize(Deserializer& deser)
deser >> fb_watch_addr_end;
}
pend_rend = false;
rend_needs_resize = true;
}
void rend_resize_renderer()
{
if (renderer == nullptr)
return;
float hres;
int vres = config::RenderResolution;
int fbwidth = 640 / (1 + VO_CONTROL.pixel_double) * (1 + SCALER_CTL.hscale);
int fbheight = FB_R_CTRL.vclk_div == 1 || SPG_CONTROL.interlace == 1 ? 480 : 240;
if (SPG_CONTROL.interlace == 0 && SCALER_CTL.vscalefactor > 0x400)
fbheight *= std::roundf((float)SCALER_CTL.vscalefactor / 0x400);
float upscaling = config::RenderResolution / 480.f;
float hres = fbwidth * upscaling;
float vres = fbheight * upscaling;
if (config::Widescreen && !config::Rotate90)
{
if (config::SuperWidescreen)
hres = (float)config::RenderResolution * settings.display.width / settings.display.height;
hres *= (float)settings.display.width / settings.display.height / 4.f * 3.f;
else
hres = config::RenderResolution * 16.f / 9.f;
}
else if (config::Rotate90)
{
vres = vres * config::ScreenStretching / 100;
hres = config::RenderResolution * 4.f / 3.f;
}
else
{
hres = config::RenderResolution * 4.f * config::ScreenStretching / 3.f / 100.f;
hres *= 4.f / 3.f;
}
if (!config::Rotate90)
hres = std::roundf(hres / 2.f) * 2.f;
DEBUG_LOG(RENDERER, "rend_resize_renderer: %d x %d", (int)hres, vres);
renderer->Resize((int)hres, vres);
DEBUG_LOG(RENDERER, "rend_resize_renderer: %d x %d", (int)hres, (int)vres);
if (renderer != nullptr)
renderer->Resize((int)hres, (int)vres);
rend_needs_resize = false;
#ifdef LIBRETRO
void retro_resize_renderer(int w, int h);
retro_resize_renderer((int)hres, (int)vres);
#endif
}
void rend_resize_renderer_if_needed()
{
if (!rend_needs_resize)
return;
rend_resize_renderer();
}

View File

@ -21,6 +21,7 @@ void rend_allow_rollback();
void rend_serialize(Serializer& ser);
void rend_deserialize(Deserializer& deser);
void rend_resize_renderer();
void rend_resize_renderer_if_needed();
///////
extern TA_context* _pvrrc;

View File

@ -7,6 +7,7 @@
bool pal_needs_update=true;
bool fog_needs_update=true;
bool rend_needs_resize = true;
u8 pvr_regs[pvr_RegSize];
@ -159,6 +160,16 @@ void pvr_WriteReg(u32 paddr,u32 data)
{
PvrReg(addr, u32) = data;
CalculateSync();
if (addr == SPG_CONTROL_addr)
rend_needs_resize = true;
}
return;
case VO_CONTROL_addr:
if (PvrReg(addr, u32) != data)
{
PvrReg(addr, u32) = data;
rend_needs_resize = true;
}
return;
@ -167,7 +178,10 @@ void pvr_WriteReg(u32 paddr,u32 data)
bool vclk_div_changed = (PvrReg(addr, u32) ^ data) & (1 << 23);
PvrReg(addr, u32) = data;
if (vclk_div_changed)
{
CalculateSync();
rend_needs_resize = true;
}
}
return;

View File

@ -36,30 +36,6 @@ static u32 lightgun_line = 0xffff;
static u32 lightgun_hpos;
static bool maple_int_pending;
static void setFramebufferScaling()
{
float scale_x = 1.f;
float scale_y = 1.f;
if (SPG_CONTROL.interlace)
{
//u32 interl_mode=VO_CONTROL.field_mode;
//if (interl_mode==2)//3 will be funny =P
// scale_y=0.5f;//single interlace
//else
scale_y = 1.f;
}
else
{
if (FB_R_CTRL.vclk_div)
scale_y = 1.0f;//non interlaced VGA mode has full resolution :)
else
scale_y = 0.5f;//non interlaced modes have half resolution
}
rend_set_fb_scale(scale_x, scale_y);
}
void CalculateSync()
{
u32 pixel_clock = PIXEL_CLOCK / (FB_R_CTRL.vclk_div ? 1 : 2);
@ -72,8 +48,6 @@ void CalculateSync()
if (SPG_CONTROL.interlace)
Line_Cycles /= 2;
setFramebufferScaling();
Frame_Cycles = pvr_numscanlines * Line_Cycles;
prv_cur_scanline = 0;
clc_pvr_scanline = 0;
@ -377,6 +351,4 @@ void spg_Deserialize(Deserializer& deser)
}
if (deser.version() < Deserializer::V14)
CalculateSync();
else
setFramebufferScaling();
}

View File

@ -168,6 +168,7 @@ struct tad_context
struct RenderPass {
bool autosort;
bool z_clear;
bool mv_op_tr_shared; // Use opaque modvols geometry for translucent modvols
u32 op_count;
u32 mvo_count;
u32 pt_count;

View File

@ -1122,9 +1122,8 @@ private:
}
};
static bool ClearZBeforePass(int pass_number);
static bool UsingAutoSort(int pass_number);
static void getRegionTileClipping(u32& xmin, u32& xmax, u32& ymin, u32& ymax);
static void getRegionSettings(int passNumber, RenderPass& pass);
//
// Check if a vertex has huge x,y,z values or negative z
@ -1343,8 +1342,7 @@ static bool ta_parse_vdrc(TA_context* ctx)
render_pass->tr_count, mergeTranslucent, &vd_rc);
tr_poly_count = render_pass->tr_count;
render_pass->mvo_tr_count = vd_rc.global_param_mvo_tr.used();
render_pass->autosort = UsingAutoSort(pass);
render_pass->z_clear = ClearZBeforePass(pass);
getRegionSettings(pass, *render_pass);
}
childCtx = childCtx->nextContext;
pass++;
@ -1769,6 +1767,27 @@ void FillBGP(TA_context* ctx)
cv[3].v = max_v;
}
static void getRegionTileAddrAndSize(u32& address, u32& size)
{
address = REGION_BASE;
const bool type1_tile = ((FPU_PARAM_CFG >> 21) & 1) == 0;
size = (type1_tile ? 5 : 6) * 4;
bool empty_first_region = true;
for (int i = type1_tile ? 4 : 5; i > 0; i--)
if ((pvr_read32p<u32>(address + i * 4) & 0x80000000) == 0)
{
empty_first_region = false;
break;
}
if (empty_first_region)
address += size;
RegionArrayTile tile;
tile.full = pvr_read32p<u32>(address);
if (tile.PreSort)
// Windows CE weirdness
size = 6 * 4;
}
static void getRegionTileClipping(u32& xmin, u32& xmax, u32& ymin, u32& ymax)
{
xmin = 20;
@ -1776,18 +1795,9 @@ static void getRegionTileClipping(u32& xmin, u32& xmax, u32& ymin, u32& ymax)
ymin = 15;
ymax = 0;
u32 addr = REGION_BASE;
const bool type1_tile = ((FPU_PARAM_CFG >> 21) & 1) == 0;
int tile_size = (type1_tile ? 5 : 6) * 4;
bool empty_first_region = true;
for (int i = type1_tile ? 4 : 5; i > 0; i--)
if ((pvr_read32p<u32>(addr + i * 4) & 0x80000000) == 0)
{
empty_first_region = false;
break;
}
if (empty_first_region)
addr += tile_size;
u32 addr;
u32 tile_size;
getRegionTileAddrAndSize(addr, tile_size);
RegionArrayTile tile;
do {
@ -1796,9 +1806,6 @@ static void getRegionTileClipping(u32& xmin, u32& xmax, u32& ymin, u32& ymax)
xmax = std::max(xmax, tile.X);
ymin = std::min(ymin, tile.Y);
ymax = std::max(ymax, tile.Y);
if (type1_tile && tile.PreSort)
// Windows CE weirdness
tile_size = 6 * 4;
addr += tile_size;
} while (!tile.LastRegion);
@ -1808,72 +1815,14 @@ static void getRegionTileClipping(u32& xmin, u32& xmax, u32& ymin, u32& ymax)
ymax *= 32;
}
static RegionArrayTile getRegionTile(int pass_number)
{
u32 addr = REGION_BASE;
const bool type1_tile = ((FPU_PARAM_CFG >> 21) & 1) == 0;
int tile_size = (type1_tile ? 5 : 6) * 4;
bool empty_first_region = true;
for (int i = type1_tile ? 4 : 5; i > 0; i--)
if ((pvr_read32p<u32>(addr + i * 4) & 0x80000000) == 0)
{
empty_first_region = false;
break;
}
if (empty_first_region)
addr += tile_size;
RegionArrayTile tile;
tile.full = pvr_read32p<u32>(addr);
if (type1_tile && tile.PreSort)
// Windows CE weirdness
tile_size = 6 * 4;
tile.full = pvr_read32p<u32>(addr + pass_number * tile_size);
return tile;
}
static bool UsingAutoSort(int pass_number)
{
if (((FPU_PARAM_CFG >> 21) & 1) == 0)
// Type 1 region header type
return ((ISP_FEED_CFG & 1) == 0);
else
{
// Type 2
RegionArrayTile tile = getRegionTile(pass_number);
return !tile.PreSort;
}
}
static bool ClearZBeforePass(int pass_number)
{
RegionArrayTile tile = getRegionTile(pass_number);
return !tile.NoZClear;
}
int getTAContextAddresses(u32 *addresses)
{
u32 addr = REGION_BASE;
const bool type1_tile = ((FPU_PARAM_CFG >> 21) & 1) == 0;
int tile_size = (type1_tile ? 5 : 6) * 4;
bool empty_first_region = true;
for (int i = type1_tile ? 4 : 5; i > 0; i--)
if ((pvr_read32p<u32>(addr + i * 4) & 0x80000000) == 0)
{
empty_first_region = false;
break;
}
if (empty_first_region)
addr += tile_size;
u32 addr;
u32 tile_size;
getRegionTileAddrAndSize(addr, tile_size);
RegionArrayTile tile;
tile.full = pvr_read32p<u32>(addr);
if (type1_tile && tile.PreSort)
// Windows CE weirdness
tile_size = 6 * 4;
u32 x = tile.X;
u32 y = tile.Y;
u32 count = 0;
@ -1906,6 +1855,25 @@ int getTAContextAddresses(u32 *addresses)
return count;
}
static void getRegionSettings(int passNumber, RenderPass& pass)
{
u32 addr;
u32 tileSize;
getRegionTileAddrAndSize(addr, tileSize);
addr += passNumber * tileSize;
RegionArrayTile tile;
tile.full = pvr_read32p<u32>(addr);
if (((FPU_PARAM_CFG >> 21) & 1) == 0)
// Type 1 region header type
pass.autosort = (ISP_FEED_CFG & 1) == 0;
else
// Type 2
pass.autosort = !tile.PreSort;
pass.z_clear = !tile.NoZClear;
pass.mv_op_tr_shared = pvr_read32p<u32>(addr + 8) == pvr_read32p<u32>(addr + 16);
}
void rend_context::newRenderPass()
{
RenderPass pass;
@ -1914,7 +1882,6 @@ void rend_context::newRenderPass()
pass.pt_count = global_param_pt.used();
pass.mvo_count = global_param_mvo.used();
pass.mvo_tr_count = global_param_mvo_tr.used();
pass.autosort = UsingAutoSort(render_passes.used());
pass.z_clear = ClearZBeforePass(render_passes.used());
getRegionSettings(render_passes.used(), pass);
*render_passes.Append() = pass;
}

View File

@ -194,7 +194,7 @@ void CHDDisc::tryOpen(const char* file)
u32 sectorSize = getSectorSize(type);
t.file = new CHDTrack(this, Offset - t.StartFAD, sectorSize,
// audio tracks are byteswapped in recent CHDv5+
t.CTRL == 0 && needAudioSwap);
!t.isDataTrack() && needAudioSwap);
// CHD files are padded, so we have to respect the offset
int padded = (frames + CD_TRACK_PADDING - 1) / CD_TRACK_PADDING;

View File

@ -87,14 +87,7 @@ Disc* OpenDisc(const std::string& path, std::vector<u8> *digest)
Disc *disc = driver(path.c_str(), digest);
if (disc != nullptr)
{
if (cdi_parse == driver) {
const char warn_str[] = "Warning: CDI Image Loaded! Many CDI images are known to be defective, GDI, CUE or CHD format is preferred. "
"Please only file bug reports when using images known to be good (GDI, CUE or CHD).";
WARN_LOG(GDROM, "%s", warn_str);
}
return disc;
}
}
return nullptr;
@ -180,25 +173,24 @@ void TermDrive()
//
//convert our nice toc struct to dc's native one :)
static u32 CreateTrackInfo(u32 ctrl, u32 addr, u32 fad)
static u32 createTrackInfo(const Track& track, u32 fad)
{
u32 addr = track.ADDR;
if (!track.isDataTrack())
// audio tracks: sub-q channel indicates current position
addr |= 1;
u8 p[4];
p[0]=(ctrl<<4)|(addr<<0);
p[1]=fad>>16;
p[2]=fad>>8;
p[3]=fad>>0;
p[0] = (track.CTRL << 4) | addr;
p[1] = fad >> 16;
p[2] = fad >> 8;
p[3] = fad >> 0;
return *(u32*)p;
return *(u32 *)p;
}
static u32 CreateTrackInfo_se(u32 ctrl, u32 addr, u32 tracknum)
static u32 createTrackInfoFirstLast(const Track& track, u32 tracknum)
{
u8 p[4];
p[0]=(ctrl<<4)|(addr<<0);
p[1]=tracknum;
p[2]=0;
p[3]=0;
return *(u32*)p;
return createTrackInfo(track, tracknum << 16);
}
void libGDR_ReadSector(u8 *buff, u32 startSector, u32 sectorCount, u32 sectorSize)
@ -209,63 +201,44 @@ void libGDR_ReadSector(u8 *buff, u32 startSector, u32 sectorCount, u32 sectorSiz
void libGDR_GetToc(u32* to, DiskArea area)
{
memset(to, 0xFF, 102 * 4);
if (!disc)
return;
memset(to, 0xFF, 102 * 4);
//can't get toc on the second area on discs that don't have it
verify(area != DoubleDensity || disc->type == GdRom);
if (area == DoubleDensity && disc->type != GdRom)
return;
//normal CDs: 1 .. tc
//GDROM: area0 is 1 .. 2, area1 is 3 ... tc
u32 first_track=1;
u32 last_track=disc->tracks.size();
if (area==DoubleDensity)
first_track=3;
else if (disc->type==GdRom)
last_track=2;
u32 first_track = 1;
u32 last_track = disc->tracks.size();
if (area == DoubleDensity)
first_track = 3;
else if (disc->type == GdRom)
last_track = 2;
//Generate the TOC info
//-1 for 1..99 0 ..98
to[99]=CreateTrackInfo_se(disc->tracks[first_track-1].CTRL,disc->tracks[first_track-1].ADDR,first_track);
to[100]=CreateTrackInfo_se(disc->tracks[last_track-1].CTRL,disc->tracks[last_track-1].ADDR,last_track);
to[99] = createTrackInfoFirstLast(disc->tracks[first_track - 1], first_track);
to[100] = createTrackInfoFirstLast(disc->tracks[last_track - 1], last_track);
if (disc->type==GdRom)
{
//use smaller LEADOUT
if (area==SingleDensity)
to[101]=CreateTrackInfo(disc->LeadOut.CTRL,disc->LeadOut.ADDR,13085);
}
if (disc->type == GdRom && area == SingleDensity)
// use smaller LEADOUT
to[101] = createTrackInfo(disc->LeadOut, 13085);
else
to[101] = CreateTrackInfo(disc->LeadOut.CTRL, disc->LeadOut.ADDR, disc->LeadOut.StartFAD);
to[101] = createTrackInfo(disc->LeadOut, disc->LeadOut.StartFAD);
for (u32 i=first_track-1;i<last_track;i++)
to[i]=CreateTrackInfo(disc->tracks[i].CTRL,disc->tracks[i].ADDR,disc->tracks[i].StartFAD);
for (u32 i = first_track - 1; i < last_track; i++)
to[i] = createTrackInfo(disc->tracks[i], disc->tracks[i].StartFAD);
}
void libGDR_GetSessionInfo(u8* to, u8 session)
{
if (!disc)
return;
to[0]=2;//status, will get overwritten anyway
to[1]=0;//0's
if (session==0)
{
to[2]=disc->sessions.size();//count of sessions
to[3]=disc->EndFAD>>16;//fad is sessions end
to[4]=disc->EndFAD>>8;
to[5]=disc->EndFAD>>0;
}
else
{
to[2]=disc->sessions[session-1].FirstTrack;//start track of this session
to[3]=disc->sessions[session-1].StartFAD>>16;//fad is session start
to[4]=disc->sessions[session-1].StartFAD>>8;
to[5]=disc->sessions[session-1].StartFAD>>0;
}
if (disc != nullptr)
disc->GetSessionInfo(to, session);
}
DiscType GuessDiscType(bool m1, bool m2, bool da)

View File

@ -96,6 +96,10 @@ struct Track
delete file;
file = nullptr;
}
bool isDataTrack() const {
return CTRL & 4;
}
};
struct Disc
@ -153,7 +157,7 @@ struct Disc
{
for (u32 i=0;i<tracks.size();i++)
{
u32 fmt=tracks[i].CTRL==4?2048:2352;
u32 fmt = tracks[i].isDataTrack() ? 2048 : 2352;
char fsto[1024];
sprintf(fsto,"%s%s%d.img",path.c_str(),".track",i);
@ -168,6 +172,38 @@ struct Disc
std::fclose(fo);
}
}
void GetSessionInfo(u8* to, u8 session) const
{
to[0] = 2; //status, will get overwritten anyway
to[1] = 0;
if (session == 0)
{
to[2] = sessions.size(); //count of sessions
to[3] = EndFAD >> 16; //fad is sessions end
to[4] = EndFAD >> 8;
to[5] = EndFAD >> 0;
}
else
{
to[2] = sessions[session - 1].FirstTrack; //start track of this session
to[3] = sessions[session - 1].StartFAD >> 16; //fad is session start
to[4] = sessions[session - 1].StartFAD >> 8;
to[5] = sessions[session - 1].StartFAD >> 0;
}
}
u32 GetBaseFAD() const
{
if (type == GdRom)
return 45150;
u8 ses[6];
GetSessionInfo(ses, 0);
GetSessionInfo(ses, ses[2]);
return (ses[3] << 16) | (ses[4] << 8) | (ses[5] << 0);
}
};
Disc* OpenDisc(const std::string& path, std::vector<u8> *digest = nullptr);

View File

@ -222,7 +222,7 @@ Disc* cue_parse(const char* file, std::vector<u8> *digest)
// Get rid of the pregap for audio tracks
for (Track& t : disc->tracks)
if (t.CTRL == 0)
if (!t.isDataTrack())
t.StartFAD += 150;
if (digest != nullptr)
*digest = md5.getDigest();

112
core/imgread/isofs.cpp Normal file
View File

@ -0,0 +1,112 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "isofs.h"
#include "iso9660.h"
static u32 decode_iso733(iso733_t v)
{
return ((v >> 56) & 0x000000FF)
| ((v >> 40) & 0x0000FF00)
| ((v >> 24) & 0x00FF0000)
| ((v >> 8) & 0xFF000000);
}
IsoFs::IsoFs(Disc *disc) : disc(disc)
{
baseFad = disc->GetBaseFAD();
}
IsoFs::Directory *IsoFs::getRoot()
{
u8 temp[2048];
disc->ReadSectors(baseFad + 16, 1, temp, 2048);
// Primary Volume Descriptor
const iso9660_pvd_t *pvd = (const iso9660_pvd_t *)temp;
Directory *root = new Directory(this);
if (pvd->type == 1 && !memcmp(pvd->id, ISO_STANDARD_ID, strlen(ISO_STANDARD_ID)) && pvd->version == 1)
{
u32 lba = decode_iso733(pvd->root_directory_record.extent);
u32 len = decode_iso733(pvd->root_directory_record.size);
len = ((len + 2047) / 2048) * 2048;
root->data.resize(len);
DEBUG_LOG(GDROM, "iso9660 root directory FAD: %d, len: %d", 150 + lba, len);
disc->ReadSectors(150 + lba, len / 2048, root->data.data(), 2048);
}
else {
WARN_LOG(GDROM, "iso9660 PVD NOT found");
root->data.resize(1);
root->data[0] = 0;
}
return root;
}
IsoFs::Entry *IsoFs::Directory::getEntry(const std::string& name)
{
std::string isoname = name + ';';
for (u32 i = 0; i < data.size(); )
{
const iso9660_dir_t *dir = (const iso9660_dir_t *)&data[i];
if (dir->length == 0)
break;
if ((u8)dir->filename.str[0] > isoname.size()
&& memcmp(dir->filename.str + 1, isoname.c_str(), isoname.size()) == 0)
{
DEBUG_LOG(GDROM, "Found %s at offset %X", name.c_str(), i);
u32 startFad = decode_iso733(dir->extent) + 150;
u32 len = decode_iso733(dir->size);
if ((dir->file_flags & ISO_DIRECTORY) == 0)
{
File *file = new File(fs);
file->startFad = startFad;
file->len = len;
return file;
}
else
{
Directory *directory = new Directory(fs);
directory->data.resize(len);
fs->disc->ReadSectors(startFad, len / 2048, directory->data.data(), 2048);
return directory;
}
}
i += dir->length;
}
return nullptr;
}
u32 IsoFs::File::read(u8 *buf, u32 size, u32 offset) const
{
size = std::min(size, len - offset);
u32 sectors = size / 2048;
fs->disc->ReadSectors(startFad + offset / 2048, sectors, buf, 2048);
size -= sectors * 2048;
if (size > 0)
{
u8 temp[2048];
fs->disc->ReadSectors(startFad + offset / 2048 + sectors, 1, temp, 2048);
memcpy(buf + sectors * 2048, temp, size);
}
return sectors * 2048 + size;
}

76
core/imgread/isofs.h Normal file
View File

@ -0,0 +1,76 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "common.h"
class IsoFs
{
public:
class Entry
{
public:
virtual bool isDirectory() const = 0;
virtual ~Entry() = default;
protected:
Entry(IsoFs *fs) : fs(fs) {}
IsoFs *fs;
};
class Directory final : public Entry
{
public:
bool isDirectory() const override { return true; }
Entry *getEntry(const std::string& name);
private:
Directory(IsoFs *fs) : Entry(fs) {}
std::vector<u8> data;
friend class IsoFs;
};
class File final : public Entry
{
public:
bool isDirectory() const override { return false; }
u32 getSize() const { return len; }
u32 read(u8 *buf, u32 size, u32 offset = 0) const;
private:
File(IsoFs *fs) : Entry(fs) {}
u32 startFad = 0;
u32 len = 0;
friend class IsoFs;
};
IsoFs(Disc *disc);
Directory *getRoot();
private:
Disc *disc;
u32 baseFad;
};

21
core/network/output.cpp Normal file
View File

@ -0,0 +1,21 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "output.h"
NetworkOutput networkOutput;

115
core/network/output.h Normal file
View File

@ -0,0 +1,115 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "types.h"
#include "net_platform.h"
#include "emulator.h"
#include "cfg/option.h"
class NetworkOutput
{
public:
void init()
{
if (!config::NetworkOutput)
return;
server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int option = 1;
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, (const char *)&option, sizeof(option));
sockaddr_in saddr{};
socklen_t saddr_len = sizeof(saddr);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons(8000);
if (::bind(server, (sockaddr *)&saddr, saddr_len) < 0)
{
perror("bind");
term();
return;
}
if (listen(server, 5) < 0)
{
perror("listen");
term();
return;
}
set_non_blocking(server);
EventManager::listen(Event::VBlank, vblankCallback, this);
}
void term()
{
EventManager::unlisten(Event::VBlank, vblankCallback, this);
for (sock_t sock : clients)
closesocket(sock);
clients.clear();
if (server != INVALID_SOCKET)
{
closesocket(server);
server = INVALID_SOCKET;
}
}
void output(const char *name, u32 value)
{
if (!config::NetworkOutput)
return;
char s[9];
sprintf(s, "%x", value);
std::string msg = std::string(name) + " = " + std::string(s) + "\n"; // mame uses \r
std::vector<sock_t> errorSockets;
for (sock_t sock : clients)
if (::send(sock, msg.c_str(), msg.length(), 0) < 0)
{
int error = get_last_error();
if (error != L_EWOULDBLOCK && error != L_EAGAIN)
errorSockets.push_back(sock);
}
for (sock_t sock : errorSockets)
{
closesocket(sock);
clients.erase(std::find(clients.begin(), clients.end(), sock));
}
}
private:
static void vblankCallback(Event event, void *param) {
((NetworkOutput *)param)->acceptConnections();
}
void acceptConnections()
{
sockaddr_in src_addr{};
socklen_t addr_len = sizeof(src_addr);
sock_t sockfd = accept(server, (sockaddr *)&src_addr, &addr_len);
if (sockfd != INVALID_SOCKET)
{
set_non_blocking(sockfd);
clients.push_back(sockfd);
}
}
sock_t server = INVALID_SOCKET;
std::vector<sock_t> clients;
};
extern NetworkOutput networkOutput;

View File

@ -7,25 +7,24 @@
*/
#include "descrambl.h"
#include "imgread/common.h"
#include <algorithm>
#define MAXCHUNK (2048*1024)
static unsigned int seed;
static u32 seed;
static void my_srand(unsigned int n)
static void my_srand(u32 n)
{
seed = n & 0xffff;
}
static unsigned int my_rand()
static u32 my_rand()
{
seed = (seed * 2109 + 9273) & 0x7fff;
return (seed + 0xc000) & 0xffff;
}
static void load_chunk(u8* &src, unsigned char *ptr, unsigned long sz)
static void load_chunk(const u8* &src, u8 *ptr, u32 sz)
{
verify(sz <= MAXCHUNK);
@ -53,32 +52,23 @@ static void load_chunk(u8* &src, unsigned char *ptr, unsigned long sz)
}
}
static void descrambl_buffer(u8* src, unsigned char *dst, unsigned long filesz)
void descrambl_buffer(const u8 *src, u8 *dst, u32 size)
{
unsigned long chunksz;
u32 chunksz;
my_srand(filesz);
my_srand(size);
/* Descramble 2 meg blocks for as long as possible, then
gradually reduce the window down to 32 bytes (1 slice) */
for (chunksz = MAXCHUNK; chunksz >= 32; chunksz >>= 1)
while (filesz >= chunksz)
while (size >= chunksz)
{
load_chunk(src, dst, chunksz);
filesz -= chunksz;
size -= chunksz;
dst += chunksz;
}
/* Load final incomplete slice */
if (filesz)
memcpy(dst, src, filesz);
}
void descrambl_file(u32 FAD, u32 file_size, u8* dst) {
u8* temp_file = new u8[file_size + 2048];
libGDR_ReadSector(temp_file, FAD, (file_size+2047) / 2048, 2048);
descrambl_buffer(temp_file, dst, file_size);
delete[] temp_file;
if (size)
memcpy(dst, src, size);
}

View File

@ -1,4 +1,4 @@
#pragma once
#include "types.h"
void descrambl_file(u32 FAD, u32 file_size, u8* dst);
void descrambl_buffer(const u8 *src, u8 *dst, u32 size);

View File

@ -358,7 +358,9 @@ static void GD_HLE_Command(gd_command cc)
DEBUG_LOG(REIOS, "GDROM: CMD PLAY first_track %x last_track %x repeats %x start_fad %x end_fad %x", first_track, last_track, cdda.repeats,
cdda.StartAddr.FAD, cdda.EndAddr.FAD);
cdda.status = cdda_t::Playing;
if (SecNumber.Status != GD_PAUSE || cdda.CurrAddr.FAD < cdda.StartAddr.FAD || cdda.CurrAddr.FAD > cdda.EndAddr.FAD)
if ((SecNumber.Status != GD_PLAY && SecNumber.Status != GD_PAUSE)
|| cdda.CurrAddr.FAD < cdda.StartAddr.FAD
|| cdda.CurrAddr.FAD > cdda.EndAddr.FAD)
cdda.CurrAddr.FAD = cdda.StartAddr.FAD;
SecNumber.Status = GD_PLAY;
}

View File

@ -21,12 +21,12 @@
#include "hw/holly/sb_mem.h"
#include "hw/holly/sb.h"
#include "hw/naomi/naomi_cart.h"
#include "iso9660.h"
#include "font.h"
#include "hw/aica/aica.h"
#include "hw/aica/aica_mem.h"
#include "hw/pvr/pvr_regs.h"
#include "imgread/common.h"
#include "imgread/isofs.h"
#include "oslib/oslib.h"
#include <map>
@ -50,120 +50,82 @@ static MemChip *flashrom;
static u32 base_fad = 45150;
static bool descrambl = false;
static u32 bootSectors;
extern Disc *disc;
static void reios_pre_init()
{
if (libGDR_GetDiscType() == GdRom) {
base_fad = 45150;
descrambl = false;
} else {
u8 ses[6];
libGDR_GetSessionInfo(ses, 0);
libGDR_GetSessionInfo(ses, ses[2]);
base_fad = (ses[3] << 16) | (ses[4] << 8) | (ses[5] << 0);
descrambl = true;
if (disc != nullptr)
{
base_fad = disc->GetBaseFAD();
descrambl = disc->type != GdRom;
}
}
static u32 decode_iso733(iso733_t v)
{
return ((v >> 56) & 0x000000FF)
| ((v >> 40) & 0x0000FF00)
| ((v >> 24) & 0x00FF0000)
| ((v >> 8) & 0xFF000000);
}
static bool reios_locate_bootfile(const char* bootfile)
{
reios_pre_init();
if (ip_meta.wince == '1' && descrambl)
{
ERROR_LOG(REIOS, "Unsupported CDI: wince == '1'");
return false;
}
// Load IP.BIN bootstrap
libGDR_ReadSector(GetMemPtr(0x8c008000, 0), base_fad, 16, 2048);
u32 data_len = 2048 * 1024;
u8* temp = new u8[data_len];
libGDR_ReadSector(temp, base_fad + 16, 1, 2048);
iso9660_pvd_t *pvd = (iso9660_pvd_t *)temp;
if (pvd->type == 1 && !memcmp(pvd->id, ISO_STANDARD_ID, strlen(ISO_STANDARD_ID)) && pvd->version == 1)
IsoFs isofs(disc);
std::unique_ptr<IsoFs::Directory> root(isofs.getRoot());
if (root == nullptr)
{
INFO_LOG(REIOS, "iso9660 PVD found");
u32 lba = decode_iso733(pvd->root_directory_record.extent);
u32 len = decode_iso733(pvd->root_directory_record.size);
data_len = ((len + 2047) / 2048) * 2048;
INFO_LOG(REIOS, "iso9660 root_directory, FAD: %d, len: %d", 150 + lba, data_len);
libGDR_ReadSector(temp, 150 + lba, data_len / 2048, 2048);
ERROR_LOG(REIOS, "ISO file system root not found");
return false;
}
else {
libGDR_ReadSector(temp, base_fad + 16, data_len / 2048, 2048);
}
int bootfile_len = strlen(bootfile);
while (bootfile_len > 0 && isspace(bootfile[bootfile_len - 1]))
bootfile_len--;
for (u32 i = 0; i < data_len; )
std::unique_ptr<IsoFs::Entry> bootEntry(root->getEntry(trim_trailing_ws(bootfile)));
if (bootEntry == nullptr || bootEntry->isDirectory())
{
iso9660_dir_t *dir = (iso9660_dir_t *)&temp[i];
if (dir->length == 0)
break;
ERROR_LOG(REIOS, "Boot file '%s' not found", bootfile);
return false;
}
IsoFs::File *bootFile = (IsoFs::File *)bootEntry.get();
if ((dir->file_flags & ISO_DIRECTORY) == 0 && memcmp(dir->filename.str + 1, bootfile, bootfile_len) == 0)
{
INFO_LOG(REIOS, "Found %.*s at offset %X", bootfile_len, bootfile, i);
u32 offset = 0;
u32 size = bootFile->getSize();
if (ip_meta.wince == '1')
{
bootFile->read(GetMemPtr(0x8ce01000, 2048), 2048);
offset = 2048;
size -= offset;
}
bootSectors = size / 2048;
u32 lba = decode_iso733(dir->extent) + 150;
u32 len = decode_iso733(dir->size);
if (ip_meta.wince == '1')
{
if (descrambl)
{
WARN_LOG(REIOS, "Unsupported CDI: wince == '1'");
delete[] temp;
return false;
}
libGDR_ReadSector(GetMemPtr(0x8ce01000, 0), lba, 1, 2048);
lba++;
len -= 2048;
}
INFO_LOG(REIOS, "file LBA: %d", lba);
INFO_LOG(REIOS, "file LEN: %d", len);
bootSectors = (len + 2047) / 2048;
if (descrambl)
descrambl_file(lba, len, GetMemPtr(0x8c010000, 0));
else
libGDR_ReadSector(GetMemPtr(0x8c010000, 0), lba, bootSectors, 2048);
delete[] temp;
u8 data[24] = {0};
// system id
for (u32 j = 0; j < 8; j++)
data[j] = _vmem_ReadMem8(0x0021a056 + j);
// system properties
for (u32 j = 0; j < 5; j++)
data[8 + j] = _vmem_ReadMem8(0x0021a000 + j);
// system settings
flash_syscfg_block syscfg{};
verify(static_cast<DCFlashChip*>(flashrom)->ReadBlock(FLASH_PT_USER, FLASH_USER_SYSCFG, &syscfg));
memcpy(&data[16], &syscfg.time_lo, 8);
memcpy(GetMemPtr(0x8c000068, sizeof(data)), data, sizeof(data));
return true;
}
i += dir->length;
if (descrambl)
{
std::vector<u8> buf(size);
bootFile->read(buf.data(), size, offset);
descrambl_buffer(buf.data(), GetMemPtr(0x8c010000, size), size);
}
else
{
bootFile->read(GetMemPtr(0x8c010000, size), size, offset);
}
delete[] temp;
return false;
u8 data[24] = {0};
// system id
for (u32 j = 0; j < 8; j++)
data[j] = _vmem_ReadMem8(0x0021a056 + j);
// system properties
for (u32 j = 0; j < 5; j++)
data[8 + j] = _vmem_ReadMem8(0x0021a000 + j);
// system settings
flash_syscfg_block syscfg{};
verify(static_cast<DCFlashChip*>(flashrom)->ReadBlock(FLASH_PT_USER, FLASH_USER_SYSCFG, &syscfg));
memcpy(&data[16], &syscfg.time_lo, 8);
memcpy(GetMemPtr(0x8c000068, sizeof(data)), data, sizeof(data));
return true;
}
ip_meta_t ip_meta;

View File

@ -13,7 +13,7 @@
#include <omp.h>
#endif
u8* vq_codebook;
const u8 *vq_codebook;
u32 palette_index;
bool KillTex=false;
u32 palette16_ram[1024];
@ -22,7 +22,6 @@ u32 pal_hash_256[4];
u32 pal_hash_16[64];
bool palette_updated;
extern bool pal_needs_update;
float fb_scale_x, fb_scale_y;
// Rough approximation of LoD bias from D adjust param, only used to increase LoD
const std::array<f32, 16> D_Adjust_LoD_Bias = {
@ -339,7 +338,7 @@ namespace directx {
#undef TEX_CONV_TABLE
static const PvrTexInfo *pvrTexInfo = opengl::pvrTexInfo;
static const u32 VQMipPoint[11] =
extern const u32 VQMipPoint[11] =
{
0x00000,//1
0x00001,//2
@ -353,7 +352,7 @@ static const u32 VQMipPoint[11] =
0x05556,//512
0x15556//1024
};
static const u32 OtherMipPoint[11] =
extern const u32 OtherMipPoint[11] =
{
0x00003,//1
0x00004,//2
@ -786,7 +785,7 @@ void BaseTextureCacheData::Update()
//lock the texture to detect changes in it
protectVRam();
UploadToGPU(upscaled_w, upscaled_h, (u8*)temp_tex_buffer, IsMipmapped(), mipmapped);
UploadToGPU(upscaled_w, upscaled_h, (const u8 *)temp_tex_buffer, IsMipmapped(), mipmapped);
if (config::DumpTextures)
{
ComputeHash();

View File

@ -9,7 +9,7 @@
#include <memory>
#include <unordered_map>
extern u8* vq_codebook;
extern const u8 *vq_codebook;
extern u32 palette_index;
extern u32 palette16_ram[1024];
extern u32 palette32_ram[1024];
@ -227,9 +227,9 @@ struct ConvertPlanar
using unpacked_type = typename Unpacker::unpacked_type;
static constexpr u32 xpp = 4;
static constexpr u32 ypp = 1;
static void Convert(PixelBuffer<unpacked_type> *pb, u8 *data)
static void Convert(PixelBuffer<unpacked_type> *pb, const u8 *data)
{
u16 *p_in = (u16 *)data;
const u16 *p_in = (const u16 *)data;
pb->prel(0, Unpacker::unpack(p_in[0]));
pb->prel(1, Unpacker::unpack(p_in[1]));
pb->prel(2, Unpacker::unpack(p_in[2]));
@ -243,10 +243,10 @@ struct ConvertPlanarYUV
using unpacked_type = u32;
static constexpr u32 xpp = 4;
static constexpr u32 ypp = 1;
static void Convert(PixelBuffer<u32> *pb, u8 *data)
static void Convert(PixelBuffer<u32> *pb, const u8 *data)
{
//convert 4x1 4444 to 4x1 8888
u32 *p_in = (u32 *)data;
const u32 *p_in = (const u32 *)data;
s32 Y0 = (p_in[0] >> 8) & 255; //
@ -280,9 +280,9 @@ struct ConvertTwiddle
using unpacked_type = typename Unpacker::unpacked_type;
static constexpr u32 xpp = 2;
static constexpr u32 ypp = 2;
static void Convert(PixelBuffer<unpacked_type> *pb, u8 *data)
static void Convert(PixelBuffer<unpacked_type> *pb, const u8 *data)
{
u16 *p_in = (u16 *)data;
const u16 *p_in = (const u16 *)data;
pb->prel(0, 0, Unpacker::unpack(p_in[0]));
pb->prel(0, 1, Unpacker::unpack(p_in[1]));
pb->prel(1, 0, Unpacker::unpack(p_in[2]));
@ -296,10 +296,10 @@ struct ConvertTwiddleYUV
using unpacked_type = u32;
static constexpr u32 xpp = 2;
static constexpr u32 ypp = 2;
static void Convert(PixelBuffer<u32> *pb, u8 *data)
static void Convert(PixelBuffer<u32> *pb, const u8 *data)
{
//convert 4x1 4444 to 4x1 8888
u16* p_in = (u16 *)data;
const u16* p_in = (const u16 *)data;
s32 Y0 = (p_in[0] >> 8) & 255; //
s32 Yu = (p_in[0] >> 0) & 255; //p_in[0]
@ -342,9 +342,9 @@ struct ConvertTwiddlePal4
using unpacked_type = typename Unpacker::unpacked_type;
static constexpr u32 xpp = 4;
static constexpr u32 ypp = 4;
static void Convert(PixelBuffer<unpacked_type> *pb, u8 *data)
static void Convert(PixelBuffer<unpacked_type> *pb, const u8 *data)
{
u8 *p_in = (u8 *)data;
const u8 *p_in = data;
pb->prel(0, 0, Unpacker::unpack(p_in[0] & 0xF));
pb->prel(0, 1, Unpacker::unpack((p_in[0] >> 4) & 0xF)); p_in++;
@ -374,9 +374,9 @@ struct ConvertTwiddlePal8
using unpacked_type = typename Unpacker::unpacked_type;
static constexpr u32 xpp = 2;
static constexpr u32 ypp = 4;
static void Convert(PixelBuffer<unpacked_type> *pb, u8 *data)
static void Convert(PixelBuffer<unpacked_type> *pb, const u8 *data)
{
u8* p_in = (u8 *)data;
const u8* p_in = (const u8 *)data;
pb->prel(0, 0, Unpacker::unpack(p_in[0])); p_in++;
pb->prel(0, 1, Unpacker::unpack(p_in[0])); p_in++;
@ -392,7 +392,7 @@ struct ConvertTwiddlePal8
//handler functions
template<class PixelConvertor>
void texture_PL(PixelBuffer<typename PixelConvertor::unpacked_type>* pb,u8* p_in,u32 Width,u32 Height)
void texture_PL(PixelBuffer<typename PixelConvertor::unpacked_type>* pb, const u8* p_in, u32 Width, u32 Height)
{
pb->amove(0,0);
@ -403,7 +403,7 @@ void texture_PL(PixelBuffer<typename PixelConvertor::unpacked_type>* pb,u8* p_in
{
for (u32 x=0;x<Width;x++)
{
u8* p = p_in;
const u8* p = p_in;
PixelConvertor::Convert(pb,p);
p_in+=8;
@ -414,7 +414,7 @@ void texture_PL(PixelBuffer<typename PixelConvertor::unpacked_type>* pb,u8* p_in
}
template<class PixelConvertor>
void texture_TW(PixelBuffer<typename PixelConvertor::unpacked_type>* pb,u8* p_in,u32 Width,u32 Height)
void texture_TW(PixelBuffer<typename PixelConvertor::unpacked_type>* pb, const u8* p_in, u32 Width, u32 Height)
{
pb->amove(0, 0);
@ -427,7 +427,7 @@ void texture_TW(PixelBuffer<typename PixelConvertor::unpacked_type>* pb,u8* p_in
{
for (u32 x = 0; x < Width; x += PixelConvertor::xpp)
{
u8* p = &p_in[(twop(x, y, bcx, bcy) / divider) << 3];
const u8* p = &p_in[(twop(x, y, bcx, bcy) / divider) << 3];
PixelConvertor::Convert(pb, p);
pb->rmovex(PixelConvertor::xpp);
@ -437,7 +437,7 @@ void texture_TW(PixelBuffer<typename PixelConvertor::unpacked_type>* pb,u8* p_in
}
template<class PixelConvertor>
void texture_VQ(PixelBuffer<typename PixelConvertor::unpacked_type>* pb,u8* p_in,u32 Width,u32 Height)
void texture_VQ(PixelBuffer<typename PixelConvertor::unpacked_type>* pb, const u8* p_in, u32 Width, u32 Height)
{
p_in += 256 * 4 * 2; // Skip VQ codebook
pb->amove(0, 0);
@ -459,9 +459,9 @@ void texture_VQ(PixelBuffer<typename PixelConvertor::unpacked_type>* pb,u8* p_in
}
}
typedef void (*TexConvFP)(PixelBuffer<u16> *pb, u8 *p_in, u32 width, u32 height);
typedef void (*TexConvFP8)(PixelBuffer<u8> *pb, u8 *p_in, u32 width, u32 height);
typedef void (*TexConvFP32)(PixelBuffer<u32> *pb, u8 *p_in, u32 width, u32 height);
typedef void (*TexConvFP)(PixelBuffer<u16> *pb, const u8 *p_in, u32 width, u32 height);
typedef void (*TexConvFP8)(PixelBuffer<u8> *pb, const u8 *p_in, u32 width, u32 height);
typedef void (*TexConvFP32)(PixelBuffer<u32> *pb, const u8 *p_in, u32 width, u32 height);
//Planar
constexpr TexConvFP tex565_PL = texture_PL<ConvertPlanar<UnpackerNop<u16>>>;
@ -669,7 +669,7 @@ public:
void ComputeHash();
void Update();
virtual void UploadToGPU(int width, int height, u8 *temp_tex_buffer, bool mipmapped, bool mipmapsIncluded = false) = 0;
virtual void UploadToGPU(int width, int height, const u8 *temp_tex_buffer, bool mipmapped, bool mipmapsIncluded = false) = 0;
virtual bool Force32BitTexture(TextureType type) const { return false; }
void CheckCustomTexture();
//true if : dirty or paletted texture and hashes don't match
@ -815,10 +815,3 @@ void dump_screenshot(u8 *buffer, u32 width, u32 height, bool alpha = false, u32
extern const std::array<f32, 16> D_Adjust_LoD_Bias;
#undef clamp
extern float fb_scale_x, fb_scale_y;
static inline void rend_set_fb_scale(float x, float y)
{
fb_scale_x = x;
fb_scale_y = y;
}

213
core/rend/boxart/boxart.cpp Normal file
View File

@ -0,0 +1,213 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "boxart.h"
#include "gamesdb.h"
#include "../game_scanner.h"
#include <chrono>
static std::string getGameFileName(const std::string& path)
{
size_t slash = get_last_slash_pos(path);
std::string fileName;
if (slash != std::string::npos)
return path.substr(slash + 1);
else
return path;
}
const GameBoxart *Boxart::getBoxart(const GameMedia& media)
{
loadDatabase();
std::string fileName = getGameFileName(media.path);
GameBoxart *boxart = nullptr;
{
std::lock_guard<std::mutex> guard(mutex);
auto it = games.find(fileName);
if (it != games.end())
{
boxart = &it->second;
if (config::FetchBoxart && !boxart->busy && !boxart->scraped)
{
boxart->busy = true;
boxart->gamePath = media.path;
toFetch.push_back(*boxart);
}
}
else
{
GameBoxart box;
box.fileName = fileName;
box.gamePath = media.path;
box.name = media.name;
box.searchName = media.gameName; // for arcade games
box.busy = true;
games[box.fileName] = box;
toFetch.push_back(box);
}
}
fetchBoxart();
return boxart;
}
void Boxart::fetchBoxart()
{
if (fetching.valid() && fetching.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
fetching.get();
if (fetching.valid())
return;
if (toFetch.empty())
return;
fetching = std::async(std::launch::async, [this]() {
if (offlineScraper == nullptr)
{
offlineScraper = std::unique_ptr<Scraper>(new OfflineScraper());
offlineScraper->initialize(getSaveDirectory());
}
if (config::FetchBoxart && scraper == nullptr)
{
scraper = std::unique_ptr<Scraper>(new TheGamesDb());
if (!scraper->initialize(getSaveDirectory()))
{
ERROR_LOG(COMMON, "thegamesdb scraper initialization failed");
scraper.reset();
return;
}
}
std::vector<GameBoxart> boxart;
{
std::lock_guard<std::mutex> guard(mutex);
size_t size = std::min(toFetch.size(), (size_t)10);
boxart = std::vector<GameBoxart>(toFetch.begin(), toFetch.begin() + size);
toFetch.erase(toFetch.begin(), toFetch.begin() + size);
}
DEBUG_LOG(COMMON, "Scraping %d games", (int)boxart.size());
offlineScraper->scrape(boxart);
{
std::lock_guard<std::mutex> guard(mutex);
for (GameBoxart& b : boxart)
if (b.scraped || b.parsed)
{
if (!config::FetchBoxart || b.scraped)
b.busy = false;
games[b.fileName] = b;
databaseDirty = true;
}
}
if (config::FetchBoxart)
{
try {
scraper->scrape(boxart);
{
std::lock_guard<std::mutex> guard(mutex);
for (GameBoxart& b : boxart)
{
b.busy = false;
games[b.fileName] = b;
}
}
databaseDirty = true;
} catch (const std::runtime_error& e) {
if (*e.what() != '\0')
INFO_LOG(COMMON, "thegamesdb error: %s", e.what());
{
// put back failed items into toFetch array
std::lock_guard<std::mutex> guard(mutex);
for (GameBoxart& b : boxart)
if (b.scraped)
{
b.busy = false;
games[b.fileName] = b;
databaseDirty = true;
}
else
{
toFetch.push_back(b);
}
}
}
}
});
}
void Boxart::saveDatabase()
{
if (fetching.valid())
fetching.get();
if (!databaseDirty)
return;
std::string db_name = getSaveDirectory() + DB_NAME;
FILE *file = nowide::fopen(db_name.c_str(), "wt");
if (file == nullptr)
{
WARN_LOG(COMMON, "Can't save boxart database to %s: error %d", db_name.c_str(), errno);
return;
}
DEBUG_LOG(COMMON, "Saving boxart database to %s", db_name.c_str());
json array;
{
std::lock_guard<std::mutex> guard(mutex);
for (const auto& game : games)
if (game.second.scraped || game.second.parsed)
array.push_back(game.second.to_json());
}
std::string serialized = array.dump(4);
fwrite(serialized.c_str(), 1, serialized.size(), file);
fclose(file);
databaseDirty = false;
}
void Boxart::loadDatabase()
{
if (databaseLoaded)
return;
databaseLoaded = true;
databaseDirty = false;
std::string save_dir = getSaveDirectory();
if (!file_exists(save_dir))
make_directory(save_dir);
std::string db_name = save_dir + DB_NAME;
FILE *f = nowide::fopen(db_name.c_str(), "rt");
if (f == nullptr)
return;
DEBUG_LOG(COMMON, "Loading boxart database from %s", db_name.c_str());
std::string all_data;
char buf[4096];
while (true)
{
int s = fread(buf, 1, sizeof(buf), f);
if (s <= 0)
break;
all_data.append(buf, s);
}
fclose(f);
try {
std::lock_guard<std::mutex> guard(mutex);
json v = json::parse(all_data);
for (const auto& o : v)
{
GameBoxart game(o);
games[game.fileName] = game;
}
} catch (const json::exception& e) {
WARN_LOG(COMMON, "Corrupted database file: %s", e.what());
}
}

53
core/rend/boxart/boxart.h Normal file
View File

@ -0,0 +1,53 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "scraper.h"
#include "stdclass.h"
#include <unordered_map>
#include <memory>
#include <future>
#include <mutex>
struct GameMedia;
class Boxart
{
public:
const GameBoxart *getBoxart(const GameMedia& media);
void saveDatabase();
private:
void loadDatabase();
std::string getSaveDirectory() const {
return get_writable_data_path("/boxart/");
}
void fetchBoxart();
std::unordered_map<std::string, GameBoxart> games;
std::mutex mutex;
std::unique_ptr<Scraper> scraper;
std::unique_ptr<Scraper> offlineScraper;
bool databaseLoaded = false;
bool databaseDirty = false;
std::vector<GameBoxart> toFetch;
std::future<void> fetching;
static constexpr char const *DB_NAME = "flycast-gamedb.json";
};

View File

@ -0,0 +1,380 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "gamesdb.h"
#include "http_client.h"
#include "stdclass.h"
#include "oslib/oslib.h"
#include "emulator.h"
#include <cctype>
#define APIKEY "3fcc5e726a129924972be97abfd577ac5311f8f12398a9d9bcb5a377d4656fa8"
std::string TheGamesDb::makeUrl(const std::string& endpoint)
{
return "https://api.thegamesdb.net/v1/" + endpoint + "?apikey=" APIKEY;
}
bool TheGamesDb::initialize(const std::string& saveDirectory)
{
if (!Scraper::initialize(saveDirectory))
return false;
http::init();
boxartCache.clear();
return true;
}
TheGamesDb::~TheGamesDb()
{
http::term();
}
void TheGamesDb::copyFile(const std::string& from, const std::string& to)
{
FILE *ffrom = nowide::fopen(from.c_str(), "rb");
if (ffrom == nullptr)
{
WARN_LOG(COMMON, "Can't open %s: error %d", from.c_str(), errno);
return;
}
FILE *fto = nowide::fopen(to.c_str(), "wb");
if (fto == nullptr)
{
WARN_LOG(COMMON, "Can't open %s: error %d", to.c_str(), errno);
fclose(ffrom);
return;
}
u8 buffer[4096];
while (true)
{
int l = fread(buffer, 1, sizeof(buffer), ffrom);
if (l == 0)
break;
fwrite(buffer, 1, l, fto);
}
fclose(ffrom);
fclose(fto);
}
json TheGamesDb::httpGet(const std::string& url)
{
if (os_GetSeconds() < blackoutPeriod)
throw std::runtime_error("");
blackoutPeriod = 0.0;
DEBUG_LOG(COMMON, "TheGameDb: GET %s", url.c_str());
std::vector<u8> receivedData;
int status = http::get(url, receivedData);
bool success = http::success(status);
if (status == 403)
// hit rate-limit cap
blackoutPeriod = os_GetSeconds() + 60.0;
else if (!success)
blackoutPeriod = os_GetSeconds() + 1.0;
if (!success)
throw std::runtime_error("http error");
std::string content((const char *)&receivedData[0], receivedData.size());
DEBUG_LOG(COMMON, "TheGameDb: received [%s]", content.c_str());
json v = json::parse(content);
int code = v["code"];
if (!http::success(code))
{
// TODO can this happen? http status should be the same
std::string status;
try {
status = v["status"];
} catch (const json::exception& e) {
}
throw std::runtime_error(std::string("TheGamesDB error ") + std::to_string(code) + ": " + status);
}
return v;
}
void TheGamesDb::fetchPlatforms()
{
if (dreamcastPlatformId != 0 && arcadePlatformId != 0)
return;
auto getPlatformId = [this](const std::string& platform)
{
std::string url = makeUrl("Platforms/ByPlatformName") + "&name=" + platform;
json v = httpGet(url);
const json& array = v["data"]["platforms"];
for (const auto& o : array)
{
std::string name = o["name"];
if (name == "Sega Dreamcast")
dreamcastPlatformId = o["id"];
else if (name == "Arcade")
arcadePlatformId = o["id"];
}
};
getPlatformId("Dreamcast");
getPlatformId("Arcade");
if (dreamcastPlatformId == 0 || arcadePlatformId == 0)
throw std::runtime_error("can't find dreamcast or arcade platform id");
}
void TheGamesDb::parseBoxart(GameBoxart& item, const json& j, int gameId)
{
std::string baseUrl;
try {
baseUrl = j["base_url"]["thumb"];
} catch (const json::exception& e) {
try {
baseUrl = j["base_url"]["small"];
} catch (const json::exception& e) {
return;
}
}
const json& images = j.contains("data") ? j["data"] : j["images"];
if (images.is_array())
// No boxart
return;
json dataArray = images[std::to_string(gameId)];
std::string imagePath;
for (const auto& o : dataArray)
{
try {
// Look for boxart
if (o["type"] != "boxart")
continue;
} catch (const json::exception& e) {
continue;
}
try {
// We want the front side if specified
if (o["side"] == "back")
continue;
} catch (const json::exception& e) {
// ignore if not found
}
imagePath = o["filename"].get<std::string>();
break;
}
if (imagePath.empty())
{
// Use titlescreen
for (const auto& o : dataArray)
{
try {
if (o["type"] != "titlescreen")
continue;
} catch (const json::exception& e) {
continue;
}
imagePath = o["filename"].get<std::string>();
break;
}
}
if (imagePath.empty())
{
// Use screenshot
for (const auto& o : dataArray)
{
try {
if (o["type"] != "screenshot")
continue;
} catch (const json::exception& e) {
continue;
}
imagePath = o["filename"].get<std::string>();
break;
}
}
if (!imagePath.empty())
{
// Build the full URL and get from cache or download
std::string url = baseUrl + imagePath;
std::string filename = makeUniqueFilename("dummy.jpg"); // thegamesdb returns some images as png, but they are really jpeg
auto cached = boxartCache.find(url);
if (cached != boxartCache.end())
{
copyFile(cached->second, filename);
item.setBoxartPath(filename);
}
else
{
if (downloadImage(url, filename))
{
item.setBoxartPath(filename);
boxartCache[url] = filename;
}
}
}
}
bool TheGamesDb::parseGameInfo(const json& gameArray, const json& boxartArray, GameBoxart& item, const std::string& diskId)
{
for (const auto& game : gameArray)
{
if (!diskId.empty())
{
bool found = false;
try {
for (const auto& uid : game["uids"])
if (uid["uid"] == diskId) {
found = true;
break;
}
} catch (const json::exception& e) {
}
if (!found)
continue;
}
// Name
item.name = game["game_title"];
// Release date
try {
item.releaseDate = game["release_date"];
} catch (const json::exception& e) {
}
// Overview
try {
item.overview = game["overview"];
} catch (const json::exception& e) {
}
// GameDB id
int id;
try {
id = game["id"];
} catch (const json::exception& e) {
return true;
}
// Boxart
parseBoxart(item, boxartArray, id);
if (item.boxartPath.empty())
{
std::string imgUrl = makeUrl("Games/Images") + "&games_id=" + std::to_string(id);
json images = httpGet(imgUrl);
parseBoxart(item, images["data"], id);
}
return true;
}
return false;
}
bool TheGamesDb::fetchGameInfo(GameBoxart& item, const std::string& url, const std::string& diskId)
{
json v = httpGet(url);
json& array = v["data"]["games"];
if (array.empty())
return false;
return parseGameInfo(array, v["include"]["boxart"], item, diskId);
}
void TheGamesDb::scrape(GameBoxart& item)
{
if (item.searchName.empty())
// invalid rom or disk
return;
fetchPlatforms();
if (!item.uniqueId.empty())
{
std::string url = makeUrl("Games/ByGameUniqueID") + "&fields=overview,uids&include=boxart&filter%5Bplatform%5D="
+ std::to_string(dreamcastPlatformId) + "&uid=" + http::urlEncode(item.uniqueId);
if (fetchGameInfo(item, url, item.uniqueId))
item.scraped = true;
}
if (!item.scraped)
fetchByName(item);
item.scraped = true;
}
void TheGamesDb::fetchByName(GameBoxart& item)
{
if (item.searchName.empty())
return;
int platform = getGamePlatform(item.gamePath.c_str());
std::string url = makeUrl("Games/ByGameName") + "&fields=overview&include=boxart&filter%5Bplatform%5D=";
if (platform == DC_PLATFORM_DREAMCAST)
url += std::to_string(dreamcastPlatformId);
else
url += std::to_string(arcadePlatformId);
url += "&name=" + http::urlEncode(item.searchName);
if (fetchGameInfo(item, url))
item.scraped = true;
}
void TheGamesDb::fetchByUids(std::vector<GameBoxart>& items)
{
std::string uidCriteria;
for (const GameBoxart& item : items)
{
if (item.scraped || item.uniqueId.empty())
continue;
if (!uidCriteria.empty())
uidCriteria += ',';
uidCriteria += item.uniqueId;
}
if (uidCriteria.empty())
return;
std::string url = makeUrl("Games/ByGameUniqueID") + "&fields=overview,uids&include=boxart&filter%5Bplatform%5D="
+ std::to_string(dreamcastPlatformId) + "&uid=" + http::urlEncode(uidCriteria);
json v = httpGet(url);
json& array = v["data"]["games"];
if (array.empty())
return;
json& boxartArray = v["include"]["boxart"];
for (GameBoxart& item : items)
{
if (!item.scraped && !item.uniqueId.empty() && parseGameInfo(array, boxartArray, item, item.uniqueId))
item.scraped = true;
}
}
void TheGamesDb::scrape(std::vector<GameBoxart>& items)
{
if (os_GetSeconds() < blackoutPeriod)
throw std::runtime_error("");
blackoutPeriod = 0.0;
fetchPlatforms();
fetchByUids(items);
for (GameBoxart& item : items)
{
if (!item.scraped)
{
if (!item.searchName.empty())
fetchByName(item);
else if (item.gamePath.empty())
{
std::string localPath = makeUniqueFilename("dreamcast_logo_grey.png");
if (downloadImage("https://flyinghead.github.io/flycast-builds/dreamcast_logo_grey.png", localPath))
item.setBoxartPath(localPath);
}
item.scraped = true;
}
}
}

View File

@ -0,0 +1,49 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include <map>
#include "scraper.h"
#include "json.hpp"
using namespace nlohmann;
class TheGamesDb : public Scraper
{
public:
bool initialize(const std::string& saveDirectory) override;
void scrape(GameBoxart& item) override;
void scrape(std::vector<GameBoxart>& items) override;
~TheGamesDb();
private:
void fetchPlatforms();
bool fetchGameInfo(GameBoxart& item, const std::string& url, const std::string& diskId = "");
std::string makeUrl(const std::string& endpoint);
void copyFile(const std::string& from, const std::string& to);
json httpGet(const std::string& url);
void parseBoxart(GameBoxart& item, const json& j, int gameId);
void fetchByUids(std::vector<GameBoxart>& items);
void fetchByName(GameBoxart& item);
bool parseGameInfo(const json& gameArray, const json& boxartArray, GameBoxart& item, const std::string& diskId);
int dreamcastPlatformId = 0;
int arcadePlatformId = 0;
double blackoutPeriod = 0.0;
std::map<std::string, std::string> boxartCache; // key: url, value: local file path
};

View File

@ -0,0 +1,137 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "build.h"
#if !defined(__ANDROID__) && !defined(__APPLE__)
#include "http_client.h"
#ifdef _WIN32
#ifndef TARGET_UWP
#include <windows.h>
#include <wininet.h>
namespace http {
static HINTERNET hInet;
void init()
{
if (hInet == NULL)
hInet = InternetOpen("Flycast/1.0", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
}
int get(const std::string& url, std::vector<u8>& content, std::string& contentType)
{
HINTERNET hUrl = InternetOpenUrl(hInet, url.c_str(), NULL, 0, INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_NO_UI, 0);
if (hUrl == NULL)
{
WARN_LOG(NETWORK, "Open URL failed: %lx", GetLastError());
return 500;
}
u8 buffer[4096];
DWORD bytesRead = sizeof(buffer);
if (HttpQueryInfo(hUrl, HTTP_QUERY_CONTENT_TYPE, buffer, &bytesRead, 0))
contentType = (const char *)buffer;
content.clear();
while (true)
{
if (!InternetReadFile(hUrl, buffer, sizeof(buffer), &bytesRead))
{
WARN_LOG(NETWORK, "InternetReadFile failed: %lx", GetLastError());
InternetCloseHandle(hUrl);
return 500;
}
if (bytesRead == 0)
break;
content.insert(content.end(), buffer, buffer + bytesRead);
}
InternetCloseHandle(hUrl);
return 200;
}
void term()
{
if (hInet != NULL)
{
InternetCloseHandle(hInet);
hInet = NULL;
}
}
}
#endif // !TARGET_UWP
#else
#include <curl/curl.h>
namespace http {
void init()
{
curl_global_init(CURL_GLOBAL_ALL);
}
static size_t receiveData(void *buffer, size_t size, size_t nmemb, std::vector<u8> *recvBuffer)
{
recvBuffer->insert(recvBuffer->end(), (u8 *)buffer, (u8 *)buffer + size * nmemb);
return nmemb * size;
}
int get(const std::string& url, std::vector<u8>& content, std::string& contentType)
{
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Flycast/1.0");
curl_easy_setopt(curl, CURLOPT_AUTOREFERER, 1);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
std::vector<u8> recvBuffer;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &recvBuffer);
CURLcode res = curl_easy_perform(curl);
long httpCode = 500;
if (res == CURLE_OK)
{
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
char *ct = nullptr;
curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);
if (ct != nullptr)
contentType = ct;
else
contentType.clear();
content = recvBuffer;
}
curl_easy_cleanup(curl);
return (int)httpCode;
}
void term()
{
curl_global_cleanup();
}
}
#endif // !_WIN32
#endif // !defined(__ANDROID__) && !defined(__APPLE__)

View File

@ -0,0 +1,66 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include <string>
#include <vector>
#include <cctype>
#include <iomanip>
#include <sstream>
#include "types.h"
namespace http {
void init();
void term();
int get(const std::string& url, std::vector<u8>& content, std::string& content_type);
static inline int get(const std::string& url, std::vector<u8>& content) {
std::string contentType;
return get(url, content, contentType);
}
static inline bool success(int status) {
return status >= 200 && status < 300;
}
static inline std::string urlEncode(const std::string& value)
{
std::ostringstream escaped;
escaped.fill('0');
escaped << std::hex;
for (char c : value)
{
if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
// Keep alphanumeric and other accepted characters intact
escaped << c;
}
else
{
// Any other characters are percent-encoded
escaped << std::uppercase;
escaped << '%' << std::setw(2) << int((u8)c);
escaped << std::nouppercase;
}
}
return escaped.str();
}
}

View File

@ -0,0 +1,143 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "types.h"
#include "rend/TexCache.h"
extern const u32 VQMipPoint[11];
extern const u32 OtherMipPoint[11];
enum PvrDataFormat {
PvrSquareTwiddled = 0x01,
PvrSquareTwiddledMipmaps = 0x02,
PvrVQ = 0x03,
PvrVQMipmaps = 0x04,
PvrPal4 = 0x05,
PvrPal4Mipmaps = 0x06,
PvrPal8 = 0x07,
PvrPal8Mipmaps = 0x08,
PvrRectangle = 0x09,
PvrStride = 0x0B,
PvrRectangleTwiddled = 0x0D,
PvrSmallVQ = 0x10,
PvrSmallVQMipmaps = 0x11,
PvrSquareTwiddledMipmapsAlt = 0x12,
};
static bool pvrParse(const u8 *data, u32 len, u32& width, u32& height, std::vector<u8>& out)
{
if (len < 16)
return false;
const u8 *p = data;
if (!memcmp("GBIX", p, 4))
{
p += 4;
u32 idxSize = *(u32 *)p;
p += 4 + idxSize;
}
if (memcmp("PVRT", p, 4))
{
WARN_LOG(COMMON, "Invalid PVR file: header not found");
return false;
}
p += 4;
u32 size = *(u32 *)p;
p += 4;
PixelFormat pixelFormat = (PixelFormat)*p++;
PvrDataFormat imgType = (PvrDataFormat)*p++;
p += 2;
width = *(u16 *)p;
p += 2;
height = *(u16 *)p;
p += 2;
::vq_codebook = p;
TexConvFP32 texConv;
switch (pixelFormat)
{
case Pixel1555:
if (imgType == PvrSquareTwiddled || imgType == PvrSquareTwiddledMipmaps
|| imgType == PvrRectangleTwiddled || imgType == PvrSquareTwiddledMipmapsAlt)
texConv = opengl::tex1555_TW32;
else if (imgType == PvrVQ || imgType == PvrVQMipmaps)
texConv = opengl::tex1555_VQ32;
else if (imgType == PvrRectangle || imgType == PvrStride)
texConv = opengl::tex1555_PL32;
else
{
WARN_LOG(COMMON, "Unsupported 1555 image type: %d", imgType);
return false;
}
break;
case Pixel565:
if (imgType == PvrSquareTwiddled || imgType == PvrSquareTwiddledMipmaps
|| imgType == PvrRectangleTwiddled || imgType == PvrSquareTwiddledMipmapsAlt)
texConv = opengl::tex565_TW32;
else if (imgType == PvrVQ || imgType == PvrVQMipmaps)
texConv = opengl::tex565_VQ32;
else if (imgType == PvrRectangle || imgType == PvrStride)
texConv = opengl::tex565_PL32;
else
{
WARN_LOG(COMMON, "Unsupported 565 image type: %d", imgType);
return false;
}
break;
case Pixel4444:
if (imgType == PvrSquareTwiddled || imgType == PvrSquareTwiddledMipmaps
|| imgType == PvrRectangleTwiddled || imgType == PvrSquareTwiddledMipmapsAlt)
texConv = opengl::tex4444_TW32;
else if (imgType == PvrVQ || imgType == PvrVQMipmaps)
texConv = opengl::tex4444_VQ32;
else if (imgType == PvrRectangle || imgType == PvrStride)
texConv = opengl::tex4444_PL32;
else
{
WARN_LOG(COMMON, "Unsupported 4444 image type: %d", imgType);
return false;
}
break;
default:
WARN_LOG(COMMON, "Unsupported PVR pixel type: %d", pixelFormat);
return false;
}
DEBUG_LOG(COMMON, "PVR file: size %d pixelFmt %d imgType %d w %d h %d\n", size, pixelFormat, imgType, width, height);
u32 texU = 3;
while (1u << texU < width)
texU++;
if (imgType == PvrSquareTwiddledMipmapsAlt)
// Hardcoding pixel size. Not correct for palette texs
p += OtherMipPoint[texU] * 2;
else if (imgType == PvrSquareTwiddledMipmaps)
// Hardcoding pixel size. Not correct for palette texs
p += (OtherMipPoint[texU] - 2) * 2;
else if (imgType == PvrVQMipmaps)
p += VQMipPoint[texU];
PixelBuffer<u32> pb;
pb.init(width, height);
texConv(&pb, p, width, height);
out.resize(width * height * 4);
memcpy(out.data(), pb.data(), out.size());
return true;
}

View File

@ -0,0 +1,190 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "scraper.h"
#include "http_client.h"
#include "stdclass.h"
#include "emulator.h"
#include "imgread/common.h"
#include "imgread/isofs.h"
#include "reios/reios.h"
#include "pvrparser.h"
#include <stb_image_write.h>
bool Scraper::downloadImage(const std::string& url, const std::string& localName)
{
DEBUG_LOG(COMMON, "downloading %s", url.c_str());
std::vector<u8> content;
std::string contentType;
if (!http::success(http::get(url, content, contentType)))
{
WARN_LOG(COMMON, "download_image http error: %s", url.c_str());
return false;
}
if (contentType.substr(0, 6) != "image/")
{
WARN_LOG(COMMON, "download_image bad content type %s", contentType.c_str());
return false;
}
FILE *f = nowide::fopen(localName.c_str(), "wb");
if (f == nullptr)
{
WARN_LOG(COMMON, "can't create local file %s: error %d", localName.c_str(), errno);
return false;
}
fwrite(&content[0], 1, content.size(), f);
fclose(f);
return true;
}
std::string Scraper::makeUniqueFilename(const std::string& url)
{
std::string extension = get_file_extension(url);
std::string path;
do {
path = saveDirectory + std::to_string(rand()) + "." + extension;
} while (file_exists(path));
return path;
}
void OfflineScraper::scrape(GameBoxart& item)
{
if (item.parsed)
return;
item.parsed = true;
int platform = getGamePlatform(item.gamePath.c_str());
if (platform == DC_PLATFORM_DREAMCAST)
{
if (item.gamePath.empty())
{
// Dreamcast BIOS
item.uniqueId.clear();
item.searchName.clear();
return;
}
Disc *disc;
try {
disc = OpenDisc(item.gamePath.c_str());
} catch (const std::runtime_error& e) {
WARN_LOG(COMMON, "Can't open disk %s: %s", item.gamePath.c_str(), e.what());
// No need to retry if the disk is invalid/corrupted
item.scraped = true;
item.uniqueId.clear();
item.searchName.clear();
return;
} catch (const std::exception& e) {
// For some reason, this doesn't catch FlycastException on macOS/clang, so we need the
// previous catch block
WARN_LOG(COMMON, "Can't open disk %s: %s", item.gamePath.c_str(), e.what());
item.scraped = true;
item.uniqueId.clear();
item.searchName.clear();
return;
}
u8 sector[2048];
disc->ReadSectors(disc->GetBaseFAD(), 1, sector, sizeof(sector));
ip_meta_t diskId;
memcpy(&diskId, sector, sizeof(diskId));
// Sanity check
if (memcmp(diskId.hardware_id, "SEGA SEGAKATANA ", sizeof(diskId.hardware_id))
|| memcmp(diskId.maker_id, "SEGA ENTERPRISES", sizeof(diskId.maker_id)))
{
WARN_LOG(COMMON, "Invalid IP META for disk %s", item.gamePath.c_str());
item.scraped = true;
item.uniqueId.clear();
item.searchName.clear();
return;
}
if (item.boxartPath.empty())
{
IsoFs isofs(disc);
std::unique_ptr<IsoFs::Directory> root(isofs.getRoot());
if (root != nullptr)
{
std::unique_ptr<IsoFs::Entry> entry(root->getEntry("0GDTEX.PVR"));
if (entry != nullptr && !entry->isDirectory())
{
IsoFs::File *gdtexFile = (IsoFs::File *)entry.get();
std::vector<u8> data(gdtexFile->getSize());
gdtexFile->read(data.data(), data.size());
std::vector<u8> out;
u32 w, h;
if (pvrParse(data.data(), data.size(), w, h, out))
{
stbi_flip_vertically_on_write(0);
item.setBoxartPath(makeUniqueFilename("gdtex.png"));
stbi_write_png(item.boxartPath.c_str(), w, h, 4, out.data(), 0);
}
}
}
}
delete disc;
item.uniqueId = trim_trailing_ws(std::string(diskId.product_number, sizeof(diskId.product_number)));
for (char& c : item.uniqueId)
if (!std::isprint(c))
c = ' ';
item.searchName = trim_trailing_ws(std::string(diskId.software_name, sizeof(diskId.software_name)));
if (item.searchName.empty())
item.searchName = item.name;
else
{
for (char& c : item.searchName)
if (!std::isprint(c))
c = ' ';
}
if (diskId.area_symbols[0] != '\0')
{
item.region = 0;
if (diskId.area_symbols[0] == 'J')
item.region |= GameBoxart::JAPAN;
if (diskId.area_symbols[1] == 'U')
item.region |= GameBoxart::USA;
if (diskId.area_symbols[2] == 'E')
item.region |= GameBoxart::EUROPE;
}
else
item.region = GameBoxart::JAPAN | GameBoxart::USA | GameBoxart::EUROPE;
}
else
{
item.uniqueId.clear();
// Use first one in case of alternate names (Virtua Tennis / Power Smash)
size_t spos = item.searchName.find('/');
if (spos != std::string::npos)
item.searchName = trim_trailing_ws(item.searchName.substr(0, spos));
// Delete trailing (...) and [...]
while (!item.searchName.empty())
{
size_t pos{ std::string::npos };
if (item.searchName.back() == ')')
pos = item.searchName.find_last_of('(');
else if (item.searchName.back() == ']')
pos = item.searchName.find_last_of('[');
if (pos == std::string::npos)
break;
item.searchName = trim_trailing_ws(item.searchName.substr(0, pos));
}
}
}

131
core/rend/boxart/scraper.h Normal file
View File

@ -0,0 +1,131 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <string>
#include <memory>
#include <vector>
#include "types.h"
#include "json.hpp"
using namespace nlohmann;
struct GameBoxart
{
std::string fileName;
std::string name;
std::string uniqueId;
std::string searchName;
u32 region = 0;
std::string releaseDate;
std::string overview;
std::string gamePath;
std::string boxartPath;
bool parsed = false;
bool scraped = false;
bool busy = false;
enum Region { JAPAN = 1, USA = 2, EUROPE = 4 };
json to_json() const
{
json j = {
{ "file_name", fileName },
{ "name", name },
{ "unique_id", uniqueId },
{ "search_name", searchName },
{ "region", region },
{ "release_date", releaseDate },
{ "overview", overview },
{ "boxart_path", boxartPath },
{ "parsed", parsed },
{ "scraped", scraped },
};
return j;
}
template<typename T>
static void loadProperty(T& i, const json& j, const std::string& propName)
{
try {
// asan error if missing contains(). json bug?
if (j.contains(propName)) {
i = j[propName].get<T>();
return;
}
} catch (const json::exception& e) {
}
i = T();
}
GameBoxart() = default;
GameBoxart(const json& j)
{
loadProperty(fileName, j, "file_name");
loadProperty(name, j, "name");
loadProperty(uniqueId, j, "unique_id");
loadProperty(searchName, j, "search_name");
loadProperty(region, j, "region");
loadProperty(releaseDate, j, "release_date");
loadProperty(overview, j, "overview");
loadProperty(boxartPath, j, "boxart_path");
loadProperty(parsed, j, "parsed");
loadProperty(scraped, j, "scraped");
}
void setBoxartPath(const std::string& path) {
if (!boxartPath.empty())
nowide::remove(boxartPath.c_str());
boxartPath = path;
}
};
class Scraper
{
public:
virtual bool initialize(const std::string& saveDirectory) {
this->saveDirectory = saveDirectory;
return true;
}
virtual void scrape(GameBoxart& item) = 0;
virtual void scrape(std::vector<GameBoxart>& items) {
for (GameBoxart& item : items)
scrape(item);
}
virtual ~Scraper() = default;
protected:
bool downloadImage(const std::string& url, const std::string& localName);
std::string makeUniqueFilename(const std::string& url);
private:
std::string saveDirectory;
};
class OfflineScraper : public Scraper
{
public:
void scrape(GameBoxart& item) override;
};

View File

@ -21,6 +21,7 @@
#include "imgui_impl_dx11.h"
#include "dx11context.h"
#include "rend/gui.h"
#include <unordered_map>
class DX11Driver final : public ImGuiDriver
{
@ -53,6 +54,49 @@ public:
frameRendered = true;
}
ImTextureID getTexture(const std::string& name) override {
auto it = textures.find(name);
if (it != textures.end())
return (ImTextureID)it->second.textureView.get();
else
return ImTextureID{};
}
ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height) override
{
Texture& texture = textures[name];
texture.texture.reset();
texture.textureView.reset();
D3D11_TEXTURE2D_DESC desc{};
desc.Width = width;
desc.Height = height;
desc.ArraySize = 1;
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc.MipLevels = 1;
theDX11Context.getDevice()->CreateTexture2D(&desc, nullptr, &texture.texture.get());
D3D11_SHADER_RESOURCE_VIEW_DESC viewDesc{};
viewDesc.Format = desc.Format;
viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
viewDesc.Texture2D.MipLevels = desc.MipLevels;
theDX11Context.getDevice()->CreateShaderResourceView(texture.texture, &viewDesc, &texture.textureView.get());
theDX11Context.getDeviceContext()->UpdateSubresource(texture.texture, 0, nullptr, data, width * 4, width * 4 * height);
return (ImTextureID)texture.textureView.get();
}
private:
struct Texture
{
ComPtr<ID3D11Texture2D> texture;
ComPtr<ID3D11ShaderResourceView> textureView;
};
bool frameRendered = false;
std::unordered_map<std::string, Texture> textures;
};

View File

@ -82,7 +82,7 @@ void DX11Overlay::draw(u32 width, u32 height, bool vmu, bool crosshair)
#ifdef LIBRETRO
if (i & 1)
continue;
w *= vmu_screen_params[i / 2].vmu_screen_size_mult;
w *= (float)vmu_screen_params[i / 2].vmu_screen_size_mult / config::ScreenStretching * 100.f;
h *= vmu_screen_params[i / 2].vmu_screen_size_mult;
switch (vmu_screen_params[i / 2].vmu_screen_position)
{
@ -170,15 +170,18 @@ void DX11Overlay::draw(u32 width, u32 height, bool vmu, bool crosshair)
float x, y;
std::tie(x, y) = getCrosshairPosition(i);
#ifdef LIBRETRO
float halfWidth = LIGHTGUN_CROSSHAIR_SIZE / 2.f;
float halfWidth = LIGHTGUN_CROSSHAIR_SIZE / 2.f / config::ScreenStretching * 100.f;
float halfHeight = LIGHTGUN_CROSSHAIR_SIZE / 2.f;
x /= config::ScreenStretching / 100.f;
#else
float halfWidth = XHAIR_WIDTH * settings.display.uiScale / 2.f;
float halfHeight = halfWidth;
#endif
D3D11_VIEWPORT vp{};
vp.TopLeftX = x - halfWidth;
vp.TopLeftY = y - halfWidth;
vp.TopLeftY = y - halfHeight;
vp.Width = halfWidth * 2;
vp.Height = halfWidth * 2;
vp.Height = halfHeight * 2;
vp.MinDepth = 0.f;
vp.MaxDepth = 1.f;
deviceContext->RSSetViewports(1, &vp);

View File

@ -503,28 +503,22 @@ void DX11Renderer::renderFramebuffer()
deviceContext->ClearRenderTargetView(theDX11Context.getRenderTarget(), colors);
int outwidth = settings.display.width;
int outheight = settings.display.height;
float renderAR = getOutputFramebufferAspectRatio();
float screenAR = (float)outwidth / outheight;
if (config::Rotate90)
std::swap(outwidth, outheight);
float renderAR = (float)width / height;
float screenAR = (float)outwidth / outheight;
int dy = 0;
int dx = 0;
if (renderAR > screenAR)
dy = (int)roundf((outheight - outwidth / renderAR) / 2.f);
dy = (int)roundf(outheight * (1 - screenAR / renderAR) / 2.f);
else
dx = (int)roundf((outwidth - outheight * renderAR) / 2.f);
dx = (int)roundf(outwidth * (1 - renderAR / screenAR) / 2.f);
float x = (float)dx;
float y = (float)dy;
float w = (float)(outwidth - 2 * dx);
float h = (float)(outheight - 2 * dy);
float x = 0, y = 0, w = (float)outwidth, h = (float)outheight;
if (dx != 0)
{
x = (float)dx;
w = (float)(outwidth - 2 * dx);
}
else
{
y = (float)dy;
h = (float)(outheight - 2 * dy);
}
// Normalize
x = x * 2.f / outwidth - 1.f;
w *= 2.f / outwidth;

View File

@ -20,7 +20,7 @@
#include "dx11context.h"
#include <versionhelpers.h>
void DX11Texture::UploadToGPU(int width, int height, u8* temp_tex_buffer, bool mipmapped, bool mipmapsIncluded)
void DX11Texture::UploadToGPU(int width, int height, const u8* temp_tex_buffer, bool mipmapped, bool mipmapsIncluded)
{
D3D11_TEXTURE2D_DESC desc{};
desc.Width = width;

View File

@ -35,7 +35,7 @@ public:
ComPtr<ID3D11ShaderResourceView> textureView;
std::string GetId() override { return std::to_string((uintptr_t)texture.get()); }
void UploadToGPU(int width, int height, u8* temp_tex_buffer, bool mipmapped,
void UploadToGPU(int width, int height, const u8* temp_tex_buffer, bool mipmapped,
bool mipmapsIncluded = false) override;
bool Delete() override;
void loadCustomTexture();

View File

@ -48,6 +48,7 @@ static ID3D11InputLayout* g_pInputLayout = NULL;
static ID3D11Buffer* g_pVertexConstantBuffer = NULL;
static ID3D11PixelShader* g_pPixelShader = NULL;
ID3D11SamplerState* g_pFontSampler = NULL;
ID3D11SamplerState* g_pTextureSampler = NULL;
ID3D11ShaderResourceView*g_pFontTextureView = NULL;
static ID3D11RasterizerState* g_pRasterizerState = NULL;
static ID3D11BlendState* g_pBlendState = NULL;
@ -81,7 +82,6 @@ static void ImGui_ImplDX11_SetupRenderState(ImDrawData* draw_data, ID3D11DeviceC
ctx->VSSetShader(g_pVertexShader, NULL, 0);
ctx->VSSetConstantBuffers(0, 1, &g_pVertexConstantBuffer);
ctx->PSSetShader(g_pPixelShader, NULL, 0);
ctx->PSSetSamplers(0, 1, &g_pFontSampler);
ctx->GSSetShader(NULL, NULL, 0);
ctx->HSSetShader(NULL, NULL, 0); // In theory we should backup and restore this as well.. very infrequently used..
ctx->DSSetShader(NULL, NULL, 0); // In theory we should backup and restore this as well.. very infrequently used..
@ -249,6 +249,10 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
// Bind texture, Draw
ID3D11ShaderResourceView* texture_srv = (ID3D11ShaderResourceView*)pcmd->TextureId;
if (pcmd->TextureId != (ImTextureID)g_pFontTextureView)
ctx->PSSetSamplers(0, 1, &g_pTextureSampler);
else
ctx->PSSetSamplers(0, 1, &g_pFontSampler);
ctx->PSSetShaderResources(0, 1, &texture_srv);
ctx->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset);
}
@ -321,7 +325,7 @@ static void ImGui_ImplDX11_CreateFontsTexture()
// Store our identifier
io.Fonts->SetTexID((ImTextureID)g_pFontTextureView);
// Create texture sampler
// Create texture samplers
{
D3D11_SAMPLER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
@ -334,6 +338,10 @@ static void ImGui_ImplDX11_CreateFontsTexture()
desc.MinLOD = 0.f;
desc.MaxLOD = 0.f;
g_pd3dDevice->CreateSamplerState(&desc, &g_pFontSampler);
desc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
desc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
g_pd3dDevice->CreateSamplerState(&desc, &g_pTextureSampler);
}
}
@ -496,6 +504,7 @@ void ImGui_ImplDX11_InvalidateDeviceObjects()
return;
if (g_pFontSampler) { g_pFontSampler->Release(); g_pFontSampler = NULL; }
if (g_pTextureSampler) { g_pTextureSampler->Release(); g_pTextureSampler = NULL; }
if (g_pFontTextureView) { g_pFontTextureView->Release(); g_pFontTextureView = NULL; ImGui::GetIO().Fonts->SetTexID(NULL); } // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well.
if (g_pIB) { g_pIB->Release(); g_pIB = NULL; }
if (g_pVB) { g_pVB->Release(); g_pVB = NULL; }

View File

@ -364,7 +364,7 @@ struct DX11OITRenderer : public DX11Renderer
}
template<bool Transparent>
void drawModVols(int first, int count)
void drawModVols(int first, int count, const ModifierVolumeParam *modVolParams)
{
if (count == 0 || pvrrc.modtrig.used() == 0 || !config::ModifierVolumes)
return;
@ -378,14 +378,14 @@ struct DX11OITRenderer : public DX11Renderer
deviceContext->PSSetShader(shaders.getModVolShader(), nullptr, 0);
deviceContext->RSSetScissorRects(1, &scissorRect);
ModifierVolumeParam* params = Transparent ? &pvrrc.global_param_mvo_tr.head()[first] : &pvrrc.global_param_mvo.head()[first];
const ModifierVolumeParam *params = &modVolParams[first];
int mod_base = -1;
const float *curMVMat = nullptr;
const float *curProjMat = nullptr;
for (int cmv = 0; cmv < count; cmv++)
{
ModifierVolumeParam& param = params[cmv];
const ModifierVolumeParam& param = params[cmv];
u32 mv_mode = param.isp.DepthMode;
@ -533,7 +533,9 @@ struct DX11OITRenderer : public DX11Renderer
u32 tr_count = current_pass.tr_count - previous_pass.tr_count;
u32 mvo_count = current_pass.mvo_count - previous_pass.mvo_count;
DEBUG_LOG(RENDERER, "Render pass %d OP %d PT %d TR %d MV %d Tr MV %d autosort %d", render_pass + 1,
op_count, pt_count, tr_count, mvo_count, current_pass.mvo_tr_count - previous_pass.mvo_tr_count, current_pass.autosort);
op_count, pt_count, tr_count, mvo_count,
current_pass.mv_op_tr_shared ? mvo_count : current_pass.mvo_tr_count - previous_pass.mvo_tr_count,
current_pass.autosort);
//
// PASS 1: Geometry pass to update depth and stencil
@ -547,7 +549,7 @@ struct DX11OITRenderer : public DX11Renderer
drawList<ListType_Opaque, false, DX11OITShaders::Depth>(pvrrc.global_param_op, previous_pass.op_count, op_count);
drawList<ListType_Punch_Through, false, DX11OITShaders::Depth>(pvrrc.global_param_pt, previous_pass.pt_count, pt_count);
drawModVols<false>(previous_pass.mvo_count, mvo_count);
drawModVols<false>(previous_pass.mvo_count, mvo_count, pvrrc.global_param_mvo.head());
//
// PASS 2: Render OP and PT to opaque render target
@ -580,8 +582,13 @@ struct DX11OITRenderer : public DX11Renderer
ID3D11ShaderResourceView *p = nullptr;
deviceContext->PSSetShaderResources(4, 1, &p);
if (!theDX11Context.isIntel())
{
// Intel Iris Plus 640 just crashes
drawModVols<true>(previous_pass.mvo_tr_count, current_pass.mvo_tr_count - previous_pass.mvo_tr_count);
if (current_pass.mv_op_tr_shared)
drawModVols<true>(previous_pass.mvo_count, mvo_count, pvrrc.global_param_mvo.head());
else
drawModVols<true>(previous_pass.mvo_tr_count, current_pass.mvo_tr_count - previous_pass.mvo_tr_count, pvrrc.global_param_mvo_tr.head());
}
}
else
{

View File

@ -1103,33 +1103,19 @@ void D3DRenderer::renderFramebuffer()
{
devCache.SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE);
device->ColorFill(backbuffer, 0, D3DCOLOR_ARGB(255, VO_BORDER_COL._red, VO_BORDER_COL._green, VO_BORDER_COL._blue));
int fx = 0;
int sx = 0;
float renderAR = getOutputFramebufferAspectRatio();
float screenAR = (float)settings.display.width / settings.display.height;
int fbwidth = width;
int fbheight = height;
if (config::Rotate90)
std::swap(fbwidth, fbheight);
float renderAR = (float)fbwidth / fbheight;
int dx = 0;
int dy = 0;
if (renderAR > screenAR)
fx = (int)roundf((fbwidth - screenAR * fbheight) / 2.f);
dy = (int)roundf(settings.display.height * (1 - screenAR / renderAR) / 2.f);
else
sx = (int)roundf((settings.display.width - renderAR * settings.display.height) / 2.f);
dx = (int)roundf(settings.display.width * (1 - renderAR / screenAR) / 2.f);
if (!config::Rotate90)
{
RECT rs { 0, 0, (long)width, (long)height };
RECT rd { 0, 0, settings.display.width, settings.display.height };
if (sx != 0)
{
rd.left = sx;
rd.right = settings.display.width - sx;
}
else
{
rs.left = fx;
rs.right = width - fx;
}
RECT rd { dx, dy, settings.display.width - dx, settings.display.height - dy };
device->StretchRect(framebufferSurface, &rs, backbuffer, &rd,
config::TextureFiltering == 1 ? D3DTEXF_POINT : D3DTEXF_LINEAR); // This can fail if window is minimized
}
@ -1154,10 +1140,10 @@ void D3DRenderer::renderFramebuffer()
device->SetFVF(D3DFVF_XYZ | D3DFVF_TEX1);
D3DVIEWPORT9 viewport;
viewport.X = sx;
viewport.Y = fx * settings.display.width / height;
viewport.Width = settings.display.width - sx * 2;
viewport.Height = settings.display.height - 2 * fx * settings.display.width / height;
viewport.X = dx;
viewport.Y = dy;
viewport.Width = settings.display.width - dx * 2;
viewport.Height = settings.display.height - dy * 2;
viewport.MinZ = 0;
viewport.MaxZ = 1;
verifyWin(device->SetViewport(&viewport));

View File

@ -18,7 +18,7 @@
*/
#include "d3d_texture.h"
void D3DTexture::UploadToGPU(int width, int height, u8* temp_tex_buffer, bool mipmapped, bool mipmapsIncluded)
void D3DTexture::UploadToGPU(int width, int height, const u8* temp_tex_buffer, bool mipmapped, bool mipmapsIncluded)
{
D3DFORMAT d3dFormat;
u32 bpp = 2;
@ -90,7 +90,7 @@ void D3DTexture::UploadToGPU(int width, int height, u8* temp_tex_buffer, bool mi
else
{
u8 *dst = (u8 *)rect.pBits;
u8 *src = temp_tex_buffer;
const u8 *src = temp_tex_buffer;
for (u32 l = 0; l < h; l++)
{
memcpy(dst, src, w * bpp);

View File

@ -31,7 +31,7 @@ public:
ComPtr<IDirect3DTexture9> texture;
std::string GetId() override { return std::to_string((uintptr_t)texture.get()); }
void UploadToGPU(int width, int height, u8* temp_tex_buffer, bool mipmapped,
void UploadToGPU(int width, int height, const u8* temp_tex_buffer, bool mipmapped,
bool mipmapsIncluded = false) override;
bool Delete() override;
void loadCustomTexture();

View File

@ -20,6 +20,7 @@
#include "rend/imgui_driver.h"
#include "imgui_impl_dx9.h"
#include "dxcontext.h"
#include <unordered_map>
class DX9Driver final : public ImGuiDriver
{
@ -52,6 +53,45 @@ public:
frameRendered = true;
}
ImTextureID getTexture(const std::string& name) override {
auto it = textures.find(name);
if (it != textures.end())
return (ImTextureID)it->second.get();
else
return ImTextureID{};
}
ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height) override
{
ComPtr<IDirect3DTexture9>& texture = textures[name];
texture.reset();
theDXContext.getDevice()->CreateTexture(width, height, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &texture.get(), 0);
width *= 4;
D3DLOCKED_RECT rect;
texture->LockRect(0, &rect, nullptr, 0);
u8 *dst = (u8 *)rect.pBits;
const u8 *src = data;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x += 4)
{
// BGRA <- RGBA
dst[x + 0] = src[x + 2];
dst[x + 1] = src[x + 1];
dst[x + 2] = src[x + 0];
dst[x + 3] = src[x + 3];
}
dst += rect.Pitch;
src += width;
}
texture->UnlockRect(0);
return (ImTextureID)texture.get();
}
private:
bool frameRendered = false;
std::unordered_map<std::string, ComPtr<IDirect3DTexture9>> textures;
};

View File

@ -193,6 +193,16 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
const RECT r = { (LONG)(pcmd->ClipRect.x - clip_off.x), (LONG)(pcmd->ClipRect.y - clip_off.y), (LONG)(pcmd->ClipRect.z - clip_off.x), (LONG)(pcmd->ClipRect.w - clip_off.y) };
const LPDIRECT3DTEXTURE9 texture = (LPDIRECT3DTEXTURE9)pcmd->TextureId;
g_pd3dDevice->SetTexture(0, texture);
if (texture != g_FontTexture)
{
g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
}
else
{
g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
}
g_pd3dDevice->SetScissorRect(&r);
g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)cmd_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
}

View File

@ -31,6 +31,7 @@
struct GameMedia {
std::string name;
std::string path;
std::string gameName; // for arcade games, from the rom list
};
static bool operator<(const GameMedia &left, const GameMedia &right)
@ -82,27 +83,28 @@ class GameScanner
if (item.name.substr(0, 2) == "._")
// Ignore Mac OS turds
continue;
std::string name(item.name);
std::string child_path = item.parentPath + "/" + name;
std::string fileName(item.name);
std::string child_path = item.parentPath + "/" + fileName;
#ifdef __APPLE__
extern std::string os_PrecomposedString(std::string string);
name = os_PrecomposedString(name);
fileName = os_PrecomposedString(fileName);
#endif
std::string extension = get_file_extension(name);
std::string gameName(get_file_basename(item.name));
std::string extension = get_file_extension(fileName);
if (extension == "zip" || extension == "7z")
{
std::string basename = get_file_basename(name);
std::string basename = get_file_basename(fileName);
string_tolower(basename);
auto it = arcade_games.find(basename);
if (it == arcade_games.end())
continue;
name = name + " (" + std::string(it->second->description) + ")";
gameName = it->second->description;
fileName = fileName + " (" + gameName + ")";
}
else if (extension == "chd" || extension == "gdi")
{
// Hide arcade gdroms
std::string basename = get_file_basename(name);
std::string basename = get_file_basename(fileName);
string_tolower(basename);
if (arcade_gdroms.count(basename) != 0)
continue;
@ -111,7 +113,7 @@ class GameScanner
|| (extension != "bin" && extension != "lst" && extension != "dat"))
&& extension != "cdi" && extension != "cue")
continue;
insert_game(GameMedia{ name, child_path });
insert_game(GameMedia{ fileName, child_path, gameName });
}
}

View File

@ -456,7 +456,7 @@ static void abufferDrawQuad()
glCheck();
}
void DrawTranslucentModVols(int first, int count)
void DrawTranslucentModVols(int first, int count, bool useOpaqueGeom)
{
if (count == 0 || pvrrc.modtrig.used() == 0)
return;
@ -478,7 +478,7 @@ void DrawTranslucentModVols(int first, int count)
glCheck();
ModifierVolumeParam* params = &pvrrc.global_param_mvo_tr.head()[first];
ModifierVolumeParam* params = useOpaqueGeom ? &pvrrc.global_param_mvo.head()[first] : &pvrrc.global_param_mvo_tr.head()[first];
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);

View File

@ -170,7 +170,7 @@ void initABuffer();
void termABuffer();
void reshapeABuffer(int width, int height);
void renderABuffer();
void DrawTranslucentModVols(int first, int count);
void DrawTranslucentModVols(int first, int count, bool useOpaqueGeom);
void checkOverflowAndReset();
extern GLuint stencilTexId;

View File

@ -532,7 +532,7 @@ void gl4DrawStrips(GLuint output_fbo, int width, int height)
current_pass.pt_count - previous_pass.pt_count,
current_pass.tr_count - previous_pass.tr_count,
current_pass.mvo_count - previous_pass.mvo_count,
current_pass.mvo_tr_count - previous_pass.mvo_tr_count,
current_pass.mv_op_tr_shared ? current_pass.mvo_count - previous_pass.mvo_count : current_pass.mvo_tr_count - previous_pass.mvo_tr_count,
current_pass.autosort);
glBindVertexArray(gl4.vbo.getMainVAO());
@ -645,7 +645,10 @@ void gl4DrawStrips(GLuint output_fbo, int width, int height)
if (config::ModifierVolumes)
{
SetBaseClipping();
DrawTranslucentModVols(previous_pass.mvo_tr_count, current_pass.mvo_tr_count - previous_pass.mvo_tr_count);
if (current_pass.mv_op_tr_shared)
DrawTranslucentModVols(previous_pass.mvo_count, current_pass.mvo_count - previous_pass.mvo_count, true);
else
DrawTranslucentModVols(previous_pass.mvo_tr_count, current_pass.mvo_tr_count - previous_pass.mvo_tr_count, false);
}
// Rebind the depth/stencil texture to the framebuffer
@ -769,10 +772,12 @@ void gl4DrawVmuTexture(u8 vmu_screen_number)
glActiveTexture(GL_TEXTURE0);
const float vmu_padding = 8.f;
float x = (config::Widescreen && config::ScreenStretching == 100 ? -(640.f * 4.f / 3.f - 640.f) / 2 : 0) + vmu_padding;
const float x_scale = 100.f / config::ScreenStretching;
const float y_scale = (float)gl.ofbo.width / gl.ofbo.height >= 8.f / 3.f - 0.1f ? 0.5f : 1.f;
float x = (config::Widescreen && config::ScreenStretching == 100 ? -1 / gl4ShaderUniforms.ndcMat[0][0] / 4.f : 0) + vmu_padding;
float y = vmu_padding;
float w = VMU_SCREEN_WIDTH * vmu_screen_params[vmu_screen_number].vmu_screen_size_mult;
float h = VMU_SCREEN_HEIGHT * vmu_screen_params[vmu_screen_number].vmu_screen_size_mult;
float w = (float)VMU_SCREEN_WIDTH * vmu_screen_params[vmu_screen_number].vmu_screen_size_mult * x_scale;
float h = (float)VMU_SCREEN_HEIGHT * vmu_screen_params[vmu_screen_number].vmu_screen_size_mult * y_scale;
if (vmu_lcd_changed[vmu_screen_number * 2] || vmuTextureId[vmu_screen_number] == 0)
UpdateVmuTexture(vmu_screen_number);
@ -782,14 +787,14 @@ void gl4DrawVmuTexture(u8 vmu_screen_number)
case UPPER_LEFT:
break;
case UPPER_RIGHT:
x = 640 - x - w;
x = 2 / gl4ShaderUniforms.ndcMat[0][0] - x - w;
break;
case LOWER_LEFT:
y = 480 - y - h;
y = -2 / gl4ShaderUniforms.ndcMat[1][1] - y - h;
break;
case LOWER_RIGHT:
x = 640 - x - w;
y = 480 - y - h;
x = 2 / gl4ShaderUniforms.ndcMat[0][0] - x - w;
y = -2 / gl4ShaderUniforms.ndcMat[1][1] - y - h;
break;
}
@ -846,13 +851,11 @@ void gl4DrawGunCrosshair(u8 port)
glActiveTexture(GL_TEXTURE0);
float x=0;
float y=0;
float w=LIGHTGUN_CROSSHAIR_SIZE;
float h=LIGHTGUN_CROSSHAIR_SIZE;
x = lightgun_params[port].x - ( LIGHTGUN_CROSSHAIR_SIZE / 2 );
y = lightgun_params[port].y - ( LIGHTGUN_CROSSHAIR_SIZE / 2 );
float stretch = config::ScreenStretching / 100.f;
float w = (float)LIGHTGUN_CROSSHAIR_SIZE / stretch;
float h = (float)LIGHTGUN_CROSSHAIR_SIZE;
float x = lightgun_params[port].x / stretch - w / 2;
float y = lightgun_params[port].y - h / 2;
if (lightgun_params[port].dirty || lightgunTextureId[port] == 0)
UpdateLightGunTexture(port);

View File

@ -4,6 +4,7 @@
#include "rend/tileclip.h"
#include "rend/osd.h"
#include "naomi2.h"
#include "rend/transform_matrix.h"
/*
@ -700,12 +701,7 @@ void DrawStrips()
void DrawFramebuffer()
{
float aspectRatio = 4.f / 3.f;
if (config::Rotate90)
aspectRatio /= config::ScreenStretching / 100.f;
else
aspectRatio *= config::ScreenStretching / 100.f;
int sx = (int)roundf((gl.ofbo.width - aspectRatio * gl.ofbo.height) / 2.f);
int sx = (int)roundf((gl.ofbo.width - 4.f / 3.f * gl.ofbo.height) / 2.f);
glViewport(sx, 0, gl.ofbo.width - sx * 2, gl.ofbo.height);
drawQuad(fbTextureId, false, true);
glcache.DeleteTextures(1, &fbTextureId);
@ -715,27 +711,21 @@ void DrawFramebuffer()
bool render_output_framebuffer()
{
glcache.Disable(GL_SCISSOR_TEST);
int fx = 0;
int sx = 0;
float screenAR = (float)settings.display.width / settings.display.height;
int fbwidth = gl.ofbo.width;
int fbheight = gl.ofbo.height;
if (config::Rotate90)
std::swap(fbwidth, fbheight);
float renderAR = (float)fbwidth / fbheight;
float renderAR = getOutputFramebufferAspectRatio();
int dx = 0;
int dy = 0;
if (renderAR > screenAR)
fx = (int)roundf((fbwidth - screenAR * fbheight) / 2.f);
dy = (int)roundf(settings.display.height * (1 - screenAR / renderAR) / 2.f);
else
sx = (int)roundf((settings.display.width - renderAR * settings.display.height) / 2.f);
dx = (int)roundf(settings.display.width * (1 - renderAR / screenAR) / 2.f);
if (gl.gl_major < 3 || config::Rotate90)
{
if (gl.ofbo.tex == 0)
return false;
if (sx != 0)
glViewport(sx, 0, settings.display.width - sx * 2, settings.display.height);
else
glViewport(-fx, 0, settings.display.width + fx * 2, settings.display.height);
glViewport(dx, dy, settings.display.width - dx * 2, settings.display.height - dy * 2);
glBindFramebuffer(GL_FRAMEBUFFER, gl.ofbo.origFbo);
glcache.ClearColor(VO_BORDER_COL.red(), VO_BORDER_COL.green(), VO_BORDER_COL.blue(), 1.f);
glClear(GL_COLOR_BUFFER_BIT);
@ -752,8 +742,8 @@ bool render_output_framebuffer()
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gl.ofbo.origFbo);
glcache.ClearColor(VO_BORDER_COL.red(), VO_BORDER_COL.green(), VO_BORDER_COL.blue(), 1.f);
glClear(GL_COLOR_BUFFER_BIT);
glBlitFramebuffer(fx, 0, gl.ofbo.width - fx, gl.ofbo.height,
sx, 0, settings.display.width - sx, settings.display.height,
glBlitFramebuffer(0, 0, gl.ofbo.width, gl.ofbo.height,
dx, dy, settings.display.width - dx, settings.display.height - dy,
GL_COLOR_BUFFER_BIT, config::TextureFiltering == 1 ? GL_NEAREST : GL_LINEAR);
glBindFramebuffer(GL_FRAMEBUFFER, gl.ofbo.origFbo);
#endif
@ -832,10 +822,12 @@ void DrawVmuTexture(u8 vmu_screen_number)
glActiveTexture(GL_TEXTURE0);
const float vmu_padding = 8.f;
float x = (config::Widescreen && config::ScreenStretching == 100 ? -(640.f * 4.f / 3.f - 640.f) / 2 : 0) + vmu_padding;
const float x_scale = 100.f / config::ScreenStretching;
const float y_scale = (float)gl.ofbo.width / gl.ofbo.height >= 8.f / 3.f - 0.1f ? 0.5f : 1.f;
float x = (config::Widescreen && config::ScreenStretching == 100 ? -1 / ShaderUniforms.ndcMat[0][0] / 4.f : 0) + vmu_padding;
float y = vmu_padding;
float w = VMU_SCREEN_WIDTH * vmu_screen_params[vmu_screen_number].vmu_screen_size_mult;
float h = VMU_SCREEN_HEIGHT * vmu_screen_params[vmu_screen_number].vmu_screen_size_mult;
float w = (float)VMU_SCREEN_WIDTH * vmu_screen_params[vmu_screen_number].vmu_screen_size_mult * x_scale;
float h = (float)VMU_SCREEN_HEIGHT * vmu_screen_params[vmu_screen_number].vmu_screen_size_mult * y_scale;
if (vmu_lcd_changed[vmu_screen_number * 2] || vmuTextureId[vmu_screen_number] == 0)
UpdateVmuTexture(vmu_screen_number);
@ -845,14 +837,14 @@ void DrawVmuTexture(u8 vmu_screen_number)
case UPPER_LEFT:
break;
case UPPER_RIGHT:
x = 640 - x - w;
x = 2 / ShaderUniforms.ndcMat[0][0] - x - w;
break;
case LOWER_LEFT:
y = 480 - y - h;
y = -2 / ShaderUniforms.ndcMat[1][1] - y - h;
break;
case LOWER_RIGHT:
x = 640 - x - w;
y = 480 - y - h;
x = 2 / ShaderUniforms.ndcMat[0][0] - x - w;
y = -2 / ShaderUniforms.ndcMat[1][1] - y - h;
break;
}
@ -938,11 +930,12 @@ void DrawGunCrosshair(u8 port)
glActiveTexture(GL_TEXTURE0);
float x = lightgun_params[port].x;
float stretch = config::ScreenStretching / 100.f;
float x = lightgun_params[port].x / stretch;
float y = lightgun_params[port].y;
float w = LIGHTGUN_CROSSHAIR_SIZE;
float h = LIGHTGUN_CROSSHAIR_SIZE;
float w = (float)LIGHTGUN_CROSSHAIR_SIZE / stretch;
float h = (float)LIGHTGUN_CROSSHAIR_SIZE;
x -= w / 2;
y -= h / 2;

View File

@ -468,10 +468,13 @@ void findGLVersion()
gl.is_gles = theGLContext.isGLES();
if (gl.is_gles)
{
gl.border_clamp_supported = false;
if (gl.gl_major >= 3)
{
gl.gl_version = "GLES3";
gl.glsl_version_header = "#version 300 es";
if (gl.gl_major > 3 || gl.gl_minor >= 2)
gl.border_clamp_supported = true;
}
else
{
@ -491,6 +494,8 @@ void findGLVersion()
GLint precision;
glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_HIGH_FLOAT, ranges, &precision);
gl.highp_float_supported = (ranges[0] != 0 || ranges[1] != 0) && precision != 0;
if (!gl.border_clamp_supported)
gl.border_clamp_supported = strstr(extensions, "GL_EXT_texture_border_clamp") != nullptr;
}
else
{
@ -511,6 +516,7 @@ void findGLVersion()
gl.single_channel_format = GL_ALPHA;
}
gl.highp_float_supported = true;
gl.border_clamp_supported = true;
}
gl.max_anisotropy = 1.f;
#if !defined(GLES2)

View File

@ -236,6 +236,7 @@ struct gl_ctx
bool highp_float_supported;
float max_anisotropy;
bool mesa_nouveau;
bool border_clamp_supported;
size_t get_index_size() { return index_type == GL_UNSIGNED_INT ? sizeof(u32) : sizeof(u16); }
};
@ -345,7 +346,7 @@ public:
GLuint texID; //gl texture
std::string GetId() override { return std::to_string(texID); }
void UploadToGPU(int width, int height, u8 *temp_tex_buffer, bool mipmapped, bool mipmapsIncluded = false) override;
void UploadToGPU(int width, int height, const u8 *temp_tex_buffer, bool mipmapped, bool mipmapsIncluded = false) override;
bool Delete() override;
};

View File

@ -10,7 +10,7 @@ GlTextureCache TexCache;
static void readAsyncPixelBuffer(u32 addr);
void TextureCacheData::UploadToGPU(int width, int height, u8 *temp_tex_buffer, bool mipmapped, bool mipmapsIncluded)
void TextureCacheData::UploadToGPU(int width, int height, const u8 *temp_tex_buffer, bool mipmapped, bool mipmapsIncluded)
{
//upload to OpenGL !
glcache.BindTexture(GL_TEXTURE_2D, texID);

View File

@ -499,30 +499,3 @@ static void ImGui_ImplOpenGL3_DrawBackground()
if (renderer != nullptr)
renderer->RenderLastFrame();
}
static ImTextureID createSimpleTexture(const unsigned int *data, u32 width, u32 height)
{
GLuint tex_id = glcache.GenTexture();
glcache.BindTexture(GL_TEXTURE_2D, tex_id);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
return reinterpret_cast<ImTextureID>(tex_id);
}
ImTextureID ImGui_ImplOpenGL3_CreateVmuTexture(const unsigned int *data)
{
return createSimpleTexture(data, 48, 32);
}
void ImGui_ImplOpenGL3_DeleteTexture(ImTextureID tex_id)
{
glcache.DeleteTextures(1, &(GLuint &)tex_id);
}
ImTextureID ImGui_ImplOpenGL3_CreateCrosshairTexture(const unsigned int *data)
{
return createSimpleTexture(data, 16, 16);
}

View File

@ -33,6 +33,3 @@ IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data);
IMGUI_IMPL_API ImTextureID ImGui_ImplOpenGL3_CreateVmuTexture(const unsigned int *);
IMGUI_IMPL_API void ImGui_ImplOpenGL3_DeleteTexture(ImTextureID);
ImTextureID ImGui_ImplOpenGL3_CreateCrosshairTexture(const unsigned int *data);

View File

@ -21,6 +21,15 @@
#include "wsi/gl_context.h"
#include "rend/osd.h"
#include "rend/gui.h"
#include "glcache.h"
#include "gles.h"
#ifndef GL_CLAMP_TO_BORDER
#define GL_CLAMP_TO_BORDER 0x812D
#endif
#ifndef GL_TEXTURE_BORDER_COLOR
#define GL_TEXTURE_BORDER_COLOR 0x1004
#endif
static constexpr int vmu_coords[8][2] = {
{ 0 , 0 },
@ -41,8 +50,8 @@ OpenGLDriver::OpenGLDriver()
for (auto& tex : vmu_lcd_tex_ids)
tex = ImTextureID();
ImGui_ImplOpenGL3_Init();
EventManager::listen(Event::Start, emuEventCallback, this);
EventManager::listen(Event::Terminate, emuEventCallback, this);
EventManager::listen(Event::Resume, emuEventCallback, this);
EventManager::listen(Event::Pause, emuEventCallback, this);
}
OpenGLDriver::~OpenGLDriver()
@ -50,19 +59,16 @@ OpenGLDriver::~OpenGLDriver()
EventManager::unlisten(Event::Start, emuEventCallback, this);
EventManager::unlisten(Event::Terminate, emuEventCallback, this);
for (u32 i = 0; i < ARRAY_SIZE(vmu_lcd_status); i++)
{
if (vmu_lcd_tex_ids[i] != ImTextureID())
{
ImGui_ImplOpenGL3_DeleteTexture(vmu_lcd_tex_ids[i]);
vmu_lcd_tex_ids[i] = ImTextureID();
}
}
std::vector<GLuint> texIds;
texIds.reserve(ARRAY_SIZE(vmu_lcd_tex_ids) + 1 + textures.size());
for (ImTextureID texId : vmu_lcd_tex_ids)
if (texId != ImTextureID())
texIds.push_back((GLuint)(uintptr_t)texId);
if (crosshairTexId != ImTextureID())
{
ImGui_ImplOpenGL3_DeleteTexture(crosshairTexId);
crosshairTexId = ImTextureID();
}
texIds.push_back((GLuint)(uintptr_t)crosshairTexId);
for (const auto& it : textures)
texIds.push_back((GLuint)(uintptr_t)it.second);
glcache.DeleteTextures(texIds.size(), &texIds[0]);
ImGui_ImplOpenGL3_Shutdown();
}
@ -84,9 +90,8 @@ void OpenGLDriver::displayVmus()
if (!vmu_lcd_status[i])
continue;
if (vmu_lcd_tex_ids[i] != (ImTextureID)0)
ImGui_ImplOpenGL3_DeleteTexture(vmu_lcd_tex_ids[i]);
vmu_lcd_tex_ids[i] = ImGui_ImplOpenGL3_CreateVmuTexture(vmu_lcd_data[i]);
if (vmu_lcd_changed[i] || vmu_lcd_tex_ids[i] == ImTextureID())
vmu_lcd_tex_ids[i] = updateTexture("__vmu" + std::to_string(i), (const u8 *)vmu_lcd_data[i], 48, 32);
int x = vmu_coords[i][0];
int y = vmu_coords[i][1];
@ -121,7 +126,8 @@ void OpenGLDriver::displayCrosshairs()
return;
if (crosshairTexId == ImTextureID())
crosshairTexId = ImGui_ImplOpenGL3_CreateCrosshairTexture(getCrosshairTextureData());
crosshairTexId = updateTexture("__crosshair", (const u8 *)getCrosshairTextureData(), 16, 16);
ImGui::SetNextWindowBgAlpha(0);
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize);
@ -164,3 +170,29 @@ void OpenGLDriver::present()
theGLContext.swap();
frameRendered = false;
}
ImTextureID OpenGLDriver::updateTexture(const std::string& name, const u8 *data, int width, int height)
{
ImTextureID oldId = getTexture(name);
if (oldId != ImTextureID())
glcache.DeleteTextures(1, (GLuint *)&oldId);
GLuint texId = glcache.GenTexture();
glcache.BindTexture(GL_TEXTURE_2D, texId);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
if (gl.border_clamp_supported)
{
float color[] = { 0.0f, 0.0f, 0.0f, 0.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
}
else
{
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
return textures[name] = (ImTextureID)(u64)texId;
}

View File

@ -19,6 +19,7 @@
#pragma once
#include "rend/imgui_driver.h"
#include "emulator.h"
#include <unordered_map>
class OpenGLDriver final : public ImGuiDriver
{
@ -37,15 +38,25 @@ public:
frameRendered = true;
}
ImTextureID getTexture(const std::string& name) override {
auto it = textures.find(name);
if (it != textures.end())
return it->second;
else
return ImTextureID{};
}
ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height) override;
private:
void emuEvent(Event event)
{
switch (event)
{
case Event::Start:
case Event::Resume:
gameStarted = true;
break;
case Event::Terminate:
case Event::Pause:
gameStarted = false;
break;
default:
@ -60,4 +71,5 @@ private:
ImTextureID crosshairTexId = ImTextureID();
bool gameStarted = false;
bool frameRendered = false;
std::unordered_map<std::string, ImTextureID> textures;
};

View File

@ -44,6 +44,7 @@
#include "lua/lua.h"
#include "gui_chat.h"
#include "imgui_driver.h"
#include "boxart/boxart.h"
#if defined(USE_SDL)
#include "sdl/sdl.h"
#endif
@ -73,6 +74,7 @@ void error_popup();
static GameScanner scanner;
static BackgroundGameLoader gameLoader;
static Boxart boxart;
static Chat chat;
static void emuEventCallback(Event event, void *)
@ -1390,6 +1392,10 @@ static void gui_display_settings()
#endif // !linux
#endif // !TARGET_IPHONE
OptionCheckbox("Box Art Game List", config::BoxartDisplayMode,
"Display game covert art in the game list.");
OptionCheckbox("Fetch Box Art", config::FetchBoxart,
"Fetch cover images from TheGamesDB.net.");
if (OptionCheckbox("Hide Legacy Naomi Roms", config::HideLegacyNaomiRoms,
"Hide .bin, .dat and .lst files from the content browser"))
scanner.refresh();
@ -2152,9 +2158,9 @@ static void gui_display_settings()
ShowHelpMarker("The local UDP port to use");
config::LocalPort.set(atoi(localPort));
}
OptionCheckbox("Enable UPnP", config::EnableUPnP);
ImGui::SameLine();
ShowHelpMarker("Automatically configure your network router for netplay");
OptionCheckbox("Enable UPnP", config::EnableUPnP, "Automatically configure your network router for netplay");
OptionCheckbox("Broadcast Digital Outputs", config::NetworkOutput, "Broadcast digital outputs and force-feedback state on TCP port 8000. "
"Compatible with the \"-output network\" MAME option. Arcade games only.");
}
ImGui::Spacing();
header("Other");
@ -2305,6 +2311,66 @@ inline static void gui_display_demo()
ImGui::ShowDemoWindow();
}
static void gameTooltip(const std::string& tip)
{
if (ImGui::IsItemHovered())
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 25.0f);
ImGui::TextUnformatted(tip.c_str());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
}
static bool getGameImage(const GameBoxart *art, ImTextureID& textureId, bool allowLoad)
{
textureId = ImTextureID{};
if (art->boxartPath.empty())
return false;
// Get the boxart texture. Load it if needed.
textureId = imguiDriver->getTexture(art->boxartPath);
if (textureId == ImTextureID() && allowLoad)
{
int width, height;
u8 *imgData = loadImage(art->boxartPath, width, height);
if (imgData != nullptr)
{
try {
textureId = imguiDriver->updateTextureAndAspectRatio(art->boxartPath, imgData, width, height);
} catch (...) {
// vulkan can throw during resizing
}
free(imgData);
}
return true;
}
return false;
}
static bool gameImageButton(ImTextureID textureId, const std::string& tooltip)
{
float ar = imguiDriver->getAspectRatio(textureId);
ImVec2 uv0 { 0.f, 0.f };
ImVec2 uv1 { 1.f, 1.f };
if (ar > 1)
{
uv0.y = -(ar - 1) / 2;
uv1.y = 1 + (ar - 1) / 2;
}
else if (ar != 0)
{
ar = 1 / ar;
uv0.x = -(ar - 1) / 2;
uv1.x = 1 + (ar - 1) / 2;
}
bool pressed = ImGui::ImageButton(textureId, ScaledVec2(200, 200) - ImGui::GetStyle().FramePadding * 2, uv0, uv1);
gameTooltip(tooltip);
return pressed;
}
static void gui_display_content()
{
fullScreenWindow(false);
@ -2345,13 +2411,42 @@ static void gui_display_content()
// Only if Filter and Settings aren't focused... ImGui::SetNextWindowFocus();
ImGui::BeginChild(ImGui::GetID("library"), ImVec2(0, 0), true, ImGuiWindowFlags_DragScrolling);
{
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ScaledVec2(8, 20));
ImGui::PushID("bios");
if (ImGui::Selectable("Dreamcast BIOS"))
gui_start_game("");
ImGui::PopID();
const int itemsPerLine = ImGui::GetContentRegionMax().x / (200 * settings.display.uiScale + ImGui::GetStyle().ItemSpacing.x);
if (config::BoxartDisplayMode)
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.5f));
else
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ScaledVec2(8, 20));
int counter = 0;
int loadedImages = 0;
if (gui_state != GuiState::SelectDisk && filter.PassFilter("Dreamcast BIOS"))
{
ImGui::PushID("bios");
bool pressed;
if (config::BoxartDisplayMode)
{
ImTextureID textureId{};
GameMedia game;
const GameBoxart *art = boxart.getBoxart(game);
if (art != nullptr)
{
if (getGameImage(art, textureId, loadedImages < 10))
loadedImages++;
}
if (textureId != ImTextureID())
pressed = gameImageButton(textureId, "Dreamcast BIOS");
else
pressed = ImGui::Button("Dreamcast BIOS", ScaledVec2(200, 200));
}
else
{
pressed = ImGui::Selectable("Dreamcast BIOS");
}
if (pressed)
gui_start_game("");
ImGui::PopID();
counter++;
}
{
scanner.get_mutex().lock();
for (const auto& game : scanner.get_game_list())
@ -2364,10 +2459,43 @@ static void gui_display_content()
// Only dreamcast disks
continue;
}
if (filter.PassFilter(game.name.c_str()))
std::string gameName = game.name;
const GameBoxart *art = nullptr;
if (config::BoxartDisplayMode)
{
art = boxart.getBoxart(game);
if (art != nullptr)
gameName = art->name;
}
if (filter.PassFilter(gameName.c_str()))
{
ImGui::PushID(game.path.c_str());
if (ImGui::Selectable(game.name.c_str()))
bool pressed;
if (config::BoxartDisplayMode)
{
if (counter % itemsPerLine != 0)
ImGui::SameLine();
counter++;
ImTextureID textureId{};
if (art != nullptr)
{
// Get the boxart texture. Load it if needed (max 10 per frame).
if (getGameImage(art, textureId, loadedImages < 10))
loadedImages++;
}
if (textureId != ImTextureID())
pressed = gameImageButton(textureId, game.name);
else
{
pressed = ImGui::Button(gameName.c_str(), ScaledVec2(200, 200));
gameTooltip(game.name);
}
}
else
{
pressed = ImGui::Selectable(gameName.c_str());
}
if (pressed)
{
if (gui_state == GuiState::SelectDisk)
{
@ -2396,6 +2524,7 @@ static void gui_display_content()
}
ImGui::PopStyleVar();
}
scrollWhenDraggingOnVoid();
windowDragScroll();
ImGui::EndChild();
ImGui::End();
@ -2732,6 +2861,7 @@ void gui_term()
EventManager::unlisten(Event::Resume, emuEventCallback);
EventManager::unlisten(Event::Start, emuEventCallback);
EventManager::unlisten(Event::Terminate, emuEventCallback);
gui_save();
}
}
@ -2767,6 +2897,11 @@ void gui_error(const std::string& what)
error_msg = what;
}
void gui_save()
{
boxart.saveDatabase();
}
#ifdef TARGET_UWP
// Ugly but a good workaround for MS stupidity
// UWP doesn't allow the UI thread to wait on a thread/task. When an std::future is ready, it is possible

View File

@ -45,6 +45,7 @@ void gui_stop_game(const std::string& message = "");
void gui_start_game(const std::string& path);
void gui_error(const std::string& what);
void gui_setOnScreenKeyboardCallback(void (*callback)(bool show));
void gui_save();
enum class GuiState {
Closed,

View File

@ -28,6 +28,9 @@
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
#include "hw/maple/maple_devs.h"
#define STBI_ONLY_JPEG
#define STBI_ONLY_PNG
#include <stb_image.h>
static std::string select_current_directory;
static std::vector<std::string> subfolders;
@ -864,3 +867,16 @@ void windowDragScroll()
}
}
}
u8 *loadImage(const std::string& path, int& width, int& height)
{
FILE *file = nowide::fopen(path.c_str(), "rb");
if (file == nullptr)
return nullptr;
int channels;
stbi_set_flip_vertically_on_load(0);
u8 *imgData = stbi_load_from_file(file, &width, &height, &channels, STBI_rgb_alpha);
std::fclose(file);
return imgData;
}

View File

@ -130,3 +130,11 @@ inline static ImVec2 operator+(const ImVec2& l, const ImVec2& r) {
inline static ImVec2 operator-(const ImVec2& l, const ImVec2& r) {
return ImVec2(l.x - r.x, l.y - r.y);
}
inline static ImVec2 operator*(const ImVec2& v, float f) {
return ImVec2(v.x * f, v.y * f);
}
inline static ImVec2 operator/(const ImVec2& v, float f) {
return ImVec2(v.x / f, v.y / f);
}
u8 *loadImage(const std::string& path, int& width, int& height);

View File

@ -20,6 +20,7 @@
#include "imgui/imgui.h"
#include "gui.h"
#include <memory>
#include <unordered_map>
class ImGuiDriver
{
@ -37,6 +38,28 @@ public:
virtual void present() = 0;
virtual void setFrameRendered() {}
virtual ImTextureID getTexture(const std::string& name) = 0;
virtual ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height) = 0;
ImTextureID updateTextureAndAspectRatio(const std::string& name, const u8 *data, int width, int height)
{
ImTextureID textureId = updateTexture(name, data, width, height);
if (textureId != ImTextureID())
aspectRatios[textureId] = (float)width / height;
return textureId;
}
float getAspectRatio(ImTextureID textureId) {
auto it = aspectRatios.find(textureId);
if (it != aspectRatios.end())
return it->second;
else
return 1;
}
private:
std::unordered_map<ImTextureID, float> aspectRatios;
};
extern std::unique_ptr<ImGuiDriver> imguiDriver;

View File

@ -41,7 +41,7 @@ class TransformMatrix
{
public:
TransformMatrix() = default;
TransformMatrix(const rend_context& renderingContext, int width = 0, int height = 0)
TransformMatrix(const rend_context& renderingContext, int width, int height)
{
CalcMatrices(&renderingContext, width, height);
}
@ -141,36 +141,28 @@ public:
normalMatrix = glm::translate(glm::vec3(startx, starty, 0));
scissorMatrix = normalMatrix;
const float screen_stretching = config::ScreenStretching / 100.f;
float scissoring_scale_x, scissoring_scale_y;
GetFramebufferScaling(true, scissoring_scale_x, scissoring_scale_y);
float x_coef;
float y_coef;
glm::mat4 trans_rot;
if (config::Rotate90)
if (config::Widescreen && !config::Rotate90)
{
float dc2s_scale_h = renderViewport.x / 640.0f;
sidebarWidth = 0;
y_coef = 2.0f / (renderViewport.y / dc2s_scale_h * scale_y) * screen_stretching * screenFlipY;
x_coef = 2.0f / dcViewport.x;
sidebarWidth = (1 - dcViewport.x / dcViewport.y * renderViewport.y / renderViewport.x) / 2;
if (config::SuperWidescreen)
dcViewport.x *= (float)settings.display.width / settings.display.height / 4.f * 3.f;
else
dcViewport.x *= 4.f / 3.f;
}
else
{
float dc2s_scale_h = renderViewport.y / 480.0f;
sidebarWidth = 0;
float x_coef = 2.0f / dcViewport.x;
float y_coef = 2.0f / dcViewport.y * screenFlipY;
sidebarWidth = (renderViewport.x - dc2s_scale_h * 640.0f * screen_stretching) / 2;
x_coef = 2.0f / (renderViewport.x / dc2s_scale_h * scale_x) * screen_stretching;
y_coef = 2.0f / dcViewport.y * screenFlipY;
}
trans_rot = glm::translate(glm::vec3(-1 + 2 * sidebarWidth / renderViewport.x, -screenFlipY, 0));
glm::mat4 trans = glm::translate(glm::vec3(-1 + 2 * sidebarWidth, -screenFlipY, 0));
normalMatrix = trans_rot
normalMatrix = trans
* glm::scale(glm::vec3(x_coef, y_coef, 1.f))
* normalMatrix;
scissorMatrix = trans_rot
scissorMatrix = trans
* glm::scale(glm::vec3(x_coef * scissoring_scale_x, y_coef * scissoring_scale_y, 1.f))
* scissorMatrix;
}
@ -200,11 +192,8 @@ private:
if (!renderingContext->isRTT && !renderingContext->isRenderFramebuffer)
{
if (!scissor)
{
scale_x = fb_scale_x;
scale_y = fb_scale_y;
}
if (!scissor && (FB_R_CTRL.vclk_div == 0 && SPG_CONTROL.interlace == 0))
scale_y /= 2.f;
if (SCALER_CTL.vscalefactor > 0x400)
{
// Interlace mode A (single framebuffer)
@ -216,9 +205,8 @@ private:
}
// VO pixel doubling is done after fb rendering/clipping
// so it should be used for scissoring as well
if (VO_CONTROL.pixel_double && !scissor)
scale_x *= 0.5f;
scale_x /= 2.f;
// the X Scaler halves the horizontal resolution but
// before clipping/scissoring
@ -238,3 +226,25 @@ private:
float scale_y = 0;
float sidebarWidth = 0;
};
inline static float getOutputFramebufferAspectRatio()
{
float renderAR;
if (config::Rotate90)
{
renderAR = 3.f / 4.f;
}
else
{
if (config::Widescreen)
{
if (config::SuperWidescreen)
renderAR = (float)settings.display.width / settings.display.height;
else
renderAR = 16.f / 9.f;
}
else
renderAR = 4.f / 3.f;
}
return renderAR * config::ScreenStretching / 100.f;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +1,53 @@
// dear imgui: Renderer for Vulkan
// This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..)
// dear imgui: Renderer Backend for Vulkan
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
// Missing features:
// [ ] Renderer: User texture binding. Changes of ImTextureID aren't supported by this binding! See https://github.com/ocornut/imgui/pull/914
// Implemented features:
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices.
// [!] Renderer: User texture binding. Use 'VkDescriptorSet' as ImTextureID. Read the FAQ about ImTextureID! See https://github.com/ocornut/imgui/pull/914 for discussions.
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
// https://github.com/ocornut/imgui
// Important: on 32-bit systems, user texture binding is only supported if your imconfig file has '#define ImTextureID ImU64'.
// See imgui_impl_vulkan.cpp file for details.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
// The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification.
// IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/
#pragma once
// Important note to the reader who wish to integrate imgui_impl_vulkan.cpp/.h in their own engine/app.
// - Common ImGui_ImplVulkan_XXX functions and structures are used to interface with imgui_impl_vulkan.cpp/.h.
// You will use those if you want to use this rendering backend in your engine/app.
// - Helper ImGui_ImplVulkanH_XXX functions and structures are only used by this example (main.cpp) and by
// the backend itself (imgui_impl_vulkan.cpp), but should PROBABLY NOT be used by your own engine/app code.
// Read comments in imgui_impl_vulkan.h.
#pragma once
#include "imgui/imgui.h" // IMGUI_IMPL_API
// [Configuration] in order to use a custom Vulkan function loader:
// (1) You'll need to disable default Vulkan function prototypes.
// We provide a '#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES' convenience configuration flag.
// In order to make sure this is visible from the imgui_impl_vulkan.cpp compilation unit:
// - Add '#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES' in your imconfig.h file
// - Or as a compilation flag in your build system
// - Or uncomment here (not recommended because you'd be modifying imgui sources!)
// - Do not simply add it in a .cpp file!
// (2) Call ImGui_ImplVulkan_LoadFunctions() before ImGui_ImplVulkan_Init() with your custom function.
// If you have no idea what this is, leave it alone!
//#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES
// Vulkan includes
#if !defined(TARGET_IPHONE)
#include <volk.h>
#undef VK_NO_PROTOTYPES
#else
#include <vulkan/vulkan.hpp>
#endif
#define IMGUI_VK_QUEUED_FRAMES 3
// Please zero-clear before use.
// Initialization data, for ImGui_ImplVulkan_Init()
// [Please zero-clear before use!]
struct ImGui_ImplVulkan_InitInfo
{
VkInstance Instance;
@ -31,62 +57,79 @@ struct ImGui_ImplVulkan_InitInfo
VkQueue Queue;
VkPipelineCache PipelineCache;
VkDescriptorPool DescriptorPool;
uint32_t Subpass;
uint32_t MinImageCount; // >= 2
uint32_t ImageCount; // >= MinImageCount
VkSampleCountFlagBits MSAASamples; // >= VK_SAMPLE_COUNT_1_BIT (0 -> default to VK_SAMPLE_COUNT_1_BIT)
const VkAllocationCallbacks* Allocator;
void (*CheckVkResultFn)(VkResult err);
};
// Called by user code
IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass render_pass, int subpass = 0);
IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown();
IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame();
IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer);
IMGUI_IMPL_API bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer);
IMGUI_IMPL_API void ImGui_ImplVulkan_InvalidateFontUploadObjects();
IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass render_pass);
IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown();
IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame();
IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline = VK_NULL_HANDLE);
IMGUI_IMPL_API bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer);
IMGUI_IMPL_API void ImGui_ImplVulkan_DestroyFontUploadObjects();
IMGUI_IMPL_API void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count); // To override MinImageCount after initialization (e.g. if swap chain is recreated)
// Called by ImGui_ImplVulkan_Init() might be useful elsewhere.
IMGUI_IMPL_API bool ImGui_ImplVulkan_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplVulkan_InvalidateDeviceObjects();
// Register a texture (VkDescriptorSet == ImTextureID)
// FIXME: This is experimental in the sense that we are unsure how to best design/tackle this problem, please post to https://github.com/ocornut/imgui/pull/914 if you have suggestions.
IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout);
// Optional: load Vulkan functions with a custom function loader
// This is only useful with IMGUI_IMPL_VULKAN_NO_PROTOTYPES / VK_NO_PROTOTYPES
IMGUI_IMPL_API bool ImGui_ImplVulkan_LoadFunctions(PFN_vkVoidFunction(*loader_func)(const char* function_name, void* user_data), void* user_data = NULL);
//-------------------------------------------------------------------------
// Internal / Miscellaneous Vulkan Helpers
// (Used by example's main.cpp. Used by multi-viewport features. PROBABLY NOT used by your own engine/app.)
//-------------------------------------------------------------------------
// You probably do NOT need to use or care about those functions.
// Those functions only exist because:
// 1) they facilitate the readability and maintenance of the multiple main.cpp examples files.
// 2) the upcoming multi-viewport feature will need them internally.
// Generally we avoid exposing any kind of superfluous high-level helpers in the bindings,
// Generally we avoid exposing any kind of superfluous high-level helpers in the backends,
// but it is too much code to duplicate everywhere so we exceptionally expose them.
// Your application/engine will likely already have code to setup all that stuff (swap chain, render pass, frame buffers, etc.).
//
// Your engine/app will likely _already_ have code to setup all that stuff (swap chain, render pass, frame buffers, etc.).
// You may read this code to learn about Vulkan, but it is recommended you use you own custom tailored code to do equivalent work.
// (those functions do not interact with any of the state used by the regular ImGui_ImplVulkan_XXX functions)
// (The ImGui_ImplVulkanH_XXX functions do not interact with any of the state used by the regular ImGui_ImplVulkan_XXX functions)
//-------------------------------------------------------------------------
struct ImGui_ImplVulkanH_FrameData;
struct ImGui_ImplVulkanH_WindowData;
struct ImGui_ImplVulkanH_Frame;
struct ImGui_ImplVulkanH_Window;
IMGUI_IMPL_API void ImGui_ImplVulkanH_CreateWindowDataCommandBuffers(VkPhysicalDevice physical_device, VkDevice device, uint32_t queue_family, ImGui_ImplVulkanH_WindowData* wd, const VkAllocationCallbacks* allocator);
IMGUI_IMPL_API void ImGui_ImplVulkanH_CreateWindowDataSwapChainAndFramebuffer(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_WindowData* wd, const VkAllocationCallbacks* allocator, int w, int h);
IMGUI_IMPL_API void ImGui_ImplVulkanH_DestroyWindowData(VkInstance instance, VkDevice device, ImGui_ImplVulkanH_WindowData* wd, const VkAllocationCallbacks* allocator);
// Helpers
IMGUI_IMPL_API void ImGui_ImplVulkanH_CreateOrResizeWindow(VkInstance instance, VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wnd, uint32_t queue_family, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count);
IMGUI_IMPL_API void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui_ImplVulkanH_Window* wnd, const VkAllocationCallbacks* allocator);
IMGUI_IMPL_API VkSurfaceFormatKHR ImGui_ImplVulkanH_SelectSurfaceFormat(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkFormat* request_formats, int request_formats_count, VkColorSpaceKHR request_color_space);
IMGUI_IMPL_API VkPresentModeKHR ImGui_ImplVulkanH_SelectPresentMode(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkPresentModeKHR* request_modes, int request_modes_count);
IMGUI_IMPL_API int ImGui_ImplVulkanH_GetMinImageCountFromPresentMode(VkPresentModeKHR present_mode);
// Helper structure to hold the data needed by one rendering frame
struct ImGui_ImplVulkanH_FrameData
// (Used by example's main.cpp. Used by multi-viewport features. Probably NOT used by your own engine/app.)
// [Please zero-clear before use!]
struct ImGui_ImplVulkanH_Frame
{
uint32_t BackbufferIndex; // Keep track of recently rendered swapchain frame indices
VkCommandPool CommandPool;
VkCommandBuffer CommandBuffer;
VkFence Fence;
VkImage Backbuffer;
VkImageView BackbufferView;
VkFramebuffer Framebuffer;
};
struct ImGui_ImplVulkanH_FrameSemaphores
{
VkSemaphore ImageAcquiredSemaphore;
VkSemaphore RenderCompleteSemaphore;
IMGUI_IMPL_API ImGui_ImplVulkanH_FrameData();
};
// Helper structure to hold the data needed by one rendering context into one OS window
struct ImGui_ImplVulkanH_WindowData
// (Used by example's main.cpp. Used by multi-viewport features. Probably NOT used by your own engine/app.)
struct ImGui_ImplVulkanH_Window
{
int Width;
int Height;
@ -95,15 +138,19 @@ struct ImGui_ImplVulkanH_WindowData
VkSurfaceFormatKHR SurfaceFormat;
VkPresentModeKHR PresentMode;
VkRenderPass RenderPass;
VkPipeline Pipeline; // The window pipeline may uses a different VkRenderPass than the one passed in ImGui_ImplVulkan_InitInfo
bool ClearEnable;
VkClearValue ClearValue;
uint32_t BackBufferCount;
VkImage BackBuffer[16];
VkImageView BackBufferView[16];
VkFramebuffer Framebuffer[16];
uint32_t FrameIndex;
ImGui_ImplVulkanH_FrameData Frames[IMGUI_VK_QUEUED_FRAMES];
uint32_t FrameIndex; // Current frame being rendered to (0 <= FrameIndex < FrameInFlightCount)
uint32_t ImageCount; // Number of simultaneous in-flight frames (returned by vkGetSwapchainImagesKHR, usually derived from min_image_count)
uint32_t SemaphoreIndex; // Current set of swapchain wait semaphores we're using (needs to be distinct from per frame data)
ImGui_ImplVulkanH_Frame* Frames;
ImGui_ImplVulkanH_FrameSemaphores* FrameSemaphores;
IMGUI_IMPL_API ImGui_ImplVulkanH_WindowData();
ImGui_ImplVulkanH_Window()
{
memset((void*)this, 0, sizeof(*this));
PresentMode = (VkPresentModeKHR)~0; // Ensure we get an error if user doesn't set this.
ClearEnable = true;
}
};

View File

@ -128,7 +128,7 @@ void OITDrawer::DrawList(const vk::CommandBuffer& cmdBuffer, u32 listType, bool
}
template<bool Translucent>
void OITDrawer::DrawModifierVolumes(const vk::CommandBuffer& cmdBuffer, int first, int count)
void OITDrawer::DrawModifierVolumes(const vk::CommandBuffer& cmdBuffer, int first, int count, const ModifierVolumeParam *modVolParams)
{
if (count == 0 || pvrrc.modtrig.used() == 0 || !config::ModifierVolumes)
return;
@ -137,14 +137,14 @@ void OITDrawer::DrawModifierVolumes(const vk::CommandBuffer& cmdBuffer, int firs
cmdBuffer.bindVertexBuffers(0, 1, &buffer, &offsets.modVolOffset);
SetScissor(cmdBuffer, baseScissor);
ModifierVolumeParam* params = Translucent ? &pvrrc.global_param_mvo_tr.head()[first] : &pvrrc.global_param_mvo.head()[first];
const ModifierVolumeParam *params = &modVolParams[first];
int mod_base = -1;
vk::Pipeline pipeline;
for (int cmv = 0; cmv < count; cmv++)
{
ModifierVolumeParam& param = params[cmv];
const ModifierVolumeParam& param = params[cmv];
if (param.count == 0)
continue;
@ -322,7 +322,8 @@ bool OITDrawer::Draw(const Texture *fogTexture, const Texture *paletteTexture)
current_pass.pt_count - previous_pass.pt_count,
current_pass.tr_count - previous_pass.tr_count,
current_pass.mvo_count - previous_pass.mvo_count,
current_pass.mvo_tr_count - previous_pass.mvo_tr_count, current_pass.autosort);
current_pass.mv_op_tr_shared ? current_pass.mvo_count - previous_pass.mvo_count : current_pass.mvo_tr_count - previous_pass.mvo_tr_count,
current_pass.autosort);
// Reset the pixel counter
oitBuffers->ResetPixelCounter(cmdBuffer);
@ -351,7 +352,7 @@ bool OITDrawer::Draw(const Texture *fogTexture, const Texture *paletteTexture)
DrawList(cmdBuffer, ListType_Opaque, false, Pass::Depth, pvrrc.global_param_op, previous_pass.op_count, current_pass.op_count);
DrawList(cmdBuffer, ListType_Punch_Through, false, Pass::Depth, pvrrc.global_param_pt, previous_pass.pt_count, current_pass.pt_count);
DrawModifierVolumes<false>(cmdBuffer, previous_pass.mvo_count, current_pass.mvo_count - previous_pass.mvo_count);
DrawModifierVolumes<false>(cmdBuffer, previous_pass.mvo_count, current_pass.mvo_count - previous_pass.mvo_count, pvrrc.global_param_mvo.head());
// Color subpass
cmdBuffer.nextSubpass(vk::SubpassContents::eInline);
@ -398,7 +399,12 @@ bool OITDrawer::Draw(const Texture *fogTexture, const Texture *paletteTexture)
}
// Tr modifier volumes
if (GetContext()->GetVendorID() != VulkanContext::VENDOR_QUALCOMM) // Adreno bug
DrawModifierVolumes<true>(cmdBuffer, previous_pass.mvo_tr_count, current_pass.mvo_tr_count - previous_pass.mvo_tr_count);
{
if (current_pass.mv_op_tr_shared)
DrawModifierVolumes<true>(cmdBuffer, previous_pass.mvo_count, current_pass.mvo_count - previous_pass.mvo_count, pvrrc.global_param_mvo.head());
else
DrawModifierVolumes<true>(cmdBuffer, previous_pass.mvo_tr_count, current_pass.mvo_tr_count - previous_pass.mvo_tr_count, pvrrc.global_param_mvo_tr.head());
}
vk::Pipeline pipeline = pipelineManager->GetFinalPipeline();
cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
@ -477,7 +483,7 @@ void OITScreenDrawer::MakeFramebuffers(const vk::Extent2D& viewport)
finalColorAttachments.clear();
transitionNeeded.clear();
clearNeeded.clear();
while (finalColorAttachments.size() < GetContext()->GetSwapChainSize())
while (finalColorAttachments.size() < GetSwapChainSize())
{
finalColorAttachments.push_back(std::unique_ptr<FramebufferAttachment>(
new FramebufferAttachment(GetContext()->GetPhysicalDevice(), GetContext()->GetDevice())));
@ -582,7 +588,7 @@ vk::CommandBuffer OITTextureDrawer::NewFrame()
depthAttachments[0]->GetImageView(),
depthAttachments[1]->GetImageView(),
};
framebuffers.resize(GetContext()->GetSwapChainSize());
framebuffers.resize(GetSwapChainSize());
framebuffers[GetCurrentImage()] = device.createFramebufferUnique(vk::FramebufferCreateInfo(vk::FramebufferCreateFlags(),
rttPipelineManager->GetRenderPass(true, true), ARRAY_SIZE(imageViews), imageViews, widthPow2, heightPow2, 1));

View File

@ -45,6 +45,7 @@ public:
virtual void EndFrame() { renderPass++; };
protected:
u32 GetSwapChainSize() { return 2; }
void Init(SamplerManager *samplerManager, OITPipelineManager *pipelineManager, OITBuffers *oitBuffers)
{
this->pipelineManager = pipelineManager;
@ -76,13 +77,13 @@ protected:
void NewImage()
{
descriptorSets.nextFrame();
imageIndex = (imageIndex + 1) % GetContext()->GetSwapChainSize();
imageIndex = (imageIndex + 1) % GetSwapChainSize();
renderPass = 0;
}
BufferData* GetMainBuffer(u32 size)
{
u32 bufferIndex = imageIndex + renderPass * GetContext()->GetSwapChainSize();
u32 bufferIndex = imageIndex + renderPass * GetSwapChainSize();
while (mainBuffers.size() <= bufferIndex)
{
mainBuffers.push_back(std::unique_ptr<BufferData>(new BufferData(std::max(512 * 1024u, size),
@ -118,7 +119,7 @@ private:
void DrawList(const vk::CommandBuffer& cmdBuffer, u32 listType, bool sortTriangles, Pass pass,
const List<PolyParam>& polys, u32 first, u32 last);
template<bool Translucent>
void DrawModifierVolumes(const vk::CommandBuffer& cmdBuffer, int first, int count);
void DrawModifierVolumes(const vk::CommandBuffer& cmdBuffer, int first, int count, const ModifierVolumeParam *modVolParams);
void UploadMainBuffer(const OITDescriptorSets::VertexShaderUniforms& vertexUniforms,
const OITDescriptorSets::FragmentShaderUniforms& fragmentUniforms);

View File

@ -54,6 +54,7 @@ public:
if ((u32)w == viewport.width && (u32)h == viewport.height)
return;
BaseVulkanRenderer::Resize(w, h);
GetContext()->WaitIdle();
screenDrawer.Init(&samplerManager, &oitShaderManager, &oitBuffers, viewport);
}

View File

@ -52,7 +52,7 @@ void VulkanOverlay::Term()
xhairTexture.reset();
}
std::unique_ptr<Texture> VulkanOverlay::createTexture(vk::CommandBuffer commandBuffer, int width, int height, u8 *data)
std::unique_ptr<Texture> VulkanOverlay::createTexture(vk::CommandBuffer commandBuffer, int width, int height, const u8 *data)
{
auto texture = std::unique_ptr<Texture>(new Texture());
texture->tex_type = TextureType::_8888;
@ -132,6 +132,8 @@ void VulkanOverlay::Draw(vk::CommandBuffer commandBuffer, vk::Extent2D viewport,
vmu_width *= 2.f;
float blendConstants[4] = { 0.75f, 0.75f, 0.75f, 0.75f };
color = blendConstants;
#else
vmu_width /= config::ScreenStretching / 100.f;
#endif
for (size_t i = 0; i < vmuTextures.size(); i++)
@ -207,8 +209,9 @@ void VulkanOverlay::Draw(vk::CommandBuffer commandBuffer, vk::Extent2D viewport,
std::tie(x, y) = getCrosshairPosition(i);
#ifdef LIBRETRO
float w = LIGHTGUN_CROSSHAIR_SIZE * scaling;
float w = LIGHTGUN_CROSSHAIR_SIZE * scaling / config::ScreenStretching * 100.f;
float h = LIGHTGUN_CROSSHAIR_SIZE * scaling;
x /= config::ScreenStretching / 100.f;
#else
float w = XHAIR_WIDTH * scaling;
float h = XHAIR_HEIGHT * scaling;

View File

@ -41,7 +41,7 @@ public:
void Draw(vk::CommandBuffer commandBuffer, vk::Extent2D viewport, float scaling, bool vmu, bool crosshair);
private:
std::unique_ptr<Texture> createTexture(vk::CommandBuffer commandBuffer, int width, int height, u8 *data);
std::unique_ptr<Texture> createTexture(vk::CommandBuffer commandBuffer, int width, int height, const u8 *data);
std::array<std::unique_ptr<Texture>, 8> vmuTextures;
std::vector<vk::UniqueCommandBuffer> commandBuffers;

View File

@ -145,7 +145,7 @@ void setImageLayout(vk::CommandBuffer const& commandBuffer, vk::Image image, vk:
commandBuffer.pipelineBarrier(sourceStage, destinationStage, {}, nullptr, nullptr, imageMemoryBarrier);
}
void Texture::UploadToGPU(int width, int height, u8 *data, bool mipmapped, bool mipmapsIncluded)
void Texture::UploadToGPU(int width, int height, const u8 *data, bool mipmapped, bool mipmapsIncluded)
{
vk::Format format = vk::Format::eUndefined;
u32 dataSize = width * height * 2;
@ -249,7 +249,7 @@ void Texture::CreateImage(vk::ImageTiling tiling, const vk::ImageUsageFlags& usa
imageView = device.createImageViewUnique(imageViewCreateInfo);
}
void Texture::SetImage(u32 srcSize, void *srcData, bool isNew, bool genMipmaps)
void Texture::SetImage(u32 srcSize, const void *srcData, bool isNew, bool genMipmaps)
{
verify((bool)commandBuffer);

View File

@ -55,7 +55,7 @@ public:
std::swap(device, other.device);
}
void UploadToGPU(int width, int height, u8 *data, bool mipmapped, bool mipmapsIncluded = false) override;
void UploadToGPU(int width, int height, const u8 *data, bool mipmapped, bool mipmapsIncluded = false) override;
u64 GetIntId() { return (u64)reinterpret_cast<uintptr_t>(this); }
std::string GetId() override { char s[20]; sprintf(s, "%p", this); return s; }
vk::ImageView GetImageView() const { return *imageView; }
@ -65,7 +65,7 @@ public:
private:
void Init(u32 width, u32 height, vk::Format format ,u32 dataSize, bool mipmapped, bool mipmapsIncluded);
void SetImage(u32 size, void *data, bool isNew, bool genMipmaps);
void SetImage(u32 size, const void *data, bool isNew, bool genMipmaps);
void CreateImage(vk::ImageTiling tiling, const vk::ImageUsageFlags& usage, vk::ImageLayout initialLayout,
const vk::ImageAspectFlags& aspectMask);
void GenerateMipmaps();

View File

@ -32,6 +32,7 @@
#include "emulator.h"
#include "oslib/oslib.h"
#include "vulkan_driver.h"
#include "rend/transform_matrix.h"
void ReInitOSD();
@ -278,8 +279,9 @@ bool VulkanContext::InitInstance(const char** extensions, uint32_t extensions_co
void VulkanContext::InitImgui()
{
imguiDriver.reset();
imguiDriver = std::unique_ptr<ImGuiDriver>(new VulkanDriver());
ImGui_ImplVulkan_InitInfo initInfo = {};
ImGui_ImplVulkan_InitInfo initInfo{};
initInfo.Instance = (VkInstance)*instance;
initInfo.PhysicalDevice = (VkPhysicalDevice)physicalDevice;
initInfo.Device = (VkDevice)*device;
@ -287,11 +289,13 @@ void VulkanContext::InitImgui()
initInfo.Queue = (VkQueue)graphicsQueue;
initInfo.PipelineCache = (VkPipelineCache)*pipelineCache;
initInfo.DescriptorPool = (VkDescriptorPool)*descriptorPool;
initInfo.MinImageCount = 2;
initInfo.ImageCount = GetSwapChainSize();
#ifdef VK_DEBUG
initInfo.CheckVkResultFn = &CheckImGuiResult;
#endif
if (!ImGui_ImplVulkan_Init(&initInfo, (VkRenderPass)*renderPass, 0))
if (!ImGui_ImplVulkan_Init(&initInfo, (VkRenderPass)*renderPass))
{
die("ImGui initialization failed");
}
@ -308,7 +312,7 @@ void VulkanContext::InitImgui()
graphicsQueue.submit(1, &submitInfo, *drawFences.front());
device->waitIdle();
ImGui_ImplVulkan_InvalidateFontUploadObjects();
ImGui_ImplVulkan_DestroyFontUploadObjects();
}
}
@ -749,13 +753,21 @@ bool VulkanContext::init()
return InitDevice();
}
void VulkanContext::NewFrame()
bool VulkanContext::recreateSwapChainIfNeeded()
{
if (resized || HasSurfaceDimensionChanged())
{
CreateSwapChain();
lastFrameView = vk::ImageView();
return true;
}
else
return false;
}
void VulkanContext::NewFrame()
{
recreateSwapChainIfNeeded();
if (!IsValid())
throw InvalidVulkanContext();
device->acquireNextImageKHR(*swapChain, UINT64_MAX, *imageAcquiredSemaphores[currentSemaphore], nullptr, &currentImage);
@ -850,14 +862,18 @@ void VulkanContext::DrawFrame(vk::ImageView imageView, const vk::Extent2D& exten
else
quadPipeline->BindPipeline(commandBuffer);
float marginWidth;
if (config::Rotate90)
marginWidth = ((float)width - (float)extent.height / extent.width * height) / 2.f;
float renderAR = getOutputFramebufferAspectRatio();
float screenAR = (float)width / height;
float dx = 0;
float dy = 0;
if (renderAR > screenAR)
dy = height * (1 - screenAR / renderAR) / 2;
else
marginWidth = ((float)width - (float)extent.width / extent.height * height) / 2.f;
vk::Viewport viewport(marginWidth, 0, width - marginWidth * 2.f, height);
dx = width * (1 - renderAR / screenAR) / 2;
vk::Viewport viewport(dx, dy, width - dx * 2, height - dy * 2);
commandBuffer.setViewport(0, 1, &viewport);
commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(std::max(0.f, marginWidth), 0), vk::Extent2D(width - marginWidth * 2.f, height)));
commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(dx, dy), vk::Extent2D(width - dx * 2, height - dy * 2)));
if (config::Rotate90)
quadRotateDrawer->Draw(commandBuffer, imageView, vtx, config::TextureFiltering == 1);
else
@ -1174,27 +1190,3 @@ VulkanContext::~VulkanContext()
verify(contextInstance == this);
contextInstance = nullptr;
}
void ImGui_ImplVulkan_RenderDrawData(ImDrawData *draw_data)
{
VulkanContext *context = VulkanContext::Instance();
if (!context->IsValid())
return;
try {
bool rendering = context->IsRendering();
vk::CommandBuffer vmuCmdBuffer;
if (!rendering)
{
context->NewFrame();
vmuCmdBuffer = context->PrepareOverlay(true, false);
context->BeginRenderPass();
context->PresentLastFrame();
context->DrawOverlay(settings.display.uiScale, true, false);
}
// Record Imgui Draw Data and draw funcs into command buffer
ImGui_ImplVulkan_RenderDrawData(draw_data, (VkCommandBuffer)context->GetCurrentCommandBuffer());
if (!rendering)
context->EndFrame(vmuCmdBuffer);
} catch (const InvalidVulkanContext& err) {
}
}

View File

@ -41,8 +41,6 @@ public:
struct ImDrawData;
class TextureCache;
void ImGui_ImplVulkan_RenderDrawData(ImDrawData *draw_data);
static vk::Format findDepthFormat(vk::PhysicalDevice physicalDevice);
class VulkanContext : public GraphicsContext
{
@ -96,7 +94,6 @@ public:
vk::Format GetColorFormat() const { return colorFormat; }
vk::Format GetDepthFormat() const { return depthFormat; }
static VulkanContext *Instance() { return contextInstance; }
bool SupportsFragmentShaderStoresAndAtomics() const { return fragmentStoresAndAtomics; }
bool SupportsSamplerAnisotropy() const { return samplerAnisotropy; }
float GetMaxSamplerAnisotropy() const { return samplerAnisotropy ? maxSamplerAnisotropy : 1.f; }
bool SupportsDedicatedAllocation() const { return dedicatedAllocationSupported; }
@ -112,6 +109,7 @@ public:
vk::SubmitInfo(0, nullptr, nullptr, bufferCount, buffers), fence);
}
bool hasPerPixel() override { return fragmentStoresAndAtomics; }
bool recreateSwapChainIfNeeded();
#ifdef VK_DEBUG
void setObjectName(u64 object, VkDebugReportObjectTypeEXT objectType, const std::string& name)

View File

@ -20,21 +20,125 @@
#include "rend/imgui_driver.h"
#include "imgui_impl_vulkan.h"
#include "vulkan_context.h"
#include "texture.h"
#include <unordered_map>
class VulkanDriver final : public ImGuiDriver
{
public:
~VulkanDriver() {
textures.clear();
linearSampler.reset();
ImGui_ImplVulkan_Shutdown();
}
void newFrame() override {
}
void renderDrawData(ImDrawData *drawData) override {
ImGui_ImplVulkan_RenderDrawData(drawData);
void renderDrawData(ImDrawData *drawData) override
{
VulkanContext *context = getContext();
if (!context->IsValid())
return;
try {
bool rendering = context->IsRendering();
if (!rendering)
{
if (context->recreateSwapChainIfNeeded())
return;
context->NewFrame();
}
vk::CommandBuffer vmuCmdBuffer{};
if (!rendering || newFrameStarted)
{
vmuCmdBuffer = getContext()->PrepareOverlay(true, false);
context->BeginRenderPass();
context->PresentLastFrame();
context->DrawOverlay(settings.display.uiScale, true, false);
}
if (!justStarted)
// Record Imgui Draw Data and draw funcs into command buffer
ImGui_ImplVulkan_RenderDrawData(drawData, (VkCommandBuffer)getCommandBuffer());
justStarted = false;
if (!rendering || newFrameStarted)
context->EndFrame(vmuCmdBuffer);
newFrameStarted = false;
} catch (const InvalidVulkanContext& err) {
}
}
void present() override {
VulkanContext::Instance()->Present();
getContext()->Present(); // may destroy this driver
}
ImTextureID getTexture(const std::string& name) override {
auto it = textures.find(name);
if (it != textures.end())
return it->second.textureId;
else
return ImTextureID{};
}
ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height) override
{
VkTexture vkTex(std::unique_ptr<Texture>(new Texture()));
vkTex.texture->tex_type = TextureType::_8888;
vkTex.texture->SetCommandBuffer(getCommandBuffer());
vkTex.texture->UploadToGPU(width, height, data, false);
vkTex.texture->SetCommandBuffer(nullptr);
if (!linearSampler)
{
linearSampler = getContext()->GetDevice().createSamplerUnique(
vk::SamplerCreateInfo(vk::SamplerCreateFlags(),
vk::Filter::eLinear, vk::Filter::eLinear,
vk::SamplerMipmapMode::eLinear,
vk::SamplerAddressMode::eClampToBorder,
vk::SamplerAddressMode::eClampToBorder,
vk::SamplerAddressMode::eClampToEdge, 0.0f, false,
0.f, false, vk::CompareOp::eNever, 0.0f, VK_LOD_CLAMP_NONE,
vk::BorderColor::eFloatTransparentBlack));
}
ImTextureID texId = vkTex.textureId = ImGui_ImplVulkan_AddTexture((VkSampler)*linearSampler, (VkImageView)vkTex.texture->GetImageView(),
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
// TODO update existing texture
//auto it = textures.find(name);
//if (it != textures.end() && it->second.texture != nullptr)
// textureCache.DestroyLater(it->second.texture.get());
textures[name] = std::move(vkTex);
return texId;
}
private:
struct VkTexture {
VkTexture() = default;
VkTexture(std::unique_ptr<Texture>&& texture, ImTextureID textureId = ImTextureID())
: texture(std::move(texture)), textureId(textureId) {}
std::unique_ptr<Texture> texture;
ImTextureID textureId{};
};
VulkanContext *getContext() {
return VulkanContext::Instance();
}
vk::CommandBuffer getCommandBuffer()
{
VulkanContext *context = getContext();
if (!context->IsRendering())
{
if (context->recreateSwapChainIfNeeded())
throw InvalidVulkanContext();
context->NewFrame();
newFrameStarted = true;
}
return context->GetCurrentCommandBuffer();
}
std::unordered_map<std::string, VkTexture> textures;
vk::UniqueSampler linearSampler;
bool newFrameStarted = false;
bool justStarted = true;
};

View File

@ -45,6 +45,7 @@ public:
if ((u32)w == viewport.width && (u32)h == viewport.height)
return;
BaseVulkanRenderer::Resize(w, h);
GetContext()->WaitIdle();
screenDrawer.Init(&samplerManager, &shaderManager, viewport);
}

View File

@ -153,8 +153,6 @@ public:
void Resize(int w, int h) override
{
if ((u32)w == viewport.width && (u32)h == viewport.height)
return;
viewport.width = w;
viewport.height = h;
}

View File

@ -607,6 +607,24 @@ static void setClipboardText(void *, const char *text)
SDL_SetClipboardText(text);
}
#ifdef TARGET_UWP
static int suspendEventFilter(void *userdata, SDL_Event *event)
{
if (event->type == SDL_APP_WILLENTERBACKGROUND)
{
gui_save();
if (gameRunning)
{
emu.stop();
if (config::AutoSaveState)
dc_savestate(config::SavestateSlot);
}
return 0;
}
return 1;
}
#endif
void sdl_window_create()
{
if (SDL_WasInit(SDL_INIT_VIDEO) == 0)
@ -624,6 +642,10 @@ void sdl_window_create()
// ImGui copy & paste
ImGui::GetIO().GetClipboardTextFn = getClipboardText;
ImGui::GetIO().SetClipboardTextFn = setClipboardText;
#ifdef TARGET_UWP
// Must be fast so an event filter is required
SDL_SetEventFilter(suspendEventFilter, nullptr);
#endif
}
void sdl_window_destroy()

View File

@ -201,6 +201,10 @@ public:
return GamepadDevice::gamepad_axis_input(code, value);
}
u16 getRumbleIntensity(float power) {
return (u16)std::min(power * 65535.f / std::pow(1.06f, 100.f - rumblePower), 65535.f);
}
void rumble(float power, float inclination, u32 duration_ms) override
{
if (rumbleEnabled)
@ -208,7 +212,7 @@ public:
vib_inclination = inclination * power;
vib_stop_time = os_GetSeconds() + duration_ms / 1000.0;
Uint16 intensity = (Uint16)std::min(power * rumblePower * 65535.f / 100.f, 65535.f);
u16 intensity = getRumbleIntensity(power);
SDL_JoystickRumble(sdl_joystick, intensity, intensity, duration_ms);
}
}
@ -223,7 +227,7 @@ public:
vib_inclination = 0;
else
{
Uint16 intensity = (Uint16)std::min(vib_inclination * rem_time * 65535.f * rumblePower / 100.f, 65535.f);
u16 intensity = getRumbleIntensity(vib_inclination * rem_time);
SDL_JoystickRumble(sdl_joystick, intensity, intensity, rem_time);
}
}

View File

@ -144,6 +144,7 @@ enum HollyInterruptID
#include <stdio.h>
namespace nowide {
FILE *fopen(char const *file_name, char const *mode);
int remove(const char *pathname);
}
#endif

View File

@ -821,6 +821,16 @@ FILE *fopen(char const *file_name, char const *mode)
return _fdopen(fd, mode);
}
int remove(char const *name)
{
wstackstring wname;
if(!wname.convert(name)) {
errno = EINVAL;
return -1;
}
return _wremove(wname.c_str());
}
}
extern "C" int SDL_main(int argc, char* argv[])

View File

@ -3,11 +3,11 @@ package com.reicast.emulator;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@ -29,6 +29,7 @@ import androidx.core.app.ActivityCompat;
import com.reicast.emulator.config.Config;
import com.reicast.emulator.debug.GenerateLogs;
import com.reicast.emulator.emu.AudioBackend;
import com.reicast.emulator.emu.HttpClient;
import com.reicast.emulator.emu.JNIdc;
import com.reicast.emulator.periph.InputDeviceManager;
import com.reicast.emulator.periph.SipEmulator;
@ -42,6 +43,7 @@ import java.util.Locale;
import tv.ouya.console.api.OuyaController;
import static android.content.res.Configuration.HARDKEYBOARDHIDDEN_NO;
import static android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
@ -60,6 +62,7 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat.
private boolean paused = true;
private boolean resumedCalled = false;
private String pendingIntentUrl;
private boolean hasKeyboard = false;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -112,7 +115,7 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat.
}
Log.i("flycast", "Environment initialized");
installButtons();
new HttpClient().nativeInit();
setStorageDirectories();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !storagePermissionGranted) {
@ -133,6 +136,8 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat.
audioBackend = new AudioBackend();
onConfigurationChanged(getResources().getConfiguration());
// When viewing a resource, pass its URI to the native code for opening
Intent intent = getIntent();
if (intent.getAction() != null) {
@ -205,6 +210,12 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat.
}
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
hasKeyboard = newConfig.hardKeyboardHidden == HARDKEYBOARDHIDDEN_NO;
}
protected abstract void doPause();
protected abstract void doResume();
protected abstract boolean isSurfaceReady();
@ -290,6 +301,8 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat.
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (InputDeviceManager.getInstance().joystickButtonEvent(event.getDeviceId(), keyCode, false))
return true;
if (hasKeyboard && InputDeviceManager.getInstance().keyboardEvent(keyCode, false))
return true;
return super.onKeyUp(keyCode, event);
}
@ -309,6 +322,12 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat.
if (InputDeviceManager.getInstance().joystickButtonEvent(event.getDeviceId(), keyCode, true))
return true;
if (hasKeyboard) {
InputDeviceManager.getInstance().keyboardEvent(keyCode, true);
if (!event.isCtrlPressed() && (event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE))
InputDeviceManager.getInstance().keyboardText(event.getUnicodeChar());
return true;
}
if (ViewConfiguration.get(this).hasPermanentMenuKey()) {
if (keyCode == KeyEvent.KEYCODE_MENU) {
return showMenu();

View File

@ -1,21 +1,32 @@
package com.reicast.emulator;
import android.content.Context;
import android.os.Bundle;
import android.text.InputType;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.RelativeLayout;
import androidx.annotation.Nullable;
import com.reicast.emulator.emu.JNIdc;
import com.reicast.emulator.emu.NativeGLView;
import com.reicast.emulator.periph.InputDeviceManager;
import com.reicast.emulator.periph.VJoy;
public final class NativeGLActivity extends BaseGLActivity {
private static ViewGroup mLayout; // used for text input
private ViewGroup mLayout; // used for text input
private NativeGLView mView;
View mTextEdit;
private boolean mScreenKeyboardShown;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -77,4 +88,215 @@ public final class NativeGLActivity extends BaseGLActivity {
}
});
}
// On-screen keyboard borrowed from SDL core android code
class ShowTextInputTask implements Runnable {
/*
* This is used to regulate the pan&scan method to have some offset from
* the bottom edge of the input region and the top edge of an input
* method (soft keyboard)
*/
static final int HEIGHT_PADDING = 15;
public int x, y, w, h;
public ShowTextInputTask(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
/* Minimum size of 1 pixel, so it takes focus. */
if (this.w <= 0) {
this.w = 1;
}
if (this.h + HEIGHT_PADDING <= 0) {
this.h = 1 - HEIGHT_PADDING;
}
}
@Override
public void run() {
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING);
params.leftMargin = x;
params.topMargin = y;
if (mTextEdit == null) {
mTextEdit = new DummyEdit(getApplicationContext(), NativeGLActivity.this);
mLayout.addView(mTextEdit, params);
} else {
mTextEdit.setLayoutParams(params);
}
mTextEdit.setVisibility(View.VISIBLE);
mTextEdit.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(mTextEdit, 0);
mScreenKeyboardShown = true;
Log.d("flycast", "ShowTextInputTask: run");
}
}
// Called from native code
public void showTextInput(int x, int y, int w, int h) {
// Transfer the task to the main thread as a Runnable
handler.post(new ShowTextInputTask(x, y, w, h));
}
// Called from native code
public void hideTextInput() {
Log.d("flycast", "hideTextInput " + (mTextEdit != null ? "mTextEdit != null" : ""));
if (mTextEdit != null) {
mTextEdit.postDelayed(new Runnable() {
@Override
public void run() {
// Note: On some devices setting view to GONE creates a flicker in landscape.
// Setting the View's sizes to 0 is similar to GONE but without the flicker.
// The sizes will be set to useful values when the keyboard is shown again.
mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0));
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);
mScreenKeyboardShown = false;
mView.requestFocus();
}
}, 50);
}
}
// Called from native code
public boolean isScreenKeyboardShown() {
if (mTextEdit == null)
return false;
if (!mScreenKeyboardShown)
return false;
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
return imm.isAcceptingText();
}
}
/* This is a fake invisible editor view that receives the input and defines the
+ * pan&scan region
+ */
class DummyEdit extends View implements View.OnKeyListener {
InputConnection ic;
NativeGLActivity activity;
class InputConnection extends BaseInputConnection {
public InputConnection(boolean fullEditor) {
super(DummyEdit.this, fullEditor);
}
@Override
public boolean sendKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)
activity.hideTextInput();
return super.sendKeyEvent(event);
}
@Override
public boolean commitText(CharSequence text, int newCursorPosition) {
InputDeviceManager devManager = InputDeviceManager.getInstance();
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c == '\n') {
activity.hideTextInput();
return true;
}
devManager.keyboardText(c);
}
return super.commitText(text, newCursorPosition);
}
@Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
// Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection
// and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
if (beforeLength > 0 && afterLength == 0) {
Log.d("flycast", "deleteSurroundingText before=" + beforeLength);
KeyEvent downEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
downEvent.setSource(InputDevice.SOURCE_KEYBOARD);
KeyEvent upEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL);
upEvent.setSource(InputDevice.SOURCE_KEYBOARD);
boolean ret = true;
// backspace(s)
while (beforeLength-- > 0) {
boolean ret_key = sendKeyEvent(downEvent);
sendKeyEvent(upEvent);
ret = ret && ret_key;
}
return ret;
}
return super.deleteSurroundingText(beforeLength, afterLength);
}
}
public DummyEdit(Context context, NativeGLActivity activity) {
super(context);
this.activity = activity;
setFocusableInTouchMode(true);
setFocusable(true);
setOnKeyListener(this);
}
@Override
public boolean onCheckIsTextEditor() {
return true;
}
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
/*
* This handles the hardware keyboard input
*/
InputDeviceManager devManager = InputDeviceManager.getInstance();
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (!event.isCtrlPressed() && (event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE))
ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
else
devManager.keyboardEvent(event.getKeyCode(), true);
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
devManager.keyboardEvent(event.getKeyCode(), false);
return true;
}
return false;
}
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
// As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
// FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
// FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
// FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout
// FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
// FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
if (activity.mTextEdit != null && activity.mTextEdit.getVisibility() == View.VISIBLE) {
// activity.hideTextInput();
KeyEvent downEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER);
downEvent.setSource(InputDevice.SOURCE_KEYBOARD);
ic.sendKeyEvent(downEvent);
KeyEvent upEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER);
upEvent.setSource(InputDevice.SOURCE_KEYBOARD);
ic.sendKeyEvent(upEvent);
}
}
return super.onKeyPreIme(keyCode, event);
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
| EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
ic = new InputConnection(true);
return ic;
}
}

View File

@ -0,0 +1,67 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
package com.reicast.emulator.emu;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class HttpClient {
// Called from native code
public int openUrl(String url_string, byte[][] content, String[] contentType)
{
try {
URL url = new URL(url_string);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.connect();
if (conn.getResponseCode() >= 200 && conn.getResponseCode() < 300) {
InputStream is = conn.getInputStream();
byte[] buffer = new byte[1024];
int length;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((length = is.read(buffer)) > 0) {
baos.write(buffer, 0, length);
}
is.close();
baos.close();
content[0] = baos.toByteArray();
if (contentType != null)
contentType[0] = conn.getContentType();
}
return conn.getResponseCode();
} catch (MalformedURLException e) {
Log.e("flycast", "Malformed URL", e);
} catch (IOException e) {
Log.e("flycast", "I/O error", e);
} catch (SecurityException e) {
Log.e("flycast", "Security error", e);
}
return 500;
}
public native void nativeInit();
}

View File

@ -113,4 +113,6 @@ public final class InputDeviceManager implements InputManager.InputDeviceListene
public native void mouseScrollEvent(int scrollValue);
private native void joystickAdded(int id, String name, int maple_port, String uniqueId, int fullAxes[], int halfAxes[]);
private native void joystickRemoved(int id);
public native boolean keyboardEvent(int key, boolean pressed);
public native void keyboardText(int c);
}

View File

@ -75,6 +75,8 @@ private:
static thread_local JVMAttacher jvm_attacher;
#include "android_gamepad.h"
#include "android_keyboard.h"
#include "http_client.h"
extern "C" JNIEXPORT jint JNICALL Java_com_reicast_emulator_emu_JNIdc_getVirtualGamepadVibration(JNIEnv *env, jobject obj)
{
@ -88,6 +90,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_JNIdc_screenChar
}
std::shared_ptr<AndroidMouse> mouse;
std::shared_ptr<AndroidKeyboard> keyboard;
float vjoy_pos[15][8];
@ -273,6 +276,15 @@ jmethodID audioInitMid;
jmethodID audioTermMid;
static jobject g_audioBackend;
// Activity
static jobject g_activity;
static jmethodID VJoyStartEditingMID;
static jmethodID VJoyStopEditingMID;
static jmethodID VJoyResetEditingMID;
static jmethodID showTextInputMid;
static jmethodID hideTextInputMid;
static jmethodID isScreenKeyboardShownMid;
extern "C" JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_JNIdc_setupMic(JNIEnv *env,jobject obj,jobject sip)
{
sipemu = env->NewGlobalRef(sip);
@ -290,6 +302,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_JNIdc_pause(JNIE
if (config::AutoSaveState)
dc_savestate(config::SavestateSlot);
}
gui_save();
}
extern "C" JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_JNIdc_resume(JNIEnv *env,jobject obj)
@ -510,6 +523,19 @@ extern "C" JNIEXPORT void JNICALL Java_com_reicast_emulator_periph_InputDeviceMa
// FIXME Don't connect it by default or any screen touch will register as button A press
mouse = std::make_shared<AndroidMouse>(-1);
GamepadDevice::Register(mouse);
keyboard = std::make_shared<AndroidKeyboard>();
GamepadDevice::Register(keyboard);
gui_setOnScreenKeyboardCallback([](bool show) {
JNIEnv *env = jvm_attacher.getEnv();
if (show != env->CallBooleanMethod(g_activity, isScreenKeyboardShownMid))
{
INFO_LOG(INPUT, "show/hide keyboard %d", show);
if (show)
env->CallVoidMethod(g_activity, showTextInputMid, 0, 0, 16, 100);
else
env->CallVoidMethod(g_activity, hideTextInputMid);
}
});
}
extern "C" JNIEXPORT void JNICALL Java_com_reicast_emulator_periph_InputDeviceManager_joystickAdded(JNIEnv *env, jobject obj, jint id, jstring name,
@ -554,6 +580,18 @@ extern "C" JNIEXPORT jboolean JNICALL Java_com_reicast_emulator_periph_InputDevi
}
extern "C" JNIEXPORT jboolean JNICALL Java_com_reicast_emulator_periph_InputDeviceManager_keyboardEvent(JNIEnv *env, jobject obj, jint key, jboolean pressed)
{
keyboard->keyboard_input(key, pressed);
return true;
}
extern "C" JNIEXPORT void JNICALL Java_com_reicast_emulator_periph_InputDeviceManager_keyboardText(JNIEnv *env, jobject obj, jint c)
{
gui_keyboard_input((u16)c);
}
static std::map<std::pair<jint, jint>, jint> previous_axis_values;
extern "C" JNIEXPORT jboolean JNICALL Java_com_reicast_emulator_periph_InputDeviceManager_joystickAxisEvent(JNIEnv *env, jobject obj, jint id, jint key, jint value)
@ -578,11 +616,6 @@ extern "C" JNIEXPORT void JNICALL Java_com_reicast_emulator_periph_InputDeviceMa
mouse->setWheel(scrollValue);
}
static jobject g_activity;
static jmethodID VJoyStartEditingMID;
static jmethodID VJoyStopEditingMID;
static jmethodID VJoyResetEditingMID;
extern "C" JNIEXPORT void JNICALL Java_com_reicast_emulator_BaseGLActivity_register(JNIEnv *env, jobject obj, jobject activity)
{
if (g_activity != NULL)
@ -595,6 +628,9 @@ extern "C" JNIEXPORT void JNICALL Java_com_reicast_emulator_BaseGLActivity_regis
VJoyStartEditingMID = env->GetMethodID(env->GetObjectClass(activity), "VJoyStartEditing", "()V");
VJoyStopEditingMID = env->GetMethodID(env->GetObjectClass(activity), "VJoyStopEditing", "(Z)V");
VJoyResetEditingMID = env->GetMethodID(env->GetObjectClass(activity), "VJoyResetEditing", "()V");
showTextInputMid = env->GetMethodID(env->GetObjectClass(activity), "showTextInput", "(IIII)V");
hideTextInputMid = env->GetMethodID(env->GetObjectClass(activity), "hideTextInput", "()V");
isScreenKeyboardShownMid = env->GetMethodID(env->GetObjectClass(activity), "isScreenKeyboardShown", "()Z");
}
}

View File

@ -0,0 +1,581 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "input/keyboard_device.h"
const u8 AndroidKeycodes[] {
/** Unknown key code. */
0,
/** Soft Left key.
* Usually situated below the display on phones and used as a multi-function
* feature key for selecting a software defined function shown on the bottom left
* of the display. */
0,
/** Soft Right key.
* Usually situated below the display on phones and used as a multi-function
* feature key for selecting a software defined function shown on the bottom right
* of the display. */
0,
/** Home key.
* This key is handled by the framework and is never delivered to applications. */
0,
/** Back key. */
0,
/** Call key. */
0,
/** End Call key. */
0,
/** '0' key. */
39,
/** '1' key. */
30,
/** '2' key. */
31,
/** '3' key. */
32,
/** '4' key. */
33,
/** '5' key. */
34,
/** '6' key. */
35,
/** '7' key. */
36,
/** '8' key. */
37,
/** '9' key. */
38,
/** '*' key. */
85, // Keypad *
/** '#' key. */
0,
/** Directional Pad Up key.
* May also be synthesized from trackball motions. */
82,
/** Directional Pad Down key.
* May also be synthesized from trackball motions. */
81,
/** Directional Pad Left key.
* May also be synthesized from trackball motions. */
80,
/** Directional Pad Right key.
* May also be synthesized from trackball motions. */
79,
/** Directional Pad Center key.
* May also be synthesized from trackball motions. */
0,
/** Volume Up key.
* Adjusts the speaker volume up. */
0,
/** Volume Down key.
* Adjusts the speaker volume down. */
0,
/** Power key. */
0,
/** Camera key.
* Used to launch a camera application or take pictures. */
0,
/** Clear key. */
0,
/** 'A' key. */
4,
/** 'B' key. */
5,
/** 'C' key. */
6,
/** 'D' key. */
7,
/** 'E' key. */
8,
/** 'F' key. */
9,
/** 'G' key. */
10,
/** 'H' key. */
11,
/** 'I' key. */
12,
/** 'J' key. */
13,
/** 'K' key. */
14,
/** 'L' key. */
15,
/** 'M' key. */
16,
/** 'N' key. */
17,
/** 'O' key. */
18,
/** 'P' key. */
19,
/** 'Q' key. */
20,
/** 'R' key. */
21,
/** 'S' key. */
22,
/** 'T' key. */
23,
/** 'U' key. */
24,
/** 'V' key. */
25,
/** 'W' key. */
26,
/** 'X' key. */
27,
/** 'Y' key. */
28,
/** 'Z' key. */
29,
/** ',' key. */
54,
/** '.' key. */
55,
/** Left Alt modifier key. */
226,
/** Right Alt modifier key. */
230,
/** Left Shift modifier key. */
225,
/** Right Shift modifier key. */
229,
/** Tab key. */
43,
/** Space key. */
44,
/** Symbol modifier key.
* Used to enter alternate symbols. */
0,
/** Explorer special function key.
* Used to launch a browser application. */
0,
/** Envelope special function key.
* Used to launch a mail application. */
0,
/** Enter key. */
40,
/** Backspace key.
* Deletes characters before the insertion point, unlike {@link AKEYCODE_FORWARD_DEL}. */
42,
/** '`' (backtick) key. */
53,
/** '-'. */
45,
/** '=' key. */
46,
/** '[' key. */
47,
/** ']' key. */
48,
/** '\' key. */
49,
/** ';' key. */
51,
/** ''' (apostrophe) key. */
52,
/** '/' key. */
56,
/** '@' key. */
0,
/** Number modifier key.
* Used to enter numeric symbols.
* This key is not {@link AKEYCODE_NUM_LOCK}; it is more like {@link AKEYCODE_ALT_LEFT}. */
83,
/** Headset Hook key.
* Used to hang up calls and stop media. */
0,
/** Camera Focus key.
* Used to focus the camera. */
0,
/** '+' key. */
87, // Keypad +
/** Menu key. */
118,
/** Notification key. */
0,
/** Search key. */
0,
/** Play/Pause media key. */
0,
/** Stop media key. */
0,
/** Play Next media key. */
0,
/** Play Previous media key. */
0,
/** Rewind media key. */
0,
/** Fast Forward media key. */
0,
/** Mute key.
* Mutes the microphone, unlike {@link AKEYCODE_VOLUME_MUTE}. */
0,
/** Page Up key. */
75,
/** Page Down key. */
78,
/** Picture Symbols modifier key.
* Used to switch symbol sets (Emoji, Kao-moji). */
0,
/** Switch Charset modifier key.
* Used to switch character sets (Kanji, Katakana). */
0,
/** A Button key.
* On a game controller, the A button should be either the button labeled A
* or the first button on the bottom row of controller buttons. */
0,
/** B Button key.
* On a game controller, the B button should be either the button labeled B
* or the second button on the bottom row of controller buttons. */
0,
/** C Button key.
* On a game controller, the C button should be either the button labeled C
* or the third button on the bottom row of controller buttons. */
0,
/** X Button key.
* On a game controller, the X button should be either the button labeled X
* or the first button on the upper row of controller buttons. */
0,
/** Y Button key.
* On a game controller, the Y button should be either the button labeled Y
* or the second button on the upper row of controller buttons. */
0,
/** Z Button key.
* On a game controller, the Z button should be either the button labeled Z
* or the third button on the upper row of controller buttons. */
0,
/** L1 Button key.
* On a game controller, the L1 button should be either the button labeled L1 (or L)
* or the top left trigger button. */
0,
/** R1 Button key.
* On a game controller, the R1 button should be either the button labeled R1 (or R)
* or the top right trigger button. */
0,
/** L2 Button key.
* On a game controller, the L2 button should be either the button labeled L2
* or the bottom left trigger button. */
0,
/** R2 Button key.
* On a game controller, the R2 button should be either the button labeled R2
* or the bottom right trigger button. */
0,
/** Left Thumb Button key.
* On a game controller, the left thumb button indicates that the left (or only)
* joystick is pressed. */
0,
/** Right Thumb Button key.
* On a game controller, the right thumb button indicates that the right
* joystick is pressed. */
0,
/** Start Button key.
* On a game controller, the button labeled Start. */
0,
/** Select Button key.
* On a game controller, the button labeled Select. */
0,
/** Mode Button key.
* On a game controller, the button labeled Mode. */
0,
/** Escape key. */
41,
/** Forward Delete key.
* Deletes characters ahead of the insertion point, unlike {@link AKEYCODE_DEL}. */
76,
/** Left Control modifier key. */
224,
/** Right Control modifier key. */
228,
/** Caps Lock key. */
57,
/** Scroll Lock key. */
71,
/** Left Meta modifier key. */
228,
/** Right Meta modifier key. */
231,
/** Function modifier key. */
0,
/** System Request / Print Screen key. */
154,
/** Break / Pause key. */
72,
/** Home Movement key.
* Used for scrolling or moving the cursor around to the start of a line
* or to the top of a list. */
74,
/** End Movement key.
* Used for scrolling or moving the cursor around to the end of a line
* or to the bottom of a list. */
77,
/** Insert key.
* Toggles insert / overwrite edit mode. */
73,
/** Forward key.
* Navigates forward in the history stack. Complement of {@link AKEYCODE_BACK}. */
0,
/** Play media key. */
0,
/** Pause media key. */
0,
/** Close media key.
* May be used to close a CD tray, for example. */
0,
/** Eject media key.
* May be used to eject a CD tray, for example. */
0,
/** Record media key. */
0,
/** F1 key. */
58,
/** F2 key. */
59,
/** F3 key. */
60,
/** F4 key. */
61,
/** F5 key. */
62,
/** F6 key. */
63,
/** F7 key. */
64,
/** F8 key. */
65,
/** F9 key. */
66,
/** F10 key. */
67,
/** F11 key. */
68,
/** F12 key. */
69,
/** Num Lock key.
* This is the Num Lock key; it is different from {@link AKEYCODE_NUM}.
* This key alters the behavior of other keys on the numeric keypad. */
83,
/** Numeric keypad '0' key. */
98,
/** Numeric keypad '1' key. */
89,
/** Numeric keypad '2' key. */
90,
/** Numeric keypad '3' key. */
91,
/** Numeric keypad '4' key. */
92,
/** Numeric keypad '5' key. */
93,
/** Numeric keypad '6' key. */
94,
/** Numeric keypad '7' key. */
95,
/** Numeric keypad '8' key. */
96,
/** Numeric keypad '9' key. */
97,
/** Numeric keypad '/' key (for division). */
84,
/** Numeric keypad '*' key (for multiplication). */
85,
/** Numeric keypad '-' key (for subtraction). */
86,
/** Numeric keypad '+' key (for addition). */
87,
/** Numeric keypad '.' key (for decimals or digit grouping). */
99, // Keypad period
/** Numeric keypad ',' key (for decimals or digit grouping). */
0,
/** Numeric keypad Enter key. */
88,
/** Numeric keypad '=' key. */
0,
/** Numeric keypad '(' key. */
0,
/** Numeric keypad ')' key. */
0,
/** Volume Mute key.
* Mutes the speaker, unlike {@link AKEYCODE_MUTE}.
* This key should normally be implemented as a toggle such that the first press
* mutes the speaker and the second press restores the original volume. */
0,
/** Info key.
* Common on TV remotes to show additional information related to what is
* currently being viewed. */
0,
/** Channel up key.
* On TV remotes, increments the television channel. */
0,
/** Channel down key.
* On TV remotes, decrements the television channel. */
0,
/** Zoom in key. */
0,
/** Zoom out key. */
0,
/** TV key.
* On TV remotes, switches to viewing live TV. */
0,
/** Window key.
* On TV remotes, toggles picture-in-picture mode or other windowing functions. */
0,
/** Guide key.
* On TV remotes, shows a programming guide. */
0,
/** DVR key.
* On some TV remotes, switches to a DVR mode for recorded shows. */
0,
/** Bookmark key.
* On some TV remotes, bookmarks content or web pages. */
0,
/** Toggle captions key.
* Switches the mode for closed-captioning text, for example during television shows. */
0,
/** Settings key.
* Starts the system settings activity. */
0,
/** TV power key.
* On TV remotes, toggles the power on a television screen. */
0,
/** TV input key.
* On TV remotes, switches the input on a television screen. */
0,
/** Set-top-box power key.
* On TV remotes, toggles the power on an external Set-top-box. */
0,
/** Set-top-box input key.
* On TV remotes, switches the input mode on an external Set-top-box. */
0,
/** A/V Receiver power key.
* On TV remotes, toggles the power on an external A/V Receiver. */
0,
/** A/V Receiver input key.
* On TV remotes, switches the input mode on an external A/V Receiver. */
0,
/** Red "programmable" key.
* On TV remotes, acts as a contextual/programmable key. */
0,
/** Green "programmable" key.
* On TV remotes, actsas a contextual/programmable key. */
0,
/** Yellow "programmable" key.
* On TV remotes, acts as a contextual/programmable key. */
0,
/** Blue "programmable" key.
* On TV remotes, acts as a contextual/programmable key. */
0,
/** App switch key.
* Should bring up the application switcher dialog. */
0,
/** Generic Game Pad Button #1.*/
0,
/** Generic Game Pad Button #2.*/
0,
/** Generic Game Pad Button #3.*/
0,
/** Generic Game Pad Button #4.*/
0,
/** Generic Game Pad Button #5.*/
0,
/** Generic Game Pad Button #6.*/
0,
/** Generic Game Pad Button #7.*/
0,
/** Generic Game Pad Button #8.*/
0,
/** Generic Game Pad Button #9.*/
0,
/** Generic Game Pad Button #10.*/
0,
/** Generic Game Pad Button #11.*/
0,
/** Generic Game Pad Button #12.*/
0,
/** Generic Game Pad Button #13.*/
0,
/** Generic Game Pad Button #14.*/
0,
/** Generic Game Pad Button #15.*/
0,
/** Generic Game Pad Button #16.*/
0,
/** Language Switch key.
* Toggles the current input language such as switching between English and Japanese on
* a QWERTY keyboard. On some devices, the same function may be performed by
* pressing Shift+Spacebar. */
0,
/** Manner Mode key.
* Toggles silent or vibrate mode on and off to make the device behave more politely
* in certain settings such as on a crowded train. On some devices, the key may only
* operate when long-pressed. */
0,
/** 3D Mode key.
* Toggles the display between 2D and 3D mode. */
0,
/** Contacts special function key.
* Used to launch an address book application. */
0,
/** Calendar special function key.
* Used to launch a calendar application. */
0,
/** Music special function key.
* Used to launch a music player application. */
0,
/** Calculator special function key.
* Used to launch a calculator application. */
0,
/** Japanese full-width / half-width key. */
0,
/** Japanese alphanumeric key. */
0,
/** Japanese non-conversion key. */
0,
/** Japanese conversion key. */
0,
/** Japanese katakana / hiragana key. */
146,
/** Japanese Yen key. */
137,
/** Japanese Ro key. */
0,
/** Japanese kana key. */
0,
};
class AndroidKeyboard : public KeyboardDeviceTemplate<int>
{
public:
AndroidKeyboard(int maple_port = 0) : KeyboardDeviceTemplate(maple_port, "Android")
{
_unique_id = "android_keyboard";
if (!find_mapping())
input_mapper = getDefaultMapping();
}
protected:
u8 convert_keycode(int keycode) override
{
if (keycode < 0 || keycode >= ARRAY_SIZE(AndroidKeycodes))
return 0;
else
return AndroidKeycodes[keycode];
}
};

View File

@ -0,0 +1,70 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "rend/boxart/http_client.h"
namespace http {
static jobject HttpClient;
static jmethodID openUrlMid;
void init() {
}
int get(const std::string &url, std::vector<u8> &content, std::string &contentType) {
JNIEnv *env = jvm_attacher.getEnv();
jstring jurl = env->NewStringUTF(url.c_str());
jclass byteArrayClass = env->FindClass("[B");
jobjectArray contentOut = env->NewObjectArray(1, byteArrayClass, NULL);
jclass stringClass = env->FindClass("java/lang/String");
jobjectArray contentTypeOut = env->NewObjectArray(1, stringClass, NULL);
int httpStatus = env->CallIntMethod(HttpClient, openUrlMid, jurl, contentOut,
contentTypeOut);
jbyteArray jcontent = (jbyteArray)env->GetObjectArrayElement(contentOut, 0);
if (jcontent != nullptr) {
int len = env->GetArrayLength(jcontent);
content.resize(len);
env->GetByteArrayRegion(jcontent, 0, len, (jbyte *)content.data());
env->DeleteLocalRef(jcontent);
}
jstring jcontentType = (jstring)env->GetObjectArrayElement(contentTypeOut, 0);
if (jcontentType != nullptr) {
const char *data = env->GetStringUTFChars(jcontentType, 0);
contentType = data;
env->ReleaseStringUTFChars(jcontentType, data);
env->DeleteLocalRef(jcontentType);
}
env->DeleteLocalRef(contentTypeOut);
env->DeleteLocalRef(contentOut);
env->DeleteLocalRef(jurl);
return httpStatus;
}
void term() {
}
}
extern "C" JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_HttpClient_nativeInit(JNIEnv *env, jobject obj)
{
http::HttpClient = env->NewGlobalRef(obj);
http::openUrlMid = env->GetMethodID(env->GetObjectClass(obj), "openUrl", "(Ljava/lang/String;[[B[Ljava/lang/String;)I");
}

View File

@ -0,0 +1,55 @@
/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#import <Foundation/Foundation.h>
#include "rend/boxart/http_client.h"
namespace http {
int get(const std::string& url, std::vector<u8>& content, std::string& contentType)
{
NSString *nsurl = [NSString stringWithCString:url.c_str()
encoding:[NSString defaultCStringEncoding]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:nsurl]];
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:urlRequest
returningResponse:&response
error:&error];
if (error != nil)
return 500;
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.MIMEType != nil)
contentType = std::string([httpResponse.MIMEType UTF8String]);
else
contentType.clear();
content.clear();
content.insert(content.begin(), (const u8 *)[data bytes], (const u8 *)[data bytes] + [data length]);
return [httpResponse statusCode];
}
void init() {
}
void term() {
}
}

Some files were not shown because too many files have changed in this diff Show More