Merge remote-tracking branch 'origin/dev'
This commit is contained in:
commit
d85baba1d3
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
||||
|
|
|
@ -173,3 +173,5 @@ private:
|
|||
bool renderTimeout = false;
|
||||
};
|
||||
extern Emulator emu;
|
||||
|
||||
int getGamePlatform(const char *path);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -166,3 +166,4 @@ struct InputDescriptors
|
|||
};
|
||||
|
||||
extern InputDescriptors *NaomiGameInputs;
|
||||
extern bool atomiswaveForceFeedback;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
};
|
|
@ -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__)
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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, ¤tImage);
|
||||
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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[])
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
};
|
|
@ -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");
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue